File size: 28,276 Bytes
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d0827a
f1e447c
 
0f916bb
f1e447c
 
 
 
4d0827a
 
 
 
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db8ef45
 
 
 
 
 
 
 
 
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
0f916bb
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db8ef45
 
 
f1e447c
 
 
 
 
 
0f916bb
4d0827a
 
 
 
 
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db8ef45
 
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63fbafb
f1e447c
 
63fbafb
 
 
 
 
 
 
 
 
 
f1e447c
 
 
 
 
 
0f916bb
 
 
63fbafb
f23dc52
f1e447c
 
 
 
 
 
 
0f916bb
f1e447c
f23dc52
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f916bb
f1e447c
 
 
 
 
0f916bb
 
f1e447c
0f916bb
 
 
 
 
 
 
 
 
 
 
f1e447c
0f916bb
 
 
f1e447c
 
 
 
 
 
0f916bb
f1e447c
 
0f916bb
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f916bb
 
 
 
 
 
 
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
0f916bb
f1e447c
 
 
 
 
 
 
0f916bb
f1e447c
db8ef45
f1e447c
 
 
0f916bb
f1e447c
f23dc52
 
 
63fbafb
 
f23dc52
 
 
f1e447c
 
f23dc52
 
 
 
 
 
 
 
 
 
 
f1e447c
db8ef45
f1e447c
 
 
63fbafb
f1e447c
db8ef45
f1e447c
 
 
 
db8ef45
 
f1e447c
 
 
 
 
 
 
 
 
 
0f916bb
 
 
 
 
 
f1e447c
 
 
0f916bb
 
f1e447c
 
 
 
 
 
 
 
 
 
 
db8ef45
f1e447c
63fbafb
0f916bb
f1e447c
 
 
 
db8ef45
 
63fbafb
db8ef45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f916bb
db8ef45
 
0f916bb
 
 
 
63fbafb
0f916bb
 
 
db8ef45
4d0827a
 
 
db8ef45
4d0827a
 
 
 
 
0f916bb
 
 
 
 
4d0827a
db8ef45
 
 
63fbafb
0f916bb
 
f1e447c
 
 
 
 
 
f23dc52
63fbafb
0f916bb
 
63fbafb
 
0f916bb
f1e447c
0f916bb
f1e447c
63fbafb
 
f23dc52
f1e447c
 
0f916bb
f1e447c
 
 
 
0f916bb
 
 
 
f1e447c
0f916bb
 
f1e447c
 
0f916bb
f1e447c
db8ef45
 
63fbafb
 
db8ef45
f1e447c
 
 
 
 
63fbafb
f1e447c
 
 
 
db8ef45
f1e447c
 
 
 
0f916bb
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db8ef45
f1e447c
 
 
0f916bb
f1e447c
db8ef45
 
f1e447c
 
0f916bb
f1e447c
 
0f916bb
f1e447c
 
 
 
0f916bb
 
 
f1e447c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f916bb
f1e447c
0f916bb
f1e447c
 
 
 
0f916bb
f1e447c
 
 
 
 
 
 
0f916bb
f1e447c
 
 
 
 
 
 
 
 
 
63fbafb
f1e447c
 
 
db8ef45
 
 
 
 
 
63fbafb
db8ef45
 
 
f1e447c
 
 
 
 
 
0f916bb
f1e447c
 
 
 
 
 
 
 
 
 
0f916bb
f1e447c
 
 
 
 
ea75b29
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hypothesis Hero — Spin • Solve • Score</title>
<style>
  :root{
    --bg:#0d1224; --panel:#121937; --panel2:#0f1530; --accent:#6ee7ff;
    --accent2:#ffd166; --good:#00e887; --bad:#ff5c75; --text:#e8ecff;
  }
  *{box-sizing:border-box}
  body{
    margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;
    color:var(--text); background:
      radial-gradient(1200px 800px at 15% 10%, #1a2354 0%, #0b0f20 40%, #050814 100%);
    min-height:100vh; display:flex; flex-direction:column; align-items:center;
  }
  header{width:100%; padding:16px 18px; display:flex; gap:12px; align-items:center; justify-content:space-between; flex-wrap:wrap}
  h1{margin:0; font-size:clamp(20px,3.2vw,34px); letter-spacing:.5px}
  .sub{opacity:.9; font-size:.9rem}
  .hud{display:flex; gap:12px; flex-wrap:wrap; align-items:center; justify-content:flex-end}
  .badge{
    background:linear-gradient(180deg, #1c2658, #0d1330); border:1px solid #27306b; color:#dfe6ff;
    padding:8px 12px; border-radius:12px; box-shadow:0 6px 14px #0007; font-weight:600; min-width:120px; text-align:center;
  }
  .linkbar a{
    text-decoration:none; font-weight:700; color:#8bd9ff; border:1px solid #2a4e7a; padding:6px 10px; border-radius:10px;
    background:linear-gradient(180deg,#0f2047,#0b1534);
  }
  .container{width:min(1200px, 94%); display:grid; grid-template-columns:420px 1fr; gap:18px; margin:8px auto 24px}
  @media (max-width: 960px){ .container{grid-template-columns:1fr} }
  .card{
    background:linear-gradient(180deg, var(--panel), var(--panel2)); border:1px solid #27306b; border-radius:18px;
    box-shadow:0 16px 48px #000a; padding:16px;
  }

  /* Wheel panel */
  .wheel-wrap{display:grid; grid-template-rows:auto auto; gap:14px; align-items:center; justify-items:center}
  canvas{background:radial-gradient(350px 350px at 50% 50%, #0f1a49, #0b102a); border-radius:50%; width:100%; max-width:360px; aspect-ratio:1; border:2px solid #27306b}
  .needle{
    width:0; height:0; border-left:16px solid transparent; border-right:16px solid transparent; border-bottom:26px solid var(--accent2);
    filter:drop-shadow(0 3px 3px #0007);
    margin-top:-6px;
  }
  .wheel-controls{display:flex; gap:10px; flex-wrap:wrap; justify-content:center}
  button{
    appearance:none; border:none; cursor:pointer; padding:10px 14px; border-radius:12px; font-weight:700;
    background:linear-gradient(180deg,#3bd6ff,#0cb5e7); color:#042234; box-shadow:0 8px 20px #00b6ff44, inset 0 0 0 1px #66e4ff;
  }
  button.secondary{background:linear-gradient(180deg,#ffd166,#ffb703); color:#3a1f00; box-shadow:0 8px 20px #ffb70355, inset 0 0 0 1px #ffe19c}
  button.ghost{background:transparent; color:var(--accent); border:1px dashed #346692}
  button:disabled{opacity:.5; cursor:not-allowed}

  /* Submit highlight */
  #submitBtn.attn{
    animation:glow 0.9s ease-in-out infinite alternate;
  }
  @keyframes glow{
    from{ box-shadow:0 0 0 0 rgba(110,231,255,.55), inset 0 0 0 1px #66e4ff; }
    to{   box-shadow:0 0 22px 6px rgba(110,231,255,.65), inset 0 0 0 1px #66e4ff; }
  }

  /* Question panel */
  .q-title{font-size:1.1rem; font-weight:700; margin-bottom:8px}
  .q-meta{opacity:.8; font-size:.95rem; margin-bottom:4px}
  .q-box{
    margin-top:8px; display:grid; grid-template-columns:1fr; gap:12px; padding:12px; border-radius:14px;
    background:linear-gradient(180deg,#121b43,#0e1538); border:1px solid #27306b;
  }
  .candidates{display:grid; grid-template-columns:1fr 1fr; gap:12px}
  @media (max-width: 700px){ .candidates{grid-template-columns:1fr} }

  .tile{
    padding:12px; border-radius:12px; background:#0f1742; border:1px solid #23306b; min-height:70px;
    display:flex; gap:8px; align-items:center; justify-content:flex-start; box-shadow:0 6px 18px #0006; cursor:grab;
    user-select:none;
  }
  .tile:active{cursor:grabbing}
  .tile .label{font-weight:800; background:var(--accent2); color:#2b1600; padding:2px 8px; border-radius:10px; min-width:34px; text-align:center}
  .tile.dragging{opacity:.7; transform:scale(1.02)}
  .dropzone{
    border:2px dashed #4661a3; border-radius:12px; padding:12px; min-height:74px; display:flex; align-items:center; justify-content:flex-start; gap:10px;
  }
  .dropzone .slot{font-weight:900; color:#9fc5ff; background:#10224f; border:1px solid #2a3b7a; padding:3px 10px; border-radius:10px}
  .dropzone.over{background:#0f204f88; outline:2px solid #6ee7ff44}
  .explain{font-size:.95rem; opacity:.9}
  .hint{opacity:.86; font-size:.9rem}
  .correct{color:var(--good)} .wrong{color:var(--bad)}

  .actions{display:flex; gap:10px; flex-wrap:wrap; justify-content:flex-end; margin-top:12px}
  .status{display:flex; gap:10px; align-items:center; flex-wrap:wrap; justify-content:space-between}

  .toast{
    position:fixed; left:50%; transform:translateX(-50%); bottom:18px; background:#0e173e; border:1px solid #2a3b7a;
    padding:12px 14px; border-radius:12px; color:#e6efff; box-shadow:0 10px 24px #000b; display:none; z-index:10;
  }
  .leader{margin-top:10px; font-size:.92rem; opacity:.9}
  .divider{height:1px; background:#243065; margin:8px 0}
  .key{font-weight:800; padding:2px 8px; background:#12255a; border:1px solid #2a3b7a; border-radius:8px}

  /* Feedback area */
  #feedback{margin-top:8px}
</style>
</head>
<body>
  <header>
    <div>
      <h1>Hypothesis Hero</h1>
      <div class="sub">Spin the wheel • Place <span class="key">A/B</span> into <span class="key">H₀/H₁</span> • Beat the clock</div>
      <div class="linkbar" style="margin-top:8px">
        <a href="https://www.linkedin.com/pulse/understanding-hypothesis-testing-from-theory-practice-michael-lively-bjvoe/" target="_blank" rel="noopener noreferrer">
          📖 Read: Understanding Hypothesis Testing — From Theory to Practice (Michael Lively)
        </a>
      </div>
    </div>
    <div class="hud">
      <div class="badge" id="scoreBox">Score: 0</div>
      <div class="badge" id="roundBox">Round: 1 / 10</div>
      <div class="badge" id="timerBox">Time: 00:30</div>
    </div>
  </header>

  <main class="container">
    <!-- Left: Wheel -->
    <section class="card wheel-wrap">
      <div class="needle" title="Wheel pointer"></div>
      <canvas id="wheel" width="600" height="600" aria-label="Spin wheel"></canvas>
      <div class="wheel-controls">
        <button id="spinBtn">🎡 Spin</button>
        <button id="powerBtn" class="secondary" title="Play a power-up if you have one">⚡ Use Power-Up</button>
        <button id="resetBtn" class="ghost">↻ Reset Game</button>
      </div>
      <div class="leader">
        <strong>Wheel Slices:</strong> +100 • +200 • +300 • Double • Speed-Round • Wild-Swap • Shield • Bust (0)
        <div class="divider"></div>
        <span class="hint">Power-Ups: <b>Shield</b> (ignore one wrong), <b>Wild-Swap</b> (swap A/B before grading), <b>Double</b> (double this round’s points).</span>
      </div>
    </section>

    <!-- Right: Question -->
    <section class="card">
      <div id="questionArea">
        <div class="q-title" id="qTitle">Spin the wheel to start!</div>
        <div class="q-meta" id="qMeta"></div>

        <div class="q-box" id="qBox" hidden>
          <div id="qText" class="explain"></div>
          <div class="candidates" id="candidates"></div>

          <div class="status">
            <div class="dropzone" id="dropH0" aria-label="Drop zone H0">
              <span class="slot">H₀</span>
            </div>
            <div class="dropzone" id="dropH1" aria-label="Drop zone H1">
              <span class="slot">H₁</span>
            </div>
          </div>

          <div id="feedback"></div>

          <div class="actions">
            <button id="submitBtn" disabled>Submit Answer</button>
            <button id="hintBtn" class="ghost">Hint</button>
            <button id="skipBtn" class="ghost">Skip (-50)</button>
          </div>
        </div>
      </div>

      <div class="divider"></div>
      <div class="leader" id="leaderboard"></div>
    </section>
  </main>

  <div class="toast" id="toast"></div>

<script>
/* =========================
   DATA: canonical H0/H1 texts per question
   ========================= */
const QUESTIONS = [
  {id:1,title:"New drug lowers blood pressure",question:"Does the new drug reduce mean systolic BP vs. the standard?",metric:"Mean systolic BP (mmHg) after 8 weeks",candidates:{A:"μ_new = μ_std",B:"μ_new < μ_std"},test:"two-sample t-test",tails:"left"},
  {id:2,title:"Email subject B increases open rate",question:"Is the open rate higher for Subject B than Subject A?",metric:"Proportion opened (p)",candidates:{A:"p_B = p_A",B:"p_B > p_A"},test:"two-proportion z-test",tails:"right"},
  {id:3,title:"Process change cuts defect rate",question:"Did the defect rate drop after the process change?",metric:"Proportion defective (p)",candidates:{A:"p_after = p_before",B:"p_after < p_before"},test:"two-proportion z-test",tails:"left"},
  {id:4,title:"Flipped classroom vs. lecture",question:"Do mean exam scores differ by method?",metric:"Mean exam score (μ)",candidates:{A:"μ_flipped = μ_lecture",B:"μ_flipped ≠ μ_lecture"},test:"two-sample t-test",tails:"two"},
  {id:5,title:"Call center SLA (≤ 2 minutes)",question:"Is the average wait time under 2 minutes?",metric:"Mean wait time (minutes)",candidates:{A:"μ ≥ 2",B:"μ < 2"},test:"one-sample t-test",tails:"left"},
  {id:6,title:"New web layout changes time-on-page",question:"Does the new layout change average time-on-page?",metric:"Mean time (seconds)",candidates:{A:"μ_new = μ_old",B:"μ_new ≠ μ_old"},test:"two-sample t-test",tails:"two"},
  {id:7,title:"Fertilizer X boosts yield",question:"Is mean yield higher with Fertilizer X?",metric:"Mean yield (kg/plot)",candidates:{A:"μ_X ≤ μ_std",B:"μ_X > μ_std"},test:"two-sample t-test",tails:"right"},
  {id:8,title:"Strategy has positive alpha",question:"Is mean monthly alpha greater than zero?",metric:"Mean alpha (μ_α)",candidates:{A:"μ_α ≤ 0",B:"μ_α > 0"},test:"one-sample t-test",tails:"right"},
  {id:9,title:"New machine reduces variability",question:"Is variance lower with the new machine?",metric:"Variance of length (σ²)",candidates:{A:"σ_new² = σ_old²",B:"σ_new² < σ_old²"},test:"F-test (or Levene/BF)",tails:"left"},
  {id:10,title:"Device remission rate meets target",question:"Is remission rate at least 60%?",metric:"Proportion in remission p",candidates:{A:"p ≤ 0.60",B:"p > 0.60"},test:"one-sample proportion z-test",tails:"right"}
];

/* =========================
   GAME STATE
   ========================= */
const state = {
  round: 1, maxRounds: 10, score: 0, timer: 30, ticking: null,
  wheelSpun: false, powerups: [], currentPU: null, wheelValue: null,
  drawn: [], currentQ: null, placed: { H0:null, H1:null },
  speedMode: false, shieldOn: false, doubleMode: false, wildAvailable: false, streak:0,
  correctRound: { A:null, B:null } // per-round mapping: which label is H0/H1
};

/* =========================
   UTILS
   ========================= */
const $ = sel => document.querySelector(sel);
const fmtTime = s => "00:" + String(Math.max(0,s)).padStart(2,'0');
const toast = (msg, ms=1600) => { const t=$("#toast"); t.textContent=msg; t.style.display="block"; setTimeout(()=>t.style.display="none", ms); };
function randChoice(arr){ return arr[Math.floor(Math.random()*arr.length)] }
function escapeHtml(s){ return s.replace(/[&<>"']/g, m=>({ "&":"&amp;","<":"&lt;","\"":"&quot;","'":"&#039;" }[m])) }

/* =========================
   WHEEL
   ========================= */
const slices = [
  { label:"+100", type:"points", value:100, color:"#26c6da" },
  { label:"+200", type:"points", value:200, color:"#29b6f6" },
  { label:"Double", type:"double", value:null, color:"#ffd166" },
  { label:"Speed", type:"speed", value:null, color:"#ab7df6" },
  { label:"+300", type:"points", value:300, color:"#26c6da" },
  { label:"Wild", type:"wild", value:null, color:"#4ade80" },
  { label:"Shield", type:"shield", value:null, color:"#a3e635" },
  { label:"Bust 0", type:"bust", value:0, color:"#ef5350" }
];

const wheel = $("#wheel");
const wctx = wheel.getContext("2d");
const center = wheel.width/2;
const radius = center - 6;

function drawWheel(){
  const arc = 2*Math.PI / slices.length;
  wctx.clearRect(0,0,wheel.width,wheel.height);
  wctx.save(); wctx.translate(center, center);
  for(let i=0;i<slices.length;i++){
    wctx.beginPath(); wctx.moveTo(0,0);
    wctx.fillStyle = slices[i].color;
    wctx.arc(0,0,radius, i*arc, (i+1)*arc);
    wctx.closePath(); wctx.fill();

    wctx.save(); wctx.rotate(i*arc + arc/2);
    wctx.fillStyle = "#041029";
    wctx.font = "bold 28px system-ui";
    wctx.textAlign="center";
    wctx.fillText(slices[i].label, radius*0.62, 10);
    wctx.restore();
  }
  wctx.beginPath(); wctx.fillStyle="#0f1a49"; wctx.arc(0,0,70,0,2*Math.PI); wctx.fill();
  wctx.beginPath(); wctx.strokeStyle="#27306b"; wctx.lineWidth=3; wctx.arc(0,0,radius,0,2*Math.PI); wctx.stroke();
  wctx.restore();
}
drawWheel();

let spinRot = 0;
function spinWheel(){
  $("#spinBtn").disabled = true;
  const rotations = 6 + Math.random()*4;
  const targetSlice = Math.floor(Math.random()*slices.length);
  const arc = 360 / slices.length;
  const targetAngle = 360*rotations + (targetSlice + 0.5)*arc;
  const start = performance.now();
  const duration = 3200;
  function animate(now){
    const t = Math.min(1, (now-start)/duration);
    const ease = 1 - Math.pow(1-t, 3);
    const angle = targetAngle*ease;
    spinRot = angle % 360;
    wheel.style.transform = `rotate(${spinRot}deg)`;
    if(t<1) requestAnimationFrame(animate);
    else {
      const result = slices[targetSlice];
      handleWheelResult(result);
      $("#spinBtn").disabled = false;
    }
  }
  requestAnimationFrame(animate);
}
function handleWheelResult(result){
  state.wheelSpun = true; state.wheelValue = result;
  if(result.type==="points"){ toast(`Wheel: +${result.value} base points this round`); }
  if(result.type==="double"){ state.doubleMode=true; state.powerups.push("double"); toast("Wheel: DOUBLE power-up earned"); }
  if(result.type==="speed"){ state.speedMode=true; toast("Wheel: SPEED-ROUND! Shorter timer."); }
  if(result.type==="wild"){ state.wildAvailable=true; state.powerups.push("wild"); toast("Wheel: WILD-SWAP power-up earned"); }
  if(result.type==="shield"){ state.shieldOn=true; state.powerups.push("shield"); toast("Wheel: SHIELD earned"); }
  if(result.type==="bust"){ toast("Wheel: BUST! 0 base points this round."); }
  startRound();
}

/* =========================
   ROUND / TIMER / QUESTION
   ========================= */
function startRound(){
  if(state.round>state.maxRounds){ endGame(); return; }

  const startSecs = state.speedMode ? 18 : 30;
  state.timer = startSecs; $("#timerBox").textContent = "Time: " + fmtTime(state.timer);
  if(state.ticking) clearInterval(state.ticking);
  state.ticking = setInterval(()=>{
    state.timer--; $("#timerBox").textContent = "Time: " + fmtTime(state.timer);
    if(state.timer<=0){ clearInterval(state.ticking); grade(false, "Time up!"); }
  }, 1000);

  // Draw question (no repeats)
  let pool = QUESTIONS.filter(q=>!state.drawn.includes(q.id));
  if(pool.length===0) pool = QUESTIONS.slice();
  const q = randChoice(pool);
  state.drawn.push(q.id);
  state.currentQ = JSON.parse(JSON.stringify(q));

  // Build UI
  $("#qBox").hidden = false;
  $("#qTitle").textContent = `Round ${state.round}: ${q.title}`;
  $("#qMeta").innerHTML = `<span class="hint">Test:</span> ${q.test} • <span class="hint">Tails:</span> ${q.tails} • <span class="hint">Metric:</span> ${q.metric}`;
  $("#qText").textContent = q.question;

  // === RANDOMLY MAP H0/H1 TEXTS TO LABELS A/B, BUT RENDER A THEN B VISUALLY ===
  const assignH0To = Math.random() < 0.5 ? "A" : "B";
  const assignH1To = assignH0To === "A" ? "B" : "A";
  state.correctRound = { [assignH0To]: "H0", [assignH1To]: "H1" };

  // Build tiles in strict order: A (left) then B (right)
  const textForA = assignH0To === "A" ? q.candidates.A : q.candidates.B; // A shows whichever text was assigned to A
  const textForB = assignH0To === "A" ? q.candidates.B : q.candidates.A; // B gets the other text

  const cand = $("#candidates"); cand.innerHTML = "";
  const tileA = document.createElement("div");
  tileA.className = "tile"; tileA.draggable = true; tileA.dataset.key = "A";
  tileA.innerHTML = `<span class="label">A</span> <span>${textForA}</span>`;
  tileA.addEventListener("click", ()=>clickPlace(tileA));
  cand.appendChild(tileA);

  const tileB = document.createElement("div");
  tileB.className = "tile"; tileB.draggable = true; tileB.dataset.key = "B";
  tileB.innerHTML = `<span class="label">B</span> <span>${textForB}</span>`;
  tileB.addEventListener("click", ()=>clickPlace(tileB));
  cand.appendChild(tileB);

  // Reset zones & feedback
  state.placed = { H0:null, H1:null };
  $("#dropH0").innerHTML = `<span class="slot">H₀</span>`;
  $("#dropH1").innerHTML = `<span class="slot">H₁</span>`;
  $("#feedback").innerHTML = "";
  $("#submitBtn").disabled = true;
  $("#submitBtn").classList.remove("attn");

  $("#roundBox").textContent = `Round: ${state.round} / ${state.maxRounds}`;

  enableDnD();
  observeZones();
  recalcPlacements();
}

function enableDnD(){
  const tiles = document.querySelectorAll(".tile");
  const zones = [$("#dropH0"), $("#dropH1")];

  tiles.forEach(t=>{
    t.addEventListener("dragstart", e=>{
      t.classList.add("dragging");
      e.dataTransfer.setData("text/plain", t.dataset.key);
      if(e.dataTransfer.setDragImage){
        const ghost = t.cloneNode(true);
        ghost.style.position="absolute"; ghost.style.top="-9999px"; document.body.appendChild(ghost);
        e.dataTransfer.setDragImage(ghost, 10, 10);
        setTimeout(()=>document.body.removeChild(ghost),0);
      }
      setTimeout(()=>t.style.opacity="0.6",0);
    });
    t.addEventListener("dragend", ()=>{
      t.classList.remove("dragging"); t.style.opacity="1";
      recalcPlacements();
    });
  });

  zones.forEach(z=>{
    z.addEventListener("dragover", e=>{ e.preventDefault(); z.classList.add("over"); });
    z.addEventListener("dragleave", ()=> z.classList.remove("over"));
    z.addEventListener("drop", e=>{
      e.preventDefault(); z.classList.remove("over");
      const key = e.dataTransfer.getData("text/plain");
      const tile = [...document.querySelectorAll(".tile")].find(t=>t.dataset.key===key);
      if(!tile) return;
      z.querySelectorAll(".tile").forEach(n=> n.remove()); // one per zone
      z.appendChild(tile);
      autoFillOpposite();
      recalcPlacements();
    });
  });
}

// Auto-place the remaining tile into the empty zone if one is set
function autoFillOpposite(){
  const h0 = $("#dropH0"), h1 = $("#dropH1");
  const tileA = [...document.querySelectorAll(".tile")].find(t=>t.dataset.key==="A");
  const tileB = [...document.querySelectorAll(".tile")].find(t=>t.dataset.key==="B");
  const h0Has = !!h0.querySelector(".tile");
  const h1Has = !!h1.querySelector(".tile");

  if(h0Has && !h1Has){
    const other = h0.contains(tileA) ? tileB : tileA;
    if(other && !h1.contains(other)){ h1.appendChild(other); }
  } else if(h1Has && !h0Has){
    const other = h1.contains(tileA) ? tileB : tileA;
    if(other && !h0.contains(other)){ h0.appendChild(other); }
  }
}

// Click-to-place: toggles into next empty slot, with auto opposite fill
function clickPlace(tile){
  const h0 = $("#dropH0"), h1 = $("#dropH1"), pool = $("#candidates");
  const inH0 = h0.contains(tile), inH1 = h1.contains(tile);
  if(inH0 || inH1){ pool.appendChild(tile); }
  else if(!h0.querySelector(".tile")){ h0.appendChild(tile); }
  else if(!h1.querySelector(".tile")){ h1.appendChild(tile); }
  else { pool.appendChild(tile); }
  autoFillOpposite();
  recalcPlacements();
}

// Observe zone mutations so Submit updates even on odd DnD behaviors
let zoneObserver;
function observeZones(){
  if(zoneObserver) zoneObserver.disconnect();
  zoneObserver = new MutationObserver(()=>{ autoFillOpposite(); recalcPlacements(); });
  zoneObserver.observe($("#dropH0"), { childList:true, subtree:false });
  zoneObserver.observe($("#dropH1"), { childList:true, subtree:false });
}

// Recompute placements + enable/disable submit strictly by occupancy (not correctness)
function recalcPlacements(){
  const h0Tile = $("#dropH0 .tile");
  const h1Tile = $("#dropH1 .tile");
  state.placed.H0 = h0Tile ? h0Tile.dataset.key : null;
  state.placed.H1 = h1Tile ? h1Tile.dataset.key : null;

  const ready = Boolean(state.placed.H0 && state.placed.H1);
  const btn = $("#submitBtn");
  btn.disabled = !ready;
  btn.classList.toggle("attn", ready);
}

/* =========================
   GRADING / SCORING
   ========================= */
function grade(manual = true, reason = ""){
  if(state.ticking){ clearInterval(state.ticking); state.ticking = null; }

  const corr = state.correctRound;
  if(!corr || (!corr.A && !corr.B)){ toast("Spin the wheel to start."); return; }

  let correct = false;
  const isH0Correct = state.placed.H0 && corr[state.placed.H0]==="H0";
  const isH1Correct = state.placed.H1 && corr[state.placed.H1]==="H1";
  correct = Boolean(isH0Correct && isH1Correct);

  // Wild auto swap
  if(state.wildAvailable && !correct){
    const swapIsH0 = state.placed.H1 && corr[state.placed.H1]==="H0";
    const swapIsH1 = state.placed.H0 && corr[state.placed.H0]==="H1";
    if(swapIsH0 && swapIsH1){ correct = true; reason = (reason?reason+" • ":"") + "Wild-SWAP auto-correct!"; state.wildAvailable=false; consumePU("wild"); }
  }

  let base = (state.wheelValue && state.wheelValue.type==="points") ? state.wheelValue.value : 0;
  const speedBonus = state.speedMode && state.timer>=8 ? 50 : 0;

  let delta = 0;
  if(correct){
    state.streak = (state.streak||0)+1;
    delta = base + 100 + speedBonus + Math.min(100, state.streak*20);
    if(state.doubleMode){ delta *= 2; consumePU("double"); state.doubleMode = false; }
    toast(`✅ Correct! +${delta}`, 1800);
  } else {
    if(state.shieldOn){ state.shieldOn=false; consumePU("shield"); delta = 0; toast("🛡️ Wrong, but SHIELD blocked the penalty.",1600); }
    else { delta = -50; state.streak = 0; toast("❌ Incorrect (-50).", 1500); }
  }

  state.score += delta; $("#scoreBox").textContent = `Score: ${state.score}`;

  // FEEDBACK: replace (don’t append) with current round only
  const fb = $("#feedback");
  const keyLine = `A = ${corr.A || '—'}, B = ${corr.B || '—'}`;
  const q = state.currentQ;
  fb.innerHTML = `
    <div class="${correct ? 'correct':'wrong'}">
      ${correct ? "You matched H₀/H₁ correctly." : "Mismatch on H₀/H₁."}
      ${reason ? " — " + reason : ""}
    </div>
    <div class="hint" style="margin-top:6px">
      <b>Answer key:</b> ${keyLine} • <b>Test:</b> ${q.test} (${q.tails}-tailed)
    </div>`;

  state.round += 1;
  $("#roundBox").textContent = `Round: ${Math.min(state.round,state.maxRounds)} / ${state.maxRounds}`;
  const btn = $("#submitBtn"); btn.disabled = true; btn.classList.remove("attn");

  setTimeout(()=>{
    if(state.round>state.maxRounds){ endGame(); }
    else {
      state.wheelSpun = false; state.wheelValue = null; state.speedMode = false;
      startSpinPhaseUI();
    }
  }, 1400);
}

function consumePU(name){
  const idx = state.powerups.indexOf(name);
  if(idx>-1) state.powerups.splice(idx,1);
}

/* =========================
   UI HOOKS
   ========================= */
function startSpinPhaseUI(){
  $("#qTitle").textContent = `Spin for Round ${state.round}`;
  $("#qMeta").textContent = "";
  $("#qBox").hidden = true;
  $("#submitBtn").classList.remove("attn");
  $("#spinBtn").disabled = false;
}

$("#spinBtn").addEventListener("click", spinWheel);
$("#submitBtn").addEventListener("click", ()=>{
  const ready = state.placed.H0 && state.placed.H1;
  if(!ready){ toast("Place A/B into H₀ and H₁ first."); return; }
  grade(true);
});
$("#hintBtn").addEventListener("click", ()=> toast("Hint: H₀ usually contains equality (status quo). H₁ claims change/effect.", 2200));
$("#skipBtn").addEventListener("click", ()=>{
  state.score -= 50; $("#scoreBox").textContent = `Score: ${state.score}`;
  toast("Skipped (-50). Moving on…", 1200); grade(false, "Skipped");
});
$("#powerBtn").addEventListener("click", ()=>{
  if(state.powerups.length===0) { toast("No power-ups available."); return; }
  const next = state.powerups[0];
  if(next==="shield"){ state.shieldOn = true; toast("🛡️ SHIELD armed for next wrong."); }
  if(next==="double"){ state.doubleMode = true; toast("✖️2 DOUBLE will apply this round."); }
  if(next==="wild"){ state.wildAvailable = true; toast("🃏 WILD ready: auto-swap if it makes you correct."); }
});
$("#resetBtn").addEventListener("click", ()=>{
  localStorage.removeItem("hypohero_highscores");
  resetGame();
});

/* =========================
   ENDGAME / LEADERBOARD
   ========================= */
function endGame(){
  $("#qTitle").textContent = "Game Over!";
  $("#qMeta").textContent = "";
  $("#qBox").hidden = true;
  if(state.ticking){ clearInterval(state.ticking); }
  const name = prompt("Enter name for leaderboard:", "Player");
  const rec = { name: name || "Player", score: state.score, ts: Date.now() };
  const key = "hypohero_highscores";
  const list = JSON.parse(localStorage.getItem(key) || "[]");
  list.push(rec); list.sort((a,b)=>b.score-a.score);
  localStorage.setItem(key, JSON.stringify(list.slice(0,10)));
  renderBoard(); toast("Saved to leaderboard. Reset to play again!");
}
function renderBoard(){
  const key = "hypohero_highscores";
  const list = JSON.parse(localStorage.getItem(key) || "[]");
  if(list.length===0){ $("#leaderboard").innerHTML = "<b>Leaderboard:</b> No scores yet. Be the first!"; return; }
  const rows = list.map((r,i)=>`<tr><td>${i+1}</td><td>${escapeHtml(r.name)}</td><td style="text-align:right">${r.score}</td></tr>`).join("");
  $("#leaderboard").innerHTML = `
    <b>Leaderboard (Top 10)</b>
    <div class="divider"></div>
    <table style="width:100%; border-collapse:collapse">
      <thead><tr><th style="text-align:left">#</th><th style="text-align:left">Player</th><th style="text-align:right">Score</th></tr></thead>
      <tbody>${rows}</tbody>
    </table>`;
}

/* =========================
   STARTUP / RESET
   ========================= */
function resetGame(){
  state.round = 1; state.maxRounds = 10; state.score = 0; state.timer = 30;
  state.wheelSpun = false; state.powerups = []; state.currentPU = null; state.wheelValue = null;
  state.drawn = []; state.currentQ = null; state.placed = {H0:null,H1:null};
  state.speedMode = false; state.shieldOn = false; state.doubleMode = false; state.wildAvailable = false; state.streak = 0;
  state.correctRound = {A:null, B:null};
  $("#scoreBox").textContent = "Score: 0";
  $("#roundBox").textContent = "Round: 1 / 10";
  $("#timerBox").textContent = "Time: 00:30";
  // CLEAR UI AREAS
  $("#qMeta").textContent = "";
  $("#qText").textContent = "";
  $("#candidates").innerHTML = "";
  $("#dropH0").innerHTML = `<span class="slot">H₀</span>`;
  $("#dropH1").innerHTML = `<span class="slot">H₁</span>`;
  $("#feedback").innerHTML = "";
  $("#qBox").hidden = true;
  $("#submitBtn").disabled = true;
  $("#submitBtn").classList.remove("attn");
  startSpinPhaseUI();
  renderBoard();
}
resetGame();

/* =========================
   Tiny SFX (optional)
   ========================= */
(function(){
  const ctx = new (window.AudioContext||window.webkitAudioContext)();
  function beep(freq=880, dur=0.05, type="square", vol=0.05){
    const o = ctx.createOscillator(); const g = ctx.createGain();
    o.type=type; o.frequency.setValueAtTime(freq, ctx.currentTime);
    g.gain.value = vol; o.connect(g); g.connect(ctx.destination);
    o.start(); o.stop(ctx.currentTime+dur);
  }
  ["spinBtn","submitBtn","hintBtn","skipBtn","powerBtn","resetBtn"].forEach(id=>{
    const btn = document.getElementById(id); if(!btn) return;
    btn.addEventListener("click", ()=>beep(600,0.045,"sine",0.03));
  });
})();
</script>
</body>
</html>