MathGrades 9-105 pages

Interactive Quadratic Functions Lesson with Live Graphs

A hands-on quadratic functions lesson with live graphs. Tune vertex form to land the vertex at a target, set zeros with factor pairs, read a graph to find its matching equation, and take an auto-graded quiz on parabolas.

Interactive Quadratic Functions Lesson with Live Graphs
Live preview - answer the questions as a student would
Pages
Picture a Parabola

Live preview - click through and answer the questions just like a student would. Auto-grading is on.

What students will learn

  • Recognize a quadratic function from its x² term
  • Explain how a, h, and k shape a parabola in vertex form
  • Read the vertex from vertex form y = a(x - h)² + k
  • Use factor pairs (x - a)(x - b) to find the zeros of a parabola
›View the lesson source

This is the exact lesson code rendered by the interactive player above. It was generated by Glowsmith’s AI from a one-line prompt. Copy it, edit it, and generate your own.

<lesson>
  <page>
    <style>
      *{box-sizing:border-box}
      body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;color:#1f2937;line-height:1.6;max-width:720px;margin:0 auto}
      h1{font-size:1.6rem;color:#0f172a;margin:0 0 4px}
      .sub{color:#64748b;font-size:0.95rem;margin:0 0 18px}
      p{font-size:0.95rem}
      .explain-demo{border:1px solid #dbe2ea;border-radius:16px;background:#f8fafc;padding:14px;margin:14px 0}
      .explain-demo .tag{display:inline-block;font-size:0.7rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:999px;padding:2px 10px;margin-bottom:10px}
      .tb-demo{display:grid;gap:12px}
      .row label{display:flex;justify-content:space-between;font-size:0.85rem;font-weight:600;color:#334155;margin-bottom:4px}
      input[type=range]{width:100%;accent-color:#7c3aed}
      .graphbox{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:6px}
      canvas{width:100%;height:auto;display:block}
      .eq{text-align:center;font-size:1.1rem;font-weight:700;color:#0f172a;margin:6px 0 0}
      .tb-note{text-align:center;font-size:0.9rem;color:#334155;margin:0}
      .readout{display:flex;gap:16px;justify-content:center;font-size:0.9rem;color:#334155;flex-wrap:wrap}
      .readout b{font-size:1.1rem;color:#7c3aed}
      .lead-in{margin-top:18px;padding:10px 14px;border-left:4px solid #2563eb;background:#eff6ff;border-radius:0 10px 10px 0;font-size:0.95rem;color:#1e3a8a}
    </style>
    <h1>Picture a Parabola</h1>
    <p class="sub">Every quadratic graphs as a U-shaped parabola you can steer with three dials.</p>
    <p>In vertex form, <strong>y = a(x - h)² + k</strong>, the vertex (lowest or highest point) is at (h, k), and <strong>a</strong> controls whether it opens up (a &gt; 0) or down (a &lt; 0). Try the free dials first &mdash; not graded.</p>
    <div class="explain-demo">
      <span class="tag">Explore (not graded)</span>
      <div class="tb-demo">
        <div class="row">
          <label>a (opening) <span id="dAv">1.0</span></label>
          <input id="dAS" type="range" min="-3" max="3" step="0.1" value="1">
        </div>
        <div class="row">
          <label>h (left/right) <span id="dHv">0.0</span></label>
          <input id="dHS" type="range" min="-5" max="5" step="0.1" value="0">
        </div>
        <div class="row">
          <label>k (up/down) <span id="dKv">0.0</span></label>
          <input id="dKS" type="range" min="-5" max="5" step="0.1" value="0">
        </div>
        <div class="graphbox">
          <canvas id="dCanvas" width="360" height="260"></canvas>
        </div>
        <div class="readout">
          <span>Vertex <b id="dVertex">(0.0, 0.0)</b></span>
          <span>Opening <b id="dOpen">up</b></span>
        </div>
        <p class="tb-note" id="dNote1">Free play: h slides the curve left/right, k slides it up/down, a flips the opening.</p>
      </div>
    </div>
    <p class="lead-in">Now for the graded run: tune the dials to plant the vertex at (1, 2) opening up. The target is only revealed on submit.</p>
    <task-block title="Put the vertex at (1, 2), opening up" data-question-key="quad-t1" feedback-correct="Perfect vertex! a controls the opening, h slides it left/right, k slides it up/down." feedback-incorrect="The vertex of y = a(x - h)² + k is exactly (h, k). Positive a opens up, bigger |a| gets narrower." submit-label="Grade my curve">
      <div class="tb-demo">
        <div class="eq" id="q1eq">y = 1(x - 0)² + 0</div>
        <div class="row">
          <label>a (opening) <span id="av">1.0</span></label>
          <input id="aS" type="range" min="-3" max="3" step="0.1" value="1">
        </div>
        <div class="row">
          <label>h (left/right) <span id="hv">0.0</span></label>
          <input id="hS" type="range" min="-5" max="5" step="0.1" value="0">
        </div>
        <div class="row">
          <label>k (up/down) <span id="kv">0.0</span></label>
          <input id="kS" type="range" min="-5" max="5" step="0.1" value="0">
        </div>
        <div class="graphbox">
          <canvas id="g1" width="360" height="260"></canvas>
        </div>
        <p class="tb-note" id="n1">Drag the dials and watch the curve chase the target (1, 2).</p>
      </div>
    </task-block>
    <script>
      var X0 = -6, X1 = 6, Y0 = -4, Y1 = 4, W = 360, H = 260;
      var px = function (x) { return (x - X0) / (X1 - X0) * W; };
      var py = function (y) { return (Y1 - y) / (Y1 - Y0) * H; };
      function plot(cv, a, h, k, showTarget, showVertex) {
        if (!cv) return;
        var ctx = cv.getContext("2d");
        ctx.clearRect(0, 0, W, H);
        ctx.strokeStyle = "#e2e8f0";
        ctx.lineWidth = 1;
        ctx.beginPath(); ctx.moveTo(0, py(0)); ctx.lineTo(W, py(0)); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(px(0), 0); ctx.lineTo(px(0), H); ctx.stroke();
        ctx.fillStyle = "#94a3b8";
        ctx.font = "11px system-ui";
        ctx.fillText("x", W - 12, py(0) - 6);
        ctx.fillText("y", px(0) + 6, 12);
        ctx.strokeStyle = "#7c3aed";
        ctx.lineWidth = 2.4;
        ctx.beginPath();
        var step = 0.05;
        var first = true;
        for (var x = X0; x <= X1; x += step) {
          var y = a * (x - h) * (x - h) + k;
          if (y > Y1 + 2 || y < Y0 - 2) { first = true; continue; }
          var Xp = px(x), Yp = py(y);
          if (first) { ctx.moveTo(Xp, Yp); first = false; } else ctx.lineTo(Xp, Yp);
        }
        ctx.stroke();
        if (showTarget) {
          ctx.fillStyle = "#dc2626";
          ctx.beginPath(); ctx.arc(px(1), py(2), 6, 0, Math.PI * 2); ctx.fill();
          ctx.font = "bold 12px system-ui";
          ctx.fillText("target (1, 2)", px(1) + 10, py(2) - 10);
        }
        if (showVertex && h >= X0 && h <= X1 && k >= Y0 && k <= Y1) {
          ctx.fillStyle = "#7c3aed";
          ctx.beginPath(); ctx.arc(px(h), py(k), 5, 0, Math.PI * 2); ctx.fill();
        }
      }
      function updateDemo() {
        var a = +document.getElementById("dAS").value;
        var h = +document.getElementById("dHS").value;
        var k = +document.getElementById("dKS").value;
        var av = document.getElementById("dAv"); if (av) av.textContent = a.toFixed(1);
        var hv = document.getElementById("dHv"); if (hv) hv.textContent = h.toFixed(1);
        var kv = document.getElementById("dKv"); if (kv) kv.textContent = k.toFixed(1);
        var vtx = document.getElementById("dVertex"); if (vtx) vtx.textContent = "(" + h.toFixed(1) + ", " + k.toFixed(1) + ")";
        var opn = document.getElementById("dOpen"); if (opn) opn.textContent = a > 0 ? "up" : (a < 0 ? "down" : "flat");
        plot(document.getElementById("dCanvas"), a, h, k, false, true);
      }
      function currentA() { return +document.getElementById("aS").value; }
      function currentH() { return +document.getElementById("hS").value; }
      function currentK() { return +document.getElementById("kS").value; }
      function update() {
        var a = currentA(), h = currentH(), k = currentK();
        var av = document.getElementById("av"); if (av) av.textContent = a.toFixed(1);
        var hv = document.getElementById("hv"); if (hv) hv.textContent = h.toFixed(1);
        var kv = document.getElementById("kv"); if (kv) kv.textContent = k.toFixed(1);
        var eq = document.getElementById("q1eq");
        if (eq) eq.innerHTML = "y = " + a.toFixed(1) + "(x - " + (h >= 0 ? h.toFixed(1) : "(" + h.toFixed(1) + ")") + ")² + " + k.toFixed(1);
        plot(document.getElementById("g1"), a, h, k, !!window.__modelxSubmitted, false);
      }
      window.__modelxGetTaskState = function () { return { a: currentA(), h: currentH(), k: currentK() }; };
      window.__modelxGradeTask = function (s) {
        if (s.a <= 0) return Math.max(0, Math.round(35 - Math.hypot(s.h - 1, s.k - 2) * 15));
        var d = Math.hypot(s.h - 1, s.k - 2);
        var w = Math.abs(s.a - 1);
        return Math.max(0, Math.round(100 - d * 25 - w * 20));
      };
      window.__modelxOnTaskSubmitted = function (score) {
        window.__modelxSubmitted = true;
        update();
        var note = document.getElementById("n1");
        var s = { a: currentA(), h: currentH(), k: currentK() };
        if (note) note.innerHTML = score >= 100 ? "Your vertex is at (" + s.h.toFixed(1) + ", " + s.k.toFixed(1) + ") with a = " + s.a.toFixed(1) + ". The target (1, 2) is marked in red." : "Your vertex landed at (" + s.h.toFixed(1) + ", " + s.k.toFixed(1) + "). Remember: h is the x of the vertex, k is the y, and the vertex of the target is (1, 2) - red dot.";
        ["aS", "hS", "kS"].forEach(function (id) { var el = document.getElementById(id); if (el) el.disabled = true; });
      };
      document.addEventListener("input", function (e) {
        var id = e.target.id;
        if (id === "dAS" || id === "dHS" || id === "dKS") updateDemo();
        if (id === "aS" || id === "hS" || id === "kS") update();
      });
      updateDemo();
      setInterval(function () {
        var cv = document.getElementById("dCanvas");
        if (cv) updateDemo();
      }, 250);
    </script>
  </page>
  <page>
    <style>
      *{box-sizing:border-box}
      body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;color:#1f2937;line-height:1.6;max-width:720px;margin:0 auto}
      h1{font-size:1.6rem;color:#0f172a;margin:0 0 4px}
      .sub{color:#64748b;font-size:0.95rem;margin:0 0 18px}
      p{font-size:0.95rem}
      .explain-demo{border:1px solid #dbe2ea;border-radius:16px;background:#f8fafc;padding:14px;margin:14px 0}
      .explain-demo .tag{display:inline-block;font-size:0.7rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:999px;padding:2px 10px;margin-bottom:10px}
      .tb-demo{display:grid;gap:12px}
      .row label{display:flex;justify-content:space-between;font-size:0.85rem;font-weight:600;color:#334155;margin-bottom:4px}
      input[type=range]{width:100%;accent-color:#0891b2}
      .graphbox{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:6px}
      canvas{width:100%;height:auto;display:block}
      .eq{text-align:center;font-size:1.05rem;font-weight:700;color:#0f172a;margin:6px 0 0}
      .tb-note{text-align:center;font-size:0.9rem;color:#334155;margin:0}
      .readout{display:flex;gap:16px;justify-content:center;font-size:0.9rem;color:#334155;flex-wrap:wrap}
      .readout b{font-size:1.1rem;color:#0891b2}
      .lead-in{margin-top:18px;padding:10px 14px;border-left:4px solid #2563eb;background:#eff6ff;border-radius:0 10px 10px 0;font-size:0.95rem;color:#1e3a8a}
    </style>
    <h1>Zeros: Where the Parabola Meets the Ground</h1>
    <p class="sub">A parabola written as y = (x - a)(x - b) crosses the x-axis at a and b.</p>
    <p>Those crossing points are the <strong>zeros</strong> (roots). Play with the free sliders &mdash; each factor hands you one zero.</p>
    <div class="explain-demo">
      <span class="tag">Explore (not graded)</span>
      <div class="tb-demo">
        <div class="row">
          <label>zero 1 (a) <span id="dZ1v">0</span></label>
          <input id="dZ1S" type="range" min="-6" max="6" value="0">
        </div>
        <div class="row">
          <label>zero 2 (b) <span id="dZ2v">0</span></label>
          <input id="dZ2S" type="range" min="-6" max="6" value="0">
        </div>
        <div class="graphbox">
          <canvas id="dCanvas2" width="360" height="260"></canvas>
        </div>
        <div class="readout">
          <span>Zeros <b id="dZeros">0, 0</b></span>
        </div>
        <p class="tb-note" id="dNote2">Free play: slide each zero and watch where the curve pokes through the x-axis.</p>
      </div>
    </div>
    <p class="lead-in">Now the challenge: this parabola has the equation <strong>y = x&sup2; - 2x - 8</strong>. Set the two factors so your curve matches it exactly. The target zeros are only revealed on submit.</p>
    <task-block title="Factor y = x&sup2; - 2x - 8" data-question-key="quad-t2" feedback-correct="x&sup2; - 2x - 8 factors to (x - 4)(x + 2), so the curve crosses the x-axis at 4 and -2. Matching the coefficients cracked it!" feedback-incorrect="Expand (x - a)(x - b) = x&sup2; - (a+b)x + ab and match it to x&sup2; - 2x - 8: you need a + b = 2 and ab = -8." submit-label="Grade my factors">
      <div class="tb-demo">
        <div class="eq" id="q2eq">y = (x - 0)(x - 0)</div>
        <div class="row">
          <label>factor 1 (a) <span id="z1v">0</span></label>
          <input id="z1S" type="range" min="-6" max="6" value="0">
        </div>
        <div class="row">
          <label>factor 2 (b) <span id="z2v">0</span></label>
          <input id="z2S" type="range" min="-6" max="6" value="0">
        </div>
        <div class="graphbox">
          <canvas id="g2" width="360" height="260"></canvas>
        </div>
        <p class="tb-note" id="n2">Your curve must equal y = x&sup2; - 2x - 8. Expand (x - a)(x - b) and match the coefficients to find a and b, then submit.</p>
      </div>
    </task-block>
    <script>
      var X0 = -6, X1 = 6, Y0 = -4, Y1 = 4, W = 360, H = 260;
      var px = function (x) { return (x - X0) / (X1 - X0) * W; };
      var py = function (y) { return (Y1 - y) / (Y1 - Y0) * H; };
      function plot2(cv, a, b) {
        if (!cv) return;
        var ctx = cv.getContext("2d");
        ctx.clearRect(0, 0, W, H);
        ctx.strokeStyle = "#e2e8f0";
        ctx.lineWidth = 1;
        ctx.beginPath(); ctx.moveTo(0, py(0)); ctx.lineTo(W, py(0)); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(px(0), 0); ctx.lineTo(px(0), H); ctx.stroke();
        ctx.strokeStyle = "#0891b2";
        ctx.lineWidth = 2.4;
        ctx.beginPath();
        var first = true;
        for (var x = X0; x <= X1; x += 0.05) {
          var y = (x - a) * (x - b);
          if (y > Y1 + 2 || y < Y0 - 2) { first = true; continue; }
          if (first) { ctx.moveTo(px(x), py(y)); first = false; } else ctx.lineTo(px(x), py(y));
        }
        ctx.stroke();
        if (a >= X0 && a <= X1) { ctx.beginPath(); ctx.arc(px(a), py(0), 4, 0, Math.PI * 2); ctx.fill(); }
        if (b >= X0 && b <= X1) { ctx.beginPath(); ctx.arc(px(b), py(0), 4, 0, Math.PI * 2); ctx.fill(); }
      }
      function updateDemo2() {
        var a = +document.getElementById("dZ1S").value;
        var b = +document.getElementById("dZ2S").value;
        var z1 = document.getElementById("dZ1v"); if (z1) z1.textContent = a;
        var z2 = document.getElementById("dZ2v"); if (z2) z2.textContent = b;
        var zr = document.getElementById("dZeros"); if (zr) zr.textContent = a + ", " + b;
        plot2(document.getElementById("dCanvas2"), a, b);
      }
      function curA() { return +document.getElementById("z1S").value; }
      function curB() { return +document.getElementById("z2S").value; }
      function update2() {
        var a = curA(), b = curB();
        var z1 = document.getElementById("z1v"); if (z1) z1.textContent = a;
        var z2 = document.getElementById("z2v"); if (z2) z2.textContent = b;
        var eq = document.getElementById("q2eq");
        if (eq) eq.textContent = "y = (x - " + a + ")(x - " + b + ")";
        plot2(document.getElementById("g2"), a, b);
      }
      window.__modelxGetTaskState = function () { return { a: curA(), b: curB() }; };
      window.__modelxGradeTask = function (s) {
        var sum = s.a + s.b, prod = s.a * s.b;
        var err = Math.abs(sum - 2) + Math.abs(prod + 8);
        if (err < 0.2) return 100;
        return Math.max(0, Math.round(100 - err * 30));
      };
      window.__modelxOnTaskSubmitted = function (score) {
        var note = document.getElementById("n2");
        var s = { a: curA(), b: curB() };
        if (note) note.innerHTML = score >= 100 ? "x&sup2; - 2x - 8 = (x - 4)(x + 2), so the curve crosses the x-axis at 4 and -2. You set a = " + s.a + " and b = " + s.b + " - the coefficients match!" : "Your factors give a + b = " + (s.a + s.b) + " and a &times; b = " + (s.a * s.b) + ". To match x&sup2; - 2x - 8 you need a + b = 2 and a &times; b = -8.";
        ["z1S", "z2S"].forEach(function (id) { var el = document.getElementById(id); if (el) el.disabled = true; });
      };
      document.addEventListener("input", function (e) {
        var id = e.target.id;
        if (id === "dZ1S" || id === "dZ2S") updateDemo2();
        if (id === "z1S" || id === "z2S") update2();
      });
      updateDemo2();
      setInterval(function () {
        var cv = document.getElementById("dCanvas2");
        if (cv) updateDemo2();
      }, 250);
    </script>
  </page>
  <page>
    <style>
      *{box-sizing:border-box}
      body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;color:#1f2937;line-height:1.6;max-width:720px;margin:0 auto}
      h1{font-size:1.6rem;color:#0f172a;margin:0 0 4px}
      .sub{color:#64748b;font-size:0.95rem;margin:0 0 18px}
      p{font-size:0.95rem}
      .explain-demo{border:1px solid #dbe2ea;border-radius:16px;background:#f8fafc;padding:14px;margin:14px 0}
      .explain-demo .tag{display:inline-block;font-size:0.7rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:999px;padding:2px 10px;margin-bottom:10px}
      .tb-demo{display:grid;gap:12px}
      .opts{display:grid;grid-template-columns:1fr;gap:10px}
      .opt{border:1px solid #e2e8f0;border-radius:12px;background:#fff;padding:14px;font-size:1rem;font-weight:600;color:#334155;cursor:pointer;transition:transform .15s ease,border-color .15s ease;text-align:left}
      .opt:hover{transform:translateY(-1px);border-color:#99f6e4}
      .opt.on{border-color:#0d9488;background:#f0fdfa;color:#0f766e}
      .opt:disabled{opacity:.55;cursor:not-allowed}
      .graphbox{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:6px}
      canvas{width:100%;height:auto;display:block}
      .tb-note{text-align:center;font-size:0.9rem;color:#334155;margin:0}
      .lead-in{margin-top:18px;padding:10px 14px;border-left:4px solid #2563eb;background:#eff6ff;border-radius:0 10px 10px 0;font-size:0.95rem;color:#1e3a8a}
    </style>
    <h1>Choose the Equation That Fits the Zeros</h1>
    <p class="sub">Same idea in reverse: zeros first, equation second.</p>
    <p>A parabola has zeros at <strong>x = 3</strong> and <strong>x = -1</strong>. Which equation produces those zeros? First try the free version with different targets &mdash; the answer appears instantly.</p>
    <div class="explain-demo">
      <span class="tag">Explore (not graded)</span>
      <div class="tb-demo">
        <div class="graphbox">
          <canvas id="dCanvas3" width="360" height="220"></canvas>
        </div>
        <div class="opts">
          <button class="opt" id="dA3" type="button">y = (x - 2)(x + 2)</button>
          <button class="opt" id="dB3" type="button">y = (x + 2)(x + 2)</button>
          <button class="opt" id="dC3" type="button">y = (x - 2)(x - 2)</button>
          <button class="opt" id="dD3" type="button">y = (x - 2)(x + 1)</button>
        </div>
        <p class="tb-note" id="dNote3">Free play: tap each equation to graph it. Red dots = the target zeros (2 and -2).</p>
      </div>
    </div>
    <p class="lead-in">Now the real one: which equation has zeros at 3 and -1?</p>
    <task-block title="Pick the matching equation" expected='{"pick":"b"}' data-question-key="quad-t3" feedback-correct="Correct! (x - 3)(x + 1) = 0 when x = 3 or x = -1." feedback-incorrect="Plug in x = 3 and x = -1. The right factor pair makes both sides equal zero." submit-label="Check my pick">
      <div class="tb-demo">
        <div class="opts">
          <button class="opt" id="qA" type="button">y = (x + 3)(x + 1)</button>
          <button class="opt" id="qB" type="button">y = (x - 3)(x + 1)</button>
          <button class="opt" id="qC" type="button">y = (x + 3)(x - 1)</button>
          <button class="opt" id="qD" type="button">y = (x - 3)(x - 1)</button>
        </div>
        <p class="tb-note" id="n3">Zeros at 3 and -1. Each factor (x - zero) turns to zero at its own x - which pair of factors gives both?</p>
      </div>
    </task-block>
    <script>
      var px3 = function (x) { return (x + 5) / 10 * 320; };
      var py3 = function (y) { return (5 - y) / 10 * 190; };
      function plot3(cv, a, b, targetA, targetB) {
        if (!cv) return;
        var ctx = cv.getContext("2d");
        ctx.clearRect(0, 0, 320, 190);
        ctx.strokeStyle = "#e2e8f0";
        ctx.beginPath(); ctx.moveTo(0, py3(0)); ctx.lineTo(320, py3(0)); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(px3(0), 0); ctx.lineTo(px3(0), 190); ctx.stroke();
        if (targetA !== null && targetB !== null) {
          ctx.fillStyle = "#dc2626";
          ctx.font = "12px system-ui";
          ctx.beginPath(); ctx.arc(px3(targetA), py3(0), 5, 0, Math.PI * 2); ctx.fill();
          ctx.beginPath(); ctx.arc(px3(targetB), py3(0), 5, 0, Math.PI * 2); ctx.fill();
        }
        ctx.strokeStyle = "#0d9488";
        ctx.lineWidth = 2.4;
        ctx.beginPath();
        for (var x = -5; x <= 5; x += 0.05) {
          var y = (x - a) * (x - b);
          if (y > 5 || y < -5) continue;
          if (x === -5) ctx.moveTo(px3(x), py3(y)); else ctx.lineTo(px3(x), py3(y));
        }
        ctx.stroke();
        if (a >= -5 && a <= 5) { ctx.beginPath(); ctx.arc(px3(a), py3(0), 4, 0, Math.PI * 2); ctx.fill(); }
        if (b >= -5 && b <= 5) { ctx.beginPath(); ctx.arc(px3(b), py3(0), 4, 0, Math.PI * 2); ctx.fill(); }
      }
      var DEMO3 = { dA3: [2, -2], dB3: [-2, -2], dC3: [2, 2], dD3: [2, -1] };
      var demo3Picked = null;
      function paintDemo3(id) {
        demo3Picked = id;
        var pair = DEMO3[id];
        plot3(document.getElementById("dCanvas3"), pair[0], pair[1], 2, -2);
        var note = document.getElementById("dNote3");
        if (note) note.textContent = "That curve has zeros " + pair[0] + " and " + pair[1] + ". Keep tapping until one passes through both red dots.";
      }
      function draw3() { return; }
      window.__modelxTaskState = { pick: "" };
      window.__modelxGetTaskState = function () { return window.__modelxTaskState; };
      window.__modelxGradeTask = function (s) { return s.pick === "b" ? 100 : 0; };
      window.__modelxOnTaskSubmitted = function (score) {
        var note = document.getElementById("n3");
        if (note) note.innerHTML = score >= 100 ? "Yes - (x - 3)(x + 1) = x² - 2x - 3, and both factors vanish at the zeros." : "Substitute x = 3: only (x - 3) makes the product zero. The other factor must vanish at x = -1, so it is (x + 1).";
        ["qA", "qB", "qC", "qD"].forEach(function (id) { var el = document.getElementById(id); if (el) el.disabled = true; });
      };
      document.addEventListener("click", function (e) {
        var id = e.target.id;
        if (DEMO3[id]) {
          paintDemo3(id);
          ["dA3", "dB3", "dC3", "dD3"].forEach(function (i) { var el = document.getElementById(i); if (el) el.classList.toggle("on", i === id); });
          return;
        }
        var map = { qA: "a", qB: "b", qC: "c", qD: "d" };
        if (map[id]) {
          window.__modelxTaskState.pick = map[id];
          ["qA", "qB", "qC", "qD"].forEach(function (i) { var el = document.getElementById(i); if (el) el.classList.toggle("on", i === id); });
          var n3 = document.getElementById("n3");
          if (n3) n3.textContent = "Picked. Submit to check.";
        }
      });
      paintDemo3("dA3");
    </script>
  </page>
  <page>
    <style>
      *{box-sizing:border-box}
      body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;color:#1f2937;line-height:1.6;max-width:720px;margin:0 auto}
      h1{font-size:1.6rem;color:#0f172a;margin:0 0 4px}
      .sub{color:#64748b;font-size:0.95rem;margin:0 0 18px}
      p{font-size:0.95rem}
      .explain-demo{border:1px solid #dbe2ea;border-radius:16px;background:#f8fafc;padding:14px;margin:14px 0}
      .explain-demo .tag{display:inline-block;font-size:0.7rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:999px;padding:2px 10px;margin-bottom:10px}
      .tb-demo{display:grid;gap:12px}
      .opts{display:grid;grid-template-columns:1fr;gap:10px}
      .opt{border:1px solid #e2e8f0;border-radius:12px;background:#fff;padding:14px;font-size:1rem;font-weight:600;color:#334155;cursor:pointer;transition:transform .15s ease,border-color .15s ease;text-align:left}
      .opt:hover{transform:translateY(-1px);border-color:#fda4af}
      .opt.on{border-color:#e11d48;background:#fff1f2;color:#be123c}
      .opt:disabled{opacity:.55;cursor:not-allowed}
      .graphbox{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:6px}
      canvas{width:100%;height:auto;display:block}
      .tb-note{text-align:center;font-size:0.9rem;color:#334155;margin:0}
      .lead-in{margin-top:18px;padding:10px 14px;border-left:4px solid #2563eb;background:#eff6ff;border-radius:0 10px 10px 0;font-size:0.95rem;color:#1e3a8a}
    </style>
    <h1>Read the Graph, Find the Vertex</h1>
    <p class="sub">The vertex is the curve&rsquo;s turning point - read it straight off the axes.</p>
    <p>First practice free: tap each equation to see its graph. The red dot marks the target peak &mdash; different numbers than the challenge.</p>
    <div class="explain-demo">
      <span class="tag">Explore (not graded)</span>
      <div class="tb-demo">
        <div class="graphbox">
          <canvas id="dCanvas4" width="360" height="240"></canvas>
        </div>
        <div class="opts">
          <button class="opt" id="dA4" type="button">y = (x - 2)² - 1</button>
          <button class="opt" id="dB4" type="button">y = -(x - 2)² - 1</button>
          <button class="opt" id="dC4" type="button">y = -(x + 2)² - 1</button>
          <button class="opt" id="dD4" type="button">y = -(x - 2)² + 1</button>
        </div>
        <p class="tb-note" id="dNote4">Free play: tap each equation. The red dot is the target peak at (2, -1).</p>
      </div>
    </div>
    <p class="lead-in">Now read this graph and pick the equation that matches it.</p>
    <task-block title="Which equation matches this graph?" expected='{"pick":"c"}' data-question-key="quad-t4" feedback-correct="Right! Vertex at (-2, 1), opens down, so y = -(x + 2)² + 1." feedback-incorrect="Check the peak: the vertex is (-2, 1). An upside-down curve needs a negative a." submit-label="Check my pick">
      <div class="tb-demo">
        <div class="graphbox">
          <canvas id="g4" width="360" height="240"></canvas>
        </div>
        <div class="opts">
          <button class="opt" id="pA" type="button">y = (x - 2)² + 1</button>
          <button class="opt" id="pB" type="button">y = -(x - 2)² + 1</button>
          <button class="opt" id="pC" type="button">y = -(x + 2)² + 1</button>
          <button class="opt" id="pD" type="button">y = -(x + 2)² - 1</button>
        </div>
        <p class="tb-note" id="n4">Where is the peak? Up or down? Shift left or right?</p>
      </div>
    </task-block>
    <script>
      var px4 = function (x) { return (x + 5) / 10 * 320; };
      var py4 = function (y) { return (4 - y) / 8 * 210; };
      function plot4(cv, a, h, k, targetH, targetK, labelVertex) {
        if (!cv) return;
        var ctx = cv.getContext("2d");
        ctx.clearRect(0, 0, 320, 210);
        ctx.strokeStyle = "#e2e8f0";
        ctx.beginPath(); ctx.moveTo(0, py4(0)); ctx.lineTo(320, py4(0)); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(px4(0), 0); ctx.lineTo(px4(0), 210); ctx.stroke();
        if (targetH !== null) {
          ctx.fillStyle = "#dc2626";
          ctx.font = "12px system-ui";
          ctx.beginPath(); ctx.arc(px4(targetH), py4(targetK), 5, 0, Math.PI * 2); ctx.fill();
        }
        ctx.strokeStyle = "#e11d48";
        ctx.lineWidth = 2.4;
        ctx.beginPath();
        for (var x = -5; x <= 5; x += 0.05) {
          var y = a * (x - h) * (x - h) + k;
          if (y > 4 || y < -4) continue;
          if (x === -5) ctx.moveTo(px4(x), py4(y)); else ctx.lineTo(px4(x), py4(y));
        }
        ctx.stroke();
        if (labelVertex) {
          ctx.fillStyle = "#e11d48";
          ctx.beginPath(); ctx.arc(px4(h), py4(k), 4, 0, Math.PI * 2); ctx.fill();
          ctx.font = "11px system-ui";
          ctx.fillText("vertex (" + h + ", " + k + ")", px4(h) + 8, py4(k) + 14);
        }
      }
      var DEMO4 = { dA4: [1, 2, -1], dB4: [-1, 2, -1], dC4: [-1, -2, -1], dD4: [-1, 2, 1] };
      function paintDemo4(id) {
        var v = DEMO4[id];
        plot4(document.getElementById("dCanvas4"), v[0], v[1], v[2], 2, -1, true);
        var note = document.getElementById("dNote4");
        if (note) note.textContent = "That curve peaks at (" + v[1] + ", " + v[2] + "). Which one hits the red target dot at (2, -1)?";
      }
      function draw4() {
        var cv = document.getElementById("g4");
        if (!cv) return;
        plot4(cv, -1, -2, 1, null, null, false);
      }
      window.__modelxTaskState = { pick: "" };
      window.__modelxGetTaskState = function () { return window.__modelxTaskState; };
      window.__modelxGradeTask = function (s) { return s.pick === "c" ? 100 : 0; };
      window.__modelxOnTaskSubmitted = function (score) {
        var note = document.getElementById("n4");
        if (note) note.innerHTML = score >= 100 ? "Yes. Vertex form y = a(x - h)² + k has its vertex at (h, k). Here a = -1 (down), h = -2, k = 1." : "The peak is at (-2, 1), so h = -2 means (x + 2) inside, k = 1, and opening down means a is negative.";
        ["pA", "pB", "pC", "pD"].forEach(function (id) { var el = document.getElementById(id); if (el) el.disabled = true; });
      };
      document.addEventListener("click", function (e) {
        var id = e.target.id;
        if (DEMO4[id]) {
          paintDemo4(id);
          ["dA4", "dB4", "dC4", "dD4"].forEach(function (i) { var el = document.getElementById(i); if (el) el.classList.toggle("on", i === id); });
          return;
        }
        var map = { pA: "a", pB: "b", pC: "c", pD: "d" };
        if (map[id]) {
          window.__modelxTaskState.pick = map[id];
          ["pA", "pB", "pC", "pD"].forEach(function (i) { var el = document.getElementById(i); if (el) el.classList.toggle("on", i === id); });
          var n4 = document.getElementById("n4");
          if (n4) n4.textContent = "Picked. Submit to check.";
        }
      });
      paintDemo4("dB4");
      setInterval(function () {
        var cv = document.getElementById("g4");
        if (cv) draw4();
      }, 250);
    </script>
  </page>
  <page>
    <style>
      *{box-sizing:border-box}
      body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;color:#1f2937;line-height:1.6;max-width:720px;margin:0 auto}
      h1{font-size:1.6rem;color:#0f172a;margin:0 0 4px}
      .sub{color:#64748b;font-size:0.95rem;margin:0 0 18px}
      p{font-size:0.95rem}
      .sum{border:1px solid #dbe2ea;border-radius:14px;background:#f8fafc;padding:16px;font-size:0.95rem;color:#334155}
      .sum b{color:#0f172a}
    </style>
    <h1>Zeros, Vertices, and Smiles</h1>
    <p class="sub">Four questions on zeros, vertices, and openings.</p>
    <div class="sum">
      <p><b>Vertex form</b>: y = a(x - h)² + k, vertex at (h, k).<br>
      <b>Zeros</b>: y = (x - a)(x - b) crosses the x-axis at a and b.<br>
      <b>a</b> flips the opening: positive smiles, negative frowns.</p>
    </div>
    <question-block question="For y = 3(x - 4)² + 5, the vertex is..." answers="(3, 4)|(4, 5)|(-4, 5)|(4, -5)" correct="(4, 5)" mode="single" feedback-correct="Correct! The vertex is at (h, k) = (4, 5)." feedback-incorrect="Read h and k directly out of y = a(x - h)² + k." data-question-key="quad-q1"></question-block>
    <question-block question="The equation y = (x - 2)(x + 5) has zeros at..." answers="x = 2 and x = -5|x = -2 and x = 5|x = 2 and x = 5|x = -2 and x = -5" correct="x = 2 and x = -5" mode="single" feedback-correct="Right! Each factor is zero at its own root." feedback-incorrect="Set each factor equal to zero: (x - 2) = 0 and (x + 5) = 0." data-question-key="quad-q2"></question-block>
    <question-block question="Which value of a makes the parabola open downward?" answers="a = 2|a = -2|a = 0|a = 1" correct="a = -2" mode="single" feedback-correct="Correct! A negative a flips the parabola upside down." feedback-incorrect="Look at the sign: positive a smiles, negative a frowns." data-question-key="quad-q3"></question-block>
    <dropdown-question text="For y = (x + 3)² - 2, the vertex is at (__blank__, -2)." options="-3|3|0|2" correct="-3" feedback-correct="Correct! h = -3, k = -2." feedback-incorrect="The vertex form is y = a(x - h)² + k, so (x + 3) means h = -3." data-question-key="quad-q4"></dropdown-question>
    <script></script>
  </page>
</lesson>