/* chapter-l2-beacon.jsx — Level 2 capstone: assemble the whole Beacon.
   A guided, step-by-step build that pulls YOUR chosen values from the project
   and ends with a live 555-driven blink. Loads beacon-build.jsx for the board. */

const { useState, useEffect, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "paper",
  "audience": "adult"
}/*EDITMODE-END*/;

const BCN_STEPS = [
  {
    tag: "§ 01 · the board",
    title: { adult: "Lay the board & power rails", kids: "Set up the board" },
    body: {
      adult: "Every breadboard has two rails down each side: + (red) and − (blue). The 9 V supply clips on here and feeds everything. Before you place a single part — where does the power go?",
      kids: "A breadboard has a red (+) line and a blue (−) line. The battery clips on and powers the whole board.",
    },
    task: {
      kind: "choice",
      prompt: { adult: "The battery's red + lead clips on. Which rail should it feed so every part can tap power?",
                kids: "The red (+) wire from the battery — which line does it go to?" },
      options: [
        { label: { adult: "The top + rail (red)", kids: "The red + line" }, ok: true },
        { label: { adult: "Straight onto the LED's leg", kids: "Right on the light" }, ok: false },
        { label: { adult: "The − rail (blue)", kids: "The blue − line" }, ok: false },
      ],
      right: { adult: "Right — the + rail runs 9 V the length of the board so every stage can tap off it.",
               kids: "Yes! The red line carries power to everything." },
      wrong: { adult: "Power lands on the + rail first; components then tap off it. Feeding a part directly skips the shared supply.",
               kids: "Send it to the red + line first — that feeds everything." },
    },
  },
  {
    tag: "§ 02 · the pulse generator",
    title: { adult: "Drop in the 555 timer", kids: "Add the blinking brain" },
    body: {
      adult: "The 555 is the heartbeat. R₁ and R₂ charge the timing capacitor C; when it fills, the chip flips its OUT pin and dumps the charge — over and over. Those three parts set the blink rate. Prove you can predict it before wiring it in.",
      kids: "The 555 chip is the brain that blinks. Two resistors fill a little bucket (the capacitor); when it's full the chip flips, empties it, and starts over — blink, blink, blink!",
    },
    task: {
      kind: "calc",
      prompt: { adult: "With your timing parts, what blink rate will the 555 produce?",
                kids: "Work out the blink speed from your parts." },
      formula: (v) => `f = 1.44 ÷ ((${bbOhm(v.R1)} + 2 × ${bbOhm(v.R2)}) × ${bbFar(v.C)})`,
      unit: "Hz", tol: 0.08,
      answer: (v) => v.blinkHz,
      hint: { adult: "Work the bracket first: R₁ + 2·R₂, then multiply by C, then divide 1.44 by the result.",
              kids: "Add R₁ and two R₂s, multiply by the bucket size, then divide 1.44 by that." },
      solution: (v) => `f = 1.44 ÷ ((${v.R1} + 2·${v.R2}) × ${v.C}) = ${v.blinkHz.toFixed(2)} Hz`,
    },
  },
  {
    tag: "§ 03 · the driver",
    title: { adult: "Add the transistor driver", kids: "Add the muscle" },
    body: {
      adult: "The 555's OUT pin can't power a big lamp directly. Its signal flows through base resistor R_b into an NPN transistor, which does the heavy switching. R_b sets the base current — size it before you place it.",
      kids: "The chip is smart but weak. It tells a transistor (the muscle) when to switch the big light. R_b sets how hard it pushes.",
    },
    task: {
      kind: "calc",
      prompt: { adult: "The 555 output sits at 9 V; the base-emitter junction drops 0.7 V. What base current does your R_b set?",
                kids: "How much control-current flows into the transistor?" },
      formula: (v) => `I_b = (9 V − 0.7 V) ÷ ${bbOhm(v.Rb)}`,
      unit: "mA", tol: 0.1,
      answer: (v) => (8.3 / v.Rb) * 1000,
      hint: { adult: "Leftover voltage is 9 − 0.7 = 8.3 V. Divide by R_b (in ohms), then ×1000 for mA.",
              kids: "8.3 volts left, divide by R_b, then make it milliamps (×1000)." },
      solution: (v) => `I_b = 8.3 V ÷ ${v.Rb} Ω = ${((8.3 / v.Rb) * 1000).toFixed(2)} mA  (needs ≥ 2 mA to fully switch)`,
    },
  },
  {
    tag: "§ 04 · the beacon",
    title: { adult: "Wire the beacon lamp", kids: "Add the light!" },
    body: {
      adult: "Finally the LED and its current-limiting resistor R_led sit between the + rail and the transistor's collector. An LED only conducts one way — get the orientation right or it stays dark.",
      kids: "Now the actual light goes in, with a resistor so it doesn't burn out. But an LED only works one way around!",
    },
    task: {
      kind: "choice",
      prompt: { adult: "How do you orient the LED between the + side and the transistor's collector?",
                kids: "Which way does the LED go?" },
      options: [
        { label: { adult: "Anode (long leg) toward +, cathode toward the collector", kids: "Long leg toward +" }, ok: true },
        { label: { adult: "Cathode toward +, anode toward the collector", kids: "Long leg toward the transistor" }, ok: false },
        { label: { adult: "Either way — LEDs aren't fussy", kids: "Doesn't matter" }, ok: false },
      ],
      right: { adult: "Correct — current flows + → anode → cathode → collector → ground when the transistor turns on.",
               kids: "Yes! Long leg to +, and it lights up when the muscle switches on." },
      wrong: { adult: "An LED is a one-way valve. Reversed, it blocks current and never lights. Anode (long leg) must face the + supply.",
               kids: "An LED only works one way. The long leg points toward +." },
    },
  },
  {
    tag: "§ 05 · power up",
    title: { adult: "Power up the Beacon", kids: "Turn it on!" },
    body: {
      adult: "That's the whole device — pulse generator, driver, and lamp, wired correctly. Hit the power switch and the 555 takes over, flashing your beacon at the rate your timing parts set.",
      kids: "Everything's connected the right way! Flip the switch and watch your Beacon blink — all by itself.",
    },
  },
];

/* one gated assembly task — must be solved correctly to install the stage. */
function BcnTask({ task, vals, kids, solved, onSolve }) {
  const [val, setVal] = useState("");
  const [pick, setPick] = useState(null);
  const [checked, setChecked] = useState(false);
  const T = (x) => (x && typeof x === "object" && ("adult" in x || "kids" in x)) ? (kids ? x.kids : x.adult) : x;

  if (task.kind === "calc") {
    const ans = task.answer(vals);
    const tol = Math.max(Math.abs(ans) * (task.tol || 0.06), 1e-9);
    const num = parseFloat(val);
    const ok = !isNaN(num) && Math.abs(num - ans) <= tol;
    const submit = () => { setChecked(true); if (ok) onSolve(); };
    return (
      <div className={`bcn-task ${solved ? "done" : ""}`}>
        <div className="bcn-task-q"><Eq>{T(task.prompt)}</Eq></div>
        <div className="bcn-task-formula mono"><Eq>{task.formula(vals)}</Eq></div>
        <div className="bcn-task-row">
          <div className="bcn-task-input">
            <input type="text" inputMode="decimal" value={val} disabled={solved}
                   onChange={(e) => { setVal(e.target.value); setChecked(false); }}
                   onKeyDown={(e) => { if (e.key === "Enter") submit(); }}
                   placeholder="your answer" aria-label="Your answer" />
            <span className="bcn-task-unit">{task.unit}</span>
          </div>
          {!solved && <button className="bcn-task-check" onClick={submit} disabled={val.trim() === ""}>Check</button>}
          {solved && <span className="bcn-task-badge">✓ installed</span>}
        </div>
        {checked && !ok && (
          <div className="bcn-task-fb wrong">Not quite — {T(task.hint)}</div>
        )}
        {(solved || ok) && (
          <div className="bcn-task-fb ok"><b>Correct.</b> <Eq>{task.solution(vals)}</Eq></div>
        )}
      </div>
    );
  }

  // choice
  const submit = () => { setChecked(true); if (pick != null && task.options[pick].ok) onSolve(); };
  const isOk = pick != null && task.options[pick].ok;
  return (
    <div className={`bcn-task ${solved ? "done" : ""}`}>
      <div className="bcn-task-q"><Eq>{T(task.prompt)}</Eq></div>
      <div className="bcn-task-opts">
        {task.options.map((o, i) => (
          <button key={i}
                  className={`bcn-opt ${pick === i ? "sel" : ""} ${checked && pick === i ? (o.ok ? "right" : "wrong") : ""} ${solved && o.ok ? "right" : ""}`}
                  disabled={solved}
                  onClick={() => { setPick(i); setChecked(false); }}>
            <span className="bcn-opt-key">{String.fromCharCode(65 + i)}</span>
            <span><Eq>{T(o.label)}</Eq></span>
          </button>
        ))}
      </div>
      <div className="bcn-task-row">
        {!solved && <button className="bcn-task-check" onClick={submit} disabled={pick == null}>Check</button>}
        {solved && <span className="bcn-task-badge">✓ installed</span>}
      </div>
      {checked && !isOk && !solved && <div className="bcn-task-fb wrong">{T(task.wrong)}</div>}
      {(solved || isOk) && <div className="bcn-task-fb ok"><b>Correct.</b> {T(task.right)}</div>}
    </div>
  );
}

function BcnStepper({ step, setStep, n, reach, solved }) {
  return (
    <div className="bcn-stepper">
      {Array.from({ length: n }, (_, i) => {
        const locked = i > reach;
        return (
          <button key={i}
                  className={`bcn-dot ${i === step ? "on" : ""} ${solved[i] ? "done" : ""} ${locked ? "locked" : ""}`}
                  disabled={locked}
                  onClick={() => { if (!locked) setStep(i); }}
                  aria-label={`Step ${i + 1}`}>{i + 1}</button>
        );
      })}
    </div>
  );
}

/* BcnScope — a tiny live oscilloscope on the 555's OUT pin. Once powered it
   scrolls the square wave that actually drives the lamp, synced to the real
   blink rate, so you can SEE the heartbeat behind the flashing beacon. */
function BcnScope({ blinkHz, powered, softing }) {
  const ref = useRef(null);
  const W = 300, H = 86, x0 = 8, x1 = W - 8, yHi = 16, yLo = H - 18;
  useEffect(() => {
    const poly = ref.current;
    if (!poly) return;
    const hz = Math.max(0.2, Math.min(20, blinkHz || 2));
    const cyclesAcross = 3, N = 300;
    const build = (t) => {
      const pts = [];
      for (let i = 0; i <= N; i++) {
        const u = i / N;
        const x = x0 + u * (x1 - x0);
        const ph = u * cyclesAcross - t * hz;
        const frac = ((ph % 1) + 1) % 1;
        pts.push(x.toFixed(1) + "," + (frac < 0.5 ? yHi : yLo));
      }
      return pts.join(" ");
    };
    let raf, t0 = performance.now();
    const tick = (now) => { poly.setAttribute("points", build((now - t0) / 1000)); raf = requestAnimationFrame(tick); };
    poly.setAttribute("points", build(0));
    if (powered) raf = requestAnimationFrame(tick);
    return () => raf && cancelAnimationFrame(raf);
  }, [blinkHz, powered]);
  const hz = Math.max(0.2, Math.min(20, blinkHz || 2));
  const grid = [];
  for (let i = 1; i < 3; i++) { const y = yHi + (i / 3) * (yLo - yHi); grid.push(<line key={i} x1={x0} y1={y} x2={x1} y2={y} />); }
  return (
    <div className={`bcn-scope ${powered ? "on" : ""}`}>
      <div className="bcn-scope-head">
        <span>555 OUT · drives the lamp</span>
        <span className="bcn-scope-dot">{powered ? (softing ? "▸ soft-start…" : "● " + hz.toFixed(2) + " Hz") : "○ idle"}</span>
      </div>
      <svg viewBox={`0 0 ${W} ${H}`} className="bcn-scope-svg" preserveAspectRatio="none">
        <rect x="0" y="0" width={W} height={H} fill="#06140d" />
        <g className="bcn-scope-grid">{grid}</g>
        <polyline ref={ref} className="bcn-scope-trace" points="" />
        <text x={x0 + 2} y={H - 5} className="bcn-scope-cap">HIGH = lamp on · LOW = off</text>
      </svg>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useCrossChapterPersistence(t, setTweak);
  useEffect(() => { document.body.setAttribute("data-theme", t.theme); }, [t.theme]);
  const kids = t.audience === "kids";

  const [vals, setVals] = useState(() => bbReadValues());
  const [step, setStep] = useState(0);
  const [solved, setSolved] = useState({});
  const [powered, setPowered] = useState(false);
  const [lit, setLit] = useState(false);
  const [softing, setSofting] = useState(false);

  // re-read chosen values when the page regains focus (player may have changed picks)
  useEffect(() => {
    const reread = () => setVals(bbReadValues());
    window.addEventListener("focus", reread);
    window.addEventListener("hte-progress-changed", reread);
    return () => { window.removeEventListener("focus", reread); window.removeEventListener("hte-progress-changed", reread); };
  }, []);

  // the live 555 blink — with a soft-start fade-in over τ before it begins
  useEffect(() => {
    if (!(powered && step >= 4)) { setLit(false); setSofting(false); return; }
    const hz = Math.max(0.2, Math.min(20, vals.blinkHz || 2));
    const half = 500 / hz; // ms per on/off half-cycle
    const tauMs = Math.max(150, Math.min(4000, (vals.tau || 1) * 1000));
    // soft-start: hold the lamp on (ramping via CSS) for one τ, then blink
    setSofting(true); setLit(true);
    let id;
    const start = setTimeout(() => {
      setSofting(false);
      let on = true;
      id = setInterval(() => { on = !on; setLit(on); }, half);
    }, tauMs);
    return () => { clearTimeout(start); if (id) clearInterval(id); };
  }, [powered, step, vals.blinkHz, vals.tau]);

  // audible 555 click — ticks in time with the blink once it's running (sound-gated)
  useBlinkBeep(Math.max(0.2, Math.min(20, vals.blinkHz || 2)),
               powered && step >= 4 && !softing, null);

  // auto-advance to power-on enables the switch
  const atEnd = step >= BCN_STEPS.length - 1;
  const s = BCN_STEPS[step];

  // gating: a step is cleared if it has no task, or its task is solved.
  // You can only reach a step once every prior task-step is solved.
  const cleared = (i) => !BCN_STEPS[i].task || solved[i];
  let reach = 0;
  for (let i = 1; i < BCN_STEPS.length; i++) {
    if (!cleared(i - 1)) break;
    reach = i;
  }
  const canNext = !atEnd && cleared(step);
  const allCleared = BCN_STEPS.every((st, i) => cleared(i));

  const navItems = [
    { id: "cover", label: "Cover" },
    { id: "spec", label: kids ? "Your parts" : "Your spec" },
    { id: "build", label: kids ? "Build it!" : "Assemble" },
    { id: "done", label: "Complete" },
  ];

  const inRange = vals.blinkHz >= 1.5 && vals.blinkHz <= 2.5;

  return (
    <>
      <ChapterStartMarker chapterN="L2-09" />
      <ProgressBar />
      <TopBar currentN="L2-09" chapterLabel="Level 2 · The Beacon"
              audience={t.audience} setAudience={(v) => setTweak("audience", v)} />
      <ChapterNav items={navItems} />

      <main>
        {/* cover */}
        <section className="cover-page" id="cover" data-screen-label="L2-09 Cover">
          <div className="hero-grid"></div>
          <div className="cover-inner">
            <div style={{ display: "flex", alignItems: "baseline", gap: 16, marginBottom: 28 }}>
              <span className="eyebrow" style={{ color: "var(--current)" }}>Level 2 · capstone</span>
              <span className="eyebrow" style={{ color: "var(--ink-faint)" }}>·</span>
              <span className="eyebrow">The whole device</span>
            </div>
            <h1 className="serif cover-title">The <em>Beacon</em>.</h1>
            <div className="eyebrow" style={{ marginTop: 36, marginBottom: 16, color: "var(--current)" }}>
              Everything, assembled · using the parts you chose
            </div>
            <div className="cover-lede">
              {kids
                ? <>You designed every piece in the project. Now build them into one real circuit — figure out each part, wire it the right way, and make it blink!</>
                : <>Ten stages, one device. The pieces you sized in the project now come together into a working beacon — but you have to earn each one: predict the key numbers and wire each stage correctly before it locks in. Get them all, then power it on.</>}
            </div>
            <div className="cover-cta">
              <a href="#spec" className="arrow-link"><span>Begin assembly</span><span className="arrow-glyph">↓</span></a>
            </div>
          </div>
        </section>

        {/* your spec */}
        <section className="section" id="spec" data-screen-label="Your spec">
          <div className="marker">§ 00 · your bill of materials</div>
          <div className="section-inner">
            <h2 className="serif">{kids ? "The parts you picked." : "Built from your chosen values."}</h2>
            <p className="lede" style={{ maxWidth: "44em" }}>
              {vals.fromProject
                ? (kids
                  ? <>These come straight from your project designs. Change them any time in <a href="project.html">The Beacon</a> and this build updates.</>
                  : <>These are pulled from the choices you locked in across <a href="project.html">The Beacon</a> project. Anything you didn't finish uses a recommended value (marked). Revisit a stage and this build follows.</>)
                : (kids
                  ? <>You haven't designed parts in <a href="project.html">The Beacon</a> yet, so we're using good recommended values. Go pick your own and they'll show up here!</>
                  : <>You haven't completed project stages yet, so this uses recommended values. Design your own in <a href="project.html">The Beacon</a> and they'll flow into this build.</>)}
            </p>
            <div className="bcn-bom">
              <BomRow label="Beacon LED resistor" value={bbOhm(vals.ledR)} src={vals.used.led} note="Chapter 1–2" />
              <BomRow label="Soft-start RC" value={`${bbOhm(vals.softR)} + ${bbFar(vals.softC)} → τ ${vals.tau.toFixed(1)} s`} src={vals.used.soft} note="Chapter 4" />
              <BomRow label="Transistor base resistor R_b" value={bbOhm(vals.Rb)} src={vals.used.drive} note="Chapter 6" />
              <BomRow label="Reverse-protection diode" value={vals.diode >= 1 ? `${vals.diode} A` : `${vals.diode * 1000} mA`} src={vals.used.protect} note="Chapter 7" />
              <BomRow label="555 · R₁" value={bbOhm(vals.R1)} src={vals.used.blink} note="Chapter 10" />
              <BomRow label="555 · R₂" value={bbOhm(vals.R2)} src={vals.used.blink} note="Chapter 10" />
              <BomRow label="555 · timing cap C" value={bbFar(vals.C)} src={vals.used.blink} note="Chapter 10" />
            </div>
            <div className="bcn-rate">
              <span className="bcn-rate-label">Resulting blink rate</span>
              <span className="bcn-rate-val"><Eq>{`f = 1.44 ÷ ((R_1 + 2·R_2)·C) = `}</Eq><b>?</b></span>
              <span className="bcn-rate-note">You'll work this one out yourself during assembly — that's step 2.</span>
            </div>
            <div className="cover-cta" style={{ marginTop: 28 }}>
              <a href="#build" className="arrow-link"><span>Start assembling</span><span className="arrow-glyph">↓</span></a>
            </div>
          </div>
        </section>

        {/* build */}
        <section className="section" id="build" data-screen-label="Assemble"
                 style={{ background: "var(--bg-deeper)" }}>
          <div className="marker">§ 01 · assemble the device</div>
          <div className="section-inner" style={{ maxWidth: 1180 }}>
            <div className="bcn-build">
              <div className="bcn-board-wrap">
                <BeaconBoard step={step} lit={lit} vals={vals} />
                {atEnd && (
                  <button className={`bcn-power ${powered ? "on" : ""}`}
                          onClick={() => setPowered(p => !p)} disabled={!allCleared}>
                    {powered ? "◼ Power off" : "⏻ Power up"}
                  </button>
                )}
              </div>
              <aside className="bcn-panel">
                <div className="bcn-step-tag">{s.tag}</div>
                <h3 className="serif bcn-step-title">{s.title[kids ? "kids" : "adult"]}</h3>
                <p className="bcn-step-body"><Eq>{s.body[kids ? "kids" : "adult"]}</Eq></p>
                {s.task && (
                  <BcnTask task={s.task} vals={vals} kids={kids}
                           solved={!!solved[step]}
                           onSolve={() => setSolved(m => ({ ...m, [step]: true }))} />
                )}
                {atEnd && (
                  <div className={`bcn-live ${lit ? "lit" : ""}`}>
                    {powered
                      ? (softing
                          ? <>Soft-starting — fading in over <b>{(vals.tau || 1).toFixed(1)} s</b> (τ = R×C)…</>
                          : <>Blinking at <b>{Math.max(0.2, Math.min(20, vals.blinkHz)).toFixed(2)} Hz</b> — {(1 / vals.blinkHz).toFixed(2)} s per flash.</>)
                      : <>Every stage checks out. Hit <b>Power up</b> to start the 555.</>}
                  </div>
                )}
                {atEnd && <BcnScope blinkHz={vals.blinkHz} powered={powered} softing={softing} />}
                <BcnStepper step={step} setStep={setStep} n={BCN_STEPS.length} reach={reach} solved={solved} />
                <div className="bcn-nav">
                  <button onClick={() => setStep(s => Math.max(0, s - 1))} disabled={step === 0}>← Back</button>
                  {!atEnd ? (
                    <button className="primary" onClick={() => setStep(s => Math.min(BCN_STEPS.length - 1, s + 1))}
                            disabled={!canNext}
                            title={!canNext ? "Solve this step to continue" : ""}>
                      {step === BCN_STEPS.length - 2 ? "Finish →" : "Next →"}
                    </button>
                  ) : (
                    powered
                      ? <a className="bcn-nav-done" href="#done">See your certificate →</a>
                      : <span className="bcn-nav-hint">Last step — hit <b>⏻ Power up</b> ↑</span>
                  )}
                </div>
              </aside>
            </div>
          </div>
        </section>

        {/* complete */}
        <section className="section" id="done" data-screen-label="Complete">
          <div className="section-inner" style={{ textAlign: "center", maxWidth: 720 }}>
            <div className="marker" style={{ justifyContent: "center" }}>§ 02 · the build is done</div>
            <h2 className="serif" style={{ fontSize: "clamp(34px,5vw,60px)" }}>{kids ? "You built a Beacon!" : "The Beacon lives."}</h2>
            <p className="lede" style={{ margin: "16px auto 0" }}>
              {kids
                ? <>From a single LED to a self-blinking beacon with a brain, muscle, and light — you wired the whole thing. That's real engineering.</>
                : <>You've integrated every Level-2 skill into one device: current limiting, a transistor driver, and a 555 oscillator — running on the component values you calculated yourself. That's the full arc, from Ohm's law to a working product.</>}
            </p>
            <WhatsNext
              currentN="L2-09"
              kids={kids}
              summary={kids ? <>The Beacon is complete — go show someone!</> : <>Capstone complete. You've finished the build track.</>}
              prevHref="chapter-l2-oscillator.html"
              prevLabel="L2 · The Oscillator"
              nextHref="capstone-l2.html"
              nextLabel="Claim your certificate"
            />

            <div style={{ marginTop: 36 }}>
              {powered
                ? <BeaconCertificate vals={vals} kids={kids} />
                : <p className="bcn-cert-hint">⏻ Power up your Beacon above to unlock your build certificate.</p>}
            </div>
          </div>
        </section>
      </main>

      <TweaksPanel title="Tweaks">
        <CommonTweaks t={t} setTweak={setTweak} />
      </TweaksPanel>
      <GlossaryFab />
    </>
  );
}

function BomRow({ label, value, src, note }) {
  return (
    <div className="bcn-bom-row">
      <span className="bcn-bom-label"><Eq>{label}</Eq></span>
      <span className="bcn-bom-mid">{note}</span>
      <span className="bcn-bom-val mono">{value}
        <i className={src ? "yours" : "rec"}>{src ? "yours" : "rec"}</i>
      </span>
    </div>
  );
}

/* ── Beacon build certificate ───────────────────────────────────────────── */
function BeaconCertificate({ vals, kids }) {
  const date = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
  const hz = Math.max(0.2, Math.min(20, vals.blinkHz || 2));
  return (
    <div className="cert-wrap" style={{ marginTop: 8 }}>
      <div className="cert" id="certificate">
        <div className="cert-border">
          <div className="cert-eyebrow">Fathohm · Level 2 capstone</div>
          <div className="cert-title">The Beacon</div>
          <div className="cert-level">· built &amp; powered ·</div>
          <div className="cert-body">
            {kids
              ? <>This certifies that the bearer assembled the whole Beacon — brain, muscle, and light — and switched it on. It blinks!</>
              : <>This certifies that the bearer integrated every Level-2 skill into one working device — current limiting, a soft-start, a transistor driver, and a 555 oscillator — and powered it to life.</>}
          </div>
          <div className="bcn-cert-specs">
            <div><span>Blink rate</span><b>{hz.toFixed(2)} Hz</b></div>
            <div><span>Soft-start τ</span><b>{(vals.tau || 1).toFixed(1)} s</b></div>
            <div><span>Beacon R</span><b>{bbOhm(vals.ledR)}</b></div>
            <div><span>555 timing</span><b>{bbOhm(vals.R1)} · {bbOhm(vals.R2)} · {bbFar(vals.C)}</b></div>
          </div>
          <div className="cert-row">
            <div>
              <div className="cert-line"></div>
              <div className="cert-cap">Powered up {date}</div>
            </div>
            <div className="cert-seal">
              <svg viewBox="0 0 80 80" width="72" height="72">
                <circle cx="40" cy="40" r="36" fill="none" stroke="var(--current)" strokeWidth="2" />
                <circle cx="40" cy="40" r="29" fill="none" stroke="var(--current)" strokeWidth="1" opacity="0.5" />
                {Array.from({ length: 8 }, (_, i) => {
                  const a = (i / 8) * Math.PI * 2 - Math.PI / 2;
                  return <line key={i} x1={40 + Math.cos(a) * 14} y1={40 + Math.sin(a) * 14}
                               x2={40 + Math.cos(a) * 22} y2={40 + Math.sin(a) * 22}
                               stroke="var(--current)" strokeWidth="2.4" strokeLinecap="round" />;
                })}
                <circle cx="40" cy="40" r="9" fill="var(--current)" />
                <text x="40" y="74" fontFamily="IBM Plex Mono, monospace" fontSize="7"
                      fill="var(--ink-faint)" textAnchor="middle" letterSpacing="0.2em">BEACON</text>
              </svg>
            </div>
          </div>
        </div>
      </div>
      <div className="cert-actions">
        <button className="cta cta-primary" onClick={() => window.print()}>Print / save certificate</button>
        <a className="cta cta-secondary" href="capstone-l2.html">Master Engineer exam →</a>
      </div>
    </div>
  );
}

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