/* φQ Chemistry: guided IA Explorer (theme → topic → IV → DV → five-step activity). Needs ia-explorer.jsx (window.IAX). */
(function () {
  const { useState, useEffect, useMemo, useRef } = React;
  const X = window.IAX, MAP = window.IA2_MAP, REL = window.IA2_REL;
  const { SYL, TOPIC, themeOf, tcol, loadLS, saveLS, seedRng, lsq, simulate, xsFor, ideaKit, cap, sym, Tx, Plot, HAS_INV, modelEq, shortLit, fromWhat } = X;
  const KP = 'chemq-ia2-progress', KL = 'chemq-ia2-list';
  const relsOfTopic = t => (MAP[t] || []).flatMap(iv => iv.ids).filter(id => REL[id]);
  const ivOf = r => (MAP[r.t] || []).find(iv => r.ivk === r.t + ':' + iv.key);
  const TECH = Object.values(REL).filter(r => r.tc);
  const shuffle = (a, seed) => { const r = seedRng(seed), b = a.slice(); for (let i = b.length - 1; i > 0; i--) { const j = Math.floor(r() * (i + 1)); [b[i], b[j]] = [b[j], b[i]]; } return b; };
  const oneMinusR2 = (A, B) => { const P = A.map((a, i) => [a, B[i]]).filter(p => isFinite(p[0]) && isFinite(p[1])); if (P.length < 3) return 1; return Math.max(0, 1 - lsq(P.map(p => p[0]), P.map(p => p[1])).r2); };
  const parseHash = () => { const h = new URLSearchParams(location.hash.slice(1)); return { th: h.get('th'), t: h.get('t'), iv: h.get('iv'), rel: h.get('rel') }; };

  /* ---------- per-relationship analysis ---------- */
  function analyse(r) {
    const g = r.g, xs = xsFor(g, 14), ys = xs.map(g.f);
    const d = ys.slice(1).map((y, i) => y - ys[i]); const sc = Math.max(...ys.map(Math.abs)) || 1;
    const sg = d.map(v => Math.abs(v) < sc * 1e-9 ? 0 : Math.sign(v)).filter(Boolean);
    let trend = 'stays the same';
    if (sg.length) { const ch = sg.filter((s, i) => i && s !== sg[i - 1]).length; trend = ch === 0 ? (sg[0] > 0 ? 'increases' : 'decreases') : sg[0] > 0 ? 'increases, then decreases' : 'decreases, then increases'; }
    const straight = oneMinusR2(xs, ys) < 1e-4;
    const s = sym(g.rx), t = sym(g.ry), isC = /°C/.test(g.rx);
    const xo = [{ l: sym(r.lin.x), f: g.X, model: 1 }, { l: s, f: x => x }, { l: s + '²', f: x => x * x }, { l: '√' + s, f: Math.sqrt }, { l: '1/' + s, f: x => 1 / x }, { l: 'ln ' + s, f: Math.log }, { l: 'log ' + s, f: Math.log10 }];
    if (isC) xo.push({ l: 'T', f: x => x + 273.15 }, { l: '1/T', f: x => 1 / (x + 273.15) });
    const yo = [{ l: sym(r.lin.y), f: g.Y, model: 1 }, { l: t, f: (x, y) => y }, { l: t + '²', f: (x, y) => y * y }, { l: '√' + t, f: (x, y) => Math.sqrt(y) }, { l: '1/' + t, f: (x, y) => 1 / y }, { l: 'ln ' + t, f: (x, y) => Math.log(y) }, { l: 'log ' + t, f: (x, y) => Math.log10(y) }];
    const dedupe = (opts, vec) => { const kept = []; opts.forEach(o => { const v = vec(o); if (!v.every(isFinite)) return; const i0 = v.findIndex(a => Math.abs(a) > 1e-300); const nv = v.map(a => a / v[i0]); if (kept.some(k => k.nv.every((a, i) => Math.abs(a - nv[i]) <= 1e-9 * Math.max(1, Math.abs(a))))) return; if (kept.some(k => k.l === o.l)) return; kept.push(Object.assign({}, o, { v, nv })); }); return kept; };
    const XO = dedupe(xo, o => xs.map(x => o.f(x))), YO = dedupe(yo, o => xs.map((x, i) => o.f(x, ys[i])));
    const mX = XO.find(o => o.model), mY = YO.find(o => o.model);
    const base = oneMinusR2(mX.v, mY.v), thr = Math.max(1e-6, 3 * base);
    return { trend, straight, XO: shuffle(XO, r.id + 'x'), YO: shuffle(YO, r.id + 'y'), mX, mY, thr };
  }
  function pairScore(A, xo, yo) { return oneMinusR2(xo.v, yo.v); }

  const REASONS_BAD = ['Every straight-line graph should pass through the origin.', 'You only expect the origin if you took a reading at zero.', 'The line should be forced through the origin so the gradient comes out right.', 'Random errors always move the line away from the origin.'];
  function quantities(r) {
    const th = themeOf(r.t);
    let pool = [...new Set(Object.values(REL).filter(x => themeOf(x.t) === th && x.q.n !== r.q.n).map(x => x.q.n))];
    if (pool.length < 3) pool = pool.concat([...new Set(Object.values(REL).filter(x => x.q.n !== r.q.n && !pool.includes(x.q.n)).map(x => x.q.n))]);
    return shuffle([r.q.n, ...shuffle(pool, r.id + 'q').slice(0, 3)], r.id + 'qq');
  }

  /* ---------- pickers ---------- */
  function Tile({ tc, kick, title, sub, onClick, tex }) {
    return <button className="ia2-tile" style={{ '--tc': tc }} onClick={onClick}><span className="kick">{kick}</span>{tex ? <Tx className="ttl" s={title} /> : <span className="ttl">{title}</span>}<span className="sub">{sub}</span></button>;
  }
  function PickTheme({ go }) {
    return <>
      <div className="ia2-step-h"><span className="iax-mono" style={{ color: 'var(--accent)' }}>Step 1 of 4</span><h2>Pick a theme</h2></div>
      {SYL.parts.map(p => <div className="ia2-group" key={p.k}><span>{p.n}</span><div className="ia2-tiles">{p.themes.map(th => { const ts = SYL.topics.filter(t => themeOf(t[0]) === th); const n = ts.reduce((a, t) => a + relsOfTopic(t[0]).length, 0); return <Tile key={th} tc={tcol(th)} kick={th} title={SYL.themes[th].n} sub={ts.length + ' topics · ' + n + ' relationships'} onClick={() => go({ th })} />; })}</div></div>)}
      <div className="ia2-step-h ia2-tech-h"><span className="iax-mono" style={{ color: 'var(--accent)' }}>Or start from the technology</span><h2>Tech-enabled investigations</h2><span className="iax-muted">Each of these needs a piece of technology to work: a data-logging sensor, a smartphone camera or light sensor, a home-built Arduino colorimeter, a spectrophotometer, video analysis or a spectral database with code.</span></div>
      <div className="ia2-tiles">{TECH.map(r => <Tile key={r.id} tex tc={tcol(r.t)} kick={r.t + ' · ' + r.tech.split(/ \+ | \(/)[0]} title={r.ti} sub={cap(ivOf(r).name) + ' → ' + r.dv.n} onClick={() => go({ th: themeOf(r.t), t: r.t, iv: r.ivk.split(':')[1], rel: r.id })} />)}</div>
    </>;
  }
  function PickTopic({ th, go }) {
    return <>
      <div className="ia2-step-h"><span className="iax-mono" style={{ color: tcol(th) }}>Step 2 of 4</span><h2>Pick a topic</h2></div>
      <div className="ia2-tiles">{SYL.topics.filter(t => themeOf(t[0]) === th).map(([c, n, ahl]) => { const ivs = MAP[c] || []; return <Tile key={c} tc={tcol(c)} kick={c + (ahl ? ' · AHL' : '')} title={n} sub={ivs.length + (ivs.length === 1 ? ' independent variable' : ' independent variables') + ' · ' + relsOfTopic(c).length + ' relationships'} onClick={() => go({ th, t: c })} />; })}</div>
    </>;
  }
  function PickIV({ t, go }) {
    return <>
      <div className="ia2-step-h"><span className="iax-mono" style={{ color: tcol(t) }}>Step 3 of 4</span><h2>Pick an independent variable</h2><span className="iax-muted">The quantity you would change on purpose.</span></div>
      {SYL.note[t] && <div className="iax-note">{SYL.note[t]}</div>}
      <div className="ia2-tiles">{(MAP[t] || []).map(iv => { const n = iv.ids.filter(id => REL[id]).length; return <Tile key={iv.key} tex tc={tcol(t)} kick={'Set with ' + iv.how} title={cap(iv.name) + ' ' + iv.sym} sub={n + (n === 1 ? ' possible DV' : ' possible DVs')} onClick={() => go({ th: themeOf(t), t, iv: iv.key })} />; })}</div>
    </>;
  }
  function PickDV({ t, iv, go, prog }) {
    const I = (MAP[t] || []).find(x => x.key === iv); if (!I) return null;
    return <>
      <div className="ia2-step-h"><span className="iax-mono" style={{ color: tcol(t) }}>Step 4 of 4</span><h2>Which dependent variable could you measure?</h2><Tx className="iax-muted" s={'As you change ' + I.name + ', what responds?'} /></div>
      <div className="ia2-tiles">{I.ids.filter(id => REL[id]).map(id => { const r = REL[id], p = prog[id]; return <Tile key={id} tex tc={tcol(t)} kick={(p && p.done ? 'explored ✓' : p ? 'in progress' : r.db ? 'database' : 'lab') + (r.tc ? ' · ⚙ tech' : '')} title={cap(r.dv.n) + ' ' + r.dv.s} sub={'measured as: ' + r.dv.m} onClick={() => go({ th: themeOf(t), t, iv, rel: id })} />; })}</div>
    </>;
  }

  /* ---------- activity ---------- */
  function Card({ n, title, state, children }) {
    return <section className={'ia2-card ' + state}><div className="ia2-card-h"><span className="ia2-num">{state === 'done' ? '✓' : n}</span><h3>{title}</h3></div>{state !== 'locked' && children}</section>;
  }
  function MCQ({ opts, value, correct, onPick, col }) {
    const done = value != null;
    return <div className={'ia2-opts' + (col ? ' col' : '')}>{opts.map(o => { const cls = !done ? '' : o === correct ? ' right' : o === value ? ' wrong' : ''; return <button key={o} className={'ia2-opt' + cls} disabled={done} onClick={() => onPick(o)}><Tx s={o} /></button>; })}</div>;
  }

  function Activity({ r, p, setP, go, inList, toggleList }) {
    const A = useMemo(() => analyse(r), [r.id]);
    const sim = useMemo(() => simulate(r.g, r.set, r.id), [r.id]);
    const I = ivOf(r), ivName = I ? I.name : 'the independent variable';
    const first = p.first || {};
    const upd = o => setP(Object.assign({}, p, o));
    const firstSet = (k, ok) => first[k] == null ? { first: Object.assign({}, first, { [k]: ok }) } : {};
    const st = n => (p.step || 1) > n ? 'done' : (p.step || 1) === n ? 'open' : 'locked';
    const next = n => { if ((p.step || 1) <= n) return { step: n + 1 }; return {}; };
    const trendOpts = ['increases', 'decreases', /decreases, then/.test(A.trend) ? 'decreases, then increases' : 'increases, then decreases', 'stays the same'];
    /* step 3 state */
    const xo = A.XO.find(o => o.l === p.xs), yo = A.YO.find(o => o.l === p.ys);
    const sc = xo && yo ? pairScore(A, xo, yo) : null;
    const verdict = sc == null ? null : sc <= A.thr ? 'straight' : sc <= Math.max(2e-3, 30 * A.thr) ? 'nearly' : 'curved';
    const livePts = xo && yo ? sim.raw.map(q => ({ x: xo.f(q.x), y: yo.f(q.x, q.y), e: Math.abs(yo.f(q.x, q.y + q.e) - yo.f(q.x, q.y - q.e)) / 2 || 0 })).filter(q => isFinite(q.x) && isFinite(q.y)) : [];
    const liveFit = livePts.length > 2 ? lsq(livePts.map(q => q.x), livePts.map(q => q.y)) : null;
    const check3 = () => { const ok = verdict === 'straight'; upd(Object.assign({ tries: (p.tries || 0) + 1, ok3: ok, fb3: verdict }, firstSet('line', ok), ok ? next(3) : {})); };
    const showMe = () => upd({ xs: A.mX.l, ys: A.mY.l, ok3: true, fb3: 'shown', ...next(3), ...firstSet('line', false) });
    /* step 4 */
    const through = /^0/.test(r.lin.c);
    const goodReason = through ? sym(r.lin.y) + ' is directly proportional to ' + sym(r.lin.x) + ': the model has no constant term.' : 'The model has a constant term, so the intercept is ' + r.lin.c.replace(/^0\s*/, '') + '.';
    const reasons = useMemo(() => shuffle([goodReason, ...shuffle(REASONS_BAD, r.id + 'r').slice(0, 2)], r.id + 'rr'), [r.id]);
    const qs = useMemo(() => quantities(r), [r.id]);
    const score = ['trend', 'shape', 'line', 'orig', 'why', 'grad'].filter(k => first[k]).length;
    const done = (p.step || 1) > 5;
    useEffect(() => { if (done && !p.done) upd({ done: 1 }); }, [done]);
    const sibs = I ? I.ids.filter(id => id !== r.id && REL[id]) : [];
    const kit = ideaKit({ kit: r.kit || [], chem: r.chem || [] });
    const lab = !r.db;

    return <div className="ia2-act" style={{ '--tc': tcol(r.t) }}>
      <div className="ia2-step-h"><span className="iax-mono" style={{ color: tcol(r.t) }}>{r.t} · {r.db ? 'database' : 'lab'}{r.bx ? ' · beyond IB' : ''}{r.tc ? ' · tech-enabled' : ''}</span>
        <h2><Tx s={cap(ivName) + ' → ' + r.dv.n} /></h2>
        {r.sys && <Tx className="iax-muted" s={'System: ' + r.sys} />}{r.tc ? <div className="iax-tech"><b>⚙ Technology: </b><Tx s={r.tech} /></div> : null}</div>

      <Card n="1" title="Predict before you look" state={st(1)}>
        <Tx className="ia2-q" s={'As ' + ivName + ' increases, ' + r.dv.n + '…'} />
        <MCQ opts={trendOpts} value={p.trend} correct={A.trend} onPick={o => upd({ trend: o, ...firstSet('trend', o === A.trend) })} />
        {p.trend != null && <>
          <Tx className="ia2-q" s={'Will a graph of ' + r.dv.n + ' against ' + ivName + ' be a straight line or a curve?'} />
          <MCQ opts={['a straight line', 'a curve']} value={p.shape} correct={A.straight ? 'a straight line' : 'a curve'} onPick={o => upd({ shape: o, ...firstSet('shape', (o === 'a straight line') === A.straight), ...next(1) })} />
        </>}
        {p.shape != null && <div className="ia2-live"><div className="iax-plot"><span className="cap">Raw simulated data</span><Plot pts={sim.raw} curve={sim.curve} xl={r.g.rx} yl={r.g.ry} /></div>
          <div className="ia2-fb">{A.straight ? 'The raw data are already straight. Not every relationship needs transforming; the value you want still comes from the gradient.' : 'A curve is hard to read a value from. In step 3 you will straighten it.'}</div></div>}
      </Card>

      <Card n="2" title="The chemistry behind it" state={st(2)}>
        <Tx className="ia2-q" s={'Write down any equation you know that contains both ' + ivName + ' and ' + r.dv.n + '.'} />
        <textarea className="iax-ta" value={p.idea || ''} onChange={e => upd({ idea: e.target.value })} placeholder="Your idea (optional)" />
        {!p.rev2 ? <div><button className="iax-btn pri" onClick={() => upd({ rev2: 1, ...next(2) })}>Reveal the chemistry</button></div> :
          <div className="iax-three">
            <div className="iax-box"><span className="k">Model</span><Tx className="v" s={r.th} />{p.idea && <span className="iax-fine">Your idea: {p.idea}</span>}</div>
            <div className="iax-box"><span className="k">Getting the IV</span><Tx className="v" s={r.ivc} />{r.ivm && <Tx className="iax-fine" s={'Measured with: ' + r.ivm} />}</div>
            <div className="iax-box"><span className="k">Getting the DV</span><Tx className="v" s={r.dv.m + '; ' + r.dv.p} /></div>
          </div>}
      </Card>

      <Card n="3" title="Make it a straight line" state={st(3)}>
        <span className="ia2-q">Compare the model with y = mx + c. Pick what to plot on each axis.</span>
        <div className="ia2-axis"><span>y-axis</span><div className="ia2-opts">{A.YO.map(o => <button key={o.l} className={'ia2-chip' + (p.ys === o.l ? ' on' : '')} disabled={p.ok3} onClick={() => upd({ ys: o.l, fb3: null })}><Tx s={o.l} /></button>)}</div></div>
        <div className="ia2-axis"><span>x-axis</span><div className="ia2-opts">{A.XO.map(o => <button key={o.l} className={'ia2-chip' + (p.xs === o.l ? ' on' : '')} disabled={p.ok3} onClick={() => upd({ xs: o.l, fb3: null })}><Tx s={o.l} /></button>)}</div></div>
        {xo && yo && <div className="ia2-live">
          <div className="iax-plot"><span className="cap"><Tx s={yo.l + ' against ' + xo.l} /></span><Plot pts={livePts} fit={liveFit} xl={xo.l} yl={yo.l} origin={!!p.ok3} /></div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '.6rem' }}>
            <span className="ia2-cap" style={{ color: verdict === 'straight' ? 'var(--green)' : verdict === 'nearly' ? 'var(--gold)' : 'var(--red)' }}>{verdict === 'straight' ? 'Straight' : verdict === 'nearly' ? 'Nearly straight' : 'Curved'}</span>
            {!p.ok3 && <div style={{ display: 'flex', gap: '.4rem', flexWrap: 'wrap' }}><button className="iax-btn pri" onClick={check3}>Check</button>{(p.tries || 0) >= 3 && <button className="iax-btn" onClick={showMe}>Show me</button>}</div>}
            {p.fb3 && <div className={'ia2-fb ' + (p.ok3 ? 'ok' : p.fb3 === 'nearly' ? 'mid' : 'bad')}>{p.fb3 === 'shown' ? 'Here is the pair the model predicts. Compare it with y = mx + c to see why.' : p.ok3 ? 'Correct: this pair gives a straight line, so the gradient and intercept mean something.' : p.fb3 === 'nearly' ? 'Close, but still slightly curved. Look at the ends.' : 'Still curved. Which function of the IV does the DV depend on?'}</div>}
            {p.ok3 && <Tx className="iax-fine" s={'Model: ' + modelEq(r.th)} />}
            {!p.ok3 && (p.tries || 0) > 0 && <span className="iax-fine">Attempts: {p.tries}</span>}
          </div></div>}
      </Card>

      <Card n="4" title="Should the line pass through the origin?" state={st(4)}>
        <MCQ opts={['Yes', 'No']} value={p.orig} correct={through ? 'Yes' : 'No'} onPick={o => upd({ orig: o, ...firstSet('orig', (o === 'Yes') === through) })} />
        {p.orig != null && <><span className="ia2-q">Why?</span><MCQ col opts={reasons} value={p.why} correct={goodReason} onPick={o => upd({ why: o, ...firstSet('why', o === goodReason), ...next(4) })} /></>}
        {p.why != null && <div className="ia2-info"><Tx s={'Intercept: ' + r.lin.c + '. '} /> Never force a line through the origin; let the data decide, then compare the intercept with what the model predicts. For Arrhenius and van ’t Hoff plots the intercept carries ln A or ΔS/R, so it is a result too.</div>}
      </Card>

      <Card n="5" title="What can the gradient tell you?" state={st(5)}>
        <Tx className="ia2-q" s={'The gradient of ' + sym(r.lin.y) + ' against ' + sym(r.lin.x) + ' is ' + r.lin.m + '. Which quantity does it give you?'} />
        <MCQ col opts={qs} value={p.grad} correct={r.q.n} onPick={o => upd({ grad: o, ...firstSet('grad', o === r.q.n), ...next(5) })} />
        {p.grad != null && <div className="iax-three">
          <div className="iax-box"><span className="k">Gradient =</span><Tx className="v" s={r.lin.m} /></div>
          <div className="iax-box"><span className="k">So you find</span><Tx className="v" s={cap(r.q.n) + ': ' + r.q.how} /></div>
          <div className="iax-box gold"><span className="k">Compare with</span><Tx className="v" s={r.q.lit} /></div>
        </div>}
      </Card>

      {done && <section className="ia2-skel">
        <div className="ia2-skel-h"><span style={{ color: 'var(--gold)' }}>★</span><h3>Your idea skeleton</h3><span className="iax-mono" style={{ color: 'var(--text-2)', marginLeft: 'auto' }}>{score}/6 first time</span></div>
        <div className="iax-kv">
          <span>IV</span><Tx s={cap(ivName) + ', plotted as ' + sym(r.lin.x)} />
          <span>DV</span><Tx s={cap(r.dv.n) + ', plotted as ' + sym(r.lin.y)} />
          <span>Graph</span><Tx s={r.lin.y + ' against ' + r.lin.x} />
          <span>Gradient</span><Tx s={r.lin.m + ' → ' + r.q.n} />
          <span>Compare with</span><Tx s={r.q.lit} />
          <span>Uncertainty</span><Tx s={r.dv.e} />
          <span>Safety</span>{lab ? <span>{r.sf && r.sf.length ? <Tx s={r.sf.join('; ')} /> : 'Standard lab rules; check with your teacher.'}{r.hz === 3 ? <b style={{ color: 'var(--red)' }}> Agree this with your teacher before you start.</b> : null}</span> : <span>Database investigation: no lab hazards.</span>}
        </div>
        <span className="ia2-q" style={{ fontWeight: 600 }}>Now write your own research question</span>
        <ul className="ia2-ticklist"><li>the IV with its symbol, unit, range and number of values</li><li>the DV and how it is measured</li><li>the specific system</li><li>the variables kept constant</li><li>the equation linking IV and DV</li><li>the quantity from the gradient and what you compare it with</li></ul>
        <textarea className="iax-ta" value={p.rq || ''} onChange={e => upd({ rq: e.target.value })} placeholder={'How does varying ' + IA_PLAIN(ivName) + ' … affect ' + IA_PLAIN(r.dv.n) + ' …?'} />
        {HAS_INV && lab && kit.length > 0 && <div className="ia2-kit">{kit.map((k, i) => <span key={i} className={k.kind}>{cap(IA_PLAIN(k.label))}{k.kind === 'no' ? ' (source)' : ''}</span>)}</div>}
        <div style={{ display: 'flex', gap: '.45rem', flexWrap: 'wrap' }}>
          <button className="iax-btn pri" onClick={() => toggleList(r.id)}>{inList ? 'On your list ✓' : 'Add to my list'}</button>
          {sibs.map(id => <button key={id} className="iax-btn" onClick={() => go({ th: themeOf(r.t), t: r.t, iv: I.key, rel: id })}><Tx s={'Try ' + I.sym + ' → ' + REL[id].dv.s} /></button>)}
          <button className="iax-btn" onClick={() => go({ th: themeOf(r.t), t: r.t })}>Choose another variable</button>
        </div>
      </section>}
    </div>;
  }

  function Basket({ list, prog, go, remove }) {
    return <aside className="ia2-basket" aria-label="My list">
      <h3>My list <span className="iax-mono" style={{ color: 'var(--text-muted)' }}>{list.length}</span>{list.length > 0 && <button className="iax-btn no-print" style={{ marginLeft: 'auto' }} onClick={() => window.print()}>Print</button>}</h3>
      {!list.length && <span className="iax-muted">Finish an activity and add it here. Your list stays on this device.</span>}
      {list.map(id => { const r = REL[id]; if (!r) return null; const p = prog[id] || {}, I = ivOf(r); return <div className="ia2-item" key={id}>
        <div className="ia2-item-top"><b style={{ color: tcol(r.t) }}>{r.t}</b><Tx s={sym(r.lin.y) + ' vs ' + sym(r.lin.x)} /><button className="x no-print" onClick={() => remove(id)} aria-label="Remove">×</button></div>
        <button className="go" onClick={() => go({ th: themeOf(r.t), t: r.t, iv: I && I.key, rel: id })}><Tx s={cap(I ? I.name : '') + ' → ' + r.dv.n} /></button>
        <span className="iax-muted" style={{ fontSize: '.78rem' }}>{p.done ? <Tx s={'→ ' + r.q.n} /> : 'in progress'}</span>
        {p.rq && <q>{p.rq}</q>}
      </div>; })}
    </aside>;
  }

  function App() {
    const norm = h => { const o = {}; if (h.th && SYL.themes[h.th]) o.th = h.th; if (h.t && TOPIC[h.t]) { o.t = h.t; o.th = themeOf(h.t); } if (o.t && h.iv && (MAP[o.t] || []).some(i => i.key === h.iv)) o.iv = h.iv; if (o.iv && h.rel && REL[h.rel]) o.rel = h.rel; return o; };
    const [route, setRoute] = useState(() => norm(parseHash()));
    const [prog, setProg] = useState(() => loadLS(KP, {}));
    const [list, setList] = useState(() => loadLS(KL, []).filter(id => REL[id]));
    useEffect(() => saveLS(KP, prog), [prog]);
    useEffect(() => saveLS(KL, list), [list]);
    useEffect(() => { const f = () => setRoute(norm(parseHash())); window.addEventListener('hashchange', f); return () => window.removeEventListener('hashchange', f); }, []);
    const go = r => { const n = norm(r); setRoute(n); const q = ['th', 't', 'iv', 'rel'].filter(k => n[k]).map(k => k + '=' + encodeURIComponent(n[k])).join('&'); history.replaceState(null, '', q ? '#' + q : location.pathname); const top = document.getElementById('iax-root').offsetTop - 70; if (window.scrollY > top) window.scrollTo({ top, behavior: 'smooth' }); };
    const toggleList = id => setList(l => l.includes(id) ? l : [...l, id]);
    const I = route.t && route.iv ? (MAP[route.t] || []).find(i => i.key === route.iv) : null;
    const r = route.rel ? REL[route.rel] : null;
    const crumbs = [['All themes', {}, 1]];
    if (route.th) crumbs.push([route.th + ' · ' + SYL.themes[route.th].n, { th: route.th }, 1]);
    if (route.t) crumbs.push([route.t + ' ' + TOPIC[route.t].n, { th: route.th, t: route.t }, 1]);
    if (I) crumbs.push([I.name, { th: route.th, t: route.t, iv: route.iv }]);
    if (r) crumbs.push([r.dv.n, route]);
    return <div className="ia2">
      <div className="ia2-main">
        <nav className="ia2-crumbs no-print" aria-label="Breadcrumbs">{crumbs.map(([l, to, plain], i) => <React.Fragment key={i}>{i > 0 && <span>›</span>}{i === crumbs.length - 1 ? (plain ? <span className="cur">{l}</span> : <Tx className="cur" s={l} />) : <button onClick={() => go(to)}>{plain ? l : <Tx s={l} />}</button>}</React.Fragment>)}</nav>
        {r ? <Activity key={r.id} r={r} p={prog[r.id] || {}} setP={v => setProg(s => ({ ...s, [r.id]: v }))} go={go} inList={list.includes(r.id)} toggleList={toggleList} />
          : route.iv ? <PickDV t={route.t} iv={route.iv} go={go} prog={prog} />
          : route.t ? <PickIV t={route.t} go={go} />
          : route.th ? <PickTopic th={route.th} go={go} />
          : <PickTheme go={go} />}
      </div>
      <Basket list={list} prog={prog} go={go} remove={id => setList(l => l.filter(x => x !== id))} />
    </div>;
  }
  ReactDOM.createRoot(document.getElementById('iax-root')).render(<App />);
})();
