File size: 34,995 Bytes
84f2f65
 
 
 
 
1e55f99
84f2f65
 
1e55f99
 
84f2f65
 
1e55f99
 
 
84f2f65
 
 
1e55f99
 
84f2f65
1e55f99
a8dd995
84f2f65
 
1e55f99
84f2f65
1e55f99
 
 
 
 
 
 
 
 
84f2f65
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84f2f65
1e55f99
84f2f65
 
1e55f99
 
 
 
 
 
 
 
 
84f2f65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1e55f99
84f2f65
 
 
1e55f99
 
84f2f65
 
 
 
 
 
1e55f99
84f2f65
 
 
1e55f99
84f2f65
 
 
1e55f99
84f2f65
 
 
 
 
 
1e55f99
 
84f2f65
 
 
 
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84f2f65
 
 
 
 
 
 
 
 
 
1e55f99
 
 
 
 
84f2f65
 
1e55f99
84f2f65
 
1e55f99
84f2f65
 
 
 
 
a8dd995
1e55f99
84f2f65
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
6d8e506
 
 
 
 
 
 
 
 
 
 
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
bd01d05
 
1e55f99
 
bd01d05
1e55f99
bd01d05
 
 
 
 
 
1e55f99
 
 
 
 
 
 
 
 
2ce843d
 
 
 
1e55f99
 
 
 
 
 
 
 
84f2f65
 
1e55f99
 
 
 
 
 
 
 
 
275e3b0
1e55f99
275e3b0
1e55f99
 
 
 
 
 
 
84f2f65
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce843d
 
 
 
 
 
 
 
 
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce843d
1e55f99
 
 
 
 
2ce843d
 
 
 
 
 
 
 
 
 
 
 
 
1e55f99
2ce843d
1e55f99
 
84f2f65
 
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84f2f65
1e55f99
6d8e506
bd01d05
84f2f65
 
 
1e55f99
84f2f65
 
 
1e55f99
bd01d05
84f2f65
 
1e55f99
84f2f65
 
1e55f99
 
84f2f65
 
1e55f99
6d8e506
bd01d05
be6166b
1e55f99
84f2f65
 
 
 
be6166b
84f2f65
 
be6166b
 
 
 
 
 
 
dbffc3c
 
 
 
be6166b
 
 
 
 
 
 
 
84f2f65
be6166b
84f2f65
1e55f99
bd01d05
84f2f65
be6166b
 
84f2f65
1e55f99
84f2f65
 
 
 
 
 
1e55f99
84f2f65
 
 
 
 
 
 
 
 
 
1e55f99
 
84f2f65
61f127b
 
 
 
 
84f2f65
1e55f99
84f2f65
96da119
 
 
 
 
 
 
61f127b
 
1e55f99
61f127b
 
84f2f65
61f127b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1e55f99
 
 
 
 
 
 
 
 
 
2ce843d
1e55f99
 
2ce843d
 
 
 
 
 
 
 
 
1e55f99
54ee23f
1e55f99
 
 
 
 
 
 
 
 
2ce843d
 
1e55f99
2ce843d
1e55f99
a8dd995
2ce843d
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce843d
 
 
 
 
 
1e55f99
 
 
 
2ce843d
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce843d
1e55f99
 
 
2ce843d
 
1e55f99
 
 
2ce843d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1e55f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce843d
 
 
 
 
 
 
1e55f99
2ce843d
 
1e55f99
 
 
 
 
 
be6166b
 
 
 
 
1e55f99
 
84f2f65
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FlowRead AI | Saliency-Guided Reading</title>
    <style>
        body {
            font-family: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; /* More paper/academic feel */
            max-width: 900px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.6;
            background-color: #fdfbf7; /* Slightly beige paper background */
            color: #292524;
        }

        h1 {
            font-size: 2.8rem;
            margin-bottom: 0.5rem;
            text-align: center;
            font-weight: 800;
            color: #1c1917;
        }

        p.subtitle {
            text-align: center;
            color: #57534e;
            font-size: 1.1rem;
            margin-bottom: 2rem;
        }

        .tabs {
            display: flex;
            justify-content: center;
            gap: 1rem;
            margin-bottom: 2rem;
        }

        .tab-btn {
            background-color: #e7e5df;
            color: #44403c;
            border: 1px solid #d6d3d1;
            padding: 0.75rem 1.5rem;
            font-size: 1rem;
            border-radius: 999px;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.2s;
            font-family: inherit;
        }

        .tab-btn.active {
            background-color: #292524;
            color: #fdfbf7;
            border-color: #292524;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
        }

        .tab-content {
            display: none;
        }
        
        .tab-content.active {
            display: block;
            animation: fadeIn 0.3s ease-in-out;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .container {
            background: #fffcf8;
            padding: 2rem;
            border-radius: 0.5rem;
            border: 1px solid #e7e5df;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
        }

        textarea, input[type="text"] {
            font-family: system-ui, -apple-system, sans-serif;
            background-color: #fffcf8;
            border: 1px solid #d6d3d1;
            color: #292524;
        }

        textarea {
            width: 100%;
            height: 150px;
            padding: 0.75rem;
            border-radius: 0.375rem;
            font-size: 1rem;
            resize: vertical;
            margin-bottom: 1rem;
            box-sizing: border-box;
        }

        .controls {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 1.5rem;
            flex-wrap: wrap;
            gap: 1rem;
        }

        .slider-group {
            display: flex;
            align-items: center;
            gap: 1rem;
            flex-grow: 1;
        }

        input[type="range"] {
            flex-grow: 1;
            max-width: 300px;
            accent-color: #57534e;
        }

        button {
            background-color: #292524;
            color: #fdfbf7;
            border: none;
            padding: 0.5rem 1.5rem;
            font-size: 1rem;
            border-radius: 0.375rem;
            cursor: pointer;
            transition: background-color 0.2s;
            font-family: inherit;
        }

        button:hover {
            background-color: #44403c;
        }

        button:disabled {
            background-color: #a8a29e;
            cursor: not-allowed;
        }

        #result-container {
            margin-top: 2rem;
            padding: 1.5rem;
            background-color: #f5f3ef;
            border: 1px solid #e7e5df;
            border-radius: 0.375rem;
            min-height: 100px;
            white-space: pre-wrap;
            font-size: 1.125rem;
        }

        /* Round Checkboxes */
        input[type="checkbox"] {
            appearance: none;
            background-color: #fffcf8;
            margin: 0;
            font: inherit;
            color: currentColor;
            width: 1.15em;
            height: 1.15em;
            border: 1px solid #a8a29e;
            border-radius: 50%;
            display: grid;
            place-content: center;
            cursor: pointer;
        }
        
        input[type="checkbox"]::before {
            content: "";
            width: 0.65em;
            height: 0.65em;
            border-radius: 50%;
            transform: scale(0);
            transition: 120ms transform ease-in-out;
            box-shadow: inset 1em 1em #292524;
        }
        
        input[type="checkbox"]:checked::before {
            transform: scale(1);
        }

        /* Token specific styles */
        .token {
            transition: font-weight 0.2s;
        }

        .highlighted {
            font-weight: 800; /* Extra bold */
            color: #000;
        }
        
        .semi-highlighted {
            font-weight: 600;
            color: #44403c;
        }

        #loading {
            display: none;
            color: #78716c;
            text-align: center;
            margin-top: 1rem;
            font-style: italic;
        }
    </style>
</head>
<body>

    <h1>Flow<span style="color: #a8a29e; font-weight: 500;">Read</span></h1>
    <p class="subtitle">Accelerate reading comprehension using LLM attention vectors.</p>

    <div class="tabs">
        <button class="tab-btn active" onclick="switchTab('study')">Take the User Study</button>
        <button class="tab-btn" onclick="switchTab('sandbox')">Playground (Sandbox)</button>
    </div>

    <!-- Playground Tab -->
    <div id="sandbox-tab" class="tab-content">
        <div class="container">
            <textarea id="text-input" placeholder="Enter or paste your text here...">In the year 1969, Apollo 11 launched a massive rocket into space, filled with brilliant astronauts and powerful technology. It was a terrifying, yet awe-inspiring journey that changed history forever.</textarea>
            
            <details style="margin-bottom: 1.5rem; border: 1px solid #d6d3d1; border-radius: 0.375rem; padding: 1rem; background: #fdfbf7;">
                <summary style="cursor: pointer; font-weight: bold; color: #57534e;">Advanced Saliency Settings</summary>
                
                <div style="margin-top: 1rem; margin-bottom: 1rem;">
                    <label for="saliency-mode" style="display:block; font-weight: 600; margin-bottom: 0.5rem; color: #44403c;">
                        Saliency Calculation Mode
                    </label>
                    <p style="font-size: 0.85rem; color: #78716c; margin-top: 0; margin-bottom: 0.5rem;">
                        Choose whether importance is calculated relative to this text alone, or absolutely across all texts.
                    </p>
                    <select id="saliency-mode" style="width: 100%; padding: 0.5rem; border: 1px solid #d6d3d1; border-radius: 0.25rem; font-size: 0.9rem; box-sizing: border-box; font-family: inherit;">
                        <option value="local">Local Mode (Relative Min/Max)</option>
                        <option value="global">Global Mode (Absolute Values with Penalty)</option>
                    </select>
                </div>

                <div style="margin-top: 1rem;">
                    <label for="preprompt" style="display:block; font-weight: 600; margin-bottom: 0.5rem; color: #44403c;">
                        Intent-Driven Reading (Preprompt)
                    </label>
                    <p style="font-size: 0.85rem; color: #78716c; margin-top: 0; margin-bottom: 0.5rem;">
                        Instruct Gemma what to focus on. Examples: "Focus on numbers and dates", "Highlight emotional words".
                    </p>
                    <input type="text" id="preprompt" placeholder="e.g., Focus only on verbs and action words..." style="width: 100%; padding: 0.5rem; border: 1px solid #d6d3d1; border-radius: 0.25rem; font-size: 0.9rem; box-sizing: border-box; margin-bottom: 1rem; font-family: inherit;">
                </div>

                <div>
                    <label for="layer-preset" style="display:block; font-weight: 600; margin-bottom: 0.5rem; color: #44403c;">
                        Network Layers
                    </label>
                    <p style="font-size: 0.85rem; color: #78716c; margin-top: 0; margin-bottom: 0.5rem;">
                        Select which section of the network's layers to extract attention scores from. Middle layers generally capture the best semantic meaning.
                    </p>
                    <select id="layer-preset" style="width: 100%; padding: 0.5rem; border: 1px solid #d6d3d1; border-radius: 0.25rem; font-size: 0.9rem; box-sizing: border-box; font-family: inherit;">
                        <option value="middle" selected>Middle Layers (Semantic focus)</option>
                        <option value="first">First Few Layers (Lexical/Syntax focus)</option>
                        <option value="last">Last Few Layers (Output formatting focus)</option>
                        <option value="all">All Layers (Averaged)</option>
                    </select>
                </div>
            </details>

            <div class="controls">
                <button id="analyze-btn">Analyze Text</button>
                <div class="slider-group">
                    <label for="threshold" title="Lower threshold highlights more words">Threshold: <span id="threshold-val">0.35</span></label>
                    <input type="range" id="threshold" min="0" max="1" step="0.01" value="0.35">
                </div>
                <div class="slider-group" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.5rem;">
                    <input type="checkbox" id="gradient-mode" style="margin: 0;">
                    <label for="gradient-mode" style="cursor:pointer; margin: 0; white-space: nowrap;" title="Maps attention scores to visual contrast dynamically">
                        Gradient Mode
                    </label>
                </div>
            </div>
            
            <div id="loading">Analyzing attention vectors with Gemma 2B... Please wait.</div>

            <div id="result-container">
                <!-- Processed text will appear here -->
            </div>
        </div>
    </div>

    <!-- Study Tab -->
    <div id="study-tab" class="tab-content active">
        <!-- Step 1: Intro -->
        <div id="study-intro" class="container" style="text-align: center;">
            <h2>Help us prove FlowRead works!</h2>
            <p style="max-width: 600px; margin: 0 auto 2rem; color: #4b5563;">
                We are testing if LLM-guided saliency highlighting improves reading speed, comprehension, and retention. 
                You will be shown three short texts (one plain, one FlowRead bolding, one FlowRead gradient) and asked a few quick questions.
            </p>
            <button id="start-study-btn" style="font-size: 1.25rem; padding: 1rem 2rem;">Start the 3-Minute Study</button>
        </div>

        <!-- Step 1.5: Loading -->
        <div id="study-loading" class="container" style="display: none; text-align: center;">
            <h2 id="study-loading-title">Preparing your study...</h2>
            <p>Our AI is analyzing the texts. This takes just a moment.</p>
        </div>

        <!-- Step 2: Reading -->
        <div id="study-reading" class="container" style="display: none;">
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
                <h2 id="study-topic" style="margin: 0; color: #1f2937;">Topic</h2>
                <span id="study-progress" style="color: #6b7280; font-weight: bold; background: #e5e7eb; padding: 0.25rem 0.75rem; border-radius: 999px;">Text 1 of 2</span>
            </div>
            <div id="study-text-content" style="font-size: 1.25rem; line-height: 1.8; margin-bottom: 2rem; padding: 1.5rem; background: #fdfbf7; border: 1px solid #d6d3d1; border-radius: 0.5rem; min-height: 150px;">
                <!-- Text goes here -->
            </div>
            <div style="text-align: center;">
                <button id="done-reading-btn" style="font-size: 1.1rem; padding: 0.75rem 2rem;">I'm Done Reading</button>
            </div>
        </div>

        <!-- Step 3: Questions -->
        <div id="study-questions" class="container" style="display: none;">
            <h2>Comprehension Check</h2>
            <p style="color: #6b7280; margin-bottom: 1.5rem;">Please answer based on the text you just read. (You cannot go back!)</p>
            <div id="questions-container" style="margin-bottom: 2rem;">
                <!-- Questions go here -->
            </div>
            <button id="submit-answers-btn">Submit Answers</button>
        </div>

        <!-- Step 3.5: Preference -->
        <div id="study-preference" class="container" style="display: none; text-align: center;">
            <h2>Which text style did you prefer?</h2>
            <p style="color: #6b7280; margin-bottom: 2rem;">Please select the reading experience you liked the most.</p>
            <div style="display: flex; flex-direction: column; gap: 1rem; max-width: 400px; margin: 0 auto 2rem;">
                <button class="pref-btn" data-pref="plain" style="padding: 1rem; font-size: 1.1rem; background: #fffcf8; color: #292524; border: 1px solid #e7e5df;">Plain Text</button>
                <button class="pref-btn" data-pref="flowread" style="padding: 1rem; font-size: 1.1rem; background: #f5f3ef; color: #292524; border: 1px solid #a8a29e;">FlowRead (bolding)</button>
                <button class="pref-btn" data-pref="gradient" style="padding: 1rem; font-size: 1.1rem; background: #eef2ff; color: #1e3a8a; border: 1px solid #c7d2fe;">FlowRead (gradient)</button>
            </div>
        </div>

        <!-- Step 4: Results Dashboard -->
        <div id="study-results" class="container" style="display: none; text-align: center;">
            <h2>Global Study Results</h2>
            <p style="color: #4b5563; margin-bottom: 2rem;">Thank you! Here is how FlowRead impacts reading across all users.</p>
            
            <div style="display: flex; justify-content: center; gap: 2rem; margin-bottom: 2rem; flex-wrap: wrap;">
                <!-- Plain Stats -->
                <div style="background: #fffcf8; padding: 1.5rem; border-radius: 0.5rem; width: 250px; border: 1px solid #e7e5df;">
                    <h3 style="margin-top: 0; color: #292524;">Plain Text</h3>
                    <p style="font-size: 2rem; font-weight: bold; margin: 0.5rem 0;" id="stat-plain-time">-- s</p>
                    <p style="margin: 0; color: #57534e;">Average Reading Time</p>
                    <p style="font-size: 1.5rem; font-weight: bold; margin: 1rem 0 0.5rem;" id="stat-plain-acc">--%</p>
                    <p style="margin: 0; color: #57534e;">Comprehension Accuracy</p>
                </div>
                <!-- FlowRead Stats -->
                <div style="background: #f5f3ef; padding: 1.5rem; border-radius: 0.5rem; width: 250px; border: 2px solid #a8a29e;">
                    <h3 style="margin-top: 0; color: #292524;">FlowRead (bolding)</h3>
                    <p style="font-size: 2rem; font-weight: bold; margin: 0.5rem 0; color: #292524;" id="stat-flow-time">-- s</p>
                    <p style="margin: 0; color: #57534e;">Average Reading Time</p>
                    <p style="font-size: 1.5rem; font-weight: bold; margin: 1rem 0 0.5rem; color: #292524;" id="stat-flow-acc">--%</p>
                    <p style="margin: 0; color: #57534e;">Comprehension Accuracy</p>
                </div>
                <!-- Gradient Stats -->
                <div style="background: #eef2ff; padding: 1.5rem; border-radius: 0.5rem; width: 250px; border: 2px solid #a5b4fc;">
                    <h3 style="margin-top: 0; color: #1e3a8a;">FlowRead (gradient)</h3>
                    <p style="font-size: 2rem; font-weight: bold; margin: 0.5rem 0; color: #1e3a8a;" id="stat-grad-time">-- s</p>
                    <p style="margin: 0; color: #3730a3;">Average Reading Time</p>
                    <p style="font-size: 1.5rem; font-weight: bold; margin: 1rem 0 0.5rem; color: #1e3a8a;" id="stat-grad-acc">--%</p>
                    <p style="margin: 0; color: #3730a3;">Comprehension Accuracy</p>
                </div>
            </div>
            
            <div style="margin-bottom: 2rem;">
                <h3 style="color: #4b5563;">User Preferences</h3>
                <p>Plain: <span id="pref-plain">0</span> | FlowRead (bolding): <span id="pref-flowread">0</span> | FlowRead (gradient): <span id="pref-gradient">0</span></p>
            </div>

            <p style="font-size: 0.9rem; color: #6b7280;">Based on <span id="stat-sample-size">0</span> total reading sessions.</p>
            <button onclick="switchTab('sandbox')" style="margin-top: 1rem;">Try FlowRead on your own text</button>
        </div>
    </div>

    <script>
        function switchTab(tabName) {
            document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
            document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
            
            if (tabName === 'study') {
                document.getElementById('study-tab').classList.add('active');
                document.querySelectorAll('.tab-btn')[0].classList.add('active');
            } else {
                document.getElementById('sandbox-tab').classList.add('active');
                document.querySelectorAll('.tab-btn')[1].classList.add('active');
            }
        }

        // ==========================================
        // PLAYGROUND LOGIC
        // ==========================================
        const inputArea = document.getElementById('text-input');
        const prepromptInput = document.getElementById('preprompt');
        const saliencyModeInput = document.getElementById('saliency-mode');
        const layerPresetInput = document.getElementById('layer-preset');
        const analyzeBtn = document.getElementById('analyze-btn');
        const thresholdSlider = document.getElementById('threshold');
        const thresholdVal = document.getElementById('threshold-val');
        const gradientMode = document.getElementById('gradient-mode');
        const resultContainer = document.getElementById('result-container');
        const loading = document.getElementById('loading');

        let currentTokens = []; 
        
        thresholdSlider.addEventListener('input', (e) => {
            thresholdVal.textContent = parseFloat(e.target.value).toFixed(2);
            renderTokens(); 
        });

        gradientMode.addEventListener('change', renderTokens);

        analyzeBtn.addEventListener('click', async () => {
            const text = inputArea.value.trim();
            const preprompt = prepromptInput.value.trim();
            const saliencyMode = saliencyModeInput.value;
            const layerPreset = layerPresetInput.value;
            const modelVersion = "2b";
            
            if (!text) return;

            analyzeBtn.disabled = true;
            loading.style.display = 'block';
            loading.textContent = 'Analyzing text with Gemma 4...';
            resultContainer.innerHTML = '';

            let isFetching = true;
            const pollStatus = async () => {
                while(isFetching) {
                    try {
                        const statusRes = await fetch('/status');
                        if (statusRes.ok) {
                            const statusData = await statusRes.json();
                            if (statusData[modelVersion] && statusData[modelVersion].startsWith("downloading")) {
                                const parts = statusData[modelVersion].split(": ");
                                const progress = parts.length > 1 ? parts[1] : "...";
                                loading.textContent = `Downloading Gemma 4 (${modelVersion}) Model ${progress}... this takes a few minutes.`;
                            }
                        }
                    } catch(e) {}
                    await new Promise(r => setTimeout(r, 2000));
                }
            };
            pollStatus();

            try {
                const response = await fetch(`/analyze/${modelVersion}`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ text, preprompt, layer_preset: layerPreset, saliency_mode: saliencyMode })
                });
                
                isFetching = false;

                if (!response.ok) throw new Error('Network response was not ok');

                const data = await response.json();
                currentTokens = data.words || [];
                renderTokens();

            } catch (error) {
                console.error('Error:', error);
                resultContainer.innerHTML = '<span style="color: red;">Error analyzing text. Is the backend running?</span>';
            } finally {
                analyzeBtn.disabled = false;
                loading.style.display = 'none';
            }
        });

        function renderTokens() {
            if (!currentTokens.length) return;
            const threshold = parseFloat(thresholdSlider.value);
            const useGradient = gradientMode.checked;
            resultContainer.innerHTML = ''; 

            // Group tokens into words based on the SentencePiece space prefix
            let words = [];
            let currentWordTokens = [];
            let currentWordMaxScore = 0;

            currentTokens.forEach((item, index) => {
                if (index === 0 && (item.token.includes('<bos>') || item.word.includes('<bos>'))) return;

                const isWhitespace = item.token.trim() === '';
                const prevIsWhitespace = currentWordTokens.length > 0 && currentWordTokens[currentWordTokens.length - 1].token.trim() === '';

                if (item.token.startsWith(' ') || (currentWordTokens.length > 0 && isWhitespace !== prevIsWhitespace)) {
                    if (currentWordTokens.length > 0) {
                        words.push({ tokens: currentWordTokens, maxScore: currentWordMaxScore });
                    }
                    currentWordTokens = [item];
                    currentWordMaxScore = item.score;
                } else {
                    currentWordTokens.push(item);
                    currentWordMaxScore = Math.max(currentWordMaxScore, item.score);
                }
            });
            if (currentWordTokens.length > 0) {
                words.push({ tokens: currentWordTokens, maxScore: currentWordMaxScore });
            }

            // Render words
            words.forEach(wordObj => {
                const isWordHighlighted = wordObj.maxScore >= threshold;

                wordObj.tokens.forEach(item => {
                    const span = document.createElement('span');
                    span.className = 'token';
                    
                    if (useGradient) {
                        // In gradient mode, we still render token-by-token detail
                        const opacity = 0.4 + (item.score * 0.6);
                        const weight = 400 + Math.round(item.score * 400);
                        span.style.opacity = opacity;
                        span.style.fontWeight = weight;
                    } else {
                        // Binary mode: entire word gets highlighted if ANY part of it crossed threshold
                        if (isWordHighlighted) span.classList.add('highlighted');
                    }
                    
                    span.textContent = item.token;
                    resultContainer.appendChild(span);
                });
            });
        }

        // ==========================================
        // USER STUDY LOGIC
        // ==========================================
        const userId = 'user_' + Math.random().toString(36).substr(2, 9);
        let studyTexts = [];
        let currentStep = 0; 
        let conditionOrder = []; 
        let readingStartTime = 0;
        let flowReadHTMLs = {}; 
        let flowReadGradientHTMLs = {};
        let currentReadingTimeMs = 0;

        // Helper to shuffle array
        function shuffle(array) {
            for (let i = array.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [array[i], array[j]] = [array[j], array[i]];
            }
            return array;
        }

        document.getElementById('start-study-btn').addEventListener('click', async () => {
            currentStep = 0;
            document.getElementById('study-intro').style.display = 'none';
            document.getElementById('study-loading').style.display = 'block';

            try {
                // 1. Fetch texts from backend
                const res = await fetch('/api/study/texts');
                const data = await res.json();
                studyTexts = data.texts;

                // 2. Randomize condition order for A/B/C testing
                conditionOrder = shuffle(['plain', 'flowread', 'gradient']);

                // 3. Load HTMLs into dicts
                const flowReadTextIndex = conditionOrder.indexOf('flowread');
                flowReadHTMLs[studyTexts[flowReadTextIndex].id] = studyTexts[flowReadTextIndex].flowread_html;
                
                const gradientTextIndex = conditionOrder.indexOf('gradient');
                flowReadGradientHTMLs[studyTexts[gradientTextIndex].id] = studyTexts[gradientTextIndex].flowread_gradient_html;

                // 4. Start the first reading task
                document.getElementById('study-loading').style.display = 'none';
                showReadingScreen();

            } catch (err) {
                console.error(err);
                alert("Error starting study. Please try again later.");
                document.getElementById('study-loading').style.display = 'none';
                document.getElementById('study-intro').style.display = 'block';
            }
        });

        function showReadingScreen() {
            const currentData = studyTexts[currentStep];
            const currentCondition = conditionOrder[currentStep];

            let topicText = currentData.topic;
            if (currentCondition === 'flowread') topicText += ' (FlowRead bolding)';
            if (currentCondition === 'gradient') topicText += ' (FlowRead gradient)';
            
            document.getElementById('study-topic').textContent = topicText;
            document.getElementById('study-progress').textContent = `Text ${currentStep + 1} of 3`;

            const contentDiv = document.getElementById('study-text-content');
            if (currentCondition === 'flowread') {
                contentDiv.innerHTML = flowReadHTMLs[currentData.id];
            } else if (currentCondition === 'gradient') {
                contentDiv.innerHTML = flowReadGradientHTMLs[currentData.id];
            } else {
                contentDiv.textContent = currentData.text;
            }

            document.getElementById('study-reading').style.display = 'block';
            // Record exact start time
            readingStartTime = performance.now();
        }

        document.getElementById('done-reading-btn').addEventListener('click', () => {
            // Record exact end time
            currentReadingTimeMs = Math.round(performance.now() - readingStartTime);
            document.getElementById('study-reading').style.display = 'none';
            showQuestionsScreen();
        });

        function showQuestionsScreen() {
            const currentData = studyTexts[currentStep];
            const container = document.getElementById('questions-container');
            container.innerHTML = '';

            currentData.questions.forEach((q, qIndex) => {
                const qDiv = document.createElement('div');
                qDiv.style.marginBottom = '1.5rem';
                qDiv.style.background = '#fff';
                qDiv.style.padding = '1rem';
                qDiv.style.borderRadius = '0.375rem';
                qDiv.style.border = '1px solid #e5e7eb';
                
                qDiv.innerHTML = `<p style="font-weight: bold; margin-top: 0; margin-bottom: 0.75rem;">${qIndex + 1}. ${q.question}</p>`;
                
                q.options.forEach((opt, oIndex) => {
                    qDiv.innerHTML += `
                        <label style="display: block; margin-bottom: 0.5rem; cursor: pointer; padding: 0.25rem 0;">
                            <input type="radio" name="q${qIndex}" value="${oIndex}" style="margin-right: 0.5rem;"> ${opt}
                        </label>
                    `;
                });
                container.appendChild(qDiv);
            });

            document.getElementById('study-questions').style.display = 'block';
        }

        document.getElementById('submit-answers-btn').addEventListener('click', async () => {
            const currentData = studyTexts[currentStep];
            let score = 0;
            let allAnswered = true;

            // Grade questions
            currentData.questions.forEach((q, qIndex) => {
                const selected = document.querySelector(`input[name="q${qIndex}"]:checked`);
                if (!selected) {
                    allAnswered = false;
                } else if (parseInt(selected.value) === q.correct) {
                    score++;
                }
            });

            if (!allAnswered) {
                alert("Please answer all questions before proceeding.");
                return;
            }

            document.getElementById('submit-answers-btn').disabled = true;
            document.getElementById('submit-answers-btn').textContent = "Submitting...";

            // Send result to backend
            try {
                await fetch('/api/study/submit', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        user_id: userId,
                        text_id: currentData.id,
                        condition: conditionOrder[currentStep],
                        reading_time_ms: currentReadingTimeMs,
                        score: score,
                        total_questions: currentData.questions.length
                    })
                });
            } catch (err) {
                console.error("Failed to submit result:", err);
            }

            document.getElementById('study-questions').style.display = 'none';
            document.getElementById('submit-answers-btn').disabled = false;
            document.getElementById('submit-answers-btn').textContent = "Submit Answers";

            currentStep++;
            if (currentStep < 3) {
                // Next text
                showReadingScreen();
            } else {
                // Done! Show preference questionnaire
                document.getElementById('study-preference').style.display = 'block';
            }
        });

        // Handle preference selection
        document.querySelectorAll('.pref-btn').forEach(btn => {
            btn.addEventListener('click', async (e) => {
                const preference = e.target.getAttribute('data-pref');
                
                // Disable buttons
                document.querySelectorAll('.pref-btn').forEach(b => b.disabled = true);
                e.target.textContent = "Submitting...";

                try {
                    await fetch('/api/study/preference', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            user_id: userId,
                            preference: preference
                        })
                    });
                } catch (err) {
                    console.error("Failed to submit preference:", err);
                }

                document.getElementById('study-preference').style.display = 'none';
                showResultsScreen();
            });
        });

        async function showResultsScreen() {
            document.getElementById('study-loading').style.display = 'block';
            document.getElementById('study-loading-title').textContent = "Loading global statistics...";
            
            try {
                const res = await fetch('/api/study/stats');
                const stats = await res.json();

                const formatTime = ms => ms ? (ms / 1000).toFixed(1) + ' s' : '-- s';
                const formatAcc = pct => pct ? Math.round(pct) + '%' : '--%';

                document.getElementById('stat-plain-time').textContent = formatTime(stats.plain.avg_reading_time_ms);
                document.getElementById('stat-plain-acc').textContent = formatAcc(stats.plain.avg_accuracy_percent);
                
                document.getElementById('stat-flow-time').textContent = formatTime(stats.flowread.avg_reading_time_ms);
                document.getElementById('stat-flow-acc').textContent = formatAcc(stats.flowread.avg_accuracy_percent);
                
                document.getElementById('stat-grad-time').textContent = formatTime(stats.gradient.avg_reading_time_ms);
                document.getElementById('stat-grad-acc').textContent = formatAcc(stats.gradient.avg_accuracy_percent);

                document.getElementById('pref-plain').textContent = stats.preferences.plain || 0;
                document.getElementById('pref-flowread').textContent = stats.preferences.flowread || 0;
                document.getElementById('pref-gradient').textContent = stats.preferences.gradient || 0;

                const totalSessions = (stats.plain.sample_size || 0) + (stats.flowread.sample_size || 0) + (stats.gradient.sample_size || 0);
                document.getElementById('stat-sample-size').textContent = totalSessions;

                document.getElementById('study-loading').style.display = 'none';
                document.getElementById('study-results').style.display = 'block';

            } catch (err) {
                console.error(err);
                alert("Failed to analyze text.");
                isFetching = false;
            } finally {
                analyzeBtn.disabled = false;
                loading.style.display = 'none';
            }
        }
    </script>
</body>
</html>