/* chapter12.jsx — Ch. 12 · The Gatekeeper (Level 1½).
   Op-amps via water: a tireless keeper compares two water levels and slams
   a huge valve fully open or shut. Feedback turns the slam into precision. */

const { useState, useEffect, useRef } = React;

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

/* ─── shared bits: two level-tanks + a keeper + an output column ───────── */
function Ch12Tank({ x, level, label, color = "var(--water)", w = 64, h = 130, y = 60 }) {
  const fillH = h * Math.max(0, Math.min(1, level / 12));
  return (
    <g>
      <rect x={x} y={y + h - fillH} width={w} height={fillH} fill={color} opacity="0.8" />
      <path d={`M ${x} ${y} L ${x} ${y + h} L ${x + w} ${y + h} L ${x + w} ${y}`}
            fill="none" stroke="var(--ink)" strokeWidth="2.5" />
      <line x1={x - 4} y1={y + h - fillH} x2={x + w + 4} y2={y + h - fillH}
            stroke="var(--water-deep)" strokeWidth="2" />
      <text x={x + w / 2} y={y + h + 22} textAnchor="middle" fontFamily="IBM Plex Mono, monospace"
            fontSize="14.5" fill="var(--ink-soft)">{label}</text>
      <text x={x + w / 2} y={y - 10} textAnchor="middle" fontFamily="IBM Plex Mono, monospace"
            fontSize="13" fill="var(--water-deep)" fontWeight="600">{level.toFixed(1)}</text>
    </g>
  );
}

function Ch12Keeper({ x, y, judging }) {
  return (
    <g transform={`translate(${x} ${y})`}>
      <circle r="17" fill="var(--bg-card)" stroke="var(--ink)" strokeWidth="2.5" />
      <circle cx="-5.5" cy="-3" r="2.2" fill="var(--ink)" />
      <circle cx="5.5" cy="-3" r="2.2" fill="var(--ink)" />
      <path d={judging === "up" ? "M -6 6 Q 0 11 6 6" : judging === "down" ? "M -6 9 Q 0 4 6 9" : "M -6 7.5 L 6 7.5"}
            fill="none" stroke="var(--ink)" strokeWidth="2" strokeLinecap="round" />
      <text y="34" textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="11"
            fill="var(--ink-faint)" letterSpacing="0.08em">KEEPER</text>
    </g>
  );
}

/* ─── §02 comparator playground ────────────────────────────────────────── */
function Ch12Comparator({ kids }) {
  const [plus, setPlus] = useState(7);
  const [minus, setMinus] = useState(5);
  const high = plus > minus;
  return (
    <section className="section" id="comparator" data-screen-label="02 Comparator">
      <div className="marker">§ 02 · the slam</div>
      <div className="section-inner">
        <div className="two-col">
          <div>
            <h2 className="serif">All or nothing.<br/><em>Instantly.</em></h2>
            <p className="lede">
              {kids
                ? <>Meet the gatekeeper. They watch two tanks — one marked <b>+</b>, one marked <b>−</b> — and follow ONE rule: if the + tank is even a drop higher, slam the big output valve ALL the way open. Otherwise, slam it shut.</>
                : <>An op-amp has two inputs (+ and −) and one rule: if V₊ exceeds V₋ by even a microvolt, the output slams to the top rail. If it's under, it slams to the bottom. The gain is absurd — a hundred thousand or more.</>}
            </p>
            <p>
              {kids
                ? <>The keeper never gets tired, never says "ehh, close enough," and reacts faster than you can blink. Used raw like this, they're called a <em>comparator</em> — a which-is-bigger machine.</>
                : <>Used raw, this is a <em>comparator</em> — a which-is-bigger machine. Thermostats ("is temp below the setpoint?"), low-battery warnings ("did V sag under 3.2?"), night lights ("did the sensor divider fall past dusk?") — all one keeper plus a reference from chapter 11's divider.</>}
            </p>
            <div className="marg" style={{ marginTop: 14 }}>
              {kids
                ? <>Slide the tanks — see if you can find a spot where the valve is half open. (Spoiler: you can't!)</>
                : <>Note what's missing: any middle ground. Raw op-amps don't do "70% open." That precision comes from feedback — next section.</>}
            </div>
          </div>
          <div>
            <div className="card" style={{ padding: "22px 26px" }}>
              <div className="eyebrow" style={{ marginBottom: 12 }}>the keeper's rule</div>
              <svg viewBox="0 0 460 250" width="100%" style={{ display: "block" }}>
                <Ch12Tank x={36} level={plus} label="+" />
                <Ch12Tank x={140} level={minus} label="−" />
                <Ch12Keeper x={268} y={120} judging={high ? "up" : "down"} />
                {/* sight lines */}
                <line x1={104} y1={190 - 130 * plus / 12 + 0} x2={252} y2={114} stroke="var(--ink-faint)" strokeWidth="1" strokeDasharray="2 4" />
                <line x1={208} y1={190 - 130 * minus / 12 + 0} x2={254} y2={122} stroke="var(--ink-faint)" strokeWidth="1" strokeDasharray="2 4" />
                {/* output column */}
                <rect x={350} y={50} width={56} height={150} fill="var(--bg-deeper)" stroke="var(--ink)" strokeWidth="2.5" rx="4" />
                <rect x={350} y={high ? 50 : 196} width={56} height={high ? 146 : 4}
                      fill={high ? "var(--current)" : "var(--current-soft)"} opacity="0.85" rx="4"
                      style={{ transition: "all 180ms ease" }} />
                <text x={378} y={226} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="13"
                      fill={high ? "var(--current-deep)" : "var(--ink-faint)"} fontWeight="600">
                  {high ? (kids ? "FULL OPEN" : "OUT = HIGH") : (kids ? "SLAMMED SHUT" : "OUT = LOW")}
                </text>
                <line x1={285} y1={120} x2={344} y2={120} stroke="var(--ink)" strokeWidth="2" markerEnd="" />
              </svg>
              <Slider name={kids ? "+ tank level" : "V₊"} value={plus} min={0} max={12} step={0.1} unit={kids ? "" : " V"} accent="water" onChange={setPlus} />
              <Slider name={kids ? "− tank level" : "V₋"} value={minus} min={0} max={12} step={0.1} unit={kids ? "" : " V"} accent="" onChange={setMinus} />
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ─── §03 feedback: the follower ───────────────────────────────────────── */
function Ch12Follower({ kids }) {
  const [input, setInput] = useState(6);
  const [out, setOut] = useState(6);
  // animate the output chasing the input
  useEffect(() => {
    let raf, last = performance.now();
    const tick = (now) => {
      const dt = Math.min(0.06, (now - last) / 1000); last = now;
      setOut(o => o + (input - o) * Math.min(1, dt * 9));
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [input]);
  const err = input - out;
  return (
    <section className="section" id="feedback" data-screen-label="03 Feedback" style={{ background: "var(--bg-deeper)" }}>
      <div className="marker">§ 03 · the leash</div>
      <div className="section-inner">
        <div className="two-col">
          <div>
            <h2 className="serif">Feedback puts the slam<br/>on a <em>leash</em>.</h2>
            <p className="lede">
              {kids
                ? <>Here's the genius trick: pipe the OUTPUT back around to the − tank. Now the keeper compares your input to <em>their own output</em>. If the output is too low — open more. Too high — close a bit. The output gets herded until it exactly matches the input!</>
                : <>Route the output back to the − input. Now the keeper compares the input to its own output: output low → open more; high → back off. The only resting point is V_out = V_in. This is <em>negative feedback</em>, and it converts infinite slam into perfect obedience.</>}
            </p>
            <p>
              {kids
                ? <>"Why bother? The output just copies the input!" Because the copy is <em>strong</em>. The input can be a whisper from a fragile sensor — the output is the keeper's mighty valve saying the same thing with muscle.</>
                : <>A copy sounds useless until you remember chapter 11's loading problem: a divider tap collapses if anything thirsty drinks from it. A follower reads the tap with almost zero sip, and repeats it with muscle. It's called a <em>buffer</em> — the fix for loading.</>}
            </p>
            <div className="marg" style={{ marginTop: 14 }}>
              {kids
                ? <>Drag the input around — watch the keeper chase it. That little gap that opens and closes? The keeper hates that gap. Their whole job is killing it.</>
                : <>Drag V_in and watch the chase. The momentary gap is the "error" — the keeper's only mission is driving it to zero. Virtually every analog circuit you'll ever meet is some flavor of this loop.</>}
            </div>
          </div>
          <div>
            <div className="card" style={{ padding: "22px 26px" }}>
              <div className="eyebrow" style={{ marginBottom: 12 }}>{kids ? "the chase" : "unity follower · out chases in"}</div>
              <svg viewBox="0 0 460 250" width="100%" style={{ display: "block" }}>
                <Ch12Tank x={36} level={input} label={kids ? "input (+)" : "V_in (+)"} />
                <Ch12Tank x={330} level={out} label={kids ? "output" : "V_out"} color="var(--current)" />
                <Ch12Keeper x={225} y={110} judging={Math.abs(err) < 0.15 ? "flat" : err > 0 ? "up" : "down"} />
                {/* feedback pipe: output top → keeper's − side */}
                <path d="M 362 44 C 362 18, 250 14, 240 88" fill="none" stroke="var(--water-deep)" strokeWidth="2.5" strokeDasharray="6 5" />
                <text x={295} y={26} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="11.5"
                      fill="var(--water-deep)" letterSpacing="0.06em">{kids ? "report back (−)" : "feedback → V₋"}</text>
                <line x1={246} y1={110} x2={324} y2={110} stroke="var(--ink)" strokeWidth="2" />
                {/* error readout */}
                <text x={225} y={196} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="13"
                      fill={Math.abs(err) < 0.15 ? "var(--water-deep)" : "var(--current-deep)"}>
                  {kids
                    ? (Math.abs(err) < 0.15 ? "matched! keeper rests" : "gap! keeper adjusting…")
                    : `error = ${err.toFixed(2)} V ${Math.abs(err) < 0.15 ? "· settled" : "· correcting"}`}
                </text>
              </svg>
              <Slider name={kids ? "Drag the input!" : "V_in"} value={input} min={0.5} max={11.5} step={0.1}
                      unit={kids ? "" : " V"} accent="water" onChange={setInput} />
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ─── §04 gain: feedback through a divider ─────────────────────────────── */
function Ch12Gain({ kids }) {
  const [r1, setR1] = useState(3);   // feedback resistor
  const [r2, setR2] = useState(1);   // to ground
  const [input, setInput] = useState(2);
  const gain = 1 + r1 / r2;
  const out = Math.min(12, input * gain);
  const clipped = input * gain > 12;
  return (
    <section className="section" id="gain" data-screen-label="04 Gain">
      <div className="marker">§ 04 · lying to the keeper</div>
      <div className="section-inner">
        <div className="two-col">
          <div>
            <h2 className="serif">Want amplification?<br/><em>Lie to the keeper.</em></h2>
            <p className="lede">
              {kids
                ? <>Last chapter you built a divider that reports a FRACTION of a level. Now the trick: put one in the report-back pipe, so the keeper only hears <em>half</em> of what the output really is. To make the report match your input, the keeper must drive the output TWICE as high!</>
                : <>Don't feed back the whole output — feed back a divided fraction (chapter 11, reporting for duty). If the keeper only "hears" half of V_out, it must drive V_out to 2×V_in before the loop rests. Gain, set purely by a resistor ratio.</>}
            </p>
            <p>
              {kids
                ? <>That's how a whisper from a microphone becomes a roar from a speaker: the keeper is told a shrunken story, so it overshoots by exactly the amount you picked.</>
                : <><Eq>Gain = 1 + R₁/R₂</Eq> — the non-inverting amplifier, workhorse of all analog electronics. Microphone preamps, sensor conditioners, headphone drivers: pick two resistors, get exactly that multiplication. The op-amp's wild 100,000× gain is never used raw — feedback trades it for <em>precision</em>.</>}
            </p>
            <EqEase kids={kids} />
            <div className="marg" style={{ marginTop: 14 }}>
              {kids
                ? <>One limit: the output can't go higher than the keeper's own supply tank. Ask for too much and it just maxes out — "clipping." (That's the crunchy sound of too-loud speakers!)</>
                : <>One hard limit: V_out can't exceed the supply rails. Demand more and the output flat-tops at the rail — <em>clipping</em>, the distortion you hear when audio is driven too hard.</>}
            </div>
          </div>
          <div>
            <div className="card" style={{ padding: "22px 26px" }}>
              <div className="eyebrow" style={{ marginBottom: 12 }}>{kids ? "the overshoot machine" : "non-inverting amp · 12 V rails"}</div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18, alignItems: "end", marginBottom: 6 }}>
                <div>
                  <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 12, color: "var(--ink-soft)", marginBottom: 6 }}>
                    {kids ? "input whisper" : "V_in"}
                  </div>
                  <div style={{ height: 120, background: "var(--bg-deeper)", border: "1.5px solid var(--rule-strong)", borderRadius: 4, position: "relative" }}>
                    <div style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: `${input / 12 * 100}%`, background: "var(--water)", opacity: 0.8, transition: "height 120ms ease" }}></div>
                  </div>
                  <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 13.5, marginTop: 6, color: "var(--water-deep)", fontWeight: 600 }}>{input.toFixed(1)} {kids ? "" : "V"}</div>
                </div>
                <div>
                  <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 12, color: "var(--ink-soft)", marginBottom: 6 }}>
                    {kids ? "output roar" : `V_out = ${gain.toFixed(1)} × V_in`}
                  </div>
                  <div style={{ height: 120, background: "var(--bg-deeper)", border: "1.5px solid var(--rule-strong)", borderRadius: 4, position: "relative", overflow: "hidden" }}>
                    <div style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: `${out / 12 * 100}%`, background: clipped ? "oklch(0.62 0.17 35)" : "var(--current)", opacity: 0.85, transition: "height 120ms ease" }}></div>
                    {clipped && <div style={{ position: "absolute", top: 4, left: 0, right: 0, textAlign: "center", fontFamily: "IBM Plex Mono, monospace", fontSize: 11, color: "oklch(0.45 0.16 35)", fontWeight: 600 }}>CLIPPED</div>}
                  </div>
                  <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 13.5, marginTop: 6, color: clipped ? "oklch(0.55 0.17 35)" : "var(--current-deep)", fontWeight: 600 }}>{out.toFixed(1)} {kids ? "" : "V"}</div>
                </div>
              </div>
              <Slider name={kids ? "Input level" : "V_in"} value={input} min={0.2} max={6} step={0.1} unit={kids ? "" : " V"} accent="water" onChange={setInput} />
              <Slider name={kids ? "Report-shrinker top (R₁)" : "R₁ · feedback"} value={r1} min={0} max={10} step={0.5} unit={kids ? "" : " kΩ"} accent="" onChange={setR1} />
              <Slider name={kids ? "Report-shrinker bottom (R₂)" : "R₂ · to ground"} value={r2} min={0.5} max={10} step={0.5} unit={kids ? "" : " kΩ"} accent="" onChange={setR2} />
              <div className="branch-readout" style={{ marginTop: 10 }}>
                <span>{kids ? "overshoot factor" : "gain = 1 + R₁/R₂"}</span>
                <span className="mono"><b style={{ color: "var(--current-deep)" }}>×{gain.toFixed(2)}</b></span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ─── App ─────────────────────────────────────────────────────────────── */
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 navItems = [
    { id: "cover", label: "Cover" },
    { id: "comparator", label: kids ? "The slam" : "Comparator" },
    { id: "feedback", label: kids ? "The leash" : "Feedback" },
    { id: "gain", label: "Gain" },
    { id: "practice", label: "Practice" },
    { id: "quiz", label: "Quiz" },
  ];

  return (
    <>
      <ProgressBar />
      <TopBar currentN="12" audience={t.audience} setAudience={(a) => setTweak("audience", a)} />
      <ChapterNav items={navItems} />

      <main>
        <CoverPage chapterN="12"
                   chapterTitle={<>The <em>Gatekeeper</em>.</>}
                   chapterSub="Op-amps & feedback"
                   kids={kids}
                   lede={kids
                     ? <>A tireless keeper who watches two tanks and slams a giant valve open or shut. Sounds crude — until you put them on a leash. Then they become the most precise tool in the shop.</>
                     : <>The op-amp: comically excessive gain, two inputs, one rule. Feedback tames the excess into precision — and that single loop is the foundation of all analog design.</>
                   }>
          <ChapterStartMarker chapterN="12" />
          <div className="marg">
            Level 1½ · builds on chapters 1, 2 & 11. The divider (ch. 11) appears inside this chapter's best trick.
          </div>
        </CoverPage>

        <Ch12Comparator kids={kids} />

        <CheckpointQuiz kids={kids} label="Checkpoint 1" pick={2}
          title={kids ? "Quick check!" : "Did it stick?"}
          questions={[
            { q: kids ? "The keeper's one rule is…" : "A raw op-amp's output is…",
              options: [kids ? "+ higher → valve fully open; otherwise fully shut" : "slammed to a rail — whichever input is higher decides which",
                        kids ? "open the valve halfway sometimes" : "a smooth copy of V₊", kids ? "watch only one tank" : "always zero", kids ? "take breaks" : "random"], correct: 0,
              explain: kids ? "All or nothing, every time. No halfway, no maybe."
                            : "Open-loop gain is ~100,000+: any input difference saturates the output at a supply rail." },
            { q: "A comparator answers exactly one question:",
              options: ["which input is bigger?", "how much current flows?", "what time is it?", "how hot is it exactly?"], correct: 0,
              explain: kids ? "It's a which-is-higher machine. That's the whole job — and it's enough for thermostats and alarms!"
                            : "Binary verdict on V₊ vs V₋. Pair it with a divider-made reference and you get thresholds: thermostats, low-battery flags, night lights." },
            { q: kids ? "Where does the keeper's 'compare level' usually come from?" : "The reference voltage on a comparator's − input typically comes from…",
              options: [kids ? "a divider from last chapter — two squeezes set the mark" : "a voltage divider — chapter 11's tap",
                        kids ? "a magic number inside" : "the mains", kids ? "the moon" : "an inductor", kids ? "nowhere" : "thin air"], correct: 0,
              explain: kids ? "Chapters stack! Two squeezes make any level you want — that's the mark the keeper compares against."
                            : "A divider sets the threshold; the comparator watches a signal cross it. Ch. 11 + ch. 12, composed." },
            { q: kids ? "Can the raw keeper hold the valve 70% open?" : "Without feedback, can an op-amp output sit at 70% of the rail?",
              options: ["No — it's all or nothing", "Yes, easily", "Only on Tuesdays", "Only when cold"], correct: 0,
              explain: kids ? "Raw keepers only slam. The gentle touch needs the leash — feedback!"
                            : "Open-loop, any error saturates it. Intermediate outputs exist only with feedback closing the loop." },
          ]} />

        <Ch12Follower kids={kids} />
        <Ch12Gain kids={kids} />

        <CheckpointQuiz kids={kids} label="Checkpoint 2" pick={2}
          title={kids ? "Spot it in real life!" : "Out in the wild."}
          questions={[
            { q: kids ? "A whispering sensor needs to talk to a thirsty gadget. You should…" : "A high-impedance sensor tap must drive a hungry load. Insert…",
              options: [kids ? "put a keeper-with-leash between them (a follower)" : "a unity follower — reads gently, drives strongly",
                        kids ? "shout at the sensor" : "a bigger divider", kids ? "thicker wires" : "a fuse", kids ? "hope" : "an inductor"], correct: 0,
              explain: kids ? "The keeper listens with barely a sip, then repeats the message with muscle. Loading problem solved!"
                            : "Buffers fix ch. 11's loading: near-infinite input impedance in, low impedance out, gain of 1." },
            { q: "A microphone preamp with gain ×50 is most likely…",
              options: ["an op-amp with a feedback divider set to 1+R₁/R₂ = 50", "fifty op-amps", "a transformer", "a big resistor"], correct: 0,
              explain: kids ? "One keeper, one report-shrinker set to one-fiftieth. The keeper overshoots ×50 — exactly as planned."
                            : "Pick R₁/R₂ = 49 and the loop settles at ×50. Precision from two passive parts plus one absurd amplifier." },
            { q: "Speakers driven too hard sound crunchy because…",
              options: ["the amp clips — output flat-tops at the supply rails", "the music gets heavier", "electrons slow down", "the wires tangle"], correct: 0,
              explain: kids ? "The keeper can't push higher than its own supply tank — the wave's tops get sliced flat, and flat tops sound crunchy."
                            : "Demanded output exceeds the rails; the waveform truncates. Clipping = the loop's one unbreakable limit." },
            { q: kids ? "A thermostat clicks the heat on when it gets cold. Inside is probably…" : "The simplest electronic thermostat is…",
              options: [kids ? "a keeper comparing a heat-sensor divider to a set mark" : "a comparator: thermistor divider vs. a setpoint divider",
                        kids ? "a tiny person" : "a 555 only", kids ? "an ice cube" : "a motor", kids ? "a fan" : "an antenna"], correct: 0,
              explain: kids ? "One tank holds the 'cold enough' mark, the other follows the sensor. Keeper slams the heater on when they cross!"
                            : "Two dividers (one temperature-sensitive) into a comparator. Hysteresis gets added in practice — but that's the core." },
          ]} />

        <PracticeProblems chapterN="12" kids={kids} problems={[
          (rng) => {
            const r1 = rng.pick([1, 2, 4, 9]), r2 = rng.pick([1, 2]);
            const g = 1 + r1 / r2;
            return {
              q: { adult: `Non-inverting amp: R₁ = ${r1} kΩ (feedback), R₂ = ${r2} kΩ (to ground). What's the gain?`,
                   kids: `The report-shrinker has a top squeeze of ${r1} and bottom of ${r2}. How many times does the keeper overshoot?` },
              unit: "×", answer: g,
              hint: { adult: "Gain = 1 + R₁/R₂.", kids: "Overshoot = 1 + top ÷ bottom." },
              solution: { adult: `1 + ${r1}/${r2} = ×${g.toFixed(1)}.`, kids: `1 + ${r1}÷${r2} = ${g.toFixed(1)} times!` },
            };
          },
          (rng) => {
            const g = rng.pick([2, 5, 10]), vin = rng.pick([0.2, 0.5, 1]);
            return {
              q: { adult: `An amp with gain ×${g} gets ${vin} V in. V_out (12 V rails — watch for clipping)?`,
                   kids: `The keeper overshoots ×${g}, and the whisper coming in is ${vin}. How big is the roar? (It can't beat 12!)` },
              unit: "V", answer: Math.min(12, g * vin),
              hint: { adult: "Multiply — then cap at the rail.", kids: "Multiply, but the output can never beat the supply tank (12)." },
              solution: { adult: `${g}×${vin} = ${(g * vin).toFixed(1)} V${g * vin > 12 ? " → clips at 12 V" : ""}.`,
                          kids: `${g} × ${vin} = ${(g * vin).toFixed(1)}${g * vin > 12 ? " — too big! It maxes out at 12." : "."}` },
            };
          },
          (rng) => {
            const vout = rng.pick([4, 6, 8]), g = rng.pick([4, 8]);
            return {
              q: { adult: `You need ${vout} V out of a gain-×${g} amp. What input do you feed it?`,
                   kids: `You want a roar of ${vout} from a keeper who overshoots ×${g}. How big should the whisper be?` },
              unit: "V", answer: vout / g,
              hint: { adult: "Run the gain backwards: V_in = V_out / gain.", kids: "Divide the roar by the overshoot." },
              solution: { adult: `${vout}/${g} = ${(vout / g).toFixed(2)} V.`, kids: `${vout} ÷ ${g} = ${(vout / g).toFixed(2)}.` },
            };
          },
        ]} />

        <ChapterQuiz chapterN="12" pick={5}
          title={kids ? "Quick quiz!" : "Check your understanding."}
          questions={kids ? [
            { q: "The keeper watches…", kind: "concept", options: ["two tanks: + and −", "one tank", "the weather", "your homework"], correct: 0,
              explain: "Two tanks, one rule: + higher → valve open. That's the whole creature." },
            { q: "Raw (no leash), the keeper's valve is always…", kind: "concept", options: ["all the way open or all the way shut", "half open", "leaky", "rusty"], correct: 0,
              explain: "Slam open or slam shut — never in between." },
            { q: "The leash (feedback) works by…", kind: "concept", options: ["showing the keeper their own output in the − tank", "tying the keeper up", "adding a second keeper", "removing a tank"], correct: 0,
              explain: "Comparing input to their own output makes them herd the output until it matches." },
            { q: "A follower's superpower is…", kind: "concept", options: ["copying a whisper with muscle", "making sound", "storing water", "going backwards"], correct: 0,
              explain: "Barely sips from the fragile source, repeats it strongly. Loading problem: solved." },
            { q: "To make the keeper amplify ×2, you…", kind: "concept", options: ["shrink the report to half with a divider", "shout twice", "add a battery", "use two keepers"], correct: 0,
              explain: "If the keeper only hears half, it drives the output double to make the report match!" },
            { q: "The output can never go higher than…", kind: "concept", options: ["the keeper's own supply", "the moon", "the input", "the pipe"], correct: 0,
              explain: "Ask for more and it just maxes out — clipping." },
          ] : [
            { q: "An op-amp's open-loop behavior:", kind: "concept", options: ["any input difference slams the output to a rail", "gain of exactly 2", "output equals V₋", "passes current backwards"], correct: 0,
              explain: "Gain ~10⁵: microvolts of difference saturate it. Useful raw only as a comparator." },
            { q: "Negative feedback connects…", kind: "concept", options: ["the output (or a fraction of it) to the − input", "the output to +", "both inputs together", "the rails together"], correct: 0,
              explain: "Output → V₋ makes errors self-correcting; the loop rests only when V₋ = V₊." },
            { q: "A unity follower has gain 1 and is useful because…", kind: "concept", options: ["it isolates a fragile source from a heavy load", "it amplifies", "it inverts", "it stores charge"], correct: 0,
              explain: "Huge input impedance, tiny output impedance — chapter 11's loading problem, solved." },
            { q: "Non-inverting gain is set by…", kind: "math", options: ["1 + R₁/R₂ in the feedback divider", "the op-amp's β", "supply voltage", "temperature"], correct: 0,
              explain: "Feed back a fraction f and the loop settles at 1/f. Precision from a resistor ratio." },
            { q: "Clipping occurs when…", kind: "concept", options: ["the demanded output exceeds the supply rails", "resistors overheat", "feedback is too strong", "inputs are equal"], correct: 0,
              explain: "The output flat-tops at the rail — the one limit feedback can't negotiate away." },
            { q: "A comparator + a divider reference makes…", kind: "concept", options: ["a threshold detector (thermostat, low-battery flag)", "an oscillator", "a rectifier", "a transformer"], correct: 0,
              explain: "Signal crosses reference → output snaps. The simplest useful 'decision' in electronics." },
            { q: "Why is the op-amp's huge gain never used raw for amplification?", kind: "concept", options: ["feedback trades the excess for precision and stability", "it's too expensive", "it only works on DC", "regulations forbid it"], correct: 0,
              explain: "10⁵ raw gain is wild and drifty; closing the loop with a ratio of two resistors makes it exact and repeatable." },
            { q: "A gain of 1 + R₁/R₂ = 100. If R₂ = 1kΩ, R₁ is roughly…", kind: "math", options: ["99 kΩ", "1 kΩ", "100 Ω", "1 MΩ"], correct: 0,
              explain: "1 + R₁/R₂ = 100 → R₁/R₂ = 99 → R₁ = 99 kΩ." },
          ]} />

        <WhatsNext currentN="12" kids={kids}
          summary={kids
            ? <>The keeper slams (comparator), the leash tames (feedback), and a shrunken report makes whispers into roars (gain). One creature, three superpowers.</>
            : <>Comparator: raw slam. Feedback: V_out herded to match. Gain = 1 + R₁/R₂: chapter 11's divider, repurposed as a precision lie. All analog design is variations on this loop.</>}
          prevHref="chapter11.html" prevLabel="Ch. 11 · The Divider"
          nextHref="chapter13.html" nextLabel="Ch. 13 · The Adding Machine" />
      </main>

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

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