// maths-brevet-helpers.jsx // Math-specific reusable building blocks for the Brevet pack. // All composed on top of locked V1.2 blocks. No page chrome — these are // inner components that go *inside* PageShell / Box / ExerciseCard. const MB_F = React.Fragment; /* ──────────────────────────────────────────────── MathExpr — display math (lightweight, print-safe) ──────────────────────────────────────────────── */ function MathExpr({ children, size = "0.16in", display = false }) { return ( {children} ); } // Fraction (numerator over denominator) — rendered with stacked divs function Frac({ num, den, size = "0.14in" }) { return ( {num} {den} ); } // Square root function Sqrt({ children, size = "0.14in" }) { return ( {children} ); } // Power function Pow({ base, exp, size = "0.14in" }) { return ( {base} {exp} ); } /* ──────────────────────────────────────────────── StepBlock — aligned calculation steps « = 3x + 12 — 5 » with leading "=" indented ──────────────────────────────────────────────── */ function StepBlock({ steps = [], hint, tone = "neutral" }) { const bg = tone === "cool" ? "var(--ae-primary-soft)" : "white"; return (
{steps.map((s, i) => (
{i === 0 ? "" : "="} {s.expr} {s.note && {s.note}}
))} {hint &&
{hint}
}
); } /* ──────────────────────────────────────────────── GridCalc — grid of small calc cells ──────────────────────────────────────────────── */ function GridCalc({ items = [], cols = 3, withResult = true }) { return (
{items.map((it, i) => (
{i + 1} {it} {withResult && = } {withResult && }
))}
); } /* ──────────────────────────────────────────────── QCMRow — single MCQ question with A/B/C ──────────────────────────────────────────────── */ function QCMRow({ n, q, options = [] }) { return (
{n}
{q}
{options.map((opt, i) => ( {String.fromCharCode(65 + i)} {opt} ))}
); } /* ──────────────────────────────────────────────── ProportionalityTable — 2-row table with coefficient ──────────────────────────────────────────────── */ function ProportionalityTable({ headers = ["Quantité", "Prix"], cols = [], coef }) { return (
{/* header row */}
{headers[0]}
{cols.map((c, i) => (
{c.x ?? "…"}
))} {/* second row */}
{headers[1]}
{cols.map((c, i) => (
{c.y ?? }
))}
{coef !== undefined && (
Coefficient de proportionnalité : × {coef === null ? : coef}
)}
); } const propCellHead = { padding: "0.07in 0.1in", fontFamily: "var(--ae-font-title)", fontWeight: 600, textAlign: "center", color: "var(--ae-primary-deep)", fontStyle: "normal", }; const propCell = { padding: "0.08in 0.1in", textAlign: "center", fontFamily: '"Cambria Math", Georgia, serif', fontStyle: "italic", }; /* ──────────────────────────────────────────────── AxisGrid — small repère orthonormé for fonctions ──────────────────────────────────────────────── */ function AxisGrid({ width = "2.6in", height = "2.2in", points = [], plotF, xMin = -3, xMax = 4, yMin = -2, yMax = 5 }) { const W = 260, H = 220; const px = x => 30 + ((x - xMin) / (xMax - xMin)) * (W - 40); const py = y => H - 25 - ((y - yMin) / (yMax - yMin)) * (H - 40); const xs = []; for (let x = xMin; x <= xMax; x++) xs.push(x); const ys = []; for (let y = yMin; y <= yMax; y++) ys.push(y); let path = ""; if (plotF) { const N = 80; for (let i = 0; i <= N; i++) { const x = xMin + (i / N) * (xMax - xMin); const y = plotF(x); const cmd = i === 0 ? "M" : "L"; path += `${cmd}${px(x).toFixed(1)},${py(y).toFixed(1)} `; } } return ( {/* gridlines */} {xs.map(x => )} {ys.map(y => )} {/* axes */} {/* arrows */} {/* tick labels */} {xs.filter(x => x !== 0).map(x => {x})} {ys.filter(y => y !== 0).map(y => {y})} x y {/* curve */} {path && } {/* points */} {points.map((p, i) => ( {p.label && {p.label}} ))} ); } /* ──────────────────────────────────────────────── BarChart — small vertical bar chart for stats ──────────────────────────────────────────────── */ function BarChart({ data = [], width = "2.8in", height = "1.8in", yMax }) { const W = 280, H = 180; const max = yMax ?? Math.max(...data.map(d => d.v)); const bw = (W - 40) / data.length; return ( {[0.25, 0.5, 0.75, 1].map((f, i) => ( {Math.round(max * f)} ))} {data.map((d, i) => { const h = (d.v / max) * (H - 40); return ( {d.label} {d.v} ); })} ); } /* ──────────────────────────────────────────────── TriangleFigure — generic SVG for Pythagore/Thalès/Trigo variant: "pythagoras" | "pythagoras-converse" | "thales" | "trigonometry" ──────────────────────────────────────────────── */ function TriangleFigure({ variant = "pythagoras", labels = {}, width = "2.8in", height = "2in" }) { const W = 280, H = 200; // Standard right triangle, right angle at C (bottom-left) const A = { x: 50, y: 30 }; const B = { x: 240, y: 160 }; const C = { x: 50, y: 160 }; const RightAngleSquare = ({ at, dir = "br" }) => { const s = 12; const dx = dir.includes("r") ? s : -s; const dy = dir.includes("b") ? -s : s; return ; }; if (variant === "thales") { // Two intersecting lines crossed by parallels — Thalès classic const O = { x: 50, y: 175 }; const M = { x: 130, y: 100 }; const A2 = { x: 220, y: 25 }; const N = { x: 175, y: 145 }; const B2 = { x: 260, y: 130 }; return ( {[[O,"O"],[M,"M"],[N,"N"],[A2,"A"],[B2,"B"]].map(([p,l],i) => ( {l} ))} ); } // Right triangle figure — used for Pythagore, converse, trigo const isTri = variant === "trigonometry"; return ( {/* triangle */} {/* vertex labels */} A B C {/* side labels */} {labels.AB ?? "?"} {labels.AC ?? ""} {labels.BC ?? ""} {/* angle marker for trigo */} {isTri && ( {labels.angle ?? "α"} )} ); } /* ──────────────────────────────────────────────── ScoreCells — score boxes for graded exercises ──────────────────────────────────────────────── */ function ScoreCells({ items = [] }) { return (
{items.map((it, i) => (
{it.label} {it.value ?? "\u00A0"} / {it.max}
))}
); } Object.assign(window, { MathExpr, Frac, Sqrt, Pow, StepBlock, GridCalc, QCMRow, ProportionalityTable, AxisGrid, BarChart, TriangleFigure, ScoreCells, });