/* ══════════════════════════════════════════════════════════
   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:'13', story:'星辰堆疊',   name:'Corsi 方塊輕敲', en:'Corsi Block-Tapping',
    glyph:'✦', domain:'memory',     dl:'視空間工作記憶', lobe:'parietal', time:'6 min', trials:'~16 序列',
    region:'Right posterior parietal / DLPFC', regionLabel:'右後頂葉 / 背外側前額葉',
    brainPos:{side:'R', x:.72, y:.18},
    desc:'九顆星點綴夜空 — 隨著星光亮起的順序，你得記住並依序回敲。Corsi 1972 典範：典型視空間 span 測驗；span 長度與空間工作記憶容量直接相關，對右頂葉與 DLPFC 損傷敏感。',
    met:['Corsi Span (最高)','總正確序列','Span × Correct 積分','序列正確率'] },

  { id:6,  side:'R', num:'14', story:'追光之舞',   name:'追蹤旋轉儀',  en:'Pursuit Rotor',
    glyph:'◐', domain:'attention',  dl:'視動協調',      lobe:'parietal', time:'4 min', trials:'5 回合',
    region:'Parietal-motor network', regionLabel:'頂葉-運動網路',
    brainPos:{side:'C', x:.50, y:.30},
    desc:'夜空中一顆光點沿圓弧游動 — 用游標緊隨它舞動。Pursuit Rotor 典範：測量持續性視動追蹤能力與學習曲線。on-target 時間比例反映小腦-頂葉協調，適合評估運動控制與注意資源。',
    met:['平均 on-target %','學習曲線斜率','最佳 trial %','累積 off-target 時間'] },

  { 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:'15', story:'靈鹿之徑',   name:'視角轉換測驗',  en:'Perspective Taking (SOT)',
    glyph:'⟡', domain:'visuospatial', dl:'空間方位',    lobe:'parietal', time:'6 min', trials:'12 題',
    region:'Right parietal / TPJ', regionLabel:'右頂葉 / 顳頂交界',
    brainPos:{side:'R', x:.78, y:.28},
    desc:'森林中的靈獸散落各處 — 站在某隻面向另一隻，請指出第三隻在你的哪個方向。Kozhevnikov & Hegarty SOT 典範：Mode A 俯瞰 / Mode B 親歷，解離 ego-centric 與 allo-centric 空間表徵。角度誤差反映右頂葉-TPJ 視角轉換效率。',
    met:['平均角度誤差 (°)','反應時間 RT','Mode A vs B 差','完成率'] },

  { 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, locked, lockReason}){
  const ac = DOMAIN_COL[q.domain];
  const lc = LOBE_COL[q.lobe];
  return (
    <div className={'qcard'+(active?' active':'')+(locked?' locked':'')}
      style={{
        '--qc-ac':ac, '--qc-lobe':lc,
        opacity: locked ? 0.45 : undefined,
        cursor:  locked ? 'not-allowed' : 'pointer',
        position:'relative',
      }}
      title={locked ? lockReason : undefined}
      onClick={() => { if (!locked) 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>
      {locked && (
        <div style={{
          position:'absolute', top:6, right:8,
          fontSize:'.62rem', letterSpacing:'.1em', fontWeight:700,
          padding:'2px 6px', borderRadius:3,
          background:'rgba(140,120,90,.85)', color:'#f5f0e4',
          fontFamily:'var(--round)', textTransform:'uppercase',
        }}>🔒</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);
  // ── artisebio-web bridge: plan tier + session subject prefill ──
  const [bridge, setBridge] = useState(() => ({
    plan: window.MyndscapeAPI?.getPlan?.() || 'free',
    session: window.MyndscapeAPI?.getSession?.() || null,
  }));
  useEffect(() => {
    if (!window.MyndscapeAPI) return;
    return window.MyndscapeAPI.subscribe(({plan, sessionInfo}) => {
      setBridge({plan, session: sessionInfo});
      // First time we get session info → prefill the subject form (only if blank)
      if (sessionInfo && (!subject.name || !subject.birthdate)){
        setSubject(s => ({
          name:      s.name      || sessionInfo.name      || '',
          birthdate: s.birthdate || sessionInfo.birthDate || '',
          gender:    s.gender    || sessionInfo.gender    || '',
        }));
      }
    });
  }, []);
  const planLabel = window.MyndscapeAPI?.planLabel?.() || '免費會員';

  const q = QUEST[sel];
  const ac = DOMAIN_COL[q.domain];
  const age = computeAge(subject.birthdate);

  // Quest gating helper — falls back to "all allowed" when bridge missing
  const canRun = (qid) => {
    if (!window.MyndscapeAPI) return true;
    return window.MyndscapeAPI.isQuestAllowed(qid);
  };
  const lockReason = (qid) => window.MyndscapeAPI?.questLockReason?.(qid) || null;

  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(),
    };
    // Also publish under the new global name used by myndscape-integration.js
    window.__myndscapeSubject = window.__themyndSubject;
  }, [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">MYNDScape</div>
          <div className="en">Map the mind. Find the gold.</div>
        </div>
        <div className="sep"></div>
        {/* Membership badge — surfaces tier from artisebio-web /auth/me */}
        <div title={
            bridge.plan === 'free'
              ? '免費會員：可使用 N-back / Stroop 基礎版（含 EEG 同步）'
              : bridge.plan === 'basic'
              ? 'Basic 會員：可使用全部 12 項測驗的基礎版'
              : 'Plus / Pro 會員：可使用全部 12 項測驗的所有等級'
          }
          style={{
            display:'inline-flex', alignItems:'center', gap:6,
            padding:'.2rem .55rem', borderRadius:3,
            fontSize:'.7rem', fontWeight:700, letterSpacing:'.1em',
            fontFamily:'var(--round)', textTransform:'uppercase',
            color: bridge.plan === 'free' ? '#8a7a60' : '#c87a10',
            background: bridge.plan === 'free' ? 'rgba(140,120,90,.12)' : 'rgba(212,147,26,.12)',
            border: `1px solid ${bridge.plan === 'free' ? 'rgba(140,120,90,.3)' : 'rgba(212,147,26,.4)'}`,
          }}>
          {planLabel}
        </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}
              locked={!canRun(q0.id)} lockReason={lockReason(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>
              {canRun(q.id) ? (
                <button className="tp-start" onClick={()=>onLaunch && onLaunch(q)}>
                  開始任務 <span className="arr">›</span>
                </button>
              ) : (
                <button className="tp-start" disabled
                  title={lockReason(q.id)}
                  style={{
                    opacity:0.5, cursor:'not-allowed',
                    background:'rgba(140,120,90,.25)', color:'#8a7a60', borderColor:'transparent',
                  }}>
                  🔒 {lockReason(q.id)}
                </button>
              )}
            </div>
          </div>
        </div>

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

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