// charte-blocks.jsx — V1.1 LOCKED COMPONENT LIBRARY // Every page in the collection composes these blocks. Pages MUST NOT // invent their own header / footer / box / spacing — only insert blocks // inside 's content area. Blocks have fixed widths (full grid // row), fixed inner padding, and rely on PageShell's --ae-block-gap for // vertical rhythm. // // Naming follows the brief: PageShell, QRBlock, KettyBubble, ObjectiveBox, // LessonBox, ExerciseCard, MethodSteps, SelfCheck, ScoreBox, WarningBox, // ChallengeBox, TableBlock, FigureBlock. /* ───────────────────────────────────────────────────────────────────── PageShell — the ONLY way to start a page. Wraps Header + content + Footer. ───────────────────────────────────────────────────────────────────── */ function PageShell({ matiere, niveau, page, density = "std", // V1.2 — official QR props (production-ready) qrVisible = true, scoreVisible = true, qrSlug, qrUrl, qrLabel = "Corriger avec Ketty", qrSubLabel, scale = 1, // V1.2.2 — audit mode (renderer PDF / QA toggle). Surfaces overflow as // a blocking error instead of silently clipping the page. audit = false, // V1.2.2 — split-page indicator (e.g. {n:1, total:2}) for content that // legitimately spans two pages. Renders a small badge in the header pill. splitOf, children, }) { return (
{children}
); } /* ───────────────────────────────────────────────────────────────────── PageTitle — always the first block of any page. Unique typo, no padding. ───────────────────────────────────────────────────────────────────── */ function PageTitle({ eyebrow, title, subtitle }) { return (
{eyebrow &&
{eyebrow}
}
{title}
{subtitle &&
{subtitle}
}
); } /* ───────────────────────────────────────────────────────────────────── Generic Box — backbone of all framed blocks. Don't use directly in a page. ───────────────────────────────────────────────────────────────────── */ function Box({ tone = "neutral", label, iconKey, icon, children, dashed = false, className = "" }) { // V1.2 — prefer iconKey (SVG registry). `icon` kept as fallback for legacy strings. const cls = [ "bk-box", `tone-${tone}`, dashed && "is-dashed", label && "has-tab", className, ].filter(Boolean).join(" "); const iconNode = iconKey ? : (icon ? {icon} : null); return (
{label && (
{iconNode && {iconNode}} {label}
)}
{children}
); } /* ───────────────────────────────────────────────────────────────────── ObjectiveBox — "Objectifs / Compétences" — primaire & collège ───────────────────────────────────────────────────────────────────── */ function ObjectiveBox({ title = "Objectifs", items = [], iconKey = "icon-cible" }) { return (
    {items.map((it, i) =>
  • {it}
  • )}
); } /* ───────────────────────────────────────────────────────────────────── LessonBox — "Je retiens / Cours" — fond jaune ───────────────────────────────────────────────────────────────────── */ function LessonBox({ title = "Je retiens", iconKey = "icon-ampoule", children, formula }) { return (
{children}
{formula &&
{formula}
}
); } /* ───────────────────────────────────────────────────────────────────── ExerciseCard — exercice structuré : numéro, consigne, contenu ───────────────────────────────────────────────────────────────────── */ function ExerciseCard({ number = 1, title = "Exercice", consigne, tone = "primary", iconKey = "icon-crayon", children }) { return ( {consigne &&
{consigne}
}
{children}
); } /* ───────────────────────────────────────────────────────────────────── MethodSteps — méthode pas à pas (vert) ───────────────────────────────────────────────────────────────────── */ function MethodSteps({ title = "Méthode", steps = [], iconKey = "icon-check" }) { return (
    {steps.map((s, i) => (
  1. {i + 1}{s}
  2. ))}
); } /* ───────────────────────────────────────────────────────────────────── WarningBox — "Erreur à éviter / Attention" ───────────────────────────────────────────────────────────────────── */ function WarningBox({ title = "Erreur à éviter", children, iconKey = "icon-attention" }) { return ( {children} ); } /* ───────────────────────────────────────────────────────────────────── ChallengeBox — "Défi Ketty / Pour aller plus loin" ───────────────────────────────────────────────────────────────────── */ function ChallengeBox({ title = "Défi Ketty", children, iconKey = "icon-etoile" }) { return ( {children} ); } /* ───────────────────────────────────────────────────────────────────── SelfCheck — checklist "Je vérifie" ───────────────────────────────────────────────────────────────────── */ function SelfCheck({ title = "Je vérifie", items = [], iconKey = "icon-check" }) { return (
    {items.map((it, i) => (
  • {it}
  • ))}
); } /* ───────────────────────────────────────────────────────────────────── ScoreBox — bilan / score d'auto-évaluation ───────────────────────────────────────────────────────────────────── */ function ScoreBox({ label = "Mon bilan", message }) { return (
{label}
{message}
__ / 10
); } /* ───────────────────────────────────────────────────────────────────── TableBlock — tableau standardisé ───────────────────────────────────────────────────────────────────── */ function TableBlock({ title, columns, rows }) { return (
{title &&
{title}
} {columns.map((c, i) => )} {rows.map((r, i) => ( {r.map((c, j) => )} ))}
{c}
{c}
); } /* ───────────────────────────────────────────────────────────────────── FigureBlock — illustration / schéma encadré + légende ───────────────────────────────────────────────────────────────────── */ function FigureBlock({ caption, label = "Document", children, height = "1.2in" }) { return (
{children ||
illustration / schéma
}
{caption &&
{label} · {caption}
}
); } /* ───────────────────────────────────────────────────────────────────── QRBlock — bloc QR sur la page (différent du QR du footer) ───────────────────────────────────────────────────────────────────── */ function QRBlock({ title = "Scanne pour la correction", // V1.2 — production props qrSlug, qrUrl = "alloeducation.fr/maths/4e/p28", qrLabel, // alias for title (production-naming consistency) qrSubLabel, url, // legacy alias for qrUrl seed = 42, size = "0.95in", }) { const finalUrl = qrSlug ? `alloeducation.fr/qr/${qrSlug}` : (url || qrUrl); const finalTitle = qrLabel || title; return (
{finalTitle}
{qrSubLabel &&
{qrSubLabel}
}
{finalUrl}
); } /* ───────────────────────────────────────────────────────────────────── KettyBubble — composant Ketty unique (slot mascotte + bulle) ───────────────────────────────────────────────────────────────────── */ function KettyBubble({ pose = "pointing", // V1.2 — production props kettyPose, kettyImageSrc, kettyAlt, label = "Astuce Ketty", tone = "primary", children, }) { return (
{label}
{children}
); } /* ───────────────────────────────────────────────────────────────────── Helpers used inside ExerciseCard ───────────────────────────────────────────────────────────────────── */ function QInput({ n, children }) { return (
{n} {children}
); } function AnswerLine({ width = "1.4in" }) { return ; } function AnswerBox({ height = "0.6in", ruled = false }) { return
; } function QCMRow({ options = [] }) { return (
{options.map(([letter, text], i) => ( {letter}{text} ))}
); } /* ───────────────────────────────────────────────────────────────────── Two-column layout helper for putting blocks side-by-side ───────────────────────────────────────────────────────────────────── */ function Cols({ ratio = "1fr 1fr", gap, className = "", children }) { return (
{children}
); } Object.assign(window, { PageShell, PageTitle, Box, ObjectiveBox, LessonBox, ExerciseCard, MethodSteps, WarningBox, ChallengeBox, SelfCheck, ScoreBox, TableBlock, FigureBlock, QRBlock, KettyBubble, QInput, AnswerLine, AnswerBox, QCMRow, Cols, });