/* ══════════════════════════════════════════════════════════
   home.jsx — 心靈庭園 home hub
   12 quest cards (L/R) · 3D watercolor brain (center) ·
   parchment task description (bottom)
══════════════════════════════════════════════════════════ */

const { useEffect, useRef, useState, useMemo } = React;

/* Test catalog — mirrors the 12 games, with brain regions */
const QUEST = [
  { id:0,  side:'L', num:'01', story:'記憶燈籠',   name:'N-Back 測驗',    en:'N-Back Task',
    glyph:'◈', domain:'memory',      dl:'工作記憶',     lobe:'frontal', time:'8 min', trials:'240',
    region:'Right DLPFC (BA 9/46)',  regionLabel:'右背外側前額葉',
    brainPos:{side:'R', x:.72, y:.30},
    desc:'看見同樣的燈光悄然浮現 — 若此燈與 2 盞前相同，輕輕召回它。受試者持續監看刺激序列，測量工作記憶的容量、更新速度與操控能力，為前額葉背外側功能的核心指標。',
    met:['正確率 Accuracy','d′ 感度指數','反應時間 RT','虛報率 FA Rate'] },

  { id:1,  side:'L', num:'02', story:'彩墨卷軸',   name:'Stroop 測驗',    en:'Stroop Color-Word',
    glyph:'⊗', domain:'inhibition',  dl:'認知抑制',     lobe:'frontal', time:'5 min', trials:'120',
    region:'dACC / mid-cingulate (BA 24/32)', regionLabel:'前扣帶皮質',
    brainPos:{side:'C', x:.50, y:.38},
    desc:'墨色書寫的字可能騙你 — 說出它的顏色，忽略它的意思。不一致與一致條件的反應時間差為 Stroop 效應量，反映前扣帶皮質介導的干擾控制能力。',
    met:['Stroop 效應量 (ms)','一致 vs. 不一致 RT','整體正確率'] },

  { id:2,  side:'L', num:'03', story:'音符飛鳥',   name:'數字記憶廣度',   en:'Digit Span',
    glyph:'≡', domain:'memory',      dl:'短期記憶',     lobe:'temporal', time:'6 min', trials:'20 組',
    region:'Left STG / IFG — phonological loop (BA 44)', regionLabel:'左顳上迴',
    brainPos:{side:'L', x:.28, y:.58},
    desc:'神鳥為你歌唱一串數字 — 先依序複誦，再由後往前回聲。正向測短期記憶容量；逆向需工作記憶主動操控。廣度差值解離注意資源分配與純記憶容量限制。',
    met:['正向廣度','逆向廣度','廣度差值 Bwd−Fwd','序列正確率'] },

  { id:3,  side:'L', num:'04', story:'分類花園',   name:'威斯康辛卡片',  en:'WCST Card Sort',
    glyph:'⟳', domain:'flexibility', dl:'認知彈性',     lobe:'frontal', time:'15 min', trials:'128',
    region:'Left DLPFC (BA 9/46)', regionLabel:'左背外側前額葉',
    brainPos:{side:'L', x:.30, y:.28},
    desc:'花床之間的分類規則悄悄改變 — 只有幼苗的回饋能告訴你。依顏色、形狀或數量分類；連對後規則無預告切換。堅持錯誤數反映前額葉認知彈性與抽象推理。',
    met:['完成類別數','堅持錯誤','非堅持錯誤','失敗維持'] },

  { id:4,  side:'L', num:'05', story:'螢火蟲小徑', name:'連線測驗 A/B',  en:'Trail Making A / B',
    glyph:'⟿', domain:'flexibility', dl:'處理速度',     lobe:'parietal', time:'5 min', trials:'2 Parts',
    region:'Right SPL (BA 7) + Frontal', regionLabel:'右頂上小葉',
    brainPos:{side:'R', x:.68, y:.20},
    desc:'夜霧森林中的螢火蟲為你標記路徑 — Part A 依序 1→25；Part B 交替數字—漢字 1→甲→2→乙... B−A 時間差獨立反映集合切換，排除純動作速度影響。',
    met:['Part A 時間','Part B 時間','B−A 彈性指標','錯誤次數'] },

  { id:5,  side:'L', num:'06', story:'森林精靈',   name:'Go/No-Go 測驗', en:'Go / No-Go Task',
    glyph:'⊕', domain:'inhibition',  dl:'反應抑制',     lobe:'frontal', time:'8 min', trials:'300',
    region:'Right IFG (BA 44/45)', regionLabel:'右下額迴',
    brainPos:{side:'R', x:.78, y:.48},
    desc:'綠燈精靈出現就點 — 紅燈精靈千萬別點。高頻率 Go 建立反應優勢後，隨機插入 No-Go 要求完全抑制。委任錯誤率反映反應抑制缺陷，廣泛應用於 ADHD 評估。',
    met:['委任錯誤','遺漏錯誤','Go RT 均值/SD','d′ Signal Detection'] },

  { id:6,  side:'R', num:'07', story:'候鳥箭矢',   name:'Flanker 測驗',  en:'Eriksen Flanker',
    glyph:'←→', domain:'attention',  dl:'選擇性注意',    lobe:'frontal', time:'6 min', trials:'240',
    region:'pre-SMA / superior ACC (BA 6)', regionLabel:'前輔助運動區',
    brainPos:{side:'C', x:.50, y:.22},
    desc:'五隻候鳥齊飛 — 只看中央那隻的方向。辨別中央箭頭方向，忽略旁側干擾。一致與不一致試次的 RT 差為 Flanker 效應量，反映 pre-SMA 衝突監控效率。',
    met:['Flanker 效應量','一致/不一致正確率差','RT 中位數','速度-正確率權衡'] },

  { id:7,  side:'R', num:'08', story:'魔法石塔',   name:'倫敦塔測驗',    en:'Tower of London',
    glyph:'△', domain:'executive',   dl:'計畫',         lobe:'frontal', time:'12 min', trials:'20 題',
    region:'Left frontopolar PFC (BA 10)', regionLabel:'左額極前額葉',
    brainPos:{side:'L', x:.36, y:.18},
    desc:'月光魔陣給你目標排列 — 在最少步數內完成。評估前瞻計畫、目標維持與問題解決策略。對前額葉損傷高度敏感；計畫潛時與最優解比率為主要指標。',
    met:['總分','最優解比率','計畫潛時','執行時間'] },

  { id:8,  side:'R', num:'09', story:'水晶雙生',   name:'心理旋轉測驗',  en:'Mental Rotation',
    glyph:'⟲', domain:'visuospatial',dl:'視空間處理',    lobe:'parietal', time:'8 min', trials:'120',
    region:'Right superior parietal (BA 7)', regionLabel:'右頂上小葉',
    brainPos:{side:'R', x:.74, y:.24},
    desc:'雲海中兩顆水晶以不同角度漂浮 — 它們是同一塊嗎？Shepard & Metzler 典範：判斷 3D 物體是否相同。RT 與角度差呈線性關係，斜率反映心智意象旋轉效率。',
    met:['正確率','Angular Velocity','RT-角度斜率','鏡像辨別'] },

  { id:9,  side:'R', num:'10', story:'雷霆止息',   name:'停止信號測驗',  en:'Stop-Signal Task',
    glyph:'⊘', domain:'inhibition',  dl:'反應抑制',     lobe:'frontal', time:'10 min', trials:'320',
    region:'Right rIFG (BA 44) + subthalamic', regionLabel:'右下額迴',
    brainPos:{side:'R', x:.82, y:.52},
    desc:'看見箭頭就奔跑 — 聽見雷鳴立刻止步。Go 後隨機出現 Stop 訊號，階梯追蹤 SSD，演算法估算隱性 SSRT，比 Go/No-Go 更精確量化神經抑制效率。',
    met:['SSRT','Stop Failure Rate','Go RT 均值/SD','SSD 追蹤軌跡'] },

  { id:10, side:'R', num:'11', story:'星之三光',   name:'注意力網路測驗',en:'Attention Network (ANT)',
    glyph:'✦', domain:'attention',   dl:'三網路注意力', lobe:'parietal', time:'20 min', trials:'288',
    region:'Right TPJ / inferior parietal (BA 39/40)', regionLabel:'右顳頂交界',
    brainPos:{side:'R', x:.76, y:.38},
    desc:'三顆守護星引導你的目光 — 警覺、定向、執行控制。Fan et al. 2002 典範整合 Posner 線索與 Flanker，單一測驗解離三個注意力網路的效率。',
    met:['Alerting Effect','Orienting Effect','Executive Control','整體 RT'] },

  { id:11, side:'R', num:'12', story:'商人寶匣',   name:'愛荷華賭博任務',en:'Iowa Gambling Task',
    glyph:'♠', domain:'executive',   dl:'決策',         lobe:'frontal', time:'12 min', trials:'100',
    region:'OFC / vmPFC (BA 11/12)', regionLabel:'眼眶額葉',
    brainPos:{side:'C', x:.50, y:.58},
    desc:'四個寶匣 — 有的即時誘人卻暗藏損失。Bechara et al. 1994 OFC 功能典範：A/B 高回報長期虧損，C/D 長期有利。OFC 損傷者持續選擇不利牌組。',
    met:['淨得分 (C+D)−(A+B)','有利選擇比率','後段比率','最終餘額'] },
];

const DOMAIN_COL = {
  memory:       '#4888c8',
  inhibition:   '#c05570',
  flexibility:  '#c8921c',
  attention:    '#7050c8',
  executive:    '#3aa878',
  visuospatial: '#c87030',
};
const LOBE_COL = {
  frontal:  '#e8b040',
  parietal: '#5090d8',
  temporal: '#40c490',
  occipital:'#a060e0',
};
const LOBE_LBL = {
  frontal:'前額葉', parietal:'頂葉', temporal:'顳葉', occipital:'枕葉',
};

/* ═════════ Watercolor brain (canvas 2D) ═════════ */
function BrainCanvas({active}){
  const ref = useRef();
  const rafRef = useRef();
  useEffect(()=>{
    const cvs = ref.current;
    const DPR = Math.min(window.devicePixelRatio||1, 2);
    function resize(){
      const r = cvs.parentElement.getBoundingClientRect();
      cvs.width = r.width * DPR;
      cvs.height = r.height * DPR;
      cvs.style.width = r.width+'px';
      cvs.style.height = r.height+'px';
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(cvs.parentElement);

    let t = 0;
    function draw(){
      t += 16;
      const ctx = cvs.getContext('2d');
      const W = cvs.width, H = cvs.height;
      ctx.clearRect(0,0,W,H);

      // soft background glow
      const bg = ctx.createRadialGradient(W/2, H*.5, 20, W/2, H*.5, W*.55);
      bg.addColorStop(0, 'rgba(252,248,230,.6)');
      bg.addColorStop(.6, 'rgba(200,220,180,.15)');
      bg.addColorStop(1, 'rgba(200,220,180,0)');
      ctx.fillStyle = bg; ctx.fillRect(0,0,W,H);

      // === BRAIN base silhouette (watercolor) ===
      const cx = W*.5, cy = H*.54;
      const rx = Math.min(W*.38, H*.42);
      const ry = rx * .82;

      // shadow (below brain)
      ctx.save();
      ctx.globalAlpha = .25;
      ctx.fillStyle = '#1e3c28';
      ctx.beginPath();
      ctx.ellipse(cx, cy + ry*.95, rx*.82, ry*.15, 0, 0, Math.PI*2);
      ctx.fill();
      ctx.restore();

      // watercolor washes — multiple overlapping ellipses
      const washes = [
        {r:[rx*1.02, ry*1.02], col:'rgba(232,200,170,.55)', ox:0, oy:0},
        {r:[rx*.98, ry*.95], col:'rgba(240,180,160,.4)', ox:-rx*.04, oy:-ry*.06},
        {r:[rx*.92, ry*.88], col:'rgba(212,152,132,.35)', ox:rx*.05, oy:ry*.03},
      ];
      washes.forEach(({r,col,ox,oy})=>{
        ctx.save();
        ctx.filter = 'blur(6px)';
        ctx.fillStyle = col;
        ctx.beginPath();
        ctx.ellipse(cx+ox, cy+oy, r[0], r[1], 0, 0, Math.PI*2);
        ctx.fill();
        ctx.restore();
      });

      // central fissure
      ctx.save();
      ctx.strokeStyle = 'rgba(120,80,70,.35)';
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.moveTo(cx, cy-ry*.92);
      ctx.bezierCurveTo(cx-4, cy-ry*.3, cx+5, cy+ry*.2, cx, cy+ry*.88);
      ctx.stroke();
      ctx.restore();

      // === gyri/sulci — hand-drawn ink lines ===
      ctx.strokeStyle = 'rgba(80,50,40,.45)';
      ctx.lineWidth = 1.4;
      ctx.lineCap = 'round';

      // Build a stable random-ish set of curves on each side
      const seeded = (seed)=>{
        let s = seed;
        return ()=>{ s = (s*9301+49297)%233280; return s/233280; };
      };
      const rng = seeded(42);

      for(let side of [-1, 1]){
        for(let i=0;i<24;i++){
          const a0 = rng() * Math.PI - Math.PI/2;
          const a1 = a0 + .25 + rng()*.35;
          const rr0 = rx * (.35 + rng()*.55);
          const rr1 = rr0 * (.85 + rng()*.2);
          const steps = 14;
          ctx.beginPath();
          for(let k=0;k<=steps;k++){
            const tt = k/steps;
            const ang = a0 + (a1-a0)*tt;
            const r = rr0 + (rr1-rr0)*tt;
            const px = cx + side * Math.cos(ang) * r * .98;
            const py = cy + Math.sin(ang) * r * ry/rx + (rng()-.5)*1.4;
            if(k===0) ctx.moveTo(px, py);
            else ctx.lineTo(px, py);
          }
          ctx.globalAlpha = .3 + rng()*.35;
          ctx.stroke();
        }
      }
      ctx.globalAlpha = 1;

      // brainstem
      ctx.fillStyle = 'rgba(180,130,105,.7)';
      ctx.beginPath();
      ctx.ellipse(cx, cy + ry*.82, rx*.12, ry*.14, 0, 0, Math.PI*2);
      ctx.fill();

      // paper-grain dots overlay on brain
      ctx.save();
      ctx.globalCompositeOperation = 'multiply';
      for(let i=0;i<40;i++){
        const a = Math.random()*Math.PI*2;
        const r = Math.sqrt(Math.random()) * rx*.9;
        const x = cx + Math.cos(a)*r;
        const y = cy + Math.sin(a)*r * ry/rx;
        ctx.fillStyle = 'rgba(80,40,30,'+(.03+Math.random()*.06)+')';
        ctx.beginPath(); ctx.arc(x, y, .8+Math.random()*1.2, 0, Math.PI*2); ctx.fill();
      }
      ctx.restore();

      // === region markers ===
      QUEST.forEach(q=>{
        const isActive = active === q.id;
        const hovered = false;
        // position in brain-relative coords
        let px, py;
        const xr = q.brainPos.x, yr = q.brainPos.y;
        // map xr in [0,1] onto [-rx*.85, rx*.85]
        px = cx + (xr - .5) * rx * 1.7;
        py = cy + (yr - .5) * ry * 1.7;
        // clamp so within ellipse
        const dx = (px - cx)/rx, dy = (py - cy)/ry;
        const d = Math.sqrt(dx*dx + dy*dy);
        if(d > .85){
          const k = .85/d;
          px = cx + (px - cx)*k;
          py = cy + (py - cy)*k;
        }

        const col = DOMAIN_COL[q.domain];
        if(isActive){
          // pulsing halo
          const pulse = .5 + Math.sin(t*.004)*.5;
          const haloR = 30 + pulse * 20;
          const hg = ctx.createRadialGradient(px, py, 0, px, py, haloR*2);
          hg.addColorStop(0, col + 'dd');
          hg.addColorStop(.3, col + '55');
          hg.addColorStop(1, col + '00');
          ctx.fillStyle = hg;
          ctx.fillRect(px-haloR*2, py-haloR*2, haloR*4, haloR*4);

          // ripple rings
          for(let k=0;k<3;k++){
            const rrr = ((t*.04 + k*40) % 60);
            const opa = Math.max(0, 1 - rrr/60);
            ctx.strokeStyle = col;
            ctx.globalAlpha = opa * .7;
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.arc(px, py, 8 + rrr, 0, Math.PI*2);
            ctx.stroke();
          }
          ctx.globalAlpha = 1;

          // inner dot
          ctx.fillStyle = col;
          ctx.beginPath(); ctx.arc(px, py, 9, 0, Math.PI*2); ctx.fill();
          ctx.fillStyle = '#fff';
          ctx.beginPath(); ctx.arc(px-3, py-3, 3, 0, Math.PI*2); ctx.fill();
        } else {
          // dimmer marker
          ctx.fillStyle = col + '55';
          ctx.beginPath(); ctx.arc(px, py, 4.5, 0, Math.PI*2); ctx.fill();
          ctx.strokeStyle = col + 'aa';
          ctx.lineWidth = 1;
          ctx.beginPath(); ctx.arc(px, py, 7, 0, Math.PI*2); ctx.stroke();
        }

        // number label for active
        if(isActive){
          ctx.font = "700 11px 'Nunito', sans-serif";
          ctx.fillStyle = '#fff';
          ctx.textAlign='center'; ctx.textBaseline='middle';
          ctx.fillText(q.num, px, py);
          // region name tooltip
          ctx.font = "600 12px 'Noto Serif TC', serif";
          ctx.fillStyle = '#f5f0e4';
          ctx.strokeStyle = 'rgba(30,40,20,.8)';
          ctx.lineWidth = 3;
          ctx.textAlign='center';
          const ty = py + (py < cy ? -22 : 22);
          ctx.strokeText(q.regionLabel, px, ty);
          ctx.fillText(q.regionLabel, px, ty);
        }
      });

      // floating petals
      for(let i=0;i<6;i++){
        const phase = i * 1.3;
        const x = (W*.08) + ((t*.015 + i*120) % (W*.9));
        const y = H*.25 + Math.sin(t*.0018 + phase) * H*.12;
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(t*.001 + phase);
        ctx.fillStyle = 'rgba(232,184,160,.55)';
        ctx.beginPath();
        ctx.ellipse(0, 0, 6, 2.8, 0, 0, Math.PI*2);
        ctx.fill();
        ctx.restore();
      }

      rafRef.current = requestAnimationFrame(draw);
    }
    draw();
    return ()=>{
      cancelAnimationFrame(rafRef.current);
      ro.disconnect();
    };
  }, [active]);

  return <canvas ref={ref}/>;
}

/* ═════════ Quest card ═════════ */
function QuestCard({q, active, onClick}){
  const ac = DOMAIN_COL[q.domain];
  const lc = LOBE_COL[q.lobe];
  return (
    <div className={'qcard'+(active?' active':'')}
      style={{'--qc-ac':ac, '--qc-lobe':lc}}
      onClick={onClick}>
      <div className="qc-top">
        <span className="qc-num">NO. {q.num}</span>
        <span className="qc-lobe">{LOBE_LBL[q.lobe]}</span>
      </div>
      <div className="qc-mid">
        <div className="qc-glyph">{q.glyph}</div>
        <div className="qc-names">
          <div className="qc-story">{q.story}</div>
          <div className="qc-name">{q.name}</div>
          <div className="qc-en">{q.en}</div>
        </div>
      </div>
      <div className="qc-bot">
        <span className="qc-domain">{q.dl}</span>
        <span className="qc-time">⏱ {q.time}</span>
      </div>
    </div>
  );
}

/* ═════════ Subject info — persists to localStorage, exposed on window ═════════ */
const todayISO = () => new Date().toISOString().slice(0,10);
function loadSubject(){
  const blank = { name:'', birthdate:'', gender:'' };
  try {
    const raw = localStorage.getItem('themynd.subject');
    if (!raw) return blank;
    const parsed = JSON.parse(raw);
    // Accept legacy { age, date } shape — drop the age field, keep date as birthdate if it exists
    return {
      name: parsed.name || '',
      birthdate: parsed.birthdate || '',
      gender: parsed.gender || '',
    };
  } catch { return blank; }
}
function computeAge(birthISO){
  if (!birthISO) return '';
  const d = new Date(birthISO);
  if (isNaN(d)) return '';
  const now = new Date();
  let age = now.getFullYear() - d.getFullYear();
  const mDiff = now.getMonth() - d.getMonth();
  if (mDiff < 0 || (mDiff === 0 && now.getDate() < d.getDate())) age--;
  return age >= 0 && age < 150 ? age : '';
}

/* ═════════ Home page ═════════ */
function Home({onLaunch}){
  const [sel, setSel] = useState(0);
  const [subject, setSubject] = useState(loadSubject);
  const [markers, setMarkers] = useState(() => (window.Markers ? window.Markers.getConfig() : {enabled:false, channelName:'sigmacog-markers'}));
  const [showMarkerPanel, setShowMarkerPanel] = useState(false);
  const q = QUEST[sel];
  const ac = DOMAIN_COL[q.domain];
  const age = computeAge(subject.birthdate);

  useEffect(() => {
    try { localStorage.setItem('themynd.subject', JSON.stringify(subject)); } catch {}
    // Expose enriched subject (with derived age + current test date) globally so results/CSV can read
    window.__themyndSubject = {
      ...subject,
      age: age === '' ? '' : String(age),
      testDate: todayISO(),
    };
  }, [subject, age]);

  // Sync marker config to the Markers singleton
  useEffect(() => {
    if (window.Markers) window.Markers.setConfig(markers);
  }, [markers]);

  const leftCards = QUEST.filter(t=>t.side==='L');
  const rightCards = QUEST.filter(t=>t.side==='R');

  const sfInputStyle = {
    background:'transparent',
    border:'none',
    borderBottom:'1px solid rgba(140,120,90,.35)',
    color:'var(--inkw)',
    fontFamily:'var(--serif)',
    fontSize:'.78rem',
    padding:'.15rem .2rem',
    outline:'none',
    width:90,
  };
  const sfLabelStyle = {
    fontSize:'.6rem',
    color:'var(--dim)',
    letterSpacing:'.1em',
    textTransform:'uppercase',
    fontFamily:'var(--round)',
    marginRight:6,
    fontWeight:700,
  };

  return (
    <div className="home">
      <header className="home-hdr">
        <div className="logo-θ">Θ</div>
        <div className="logo-tx">
          <div className="cn">心靈庭園</div>
          <div className="en">THEMynd · Cognitive Garden</div>
        </div>
        <div className="sep"></div>
        <div style={{
          display:'flex',alignItems:'center',gap:14,flexWrap:'nowrap',
          padding:'.2rem .6rem',
          background:'rgba(245,240,228,.55)',
          border:'1px solid rgba(140,120,90,.22)',
          borderRadius:3,
        }}>
          <label style={{display:'flex',alignItems:'center'}}>
            <span style={sfLabelStyle}>受測者</span>
            <input style={sfInputStyle} value={subject.name}
              onChange={e => setSubject(s => ({...s, name: e.target.value}))}
              placeholder="姓名" />
          </label>
          <label style={{display:'flex',alignItems:'center'}}>
            <span style={sfLabelStyle}>出生</span>
            <input style={{...sfInputStyle, width:120, colorScheme:'light'}} type="date"
              max={todayISO()}
              value={subject.birthdate}
              onChange={e => setSubject(s => ({...s, birthdate: e.target.value}))} />
            <span style={{
              marginLeft:8,fontSize:'.72rem',color:'var(--dim)',
              fontFamily:'var(--italic)',fontStyle:'italic',minWidth:42,
            }}>{age !== '' ? `${age} 歲` : ''}</span>
          </label>
          <label style={{display:'flex',alignItems:'center'}}>
            <span style={sfLabelStyle}>性別</span>
            <select style={{...sfInputStyle, width:60, cursor:'pointer'}}
              value={subject.gender}
              onChange={e => setSubject(s => ({...s, gender: e.target.value}))}>
              <option value="">—</option>
              <option value="男">男</option>
              <option value="女">女</option>
              <option value="其他">其他</option>
            </select>
          </label>
          <span style={{
            fontSize:'.65rem', color:'var(--dim)',
            fontFamily:'var(--italic)', fontStyle:'italic', letterSpacing:'.04em',
          }}>測驗日期 {todayISO()}</span>
          <button
            type="button"
            onClick={() => setShowMarkerPanel(v => !v)}
            title="EEG Event Markers"
            style={{
              marginLeft:6,
              background: markers.enabled ? 'linear-gradient(135deg,#d4931a,#f0c858)' : 'transparent',
              color: markers.enabled ? '#2c2416' : 'var(--dim)',
              border: '1px solid ' + (markers.enabled ? 'transparent' : 'rgba(140,120,90,.35)'),
              fontFamily:'var(--round)', fontSize:'.62rem', fontWeight:700,
              padding:'.15rem .5rem', cursor:'pointer', borderRadius:3,
              letterSpacing:'.08em',
            }}
          >⚙ EEG {markers.enabled ? 'ON' : 'OFF'}</button>
        </div>
        <div className="tag" style={{marginLeft:12}}>為孩子而生的神經心理評估旅程</div>
        <div className="r">
          <div className="stat"><span className="n">12</span><span className="l">Quests</span></div>
          <div className="stat"><span className="n">6</span><span className="l">Domains</span></div>
          <div className="stat"><span className="n">~115</span><span className="l">minutes</span></div>
        </div>
      </header>

      {showMarkerPanel && (
        <div style={{
          position:'absolute', top:72, right:24, zIndex:80,
          width:360, background:'var(--cream)',
          borderTop:'3px solid #d4931a',
          border:'1px solid rgba(140,120,90,.35)',
          borderRadius:4, padding:'1rem 1.2rem',
          boxShadow:'0 8px 28px rgba(30,40,20,.25)',
          fontFamily:'var(--serif)', color:'var(--inkw)',
          display:'flex', flexDirection:'column', gap:'.7rem',
        }}>
          <div style={{display:'flex',alignItems:'center',justifyContent:'space-between'}}>
            <div style={{fontSize:'.95rem',fontWeight:700,letterSpacing:'.06em'}}>EEG Event Markers</div>
            <button onClick={() => setShowMarkerPanel(false)} style={{
              background:'none',border:'none',color:'var(--dim)',cursor:'pointer',fontSize:'1.1rem',lineHeight:1,
            }}>×</button>
          </div>
          <div style={{fontSize:'.72rem',color:'var(--dim)',fontFamily:'var(--italic)',fontStyle:'italic',lineHeight:1.6}}>
            透過 BroadcastChannel 廣播事件，sigmacog 等 EEG 記錄端可同步接收 stimulus onset / response 標記。
          </div>
          <label style={{display:'flex',alignItems:'center',gap:'.6rem',cursor:'pointer'}}>
            <input type="checkbox" checked={markers.enabled}
              onChange={e => setMarkers(m => ({...m, enabled: e.target.checked}))}
              style={{accentColor:'#d4931a'}} />
            <span style={{fontSize:'.85rem'}}>啟用 Event Markers</span>
          </label>
          <label style={{display:'flex',alignItems:'center',gap:'.6rem'}}>
            <span style={{fontSize:'.78rem',color:'var(--dim)',minWidth:72}}>Channel</span>
            <input
              value={markers.channelName}
              onChange={e => setMarkers(m => ({...m, channelName: e.target.value}))}
              placeholder="sigmacog-markers"
              style={{
                flex:1, background:'var(--parch)', border:'1px solid var(--wcbdr)',
                color:'var(--inkw)', fontFamily:'var(--serif)', fontSize:'.82rem',
                padding:'.25rem .5rem', outline:'none', borderRadius:2,
              }}
            />
          </label>
          <div style={{
            fontSize:'.7rem',color:'var(--dim)',lineHeight:1.6,
            padding:'.55rem .7rem',
            background:'var(--parch)',border:'1px solid var(--wcbdr)',borderRadius:2,
          }}>
            <b>預設 Marker ID：</b>任務 × 10 + 101 (stim) / 102 (response) / 103 (feedback) / 100 (task_start) / 109 (task_end)。
            範例：N-Back stim = 101、response = 102；Stroop stim = 111、response = 112；IGT stim = 211、response = 212。
            個別測驗可在設定視窗覆寫。
          </div>
          {markers.enabled && !window.BroadcastChannel && (
            <div style={{
              fontSize:'.72rem',color:'#8a5018',padding:'.5rem .7rem',
              background:'rgba(212,147,26,.14)',border:'1px solid rgba(212,147,26,.45)',borderRadius:2,
            }}>⚠ 此瀏覽器不支援 BroadcastChannel，Markers 無法送出。</div>
          )}
        </div>
      )}

      <div className="home-main">
        <div className="qcol">
          {leftCards.map(q0 =>
            <QuestCard key={q0.id} q={q0} active={sel===q0.id} onClick={()=>setSel(q0.id)}/>
          )}
        </div>

        <div className="center-col">
          <div className="brain-card">
            <div className="brain-stage">
              <BrainCanvas active={sel}/>
            </div>
            <div className="brain-label">
              <div className="chap">NEURAL MAP · 神經地圖</div>
              <div className="tit">{q.regionLabel}</div>
            </div>
            <div className="brain-legend">
              {Object.entries({memory:'記憶', inhibition:'抑制', flexibility:'彈性', attention:'注意', executive:'執行', visuospatial:'視空間'}).map(([k,v])=>(
                <div key={k} className="li"><span className="d" style={{background:DOMAIN_COL[k]}}></span>{v}</div>
              ))}
            </div>
            <div className="brain-hint">點選左右任務卡片 · 觀察腦區亮起</div>
          </div>

          <div className="task-panel" style={{'--tp-ac':ac}}>
            <div className="tp-l">
              <div className="tp-head">
                <div className="tp-titleset">
                  <div className="tp-story">{q.story}</div>
                  <div className="tp-name">{q.name}</div>
                  <div className="tp-en">{q.en}</div>
                </div>
                <div className="tp-tags">
                  <span className="tp-tag dom">{q.dl}</span>
                  <span className="tp-tag">{LOBE_LBL[q.lobe]}</span>
                </div>
              </div>
              <div className="tp-desc">{q.desc}</div>
              <div className="tp-meta">
                <div className="mi"><b>⏱</b>{q.time}</div>
                <div className="mi"><b>試次</b>{q.trials}</div>
                <div className="mi"><b>領域</b>{q.dl}</div>
              </div>
            </div>
            <div className="tp-r">
              <div className="tp-section-lbl">腦區 · Target Region</div>
              <div className="tp-region"><b>{q.regionLabel}</b><br/>{q.region}</div>
              <div className="tp-section-lbl">主要指標 · Key Metrics</div>
              <div className="tp-metrics">
                {q.met.map((m,i)=>(
                  <div key={i} className="tp-metric"><span className="tick">✓</span>{m}</div>
                ))}
              </div>
              <button className="tp-start" onClick={()=>onLaunch && onLaunch(q)}>
                開始任務 <span className="arr">›</span>
              </button>
            </div>
          </div>
        </div>

        <div className="qcol">
          {rightCards.map(q0 =>
            <QuestCard key={q0.id} q={q0} active={sel===q0.id} onClick={()=>setSel(q0.id)}/>
          )}
        </div>
      </div>
    </div>
  );
}

window.Home = Home;
window.QUEST = QUEST;
window.DOMAIN_COL = DOMAIN_COL;
