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.
Live preview - click through and answer the questions just like a student would. Auto-grading is on.
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 > 0) or down (a < 0). Try the free dials first — 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 — 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² - 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² - 2x - 8" data-question-key="quad-t2" feedback-correct="x² - 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² - (a+b)x + ab and match it to x² - 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² - 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² - 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 × b = " + (s.a * s.b) + ". To match x² - 2x - 8 you need a + b = 2 and a × 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 — 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’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 — 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>