/* =========================================================
   NEWTON — Physics
   Blueprint / physics-notebook aesthetic.
   ========================================================= */
const {useState, useEffect, useRef, useMemo, useCallback} = React;

/* ================= DATA ================= */

const BUNDLE = window.NEWTON_BUNDLE || null;
const SHALAVIM = BUNDLE ? BUNDLE.shalavim : null;
const CHARTER = BUNDLE ? BUNDLE.charter : null;
const I18N = BUNDLE ? BUNDLE.i18n : null;
const ATLAS = BUNDLE ? BUNDLE.atlas : null;
const CHECKPOINTS = BUNDLE ? BUNDLE.checkpoints : null;
const GATE_SEDARIM = [14, 34, 46, 64, 78, 94, 96];

// PHASES — bundle-aware. Kept compatible with existing references (id, role, topic, bl).
const PHASES = SHALAVIM ? SHALAVIM.map(s => ({
  id: s.id,
  role: s.role,
  topic: s.name,
  topic_he: s.name_he,
  bl: s.blurb,
  innerGoal: s.inner_goal,
  gateSeder: s.gate_seder,
  daysRange: s.days,
})) : [
  {id:1, role:'Observer',            topic:'Kinematics & Vectors',    bl:'Arrives at the phenomenon before the equation.'},
  {id:2, role:'Mechanic',            topic:'Forces & Energy',         bl:'Draws the free-body diagram before F = ma.'},
  {id:3, role:'Listener',            topic:'Waves & Oscillations',    bl:'Hears the pattern before reaching for ω.'},
  {id:4, role:'Conductor',           topic:'Electricity & Circuits',  bl:'Traces current before Kirchhoff.'},
  {id:5, role:'Field Reader',        topic:'Magnetism & Fields',      bl:'Sketches field lines before evaluating.'},
  {id:6, role:'Natural Philosopher', topic:'Modern Physics',          bl:'Treats physics as a story about models.'},
];

// Map seder concept_id → existing widget kind (preserves widgets per WIRING_SPEC).
const ARTIFACT_BY_CONCEPT = {
  concept_position_and_displacement: 'vector-walker',
  concept_vector_decomposition: 'vector-walker',
  concept_velocity_as_vector: 'vector-walker',
  concept_reading_kinematic_graphs: 'kinematics-plot',
  concept_kinematic_equations: 'kinematics-plot',
  concept_projectile_motion: 'newton-apple',
  concept_newtons_laws: 'newton-apple',
  concept_simple_harmonic_motion: 'pendulum',
  concept_shm_as_circular_projection: 'pendulum',
  concept_pendulum: 'pendulum',
  concept_mass_spring: 'spring-mass',
  concept_coulombs_law: 'coulomb',
  concept_electric_field: 'coulomb',
  concept_bohr_atom: 'einstein-elevator',
  concept_photoelectric_effect: 'einstein-elevator',
};
function pickArtifact(seder){
  const cid = seder?.spine?.concept_id;
  if (cid && ARTIFACT_BY_CONCEPT[cid]) return ARTIFACT_BY_CONCEPT[cid];
  if (seder?.shalav === 1) return 'vector-walker';
  if (seder?.shalav === 2) return 'newton-apple';
  if (seder?.shalav === 3) return 'pendulum';
  if (seder?.shalav === 4) return 'coulomb';
  if (seder?.shalav === 5) return 'coulomb';
  if (seder?.shalav === 6) return 'einstein-elevator';
  return 'vector-walker';
}

const SAMPLER_LESSONS = [
  {
    id:1, phase:1, title:'The First Sketch',
    sub:'Position, displacement, and the diagram-first rule',
    mentor:`Show me the picture. Sketch the walk before you compute anything. Every problem begins as a scene, not a symbol.`,
    video:{title:'Introduction to vectors — displacement vs distance', channel:'Khan Academy', url:'https://www.youtube.com/embed/ihNZlp7iUHE'},
    sections:[
      {h:'Why sketch first?', p:'Every problem in physics is a scene first, symbols second. A displacement is an arrow between two points — not a number. The arrow is what the symbols describe.'},
      {h:'Distance vs displacement', p:'Distance is the path. Displacement is the straight arrow from start to end. Walk 3 m east then 4 m north — distance is 7 m, displacement is 5 m at 53° north of east.'},
      {formula:'|Δr| = √(Δx² + Δy²)          θ = atan2(Δy, Δx)'},
      {callout:'Displacement can be <b>zero</b> when distance is not. Walk around a block and return home — distance is positive, displacement is zero. Scalar vs vector, always.'},
    ],
    artifact:'vector-walker',
    drill:{q:'Walk 5 m east then 12 m north. Displacement magnitude in metres?', a:13, tol:0.2, unit:'m', hint:'|Δr| = √(5² + 12²) — a 5-12-13 right triangle.'},
  },
  {
    id:2, phase:1, title:'Velocity & Acceleration',
    sub:'Position-time graphs, the three kinematic quantities',
    mentor:`Velocity, not speed. Direction matters. And write the units every step — a number without units is a guess pretending to be an answer.`,
    video:{title:'Average velocity and speed', channel:'Khan Academy', url:'https://www.youtube.com/embed/oRKxmXwLvUU'},
    sections:[
      {h:'Three quantities, three stories', p:'x(t) is where. v = dx/dt is how fast. a = dv/dt is how fast that changes. Each is the slope of the one before.'},
      {formula:'v = dx/dt          a = dv/dt          x(t) = x₀ + v₀t + ½at²'},
      {h:'Reading a graph', p:'A position-time curve that bends upward means speeding up in the positive direction. A v-t line with negative slope means deceleration.'},
      {callout:'The fourth kinematic: <b>v² = v₀² + 2a·Δx</b>. Reach for it when the question gives you start, end, and push — but not time.'},
    ],
    artifact:'kinematics-plot',
    drill:{q:'A ball dropped from 45 m reaches the ground after how long? (g = 9.81)', a:3.03, tol:0.1, unit:'s', hint:'45 = ½·9.81·t² → t = √(2·45/9.81).'},
  },
  {
    id:3, phase:2, title:"Newton's Laws",
    sub:'Three laws, free-body diagrams, F = ma',
    mentor:`Three laws, written in 1687, still govern every bridge, rocket, and falling apple. Before you write F = ma, draw the free-body.`,
    video:{title:"Newton's first law of motion", channel:'Khan Academy', url:'https://www.youtube.com/embed/CQYELiTtUs8'},
    sections:[
      {h:'First — inertia', p:'An object at rest stays at rest. An object in motion — straight line, constant speed — stays that way unless a net force acts. The default of the universe is uniform motion, not rest.'},
      {h:'Second — F = ma', p:'Net force equals mass times acceleration. Force is the cause, acceleration the effect, mass the rate of exchange.'},
      {formula:'∑F = m · a'},
      {h:'Third — reaction', p:'Every action has an equal and opposite reaction. Push the ground; the ground pushes you. Rockets work by throwing mass backwards.'},
    ],
    artifact:'newton-apple',
    drill:{q:'A 10 kg block on a 30° frictionless incline. Acceleration down the slope?', a:4.9, tol:0.1, unit:'m/s²', hint:'a = g·sin θ — no mass, no friction.'},
  },
  {
    id:4, phase:3, title:'Simple Harmonic Motion',
    sub:'Springs, pendula, the sine that binds them',
    mentor:`When restoring force is proportional to displacement, motion becomes a sine. Spring, pendulum, tuning fork, atom — the same shape.`,
    video:{title:'Introduction to simple harmonic motion', channel:'Khan Academy', url:'https://www.youtube.com/embed/Nk2q-_jkJVs'},
    sections:[
      {h:'The spring', p:'Pull a spring by x, it pulls back with F = −kx. Apply Newton and the differential equation solves to sine and cosine.'},
      {formula:'F = −kx          T = 2π √(m/k)'},
      {h:'The small-angle pendulum', p:'For small angles, a pendulum obeys the same shape. Period depends only on length and gravity — the bob mass cancels.'},
      {formula:'T = 2π √(L/g)'},
    ],
    artifact:'pendulum',
    drill:{q:'A 1 kg mass on a k = 20 N/m spring. Period?', a:1.4, tol:0.05, unit:'s', hint:'T = 2π·√(m/k).'},
  },
  {
    id:5, phase:4, title:"Coulomb's Law",
    sub:'Charge, field, inverse square',
    mentor:`Charges push and pull across empty space. The form is the same as Newton's gravity — 1/r² — different constant. The universe rhymes.`,
    video:{title:"Coulomb's law introduction", channel:'Khan Academy', url:'https://www.youtube.com/embed/Re7lrxwWkaU'},
    sections:[
      {h:'Inverse square', p:'Force drops as 1/r². Double the distance, force drops by four. Same shape as gravity, vastly larger constant.'},
      {formula:'F = k · q₁q₂ / r²          k ≈ 8.99×10⁹ N·m²/C²'},
      {h:'Like repels, unlike attracts', p:'The one place in nature where the pattern is simpler than social life.'},
    ],
    artifact:'coulomb',
    drill:{q:'Two +2 μC charges, 0.3 m apart. Force magnitude?', a:0.4, tol:0.02, unit:'N', hint:'F = 8.99e9·(2e-6)²/0.3².'},
  },
  {
    id:6, phase:6, title:'Einstein & Equivalence',
    sub:'Why free-fall looks like weightlessness',
    mentor:`Einstein's happiest thought: a person falling from a roof does not feel their weight. Gravity and acceleration are locally the same thing.`,
    video:{title:'The equivalence principle', channel:'Fermilab', url:'https://www.youtube.com/embed/RJOmlzewS6I'},
    sections:[
      {h:'The happiest thought', p:'Inside a falling elevator, a released apple does not fall relative to you — it floats. From inside, it is indistinguishable from deep space.'},
      {formula:'a_free-fall = g          (locally, gravity ≡ acceleration)'},
      {h:'Why it matters', p:'Once gravity equals acceleration locally, spacetime itself can curve. Mass tells spacetime how to bend; bent spacetime tells matter how to move.'},
      {callout:'The principle is <b>local</b>. Over large regions, tidal forces reveal real gravity. But in a small box for a short time, free-fall is weightlessness.'},
    ],
    artifact:'einstein-elevator',
    drill:{q:'An astronaut in low Earth orbit experiences what acceleration (rel. to Earth centre)?', a:8.7, tol:0.5, unit:'m/s²', hint:'Orbit is perpetual free-fall. g at ~400 km altitude.'},
  },
];

// LESSONS — when bundle present, all 96 sedarim mapped to lesson-card shape; full content kept in `_full` for SederOverlay.
function _sederShortMentor(s){
  if (!s.mentor_intro) return '';
  const first = String(s.mentor_intro).split('\n').filter(l => l.trim()).slice(0,2).join(' ');
  return first.length > 220 ? first.slice(0, 217) + '…' : first;
}
const LESSONS = (BUNDLE && BUNDLE.days) ? BUNDLE.days.map(d => ({
  id:       d.id,
  phase:    d.shalav,
  title:    d.title,
  sub:      d.subtitle || (d.spine?.concept_id ? d.spine.concept_id.replace(/^concept_/, '').replace(/_/g, ' ') : ''),
  mentor:   _sederShortMentor(d),
  video:    null,
  sections: [],
  artifact: pickArtifact(d),
  drill:    null,
  _full:    d,
  isGate:   GATE_SEDARIM.includes(d.id),
  type:     d.type || 'learn',
})) : SAMPLER_LESSONS;

const PROBLEMS = [
  {id:'q1', lesson:1, title:'The hiker',   diff:'easy',   xp:20, q:'Walk 600 m N, 800 m E, 300 m S. Displacement magnitude?',  a:854.4, tol:2,    unit:'m',     hint:'Net (Δx, Δy) = (800, 300).',        sol:'√(800² + 300²) ≈ 854.4 m'},
  {id:'q2', lesson:2, title:'Free fall',   diff:'easy',   xp:25, q:'Dropped from 80 m. Time to ground?',                       a:4.04,  tol:0.1,  unit:'s',     hint:'t = √(2h/g).',                      sol:'√(2·80/9.81) ≈ 4.04 s'},
  {id:'q3', lesson:3, title:'Atwood',      diff:'medium', xp:35, q:'2 kg and 3 kg on a pulley. Acceleration?',                 a:1.96,  tol:0.1,  unit:'m/s²',  hint:'(m₂−m₁)g / (m₁+m₂).',               sol:'(3−2)·9.81/5 ≈ 1.96 m/s²'},
  {id:'q4', lesson:4, title:'Spring T',    diff:'easy',   xp:25, q:'0.5 kg mass, k = 200 N/m. Period?',                        a:0.314, tol:0.02, unit:'s',     hint:'T = 2π√(m/k).',                     sol:'2π·√(0.5/200) ≈ 0.314 s'},
  {id:'q5', lesson:5, title:'Two charges', diff:'medium', xp:35, q:'+1 μC and +1 μC, 10 cm apart. Force?',                      a:0.899, tol:0.05, unit:'N',     hint:'F = kq₁q₂/r².',                     sol:'8.99e9·(1e-6)²/(0.1)² ≈ 0.9 N'},
  {id:'q6', lesson:6, title:'Orbit speed', diff:'hard',   xp:45, q:'Circular orbit at r = 7000 km. Orbital speed?',             a:7546,  tol:100,  unit:'m/s',   hint:'v = √(GM/r). GM⊕ ≈ 3.986e14.',      sol:'√(3.986e14/7e6) ≈ 7550 m/s'},
];

const BADGES = [
  {id:'b1',  n:'Observer',     ic:'∝', earned:true},
  {id:'b2',  n:'Vectors',      ic:'Σ', earned:true},
  {id:'b3',  n:'Free body',    ic:'⇵', earned:false},
  {id:'b4',  n:'Harmonic',     ic:'∿', earned:false},
  {id:'b5',  n:'Field',        ic:'⊙', earned:false},
  {id:'b6',  n:'Equivalence',  ic:'≡', earned:false},
  {id:'b7',  n:'Quanta',       ic:'ℏ', earned:false},
  {id:'b8',  n:'Light',        ic:'λ', earned:false},
  {id:'b9',  n:'Conserved',    ic:'∮', earned:false},
  {id:'b10', n:'Action',       ic:'∫', earned:false},
  {id:'b11', n:'Uncertainty',  ic:'Δ', earned:false},
  {id:'b12', n:'Final',        ic:'✺', earned:false},
];

/* ================= APP ================= */
function App(){
  const [theme,setTheme] = useState(()=>localStorage.getItem('nw_theme')||'dark');
  const [view,setView] = useState(()=>localStorage.getItem('nw_view')||'dash');
  const [phase,setPhase] = useState(1);
  const [xp,setXp] = useState(()=>+localStorage.getItem('nw_xp')||0);
  const [completed,setCompleted] = useState(()=>{ try{return JSON.parse(localStorage.getItem('nw_comp'))||{}}catch{return {}} });
  const [lessonOpen,setLessonOpen] = useState(null);
  const [problemOpen,setProblemOpen] = useState(null);
  const [toast,setToast] = useState(null);
  const [lang,setLang] = useState(()=>localStorage.getItem('newton_settings_v1_lang')||'he_en');

  useEffect(()=>{document.documentElement.dataset.theme = theme; localStorage.setItem('nw_theme',theme)},[theme]);
  useEffect(()=>{localStorage.setItem('nw_view',view)},[view]);
  useEffect(()=>{localStorage.setItem('nw_xp',xp)},[xp]);
  useEffect(()=>{localStorage.setItem('nw_comp',JSON.stringify(completed))},[completed]);
  useEffect(()=>{localStorage.setItem('newton_settings_v1_lang',lang)},[lang]);

  const award = (n,label)=>{setXp(x=>x+n); showToast(`+${n} XP · ${label||'earned'}`)};
  const showToast = (m)=>{setToast(m); setTimeout(()=>setToast(null),1800)};
  const markDone = (id)=>setCompleted(c=>({...c,[id]:true}));

  const rankIdx = Math.min(PHASES.length-1, Math.floor(xp/140));
  const nextRankXp = (rankIdx+1)*140;
  const t = makeT(lang);

  // Bundle-aware nav. When bundle present, expose Charter + Yoman.
  const navItems = BUNDLE
    ? [['dash','Home'],['lessons','Sedarim'],['problems','Problems'],['charter','Charter'],['yoman','Yoman'],['lab','Lab'],['games','Games'],['profile','Profile']]
    : [['dash','Home'],['lessons','Lessons'],['problems','Problems'],['lab','Lab'],['games','Games'],['profile','Profile']];

  return <div className="app">
    <header className="titlebar">
      <div className="brand">
        <span className="brand-mark"><span className="brand-dot"/></span>
        NEWTON
        <span className="brand-sub">{BUNDLE ? `${t('app.subtitle','PHYSICS · ISRAELI BAGRUT')} · ${BUNDLE.meta.total_sedarim} סדרים` : 'PHYSICS · ISRAELI BAGRUT'}</span>
      </div>

      <div className="tb-right">
        <span className="tb-rank">{PHASES[rankIdx].role}</span>
        <div className="xp-row">
          <div className="xp-track"><div className="xp-fill" style={{width:`${(xp%140)/140*100}%`}}/></div>
          <span className="xp-num">{xp}/{nextRankXp}</span>
        </div>
        {BUNDLE && <select className="tb-btn" value={lang} onChange={e=>setLang(e.target.value)} style={{padding:'4px 8px',fontFamily:'JetBrains Mono, monospace',fontSize:10,letterSpacing:1.2,textTransform:'uppercase'}}>
          <option value="en">EN</option>
          <option value="he_en">HE+EN</option>
        </select>}
        <button className="tb-btn" onClick={()=>setTheme(tt=>tt==='dark'?'light':'dark')}>{theme==='dark'?'◐ light':'◑ dark'}</button>
      </div>
    </header>

    <nav className="nav">
      {navItems.map(([k,l]) =>
        <button key={k} className={'nav-btn '+(view===k?'active':'')} onClick={()=>setView(k)}>{l}</button>
      )}
    </nav>

    <main className="main">
      {view==='dash'     && <Home xp={xp} phase={phase} setPhase={setPhase} onOpen={id=>setLessonOpen(id)} goto={setView} completed={completed}/>}
      {view==='lessons'  && <LessonList phase={phase} setPhase={setPhase} onOpen={id=>setLessonOpen(id)} completed={completed}/>}
      {view==='problems' && <ProblemList onOpen={id=>setProblemOpen(id)} completed={completed}/>}
      {view==='lab'      && <Lab onAward={award}/>}
      {view==='games'    && <Games onAward={award}/>}
      {view==='profile'  && <Profile xp={xp} rankIdx={rankIdx} completed={completed}/>}
      {view==='charter'  && <CharterView lang={lang}/>}
      {view==='yoman'    && <YomanEditor lang={lang}/>}
    </main>

    {lessonOpen !== null && <LessonOverlay
      lesson={LESSONS.find(d=>d.id===lessonOpen)}
      onClose={()=>setLessonOpen(null)}
      onDone={(id)=>{markDone('l'+id); award(50,'lesson complete'); setLessonOpen(null)}}
      done={!!completed['l'+lessonOpen]}
    />}
    {problemOpen && <ProblemOverlay
      p={PROBLEMS.find(c=>c.id===problemOpen)}
      onClose={()=>setProblemOpen(null)}
      onSolve={(pp)=>{markDone(pp.id); award(pp.xp,'problem solved'); setProblemOpen(null)}}
      done={!!completed[problemOpen]}
    />}
    {toast && <div className="toast">{toast}</div>}
  </div>;
}

/* i18n helper. Reads from BUNDLE.i18n[lang][dot.path]; falls back to provided default. */
function tLookup(pack, dotPath){
  if (!pack) return null;
  const parts = dotPath.split('.');
  let cur = pack;
  for (const p of parts){ if (cur && typeof cur === 'object' && p in cur) cur = cur[p]; else return null; }
  return cur;
}
function makeT(lang){
  return (key, fallback) => {
    const v = I18N ? tLookup(I18N[lang], key) : null;
    return v != null ? v : (fallback != null ? fallback : key);
  };
}

/* ================= HOME ================= */
function Home({xp, phase, setPhase, onOpen, goto, completed}){
  const next = LESSONS.find(s=>!completed['l'+s.id]) || LESSONS[0];
  const lessonCount = Object.keys(completed).filter(k=>k.startsWith('l')).length;
  const probCount = Object.keys(completed).filter(k=>k.startsWith('q')).length;
  return <div className="page">
    <div className="kicker">TODAY · <b>{PHASES[next.phase-1].topic}</b></div>

    <section className="hero">
      <NewtonRings/>
      <div className="title">{next.title}</div>
      <p className="lead">{next.sub}.</p>
      <div className="actions">
        <button className="btn primary" onClick={()=>onOpen(next.id)}>Begin lesson →</button>
        <button className="btn" onClick={()=>goto('games')}>Play a minigame</button>
      </div>
    </section>

    <div className="stats">
      <div className="stat"><div className="n">{lessonCount}</div><div className="l">Lessons done</div></div>
      <div className="stat"><div className="n">{probCount}</div><div className="l">Problems solved</div></div>
      <div className="stat"><div className="n">{xp}</div><div className="l">XP total</div></div>
    </div>

    <div className="section-title">Course phases</div>
    <div className="section-rule"/>
    <div className="phases">
      {PHASES.map(p =>
        <button key={p.id} className={'phase '+(phase===p.id?'active':'')} onClick={()=>setPhase(p.id)}>
          <div className="ix">PHASE {String(p.id).padStart(2,'0')}</div>
          <div className="role">{p.role}</div>
          <div className="topic">{p.topic}</div>
        </button>
      )}
    </div>

    <div className="section-title">All lessons</div>
    <div className="section-rule"/>
    <div className="card-grid">
      {LESSONS.map(d =>
        <article key={d.id} className={'card '+(completed['l'+d.id]?'done':'')} onClick={()=>onOpen(d.id)}>
          {completed['l'+d.id] && <div className="done-dot"/>}
          <div className="ix">DAY {String(d.id).padStart(2,'0')} · {PHASES[d.phase-1].role}</div>
          <div className="title">{d.title}</div>
          <div className="desc">{d.sub}.</div>
          <div className="meta"><span>~{d._full?.estimated_min ?? 25} min</span><span>{d.isGate ? <b style={{color:'var(--accent)'}}>gate</b> : d._full?.flags?.aha_beat_id ? <b>aha</b> : d._full?.flags?.risk_day ? <b>risk</b> : <b>{d.sections?.length ?? 0}</b>} {d.isGate || d._full?.flags?.aha_beat_id || d._full?.flags?.risk_day ? '' : (d.sections?.length === 1 ? 'section' : 'sections')}</span></div>
        </article>
      )}
    </div>
  </div>;
}

/* ================= LESSON LIST ================= */
function LessonList({phase, setPhase, onOpen, completed}){
  return <div className="page">
    <div className="section-title">Lessons</div>
    <div className="section-rule"/>
    <div className="section-sub">{LESSONS.length} of 96 · filtered by phase</div>
    <div className="phases">
      {PHASES.map(p =>
        <button key={p.id} className={'phase '+(phase===p.id?'active':'')} onClick={()=>setPhase(p.id)}>
          <div className="ix">PHASE {String(p.id).padStart(2,'0')}</div>
          <div className="role">{p.role}</div>
          <div className="topic">{p.topic}</div>
        </button>
      )}
    </div>
    <div className="card-grid">
      {LESSONS.filter(d=>d.phase===phase).map(d =>
        <article key={d.id} className={'card '+(completed['l'+d.id]?'done':'')} onClick={()=>onOpen(d.id)}>
          {completed['l'+d.id] && <div className="done-dot"/>}
          <div className="ix">DAY {String(d.id).padStart(2,'0')}</div>
          <div className="title">{d.title}</div>
          <div className="desc">{d.sub}.</div>
          <div className="meta"><span>~{d._full?.estimated_min ?? 25} min</span><span>{d.isGate ? <b style={{color:'var(--accent)'}}>gate</b> : d._full?.flags?.aha_beat_id ? <b>aha</b> : d._full?.flags?.risk_day ? <b>risk</b> : <b>{d.sections?.length ?? 0}</b>} {d.isGate || d._full?.flags?.aha_beat_id || d._full?.flags?.risk_day ? '' : (d.sections?.length === 1 ? 'section' : 'sections')}</span></div>
        </article>
      )}
      {LESSONS.filter(d=>d.phase===phase).length === 0 &&
        <div style={{padding:'30px 0',color:'var(--text-3)',fontStyle:'italic',gridColumn:'1/-1'}}>
          Phase {phase} lessons are in the pipeline. Full 96-lesson course content lives in <code>Newton/Course/07_DAYS/</code>.
        </div>
      }
    </div>
  </div>;
}

/* ================= LESSON OVERLAY ================= */
// Dispatcher — no hooks. Routes to bundle-seder or sampler overlay. Keeps hook order stable.
function LessonOverlay({lesson, onClose, onDone, done}){
  if (!lesson) return null;
  if (lesson._full) return <SederOverlay seder={lesson._full} lesson={lesson} onClose={onClose} onDone={onDone} done={done}/>;
  return <SamplerLessonOverlay lesson={lesson} onClose={onClose} onDone={onDone} done={done}/>;
}

function SamplerLessonOverlay({lesson, onClose, onDone, done}){
  const [step,setStep] = useState(0);
  const steps = ['Intro','Watch','Read','Practice','Check'];

  return <div className="overlay" onClick={(e)=>e.target.classList.contains('overlay') && onClose()}>
    <div className="modal">
      <div className="modal-head">
        <div>
          <div className="kicker">DAY {String(lesson.id).padStart(2,'0')} · {PHASES[lesson.phase-1].role}</div>
          <h2>{lesson.title}</h2>
          <div className="sub">{lesson.sub}</div>
        </div>
        <button className="modal-close" onClick={onClose}>×</button>
      </div>

      <div className="modal-steps">
        {steps.map((s,i) =>
          <button key={s} className={'step-tab '+(step===i?'on':step>i?'done':'')} onClick={()=>setStep(i)}>
            <span className="num">{String(i+1).padStart(2,'0')}</span>{s}
          </button>
        )}
      </div>

      <div className="modal-body">
        {step===0 && <>
          <div className="mentor-line">"{lesson.mentor}"</div>
          <h3>What you'll do</h3>
          <ul>
            <li><b>Watch</b> — ~6-minute video setting the scene.</li>
            <li><b>Read</b> — the sections below.</li>
            <li><b>Practice</b> — an interactive artifact. Move sliders. Watch the physics respond.</li>
            <li><b>Check</b> — one numerical problem with units.</li>
          </ul>
          <p style={{color:'var(--text-3)',fontSize:12,marginTop:20}}>Sketch before symbol. Keep a notebook open — the app accompanies it.</p>
        </>}

        {step===1 && lesson.video && <>
          <h3>{lesson.video.title}</h3>
          <div className="video-wrap">
            <iframe src={lesson.video.url} title={lesson.video.title} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen/>
          </div>
          <p style={{color:'var(--text-3)',fontSize:12}}>Source: {lesson.video.channel}. Watch once, then move on.</p>
        </>}

        {step===2 && <>
          <p className="lead">{lesson.sections[0]?.p}</p>
          {lesson.sections.slice(1).map((s,i) => {
            if (s.formula) return <div key={i} className="formula">{s.formula}</div>;
            if (s.callout) return <div key={i} className="callout" dangerouslySetInnerHTML={{__html:s.callout}}/>;
            return <React.Fragment key={i}><h3>{s.h}</h3><p>{s.p}</p></React.Fragment>;
          })}
        </>}

        {step===3 && <ArtifactWidget kind={lesson.artifact}/>}

        {step===4 && lesson.drill && <DrillWidget drill={lesson.drill} onSolve={()=>onDone(lesson.id)}/>}
      </div>

      <div className="modal-foot">
        <button className="btn" onClick={()=>setStep(Math.max(0,step-1))} disabled={step===0}>← Back</button>
        <div className="step-dots">
          {steps.map((s,i)=><div key={i} className={'step-dot '+(i<step?'done':i===step?'on':'')}/>)}
        </div>
        {step<steps.length-1
          ? <button className="btn primary" onClick={()=>setStep(step+1)}>Next →</button>
          : <button className="btn primary" onClick={()=>onDone(lesson.id)}>Mark complete ✓</button>}
      </div>
    </div>
  </div>;
}

/* ================= ARTIFACTS ================= */
function ArtifactWidget({kind}){
  if (kind==='vector-walker')     return <VectorWalker/>;
  if (kind==='kinematics-plot')   return <KinematicsPlot/>;
  if (kind==='newton-apple')      return <NewtonApple/>;
  if (kind==='pendulum')          return <PendulumArt/>;
  if (kind==='spring-mass')       return <SpringMassArt/>;
  if (kind==='coulomb')           return <CoulombArt/>;
  if (kind==='pv-diagram')        return <PVDiagram/>;
  if (kind==='einstein-elevator') return <EinsteinElevator/>;
  return <div className="artifact"><div className="artifact-kicker">Artifact</div><div>Coming soon.</div></div>;
}

/* ---- Vector Walker ---- */
function VectorWalker(){
  const [pts,setPts] = useState([{x:40,y:220},{x:160,y:220},{x:160,y:100}]);
  const [dragIx,setDragIx] = useState(null);
  const svgRef = useRef(null);
  const scale = 1/20;
  const onDown = (i) => (e) => {e.stopPropagation(); setDragIx(i)};
  const onMove = (e) => {
    if (dragIx===null || !svgRef.current) return;
    const r = svgRef.current.getBoundingClientRect();
    const scaleX = 420 / r.width, scaleY = 260 / r.height;
    const x = Math.max(20, Math.min(400, Math.round((e.clientX-r.left)*scaleX / 10)*10));
    const y = Math.max(20, Math.min(240, Math.round((e.clientY-r.top)*scaleY / 10)*10));
    setPts(pts.map((p,i)=>i===dragIx?{x,y}:p));
  };
  const onUp = () => setDragIx(null);

  const dist = pts.slice(1).reduce((s,p,i)=>s+Math.hypot(p.x-pts[i].x, p.y-pts[i].y), 0) * scale;
  const start = pts[0], end = pts[pts.length-1];
  const dx = (end.x-start.x)*scale, dy = -(end.y-start.y)*scale;
  const disp = Math.hypot(dx, dy);
  const ang = (Math.atan2(dy, dx)*180/Math.PI).toFixed(0);

  const addPt = () => {const last = pts[pts.length-1]; setPts([...pts,{x:Math.min(400,last.x+60),y:last.y}])};
  const reset = () => setPts([{x:40,y:220},{x:160,y:220},{x:160,y:100}]);

  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Walker on the grid</div>
        <div className="artifact-hint">Drag waypoints. Copper = displacement. White dashed = path.</div>
      </div>
      <div style={{display:'flex',gap:6}}>
        <button className="btn sm" onClick={addPt}>+ point</button>
        <button className="btn sm" onClick={reset}>reset</button>
      </div>
    </div>
    <div className="canvas">
      <svg ref={svgRef} viewBox="0 0 420 260" onPointerMove={onMove} onPointerUp={onUp} onPointerLeave={onUp}>
        <defs>
          <marker id="arr-disp" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
            <path d="M0,0 L10,5 L0,10 z" fill="var(--accent)"/>
          </marker>
          <marker id="arr-path" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
            <path d="M0,0 L10,5 L0,10 z" fill="var(--text)"/>
          </marker>
        </defs>
        {pts.slice(1).map((p,i)=>{
          const a = pts[i];
          return <line key={'seg'+i} x1={a.x} y1={a.y} x2={p.x} y2={p.y} stroke="var(--text)" strokeWidth="1" strokeDasharray="4 3" opacity=".55" markerEnd="url(#arr-path)"/>;
        })}
        <line x1={start.x} y1={start.y} x2={end.x} y2={end.y} stroke="var(--accent)" strokeWidth="2" markerEnd="url(#arr-disp)"/>
        {pts.map((p,i)=>
          <g key={'p'+i} onPointerDown={onDown(i)} style={{cursor:'grab'}}>
            <circle cx={p.x} cy={p.y} r="7" fill={i===0||i===pts.length-1?'var(--accent)':'var(--text-2)'} stroke="var(--bg)" strokeWidth="2"/>
            <text x={p.x+11} y={p.y-9} fontSize="10" fill="var(--text-3)">{i===0?'start':i===pts.length-1?'end':'p'+i}</text>
          </g>
        )}
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">Path distance</div><div className="v">{dist.toFixed(1)} m</div></div>
      <div className="readout"><div className="k">|Δr|</div><div className="v">{disp.toFixed(1)} m</div></div>
      <div className="readout"><div className="k">Angle (E)</div><div className="v">{ang}°</div></div>
    </div>
  </div>;
}

/* ---- Kinematics ---- */
function KinematicsPlot(){
  const [v0,setV0] = useState(5);
  const [a,setA] = useState(2);
  const tMax = 5, pts = 60;
  const data = Array.from({length:pts+1},(_,i)=>{const t=i*tMax/pts; return {t, x:v0*t+0.5*a*t*t, v:v0+a*t}});
  const maxX = Math.max(...data.map(d=>Math.abs(d.x)), 1);
  const maxV = Math.max(...data.map(d=>Math.abs(d.v)), 1);
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Position & velocity over time</div>
      </div>
    </div>
    <div className="knob"><label>v₀</label><input type="range" min="-10" max="10" step="0.5" value={v0} onChange={e=>setV0(+e.target.value)}/><span className="val">{v0.toFixed(1)}<span className="u">m/s</span></span></div>
    <div className="knob"><label>a</label><input type="range" min="-10" max="10" step="0.5" value={a} onChange={e=>setA(+e.target.value)}/><span className="val">{a.toFixed(1)}<span className="u">m/s²</span></span></div>
    <div className="canvas">
      <svg viewBox="0 0 420 220">
        <line x1="30" y1="110" x2="408" y2="110" stroke="var(--border)"/>
        <line x1="30" y1="10" x2="30" y2="210" stroke="var(--border)"/>
        <polyline points={data.map(d=>`${30+d.t/tMax*378},${110-d.x/maxX*90}`).join(' ')} stroke="var(--text)" strokeWidth="1.5" fill="none"/>
        <polyline points={data.map(d=>`${30+d.t/tMax*378},${110-d.v/maxV*90}`).join(' ')} stroke="var(--accent)" strokeWidth="1.5" fill="none" strokeDasharray="4 3"/>
        <text x="36" y="22" fontSize="10" fill="var(--text)">x(t)</text>
        <text x="36" y="36" fontSize="10" fill="var(--accent)">v(t) — — </text>
        <text x="402" y="224" fontSize="9" fill="var(--text-3)" textAnchor="end">t</text>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">x(5s)</div><div className="v">{data[pts].x.toFixed(1)} m</div></div>
      <div className="readout"><div className="k">v(5s)</div><div className="v">{data[pts].v.toFixed(1)} m/s</div></div>
      <div className="readout"><div className="k">Δx avg</div><div className="v">{(0.5*(v0+data[pts].v)*tMax).toFixed(1)} m</div></div>
    </div>
  </div>;
}

/* ---- Newton's apple ---- */
function NewtonApple(){
  const [g,setG] = useState(9.81);
  const [h,setH] = useState(5);
  const [t,setT] = useState(0);
  const [running,setRunning] = useState(false);
  useEffect(()=>{
    if (!running) return;
    const id = setInterval(()=>setT(x=>{
      const nx = x + 0.02;
      const y = 0.5*g*nx*nx;
      if (y >= h){setRunning(false); return Math.sqrt(2*h/g)}
      return nx;
    }),20);
    return ()=>clearInterval(id);
  },[running,g,h]);
  const fallen = Math.min(h, 0.5*g*t*t);
  const vy = g*t;
  const py = 40 + fallen/h*160;
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Newton's apple</div>
        <div className="artifact-hint">x = ½·g·t² — tap drop. The moon and the apple obey the same law.</div>
      </div>
      <button className="btn primary sm" onClick={()=>{setT(0); setRunning(true)}}>▶ drop</button>
    </div>
    <div className="knob"><label>height</label><input type="range" min="1" max="20" step="0.5" value={h} onChange={e=>setH(+e.target.value)}/><span className="val">{h.toFixed(1)}<span className="u">m</span></span></div>
    <div className="knob"><label>g</label><input type="range" min="1.6" max="24.8" step="0.1" value={g} onChange={e=>setG(+e.target.value)}/><span className="val">{g.toFixed(2)}<span className="u">m/s²</span></span></div>
    <div className="canvas">
      <svg viewBox="0 0 420 240">
        {/* tree */}
        <rect x="60" y="30" width="6" height="180" fill="var(--text-2)"/>
        <ellipse cx="63" cy="34" rx="44" ry="22" fill="none" stroke="var(--text-2)" strokeWidth="1"/>
        <ellipse cx="80" cy="28" rx="32" ry="18" fill="none" stroke="var(--text-2)" strokeWidth="1"/>
        {/* ground */}
        <line x1="0" y1="212" x2="420" y2="212" stroke="var(--border)"/>
        {/* Newton stick figure */}
        <g stroke="var(--text)" strokeWidth="1.2" fill="none">
          <circle cx="220" cy="180" r="7"/>
          <line x1="220" y1="187" x2="220" y2="206"/>
          <line x1="220" y1="194" x2="210" y2="203"/>
          <line x1="220" y1="194" x2="230" y2="203"/>
        </g>
        {/* apple */}
        <circle cx="63" cy={py} r="7" fill="var(--accent)"/>
        <line x1="63" y1={py-7} x2="66" y2={py-11} stroke="var(--text-2)"/>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">t</div><div className="v">{t.toFixed(2)} s</div></div>
      <div className="readout"><div className="k">y fallen</div><div className="v">{fallen.toFixed(2)} m</div></div>
      <div className="readout"><div className="k">v</div><div className="v">{vy.toFixed(2)} m/s</div></div>
    </div>
  </div>;
}

/* ---- Pendulum ---- */
function PendulumArt(){
  const [L,setL] = useState(1);
  const [amp,setAmp] = useState(20);
  const [t,setT] = useState(0);
  const T = 2*Math.PI*Math.sqrt(L/9.81);
  useEffect(()=>{const id = setInterval(()=>setT(x=>x+0.02),20); return ()=>clearInterval(id)},[]);
  const phase = Math.cos(2*Math.PI*t/T)*amp;
  const px = 210 + 110*L*Math.sin(phase*Math.PI/180);
  const py = 20 + 110*L*Math.cos(phase*Math.PI/180);
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Pendulum — T = 2π√(L/g)</div>
      </div>
    </div>
    <div className="knob"><label>length</label><input type="range" min="0.2" max="2" step="0.05" value={L} onChange={e=>setL(+e.target.value)}/><span className="val">{L.toFixed(2)}<span className="u">m</span></span></div>
    <div className="knob"><label>amplitude</label><input type="range" min="5" max="40" step="1" value={amp} onChange={e=>setAmp(+e.target.value)}/><span className="val">{amp}<span className="u">°</span></span></div>
    <div className="canvas">
      <svg viewBox="0 0 420 240">
        <line x1="0" y1="20" x2="420" y2="20" stroke="var(--border)"/>
        <line x1="210" y1="20" x2={px} y2={py} stroke="var(--text)" strokeWidth="1"/>
        <circle cx={px} cy={py} r="10" fill="var(--accent)"/>
        <circle cx="210" cy="20" r="2" fill="var(--text)"/>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">T</div><div className="v">{T.toFixed(2)} s</div></div>
      <div className="readout"><div className="k">f</div><div className="v">{(1/T).toFixed(2)} Hz</div></div>
      <div className="readout"><div className="k">ω</div><div className="v">{(2*Math.PI/T).toFixed(2)} rad/s</div></div>
    </div>
  </div>;
}

/* ---- Spring-mass (reserved) ---- */
function SpringMassArt(){
  const [m,setM] = useState(1);
  const [k,setK] = useState(20);
  const [t,setT] = useState(0);
  const T = 2*Math.PI*Math.sqrt(m/k);
  useEffect(()=>{const id = setInterval(()=>setT(x=>x+0.02),20); return ()=>clearInterval(id)},[]);
  const x = 40*Math.sin(2*Math.PI*t/T);
  const coilW = Math.max(20, 140 + x);
  const coilPath = Array.from({length:11},(_,i)=>`${40+(i/10)*coilW},${i%2===0 ? 70 : 100}`).join(' L ');
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Spring-mass — F = −kx</div>
      </div>
    </div>
    <div className="knob"><label>mass</label><input type="range" min="0.1" max="5" step="0.1" value={m} onChange={e=>setM(+e.target.value)}/><span className="val">{m.toFixed(1)}<span className="u">kg</span></span></div>
    <div className="knob"><label>k</label><input type="range" min="5" max="100" step="1" value={k} onChange={e=>setK(+e.target.value)}/><span className="val">{k}<span className="u">N/m</span></span></div>
    <div className="canvas">
      <svg viewBox="0 0 420 170">
        <rect x="20" y="50" width="8" height="70" fill="var(--text)"/>
        <path d={`M 30,85 L ${coilPath} L ${40+coilW},85`} stroke="var(--accent)" strokeWidth="1.5" fill="none" strokeLinejoin="round"/>
        <rect x={40+coilW} y="60" width="44" height="52" fill="none" stroke="var(--text)" strokeWidth="1.5"/>
        <text x={40+coilW+22} y="92" fontSize="11" fill="var(--text)" textAnchor="middle">{m.toFixed(1)}kg</text>
        <line x1="20" y1="130" x2="400" y2="130" stroke="var(--border)" strokeDasharray="3 3"/>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">T</div><div className="v">{T.toFixed(2)} s</div></div>
      <div className="readout"><div className="k">ω²</div><div className="v">{(k/m).toFixed(2)}</div></div>
      <div className="readout"><div className="k">x/A</div><div className="v">{(x/40).toFixed(2)}</div></div>
    </div>
  </div>;
}

/* ---- Coulomb ---- */
function CoulombArt(){
  const [q1,setQ1] = useState(2);
  const [q2,setQ2] = useState(-2);
  const [r,setR]   = useState(0.5);
  const k = 8.99e9;
  const F = k*q1*q2*1e-12/(r*r);
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Coulomb force — F = k·q₁q₂/r²</div>
      </div>
    </div>
    <div className="knob"><label>q₁ (μC)</label><input type="range" min="-5" max="5" step="0.5" value={q1} onChange={e=>setQ1(+e.target.value)}/><span className="val">{q1.toFixed(1)}</span></div>
    <div className="knob"><label>q₂ (μC)</label><input type="range" min="-5" max="5" step="0.5" value={q2} onChange={e=>setQ2(+e.target.value)}/><span className="val">{q2.toFixed(1)}</span></div>
    <div className="knob"><label>distance</label><input type="range" min="0.1" max="2" step="0.05" value={r} onChange={e=>setR(+e.target.value)}/><span className="val">{r.toFixed(2)}<span className="u">m</span></span></div>
    <div className="canvas">
      <svg viewBox="0 0 420 160">
        {[0,45,90,135,180,225,270,315].map(a=>{
          const rad = a*Math.PI/180;
          return <g key={a}>
            <line x1="80" y1="80" x2={80+Math.cos(rad)*32} y2={80+Math.sin(rad)*32} stroke="var(--text-3)" strokeWidth=".8"/>
            <line x1={80+r*150} y1="80" x2={80+r*150+Math.cos(rad)*32} y2={80+Math.sin(rad)*32} stroke="var(--text-3)" strokeWidth=".8"/>
          </g>;
        })}
        <circle cx="80" cy="80" r="18" fill="none" stroke={q1>=0?'var(--error)':'var(--text)'} strokeWidth="2"/>
        <text x="80" y="85" textAnchor="middle" fill={q1>=0?'var(--error)':'var(--text)'} fontSize="15">{q1>=0?'+':'−'}</text>
        <circle cx={80+r*150} cy="80" r="18" fill="none" stroke={q2>=0?'var(--error)':'var(--text)'} strokeWidth="2"/>
        <text x={80+r*150} y="85" textAnchor="middle" fill={q2>=0?'var(--error)':'var(--text)'} fontSize="15">{q2>=0?'+':'−'}</text>
        <line x1="100" y1="80" x2={60+r*150} y2="80" stroke="var(--accent)" strokeDasharray="3 3"/>
        <text x={80+r*75} y="70" textAnchor="middle" fontSize="10" fill="var(--accent)">r = {r.toFixed(2)} m</text>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">|F|</div><div className="v">{Math.abs(F).toFixed(3)} N</div></div>
      <div className="readout"><div className="k">direction</div><div className="v">{q1*q2>0?'repel':q1*q2<0?'attract':'–'}</div></div>
      <div className="readout"><div className="k">at 2r</div><div className="v">{(F/4).toFixed(3)} N</div></div>
    </div>
  </div>;
}

/* ---- PV diagram (reserved) ---- */
function PVDiagram(){
  const [T,setTemp] = useState(300);
  const [n,setN] = useState(1);
  const R = 8.314;
  const vs = Array.from({length:60},(_,i)=>0.0005+i*0.0015);
  const ps = vs.map(v => n*R*T/v/1000);
  const maxP = 800;
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">PV = nRT — isothermal curves</div>
      </div>
    </div>
    <div className="knob"><label>T (K)</label><input type="range" min="200" max="600" step="10" value={T} onChange={e=>setTemp(+e.target.value)}/><span className="val">{T}</span></div>
    <div className="knob"><label>n (mol)</label><input type="range" min="0.5" max="3" step="0.1" value={n} onChange={e=>setN(+e.target.value)}/><span className="val">{n.toFixed(1)}</span></div>
    <div className="canvas">
      <svg viewBox="0 0 420 200">
        <line x1="30" y1="180" x2="400" y2="180" stroke="var(--border)"/>
        <line x1="30" y1="10" x2="30" y2="180" stroke="var(--border)"/>
        <polyline points={vs.map((v,i)=>`${30+v*1e5},${180-Math.min(maxP,ps[i])/maxP*160}`).join(' ')} stroke="var(--accent)" strokeWidth="1.5" fill="none"/>
        <text x="405" y="184" fontSize="9" fill="var(--text-3)" textAnchor="end">V</text>
        <text x="36" y="16" fontSize="9" fill="var(--text-3)">P</text>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">PV product</div><div className="v">{(n*R*T).toFixed(1)} J</div></div>
      <div className="readout"><div className="k">at 1 L</div><div className="v">{(n*R*T).toFixed(0)} kPa</div></div>
      <div className="readout"><div className="k">nRT</div><div className="v">{(n*R*T).toFixed(1)}</div></div>
    </div>
  </div>;
}

/* ---- Einstein elevator ---- */
function EinsteinElevator(){
  const [falling,setFalling] = useState(false);
  const [y,setY] = useState(0);
  const [t,setT] = useState(0);
  useEffect(()=>{
    if (!falling) return;
    const id = setInterval(()=>{
      setT(tt => tt + 0.02);
      setY(yy => yy + 1.6);
    }, 20);
    return ()=>clearInterval(id);
  },[falling]);
  useEffect(()=>{ if (y > 120){setFalling(false)} },[y]);
  const reset = ()=>{setFalling(false); setY(0); setT(0)};
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">PRACTICE</div>
        <div className="artifact-title">Equivalence principle</div>
        <div className="artifact-hint">Cut the cable. The released apple stays next to the person — free-fall ≡ weightless.</div>
      </div>
      <div style={{display:'flex',gap:6}}>
        <button className="btn primary sm" onClick={()=>{reset(); setFalling(true)}}>▶ cut cable</button>
        <button className="btn sm" onClick={reset}>reset</button>
      </div>
    </div>
    <div className="canvas">
      <svg viewBox="0 0 420 240">
        <line x1="60" y1="0" x2="60" y2="240" stroke="var(--border)" strokeDasharray="2 3"/>
        <line x1="360" y1="0" x2="360" y2="240" stroke="var(--border)" strokeDasharray="2 3"/>
        <line x1="210" y1="0" x2="210" y2={falling?10:(50+y)} stroke={falling?'var(--error)':'var(--text-2)'} strokeWidth="1" strokeDasharray={falling?'3 4':'none'}/>
        <g transform={`translate(0,${y})`}>
          <rect x="110" y="50" width="200" height="130" fill="none" stroke="var(--accent)" strokeWidth="1.5"/>
          <g stroke="var(--text)" strokeWidth="1.2" fill="none">
            <circle cx="170" cy="118" r="6"/>
            <line x1="170" y1="124" x2="170" y2="148"/>
            <line x1="170" y1="130" x2="160" y2="142"/>
            <line x1="170" y1="130" x2="180" y2="142"/>
            <line x1="170" y1="148" x2="162" y2="164"/>
            <line x1="170" y1="148" x2="178" y2="164"/>
          </g>
          <circle cx="240" cy={falling ? 118 : 160} r="6" fill="var(--accent)"/>
          <text x="210" y="42" fontSize="10" fill="var(--accent)" textAnchor="middle">{falling?'FREE-FALL':'AT REST'}</text>
        </g>
        <line x1="0" y1="232" x2="420" y2="232" stroke="var(--border)"/>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">state</div><div className="v">{falling?'falling':'static'}</div></div>
      <div className="readout"><div className="k">t</div><div className="v">{t.toFixed(2)} s</div></div>
      <div className="readout"><div className="k">apparent weight</div><div className="v">{falling?'0 N':'mg N'}</div></div>
    </div>
  </div>;
}

/* ================= DRILL ================= */
function DrillWidget({drill, onSolve}){
  const [answer,setAnswer] = useState('');
  const [result,setResult] = useState(null);
  const [showHint,setShowHint] = useState(false);
  const check = ()=>{
    const v = parseFloat(answer);
    if (!isFinite(v)){setResult('err'); return}
    if (Math.abs(v-drill.a) <= drill.tol){setResult('ok'); setTimeout(onSolve,700)}
    else setResult('no');
  };
  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">CHECK</div>
        <div className="artifact-title">{drill.q}</div>
        <div className="artifact-hint">Answer with units. Units first.</div>
      </div>
      <button className="btn sm" onClick={()=>setShowHint(v=>!v)}>{showHint?'hide':'hint'}</button>
    </div>
    {showHint && <div className="callout"><b>Hint.</b> {drill.hint}</div>}
    <div className="drill-row">
      <input value={answer} onChange={e=>setAnswer(e.target.value)} onKeyDown={e=>e.key==='Enter'&&check()} placeholder="your answer"/>
      <span className="unit">{drill.unit}</span>
      <button className="btn primary" onClick={check}>Submit</button>
    </div>
    {result==='ok'  && <div className="result-ok">Correct — within tolerance.</div>}
    {result==='no'  && <div className="result-no">Not yet. Target near {drill.a} {drill.unit}.</div>}
    {result==='err' && <div className="result-no">Enter a number.</div>}
  </div>;
}

/* ================= PROBLEMS ================= */
function ProblemList({onOpen, completed}){
  return <div className="page">
    <div className="section-title">Problems</div>
    <div className="section-rule"/>
    <div className="section-sub">{PROBLEMS.length} bagrut-style · tied to lessons</div>
    <div className="card-grid">
      {PROBLEMS.map(c =>
        <article key={c.id} className={'card '+(completed[c.id]?'done':'')} onClick={()=>onOpen(c.id)}>
          {completed[c.id] && <div className="done-dot"/>}
          <div className="ix">{c.diff.toUpperCase()} · {c.xp} XP</div>
          <div className="title">{c.title}</div>
          <div className="desc">{c.q}</div>
          <div className="meta"><span>DAY {String(c.lesson).padStart(2,'0')}</span><span>answer in <b>{c.unit}</b></span></div>
        </article>
      )}
    </div>
  </div>;
}

function ProblemOverlay({p, onClose, onSolve, done}){
  const [answer,setAnswer] = useState('');
  const [result,setResult] = useState(null);
  const [showHint,setShowHint] = useState(false);
  const [showSol,setShowSol] = useState(false);
  const check = ()=>{
    const v = parseFloat(answer);
    if (!isFinite(v)){setResult('err'); return}
    if (Math.abs(v-p.a) <= p.tol){setResult('ok'); setTimeout(()=>onSolve(p),800)}
    else setResult('no');
  };
  return <div className="overlay" onClick={e=>e.target.classList.contains('overlay')&&onClose()}>
    <div className="modal" style={{maxWidth:720}}>
      <div className="modal-head">
        <div>
          <div className="kicker">{p.diff.toUpperCase()} · {p.xp} XP</div>
          <h2>{p.title}</h2>
        </div>
        <button className="modal-close" onClick={onClose}>×</button>
      </div>
      <div className="modal-body">
        <p className="lead">{p.q}</p>
        <div className="drill-row">
          <input value={answer} onChange={e=>setAnswer(e.target.value)} onKeyDown={e=>e.key==='Enter'&&check()} placeholder="your answer"/>
          <span className="unit">{p.unit}</span>
          <button className="btn primary" onClick={check}>Submit</button>
        </div>
        {result==='ok'  && <div className="result-ok">Correct. XP awarded.</div>}
        {result==='no'  && <div className="result-no">Not yet. Check units and order of magnitude.</div>}
        {result==='err' && <div className="result-no">Enter a number.</div>}
        {showHint && <div className="callout"><b>Hint.</b> {p.hint}</div>}
        {showSol && <div className="callout"><b>Solution.</b> {p.sol}</div>}
      </div>
      <div className="modal-foot">
        <div style={{display:'flex',gap:8}}>
          <button className="btn sm" onClick={()=>setShowHint(v=>!v)}>{showHint?'hide':'show'} hint</button>
          <button className="btn sm" onClick={()=>setShowSol(v=>!v)}>{showSol?'hide':'reveal'} solution</button>
        </div>
        <div style={{fontSize:10,color:'var(--text-3)',letterSpacing:1.5}}>{done?'✓ SOLVED':'OPEN'}</div>
      </div>
    </div>
  </div>;
}

/* ================= LAB ================= */
function Lab({onAward}){
  const [objects,setObjects] = useState([]);
  const [selected,setSelected] = useState(null);
  const [inspector,setInspector] = useState('scene');
  const canvasRef = useRef(null);
  const draggingRef = useRef(null);

  const addObject = (kind)=>{
    const id = 'o'+Date.now()+Math.floor(Math.random()*100);
    setObjects(os => [...os, {id, kind, x:120+Math.random()*200, y:120+Math.random()*180, mass:kind==='mass'?2:0, charge:kind==='charge-plus'?1:kind==='charge-minus'?-1:0}]);
  };
  const onPointerDown = (e, obj)=>{
    e.stopPropagation();
    const r = canvasRef.current.getBoundingClientRect();
    draggingRef.current = {id:obj.id, dx:e.clientX-r.left-obj.x, dy:e.clientY-r.top-obj.y};
    setSelected(obj.id);
  };
  const onPointerMove = (e)=>{
    if (!draggingRef.current) return;
    const r = canvasRef.current.getBoundingClientRect();
    const nx = e.clientX-r.left-draggingRef.current.dx;
    const ny = e.clientY-r.top-draggingRef.current.dy;
    setObjects(os => os.map(o => o.id===draggingRef.current.id ? {...o, x:nx, y:ny} : o));
  };
  const onPointerUp = ()=>{draggingRef.current = null};
  const sel = objects.find(o=>o.id===selected);

  return <div className="page wide">
    <div className="section-title">Lab</div>
    <div className="section-rule"/>
    <div className="section-sub">Sandbox — drag objects, run scenes</div>
    <div className="bench">
      <aside className="bench-rail">
        <h4>Primitives</h4>
        <div className="tool-grid">
          <button className="tool-btn" onClick={()=>addObject('mass')}>
            <div className="ico"><div className="prim-mass" style={{width:22,height:22,fontSize:10}}>m</div></div>
            Mass
          </button>
          <button className="tool-btn" onClick={()=>addObject('charge-plus')}>
            <div className="ico"><div className="prim-charge-plus" style={{width:22,height:22,fontSize:13,borderWidth:1.5}}>+</div></div>
            + Charge
          </button>
          <button className="tool-btn" onClick={()=>addObject('charge-minus')}>
            <div className="ico"><div className="prim-charge-minus" style={{width:22,height:22,fontSize:14,borderWidth:1.5}}>−</div></div>
            − Charge
          </button>
          <button className="tool-btn" onClick={()=>addObject('spring')}>
            <div className="ico"><div className="prim-spring" style={{width:28,height:10}}/></div>
            Spring
          </button>
          <button className="tool-btn" onClick={()=>addObject('pendulum')}>
            <div className="ico"><div className="prim-pendulum" style={{transform:'scale(.3)',height:36}}/></div>
            Pendulum
          </button>
          <button className="tool-btn" onClick={()=>setObjects([])}>
            <div className="ico"><div style={{width:22,height:22,border:'1px solid var(--border)',display:'flex',alignItems:'center',justifyContent:'center'}}>×</div></div>
            Clear
          </button>
        </div>
        <h4>Scenes</h4>
        <button className="btn sm" style={{width:'100%',marginBottom:6}} onClick={()=>setObjects([
          {id:'a',kind:'mass',x:140,y:200,mass:2},
          {id:'b',kind:'mass',x:260,y:200,mass:3},
          {id:'c',kind:'spring',x:180,y:210},
        ])}>collision</button>
        <button className="btn sm" style={{width:'100%',marginBottom:6}} onClick={()=>setObjects([
          {id:'a',kind:'charge-plus',x:140,y:180,charge:1},
          {id:'b',kind:'charge-minus',x:320,y:180,charge:-1},
        ])}>dipole</button>
        <button className="btn sm" style={{width:'100%'}} onClick={()=>setObjects([
          {id:'p',kind:'pendulum',x:220,y:30},
        ])}>pendulum</button>
      </aside>

      <div className="bench-canvas" ref={canvasRef} onPointerMove={onPointerMove} onPointerUp={onPointerUp} onClick={()=>setSelected(null)}>
        {objects.map(o => {
          const cls = 'prim '+(selected===o.id?'selected':'');
          const style = {left:o.x, top:o.y};
          if (o.kind==='mass')         return <div key={o.id} className={cls} style={style} onPointerDown={e=>onPointerDown(e,o)}><div className="prim-mass">{o.mass}kg</div></div>;
          if (o.kind==='charge-plus')  return <div key={o.id} className={cls} style={style} onPointerDown={e=>onPointerDown(e,o)}><div className="prim-charge-plus">+</div></div>;
          if (o.kind==='charge-minus') return <div key={o.id} className={cls} style={style} onPointerDown={e=>onPointerDown(e,o)}><div className="prim-charge-minus">−</div></div>;
          if (o.kind==='spring')       return <div key={o.id} className={cls} style={style} onPointerDown={e=>onPointerDown(e,o)}><div className="prim-spring"/></div>;
          if (o.kind==='pendulum')     return <div key={o.id} className={cls} style={style} onPointerDown={e=>onPointerDown(e,o)}><div className="prim-pendulum"/></div>;
          return null;
        })}
        {objects.length===0 && <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',flexDirection:'column',gap:8,color:'var(--text-3)'}}>
          <div className="display" style={{fontSize:24}}>Empty bench.</div>
          <div style={{fontSize:12,letterSpacing:1,textTransform:'uppercase'}}>Click a primitive or scene at left</div>
        </div>}
      </div>

      <aside className="bench-side">
        <div className="bench-tabs">
          {['scene','formulas','const'].map(t =>
            <button key={t} className={inspector===t?'on':''} onClick={()=>setInspector(t)}>{t}</button>
          )}
        </div>
        {inspector==='scene' && <>
          <h4>Inspector</h4>
          {sel ? <>
            <div className="bench-row"><span className="k">kind</span><span className="v">{sel.kind}</span></div>
            <div className="bench-row"><span className="k">x</span><span className="v">{sel.x.toFixed(0)}</span></div>
            <div className="bench-row"><span className="k">y</span><span className="v">{sel.y.toFixed(0)}</span></div>
            {!!sel.mass && <div className="bench-row"><span className="k">mass</span><span className="v">{sel.mass} kg</span></div>}
            {!!sel.charge && <div className="bench-row"><span className="k">charge</span><span className="v">{sel.charge} μC</span></div>}
          </> : <div style={{fontSize:11,color:'var(--text-3)'}}>Select an object.</div>}
          <h4>Totals</h4>
          <div className="bench-row"><span className="k">objects</span><span className="v">{objects.length}</span></div>
          <div className="bench-row"><span className="k">masses</span><span className="v">{objects.filter(o=>o.kind==='mass').length}</span></div>
          <div className="bench-row"><span className="k">charges</span><span className="v">{objects.filter(o=>o.kind.includes('charge')).length}</span></div>
        </>}
        {inspector==='formulas' && <>
          <h4>Reference</h4>
          {['F = ma','W = F·d','KE = ½mv²','PE = mgh','F = −kx','F = kq₁q₂/r²','PV = nRT','T = 2π√(L/g)'].map(f =>
            <div key={f} className="formula" style={{margin:'6px 0',fontSize:12,padding:'6px 10px'}}>{f}</div>
          )}
        </>}
        {inspector==='const' && <>
          <h4>Constants</h4>
          <div className="bench-row"><span className="k">g</span><span className="v">9.81 m/s²</span></div>
          <div className="bench-row"><span className="k">G</span><span className="v">6.674e-11</span></div>
          <div className="bench-row"><span className="k">k coulomb</span><span className="v">8.99e9</span></div>
          <div className="bench-row"><span className="k">R</span><span className="v">8.314</span></div>
          <div className="bench-row"><span className="k">c</span><span className="v">3.00e8</span></div>
          <div className="bench-row"><span className="k">h</span><span className="v">6.626e-34</span></div>
          <div className="bench-row"><span className="k">ε₀</span><span className="v">8.854e-12</span></div>
          <div className="bench-row"><span className="k">eV</span><span className="v">1.602e-19</span></div>
        </>}
      </aside>
    </div>
  </div>;
}

/* ================= GAMES ================= */
function Games({onAward}){
  const [game,setGame] = useState(null);
  const GAMES = [
    {id:'cannon',   title:'Cannon Target', desc:'Hit the target with projectile motion.'},
    {id:'catch',    title:'Apple Catch',   desc:'Click when the apple reaches Newton.'},
    {id:'units',    title:'Unit Match',    desc:'Match the quantity to its correct unit.'},
    {id:'graph',    title:'Graph Match',   desc:'Pick the x(t) that matches the motion.'},
  ];
  if (game) return <div className="page wide">
    <button className="btn" onClick={()=>setGame(null)}>← Back to games</button>
    <div style={{height:20}}/>
    {game==='cannon' && <CannonGame onAward={onAward}/>}
    {game==='catch'  && <CatchGame  onAward={onAward}/>}
    {game==='units'  && <UnitMatch  onAward={onAward}/>}
    {game==='graph'  && <GraphMatch onAward={onAward}/>}
  </div>;
  return <div className="page">
    <div className="section-title">Games</div>
    <div className="section-rule"/>
    <div className="section-sub">Four quick drills — the fastest way to feel the physics</div>
    <div className="card-grid">
      {GAMES.map(g =>
        <article key={g.id} className="card" onClick={()=>setGame(g.id)}>
          <div className="ix">MINIGAME</div>
          <div className="title">{g.title}</div>
          <div className="desc">{g.desc}</div>
          <div className="meta"><span>~2 min</span><span>play →</span></div>
        </article>
      )}
    </div>
  </div>;
}

/* ---- Cannon ---- */
function CannonGame({onAward}){
  const [angle,setAngle] = useState(45);
  const [v0,setV0]       = useState(30);
  const [targetX,setTargetX] = useState(300);
  const [shots,setShots] = useState([]);
  const [flying,setFlying] = useState(false);
  const [t,setT] = useState(0);
  const [hit,setHit] = useState(false);
  const [score,setScore] = useState(0);
  const g = 9.81;
  const theta = angle*Math.PI/180;
  const vx = v0*Math.cos(theta), vy = v0*Math.sin(theta);

  useEffect(()=>{
    if (!flying) return;
    const id = setInterval(()=>setT(tt=>tt+0.04), 25);
    return ()=>clearInterval(id);
  },[flying]);

  const scale = 5;
  const cannonX = 30, cannonY = 280;
  const px = cannonX + vx*t*scale;
  const py = cannonY - (vy*t - 0.5*g*t*t)*scale;

  useEffect(()=>{
    if (!flying) return;
    if (py > cannonY || px > 620){
      const flight = 2*vy/g;
      const landX = cannonX + vx*flight*scale;
      setFlying(false);
      setShots(s=>[...s,{angle,v0,endX:landX}].slice(-4));
      if (Math.abs(landX-(cannonX+targetX)) < 18){
        setHit(true); setScore(s=>s+1); onAward(30,'target hit');
        setTimeout(()=>{setHit(false); setTargetX(140+Math.random()*360)},800);
      }
    }
  },[px,py,flying]);

  const preview = useMemo(()=>{
    const pts = []; const dt=0.04;
    for (let tt=0; tt<6; tt+=dt){
      const xx = cannonX + vx*tt*scale;
      const yy = cannonY - (vy*tt - 0.5*g*tt*tt)*scale;
      if (yy > cannonY+4) break;
      pts.push(`${xx},${yy}`);
    }
    return pts.join(' ');
  },[vx,vy]);

  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">GAME · SCORE {score}</div>
        <div className="artifact-title">Cannon Target</div>
        <div className="artifact-hint">Adjust angle and speed. Dashed line = preview. Hit the circle.</div>
      </div>
      <button className="btn primary" onClick={()=>{if(!flying){setT(0); setFlying(true); setHit(false)}}} disabled={flying}>{flying?'…flying':'▶ fire'}</button>
    </div>
    <div className="knob"><label>angle</label><input type="range" min="5" max="85" value={angle} onChange={e=>setAngle(+e.target.value)}/><span className="val">{angle}<span className="u">°</span></span></div>
    <div className="knob"><label>v₀</label><input type="range" min="10" max="60" step="0.5" value={v0} onChange={e=>setV0(+e.target.value)}/><span className="val">{v0.toFixed(1)}<span className="u">m/s</span></span></div>
    <div className="canvas tall">
      <svg viewBox="0 0 640 320">
        <line x1="0" y1="280" x2="640" y2="280" stroke="var(--border)"/>
        <rect x="14" y="270" width="32" height="14" fill="var(--text-2)"/>
        <line x1="30" y1="278" x2={30+30*Math.cos(theta)} y2={278-30*Math.sin(theta)} stroke="var(--accent)" strokeWidth="5" strokeLinecap="round"/>
        {!flying && <polyline points={preview} stroke="var(--text-2)" strokeWidth="1" strokeDasharray="3 4" fill="none"/>}
        {shots.map((s,i)=>{
          const th = s.angle*Math.PI/180;
          const vxi = s.v0*Math.cos(th), vyi = s.v0*Math.sin(th);
          const pts=[]; const dt=0.04;
          for (let tt=0; tt<6; tt+=dt){
            const xx = cannonX + vxi*tt*scale;
            const yy = cannonY - (vyi*tt - 0.5*g*tt*tt)*scale;
            if (yy > cannonY+4) break;
            pts.push(`${xx},${yy}`);
          }
          return <polyline key={i} points={pts.join(' ')} stroke="var(--text-3)" strokeWidth=".8" fill="none" opacity=".45"/>;
        })}
        {flying && py < cannonY+10 && <circle cx={px} cy={py} r="5" fill="var(--accent)"/>}
        <g transform={`translate(${cannonX+targetX},276)`}>
          <circle r="12" fill="none" stroke={hit?'var(--success)':'var(--error)'} strokeWidth="2"/>
          <circle r="4" fill={hit?'var(--success)':'var(--error)'}/>
        </g>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">range</div><div className="v">{(v0*v0*Math.sin(2*theta)/g).toFixed(1)} m</div></div>
      <div className="readout"><div className="k">peak</div><div className="v">{((v0*Math.sin(theta))**2/(2*g)).toFixed(1)} m</div></div>
      <div className="readout"><div className="k">time of flight</div><div className="v">{(2*v0*Math.sin(theta)/g).toFixed(2)} s</div></div>
    </div>
  </div>;
}

/* ---- Apple Catch ---- */
function CatchGame({onAward}){
  const [h,setH]       = useState(8);  // height m
  const [gval]         = useState(9.81);
  const [t,setT]       = useState(0);
  const [running,setRunning] = useState(false);
  const [result,setResult]   = useState(null); // 'hit' | 'miss' | null
  const [score,setScore]     = useState(0);
  const [round,setRound]     = useState(0);
  const trueT = Math.sqrt(2*h/gval);

  useEffect(()=>{
    if (!running) return;
    const id = setInterval(()=>setT(x=>{
      const nx = x + 0.02;
      if (nx > trueT + 0.6){setRunning(false); setResult('miss')}
      return nx;
    }), 20);
    return ()=>clearInterval(id);
  },[running,trueT]);

  const drop = ()=>{setT(0); setResult(null); setRunning(true)};
  const tap = ()=>{
    if (!running) return;
    setRunning(false);
    const err = Math.abs(t - trueT);
    if (err < 0.15){setResult('hit'); setScore(s=>s+1); onAward(20,'clean catch')}
    else setResult('miss');
  };
  const next = ()=>{
    setH(4+Math.random()*14);
    setT(0); setResult(null);
    setRound(r=>r+1);
  };

  const fallen = Math.min(h, 0.5*gval*t*t);
  const py = 40 + fallen/h*160;

  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">GAME · SCORE {score}</div>
        <div className="artifact-title">Apple Catch</div>
        <div className="artifact-hint">The apple falls from height. Predict t = √(2h/g). Click catch at the right moment.</div>
      </div>
      <div style={{display:'flex',gap:6}}>
        <button className="btn primary sm" onClick={running?tap:drop}>{running?'✋ catch':'▶ drop'}</button>
        <button className="btn sm" onClick={next}>new height</button>
      </div>
    </div>
    <div className="canvas">
      <svg viewBox="0 0 420 240">
        <rect x="60" y="30" width="6" height="180" fill="var(--text-2)"/>
        <ellipse cx="63" cy="34" rx="44" ry="22" fill="none" stroke="var(--text-2)" strokeWidth="1"/>
        <line x1="0" y1="212" x2="420" y2="212" stroke="var(--border)"/>
        <g stroke="var(--text)" strokeWidth="1.2" fill="none">
          <circle cx="220" cy="180" r="7"/>
          <line x1="220" y1="187" x2="220" y2="206"/>
          <line x1="220" y1="194" x2="210" y2="203"/>
          <line x1="220" y1="194" x2="230" y2="203"/>
        </g>
        <circle cx="63" cy={py} r="7" fill="var(--accent)"/>
      </svg>
    </div>
    <div className="readouts">
      <div className="readout"><div className="k">height</div><div className="v">{h.toFixed(1)} m</div></div>
      <div className="readout"><div className="k">t elapsed</div><div className="v">{t.toFixed(2)} s</div></div>
      <div className="readout"><div className="k">true catch t</div><div className="v">{trueT.toFixed(2)} s</div></div>
    </div>
    {result==='hit'  && <div className="result-ok">Caught — within 0.15 s.</div>}
    {result==='miss' && <div className="result-no">Missed by {Math.abs(t-trueT).toFixed(2)} s. Try again.</div>}
  </div>;
}

/* ---- Unit Match ---- */
function UnitMatch({onAward}){
  const QUESTIONS = [
    {q:'Velocity',          opts:['m/s','m/s²','kg·m/s','N']},
    {q:'Acceleration',      opts:['m/s²','m/s','J','W']},
    {q:'Force',             opts:['N','J','Pa','kg']},
    {q:'Energy',            opts:['J','N','W','Pa']},
    {q:'Momentum',          opts:['kg·m/s','kg·m/s²','N·m','J']},
    {q:'Power',             opts:['W','J','N','Hz']},
    {q:'Frequency',         opts:['Hz','s','rad','m⁻¹']},
    {q:'Pressure',          opts:['Pa','N','J','bar/s']},
    {q:'Charge',            opts:['C','A','V','Ω']},
    {q:'Electric potential',opts:['V','C','Ω','W']},
  ];
  const [i,setI] = useState(0);
  const [picks,setPicks] = useState([]); // array of {correct:bool}
  const [score,setScore] = useState(0);
  const cur = QUESTIONS[i];
  const answerIsFirst = true; // opts[0] is always correct; we shuffle in render

  const shuffled = useMemo(()=>{
    const base = cur.opts.slice();
    const arr = base.map(v=>({v, r:Math.random()}));
    arr.sort((a,b)=>a.r-b.r);
    return arr.map(x=>x.v);
  },[i]);

  const pick = (opt)=>{
    const ok = opt === cur.opts[0];
    setPicks(p=>[...p, {q:cur.q, pick:opt, ok}]);
    if (ok){setScore(s=>s+1); onAward(8,'unit correct')}
    setTimeout(()=>{
      if (i+1 < QUESTIONS.length) setI(i+1);
    }, 450);
  };

  const done = picks.length === QUESTIONS.length;

  if (done) return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">GAME · COMPLETE</div>
        <div className="artifact-title">Unit Match — final score {score}/{QUESTIONS.length}</div>
      </div>
      <button className="btn primary" onClick={()=>{setI(0); setPicks([]); setScore(0)}}>play again</button>
    </div>
    <div style={{display:'flex',flexDirection:'column',gap:6,marginTop:10}}>
      {picks.map((p,idx)=>
        <div key={idx} className="bench-row" style={{borderBottom:'1px solid var(--border)'}}>
          <span className="k">{p.q}</span>
          <span className="v" style={{color:p.ok?'var(--success)':'var(--error)'}}>{p.pick} {p.ok?'✓':`(→ ${QUESTIONS[idx].opts[0]})`}</span>
        </div>
      )}
    </div>
  </div>;

  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">GAME · {i+1}/{QUESTIONS.length} · SCORE {score}</div>
        <div className="artifact-title">Which unit measures <span style={{color:'var(--accent)'}}>{cur.q}</span>?</div>
        <div className="artifact-hint">No calculation — pure unit recognition. Fast.</div>
      </div>
    </div>
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,marginTop:10}}>
      {shuffled.map(opt =>
        <button key={opt} className="btn" style={{padding:'14px',fontSize:16,justifyContent:'center'}} onClick={()=>pick(opt)}>{opt}</button>
      )}
    </div>
    <div style={{marginTop:14,display:'flex',gap:4}}>
      {QUESTIONS.map((_,k)=>
        <div key={k} style={{flex:1,height:3,background:k<picks.length?(picks[k].ok?'var(--success)':'var(--error)'):'var(--text-4)'}}/>
      )}
    </div>
  </div>;
}

/* ---- Graph Match ---- */
function GraphMatch({onAward}){
  // motion scenarios: return function x(t) over [0..5]
  const SCEN = [
    {title:'Object dropped from rest (g=10)', fn:t=> -5*t*t, label:'parabola opening down'},
    {title:'Constant velocity +3 m/s',        fn:t=> 3*t,    label:'straight line, positive slope'},
    {title:'At rest',                          fn:t=> 0,      label:'horizontal line'},
    {title:'Decelerating to a stop from 10 m/s (a=-2)', fn:t=> 10*t - t*t, label:'parabola, peak then level'},
    {title:'Simple harmonic A=4, T=4',        fn:t=> 4*Math.sin(2*Math.PI*t/4), label:'sinusoidal'},
    {title:'Constant accel from rest, a=2',   fn:t=> t*t,    label:'parabola opening up'},
  ];
  const [round,setRound] = useState(0);
  const [score,setScore] = useState(0);
  const [picked,setPicked] = useState(null);

  const {truth, options} = useMemo(()=>{
    const truth = SCEN[Math.floor(Math.random()*SCEN.length)];
    const pool = SCEN.filter(s=>s!==truth);
    const decoys = pool.sort(()=>Math.random()-0.5).slice(0,3);
    const arr = [truth, ...decoys].sort(()=>Math.random()-0.5);
    return {truth, options:arr};
  },[round]);

  const pts = (fn) => {
    const p = [];
    for (let i=0; i<=40; i++){
      const t = i*5/40;
      p.push([t, fn(t)]);
    }
    return p;
  };

  const render = (scenario) => {
    const data = pts(scenario.fn);
    const ys = data.map(d=>d[1]);
    const maxY = Math.max(1, ...ys.map(Math.abs));
    const toSvg = (t,y) => [20 + t/5*100, 60 - y/maxY*40];
    const path = data.map(d=>toSvg(d[0], d[1]).join(',')).join(' ');
    return <svg viewBox="0 0 140 80" style={{width:'100%',height:'100%',display:'block'}}>
      <line x1="20" y1="60" x2="128" y2="60" stroke="var(--border)"/>
      <line x1="20" y1="10" x2="20" y2="70" stroke="var(--border)"/>
      <polyline points={path} stroke="var(--accent)" strokeWidth="1.5" fill="none"/>
    </svg>;
  };

  const pick = (opt)=>{
    if (picked) return;
    setPicked(opt);
    const ok = opt === truth;
    if (ok){setScore(s=>s+1); onAward(15,'graph matched')}
    setTimeout(()=>{setPicked(null); setRound(r=>r+1)}, 900);
  };

  return <div className="artifact">
    <div className="artifact-head">
      <div>
        <div className="artifact-kicker">GAME · ROUND {round+1} · SCORE {score}</div>
        <div className="artifact-title">{truth.title}</div>
        <div className="artifact-hint">Which x(t) graph matches? The correct shape tells the motion.</div>
      </div>
    </div>
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,marginTop:10}}>
      {options.map((opt,i) => {
        const isChosen = picked === opt;
        const isRight = opt === truth;
        const borderColor = !picked ? 'var(--border)' : (isRight?'var(--success)':(isChosen?'var(--error)':'var(--border)'));
        return <button key={i} onClick={()=>pick(opt)}
          style={{border:`1px solid ${borderColor}`,padding:6,background:'transparent',height:140,cursor:'pointer'}}>
          {render(opt)}
        </button>;
      })}
    </div>
  </div>;
}

/* ================= PROFILE ================= */
function Profile({xp, rankIdx, completed}){
  const doneCount = Object.values(completed).filter(Boolean).length;
  const earned = BADGES.filter(b=>b.earned).length;
  return <div className="page">
    <div className="section-title">Profile</div>
    <div className="section-rule"/>
    <div className="section-sub">Track progress and roles</div>
    <div className="profile-grid">
      <div className="profile-card">
        <div className="avatar">N</div>
        <div className="display" style={{fontSize:22}}>Student</div>
        <div style={{fontSize:10,color:'var(--accent)',letterSpacing:2,textTransform:'uppercase',marginTop:4}}>{PHASES[rankIdx].role}</div>
        <div style={{fontFamily:'var(--display)',fontSize:36,color:'var(--text)',marginTop:14}}>{xp}</div>
        <div style={{fontSize:10,color:'var(--text-3)',letterSpacing:2,textTransform:'uppercase'}}>XP total</div>
        <div className="stats" style={{marginTop:16,border:'1px solid var(--border)'}}>
          <div className="stat"><div className="n">{doneCount}</div><div className="l">Completed</div></div>
          <div className="stat"><div className="n">{earned}</div><div className="l">Badges</div></div>
        </div>
      </div>
      <div>
        <div className="ranks">
          <h4 style={{fontSize:10,color:'var(--accent)',letterSpacing:2,textTransform:'uppercase',marginBottom:10}}>Role progression</h4>
          {PHASES.map((s,i) =>
            <div key={s.id} className={'rank-row '+(i===rankIdx?'on':'')}>
              <div className="lbl">
                <div className="n">{String(i+1).padStart(2,'0')}</div>
                <div>
                  <div className="role">{s.role}</div>
                  <div className="bl">{s.bl}</div>
                </div>
              </div>
              <div className="xp">{i*140} XP</div>
            </div>
          )}
        </div>
        <div className="ranks" style={{marginTop:14}}>
          <h4 style={{fontSize:10,color:'var(--accent)',letterSpacing:2,textTransform:'uppercase',marginBottom:10}}>Badges</h4>
          <div className="badge-grid">
            {BADGES.map(b =>
              <div key={b.id} className={'badge '+(b.earned?'earned':'locked')}>
                <div className="bi">{b.ic}</div>
                <div className="bn">{b.n}</div>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  </div>;
}

/* ================= Newton's rings — hero decoration ================= */
function NewtonRings(){
  return <svg className="rings" viewBox="0 0 240 240" aria-hidden="true">
    <circle cx="180" cy="60" r="20"/>
    <circle cx="180" cy="60" r="40"/>
    <circle cx="180" cy="60" r="65"/>
    <circle cx="180" cy="60" r="95"/>
    <circle cx="180" cy="60" r="130"/>
    <circle cx="180" cy="60" r="170" opacity=".4"/>
  </svg>;
}

/* ================= SEDER OVERLAY (bundle sedarim) ================= */
function SederOverlay({seder, lesson, onClose, onDone, done}){
  const [step,setStep] = useState(0);
  const isGate = GATE_SEDARIM.includes(seder.seder_id || lesson.id);
  const steps = isGate
    ? ['Gate','Problems','Reflection']
    : ['Mentor','Spine','Active','Practice','Codex'];

  const shalavName = PHASES[lesson.phase-1]?.role || `Shalav ${lesson.phase}`;

  return <div className="overlay" onClick={(e)=>e.target.classList.contains('overlay') && onClose()}>
    <div className="modal">
      <div className="modal-head">
        <div>
          <div className="kicker">SEDER {String(seder.seder_id).padStart(2,'0')} · {shalavName}{isGate ? ' · GATE' : ''}{seder.flags?.aha_beat_id ? ` · ${seder.flags.aha_beat_id.toUpperCase()}` : ''}{seder.flags?.risk_day ? ' · RISK' : ''}</div>
          <h2>{seder.title}</h2>
          {seder.subtitle && <div className="sub">{seder.subtitle}</div>}
        </div>
        <button className="modal-close" onClick={onClose}>×</button>
      </div>

      <div className="modal-steps">
        {steps.map((s,i) =>
          <button key={s} className={'step-tab '+(step===i?'on':step>i?'done':'')} onClick={()=>setStep(i)}>
            <span className="num">{String(i+1).padStart(2,'0')}</span>{s}
          </button>
        )}
      </div>

      <div className="modal-body">
        {!isGate && step===0 && <SederMentor seder={seder}/>}
        {!isGate && step===1 && <SederSpine seder={seder}/>}
        {!isGate && step===2 && <SederActiveProduction seder={seder}/>}
        {!isGate && step===3 && <SederPractice seder={seder} lesson={lesson}/>}
        {!isGate && step===4 && <SederCodex seder={seder} onSolve={()=>onDone(lesson.id)}/>}

        {isGate && step===0 && <GateOpening seder={seder}/>}
        {isGate && step===1 && <GateProblems seder={seder}/>}
        {isGate && step===2 && <GateReflection seder={seder} onSolve={()=>onDone(lesson.id)}/>}
      </div>

      <div className="modal-foot">
        <button className="btn" onClick={()=>setStep(Math.max(0,step-1))} disabled={step===0}>← Back</button>
        <div className="step-dots">
          {steps.map((s,i)=><div key={i} className={'step-dot '+(i<step?'done':i===step?'on':'')}/>)}
        </div>
        {step<steps.length-1
          ? <button className="btn primary" onClick={()=>setStep(step+1)}>Next →</button>
          : <button className="btn primary" onClick={()=>onDone(lesson.id)}>Mark complete ✓</button>}
      </div>
    </div>
  </div>;
}

function SederMentor({seder}){
  const intro = seder.mentor_intro || '(no mentor intro)';
  return <>
    <div className="mentor-line" style={{whiteSpace:'pre-wrap'}}>{intro}</div>
    {seder.prereq_retrieval && <div className="callout" style={{marginTop:14}}>
      <b>Retrieval ({seder.prereq_retrieval.type || 'free recall'}):</b> {seder.prereq_retrieval.prompt}
      {seder.prereq_retrieval.pass_criterion && <div style={{fontSize:11,color:'var(--text-3)',marginTop:4}}>Pass: {seder.prereq_retrieval.pass_criterion}</div>}
    </div>}
    {seder.flags?.aha_beat_id && <div className="callout" style={{marginTop:10,borderColor:'var(--accent)'}}>
      <b>Aha beat scheduled:</b> {seder.flags.aha_beat_id}
    </div>}
  </>;
}

function SederSpine({seder}){
  const sp = seder.spine;
  if (!sp) return <p style={{color:'var(--text-3)'}}>No Spine for this seder (preparation / review day).</p>;
  const conceptId = sp.concept_id;
  const concept = conceptId ? findConceptInAtlas(conceptId) : null;
  return <>
    <h3>Spine — {conceptId || '(no concept)'}</h3>
    {concept && concept.spine && <>
      {concept.spine.adept_analogy && <><h4 style={{marginTop:10,color:'var(--accent)'}}>Analogy</h4><p style={{whiteSpace:'pre-wrap'}}>{concept.spine.adept_analogy}</p></>}
      {concept.spine.adept_diagram_description && <><h4 style={{marginTop:10,color:'var(--accent)'}}>Diagram</h4><p style={{whiteSpace:'pre-wrap'}}>{concept.spine.adept_diagram_description}</p></>}
      {concept.spine.adept_example && <><h4 style={{marginTop:10,color:'var(--accent)'}}>Example</h4><p style={{whiteSpace:'pre-wrap'}}>{concept.spine.adept_example}</p></>}
      {concept.spine.adept_plain_english && <><h4 style={{marginTop:10,color:'var(--accent)'}}>Plain English</h4><p style={{whiteSpace:'pre-wrap'}}>{concept.spine.adept_plain_english}</p></>}
      {concept.spine.adept_technical && <><h4 style={{marginTop:10,color:'var(--accent)'}}>Technical</h4><div className="formula" style={{whiteSpace:'pre-wrap',textAlign:'left'}}>{concept.spine.adept_technical}</div></>}
    </>}
    {sp.teaching_notes_for_this_day && <div className="callout" style={{marginTop:14}}><b>For today:</b> {sp.teaching_notes_for_this_day}</div>}
    {sp.format && <div style={{fontSize:11,color:'var(--text-3)',marginTop:8,letterSpacing:1.2,textTransform:'uppercase'}}>Format: {sp.format}</div>}
  </>;
}

function SederActiveProduction({seder}){
  const ap = seder.active_production;
  const [text,setText] = useState(()=>localStorage.getItem(`newton_ap_${seder.seder_id}`)||'');
  useEffect(()=>{localStorage.setItem(`newton_ap_${seder.seder_id}`, text)},[text,seder.seder_id]);
  if (!ap) return <p style={{color:'var(--text-3)'}}>No active production for this seder.</p>;
  const promptText = ap.prompt || '(no prompt)';
  const isSketchGate = /sketch|diagram|FBD|free.body|draw/i.test(promptText);
  return <>
    <div className="kicker" style={{marginBottom:6}}>ACTIVE PRODUCTION · {ap.mode || 'written'} · {ap.expected_duration_minutes || 20} min</div>
    <p className="lead" style={{whiteSpace:'pre-wrap'}}>{promptText}</p>
    {isSketchGate && <div className="callout" style={{marginTop:10,borderColor:'var(--accent)'}}>
      <b>Sketch-first gate.</b> The active production cannot proceed until you have a sketch on paper. Photograph it, or draw it in-app, then write your reasoning below.
    </div>}
    <textarea
      value={text}
      onChange={e=>setText(e.target.value)}
      placeholder="Your work…"
      style={{width:'100%',minHeight:160,marginTop:12,padding:10,fontFamily:'JetBrains Mono, monospace',fontSize:12,lineHeight:1.6}}
    />
    <div style={{fontSize:10,color:'var(--text-3)',marginTop:4,letterSpacing:1}}>Saved to localStorage on every keystroke.</div>
  </>;
}

function SederPractice({seder, lesson}){
  const pl = seder.practice_ladder;
  if (!pl) return <ArtifactWidget kind={lesson.artifact}/>;
  const [rung,setRung] = useState('worked');
  const rungs = ['worked','faded','independent'].filter(r => pl[r]);
  if (rungs.length === 0) return <ArtifactWidget kind={lesson.artifact}/>;
  const cur = pl[rung];
  return <>
    <div className="modal-steps" style={{marginBottom:10}}>
      {rungs.map(r =>
        <button key={r} className={'step-tab '+(rung===r?'on':'')} onClick={()=>setRung(r)} style={{flex:1}}>
          <span className="num">{r==='worked'?'1':r==='faded'?'2':'3'}</span>{r}
        </button>
      )}
    </div>
    {cur.objective && <><h4 style={{color:'var(--accent)'}}>Objective</h4><p style={{whiteSpace:'pre-wrap'}}>{cur.objective}</p></>}
    {cur.context && <><h4 style={{color:'var(--accent)',marginTop:8}}>Context</h4><p style={{whiteSpace:'pre-wrap'}}>{cur.context}</p></>}
    {Array.isArray(cur.hints) && cur.hints.length > 0 && <><h4 style={{color:'var(--accent)',marginTop:8}}>Hints</h4><ul>{cur.hints.map((h,i)=><li key={i} style={{whiteSpace:'pre-wrap'}}>{typeof h === 'string' ? h : (h.text || JSON.stringify(h))}</li>)}</ul></>}
    {cur.solution && <details style={{marginTop:10}}><summary style={{cursor:'pointer',color:'var(--accent)'}}>Reveal solution</summary><div className="callout" style={{whiteSpace:'pre-wrap',marginTop:6}}>{cur.solution}</div></details>}
    {cur.problems && <div style={{marginTop:10}}>{Object.entries(cur.problems).map(([k,p]) => <div key={k} className="callout" style={{marginBottom:6}}><b>{k}.</b> <span style={{whiteSpace:'pre-wrap'}}>{typeof p === 'string' ? p : (p.statement || p.problem || JSON.stringify(p))}</span></div>)}</div>}
    <div style={{marginTop:14}}>
      <h4 style={{color:'var(--accent)'}}>Lab artifact</h4>
      <ArtifactWidget kind={lesson.artifact}/>
    </div>
  </>;
}

function SederCodex({seder, onSolve}){
  const ce = seder.codex_entry;
  const mc = seder.micro_checkpoint;
  const ii = seder.implementation_intention;
  const [entry,setEntry] = useState(()=>localStorage.getItem(`newton_codex_${seder.seder_id}`)||'');
  const [rating,setRating] = useState(()=>localStorage.getItem(`newton_rating_${seder.seder_id}`)||'');
  useEffect(()=>{localStorage.setItem(`newton_codex_${seder.seder_id}`, entry)},[entry,seder.seder_id]);
  useEffect(()=>{localStorage.setItem(`newton_rating_${seder.seder_id}`, rating)},[rating,seder.seder_id]);
  return <>
    {mc && <>
      <h4 style={{color:'var(--accent)'}}>Micro checkpoint</h4>
      <p>{mc.prompt}</p>
      {Array.isArray(mc.rating_options) && <div style={{display:'flex',gap:8,marginTop:6}}>
        {mc.rating_options.map(r => <button key={r} className={'btn sm '+(rating===r?'primary':'')} onClick={()=>setRating(r)}>{r}</button>)}
      </div>}
      {mc.inner_goal_pulse && <div style={{fontSize:11,color:'var(--text-3)',marginTop:6}}>Inner-goal pulse: {mc.inner_goal_pulse}</div>}
    </>}
    {ce && <>
      <h4 style={{color:'var(--accent)',marginTop:14}}>Codex entry — Yoman Maa'bada</h4>
      {ce.prompt && <p>{ce.prompt}</p>}
      <textarea value={entry} onChange={e=>setEntry(e.target.value)} placeholder="One sentence + diagram + open question…" style={{width:'100%',minHeight:100,marginTop:6,padding:10,fontFamily:'JetBrains Mono, monospace',fontSize:12}}/>
    </>}
    {ii && <div className="callout" style={{marginTop:14,borderColor:'var(--accent)'}}>
      <b>Implementation intention:</b>
      <p style={{whiteSpace:'pre-wrap',marginTop:4}}>{ii.prompt}</p>
    </div>}
    <button className="btn primary" style={{marginTop:14}} onClick={onSolve}>Mark seder complete ✓</button>
  </>;
}

function findConceptInAtlas(conceptId){
  if (!ATLAS) return null;
  for (const file of Object.values(ATLAS)){
    if (file && Array.isArray(file.concept_atlas)){
      for (const c of file.concept_atlas){
        if (c.id === conceptId) return c;
      }
    }
  }
  return null;
}

/* ================= GATE OVERLAY (timed-test gates) ================= */
function GateOpening({seder}){
  return <>
    <div className="kicker" style={{marginBottom:8,color:'var(--accent)'}}>MACRO GATE · SEDER {seder.seder_id}</div>
    <div className="mentor-line" style={{whiteSpace:'pre-wrap'}}>{seder.mentor_intro || '(no intro)'}</div>
    {seder.spine?.teaching_notes_for_this_day && <div className="callout" style={{marginTop:14}}><b>Format:</b> {seder.spine.teaching_notes_for_this_day}</div>}
    <p style={{marginTop:14,fontSize:12,color:'var(--text-3)'}}>Threshold: <b>{seder.seder_id === 96 ? '85' : '80'}/100</b>. Below threshold → fail-path recovery (2-5 sedarim drill, then retake).</p>
  </>;
}

function GateProblems({seder}){
  const ap = seder.active_production;
  const pl = seder.practice_ladder;
  const ind = pl?.independent;
  const totalMin = (ap?.expected_duration_minutes) || (ind?.context?.match(/(\d+)\s*min/i)?.[1] && +ind.context.match(/(\d+)\s*min/i)[1]) || 90;
  const [secsLeft,setSecsLeft] = useState(totalMin*60);
  const [running,setRunning] = useState(false);
  useEffect(()=>{
    if (!running) return;
    const id = setInterval(()=>setSecsLeft(s => s>0 ? s-1 : 0), 1000);
    return ()=>clearInterval(id);
  },[running]);
  const m = Math.floor(secsLeft/60), s = secsLeft%60;
  return <>
    <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:14,padding:12,border:'1px solid var(--accent)',background:'var(--accent-dim)'}}>
      <div>
        <div className="kicker" style={{color:'var(--accent)'}}>TIMED GATE</div>
        <div style={{fontFamily:'JetBrains Mono, monospace',fontSize:28,fontWeight:600,color:'var(--accent)'}}>{String(m).padStart(2,'0')}:{String(s).padStart(2,'0')}</div>
      </div>
      <div style={{display:'flex',gap:8}}>
        <button className="btn primary" onClick={()=>setRunning(r=>!r)}>{running?'⏸ pause':'▶ start'}</button>
        <button className="btn sm" onClick={()=>{setRunning(false); setSecsLeft(totalMin*60)}}>reset</button>
      </div>
    </div>
    {ap?.prompt && <div style={{whiteSpace:'pre-wrap'}}>{ap.prompt}</div>}
    {ind && <div style={{marginTop:14}}>
      {ind.objective && <><h4 style={{color:'var(--accent)'}}>Objective</h4><p style={{whiteSpace:'pre-wrap'}}>{ind.objective}</p></>}
      {ind.context && <><h4 style={{color:'var(--accent)',marginTop:8}}>Context</h4><p style={{whiteSpace:'pre-wrap'}}>{ind.context}</p></>}
      {ind.problems && <div style={{marginTop:10}}>{Object.entries(ind.problems).map(([k,p]) =>
        <div key={k} className="callout" style={{marginBottom:8}}>
          <b>{k}.</b> <span style={{whiteSpace:'pre-wrap'}}>{typeof p === 'string' ? p : (p.statement || p.problem || JSON.stringify(p, null, 2))}</span>
        </div>
      )}</div>}
    </div>}
  </>;
}

function GateReflection({seder, onSolve}){
  const ce = seder.codex_entry;
  const cc = seder.calibration_cycle;
  const [predicted,setPredicted] = useState(()=>localStorage.getItem(`newton_gate_pred_${seder.seder_id}`)||'');
  const [actual,setActual] = useState(()=>localStorage.getItem(`newton_gate_actual_${seder.seder_id}`)||'');
  const [reflection,setReflection] = useState(()=>localStorage.getItem(`newton_gate_reflect_${seder.seder_id}`)||'');
  useEffect(()=>{localStorage.setItem(`newton_gate_pred_${seder.seder_id}`, predicted)},[predicted,seder.seder_id]);
  useEffect(()=>{localStorage.setItem(`newton_gate_actual_${seder.seder_id}`, actual)},[actual,seder.seder_id]);
  useEffect(()=>{localStorage.setItem(`newton_gate_reflect_${seder.seder_id}`, reflection)},[reflection,seder.seder_id]);
  return <>
    {cc && <>
      <h4 style={{color:'var(--accent)'}}>Calibration cycle</h4>
      {cc.predict_prompt && <><p>{cc.predict_prompt}</p>
        <input type="number" value={predicted} onChange={e=>setPredicted(e.target.value)} placeholder="predicted score" style={{width:160,marginRight:8}}/>%
      </>}
      {cc.compare_prompt && <><p style={{marginTop:10}}>{cc.compare_prompt}</p>
        <input type="number" value={actual} onChange={e=>setActual(e.target.value)} placeholder="actual score" style={{width:160,marginRight:8}}/>%
      </>}
      {predicted && actual && <div className="callout" style={{marginTop:8}}>
        <b>Gap:</b> {(+actual - +predicted).toFixed(0)}% — {Math.abs(+actual - +predicted) < 5 ? 'tightly calibrated' : Math.abs(+actual - +predicted) < 15 ? 'moderate gap' : 'large gap — meso reflection in Yoman'}
      </div>}
    </>}
    {ce && <>
      <h4 style={{color:'var(--accent)',marginTop:14}}>Codex entry — letter</h4>
      {ce.prompt && <p>{ce.prompt}</p>}
      <textarea value={reflection} onChange={e=>setReflection(e.target.value)} placeholder="Letter to next-shalav-self / reflection…" style={{width:'100%',minHeight:140,padding:10,fontFamily:'JetBrains Mono, monospace',fontSize:12,marginTop:6}}/>
    </>}
    <button className="btn primary" style={{marginTop:14}} onClick={onSolve}>Submit gate ✓</button>
  </>;
}

/* ================= CHARTER VIEW ================= */
function CharterView({lang}){
  const t = makeT(lang);
  const c = CHARTER || {identity:'', habit:'', resistance:''};
  const [signed,setSigned] = useState(()=>localStorage.getItem('newton_charter_signed')||'');
  const [date,setDate] = useState(()=>localStorage.getItem('newton_charter_date')||'');
  useEffect(()=>{localStorage.setItem('newton_charter_signed',signed)},[signed]);
  useEffect(()=>{localStorage.setItem('newton_charter_date',date)},[date]);
  return <div className="page">
    <div className="kicker">INNER-GOALS CHARTER · DR. HILA SHOHAM, WITNESS</div>
    <section className="hero">
      <div className="title">The Charter</div>
      <p className="lead">Three vows. Read aloud on Seder 1. Signed and dated. Re-read at every macro gate.</p>
    </section>

    <div className="section-title">Identity</div>
    <div className="section-rule"/>
    <p className="lead" style={{marginBottom:24}}>{c.identity}</p>

    <div className="section-title">Habit</div>
    <div className="section-rule"/>
    <p className="lead" style={{marginBottom:24}}>{c.habit}</p>

    <div className="section-title">Resistance</div>
    <div className="section-rule"/>
    <p className="lead" style={{marginBottom:24}}>{c.resistance}</p>

    <div className="section-title">Signature</div>
    <div className="section-rule"/>
    <div style={{display:'grid',gridTemplateColumns:'2fr 1fr',gap:12,maxWidth:520,marginTop:14}}>
      <input value={signed} onChange={e=>setSigned(e.target.value)} placeholder="Your name (signed)"/>
      <input type="date" value={date} onChange={e=>setDate(e.target.value)}/>
    </div>
    <p style={{fontSize:11,color:'var(--text-3)',marginTop:8,letterSpacing:1}}>Witnessed: Dr. Hila Shoham (canonical mentor — Newton course)</p>
    {signed && date && <div className="callout" style={{marginTop:18,borderColor:'var(--accent)'}}>
      <b>✓ Signed.</b> Re-read at every macro gate (sedarim 14, 34, 46, 64, 78, 94, 96).
    </div>}
  </div>;
}

/* ================= YOMAN EDITOR ================= */
function YomanEditor({lang}){
  const [yoman,setYoman] = useState(()=>{
    try { return JSON.parse(localStorage.getItem('newton_yoman_v1')) || {entries:{}, errors:[], aha:[]}; }
    catch { return {entries:{}, errors:[], aha:[]}; }
  });
  useEffect(()=>{localStorage.setItem('newton_yoman_v1', JSON.stringify(yoman))},[yoman]);
  const [tab,setTab] = useState('distillates');
  const [filter,setFilter] = useState('');

  const updateEntry = (id, field, value) => setYoman(y => ({
    ...y,
    entries: {...y.entries, [id]: {...(y.entries[id]||{}), [field]: value}},
  }));
  const addError = () => setYoman(y => ({...y, errors:[...y.errors, {date:new Date().toISOString().slice(0,10), seder:'', concept:'', wrong:'', tag:'concept_gap', revisit:''}]}));
  const updateError = (i, field, value) => setYoman(y => ({...y, errors: y.errors.map((er,j) => j===i ? {...er, [field]:value} : er)}));
  const removeError = (i) => setYoman(y => ({...y, errors: y.errors.filter((_,j) => j!==i)}));

  const tags = ['concept_gap','diagram_incomplete','procedural','sign_inconsistent','units_crashed','fatigue','peeked_too_early','estimate_skipped'];
  const tabs = [['distillates','Daily Distillates'],['errors','Yoman Shgi\'ot'],['aha','Aha Captures'],['summary','Summary']];

  return <div className="page wide">
    <div className="kicker">YOMAN MAA'BADA · WORKSHOP JOURNAL</div>
    <div className="section-title">Yoman</div>
    <div className="section-rule"/>
    <div className="section-sub">Saved to localStorage. The course refers back at every meso/macro checkpoint.</div>

    <div className="modal-steps" style={{margin:'14px 0'}}>
      {tabs.map(([k,l]) =>
        <button key={k} className={'step-tab '+(tab===k?'on':'')} onClick={()=>setTab(k)} style={{flex:1}}>{l}</button>
      )}
    </div>

    {tab==='distillates' && <>
      <input value={filter} onChange={e=>setFilter(e.target.value)} placeholder="Filter by seder number or title…" style={{width:'100%',marginBottom:10}}/>
      <div style={{display:'grid',gap:10}}>
        {LESSONS.filter(d => !filter || String(d.id).includes(filter) || (d.title||'').toLowerCase().includes(filter.toLowerCase())).slice(0,30).map(d => {
          const e = yoman.entries[d.id] || {};
          return <div key={d.id} className="card" style={{padding:14}}>
            <div className="ix">SEDER {String(d.id).padStart(2,'0')}</div>
            <div className="title" style={{marginBottom:6}}>{d.title}</div>
            <textarea value={e.distillate||''} onChange={ev=>updateEntry(d.id,'distillate',ev.target.value)} placeholder="One sentence: what clicked today?" style={{width:'100%',minHeight:50,padding:8,fontSize:12}}/>
            <div style={{display:'flex',gap:6,marginTop:6,alignItems:'center',fontSize:12}}>
              <span style={{color:'var(--text-3)'}}>Rating:</span>
              {['🤔','💡','🔥'].map(r => <button key={r} className={'btn sm '+(e.rating===r?'primary':'')} onClick={()=>updateEntry(d.id,'rating',r)}>{r}</button>)}
              <span style={{color:'var(--text-3)',marginLeft:12}}>Sketched first?</span>
              {['Yes','Most','No'].map(p => <button key={p} className={'btn sm '+(e.pulse===p?'primary':'')} onClick={()=>updateEntry(d.id,'pulse',p)}>{p}</button>)}
            </div>
          </div>;
        })}
      </div>
      {LESSONS.length > 30 && <p style={{color:'var(--text-3)',fontSize:11,marginTop:10}}>Showing first 30 sedarim. Type a number or title above to filter.</p>}
    </>}

    {tab==='errors' && <>
      <button className="btn primary" onClick={addError} style={{marginBottom:10}}>+ Log error</button>
      <div style={{display:'grid',gap:10}}>
        {yoman.errors.length === 0 && <p style={{color:'var(--text-3)'}}>No errors logged yet. Press the button above to add one.</p>}
        {yoman.errors.map((er,i) => <div key={i} className="card" style={{padding:14}}>
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:8}}>
            <input value={er.date} onChange={e=>updateError(i,'date',e.target.value)} placeholder="date"/>
            <input value={er.seder} onChange={e=>updateError(i,'seder',e.target.value)} placeholder="seder #"/>
            <input value={er.concept} onChange={e=>updateError(i,'concept',e.target.value)} placeholder="concept"/>
          </div>
          <textarea value={er.wrong} onChange={e=>updateError(i,'wrong',e.target.value)} placeholder="What I got wrong…" style={{width:'100%',marginTop:6,minHeight:50,padding:8,fontSize:12}}/>
          <div style={{display:'flex',gap:8,marginTop:6,alignItems:'center'}}>
            <select value={er.tag} onChange={e=>updateError(i,'tag',e.target.value)}>{tags.map(t => <option key={t} value={t}>{t}</option>)}</select>
            <input value={er.revisit} onChange={e=>updateError(i,'revisit',e.target.value)} placeholder="revisit by (date)" style={{flex:1}}/>
            <button className="btn sm" onClick={()=>removeError(i)}>×</button>
          </div>
        </div>)}
      </div>
    </>}

    {tab==='aha' && <>
      <p style={{color:'var(--text-3)',marginBottom:10}}>24 aha beats engineered across the course. Capture each in one sentence after the 60-second sitting countdown.</p>
      <div style={{display:'grid',gap:10}}>
        {Array.from({length:24},(_,i)=>i+1).map(n => {
          const id = `aha_${String(n).padStart(2,'0')}`;
          const v = yoman.aha[n-1] || '';
          return <div key={id} className="card" style={{padding:10,display:'grid',gridTemplateColumns:'80px 1fr',gap:10,alignItems:'center'}}>
            <div className="ix">{id}</div>
            <input value={v} onChange={e=>setYoman(y => {const arr=[...y.aha]; arr[n-1]=e.target.value; return {...y,aha:arr}})} placeholder={`aha #${n} — one sentence capture`}/>
          </div>;
        })}
      </div>
    </>}

    {tab==='summary' && <>
      <div className="stats" style={{marginTop:14}}>
        <div className="stat"><div className="n">{Object.keys(yoman.entries).length}</div><div className="l">Daily distillates</div></div>
        <div className="stat"><div className="n">{yoman.errors.length}</div><div className="l">Errors logged</div></div>
        <div className="stat"><div className="n">{yoman.aha.filter(Boolean).length}/24</div><div className="l">Aha captures</div></div>
      </div>
      <div style={{marginTop:18,padding:14,border:'1px solid var(--border)'}}>
        <h4 style={{color:'var(--accent)'}}>Export</h4>
        <p style={{fontSize:12,color:'var(--text-3)'}}>Copy-paste into Obsidian / Notion / paper notebook for permanent record.</p>
        <textarea readOnly value={JSON.stringify(yoman, null, 2)} style={{width:'100%',minHeight:200,marginTop:8,padding:10,fontFamily:'JetBrains Mono, monospace',fontSize:11}}/>
      </div>
    </>}
  </div>;
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
