/* ============================================================
   DARWIN — Biology Course
   Herbarium / specimen aesthetic — sepia on warm paper
   ============================================================ */
const {useState, useEffect, useRef, useMemo} = React;

const PHASES = [
  {id:1, name:'Cells & Molecules'},
  {id:2, name:'Genetics'},
  {id:3, name:'Evolution'},
  {id:4, name:'Ecology'},
  {id:5, name:'Physiology'},
];

const DAYS = [
  {id:1, phase:1, title:'The Cell', sub:'organelles · boundaries',
   intro:'Every living thing is a cell, or a colony of cells. The first microscopes revealed a second world — bounded, compartmented, improvised — where molecules cooperate to be alive.',
   sections:[
     {h:'The membrane', p:'A phospholipid bilayer — hydrophobic tails facing inward, hydrophilic heads facing water. It\'s flexible, self-healing, and selectively permeable. The first decision any cell makes is what to let in.'},
     {h:'The nucleus & genetic core', p:'In eukaryotes, DNA lives in a nucleus — a membrane-bound library. In prokaryotes, it floats free in the cytoplasm. Both are readable by the same molecular machinery.'},
     {callout:'Mitochondria have their own DNA, circular and bacterial. The leading hypothesis: billions of years ago one cell swallowed another and they never separated. You are walking around with ancient endosymbionts.'},
   ],
   artifact:'cell-diagram', drill:'organelles'},
  {id:2, phase:1, title:'DNA → RNA → Protein', sub:'the central dogma',
   intro:'Information flows one way in biology: DNA holds the blueprint, RNA carries the message, proteins do the work. Every living cell — every virus, every you — runs this pipeline.',
   sections:[
     {h:'Transcription', p:'RNA polymerase reads a DNA strand and builds a complementary mRNA copy. A → U, T → A, G → C, C → G. The copy can leave the nucleus.'},
     {h:'Translation', p:'Ribosomes read mRNA three letters at a time (a codon). Each codon specifies one of 20 amino acids, or a stop. The chain folds into a protein.'},
     {formula:'DNA → mRNA → ribosome → amino acids → protein'},
   ],
   artifact:'codon-table', drill:'transcribe'},
  {id:3, phase:2, title:'Mendelian Inheritance', sub:'dominant · recessive · Punnett',
   intro:'An Augustinian monk counted pea plants for eight years and discovered the first rule of genetics. One gene. Two alleles. Traits that skip generations and come back predictably.',
   sections:[
     {h:'Alleles', p:'Each gene comes in versions. Capital letter = dominant, lowercase = recessive. Every organism has two copies (one from each parent). You show the dominant trait if you have at least one dominant allele.'},
     {h:'The Punnett square', p:'Cross Aa × Aa. Offspring genotypes: 1 AA : 2 Aa : 1 aa. Phenotypes: 3 dominant : 1 recessive. This 3:1 ratio, repeated across thousands of pea plants, is how Mendel found the rules.'},
   ],
   artifact:'punnett', drill:'punnett'},
  {id:4, phase:3, title:'Natural Selection', sub:'variation · heritability · differential success',
   intro:'Darwin\'s dangerous idea, in three lines: individuals vary; variation is inherited; some variants survive and reproduce more than others. Repeat for enough generations and you get finches with fourteen beak shapes.',
   sections:[
     {h:'The three conditions', p:'For evolution by natural selection, you need: (1) variation in the population, (2) that variation being heritable, and (3) differential reproductive success. All three, or nothing happens.'},
     {callout:'Selection does not have a direction. It pushes toward whatever works in the current environment. Change the environment and a once-advantageous trait becomes a liability — the peppered moth, the polar bear.'},
   ],
   artifact:'selection-sim', drill:'fitness'},
  {id:5, phase:4, title:'Populations & Ecosystems', sub:'carrying capacity · trophic pyramids',
   intro:'Biology at scale. Populations grow exponentially until they can\'t, then oscillate around their carrying capacity. Energy flows from sun to plant to animal to animal, losing ~90% at each step.',
   sections:[
     {h:'Logistic growth', p:'Populations grow fast when small, slow as they approach the carrying capacity K. dN/dt = rN(1 - N/K) — the equation that governs rabbits, bacteria, and human cities.'},
     {formula:'dN/dt = rN(1 − N/K)'},
     {h:'Trophic pyramids', p:'Primary producers (plants) → herbivores → carnivores → apex predators. Only ~10% of energy passes up each level, which is why apex predators are rare.'},
   ],
   artifact:'logistic', drill:'logistic'},
  {id:6, phase:5, title:'Homeostasis', sub:'feedback loops · set points',
   intro:'Every living thing maintains an internal environment very different from outside. Body temperature at 37°C, blood pH at 7.4, glucose at ~90 mg/dL. Homeostasis is the technology for staying the same.',
   sections:[
     {h:'Negative feedback', p:'A perturbation triggers a response that counteracts it. Too hot → sweat → cool down. Too cold → shiver → warm up. Most of physiology is negative feedback loops.'},
     {h:'Positive feedback', p:'Rare — because it\'s dangerous. Blood clotting, childbirth, action potentials. Each one ends in a definite outcome, then stops.'},
   ],
   artifact:'feedback', drill:'feedback'},
];

const CHALLENGES = [
  {id:'b1', day:1, title:'Identify the organelle', difficulty:'easy', xp:15,
   prompt:'Which organelle has its own circular DNA, suggesting a bacterial ancestor?',
   options:['Nucleus','Mitochondrion','Golgi apparatus','Ribosome'], correct:1,
   hint:'Endosymbiotic theory.', solution:'Mitochondria (and chloroplasts) have their own circular DNA — remnants of their ancient bacterial origins.'},
  {id:'b2', day:2, title:'Transcribe a sequence', difficulty:'medium', xp:25,
   prompt:'DNA template: 3′-TACGGA-5′. What is the mRNA (5′→3′)?',
   free:true, answer:'AUGCCU', unit:'',
   hint:'Complement + read 5′→3′. T→A, A→U, G→C, C→G.', solution:'mRNA = 5′-AUGCCU-3′. Each base is the complement of the template, read in reverse.'},
  {id:'b3', day:3, title:'Monohybrid cross', difficulty:'medium', xp:30,
   prompt:'Aa × Aa. Fraction of offspring with aa genotype?',
   free:true, answer:'0.25', tol:0.01, unit:'',
   hint:'Punnett square: AA, Aa, Aa, aa → 1/4 aa.', solution:'1/4 or 0.25 — the Aa × Aa cross gives a 1:2:1 genotype ratio.'},
  {id:'b4', day:4, title:'Hardy-Weinberg', difficulty:'hard', xp:40,
   prompt:'If q² = 0.16 (recessive homozygotes), what is the allele frequency q?',
   free:true, answer:'0.4', tol:0.02, unit:'',
   hint:'q = √(q²)', solution:'q = √0.16 = 0.4. Then p = 1 - q = 0.6 and heterozygotes = 2pq = 0.48.'},
  {id:'b5', day:5, title:'Carrying capacity', difficulty:'medium', xp:30,
   prompt:'At N = K/2, dN/dt is at its maximum. True or false?',
   options:['True','False'], correct:0,
   hint:'Differentiate rN(1-N/K) and set to zero.', solution:'True — growth is fastest at half carrying capacity, where dN/dt = rK/4.'},
  {id:'b6', day:6, title:'Negative feedback', difficulty:'easy', xp:20,
   prompt:'Which is an example of POSITIVE feedback?',
   options:['Sweating when hot','Shivering when cold','Blood clotting','Insulin release after a meal'], correct:2,
   hint:'Positive feedback amplifies toward a definite end.', solution:'Blood clotting: one activated factor activates more, rapidly producing a clot.'},
];

const RANKS = ['Collector','Field Assistant','Taxonomist','Naturalist','Curator','Fellow'];
const BADGES = [
  {id:'b1', n:'Cytology', e:true, ic:'①'},
  {id:'b2', n:'Central Dogma', e:true, ic:'②'},
  {id:'b3', n:'Heredity', e:false, ic:'③'},
  {id:'b4', n:'Selection', e:false, ic:'④'},
  {id:'b5', n:'Ecology', e:false, ic:'⑤'},
  {id:'b6', n:'Physiology', e:false, ic:'⑥'},
  {id:'b7', n:'Anatomy', e:false, ic:'⑦'},
  {id:'b8', n:'Behavior', e:false, ic:'⑧'},
];

function App(){
  const [theme,setTheme] = useState(()=>localStorage.getItem('dw_theme')||'light');
  const [view,setView] = useState(()=>localStorage.getItem('dw_view')||'dash');
  const [phase,setPhase] = useState(1);
  const [xp,setXp] = useState(()=>+localStorage.getItem('dw_xp')||0);
  const [completed,setCompleted] = useState(()=>{ try{return JSON.parse(localStorage.getItem('dw_comp'))||{}}catch{return {}} });
  const [lesson,setLesson] = useState(null);
  const [challenge,setChallenge] = useState(null);
  const [toast,setToast] = useState(null);

  useEffect(()=>{document.documentElement.dataset.theme = theme; localStorage.setItem('dw_theme',theme)},[theme]);
  useEffect(()=>{localStorage.setItem('dw_view',view)},[view]);
  useEffect(()=>{localStorage.setItem('dw_xp',xp)},[xp]);
  useEffect(()=>{localStorage.setItem('dw_comp',JSON.stringify(completed))},[completed]);

  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(RANKS.length-1, Math.floor(xp/200));
  const nextRankXp = (rankIdx+1)*200;

  return <>
    <div className="header">
      <div className="logo">
        <div className="mark">Darwin</div>
        <div className="sub">Natural History</div>
      </div>
      <div className="hcenter">
        <div className="rank-chip"><div className="dot"/>Rank · {RANKS[rankIdx]}</div>
        <div className="xp-bar">
          <span>XP</span>
          <div className="xp-track"><div className="xp-fill" style={{width:`${(xp%200)/200*100}%`}}/></div>
          <span>{xp}/{nextRankXp}</span>
        </div>
        <div className="streak">✦ 3 day streak</div>
      </div>
      <div className="hright">
        <button className="chip-btn" onClick={()=>setTheme(t=>t==='dark'?'light':'dark')}>{theme==='dark'?'☀ light':'☾ dark'}</button>
      </div>
    </div>

    <div className="tabs">
      {[
        ['dash','Field Notes'],['learn','Curriculum'],['challenges','Problems'],
        ['sandbox','Herbarium'],['profile','Ledger']
      ].map(([k,l]) =>
        <button key={k} className={'tab '+(view===k?'active':'')} onClick={()=>setView(k)}>{l}</button>
      )}
    </div>

    <div className="shell">
      {view==='dash' && <Dashboard xp={xp} phase={phase} setPhase={setPhase} onOpenDay={id=>setLesson(id)} onOpenSandbox={()=>setView('sandbox')} completed={completed}/>}
      {view==='learn' && <Curriculum phase={phase} setPhase={setPhase} onOpenDay={id=>setLesson(id)} completed={completed}/>}
      {view==='challenges' && <Challenges onOpen={id=>setChallenge(id)} completed={completed}/>}
      {view==='sandbox' && <Sandbox onAward={award}/>}
      {view==='profile' && <Profile xp={xp} rankIdx={rankIdx} completed={completed}/>}
    </div>

    {lesson !== null && <LessonOverlay day={DAYS.find(d=>d.id===lesson)} onClose={()=>setLesson(null)} onDone={(id)=>{markDone('d'+id); award(50,'LESSON COMPLETE'); setLesson(null)}} done={!!completed['d'+lesson]}/>}
    {challenge && <ChallengeOverlay ch={CHALLENGES.find(c=>c.id===challenge)} onClose={()=>setChallenge(null)} onSolve={(ch)=>{markDone(ch.id); award(ch.xp,'SOLVED'); setChallenge(null)}} done={!!completed[challenge]}/>}
    {toast && <div className="toast">{toast}</div>}
  </>;
}

function Dashboard({xp, phase, setPhase, onOpenDay, onOpenSandbox, completed}){
  const lessonCount = Object.keys(completed).filter(k=>k.startsWith('d')).length;
  const probCount = Object.keys(completed).filter(k=>k.startsWith('b')).length;
  return <>
    <div className="dash-hero">
      <div className="dash-card">
        <div className="tag">Today · Cells</div>
        <h1>Every living thing is a cell — or a colony of cells.</h1>
        <p>We begin where biology begins: with membranes, organelles, and the quiet chemistry that separates alive from not-alive. Today\'s lesson traces the ancient bargain between a cell and its mitochondria.</p>
        <div className="dash-actions">
          <button className="btn btn-solid" onClick={()=>onOpenDay(1)}>Begin lesson →</button>
          <button className="btn" onClick={onOpenSandbox}>Open herbarium</button>
        </div>
      </div>
      <div>
        <div className="stats" style={{marginBottom:14}}>
          <div className="stat"><div className="n">{lessonCount}</div><div className="l">Lessons</div></div>
          <div className="stat"><div className="n">{probCount}</div><div className="l">Problems</div></div>
          <div className="stat"><div className="n">{xp}</div><div className="l">XP total</div></div>
        </div>
        <div className="dash-card">
          <div className="tag">From the Voyage</div>
          <p className="serif" style={{fontStyle:'italic',fontSize:16,color:'var(--ink)',marginBottom:6}}>"It is not the strongest that survives, nor the most intelligent, but the one most responsive to change."</p>
          <p style={{fontSize:11,color:'var(--ink-3)',letterSpacing:2}}>— ATTRIBUTED TO DARWIN</p>
        </div>
      </div>
    </div>

    <div className="section-title">Phase · {PHASES.find(p=>p.id===phase).name} <span className="section-sub">// {phase} OF {PHASES.length}</span></div>
    <div className="day-bar">
      {DAYS.filter(d=>d.phase===phase).map(d =>
        <div key={d.id} className={'day '+(completed['d'+d.id]?'active':'')} onClick={()=>onOpenDay(d.id)}>
          <div className="d-idx">{d.id.toString().padStart(2,'0')}</div>
          <div className="d-sub">Day · {d.sub}</div>
          <h3>{d.title}</h3>
          <div className="d-meta"><span>~20 min</span><span>· 1 artifact</span><span>· 1 drill</span></div>
          <div className="day-prog"><div className="fill" style={{width:completed['d'+d.id]?'100%':'0%'}}/></div>
        </div>
      )}
    </div>

    <div className="section-title">Phases <span className="section-sub">// TRACK</span></div>
    <div className="stats" style={{gridTemplateColumns:'repeat(5,1fr)'}}>
      {PHASES.map(p =>
        <div key={p.id} className="stat" onClick={()=>setPhase(p.id)} style={{cursor:'pointer',borderColor:phase===p.id?'var(--g)':'var(--line)'}}>
          <div className="n" style={{fontSize:22}}>{p.id.toString().padStart(2,'0')}</div>
          <div className="l">{p.name}</div>
        </div>
      )}
    </div>
  </>;
}

function Curriculum({phase, setPhase, onOpenDay, completed}){
  return <>
    <div className="section-title">Curriculum <span className="section-sub">// {DAYS.filter(d=>d.phase===phase).length} LESSONS · PHASE {phase}</span></div>
    <div style={{display:'flex',gap:8,marginBottom:20,flexWrap:'wrap'}}>
      {PHASES.map(p =>
        <button key={p.id} className={'chip-btn '+(phase===p.id?'active':'')} onClick={()=>setPhase(p.id)}>
          {p.id.toString().padStart(2,'0')} · {p.name}
        </button>
      )}
    </div>
    <div className="lesson-grid">
      {DAYS.filter(d=>d.phase===phase).map(d =>
        <div key={d.id} className={'lesson '+(completed['d'+d.id]?'done':'')} onClick={()=>onOpenDay(d.id)}>
          {completed['d'+d.id] && <div className="done-mark">✓</div>}
          <div className="tag">Lesson · {d.id.toString().padStart(2,'0')}</div>
          <h4>{d.title}</h4>
          <p>{d.intro}</p>
          <div className="foot"><span>~20 min</span><span>· {d.sections.length} sections</span><span>· 1 artifact</span></div>
        </div>
      )}
    </div>
  </>;
}

function LessonOverlay({day, onClose, onDone, done}){
  const [step,setStep] = useState(0);
  const stepNames = ['Read','Artifact','Drill'];
  return <div className="overlay">
    <div className="modal" style={{maxWidth:960}}>
      <div className="modal-head">
        <div>
          <div className="mtag">Lesson {day.id.toString().padStart(2,'0')} · {stepNames[step]}</div>
          <h2>{day.title}</h2>
        </div>
        <div style={{display:'flex',gap:6,alignItems:'center'}}>
          {stepNames.map((s,i) => <button key={i} className={'btn btn-sm '+(step===i?'btn-solid':'')} onClick={()=>setStep(i)}>{s}</button>)}
          <button className="modal-close" onClick={onClose}>×</button>
        </div>
      </div>
      <div className="modal-body">
        {step===0 && <>
          <p className="lead">{day.intro}</p>
          {day.sections.map((s,i) => {
            if(s.formula) return <div key={i} className="formula">{s.formula}</div>;
            if(s.callout) return <div key={i} className="callout">{s.callout}</div>;
            return <React.Fragment key={i}><h3>{s.h}</h3><p>{s.p}</p></React.Fragment>;
          })}
        </>}
        {step===1 && <>
          <p>Study the specimen. Move the controls. Observe what changes.</p>
          <ArtifactWidget kind={day.artifact}/>
        </>}
        {step===2 && <>
          <p>Test the idea. Solve the drill to mark this lesson complete.</p>
          <DrillWidget kind={day.drill} onSolve={()=>onDone(day.id)}/>
        </>}
      </div>
      <div className="modal-foot">
        <button className="btn" onClick={()=>setStep(Math.max(0,step-1))} disabled={step===0}>← Back</button>
        <div style={{fontSize:10,letterSpacing:2,color:'var(--ink-3)'}}>{done?'✓ COMPLETE':'STEP '+(step+1)+' OF 3'}</div>
        {step<2 ? <button className="btn btn-solid" onClick={()=>setStep(step+1)}>Next →</button>
                : <button className="btn btn-sepia" onClick={()=>onDone(day.id)}>Mark complete ✓</button>}
      </div>
    </div>
  </div>;
}

function ArtifactWidget({kind}){
  if(kind==='cell-diagram') return <CellDiagram/>;
  if(kind==='codon-table') return <CodonLookup/>;
  if(kind==='punnett') return <PunnettArt/>;
  if(kind==='selection-sim') return <SelectionSim/>;
  if(kind==='logistic') return <LogisticArt/>;
  if(kind==='feedback') return <FeedbackArt/>;
  return <div className="artifact"><div className="a-tag">Artifact</div>Placeholder</div>;
}

function CellDiagram(){
  const [zoom,setZoom] = useState(1);
  return <div className="artifact">
    <div className="a-tag">Artifact · Eukaryotic cell</div>
    <div className="a-title">Interactive specimen</div>
    <div className="knob-row"><label>magnify</label><input type="range" min="0.7" max="1.5" step="0.05" value={zoom} onChange={e=>setZoom(+e.target.value)}/><span className="val">{zoom.toFixed(2)}×</span></div>
    <svg viewBox="0 0 420 240" style={{width:'100%',border:'1px solid var(--line)',background:'var(--bg)',marginTop:10}}>
      <g transform={`translate(210 120) scale(${zoom}) translate(-210 -120)`}>
        <ellipse cx="210" cy="120" rx="190" ry="100" fill="var(--g-dim)" stroke="var(--g)" strokeWidth="2" strokeDasharray="1 2"/>
        <ellipse cx="210" cy="120" rx="45" ry="40" fill="var(--plum)" opacity=".8"/>
        <circle cx="200" cy="115" r="8" fill="var(--ink)" opacity=".6"/>
        <text x="210" y="124" textAnchor="middle" fontSize="10" fill="#fff" fontFamily="Playfair Display" fontStyle="italic">nucleus</text>
        <ellipse cx="130" cy="80" rx="22" ry="14" fill="var(--red)" opacity=".9"/>
        <text x="130" y="74" textAnchor="middle" fontSize="8" fill="var(--ink-2)" fontFamily="Playfair Display" fontStyle="italic">mitochondrion</text>
        <ellipse cx="310" cy="170" rx="24" ry="14" fill="var(--red)" opacity=".9"/>
        <ellipse cx="100" cy="165" rx="20" ry="12" fill="var(--g)" opacity=".9"/>
        <text x="100" y="186" textAnchor="middle" fontSize="8" fill="var(--ink-2)" fontFamily="Playfair Display" fontStyle="italic">chloroplast</text>
        <path d="M 280 60 Q 320 70, 340 90 Q 350 100, 330 110" fill="none" stroke="var(--sepia)" strokeWidth="2"/>
        <text x="340" y="58" fontSize="8" fill="var(--ink-2)" fontFamily="Playfair Display" fontStyle="italic">ER</text>
        {[...Array(12)].map((_,i) => <circle key={i} cx={60+i*30} cy={30+((i%3)*7)} r="3" fill="var(--sepia)"/>)}
        <text x="60" y="22" fontSize="8" fill="var(--ink-2)" fontFamily="Playfair Display" fontStyle="italic">ribosomes</text>
      </g>
    </svg>
    <div style={{fontSize:11,color:'var(--ink-3)',marginTop:6,fontStyle:'italic',fontFamily:'Playfair Display'}}>Fig. 01 — simplified eukaryotic cell, after Hooke & Leeuwenhoek.</div>
  </div>;
}

function CodonLookup(){
  const [seq,setSeq] = useState('AUGCCU');
  const codons = seq.toUpperCase().replace(/[^AUGC]/g,'').match(/.{1,3}/g) || [];
  const table = {
    AUG:'Met (START)',UUU:'Phe',UUC:'Phe',UUA:'Leu',UUG:'Leu',CUU:'Leu',CUC:'Leu',CUA:'Leu',CUG:'Leu',
    AUU:'Ile',AUC:'Ile',AUA:'Ile',GUU:'Val',GUC:'Val',GUA:'Val',GUG:'Val',
    UCU:'Ser',UCC:'Ser',UCA:'Ser',UCG:'Ser',CCU:'Pro',CCC:'Pro',CCA:'Pro',CCG:'Pro',
    ACU:'Thr',ACC:'Thr',ACA:'Thr',ACG:'Thr',GCU:'Ala',GCC:'Ala',GCA:'Ala',GCG:'Ala',
    UAU:'Tyr',UAC:'Tyr',UAA:'STOP',UAG:'STOP',UGA:'STOP',
    CAU:'His',CAC:'His',CAA:'Gln',CAG:'Gln',AAU:'Asn',AAC:'Asn',AAA:'Lys',AAG:'Lys',
    GAU:'Asp',GAC:'Asp',GAA:'Glu',GAG:'Glu',UGU:'Cys',UGC:'Cys',UGG:'Trp',
    CGU:'Arg',CGC:'Arg',CGA:'Arg',CGG:'Arg',AGU:'Ser',AGC:'Ser',AGA:'Arg',AGG:'Arg',
    GGU:'Gly',GGC:'Gly',GGA:'Gly',GGG:'Gly'
  };
  return <div className="artifact">
    <div className="a-tag">Artifact · Codon table</div>
    <div className="a-title">mRNA → amino acid</div>
    <div className="knob-row"><label>mRNA</label>
      <input type="text" value={seq} onChange={e=>setSeq(e.target.value)} style={{padding:'6px 10px',border:'1px solid var(--line)',background:'var(--bg)',fontFamily:'IBM Plex Mono, monospace',fontSize:13,letterSpacing:2,textTransform:'uppercase',color:'var(--ink)'}}/>
      <span/>
    </div>
    <div style={{display:'flex',gap:6,flexWrap:'wrap',marginTop:10}}>
      {codons.map((c,i) => {
        const aa = table[c] || '?';
        const stop = aa==='STOP';
        return <div key={i} style={{padding:'8px 12px',background:stop?'var(--bg)':'var(--g-dim)',border:'1px solid '+(stop?'var(--red)':'var(--g)'),borderRadius:2,textAlign:'center',minWidth:60}}>
          <div style={{fontFamily:'IBM Plex Mono, monospace',fontSize:13,letterSpacing:2,color:'var(--ink)'}}>{c}</div>
          <div style={{fontFamily:'Playfair Display',fontStyle:'italic',fontSize:11,color:stop?'var(--red)':'var(--g)',marginTop:2}}>{aa}</div>
        </div>;
      })}
    </div>
  </div>;
}

function PunnettArt(){
  const [p1,setP1] = useState('Aa');
  const [p2,setP2] = useState('Aa');
  const cells = [];
  for(const a of p1) for(const b of p2) cells.push((a+b).split('').sort((x,y)=>{
    // uppercase first
    if(x===x.toUpperCase() && y!==y.toUpperCase()) return -1;
    if(y===y.toUpperCase() && x!==x.toUpperCase()) return 1;
    return 0;
  }).join(''));
  const classify = g => {
    if(g[0]===g[0].toUpperCase() && g[1]===g[1].toUpperCase()) return 'dom';
    if(g[0]!==g[0].toUpperCase() && g[1]!==g[1].toUpperCase()) return 'rec';
    return 'het';
  };
  return <div className="artifact">
    <div className="a-tag">Artifact · Punnett square</div>
    <div className="a-title">Monohybrid cross</div>
    <div style={{display:'flex',gap:14,alignItems:'center',marginBottom:10}}>
      <label style={{fontSize:12,color:'var(--ink-2)'}}>P1 <input type="text" value={p1} onChange={e=>setP1(e.target.value.slice(0,2))} style={{width:50,padding:'4px 8px',border:'1px solid var(--line)',background:'var(--bg)',fontFamily:'Playfair Display',fontStyle:'italic',fontSize:14,color:'var(--ink)',marginLeft:6}}/></label>
      <label style={{fontSize:12,color:'var(--ink-2)'}}>P2 <input type="text" value={p2} onChange={e=>setP2(e.target.value.slice(0,2))} style={{width:50,padding:'4px 8px',border:'1px solid var(--line)',background:'var(--bg)',fontFamily:'Playfair Display',fontStyle:'italic',fontSize:14,color:'var(--ink)',marginLeft:6}}/></label>
    </div>
    <div className="punnett">
      <div className="hdr"/>
      <div className="hdr">{p2[0]}</div>
      <div className="hdr">{p2[1]}</div>
      <div className="hdr">{p1[0]}</div>
      <div className={'cell '+classify(cells[0])}>{cells[0]}</div>
      <div className={'cell '+classify(cells[1])}>{cells[1]}</div>
      <div className="hdr">{p1[1]}</div>
      <div className={'cell '+classify(cells[2])}>{cells[2]}</div>
      <div className={'cell '+classify(cells[3])}>{cells[3]}</div>
    </div>
    <div style={{fontSize:11,color:'var(--ink-3)',marginTop:10,fontStyle:'italic',fontFamily:'Playfair Display'}}>
      Homozygous dom: {cells.filter(c=>classify(c)==='dom').length}/4 · heterozygous: {cells.filter(c=>classify(c)==='het').length}/4 · recessive: {cells.filter(c=>classify(c)==='rec').length}/4
    </div>
  </div>;
}

function SelectionSim(){
  const [pressure,setPressure] = useState(0.5);
  const [gen,setGen] = useState(0);
  const [pop,setPop] = useState(() => Array.from({length:40},() => Math.random()));
  const run = () => {
    const next = [];
    for(let i=0;i<40;i++){
      const fit = Math.random();
      const survive = fit < (1 - pressure*(1-pop[Math.floor(Math.random()*pop.length)]));
      if(survive) next.push(Math.min(1, pop[Math.floor(Math.random()*pop.length)] + (Math.random()-0.3)*0.08));
    }
    while(next.length<40) next.push(Math.random());
    setPop(next); setGen(g=>g+1);
  };
  const avg = pop.reduce((a,b)=>a+b,0)/pop.length;
  return <div className="artifact">
    <div className="a-tag">Artifact · Natural selection</div>
    <div className="a-title">Trait drift over generations</div>
    <div className="knob-row"><label>pressure</label><input type="range" min="0" max="1" step="0.05" value={pressure} onChange={e=>setPressure(+e.target.value)}/><span className="val">{pressure.toFixed(2)}</span></div>
    <svg viewBox="0 0 420 120" style={{width:'100%',border:'1px solid var(--line)',background:'var(--bg)',marginTop:10}}>
      {pop.map((v,i) => <circle key={i} cx={20+i*10} cy={60+Math.sin(i)*30} r={3+v*4} fill={`oklch(60% 30% ${140*v})`}/>)}
      <text x="10" y="16" fontSize="10" fill="var(--ink-2)" fontFamily="IBM Plex Mono">gen {gen}</text>
      <text x="10" y="112" fontSize="10" fill="var(--g)" fontFamily="IBM Plex Mono">avg trait: {avg.toFixed(2)}</text>
    </svg>
    <div style={{display:'flex',gap:8,marginTop:10}}>
      <button className="btn btn-sm btn-solid" onClick={run}>Next generation →</button>
      <button className="btn btn-sm" onClick={()=>{setPop(Array.from({length:40},()=>Math.random())); setGen(0)}}>Reset</button>
    </div>
  </div>;
}

function LogisticArt(){
  const [r,setR] = useState(0.3);
  const [K,setK] = useState(500);
  const pts = [];
  let N = 10;
  for(let t=0;t<=60;t++){
    pts.push([t,N]);
    N = N + r*N*(1-N/K);
  }
  return <div className="artifact">
    <div className="a-tag">Artifact · Logistic growth</div>
    <div className="a-title">dN/dt = rN(1 − N/K)</div>
    <div className="knob-row"><label>r (rate)</label><input type="range" min="0.05" max="1" step="0.05" value={r} onChange={e=>setR(+e.target.value)}/><span className="val">{r.toFixed(2)}</span></div>
    <div className="knob-row"><label>K (capacity)</label><input type="range" min="100" max="1000" step="50" value={K} onChange={e=>setK(+e.target.value)}/><span className="val">{K}</span></div>
    <svg viewBox="0 0 420 160" style={{width:'100%',border:'1px solid var(--line)',background:'var(--bg)',marginTop:10}}>
      <line x1="30" y1="10" x2="30" y2="140" stroke="var(--line)"/>
      <line x1="30" y1="140" x2="410" y2="140" stroke="var(--line)"/>
      <line x1="30" y1={140-(K/1000)*120} x2="410" y2={140-(K/1000)*120} stroke="var(--sepia)" strokeDasharray="3 3" strokeWidth="0.8"/>
      <text x="405" y={138-(K/1000)*120} fontSize="9" fill="var(--sepia)" textAnchor="end">K</text>
      <polyline points={pts.map(([t,n])=>`${30+t*6.3},${140-Math.min(n,1000)/1000*120}`).join(' ')} stroke="var(--g)" strokeWidth="1.8" fill="none"/>
      <text x="32" y="20" fontSize="9" fill="var(--ink-3)">N</text>
      <text x="405" y="152" fontSize="9" fill="var(--ink-3)" textAnchor="end">t</text>
    </svg>
  </div>;
}

function FeedbackArt(){
  const [setpt,setSetpt] = useState(37);
  const [disturb,setDisturb] = useState(0);
  const [t,setT] = useState(0);
  const [temp,setTemp] = useState(37);
  useEffect(()=>{
    const id = setInterval(()=>{
      setT(x=>x+0.2);
      setTemp(c => c + (setpt-c)*0.08 + disturb*0.3 + (Math.random()-0.5)*0.1);
    },100);
    return ()=>clearInterval(id);
  },[setpt,disturb]);
  const history = useRef([]);
  history.current = [...history.current, temp].slice(-100);
  return <div className="artifact">
    <div className="a-tag">Artifact · Negative feedback</div>
    <div className="a-title">Body temperature regulation</div>
    <div className="knob-row"><label>setpoint</label><input type="range" min="35" max="40" step="0.1" value={setpt} onChange={e=>setSetpt(+e.target.value)}/><span className="val">{setpt}°C</span></div>
    <div className="knob-row"><label>disturb</label><input type="range" min="-3" max="3" step="0.1" value={disturb} onChange={e=>setDisturb(+e.target.value)}/><span className="val">{disturb.toFixed(1)}</span></div>
    <svg viewBox="0 0 420 140" style={{width:'100%',border:'1px solid var(--line)',background:'var(--bg)',marginTop:10}}>
      <line x1="0" y1={70-(setpt-37)*8} x2="420" y2={70-(setpt-37)*8} stroke="var(--sepia)" strokeDasharray="3 3" strokeWidth="0.8"/>
      <polyline points={history.current.map((v,i) => `${i*4.2},${70-(v-37)*8}`).join(' ')} stroke="var(--g)" strokeWidth="1.5" fill="none"/>
      <text x="5" y="14" fontSize="9" fill="var(--ink-3)">temp</text>
      <text x="415" y="138" fontSize="9" fill="var(--ink-3)" textAnchor="end">time →</text>
    </svg>
    <div style={{fontSize:11,color:'var(--g)',marginTop:6,fontStyle:'italic',fontFamily:'Playfair Display',fontSize:14}}>Current: {temp.toFixed(2)} °C</div>
  </div>;
}

function DrillWidget({kind, onSolve}){
  const [answer,setAnswer] = useState('');
  const [result,setResult] = useState(null);
  const drills = {
    'organelles': {q:'Which organelle performs aerobic respiration?', opts:['Nucleus','Ribosome','Mitochondrion','Golgi'], correct:2},
    'transcribe': {q:'DNA: 3′-AAT-5′. What is mRNA 5′→3′?', a:'AUU'},
    'punnett': {q:'Aa × aa. Fraction heterozygous offspring?', a:'0.5', tol:0.02},
    'fitness': {q:'If an allele has 10% advantage per generation, after 10 generations it becomes ~__× more common? (e.g. 2.59)', a:'2.59', tol:0.2},
    'logistic': {q:'At N=K, dN/dt = ?', opts:['0','rK','rK/4','K/r'], correct:0},
    'feedback': {q:'Shivering when cold is an example of ___ feedback.', opts:['positive','negative','zero','random'], correct:1},
  };
  const d = drills[kind] || drills['organelles'];
  const check = ()=>{
    if(d.opts){
      const idx = parseInt(answer);
      if(idx===d.correct){setResult('ok'); setTimeout(onSolve,500)} else setResult('no');
    } else {
      const v = d.tol ? parseFloat(answer) : answer.trim().toUpperCase();
      if(d.tol ? Math.abs(v-parseFloat(d.a))<=d.tol : v===d.a.toUpperCase()){setResult('ok'); setTimeout(onSolve,500)}
      else setResult('no');
    }
  };
  return <div className="artifact">
    <div className="a-tag">Drill</div>
    <div className="a-title">{d.q}</div>
    {d.opts ? <div style={{display:'flex',flexDirection:'column',gap:6,marginTop:10}}>
      {d.opts.map((o,i) => <button key={i} className={'btn btn-sm '+(answer===''+i?'btn-solid':'')} style={{textAlign:'left'}} onClick={()=>setAnswer(''+i)}>{String.fromCharCode(65+i)}. {o}</button>)}
    </div> : <input type="text" value={answer} onChange={e=>setAnswer(e.target.value)} placeholder="your answer" style={{padding:'8px 12px',border:'1px solid var(--line)',background:'var(--bg)',color:'var(--ink)',fontFamily:'Playfair Display',fontStyle:'italic',fontSize:16,marginTop:10,width:'100%'}}/>}
    <div style={{marginTop:10,display:'flex',alignItems:'center',gap:10}}>
      <button className="btn btn-sepia btn-sm" onClick={check}>Submit</button>
      {result==='ok' && <span style={{color:'var(--g)',fontFamily:'Playfair Display',fontStyle:'italic'}}>✓ Correct.</span>}
      {result==='no' && <span style={{color:'var(--red)',fontSize:12}}>Not quite. Try again.</span>}
    </div>
  </div>;
}

function Challenges({onOpen, completed}){
  return <>
    <div className="section-title">Problem set <span className="section-sub">// {CHALLENGES.length} PROBLEMS</span></div>
    <div className="lesson-grid">
      {CHALLENGES.map(c =>
        <div key={c.id} className={'lesson '+(completed[c.id]?'done':'')} onClick={()=>onOpen(c.id)}>
          {completed[c.id] && <div className="done-mark">✓</div>}
          <div className="tag">{c.difficulty} · {c.xp} XP</div>
          <h4>{c.title}</h4>
          <p>{c.prompt}</p>
          <div className="foot"><span>Lesson {c.day}</span></div>
        </div>
      )}
    </div>
  </>;
}

function ChallengeOverlay({ch, onClose, onSolve, done}){
  const [answer,setAnswer] = useState('');
  const [result,setResult] = useState(null);
  const [showHint,setShowHint] = useState(false);
  const [showSol,setShowSol] = useState(false);
  const check = ()=>{
    if(ch.options){
      const idx = parseInt(answer);
      if(idx===ch.correct){setResult('ok'); setTimeout(()=>onSolve(ch),700)} else setResult('no');
    } else {
      const v = ch.tol ? parseFloat(answer) : answer.trim().toUpperCase();
      if(ch.tol ? Math.abs(v-parseFloat(ch.answer))<=ch.tol : v===ch.answer.toUpperCase()){setResult('ok'); setTimeout(()=>onSolve(ch),700)}
      else setResult('no');
    }
  };
  return <div className="overlay">
    <div className="modal">
      <div className="modal-head">
        <div>
          <div className="mtag">{ch.difficulty} · {ch.xp} XP</div>
          <h2>{ch.title}</h2>
        </div>
        <button className="modal-close" onClick={onClose}>×</button>
      </div>
      <div className="modal-body">
        <p className="lead">{ch.prompt}</p>
        {ch.options ? <div style={{display:'flex',flexDirection:'column',gap:8}}>
          {ch.options.map((o,i) => <button key={i} className={'btn '+(answer===''+i?'btn-solid':'')} style={{textAlign:'left'}} onClick={()=>setAnswer(''+i)}>{String.fromCharCode(65+i)}. {o}</button>)}
        </div> : <input type="text" value={answer} onChange={e=>setAnswer(e.target.value)} placeholder="your answer" style={{padding:'10px 14px',border:'1px solid var(--line)',background:'var(--bg)',color:'var(--ink)',fontFamily:'Playfair Display',fontStyle:'italic',fontSize:18,width:'100%'}}/>}
        <div style={{margin:'18px 0',display:'flex',gap:10,alignItems:'center'}}>
          <button className="btn btn-sepia" onClick={check}>Submit</button>
          {result==='ok' && <span style={{color:'var(--g)',fontFamily:'Playfair Display',fontStyle:'italic',fontSize:18}}>✓ Correct.</span>}
          {result==='no' && <span style={{color:'var(--red)',fontSize:13}}>Not quite. Check your work.</span>}
        </div>
        {showHint && <div className="callout"><b>Hint.</b> {ch.hint}</div>}
        {showSol && <div className="callout"><b>Solution.</b> {ch.solution}</div>}
      </div>
      <div className="modal-foot">
        <div style={{display:'flex',gap:8}}>
          <button className="btn btn-sm" onClick={()=>setShowHint(v=>!v)}>{showHint?'Hide':'Show'} hint</button>
          <button className="btn btn-sm btn-ghost" onClick={()=>setShowSol(v=>!v)}>{showSol?'Hide':'Reveal'} solution</button>
        </div>
        <div style={{fontSize:10,letterSpacing:2,color:'var(--ink-3)'}}>{done?'✓ SOLVED':'OPEN'}</div>
      </div>
    </div>
  </div>;
}

/* Sandbox — Herbarium (cell builder + phylogeny) */
function Sandbox({onAward}){
  const [objects,setObjects] = useState([]);
  const [selected,setSelected] = useState(null);
  const [inspector,setInspector] = useState('specimen');
  const [mode,setMode] = useState('cell'); // 'cell' | 'tree'
  const canvasRef = useRef(null);
  const draggingRef = useRef(null);

  const addOrg = (kind)=>{
    const id = 'o'+Date.now();
    const colors = {nucleus:'plum',mito:'red',chloro:'g',ribo:'sepia'};
    setObjects(os => [...os, {id, kind, x:80+Math.random()*200, y:80+Math.random()*150}]);
  };
  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="sandbox">
    <div className="sbx-rail">
      <h4>Mode</h4>
      <div style={{display:'flex',gap:6,marginBottom:14}}>
        <button className={'btn btn-sm '+(mode==='cell'?'btn-solid':'')} onClick={()=>setMode('cell')}>Cell</button>
        <button className={'btn btn-sm '+(mode==='tree'?'btn-solid':'')} onClick={()=>setMode('tree')}>Phylogeny</button>
      </div>
      {mode==='cell' && <>
        <h4>Organelles</h4>
        <div className="palette">
          <button onClick={()=>addOrg('nucleus')}><div className="organelle org-nucleus" style={{width:32,height:32,fontSize:10}}>N</div><span>Nucleus</span></button>
          <button onClick={()=>addOrg('mito')}><div className="organelle org-mito" style={{width:34,height:22,fontSize:9}}>M</div><span>Mitochondrion</span></button>
          <button onClick={()=>addOrg('chloro')}><div className="organelle org-chloro" style={{width:30,height:20,fontSize:9}}>Cl</div><span>Chloroplast</span></button>
          <button onClick={()=>addOrg('ribo')}><div className="organelle org-ribo" style={{width:10,height:10}}/><span>Ribosome</span></button>
          <button onClick={()=>setObjects([])}><div style={{width:20,height:20,border:'1px solid var(--line)'}}>×</div><span>Clear</span></button>
        </div>
        <h4>Specimens</h4>
        <button className="btn btn-sm" style={{width:'100%',marginBottom:6}} onClick={()=>setObjects([
          {id:'n',kind:'nucleus',x:180,y:120},{id:'m1',kind:'mito',x:80,y:80},{id:'m2',kind:'mito',x:280,y:160},
          {id:'r1',kind:'ribo',x:60,y:40},{id:'r2',kind:'ribo',x:90,y:48},{id:'r3',kind:'ribo',x:120,y:40}
        ])}>Animal cell</button>
        <button className="btn btn-sm" style={{width:'100%'}} onClick={()=>setObjects([
          {id:'n',kind:'nucleus',x:180,y:120},{id:'m',kind:'mito',x:80,y:80},
          {id:'c1',kind:'chloro',x:280,y:90},{id:'c2',kind:'chloro',x:300,y:160}
        ])}>Plant cell</button>
      </>}
      {mode==='tree' && <>
        <h4>Phylogeny</h4>
        <div style={{fontSize:11,color:'var(--ink-2)',lineHeight:1.6}}>A cladogram of living kingdoms. Hover branches in the canvas.</div>
      </>}
    </div>

    <div className="sbx-canvas" ref={canvasRef} onPointerMove={onPointerMove} onPointerUp={onPointerUp} onClick={()=>setSelected(null)}>
      <div className="sbx-toolbar">
        <button className="on">Specimen</button>
        <button onClick={()=>onAward(15,'OBSERVATION')}>Log finding</button>
      </div>
      {mode==='cell' && <>
        <svg viewBox="0 0 800 500" preserveAspectRatio="none" style={{position:'absolute',inset:0,width:'100%',height:'100%',opacity:.5,pointerEvents:'none'}}>
          <ellipse cx="400" cy="250" rx="340" ry="200" fill="none" stroke="var(--g)" strokeWidth="1.5" strokeDasharray="4 3"/>
        </svg>
        {objects.map(o => {
          const cls = 'prim '+(selected===o.id?'selected':'');
          const shape = {
            nucleus:<div className="organelle org-nucleus">nucleus</div>,
            mito:<div className="organelle org-mito">mito</div>,
            chloro:<div className="organelle org-chloro">Cl</div>,
            ribo:<div className="organelle org-ribo"/>
          }[o.kind];
          return <div key={o.id} className={cls} style={{left:o.x,top:o.y}} onPointerDown={e=>onPointerDown(e,o)}>{shape}</div>;
        })}
        {objects.length===0 && <div style={{position:'absolute',top:'50%',left:'50%',transform:'translate(-50%,-50%)',color:'var(--ink-3)',fontFamily:'Playfair Display',fontStyle:'italic',fontSize:20}}>Drag organelles from the rail, or load a specimen.</div>}
      </>}
      {mode==='tree' && <div style={{padding:20}}>
        <svg viewBox="0 0 600 360" className="tree-svg">
          <line x1="30" y1="180" x2="150" y2="180" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="150" y1="60" x2="150" y2="300" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="150" y1="60" x2="300" y2="60" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="150" y1="180" x2="300" y2="180" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="150" y1="300" x2="300" y2="300" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="300" y1="140" x2="300" y2="220" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="300" y1="140" x2="450" y2="140" stroke="var(--ink)" strokeWidth="1.5"/>
          <line x1="300" y1="220" x2="450" y2="220" stroke="var(--ink)" strokeWidth="1.5"/>
          <text x="305" y="60" fontSize="12" fontFamily="Playfair Display" fontStyle="italic" fill="var(--g)">Bacteria</text>
          <text x="460" y="140" fontSize="12" fontFamily="Playfair Display" fontStyle="italic" fill="var(--plum)">Archaea</text>
          <text x="460" y="220" fontSize="12" fontFamily="Playfair Display" fontStyle="italic" fill="var(--sepia)">Eukarya</text>
          <text x="305" y="300" fontSize="12" fontFamily="Playfair Display" fontStyle="italic" fill="var(--red)">(extinct)</text>
          <text x="30" y="170" fontSize="10" fontFamily="IBM Plex Mono" fill="var(--ink-3)">LUCA</text>
          <text x="10" y="20" fontSize="11" fontFamily="Playfair Display" fontStyle="italic" fill="var(--ink-2)">Three-domain tree of life (after Woese, 1977)</text>
        </svg>
      </div>}
    </div>

    <div className="sbx-side">
      <div style={{display:'flex',gap:4,marginBottom:10}}>
        {['specimen','notes','taxonomy'].map(t =>
          <button key={t} className={'chip-btn '+(inspector===t?'active':'')} style={{padding:'3px 8px',fontSize:10}} onClick={()=>setInspector(t)}>{t}</button>
        )}
      </div>
      {inspector==='specimen' && <>
        <h4>Inspector</h4>
        {sel ? <>
          <div className="inspector-row"><span className="k">organelle</span><span className="v">{sel.kind}</span></div>
          <div className="inspector-row"><span className="k">x</span><span className="v">{sel.x.toFixed(0)}</span></div>
          <div className="inspector-row"><span className="k">y</span><span className="v">{sel.y.toFixed(0)}</span></div>
        </> : <div style={{fontSize:11,color:'var(--ink-3)'}}>Select an organelle.</div>}
        <h4 style={{marginTop:16}}>Counts</h4>
        <div className="inspector-row"><span className="k">total</span><span className="v">{objects.length}</span></div>
        <div className="inspector-row"><span className="k">nuclei</span><span className="v">{objects.filter(o=>o.kind==='nucleus').length}</span></div>
        <div className="inspector-row"><span className="k">mitochondria</span><span className="v">{objects.filter(o=>o.kind==='mito').length}</span></div>
        <div className="inspector-row"><span className="k">chloroplasts</span><span className="v">{objects.filter(o=>o.kind==='chloro').length}</span></div>
      </>}
      {inspector==='notes' && <>
        <h4>Field notes</h4>
        <textarea placeholder="Observations, measurements, hypotheses…" style={{width:'100%',minHeight:200,padding:8,border:'1px solid var(--line)',background:'var(--bg)',fontFamily:'Bitter, serif',fontSize:12,color:'var(--ink)',resize:'vertical'}}/>
      </>}
      {inspector==='taxonomy' && <>
        <h4>Linnaean hierarchy</h4>
        {['Domain','Kingdom','Phylum','Class','Order','Family','Genus','Species'].map(r =>
          <div key={r} className="inspector-row"><span className="k">{r}</span><span className="v">—</span></div>
        )}
      </>}
    </div>
  </div>;
}

function Profile({xp, rankIdx, completed}){
  const doneCount = Object.values(completed).filter(Boolean).length;
  return <div className="profile-grid">
    <div className="profile-card">
      <div className="avatar">D</div>
      <h3 className="serif" style={{fontStyle:'italic',fontSize:22,marginBottom:4}}>Field Notes</h3>
      <div style={{fontSize:10,letterSpacing:2,color:'var(--sepia)',textTransform:'uppercase'}}>Rank · {RANKS[rankIdx]}</div>
      <div style={{fontSize:30,fontFamily:'Playfair Display',fontStyle:'italic',color:'var(--g)',marginTop:16}}>{xp}</div>
      <div style={{fontSize:10,letterSpacing:2,color:'var(--ink-3)',textTransform:'uppercase'}}>XP Total</div>
      <div className="stats" style={{marginTop:18,gridTemplateColumns:'1fr 1fr'}}>
        <div className="stat"><div className="n">{doneCount}</div><div className="l">Completed</div></div>
        <div className="stat"><div className="n">{BADGES.filter(b=>b.e).length}</div><div className="l">Badges</div></div>
      </div>
    </div>
    <div>
      <div className="ranks">
        <h4 style={{fontSize:10,letterSpacing:2.5,color:'var(--sepia)',textTransform:'uppercase',marginBottom:10,fontFamily:'IBM Plex Mono'}}>Progression</h4>
        {RANKS.map((r,i) => <div key={r} className="rank-row">
          <div className="lbl"><div className="n">{(i+1).toString().padStart(2,'0')}</div><span>{r}</span></div>
          <div className="rnk">{i*200} XP</div>
        </div>)}
      </div>
      <div className="ranks" style={{marginTop:14}}>
        <h4 style={{fontSize:10,letterSpacing:2.5,color:'var(--sepia)',textTransform:'uppercase',marginBottom:4,fontFamily:'IBM Plex Mono'}}>Badges</h4>
        <div className="badge-grid">
          {BADGES.map(b => <div key={b.id} className={'badge '+(b.e?'earned':'locked')}><div className="bi">{b.ic}</div><div className="bn">{b.n}</div></div>)}
        </div>
      </div>
    </div>
  </div>;
}

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