// witness.jsx — "The Witness": a teaching mode. Watch a master — Dipa Ma or Goenka —
// in a sitting OR in daily life (walking, cooking, working). Thoughts and sensations
// arise; SEE how the master meets each one — notes it, and returns to the anchor.
// Each phenomenon arises, lingers, and passes (anicca).

// ── Life trials — how a master meets day-to-day trouble ───────────
// The inner steps are illustrative reconstructions in each teacher's
// documented method; the closing words are their own (or, marked as
// "her lesson", from Dipa Ma's recorded ten lessons). Two trials are
// true biographical accounts and say so.
// ── Heavy thoughts — the real troubles that surface on the cushion.
// Each carries, per teacher, a one-line meeting (how they meet it and
// return) and the teacher's own words, drawn from the verified quote
// pools. Two are anchored in true biography and say so.
const MT_HEAVY = [
  { word: 'the shout', detail: 'a raised voice from this morning, still stinging', hue: '#FC6255',
    d: 'She notes it — “anger… hurt” — wishes the one who shouted well, and lets it pass.', dq: 'Cool anger and emotional reactivity.',
    g: 'He turns into the heat in the chest — neither craving nor averting — and it thins.', gq: 'Every time you react, you tie a new knot. Every time you observe, one dissolves.' },
  { word: 'the diagnosis', detail: 'the doctor’s words, dividing before from after', hue: '#7CC4FF',
    d: 'She lets the fear be felt, fires no second arrow about the years to come, holds her body tenderly.', dq: 'Suffering is wanting things to be other than they are.',
    g: 'Fear is only sensation — cold, hollow. He observes it, as he once observed his own incurable pain.', gq: 'Be equanimous, and you will come out of all your misery.', story: 'True: Goenka came to this path through incurable migraines.' },
  { word: 'the money', detail: 'a sum that will not add up, at 3 a.m.', hue: '#FFC78A',
    d: 'Widowed, raising a child on little, she knew this clutch — she softens it, then takes one step.', dq: 'Any situation can be used for practice.',
    g: 'The mind spins debt and shame; he sees projections, returns to the breath, then to the arithmetic.', gq: 'Observe reality as it is, not as you would like it to be.' },
  { word: 'the sick child', detail: 'a small fever, the long night ahead', hue: '#F2A6C2',
    d: 'The fever-watch IS the practice hall — each act done completely, worry softened back into care.', dq: 'When you do anything, do it completely — as if it is the only thing you have to do.',
    g: 'He does what must be done with full attention; the panic between is observed, not obeyed.', gq: 'Work patiently and persistently — you are bound to be successful.' },
  { word: 'the betrayal', detail: 'a friend’s words, reaching me secondhand', hue: '#C79BFF',
    d: 'She lets it ache, unfed, declines the mind’s courtroom, and in time wishes the friend well.', dq: 'The whole path is a path of letting go.',
    g: 'Each replay ties a fresh knot; he stops replaying and observes the burn until it cools.', gq: 'Remain equanimous, knowing everything changes.' },
  { word: 'the collapse', detail: 'the plan, dissolved by one phone call', hue: '#FFC78A',
    d: '“Ruined” is a story; she does not believe everything the mind tells her — least of all this loud.', dq: 'Free your mind. Don’t fill it up.',
    g: 'The plan was anicca too — it arose, it passed; he adjusts without paying the tax of fury.', gq: 'Do not react. Only observe, with a balanced mind.' },
  { word: 'a wave of fear', detail: 'the body bracing, as if the floor gave way', hue: '#B49CFF',
    d: 'Once, in violent turbulence, she sat still — sending lovingkindness to everyone aboard. So here.', dq: 'Whatever you are doing, be aware of it.', story: 'True: passengers recall Dipa Ma calm through a terrifying flight.',
    g: 'The lurch, the racing heart — vibration, arising and passing. He scans it and stays balanced.', gq: 'Anicca — everything that arises passes away.' },
  { word: 'the grief', detail: 'an emptiness where a person was', hue: '#7CC4FF',
    d: 'Her path began in such loss; she did not transcend grief but witnessed each wave, till love remained.', dq: 'If you can be mindful for one moment, you can be mindful for two.', story: 'True: losing her children and husband is where Dipa Ma’s path began.',
    g: 'Grief is love’s accounting; he keeps it company in the throat and chest, and wishes the departed well.', gq: 'May all beings be happy. May all beings be peaceful. May all beings be liberated.' },
];


function mindHue(sub) { return sub === 'sound' ? '#7CC4FF' : sub === 'feeling' ? '#F2A6C2' : '#B49CFF'; }

function WitnessScreen({ vp, audio, onExit }) {
  useWakeLock(true); // witness sessions keep the screen awake too
  const C = MT_COLORS;
  const ctxPose = React.useContext(MTPoseContext);

  const [teacherId, setTeacherId] = React.useState('dipama');
  const [sceneId, setSceneId] = React.useState('sitting');
  const [mode, setMode] = React.useState('watch'); // 'watch' (the master) | 'practice' (you note)
  const [stage, setStage] = React.useState('intro');
  const heavyRef = React.useRef(0);          // cycles through MT_HEAVY in order
  const holdUntilRef = React.useRef(0);      // master dwells on a heavy thought before noting the next

  const teacher = MT_SIM_TEACHERS[teacherId];
  const scene = MT_SCENES[sceneId];
  const [vbW, vbH] = scene.aspect;

  const phenRef = React.useRef([]);
  const [, force] = React.useReducer(x => x + 1, 0);
  const idRef = React.useRef(1);
  const statsRef = React.useRef({ arisen: 0, noted: 0 });
  const arisenLogRef = React.useRef([]);
  const [share, setShare] = React.useState(null);
  const [narr, setNarr] = React.useState(0);
  const responseRef = React.useRef(null); // { text, word, hue, until } — read in render
  const [anchorPulse, setAnchorPulse] = React.useState(0);
  const lastSpawnRef = React.useRef(0);
  const lastTsRef = React.useRef(0);
  const modeRef = React.useRef(mode);
  React.useEffect(() => { modeRef.current = mode; }, [mode]);
  // keep scene valid for the chosen teacher
  React.useEffect(() => {
    if (!teacher.scenes.includes(sceneId)) setSceneId(teacher.scenes[0]);
  }, [teacherId]); // eslint-disable-line

  const landscape = vp.landscape;

  function spawn() {
    const list = phenRef.current;
    if (list.length >= 4) return;
    const r = Math.random();
    const hx = scene.headPos[0], hy = scene.headPos[1];
    let p;
    if (r < 0.4) {
      const b = scene.body[Math.floor(Math.random() * scene.body.length)];
      const sens = MT_SENS_MAP[b.sid] || { hue: C.teal };
      p = { id: idRef.current++, kind: 'body', word: b.word, detail: b.detail, hue: sens.hue,
        lx: b.pos[0] * 100, ly: b.pos[1] * 100, age: 0, life: 6000 + Math.random() * 1500, noted: false };
    } else if (r < 0.66) {
      const m = scene.mind[Math.floor(Math.random() * scene.mind.length)];
      p = { id: idRef.current++, kind: 'mind', word: m.word, detail: m.detail, hue: '#B49CFF',
        lx: hx * 100 + (Math.random() * 38 - 19), ly: Math.max(4, hy * 100 - 12 + (Math.random() * 8 - 4)),
        age: 0, life: 5200 + Math.random() * 1500, noted: false };
    } else {
      // a heavy thought surfaces — a real trouble from life, cycled in order
      const h = MT_HEAVY[heavyRef.current % MT_HEAVY.length]; heavyRef.current++;
      p = { id: idRef.current++, kind: 'mind', heavy: h, word: h.word, detail: h.detail, hue: h.hue,
        lx: hx * 100 + (Math.random() * 30 - 15), ly: Math.max(4, hy * 100 - 12 + (Math.random() * 6 - 3)),
        age: 0, life: 7600 + Math.random() * 1500, noted: false };
    }
    statsRef.current.arisen++;
    list.push(p);
    if (arisenLogRef.current.length < 26) arisenLogRef.current.push({ kind: p.kind, hue: p.hue, lx: p.lx, ly: p.ly, word: p.word });
  }

  function note(p, byMaster) {
    if (p.noted) return;
    p.noted = true;
    p.life = p.age + 820;
    statsRef.current.noted++;
    audio.pluck(p.kind === 'mind' ? 880 : 660, 0.07);
    setAnchorPulse(x => x + 1);
    if (byMaster || p.heavy) {
      if (p.heavy) {
        const dm = teacherId === 'dipama';
        const until = performance.now() + 7000;
        responseRef.current = { word: p.word, hue: p.hue, text: dm ? p.heavy.d : p.heavy.g,
          quote: dm ? p.heavy.dq : p.heavy.gq, story: p.heavy.story, until };
        holdUntilRef.current = until;   // the master dwells before meeting the next arising
      } else {
        const r = teacher.responses[Math.floor(Math.random() * teacher.responses.length)];
        responseRef.current = { text: `${teacher.short} ${r} ${scene.anchorLabel}`, word: p.word, hue: p.hue, until: performance.now() + 3400 };
      }
    }
    force();
  }

  // lifecycle
  React.useEffect(() => {
    if (stage !== 'live') return;
    phenRef.current = []; statsRef.current = { arisen: 0, noted: 0 }; arisenLogRef.current = [];
    lastTsRef.current = performance.now(); lastSpawnRef.current = performance.now();
    spawn(); if (phenRef.current[0]) phenRef.current[0].age = 2400;
    force();
    const id = setInterval(() => {
      const now = performance.now();
      const dt = Math.min(400, now - lastTsRef.current); lastTsRef.current = now;
      const list = phenRef.current;
      for (const p of list) {
        p.age += dt;
        // in watch mode, the MASTER notes each arising around mid-life
        if (modeRef.current === 'watch' && !p.noted && p.age > p.life * 0.4 && now > holdUntilRef.current) note(p, true);
      }
      phenRef.current = list.filter(p => p.age < p.life);
      const gap = 2300 + Math.random() * 1500;
      if (now - lastSpawnRef.current > gap) { spawn(); lastSpawnRef.current = now; }
      force();
    }, 40);
    return () => clearInterval(id);
    // eslint-disable-next-line
  }, [stage, sceneId, teacherId]);

  // audio + rotating guidance
  React.useEffect(() => {
    if (stage !== 'live') return;
    audio.bell(528); audio.fade(0.15, 3);
    const id = setInterval(() => setNarr(n => n + 1), 8200);
    return () => { clearInterval(id); audio.fade(0, 1.4); };
    // eslint-disable-next-line
  }, [stage]);

  // clear stale response caption is handled in-render via responseRef.until

  const phenoms = phenRef.current;
  const st = statsRef.current;
  const guideLine = teacher.guidance[narr % teacher.guidance.length];
  const response = (responseRef.current && responseRef.current.until > performance.now()) ? responseRef.current : null;

  // ── intro: choose teacher, scene, mode ──
  if (stage === 'intro') {
    return (
      <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 80% at 50% 0%, #161033 0%, ${C.bg} 60%)`, color: C.ink, overflow: 'auto' }}>
        <ParticleField count={42} />
        <WitnessTopExit onExit={onExit} label="Close" />
        <div style={{ maxWidth: 560, margin: '0 auto', padding: '74px 24px 40px', position: 'relative' }}>
          <div style={{ textAlign: 'center', marginBottom: 22 }}>
            <MTEyebrow color="#B49CFF">Learn meditation</MTEyebrow>
            <h1 style={{ font: '300 30px/1.18 var(--serif)', margin: '12px 0 10px' }}>Watch a master</h1>
            <p style={{ fontSize: 15.5, lineHeight: 1.6, color: C.inkDim, margin: '0 auto', maxWidth: 430, textWrap: 'pretty' }}>
              See a sitting from inside. Ordinary thoughts and the hard ones — a shout, a worry, a grief — arise. Watch how the master meets each, and returns.
            </p>
          </div>

          {/* teacher */}
          <MTEyebrow>The master</MTEyebrow>
          <div style={{ display: 'flex', gap: 10, margin: '12px 0 22px' }}>
            {MT_SIM_ORDER.map(id => {
              const t = MT_SIM_TEACHERS[id]; const on = teacherId === id;
              return (
                <button key={id} onClick={() => setTeacherId(id)} style={{
                  flex: 1, appearance: 'none', cursor: 'pointer', font: 'inherit', textAlign: 'left', padding: '14px 15px', borderRadius: 18,
                  border: `1px solid ${on ? t.accent : C.inkGhost}`, background: on ? hexA(t.accent, 0.1) : 'rgba(255,255,255,0.02)', color: C.ink,
                }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
                    <span style={{ width: 34, height: 34, borderRadius: '50%', display: 'grid', placeItems: 'center', flexShrink: 0,
                      background: `radial-gradient(60% 60% at 50% 38%, ${hexA(t.accent, 0.34)}, ${hexA(t.accent, 0.05)})`, border: `1px solid ${hexA(t.accent, 0.5)}`,
                      fontFamily: 'var(--serif)', fontSize: 13, color: C.ink }}>{t.initials}</span>
                    <span style={{ fontSize: 15.5, fontWeight: 600 }}>{t.name}</span>
                  </div>
                  <div style={{ fontSize: 12.5, color: C.inkFaint, lineHeight: 1.45 }}>{t.blurb}</div>
                </button>
              );
            })}
          </div>

          {/* scene */}
          <MTEyebrow>The scene</MTEyebrow>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 9, margin: '12px 0 8px' }}>
            {['sitting', 'walking', 'cooking', 'working'].map(id => {
              const s = MT_SCENES[id]; const avail = teacher.scenes.includes(id); const on = sceneId === id;
              return (
                <button key={id} disabled={!avail} onClick={() => setSceneId(id)} style={{
                  appearance: 'none', cursor: avail ? 'pointer' : 'not-allowed', font: 'inherit', padding: '13px 14px', borderRadius: 14,
                  border: `1px solid ${on ? teacher.accent : C.inkGhost}`, background: on ? hexA(teacher.accent, 0.1) : 'rgba(255,255,255,0.02)',
                  color: avail ? C.ink : C.inkFaint, opacity: avail ? 1 : 0.4, display: 'flex', alignItems: 'center', gap: 11,
                }}>
                  <span style={{ width: 34, height: 34, borderRadius: 11, display: 'grid', placeItems: 'center', flexShrink: 0,
                    background: on ? hexA(teacher.accent, 0.16) : 'rgba(255,255,255,0.03)', color: on ? teacher.accent : C.inkDim }}>
                    <MTIcon name={s.icon} size={19} />
                  </span>
                  <div style={{ textAlign: 'left' }}>
                    <div style={{ fontSize: 14.5, fontWeight: 600 }}>{s.label}</div>
                    <div style={{ fontSize: 11, color: C.inkFaint, marginTop: 1 }}>{avail ? s.anchorLabel : 'in sitting only'}</div>
                  </div>
                </button>
              );
            })}
          </div>
          <div style={{ fontSize: 11.5, color: C.inkFaint, marginBottom: 22, lineHeight: 1.5 }}>
            {teacherId === 'dipama' ? 'Dipa Ma taught awakening inside ordinary life — at the stove, with the children, at work.' : 'Goenka’s Vipassana centers on the seated body-sweep and mindful walking.'}
          </div>

          {/* mode */}
          <MTEyebrow>How to watch</MTEyebrow>
          <div style={{ display: 'flex', gap: 10, margin: '12px 0 24px' }}>
            {[['watch', 'Watch the master', 'See how ' + teacher.short + ' meets each arising'], ['practice', 'I note them', 'You tap each one yourself']].map(([id, t, d]) => (
              <button key={id} onClick={() => setMode(id)} style={{
                flex: 1, appearance: 'none', cursor: 'pointer', font: 'inherit', textAlign: 'left', padding: '13px 15px', borderRadius: 16,
                border: `1px solid ${mode === id ? teacher.accent : C.inkGhost}`, background: mode === id ? hexA(teacher.accent, 0.09) : 'rgba(255,255,255,0.02)', color: C.ink,
              }}>
                <div style={{ fontSize: 14.5, fontWeight: 600 }}>{t}</div>
                <div style={{ fontSize: 11.5, color: C.inkFaint, marginTop: 3, lineHeight: 1.4 }}>{d}</div>
              </button>
            ))}
          </div>

          <MTButton size="lg" full onClick={() => setStage('live')} style={{ background: `linear-gradient(180deg, ${teacher.accent}, #1FBFAC)` }}>
            <MTIcon name="play" size={20} color="#022019" /> Watch {teacher.short} {scene.label.toLowerCase()}
          </MTButton>

        </div>
      </div>
    );
  }

  // ── summary ──
  if (stage === 'summary') {
    return (
      <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 80% at 50% 0%, #161033 0%, ${C.bg} 60%)`, color: C.ink, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 26, overflow: 'auto' }}>
        <ParticleField count={40} />
        <div style={{ maxWidth: 430, textAlign: 'center', position: 'relative', padding: '40px 0' }}>
          <MTEyebrow color={teacher.accent}>{scene.label} · {teacher.short}</MTEyebrow>
          <h1 style={{ font: '300 30px/1.2 var(--serif)', margin: '12px 0 22px' }}>What you witnessed</h1>
          <div style={{ display: 'flex', justifyContent: 'center', gap: 14, marginBottom: 24 }}>
            {[[st.arisen, 'arose'], [st.noted, mode === 'watch' ? 'the master met' : 'you noted']].map(([v, l]) => (
              <MTPanel key={l} style={{ padding: '18px 22px', minWidth: 120 }}>
                <div style={{ fontFamily: 'var(--mono)', fontSize: 30, color: teacher.accent }}>{v}</div>
                <div style={{ fontSize: 12.5, color: C.inkFaint, marginTop: 4 }}>{l}</div>
              </MTPanel>
            ))}
          </div>
          <p style={{ font: 'italic 300 18px/1.5 var(--serif)', color: C.inkDim, marginBottom: 24, textWrap: 'pretty' }}>
            {mode === 'watch'
              ? `Not one of them was pushed away or chased. ${teacher.short} simply met each, and returned to ${scene.anchorLabel}. This is the whole practice.`
              : `Each one you met arose and passed. Nothing stayed — you simply watched.`}
          </p>
          <div style={{ marginBottom: 26 }}>
            <QuoteBlock quote={{ t: teacher.guidance[0], a: teacher.name, r: 'teaching mode' }} align="center" size={16} />
          </div>
          <MTButton size="lg" full onClick={() => setShare({
            pose: ctxPose, phenoms: arisenLogRef.current, arose: st.arisen, noted: st.noted,
            quote: { t: teacher.guidance[0], a: teacher.name },
            dateLabel: `${teacher.short} · ${scene.label.toLowerCase()}`,
          })} style={{ background: `linear-gradient(180deg, ${teacher.accent}, #1FBFAC)` }}>
            <MTIcon name="spark" size={19} color="#022019" /> Create shareable image
          </MTButton>
          <div style={{ display: 'flex', gap: 14, justifyContent: 'center', marginTop: 16 }}>
            <button onClick={() => setStage('intro')} style={ghostLink(C)}>Watch another</button>
            <button onClick={onExit} style={ghostLink(C)}>Return</button>
          </div>
        </div>
        {share && <ShareCard data={share} onClose={() => setShare(null)} />}
      </div>
    );
  }

  // ── live ──
  const figureInner = scene.figure === 'sitting'
    ? <BodyFigure pose="seated" interactive={false} breathing dim markers={[]} />
    : <ActivityFigure scene={scene.figure} breathing anchor={scene.anchor} />;

  const figureBox = (
    <div style={{ position: 'relative', height: landscape ? 'min(80vh,640px)' : 'min(54vh,480px)', aspectRatio: `${vbW}/${vbH}` }}>
      {figureInner}
      {/* mind-field halo at the head */}
      <div style={{ position: 'absolute', left: `${scene.headPos[0] * 100}%`, top: `${scene.headPos[1] * 100 - 10}%`, width: '130%', height: '30%', transform: 'translate(-50%,-50%)',
        background: 'radial-gradient(60% 60% at 50% 60%, rgba(180,156,255,0.16), rgba(180,156,255,0))', pointerEvents: 'none' }} />
      {/* attention-anchor re-pulse ring (only when the master returns) */}
      {scene.figure !== 'sitting' && (
        <span key={anchorPulse} style={{ position: 'absolute', left: `${scene.anchor[0] * 100}%`, top: `${scene.anchor[1] * 100}%`,
          width: 30, height: 30, marginLeft: -15, marginTop: -15, borderRadius: '50%', border: `1.5px solid ${hexA(teacher.accent, 0.7)}`,
          animation: 'mtRipple .9s ease-out forwards', pointerEvents: 'none' }} />
      )}
      {/* phenomena */}
      <div style={{ position: 'absolute', inset: 0, overflow: 'visible', pointerEvents: 'none' }}>
        {phenoms.map(p => <Phenom key={p.id} p={p} landscape={landscape} onNote={() => note(p, false)} interactive={mode === 'practice'} />)}
      </div>
      {/* anchor label */}
      <div style={{ position: 'absolute', left: `${scene.anchor[0] * 100}%`, top: `${scene.anchor[1] * 100 + 6}%`, transform: 'translateX(-50%)',
        fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: 1, color: hexA(teacher.accent, 0.85), whiteSpace: 'nowrap', pointerEvents: 'none', textTransform: 'uppercase' }}>
        ↑ {scene.anchorLabel}
      </div>
    </div>
  );

  const sideContent = (
    <React.Fragment>
      <div style={{ textAlign: landscape ? 'left' : 'center', minHeight: 80 }}>
        {/* master's response caption */}
        {response ? (
          <div key={response.word + response.until} style={{ animation: 'mtRise .5s ease-out' }}>
            <div style={{ fontSize: 12, color: C.inkFaint, fontFamily: 'var(--mono)', letterSpacing: 1, marginBottom: 6 }}>“{response.word}” arises</div>
            <div style={{ display: 'inline-flex', alignItems: response.quote ? 'flex-start' : 'center', gap: 8, padding: '9px 15px', borderRadius: response.quote ? 16 : 999, maxWidth: 460, background: hexA(teacher.accent, 0.1), border: `1px solid ${hexA(teacher.accent, 0.4)}` }}>
              <MTIcon name="check" size={15} color={teacher.accent} sw={2.4} />
              <span style={{ fontSize: response.quote ? 14.5 : 15, lineHeight: 1.5, color: C.ink, fontWeight: 500, textWrap: 'pretty' }}>{response.text}</span>
            </div>
            {response.quote && (
              <div style={{ marginTop: 9, maxWidth: 460, marginLeft: 'auto', marginRight: 'auto' }}>
                <div style={{ font: 'italic 300 15px/1.5 var(--serif)', color: C.inkDim, textWrap: 'pretty' }}>“{response.quote}”</div>
                <div style={{ fontSize: 11, color: C.inkFaint, fontFamily: 'var(--mono)', letterSpacing: 0.6, marginTop: 4 }}>{teacher.name}</div>
                {response.story && <div style={{ fontSize: 11, color: '#FF9E6B', marginTop: 6, lineHeight: 1.5 }}>{response.story}</div>}
              </div>
            )}
          </div>
        ) : (
          <div key={narr} style={{ font: 'italic 300 19px/1.45 var(--serif)', color: C.inkDim, animation: 'mtRise .6s ease-out', textWrap: 'pretty' }}>
            “{guideLine}”
          </div>
        )}
      </div>
      <div style={{ display: 'flex', gap: 16, justifyContent: landscape ? 'flex-start' : 'center', alignItems: 'center', marginTop: 18, flexWrap: 'wrap' }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: 12, color: C.inkDim }}><span style={{ width: 9, height: 9, borderRadius: 9, background: '#B49CFF' }} /> mind</span>
        <span style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: 12, color: C.inkDim }}><span style={{ width: 9, height: 9, borderRadius: 9, background: C.teal }} /> body</span>
        <span style={{ marginLeft: 4, fontFamily: 'var(--mono)', fontSize: 13, color: teacher.accent }}>
          {mode === 'watch' ? 'met' : 'noted'} {st.noted}<span style={{ color: C.inkFaint }}> / {st.arisen}</span>
        </span>
      </div>
    </React.Fragment>
  );

  return (
    <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 80% at 50% 8%, #14112c 0%, ${C.bg} 60%)`, color: C.ink, overflow: 'hidden' }}>
      <ParticleField count={50} />
      <WitnessTopExit onExit={() => setStage('summary')} label="Done"
        center={<div style={{ fontFamily: 'var(--mono)', fontSize: 12, letterSpacing: 1, color: C.inkDim }}>{teacher.short.toUpperCase()} · {scene.label.toUpperCase()}</div>}
        right={<button onClick={() => setMode(m => m === 'watch' ? 'practice' : 'watch')} style={{
          appearance: 'none', cursor: 'pointer', background: 'rgba(255,255,255,0.05)', border: `1px solid ${C.inkGhost}`,
          borderRadius: 999, padding: '8px 14px', color: C.inkDim, fontSize: 12, fontFamily: 'var(--mono)' }}>
          {mode === 'watch' ? 'WATCHING' : 'YOU NOTE'}
        </button>} />

      {landscape ? (
        <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center' }}>
          <div style={{ flex: '1 1 46%', display: 'grid', placeItems: 'center', minWidth: 0 }}>{figureBox}</div>
          <div style={{ flex: '1 1 54%', padding: '0 6vw', maxWidth: 640 }}>
            <div style={{ marginBottom: 14 }}><MTEyebrow color={teacher.accent}>{teacher.name} · {scene.label}</MTEyebrow></div>
            {sideContent}
            <div style={{ marginTop: 26, fontSize: 12, color: C.inkFaint, fontFamily: 'var(--mono)', letterSpacing: 1 }}>
              {mode === 'practice' ? 'TAP EACH ARISING TO NOTE IT YOURSELF' : `WATCH HOW ${teacher.short.toUpperCase()} MEETS EACH ONE`}
            </div>
          </div>
        </div>
      ) : (
        <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: 60 }}>
          <div style={{ flex: 1, display: 'grid', placeItems: 'center', minHeight: 0, width: '100%' }}>{figureBox}</div>
          <div style={{ width: '100%', maxWidth: 560, padding: '0 24px 30px' }}>
            {sideContent}
            <div style={{ textAlign: 'center', marginTop: 14, fontSize: 11, color: C.inkFaint, fontFamily: 'var(--mono)', letterSpacing: 1 }}>
              {mode === 'practice' ? 'TAP EACH ARISING TO NOTE IT' : `WATCH HOW ${teacher.short.toUpperCase()} MEETS EACH ONE`}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function ghostLink(C) {
  return { appearance: 'none', background: 'none', border: 'none', color: C.inkFaint, cursor: 'pointer', fontSize: 15, fontFamily: 'var(--sans)' };
}

function WitnessTopExit({ onExit, label, right, center }) {
  const C = MT_COLORS;
  return (
    <div style={{ position: 'absolute', top: 26, left: 20, right: 20, display: 'flex', alignItems: 'center', justifyContent: 'space-between', zIndex: 20 }}>
      <button onClick={onExit} style={{ appearance: 'none', cursor: 'pointer', background: 'rgba(255,255,255,0.05)', border: `1px solid ${C.inkGhost}`, borderRadius: 999, padding: '8px 16px', color: C.inkDim, fontSize: 13, fontFamily: 'var(--mono)' }}>{label}</button>
      {center || <span />}
      {right || <span style={{ width: 60 }} />}
    </div>
  );
}

// one phenomenon: glowing node + label, arise→hold→pass lifecycle
function Phenom({ p, landscape, onNote, interactive }) {
  const C = MT_COLORS;
  const prog = Math.min(1, p.age / p.life);
  let op, scale;
  if (prog < 0.14) { const a = prog / 0.14; op = a; scale = 0.5 + 0.5 * a; }
  else if (prog < 0.72) { op = 1; scale = 1; }
  else { const q = (prog - 0.72) / 0.28; op = 1 - q; scale = 1 + (p.kind === 'mind' ? 0.18 : 0.4) * q; }
  const driftY = p.kind === 'mind' ? -prog * (landscape ? 60 : 44) : 0;
  const nodeR = p.kind === 'body' ? 12 : 9;

  return (
    <div style={{ position: 'absolute', left: `${p.lx}%`, top: `${p.ly}%`,
      transform: `translate(-50%,-50%) translateY(${driftY}px) scale(${scale})`, opacity: op,
      pointerEvents: interactive && !p.noted ? 'auto' : 'none', cursor: interactive ? 'pointer' : 'default', transition: 'opacity .12s linear' }}
      onClick={interactive ? onNote : undefined}>
      <div style={{ position: 'absolute', left: '50%', top: '50%', width: 96, height: 56, transform: 'translate(-50%,-50%)' }} />
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 7 }}>
        <div style={{ position: 'relative', width: nodeR * 2, height: nodeR * 2 }}>
          <div style={{ position: 'absolute', inset: -nodeR, borderRadius: '50%', background: hexA(p.hue, 0.22), filter: 'blur(6px)' }} />
          <div style={{ position: 'absolute', inset: 0, borderRadius: '50%', background: p.hue, boxShadow: `0 0 14px ${hexA(p.hue, 0.8)}`, animation: 'mtPulse 2.6s ease-in-out infinite' }} />
          {p.noted && <div style={{ position: 'absolute', inset: -8, borderRadius: '50%', border: `1.5px solid ${hexA(p.hue, 0.7)}`, animation: 'mtRipple .72s ease-out forwards' }} />}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', whiteSpace: 'nowrap',
          background: 'rgba(5,12,18,0.66)', border: `1px solid ${hexA(p.hue, p.noted ? 0.7 : 0.3)}`, borderRadius: 999, padding: '4px 12px', backdropFilter: 'blur(4px)' }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 13.5, fontWeight: 600, color: C.ink, letterSpacing: 0.2 }}>
            {p.noted && <MTIcon name="check" size={13} color={p.hue} sw={2.4} />}{p.word}
          </span>
          <span style={{ fontSize: 10.5, color: C.inkFaint, fontFamily: 'var(--serif)', fontStyle: 'italic' }}>{p.detail}</span>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { WitnessScreen, Phenom });
