pantdipendra commited on
Commit
d8a4b22
·
verified ·
1 Parent(s): a012e24
Files changed (1) hide show
  1. index.html +1249 -19
index.html CHANGED
@@ -1,19 +1,1249 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Secondary Use of Health Records Navigator</title>
7
+ <style>
8
+ /* Color-blind–safe palette */
9
+ :root {
10
+ --bg: #0b1020;
11
+ --ink: #eaf0ff;
12
+ --muted: #a6b0d4;
13
+ --card: #121933;
14
+ --accent: #0072b2; /* blue */
15
+ --accent2: #56b4e9; /* sky blue */
16
+
17
+ /* Status colors */
18
+ --ok: #009e73; /* bluish green */
19
+ --warn: #f0e442; /* yellow */
20
+ --no: #d55e00; /* vermillion */
21
+
22
+ --br: 14px;
23
+ }
24
+ * {
25
+ box-sizing: border-box;
26
+ }
27
+ body {
28
+ margin: 0;
29
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial,
30
+ sans-serif;
31
+ color: var(--ink);
32
+ background: radial-gradient(
33
+ 900px 500px at 20% -10%,
34
+ #1b2656,
35
+ transparent 60%
36
+ )
37
+ no-repeat,
38
+ var(--bg);
39
+ }
40
+ header {
41
+ padding: 22px 16px 8px;
42
+ text-align: center;
43
+ }
44
+ h1 {
45
+ margin: 0 0 4px;
46
+ font-size: clamp(20px, 2.2vw, 30px);
47
+ font-weight: 800;
48
+ }
49
+ header p {
50
+ margin: 0;
51
+ color: var(--muted);
52
+ }
53
+ .wrap {
54
+ max-width: 980px;
55
+ margin: 14px auto 80px;
56
+ padding: 0 14px;
57
+ }
58
+ .card {
59
+ background: linear-gradient(
60
+ 180deg,
61
+ rgba(255, 255, 255, 0.04),
62
+ rgba(255, 255, 255, 0.02)
63
+ );
64
+ border: 1px solid rgba(255, 255, 255, 0.1);
65
+ border-radius: var(--br);
66
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
67
+ }
68
+ .section {
69
+ padding: 16px;
70
+ border-bottom: 1px dashed rgba(255, 255, 255, 0.08);
71
+ transition: background 0.2s ease, border-color 0.2s ease,
72
+ box-shadow 0.2s ease, transform 0.2s ease;
73
+ will-change: transform, box-shadow;
74
+ }
75
+ .section:last-child {
76
+ border-bottom: 0;
77
+ }
78
+ /* Hover highlight for any top-level section */
79
+ .card .section:hover,
80
+ .card .section:focus-within {
81
+ background: linear-gradient(
82
+ 180deg,
83
+ rgba(22, 30, 60, 0.92),
84
+ rgba(18, 26, 52, 0.92)
85
+ );
86
+ border-color: rgba(86, 180, 233, 0.35);
87
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
88
+ transform: translateY(-1px);
89
+ }
90
+
91
+ /* sticky nav */
92
+ .topnav {
93
+ position: sticky;
94
+ top: 0;
95
+ z-index: 5;
96
+ background: rgba(11, 16, 32, 0.7);
97
+ backdrop-filter: blur(8px);
98
+ padding: 10px 14px;
99
+ }
100
+ .toprow {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 10px;
104
+ }
105
+ .progress {
106
+ height: 10px;
107
+ border-radius: 999px;
108
+ background: #17224a;
109
+ overflow: hidden;
110
+ border: 1px solid rgba(255, 255, 255, 0.12);
111
+ flex: 1;
112
+ }
113
+ .progress > span {
114
+ display: block;
115
+ height: 100%;
116
+ width: 0;
117
+ background: linear-gradient(90deg, var(--ok));
118
+ transition: width 0.6s ease;
119
+ }
120
+ .clear {
121
+ margin-left: auto;
122
+ border: 1px solid rgba(255, 255, 255, 0.2);
123
+ background: rgba(255, 255, 255, 0.06);
124
+ color: #fff;
125
+ border-radius: 10px;
126
+ padding: 8px 12px;
127
+ cursor: pointer;
128
+ transition: transform 0.15s ease, background 0.15s ease;
129
+ }
130
+ .clear:hover {
131
+ background: rgba(255, 255, 255, 0.12);
132
+ transform: translateY(-1px);
133
+ }
134
+ .graph {
135
+ display: flex;
136
+ gap: 8px;
137
+ justify-content: center;
138
+ margin-top: 10px;
139
+ flex-wrap: wrap;
140
+ }
141
+ .dot {
142
+ position: relative;
143
+ display: grid;
144
+ place-items: center;
145
+ width: 28px;
146
+ height: 28px;
147
+ border-radius: 50%;
148
+ background: #101a3f;
149
+ border: 1px solid rgba(255, 255, 255, 0.18);
150
+ color: #cfe0ff;
151
+ font-weight: 800;
152
+ opacity: 0.45;
153
+ transition: 0.2s;
154
+ will-change: transform, box-shadow;
155
+ }
156
+ .dot.unlocked {
157
+ opacity: 1;
158
+ cursor: pointer;
159
+ }
160
+ .dot.active {
161
+ outline: 2px solid var(--accent);
162
+ }
163
+ .dot.unlocked:hover {
164
+ transform: translateY(-2px) scale(1.05);
165
+ border-color: var(--accent2);
166
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
167
+ }
168
+ .dot.done {
169
+ background: var(--ok);
170
+ color: #ffffff;
171
+ border-color: transparent;
172
+ }
173
+ .dot.done::after {
174
+ content: "✓";
175
+ position: absolute;
176
+ right: -4px;
177
+ bottom: -6px;
178
+ font-size: 12px;
179
+ background: var(--ok);
180
+ color: #fff;
181
+ border-radius: 6px;
182
+ padding: 0 3px;
183
+ }
184
+
185
+ /* purpose */
186
+ .purpose {
187
+ display: grid;
188
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
189
+ gap: 10px;
190
+ }
191
+ .option {
192
+ padding: 14px;
193
+ border-radius: 12px;
194
+ border: 1px solid rgba(255, 255, 255, 0.12);
195
+ background: rgba(255, 255, 255, 0.02);
196
+ cursor: pointer;
197
+ user-select: none;
198
+ transition: 0.2s transform, 0.2s border-color, 0.2s background,
199
+ box-shadow 0.2s ease;
200
+ }
201
+ .option:hover {
202
+ transform: translateY(-2px);
203
+ border-color: rgba(255, 255, 255, 0.22);
204
+ box-shadow: 0 10px 26px rgba(0, 0, 0, 0.25);
205
+ }
206
+ .option.active {
207
+ outline: 2px solid var(--accent);
208
+ background: rgba(86, 180, 233, 0.12);
209
+ }
210
+ .option input {
211
+ display: none;
212
+ }
213
+ .tag {
214
+ font-size: 0.75rem;
215
+ padding: 2px 8px;
216
+ color: var(--muted);
217
+ }
218
+ .gdpr {
219
+ color: #c6ede2;
220
+ }
221
+
222
+ /* steps */
223
+ .timeline {
224
+ padding: 10px;
225
+ }
226
+ .step {
227
+ margin: 12px 6px;
228
+ border-radius: 14px;
229
+ border: 1px solid rgba(255, 255, 255, 0.14);
230
+ background: linear-gradient(
231
+ 180deg,
232
+ rgba(16, 22, 44, 0.86),
233
+ rgba(14, 20, 38, 0.86)
234
+ );
235
+ overflow: hidden;
236
+ animation: pop 0.35s ease both;
237
+ transition: background 0.2s ease, border-color 0.2s ease,
238
+ box-shadow 0.2s ease, transform 0.2s ease;
239
+ will-change: transform, box-shadow;
240
+ }
241
+ @keyframes pop {
242
+ from {
243
+ opacity: 0;
244
+ transform: translateY(8px) scale(0.98);
245
+ }
246
+ to {
247
+ opacity: 1;
248
+ transform: translateY(0) scale(1);
249
+ }
250
+ }
251
+ /* Hover highlight for each step block */
252
+ .step:hover,
253
+ .step:focus-within {
254
+ transform: translateY(-2px);
255
+ border-color: rgba(86, 180, 233, 0.35);
256
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
257
+ background: linear-gradient(
258
+ 180deg,
259
+ rgba(20, 28, 56, 0.92),
260
+ rgba(18, 26, 52, 0.92)
261
+ );
262
+ }
263
+
264
+ .head {
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: space-between;
268
+ padding: 14px 16px;
269
+ }
270
+ .title {
271
+ display: flex;
272
+ align-items: center;
273
+ gap: 10px;
274
+ }
275
+ .num {
276
+ width: 32px;
277
+ height: 32px;
278
+ border-radius: 50%;
279
+ display: grid;
280
+ place-items: center;
281
+ background: linear-gradient(180deg, #27336a, #202a56);
282
+ border: 1px solid rgba(255, 255, 255, 0.18);
283
+ font-weight: 800;
284
+ color: #ffffff;
285
+ transition: background 0.2s ease, border-color 0.2s ease;
286
+ }
287
+ .badge {
288
+ font-size: 0.85rem;
289
+ padding: 4px 8px;
290
+ border-radius: 999px;
291
+ border: 1px solid rgba(255, 255, 255, 0.12);
292
+ color: #cde6ff;
293
+ background: transparent;
294
+ transition: background 0.2s ease, color 0.2s ease,
295
+ border-color 0.2s ease;
296
+ }
297
+ .step.done {
298
+ border-color: rgba(0, 158, 115, 0.55);
299
+ box-shadow: 0 0 0 1px rgba(0, 158, 115, 0.25) inset;
300
+ }
301
+ .step.done .num {
302
+ background: var(--ok);
303
+ border-color: transparent;
304
+ color: #ffffff;
305
+ }
306
+ .step.done .badge {
307
+ background: var(--ok);
308
+ color: #ffffff;
309
+ border-color: transparent;
310
+ }
311
+ .body {
312
+ padding: 0 16px 14px;
313
+ }
314
+ .list {
315
+ list-style: none;
316
+ margin: 0;
317
+ padding: 0;
318
+ }
319
+ .li {
320
+ display: grid;
321
+ grid-template-columns: auto 1fr auto;
322
+ gap: 10px;
323
+ align-items: flex-start;
324
+ padding: 10px;
325
+ border: 1px dashed rgba(255, 255, 255, 0.14);
326
+ border-radius: 10px;
327
+ margin: 8px 0;
328
+ background: rgba(255, 255, 255, 0.02);
329
+ transition: background 0.15s ease, border-color 0.15s ease,
330
+ transform 0.15s ease;
331
+ }
332
+ .li:hover {
333
+ background: rgba(86, 180, 233, 0.08);
334
+ border-color: rgba(86, 180, 233, 0.35);
335
+ transform: translateY(-1px);
336
+ }
337
+ .letters {
338
+ font-weight: 700;
339
+ color: #cbe0ff;
340
+ width: 24px;
341
+ }
342
+ .li a {
343
+ color: var(--accent2);
344
+ text-decoration: none;
345
+ }
346
+ .li a:hover {
347
+ text-decoration: underline;
348
+ }
349
+ .req {
350
+ color: var(--no);
351
+ margin-left: 6px;
352
+ }
353
+ .opt {
354
+ color: #a9b6e6;
355
+ font-size: 0.85rem;
356
+ }
357
+ .fold {
358
+ display: none;
359
+ }
360
+ .toggle {
361
+ background: transparent;
362
+ border: 1px solid rgba(255, 255, 255, 0.18);
363
+ color: #dfe7ff;
364
+ border-radius: 10px;
365
+ padding: 6px 10px;
366
+ cursor: pointer;
367
+ transition: background 0.15s ease, transform 0.15s ease,
368
+ border-color 0.15s ease;
369
+ }
370
+ .toggle:hover {
371
+ background: rgba(86, 180, 233, 0.12);
372
+ transform: translateY(-1px);
373
+ border-color: rgba(86, 180, 233, 0.35);
374
+ }
375
+
376
+ /* animated checkbox */
377
+ .chk {
378
+ position: absolute;
379
+ opacity: 0;
380
+ }
381
+ .box {
382
+ width: 22px;
383
+ height: 22px;
384
+ border-radius: 6px;
385
+ border: 1px solid rgba(255, 255, 255, 0.22);
386
+ background: #0f1533;
387
+ }
388
+ .tick {
389
+ stroke: #cfe0ff;
390
+ stroke-width: 3;
391
+ fill: none;
392
+ stroke-dasharray: 30;
393
+ stroke-dashoffset: 30;
394
+ transition: stroke-dashoffset 0.32s ease;
395
+ }
396
+ .chk:checked + .box {
397
+ background: #1e396e;
398
+ border-color: var(--accent);
399
+ }
400
+ .chk:checked + .box .tick {
401
+ stroke-dashoffset: 0;
402
+ }
403
+
404
+ .tiny {
405
+ font-size: 0.82rem;
406
+ color: var(--muted);
407
+ }
408
+ .center {
409
+ display: grid;
410
+ place-items: center;
411
+ }
412
+ .hidden {
413
+ display: none !important;
414
+ }
415
+
416
+ /* flow buttons */
417
+ .flow {
418
+ position: fixed;
419
+ left: 50%;
420
+ transform: translateX(-50%);
421
+ bottom: 16px;
422
+ width: min(960px, 92vw);
423
+ background: #0f1533;
424
+ border: 1px solid rgba(255, 255, 255, 0.14);
425
+ border-radius: 14px;
426
+ padding: 10px 12px;
427
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
428
+ }
429
+ .q {
430
+ display: flex;
431
+ align-items: center;
432
+ justify-content: space-between;
433
+ gap: 12px;
434
+ flex-wrap: wrap;
435
+ }
436
+ .q strong {
437
+ font-size: 1rem;
438
+ }
439
+ .btns {
440
+ display: flex;
441
+ gap: 10px;
442
+ }
443
+ .cta {
444
+ font-weight: 800;
445
+ border-radius: 12px;
446
+ border: 1px solid rgba(255, 255, 255, 0.2);
447
+ padding: 12px 16px;
448
+ cursor: pointer;
449
+ transition: transform 0.15s ease, filter 0.15s ease;
450
+ }
451
+ .cta:hover {
452
+ transform: translateY(-1px);
453
+ filter: brightness(1.05);
454
+ }
455
+ .cta.y {
456
+ background: linear-gradient(180deg, var(--ok));
457
+ }
458
+ .cta.n {
459
+ background: linear-gradient(180deg, var(--no));
460
+ }
461
+
462
+ /* finish */
463
+ .finish {
464
+ display: none;
465
+ text-align: center;
466
+ padding: 18px;
467
+ }
468
+ .finish.show {
469
+ display: block;
470
+ animation: pop 0.35s ease both;
471
+ }
472
+ .confetti {
473
+ position: relative;
474
+ height: 0;
475
+ }
476
+ .confetti i {
477
+ position: absolute;
478
+ width: 8px;
479
+ height: 14px;
480
+ background: var(--accent);
481
+ top: -10px;
482
+ left: 50%;
483
+ animation: drop 1.2s ease-out forwards;
484
+ transform: translateX(-50%);
485
+ }
486
+ @keyframes drop {
487
+ to {
488
+ top: -90px;
489
+ transform: translateX(calc(-50% + var(--dx))) rotate(120deg);
490
+ }
491
+ }
492
+ </style>
493
+ </head>
494
+ <body>
495
+ <header>
496
+ <h1>Secondary Use of Health Records — Flow Navigator</h1>
497
+ </header>
498
+
499
+ <div class="topnav">
500
+ <div class="toprow">
501
+ <div class="progress" aria-label="progress">
502
+ <span id="pbar"></span>
503
+ </div>
504
+ <button id="clearBtn" class="clear" title="Clear all progress">
505
+ Clear
506
+ </button>
507
+ </div>
508
+ <div id="dots" class="graph" aria-label="step navigation"></div>
509
+ </div>
510
+
511
+ <div class="wrap card">
512
+ <!-- PURPOSE -->
513
+ <div class="section">
514
+ <h2 style="margin: 0 0 6px">
515
+ What is the primary purpose of secondary use ?
516
+ </h2>
517
+ <div class="purpose" id="purposeList"></div>
518
+ </div>
519
+
520
+ <!-- Anonymized outcome -->
521
+ <div class="section hidden" id="anonMsg">
522
+ <div class="center">
523
+ <svg width="72" height="72" viewBox="0 0 24 24" aria-hidden="true">
524
+ <path
525
+ fill="#79ffb7"
526
+ d="M12 1 3 5v6c0 5 3.8 9.7 9 11c5.2-1.3 9-6 9-11V5z"
527
+ />
528
+ </svg>
529
+ <p class="tiny" style="margin: 8px 0 0">
530
+ <b>GDPR does not apply</b> to truly anonymized data (<a
531
+ href="https://gdpr.eu/recital-26/"
532
+ target="_blank"
533
+ >Recital 26</a
534
+ >).
535
+ </p>
536
+ </div>
537
+ </div>
538
+
539
+ <!-- TIMELINE -->
540
+ <div class="section timeline" id="timeline"></div>
541
+
542
+ <!-- Finish -->
543
+ <div id="finish" class="finish" aria-live="polite">
544
+ <div class="confetti" id="confetti"></div>
545
+ <h2 id="finishTitle" style="margin: 6px 0 0">End</h2>
546
+ <p id="finishSub" class="tiny"></p>
547
+ </div>
548
+ </div>
549
+
550
+ <!-- Flow controller -->
551
+ <div id="flow" class="flow hidden" role="group" aria-label="flow question">
552
+ <div class="q">
553
+ <strong id="qtxt">Ready?</strong>
554
+ <div class="btns">
555
+ <button class="cta y" id="yesBtn" aria-label="Yes">Yes</button>
556
+ <button class="cta n" id="noBtn" aria-label="No">No</button>
557
+ </div>
558
+ </div>
559
+ </div>
560
+
561
+ <script>
562
+ /* ------------ Data ------------ */
563
+ const PURPOSES = [
564
+ {
565
+ id: "research",
566
+ title: "Health Research (HRA)",
567
+ blurb: "Scientific knowledge.",
568
+ refs: [],
569
+ },
570
+ {
571
+ id: "quality",
572
+ title: "Quality Improvement",
573
+ blurb: "Improve your own service.",
574
+ refs: [],
575
+ },
576
+ {
577
+ id: "statistics",
578
+ title: "Statistics / Official",
579
+ blurb: "Org/regional/national stats.",
580
+ refs: [],
581
+ },
582
+ {
583
+ id: "innovation",
584
+ title: "Innovation / AI",
585
+ blurb: "Tool/algorithm/model.",
586
+ refs: [],
587
+ },
588
+ {
589
+ id: "anon",
590
+ title: "Exclusively Anonymized Data",
591
+ blurb: "No reasonable reidentification risk.",
592
+ refs: [],
593
+ },
594
+ ];
595
+
596
+ const STEPS = [
597
+ {
598
+ num: 1,
599
+ key: "classification",
600
+ title: "Project classification",
601
+ items: [
602
+ { k: "1a", label: "(a) Confirm pathway.", req: true },
603
+ {
604
+ k: "1b",
605
+ label: "(b) Controller / joint controller — Art. 4(7).",
606
+ req: true,
607
+ },
608
+ { k: "1c", label: "(c) 1-page summary.", req: true },
609
+ { k: "1d", label: "(d) Special categories? — Art. 9.", req: true },
610
+ { k: "1e", label: "(e) Vulnerable groups screened.", req: true },
611
+ {
612
+ k: "1f",
613
+ label: "(f) DPIA screening / DPIA — Art. 35.",
614
+ req: true,
615
+ },
616
+ { k: "1g", label: "(g) Transfers (if any) — Chap. V.", req: false },
617
+ {
618
+ k: "1h",
619
+ label: "(h) Plan anonymization/pseudonymization — Recital 26.",
620
+ req: false,
621
+ },
622
+ ],
623
+ },
624
+ {
625
+ num: 2,
626
+ key: "legal",
627
+ title: "Legal basis & consent",
628
+ items: [
629
+ { k: "2a", label: "(a) Art. 6 lawful basis.", req: true },
630
+ { k: "2b", label: "(b) Art. 9(2) condition.", req: true },
631
+ {
632
+ k: "2d",
633
+ label: "(d) Purpose limitation — Art. 5(1)(b).",
634
+ req: true,
635
+ },
636
+ {
637
+ k: "2c",
638
+ label: "(c) Consent quality/withdrawal (if used).",
639
+ req: false,
640
+ },
641
+ {
642
+ k: "2e",
643
+ label: "(e) Research/statistics safeguards — Art. 89.",
644
+ req: false,
645
+ },
646
+ ],
647
+ },
648
+ {
649
+ num: 3,
650
+ key: "ethics",
651
+ title: "Approvals",
652
+ items: [
653
+ {
654
+ k: "3a",
655
+ label: "(a) Research: IRB/HRA §§2–4, §9, §33.",
656
+ req: true,
657
+ showFor: ["research"],
658
+ },
659
+ {
660
+ k: "3b",
661
+ label: "(b) Statistics: HRL §19–19h.",
662
+ req: true,
663
+ showFor: ["statistics"],
664
+ },
665
+ {
666
+ k: "3c",
667
+ label: "(c) QI/Other: internal governance.",
668
+ req: true,
669
+ showFor: ["quality", "other"],
670
+ },
671
+ {
672
+ k: "3d",
673
+ label: "(d) Innovation/AI: risk review / AI Act.",
674
+ req: true,
675
+ showFor: ["innovation"],
676
+ },
677
+ {
678
+ k: "3e",
679
+ label: "(e) RoPA + notices — Arts. 30, 13/14.",
680
+ req: true,
681
+ },
682
+ ],
683
+ },
684
+ {
685
+ num: 4,
686
+ key: "access",
687
+ title: "Data access & contracts",
688
+ items: [
689
+ { k: "4a", label: "(a) Access route confirmed.", req: true },
690
+ { k: "4b", label: "(b) DPA / sharing / DUA ready.", req: true },
691
+ { k: "4c", label: "(c) Minimize data — Art. 5(1)(c).", req: true },
692
+ { k: "4d", label: "(d) Transfers (if any) — Chap. V.", req: false },
693
+ ],
694
+ },
695
+ {
696
+ num: 5,
697
+ key: "security",
698
+ title: "Security & privacy",
699
+ items: [
700
+ { k: "5a", label: "(a) TOMs — Art. 32.", req: true },
701
+ { k: "5b", label: "(b) Pseudonymize; separate keys.", req: true },
702
+ { k: "5c", label: "(c) DPIA outcomes signed.", req: true },
703
+ { k: "5d", label: "(d) Breach plan — 33/34.", req: true },
704
+ ],
705
+ },
706
+ {
707
+ num: 6,
708
+ key: "quality",
709
+ title: "Data minimization and quality",
710
+ items: [
711
+ { k: "6a", label: "(a) Provenance & dictionary.", req: true },
712
+ {
713
+ k: "6b",
714
+ label: "(b) Validation / missing data plan.",
715
+ req: true,
716
+ },
717
+ ],
718
+ },
719
+ {
720
+ num: 7,
721
+ key: "analysis",
722
+ title: "Analysis & AI development",
723
+ items: [
724
+ { k: "7a", label: "(a) Extract matches approvals.", req: true },
725
+ {
726
+ k: "7c",
727
+ label: "(c) AI docs & bias checks (if AI).",
728
+ req: true,
729
+ showFor: ["innovation"],
730
+ },
731
+ ],
732
+ },
733
+ {
734
+ num: 8,
735
+ key: "compliance",
736
+ title: "Compliance monitoring and auditing",
737
+ items: [
738
+ { k: "8a", label: "(a) Internal audit.", req: true },
739
+ { k: "8b", label: "(b) Keep RoPA up to date.", req: true },
740
+ ],
741
+ },
742
+ {
743
+ num: 9,
744
+ key: "closeout",
745
+ title: "Dissemination, closeout, retention & deletion",
746
+ items: [
747
+ { k: "9a", label: "(a) Disclosure control OK.", req: true },
748
+ {
749
+ k: "9c",
750
+ label: "(c) Retention/deletion — Art. 5(1)(e).",
751
+ req: true,
752
+ },
753
+ ],
754
+ },
755
+ ];
756
+
757
+ /* Flow questions derived from the flowchart */
758
+ const FLOW = {
759
+ 1: {
760
+ q: "Project classification complete & documented?",
761
+ onYes: 2,
762
+ onNo: 1,
763
+ },
764
+ 2: { q: "Valid legal route and consent completed?", onYes: 3, onNo: 2 },
765
+ 3: {
766
+ q: "All ethical and regulatory approvals in place?",
767
+ onYes: 4,
768
+ onNo: 3,
769
+ },
770
+ 4: { q: "Data access route & contracts are ok?", onYes: 5, onNo: 4 },
771
+ 5: {
772
+ q: "Information security & privacy measures and risks controlled?",
773
+ onYes: 6,
774
+ onNo: 5,
775
+ },
776
+ 6: { q: "Proceed to analysis?", onYes: 7, onNo: 6 },
777
+ /* Step 7 has several conditional checks in order */
778
+ 7: [
779
+ { q: "Extract matches necessary approvals?", yes: 8, no: 3 },
780
+ { q: "Any new purpose?", yes: 1, no: "next" },
781
+ { q: "Any new approvals needed?", yes: 3, no: "next" },
782
+ { q: "Any new access/contracts?", yes: 4, no: 8 },
783
+ ],
784
+ 8: { q: "Any gaps found?", onYes: 4, onNo: 9 },
785
+ 9: { q: "Retain/reuse beyond plan?", onYes: 1, onNo: "end" },
786
+ };
787
+
788
+ /* ------------ State ------------ */
789
+ const STORAGE = "suhr_flow_v1";
790
+ const $ = (s) => document.querySelector(s);
791
+ const $$ = (s) => Array.from(document.querySelectorAll(s));
792
+ const state = {
793
+ purpose: null,
794
+ checks: {},
795
+ savedAt: null,
796
+ visible: 0,
797
+ step: 0,
798
+ subQ: 0,
799
+ reachedEnd: false, // track if user ever reached the end screen
800
+ };
801
+
802
+ /* ------------ Purpose UI ------------ */
803
+ function renderPurposes() {
804
+ const host = $("#purposeList");
805
+ host.innerHTML = "";
806
+ PURPOSES.forEach((p) => {
807
+ const el = document.createElement("label");
808
+ el.className = "option";
809
+ el.innerHTML = `
810
+ <input type="radio" name="purpose" value="${p.id}">
811
+ <div style="display:flex;justify-content:space-between;gap:8px">
812
+ <div><b>${p.title}</b><div class="tiny" style="margin-top:4px">${
813
+ p.blurb
814
+ }</div></div>
815
+ <span class="tag gdpr">${
816
+ p.id === "anon" ? "No GDPR" : "GDPR"
817
+ }</span>
818
+ </div>`;
819
+ el.addEventListener("click", () => {
820
+ state.purpose = p.id;
821
+ save(true);
822
+ $("#anonMsg").classList.toggle("hidden", state.purpose !== "anon");
823
+ if (p.id === "anon") {
824
+ hideFlow();
825
+ renderDots(0);
826
+ $("#timeline").innerHTML = "";
827
+ $("#finish").classList.remove("show");
828
+ state.reachedEnd = false;
829
+ return;
830
+ }
831
+ // start at step 1
832
+ state.visible = 1;
833
+ state.step = 1;
834
+ state.subQ = 0;
835
+ state.reachedEnd = false;
836
+ save(true);
837
+ $("#finish").classList.remove("show");
838
+ renderDots(state.visible);
839
+ renderTimeline();
840
+ showFlow();
841
+ askCurrent(); // auto-expand step 1
842
+ });
843
+ host.appendChild(el);
844
+ });
845
+ if (state.purpose) {
846
+ $$('input[name="purpose"]').forEach((i) => {
847
+ if (i.value === state.purpose) {
848
+ i.checked = true;
849
+ i.closest(".option").classList.add("active");
850
+ }
851
+ });
852
+ $("#anonMsg").classList.toggle("hidden", state.purpose !== "anon");
853
+ }
854
+ }
855
+
856
+ /* ------------ Timeline (accordion) ------------ */
857
+ function renderTimeline() {
858
+ const host = $("#timeline");
859
+ host.innerHTML = "";
860
+ if (!state.purpose || state.purpose === "anon") return;
861
+ const v = state.visible || 1;
862
+ for (let i = 0; i < v; i++) addStep(host, STEPS[i], i + 1);
863
+ expandDetailsForStep(state.step);
864
+ }
865
+
866
+ function addStep(host, step, idx) {
867
+ const done = isStepComplete(step);
868
+ const sec = document.createElement("section");
869
+ sec.className = "step" + (done ? " done" : "");
870
+ sec.dataset.step = step.key;
871
+ sec.innerHTML = `
872
+ <div class="head">
873
+ <div class="title">
874
+ <span class="num">${step.num}</span>
875
+ <div><b>${step.title}</b></div>
876
+ </div>
877
+ <div style="display:flex;gap:8px;align-items:center">
878
+ <span class="badge" id="badge_${step.key}">${
879
+ done ? "Complete ✓" : "In progress"
880
+ }</span>
881
+ <button class="toggle" data-fold="${step.key}">Details</button>
882
+ </div>
883
+ </div>
884
+ <div class="body fold" id="fold_${step.key}">
885
+ <ul class="list"></ul>
886
+ </div>`;
887
+ const ul = sec.querySelector(".list");
888
+ step.items.forEach((it) => {
889
+ if (it.showFor && !it.showFor.includes(state.purpose)) return;
890
+ const checked = !!state.checks[step.key]?.[it.k];
891
+ const li = document.createElement("li");
892
+ li.className = "li";
893
+ const id = `${step.key}_${it.k}`;
894
+ li.innerHTML = `
895
+ <div class="letters">${it.k}</div>
896
+ <label for="${id}">${it.label}${
897
+ it.req
898
+ ? ` <span class='req'>★</span>`
899
+ : " <span class='opt'>(opt)</span>"
900
+ }</label>
901
+ <div style="display:flex;align-items:center;gap:8px">
902
+ <input class="chk" type="checkbox" id="${id}" ${
903
+ checked ? "checked" : ""
904
+ }/>
905
+ <svg class="box" viewBox="0 0 24 24" aria-hidden="true"><path class="tick" d="M5 13l4 4L19 7"/></svg>
906
+ </div>`;
907
+ li.querySelector(".chk").addEventListener("change", (e) => {
908
+ (state.checks[step.key] ??= {})[it.k] = e.target.checked;
909
+ save(true);
910
+
911
+ const nowDone = isStepComplete(step);
912
+ $("#badge_" + step.key).textContent = nowDone
913
+ ? "Complete ✓"
914
+ : "In progress";
915
+ if (nowDone && !sec.classList.contains("done")) {
916
+ sec.classList.add("done");
917
+ if (idx === 1) advanceToStep(2); // auto show Step 2 when Step 1 completes
918
+ } else if (!nowDone) {
919
+ sec.classList.remove("done");
920
+ }
921
+
922
+ renderDots(state.visible); // refresh top dots' done state
923
+ updateProgress();
924
+
925
+ // If the end screen is visible (or was reached before), update/show its message when all steps are complete.
926
+ if ($("#finish").classList.contains("show")) {
927
+ refreshFinishBanner();
928
+ } else if (state.reachedEnd && allStepsComplete()) {
929
+ finish();
930
+ }
931
+ });
932
+ ul.appendChild(li);
933
+ });
934
+
935
+ // "Details" toggle
936
+ sec.querySelector(".toggle").addEventListener("click", () => {
937
+ const pane = document.getElementById("fold_" + step.key);
938
+ pane.classList.toggle("fold");
939
+ });
940
+
941
+ host.appendChild(sec);
942
+ }
943
+
944
+ /* Expand current step details and collapse others while asking each question */
945
+ function expandDetailsForStep(stepNum) {
946
+ if (!stepNum) return;
947
+ // collapse all
948
+ $$("#timeline .body").forEach((pane) => pane.classList.add("fold"));
949
+ const current = STEPS[stepNum - 1];
950
+ if (!current) return;
951
+ const pane = document.getElementById("fold_" + current.key);
952
+ if (pane) pane.classList.remove("fold");
953
+ }
954
+
955
+ /* ------------ Dots / progress ------------ */
956
+ function renderDots(visible) {
957
+ const d = $("#dots");
958
+ d.innerHTML = "";
959
+ for (let i = 1; i <= STEPS.length; i++) {
960
+ const stepObj = STEPS[i - 1];
961
+ const stepDone = isStepComplete(stepObj);
962
+ const dot = document.createElement("div");
963
+ dot.className =
964
+ "dot" +
965
+ (i <= visible ? " unlocked" : "") +
966
+ (i === state.step ? " active" : "") +
967
+ (stepDone ? " done" : "");
968
+ dot.textContent = i;
969
+ dot.setAttribute(
970
+ "aria-label",
971
+ `Step ${i}${stepDone ? " complete" : ""}`
972
+ );
973
+ if (i <= visible) {
974
+ dot.addEventListener("click", () => {
975
+ state.step = i;
976
+ state.subQ = 0;
977
+ save(true);
978
+ askCurrent();
979
+ scrollToStep(i);
980
+ highlightDot();
981
+ });
982
+ }
983
+ d.appendChild(dot);
984
+ }
985
+ updateProgress();
986
+ }
987
+ function highlightDot() {
988
+ $$("#dots .dot").forEach((el, idx) => {
989
+ el.classList.toggle("active", idx + 1 === state.step);
990
+ });
991
+ }
992
+ function scrollToStep(n) {
993
+ const el = document.querySelector(`.step:nth-of-type(${n})`);
994
+ if (!el) return;
995
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
996
+ el.animate(
997
+ [
998
+ { outlineColor: "#56b4e9", outlineWidth: "0px" },
999
+ { outlineColor: "#56b4e9", outlineWidth: "6px" },
1000
+ { outlineColor: "transparent", outlineWidth: "0px" },
1001
+ ],
1002
+ { duration: 800 }
1003
+ );
1004
+ }
1005
+
1006
+ /* ------------ Flow controller (Yes/No) ------------ */
1007
+ const flow = $("#flow"),
1008
+ qtxt = $("#qtxt"),
1009
+ yesBtn = $("#yesBtn"),
1010
+ noBtn = $("#noBtn");
1011
+
1012
+ function showFlow() {
1013
+ flow.classList.remove("hidden");
1014
+ }
1015
+ function hideFlow() {
1016
+ flow.classList.add("hidden");
1017
+ }
1018
+
1019
+ function askCurrent() {
1020
+ // Reset finish visibility text live region while navigating
1021
+ $("#finish").classList.remove("show");
1022
+
1023
+ if (state.purpose === "anon" || !state.step) {
1024
+ hideFlow();
1025
+ return;
1026
+ }
1027
+
1028
+ // Ensure the current step's checklist is expanded (and others collapsed)
1029
+ expandDetailsForStep(state.step);
1030
+
1031
+ // Step 7 special sequence
1032
+ if (state.step === 7) {
1033
+ const seq = FLOW[7];
1034
+ let idx = state.subQ;
1035
+ if (idx >= seq.length) {
1036
+ state.step = 8;
1037
+ state.subQ = 0;
1038
+ renderDots(state.visible);
1039
+ askCurrent();
1040
+ return;
1041
+ }
1042
+ const node = seq[idx];
1043
+ qtxt.textContent = node.q;
1044
+ yesBtn.onclick = () => {
1045
+ if (node.yes === "next") {
1046
+ state.subQ++;
1047
+ askCurrent();
1048
+ return;
1049
+ }
1050
+ goto(node.yes);
1051
+ };
1052
+ noBtn.onclick = () => {
1053
+ if (node.no === "next") {
1054
+ state.subQ++;
1055
+ askCurrent();
1056
+ return;
1057
+ }
1058
+ goto(node.no);
1059
+ };
1060
+ return;
1061
+ }
1062
+
1063
+ // Regular step question
1064
+ const node = FLOW[state.step];
1065
+ if (!node) {
1066
+ hideFlow();
1067
+ return;
1068
+ }
1069
+ qtxt.textContent = node.q;
1070
+ yesBtn.onclick = () => goto(node.onYes);
1071
+ noBtn.onclick = () => goto(node.onNo);
1072
+ }
1073
+
1074
+ function goto(dest) {
1075
+ if (dest === "end") {
1076
+ finish();
1077
+ return;
1078
+ }
1079
+ // move
1080
+ state.step = dest;
1081
+ state.subQ = 0;
1082
+ if (state.step > state.visible) state.visible = state.step; // grow visibility
1083
+ renderDots(state.visible);
1084
+ renderTimeline();
1085
+ askCurrent();
1086
+ scrollToStep(state.step);
1087
+ save(true);
1088
+ }
1089
+
1090
+ /* Called when step 1 completes to auto reveal step 2 */
1091
+ function advanceToStep(n) {
1092
+ if (state.visible < n) {
1093
+ state.visible = n;
1094
+ renderDots(state.visible);
1095
+ renderTimeline();
1096
+ scrollToStep(n);
1097
+ }
1098
+ state.step = n;
1099
+ state.subQ = 0;
1100
+ askCurrent();
1101
+ save(true);
1102
+ }
1103
+
1104
+ /* ------------ Helpers ------------ */
1105
+ function isStepComplete(step) {
1106
+ const d = state.checks[step.key] || {};
1107
+ const req = step.items.filter(
1108
+ (it) => !(it.showFor && !it.showFor.includes(state.purpose)) && it.req
1109
+ );
1110
+ return req.length ? req.every((it) => !!d[it.k]) : true;
1111
+ }
1112
+ function allStepsComplete() {
1113
+ if (!state.purpose || state.purpose === "anon") return false;
1114
+ return STEPS.every((s) => isStepComplete(s));
1115
+ }
1116
+ function updateProgress() {
1117
+ let total = 0,
1118
+ done = 0;
1119
+ STEPS.forEach((s) => {
1120
+ if (state.purpose === "anon" && s.num > 1) return;
1121
+ s.items.forEach((it) => {
1122
+ if (it.showFor && !it.showFor.includes(state.purpose)) return;
1123
+ if (it.req) {
1124
+ total++;
1125
+ if (state.checks[s.key]?.[it.k]) done++;
1126
+ }
1127
+ });
1128
+ });
1129
+ const pct = total ? Math.round((100 * done) / total) : 0;
1130
+ $("#pbar").style.width = pct + "%";
1131
+ }
1132
+
1133
+ /* ------------ Finish ------------ */
1134
+ function finish() {
1135
+ hideFlow();
1136
+ state.reachedEnd = true;
1137
+ save(true);
1138
+
1139
+ const ok = allStepsComplete();
1140
+ const finishEl = $("#finish");
1141
+ const titleEl = $("#finishTitle");
1142
+ const subEl = $("#finishSub");
1143
+ const confettiHost = $("#confetti");
1144
+
1145
+ if (ok) {
1146
+ titleEl.textContent = "End 🎉";
1147
+ subEl.textContent = "All flow checks passed.";
1148
+ confettiHost.innerHTML = "";
1149
+ confetti(24);
1150
+ } else {
1151
+ titleEl.textContent = "End";
1152
+ subEl.textContent = "";
1153
+ confettiHost.innerHTML = ""; // no confetti if not fully complete
1154
+ }
1155
+ finishEl.classList.add("show");
1156
+ }
1157
+
1158
+ function refreshFinishBanner() {
1159
+ // Update finish banner live without toggling visibility
1160
+ const ok = allStepsComplete();
1161
+ const titleEl = $("#finishTitle");
1162
+ const subEl = $("#finishSub");
1163
+ const confettiHost = $("#confetti");
1164
+
1165
+ if (ok) {
1166
+ titleEl.textContent = "End 🎉";
1167
+ subEl.textContent = "All flow checks passed.";
1168
+ confettiHost.innerHTML = "";
1169
+ confetti(24);
1170
+ } else {
1171
+ titleEl.textContent = "End";
1172
+ subEl.textContent = "";
1173
+ confettiHost.innerHTML = "";
1174
+ }
1175
+ }
1176
+
1177
+ function confetti(n) {
1178
+ const host = $("#confetti");
1179
+ host.innerHTML = "";
1180
+ for (let i = 0; i < n; i++) {
1181
+ const piece = document.createElement("i");
1182
+ piece.style.setProperty("--dx", Math.random() * 300 - 150 + "px");
1183
+ piece.style.left = 20 + Math.random() * 60 + "%";
1184
+ piece.style.background = i % 2 ? "#0072B2" : "#E69F00";
1185
+ piece.style.animationDelay = Math.random() * 0.4 + "s";
1186
+ host.appendChild(piece);
1187
+ }
1188
+ }
1189
+
1190
+ /* ------------ Save/Load ------------ */
1191
+ function save(quiet = false) {
1192
+ state.savedAt = Date.now();
1193
+ localStorage.setItem(STORAGE, JSON.stringify(state));
1194
+ if (!quiet) updateProgress();
1195
+ }
1196
+ function load() {
1197
+ try {
1198
+ const raw = localStorage.getItem(STORAGE);
1199
+ if (raw) Object.assign(state, JSON.parse(raw));
1200
+ } catch (e) {}
1201
+ }
1202
+
1203
+ /* ------------ Clear / Reset ------------ */
1204
+ function clearAll() {
1205
+ try {
1206
+ localStorage.removeItem(STORAGE);
1207
+ } catch (e) {}
1208
+ state.purpose = null;
1209
+ state.checks = {};
1210
+ state.savedAt = null;
1211
+ state.visible = 0;
1212
+ state.step = 0;
1213
+ state.subQ = 0;
1214
+ state.reachedEnd = false;
1215
+
1216
+ renderPurposes();
1217
+ renderDots(0);
1218
+ $("#timeline").innerHTML = "";
1219
+ $("#finish").classList.remove("show");
1220
+ $("#anonMsg").classList.add("hidden");
1221
+ hideFlow();
1222
+ updateProgress();
1223
+ }
1224
+
1225
+ /* ------------ Boot ------------ */
1226
+ load();
1227
+ renderPurposes();
1228
+ if (state.purpose && state.purpose !== "anon") {
1229
+ state.visible = state.visible || 1;
1230
+ state.step = state.step || 1;
1231
+ renderDots(state.visible);
1232
+ renderTimeline();
1233
+ showFlow();
1234
+ askCurrent();
1235
+ } else {
1236
+ hideFlow();
1237
+ }
1238
+ updateProgress();
1239
+
1240
+ // Clear button handler
1241
+ document.getElementById("clearBtn").addEventListener("click", () => {
1242
+ const ok = confirm(
1243
+ "Clear all saved progress and do a fresh start? This will remove your selections!"
1244
+ );
1245
+ if (ok) clearAll();
1246
+ });
1247
+ </script>
1248
+ </body>
1249
+ </html>