// template-pack-4-jeux-educatifs.jsx
// PACK 4 · JEUX ÉDUCATIFS UNIVERSELS — Allo Education Books v1.2
// 12 templates ludiques harmonisés avec la charte verrouillée.
// Toutes les pages passent par <PageShell>. Aucun header/footer recréé.
// Pas d'emoji. Toutes icônes via <Icon /> ou iconKey. Pilotables par data.

const { Fragment: J_F } = React;

/* ════════════════════════════════════════════════════════════════════════
   Helpers locaux — strictement décoratifs, ne touchent pas la charte.
   ════════════════════════════════════════════════════════════════════════ */

function GameMeta({ niveau, duree, joueurs, difficulte = 2, max = 3 }) {
  return (
    <div style={{
      display: "flex", gap: "0.16in", alignItems: "center",
      flexWrap: "wrap",
      padding: "0.08in 0.14in",
      borderRadius: "0.1in",
      background: "var(--ae-primary-soft)",
      border: "1px solid var(--ae-primary)",
      fontSize: "var(--ae-fs-body)",
      fontFamily: "var(--ae-font-title)",
      color: "var(--ae-primary-deep)",
    }}>
      <span style={{ display: "inline-flex", alignItems: "center", gap: "0.06in" }}>
        <Icon k="icon-cible" /> <b>{niveau}</b>
      </span>
      <Sep />
      <span style={{ display: "inline-flex", alignItems: "center", gap: "0.06in" }}>
        <Icon k="icon-horloge" /> {duree}
      </span>
      {joueurs && (<><Sep /><span>{joueurs}</span></>)}
      <Sep />
      <span style={{ display: "inline-flex", alignItems: "center", gap: "0.04in" }}>
        Difficulté <Stars n={difficulte} max={max} />
      </span>
    </div>
  );
}
function Sep() {
  return <span style={{ width: "0.04in", height: "0.04in", borderRadius: "999px", background: "var(--ae-primary)" }} />;
}
function Stars({ n = 2, max = 3 }) {
  return (
    <span style={{ display: "inline-flex", gap: "0.02in" }}>
      {Array.from({ length: max }).map((_, i) => (
        <Icon key={i} k="icon-etoile" style={{ color: i < n ? "var(--ae-orange)" : "var(--ae-border)" }} />
      ))}
    </span>
  );
}

function NumChip({ n, tone = "primary", size = "0.22in" }) {
  const bg = tone === "orange" ? "var(--ae-orange)"
    : tone === "green" ? "var(--ae-success)"
    : tone === "yellow" ? "var(--ae-yellow-dark)"
    : "var(--ae-primary)";
  return (
    <span style={{
      display: "inline-grid", placeItems: "center",
      width: size, height: size, borderRadius: "999px",
      background: bg, color: "white",
      fontFamily: "var(--ae-font-title)", fontWeight: 700,
      fontSize: "0.13in", flex: "0 0 auto",
    }}>{n}</span>
  );
}

function AnswerSlot({ width = "0.32in", height = "0.32in" }) {
  return <span style={{
    display: "inline-block", width, height,
    border: "1.4px solid var(--ae-text-soft)",
    borderRadius: "0.04in", background: "white",
    verticalAlign: "middle",
  }} />;
}

/* ════════════════════════════════════════════════════════════════════════
   1 · MOTS CROISÉS  (template: "crossword-game")
   ════════════════════════════════════════════════════════════════════════ */

// Grid encoded as a string array, "." = case noire, " " = case ouverte.
// Numbers placed on first letter of each across/down word.
function CrosswordGrid({ rows, numbers = {}, cell = "0.3in" }) {
  const ROWS = rows.length, COLS = rows[0].length;
  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: `repeat(${COLS}, ${cell})`,
      gap: 0,
      width: "max-content",
      borderTop: "1.5px solid var(--ae-text)",
      borderLeft: "1.5px solid var(--ae-text)",
      background: "var(--ae-text)",
    }}>
      {rows.map((row, y) =>
        Array.from(row).map((c, x) => {
          const isBlack = c === ".";
          const key = `${x}-${y}`;
          const num = numbers[key];
          return (
            <div key={key} style={{
              width: cell, height: cell,
              borderRight: "1.5px solid var(--ae-text)",
              borderBottom: "1.5px solid var(--ae-text)",
              background: isBlack ? "var(--ae-text)" : "white",
              position: "relative",
            }}>
              {!isBlack && num != null && (
                <span style={{
                  position: "absolute", top: 1, left: 2,
                  fontSize: "0.085in",
                  fontFamily: "var(--ae-font-title)", fontWeight: 700,
                  color: "var(--ae-primary-deep)", lineHeight: 1,
                }}>{num}</span>
              )}
            </div>
          );
        })
      )}
    </div>
  );
}

function CluesList({ title, items, tone = "primary" }) {
  const accent = tone === "orange" ? "var(--ae-orange)" : "var(--ae-primary)";
  return (
    <div>
      <div style={{
        fontFamily: "var(--ae-font-title)", fontWeight: 700,
        fontSize: "var(--ae-fs-h-inblock)", color: accent,
        textTransform: "uppercase", letterSpacing: "0.06em",
        marginBottom: "0.06in",
      }}>{title}</div>
      <ol style={{
        margin: 0, paddingLeft: 0, listStyle: "none",
        display: "flex", flexDirection: "column", gap: "0.05in",
        fontSize: "var(--ae-fs-body)", lineHeight: 1.35,
      }}>
        {items.map(([n, txt]) => (
          <li key={n} style={{ display: "flex", gap: "0.08in", alignItems: "baseline" }}>
            <span style={{
              fontFamily: "var(--ae-font-title)", fontWeight: 700,
              color: accent, minWidth: "0.18in",
            }}>{n}.</span>
            <span>{txt}</span>
          </li>
        ))}
      </ol>
    </div>
  );
}

function J1Crossword({ density, qrVisible, scoreVisible, scale = 1 }) {
  // 11×8 grid — Moyen Âge. Black cells = "."
  const rows = [
    "...ROI....",
    "..A...V...",
    ".CHEVALIER",
    "..B...S...",
    ".SEIGNEUR.",
    "..Y...A...",
    "FIEF..L...",
    "...CROISA.", // last col cut to fit
  ].map(r => r.padEnd(10, "."));
  const numbers = {
    "3-0": 1,    // ROI across
    "1-1": 2,    // ABBAYE down (col 1)
    "6-1": 3,    // VASSAL down (col 6)
    "1-2": 4,    // CHEVALIER across
    "1-4": 5,    // SEIGNEUR across
    "0-6": 6,    // FIEF across
    "3-7": 7,    // CROISA(DE) across
  };
  const acrossClues = [
    [1, "Souverain qui règne sur le royaume."],
    [4, "Combattant à cheval au service du seigneur."],
    [5, "Personnage qui possède une terre et y exerce le pouvoir."],
    [6, "Terre confiée par le seigneur à son vassal."],
    [7, "Expédition militaire vers Jérusalem (XIᵉ–XIIIᵉ s.)."],
  ];
  const downClues = [
    [2, "Lieu où vivent les moines, dirigé par un abbé."],
    [3, "Homme lié à un seigneur par un serment de fidélité."],
  ];

  return (
    <PageShell
      matiere="Histoire" niveau="5ᵉ" page={"Jeux · Page 12"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="hist-5-mc-moyenage"
      qrLabel="Vérifier la grille"
      scale={scale}
    >
      <PageTitle
        eyebrow="Mots croisés · Vocabulaire d'histoire"
        title="Le Moyen Âge en 7 mots"
        subtitle="Complète la grille à partir des définitions horizontales et verticales."
      />

      <GameMeta niveau="5ᵉ — collège" duree="15–20 min" joueurs="Seul ou en binôme" difficulte={2} />

      <Cols className="ae-major" ratio="1.05fr 1fr" gap="0.22in">
        <div style={{
          display: "flex", justifyContent: "center", alignItems: "flex-start",
          padding: "0.16in",
          background: "var(--ae-bg-cool)",
          border: "1.5px solid var(--ae-border)",
          borderRadius: "var(--ae-r-lg)",
        }}>
          <CrosswordGrid rows={rows} numbers={numbers} cell="0.3in" />
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: "0.16in" }}>
          <CluesList title="Horizontalement" items={acrossClues} tone="primary" />
          <CluesList title="Verticalement" items={downClues} tone="orange" />
        </div>
      </Cols>

      <Cols ratio="1.4fr 1fr">
        <KettyBubble pose="pointing" tone="yellow" label="Astuce Ketty">
          Commence toujours par le mot dont tu es le plus sûr. Ses lettres
          serviront d'indices pour les mots qui se croisent.
        </KettyBubble>
        <ScoreBox label="Mots trouvés" message="1 point par mot exact + orthographe correcte." />
      </Cols>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   2 · MOTS MÊLÉS  (template: "word-search-game")
   ════════════════════════════════════════════════════════════════════════ */

function WordSearchGrid({ grid, cell = "0.26in" }) {
  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: `repeat(${grid[0].length}, ${cell})`,
      gap: 0,
      width: "max-content",
      border: "1.5px solid var(--ae-text)",
      background: "white",
      borderRadius: "0.04in",
      overflow: "hidden",
    }}>
      {grid.map((row, y) =>
        row.map((ch, x) => (
          <div key={`${x}-${y}`} style={{
            width: cell, height: cell,
            display: "grid", placeItems: "center",
            fontFamily: "var(--ae-font-title)", fontWeight: 600,
            fontSize: "0.14in", color: "var(--ae-text)",
            borderRight: x < row.length - 1 ? "1px solid var(--ae-border-soft)" : "none",
            borderBottom: y < grid.length - 1 ? "1px solid var(--ae-border-soft)" : "none",
            letterSpacing: "0.02em",
          }}>{ch}</div>
        ))
      )}
    </div>
  );
}

function J2WordSearch({ density, qrVisible, scoreVisible, scale = 1 }) {
  // 12×10 grid with planètes hidden (some real placements)
  const grid = [
    "MERCUREZBNV".split(""),
    "AKLPLANETEN".split(""),
    "RPSATURNEPL".split(""),
    "SVENUSXMQTU".split(""),
    "TERREORBITN".split(""),
    "LZJUPITERAE".split(""),
    "URANUSEHJST".split(""),
    "FETOILEKMUO".split(""),
    "ASOLEILBRMN".split(""),
    "TGALAXIEZWE".split(""),
  ];
  const words = [
    "SOLEIL", "MERCURE", "VENUS", "TERRE", "MARS",
    "JUPITER", "SATURNE", "URANUS", "NEPTUNE",
    "PLANETE", "ETOILE", "ORBITE", "GALAXIE",
  ];

  return (
    <PageShell
      matiere="Sciences" niveau="6ᵉ" page={"Jeux · Page 5"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="svt-6-motsmeles-systemesolaire"
      qrLabel="Voir les mots cachés"
      scale={scale}
    >
      <PageTitle
        eyebrow="Mots mêlés · Le système solaire"
        title="Trouve les 13 mots cachés"
        subtitle="Les mots peuvent être écrits horizontalement ou verticalement, jamais en diagonale."
      />

      <GameMeta niveau="6ᵉ — collège" duree="10–15 min" joueurs="Seul" difficulte={2} />

      <Cols ratio="1.2fr 1fr" gap="0.22in">
        <div style={{
          padding: "0.18in", background: "var(--ae-bg-cool)",
          border: "1.5px solid var(--ae-border)", borderRadius: "var(--ae-r-lg)",
          display: "flex", justifyContent: "center",
        }}>
          <WordSearchGrid grid={grid} cell="0.27in" />
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: "0.12in" }}>
          <Box tone="primary" label="Mots à trouver" iconKey="icon-loupe">
            <div style={{
              display: "grid", gridTemplateColumns: "1fr 1fr",
              rowGap: "0.05in", columnGap: "0.08in",
              fontFamily: "var(--ae-font-title)", fontSize: "var(--ae-fs-body)",
              color: "var(--ae-primary-deep)",
            }}>
              {words.map(w => (
                <span key={w} style={{ display: "flex", alignItems: "center", gap: "0.06in" }}>
                  <span style={{
                    width: "0.14in", height: "0.14in",
                    border: "1.4px solid var(--ae-primary)",
                    borderRadius: "3px", background: "white",
                  }} />
                  {w}
                </span>
              ))}
            </div>
          </Box>
          <Box tone="orange" label="Mini-consigne" iconKey="icon-info">
            <div style={{ fontSize: "var(--ae-fs-body)" }}>
              Entoure chaque mot trouvé. Coche-le dans la liste ci-contre. Si
              tu bloques, le QR du footer dévoile la solution.
            </div>
          </Box>
        </div>
      </Cols>

      <Cols ratio="1fr 1fr">
        <ScoreBox label="Mots entourés" message="13 mots à trouver — 1 point chacun." />
        <KettyBubble pose="thinking" tone="primary" label="Stratégie">
          Cherche d'abord les mots les <b>plus longs</b> (NEPTUNE, GALAXIE) :
          ils éliminent rapidement plusieurs lignes de la grille.
        </KettyBubble>
      </Cols>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   3 · CODE SECRET  (template: "secret-code-game")
   ════════════════════════════════════════════════════════════════════════ */

function CodeKeyTable({ pairs, cols = 13 }) {
  const rows = [];
  for (let i = 0; i < pairs.length; i += cols) rows.push(pairs.slice(i, i + cols));
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 0,
      border: "1.5px solid var(--ae-primary)", borderRadius: "var(--ae-r-md)",
      overflow: "hidden", background: "white" }}>
      {rows.map((r, ri) => (
        <J_F key={ri}>
          <div style={{ display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, background: "var(--ae-primary)" }}>
            {r.map(([letter]) => (
              <div key={letter} style={{
                padding: "0.04in 0", textAlign: "center",
                fontFamily: "var(--ae-font-title)", fontWeight: 700,
                color: "white", fontSize: "0.13in",
                borderRight: "1px solid rgba(255,255,255,.25)",
              }}>{letter}</div>
            ))}
          </div>
          <div style={{ display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)` }}>
            {r.map(([, num]) => (
              <div key={num} style={{
                padding: "0.05in 0", textAlign: "center",
                fontFamily: "var(--ae-font-mono)", fontWeight: 600,
                color: "var(--ae-primary-deep)", fontSize: "0.12in",
                borderRight: "1px solid var(--ae-border-soft)",
                borderTop: ri > 0 ? "1px solid var(--ae-border)" : "none",
                background: ri % 2 ? "var(--ae-bg-cool)" : "white",
              }}>{num}</div>
            ))}
          </div>
        </J_F>
      ))}
    </div>
  );
}

function J3SecretCode({ density, qrVisible, scoreVisible, scale = 1 }) {
  // A=1, B=2 ... full alphabet. We display only the active subset.
  const used = "BRAVOLEMOTSECRETESTGAGNE".split("");
  const uniq = [...new Set(used)].sort();
  const pairs = uniq.map(l => [l, l.charCodeAt(0) - 64]);
  // Operations: each result corresponds to a letter in "BRAVOLEMOT..."
  // Calculations to decipher 5 letters: B(2), R(18), A(1), V(22), O(15)
  const calcs = [
    ["1.", "(8 × 4) − 30",      "B"],
    ["2.", "9 × 2",              "R"],
    ["3.", "(5 + 3) − 7",        "A"],
    ["4.", "(11 × 2)",           "V"],
    ["5.", "(60 ÷ 4)",           "O"],
  ];

  return (
    <PageShell
      matiere="Maths" niveau="CM2 — 6ᵉ" page={"Jeux · Page 9"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="maths-cm2-codesecret"
      qrLabel="Décoder le message complet"
      scale={scale}
    >
      <PageTitle
        eyebrow="Code secret · Calcul mental"
        title="Décode le message caché"
        subtitle="Résous chaque calcul, traduis le résultat en lettre, et lis le mot mystère."
      />

      <Box tone="primary" label="Table de correspondance" iconKey="icon-info">
        <div style={{ fontSize: "var(--ae-fs-body)", marginBottom: "0.08in" }}>
          Chaque lettre vaut son rang dans l'alphabet : <b>A = 1, B = 2, C = 3 …</b>
        </div>
        <CodeKeyTable pairs={pairs} cols={Math.min(13, uniq.length)} />
      </Box>

      <ExerciseCard number={1} consigne="Résous chaque opération, écris la lettre correspondante, puis lis le mot final.">
        <div style={{
          display: "grid", gridTemplateColumns: "0.4in 2fr 0.5in 0.5in",
          rowGap: "0.08in", columnGap: "0.1in",
          alignItems: "center", fontSize: "var(--ae-fs-body)",
        }}>
          <div className="bk-col-head">N°</div>
          <div className="bk-col-head">Calcul</div>
          <div className="bk-col-head" style={{ textAlign: "center" }}>Résultat</div>
          <div className="bk-col-head" style={{ textAlign: "center" }}>Lettre</div>
          {calcs.map(([n, op]) => (
            <J_F key={n}>
              <div style={{ fontFamily: "var(--ae-font-title)", fontWeight: 700, color: "var(--ae-primary)" }}>{n}</div>
              <div style={{ fontFamily: "var(--ae-font-mono)" }}>{op} =</div>
              <div style={{ display: "grid", placeItems: "center" }}><AnswerSlot width="0.5in" height="0.32in" /></div>
              <div style={{ display: "grid", placeItems: "center" }}><AnswerSlot width="0.32in" height="0.32in" /></div>
            </J_F>
          ))}
        </div>

        <div style={{ marginTop: "0.16in" }}>
          <div className="bk-col-head" style={{ marginBottom: "0.06in" }}>Mot mystère</div>
          <div style={{ display: "flex", gap: "0.05in", flexWrap: "wrap" }}>
            {Array.from("BRAVO").map((_, i) => (
              <AnswerSlot key={i} width="0.4in" height="0.46in" />
            ))}
          </div>
        </div>
      </ExerciseCard>

      <KettyBubble pose="pointing" tone="primary" label="Indice de Ketty">
        Les nombres de la table vont de <b>1 à 26</b>. Si ton résultat est
        au-dessus de 26 : tu t'es trompé quelque part — refais le calcul.
      </KettyBubble>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   4 · JEU DE L'INTRUS  (template: "odd-one-out-game")
   ════════════════════════════════════════════════════════════════════════ */

function IntrusItem({ children }) {
  return (
    <span style={{
      display: "inline-block",
      padding: "0.06in 0.14in",
      borderRadius: "999px",
      border: "1.5px solid var(--ae-border)",
      background: "white",
      fontFamily: "var(--ae-font-title)", fontWeight: 600,
      fontSize: "var(--ae-fs-body)",
      color: "var(--ae-text)",
    }}>{children}</span>
  );
}

function J4OddOneOut({ density, qrVisible, scoreVisible, scale = 1 }) {
  const series = [
    { theme: "Capitales", items: ["Paris", "Madrid", "Berlin", "Lyon", "Rome"] },
    { theme: "Mammifères", items: ["Chien", "Chat", "Dauphin", "Requin", "Baleine"] },
    { theme: "Multiples de 3", items: ["9", "15", "22", "27", "33"] },
    { theme: "Verbes du 1ᵉʳ groupe", items: ["chanter", "parler", "finir", "manger", "danser"] },
    { theme: "Mots invariables", items: ["mais", "où", "donc", "joli", "car"] },
    { theme: "Sources d'énergie renouvelables", items: ["soleil", "vent", "eau", "pétrole", "biomasse"] },
  ];

  return (
    <PageShell
      matiere="Multi-matières" niveau="CM2 — 5ᵉ" page={"Jeux · Page 14"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="multi-cm2-intrus"
      qrLabel="Justifications complètes"
      scale={scale}
    >
      <PageTitle
        eyebrow="Cherche l'intrus · Toutes matières"
        title="Quel mot ne va pas avec les autres ?"
        subtitle="Entoure l'intrus de chaque ligne, puis explique pourquoi en une phrase."
      />

      <GameMeta niveau="CM2 — 5ᵉ" duree="15 min" joueurs="Seul" difficulte={2} />

      <ExerciseCard number={1} consigne="Pour chaque série, repère le mot qui n'a pas sa place.">
        <div style={{ display: "flex", flexDirection: "column", gap: "0.12in" }}>
          {series.map((s, i) => (
            <div key={i} style={{
              display: "grid",
              gridTemplateColumns: "0.3in 1.1fr 1.4fr",
              alignItems: "center", gap: "0.12in",
              padding: "0.08in 0",
              borderBottom: i < series.length - 1 ? "1px dashed var(--ae-border)" : "none",
            }}>
              <NumChip n={i + 1} tone={i % 2 ? "orange" : "primary"} />
              <div>
                <div style={{
                  fontSize: "var(--ae-fs-eyebrow)", textTransform: "uppercase",
                  letterSpacing: "0.08em", color: "var(--ae-muted)",
                  fontWeight: 700, marginBottom: "0.04in",
                }}>{s.theme}</div>
                <div style={{ display: "flex", flexWrap: "wrap", gap: "0.06in" }}>
                  {s.items.map(it => <IntrusItem key={it}>{it}</IntrusItem>)}
                </div>
              </div>
              <div>
                <div style={{
                  fontSize: "var(--ae-fs-eyebrow)", textTransform: "uppercase",
                  letterSpacing: "0.08em", color: "var(--ae-muted)",
                  fontWeight: 700, marginBottom: "0.04in",
                }}>J'explique pourquoi</div>
                <div style={{
                  height: "0.36in", border: "1px solid var(--ae-border)",
                  borderRadius: "0.06in", background: "white",
                }} />
              </div>
            </div>
          ))}
        </div>
      </ExerciseCard>

      <Cols ratio="1fr 1fr">
        <ScoreBox label="Mes points" message="2 pts par intrus + 1 pt si la justification est juste." />
        <KettyBubble pose="thinking" tone="yellow" label="Méthode Ketty">
          Avant d'entourer, demande-toi : <b>quel point commun</b> ont 4 mots
          de la liste ? L'intrus est celui qui ne le partage pas.
        </KettyBubble>
      </Cols>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   5 · VRAI / FAUX EXPRESS  (template: "true-false-game")
   ════════════════════════════════════════════════════════════════════════ */

function J5TrueFalse({ density, qrVisible, scoreVisible, scale = 1 }) {
  const items = [
    "La Révolution française commence en 1789.",
    "Louis XVI était empereur des Français.",
    "La prise de la Bastille a eu lieu le 14 juillet 1789.",
    "La Déclaration des droits de l'homme date de 1791.",
    "Marie-Antoinette était reine d'Angleterre.",
    "La Première République est proclamée en 1792.",
    "Robespierre était un général de l'Empire.",
    "La devise « Liberté, Égalité, Fraternité » date de la Révolution.",
  ];

  return (
    <PageShell
      matiere="Histoire" niveau="4ᵉ" page={"Jeux · Page 22"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="hist-4-vraifauxrev"
      qrLabel="Voir les justifications"
      scale={scale}
    >
      <PageTitle
        eyebrow="Vrai ou faux express · La Révolution française"
        title="Coche, justifie, marque tes points"
        subtitle="Une seule case par ligne. Si c'est faux, écris une phrase pour expliquer."
      />

      <GameMeta niveau="4ᵉ — collège" duree="10 min" joueurs="Seul ou en duel" difficulte={2} />

      <ExerciseCard number={1} consigne="V = Vrai · F = Faux. Justifie chaque « Faux » sur la ligne dessous.">
        <div style={{ display: "flex", flexDirection: "column", gap: "0.06in" }}>
          {items.map((txt, i) => (
            <div key={i} style={{
              display: "grid",
              gridTemplateColumns: "0.32in 1fr 0.4in 0.4in",
              alignItems: "center", gap: "0.08in",
              padding: "0.08in 0",
              borderTop: i > 0 ? "1px dashed var(--ae-border)" : "none",
            }}>
              <NumChip n={i + 1} tone={i % 2 ? "orange" : "primary"} />
              <span style={{ fontSize: "var(--ae-fs-body)", lineHeight: 1.4 }}>{txt}</span>
              <div style={{ display: "grid", placeItems: "center" }}>
                <span style={{
                  display: "grid", placeItems: "center",
                  width: "0.34in", height: "0.34in",
                  border: "1.5px solid var(--ae-success)",
                  borderRadius: "0.06in",
                  fontFamily: "var(--ae-font-title)", fontWeight: 700,
                  color: "var(--ae-success)", fontSize: "0.16in",
                  background: "white",
                }}>V</span>
              </div>
              <div style={{ display: "grid", placeItems: "center" }}>
                <span style={{
                  display: "grid", placeItems: "center",
                  width: "0.34in", height: "0.34in",
                  border: "1.5px solid var(--ae-error)",
                  borderRadius: "0.06in",
                  fontFamily: "var(--ae-font-title)", fontWeight: 700,
                  color: "var(--ae-error)", fontSize: "0.16in",
                  background: "white",
                }}>F</span>
              </div>
            </div>
          ))}
        </div>
      </ExerciseCard>

      <Cols ratio="1fr 1fr">
        <WarningBox title="Erreur fréquente" iconKey="icon-attention">
          Confondre « roi » et « empereur ». Sous l'<b>Ancien Régime</b>, le
          dirigeant est un roi. L'<b>empereur</b>, c'est plus tard, avec
          Napoléon.
        </WarningBox>
        <ScoreBox label="Mon score" message="1 point par bonne case + 1 point par justification correcte." />
      </Cols>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   6 · RELIER / ASSOCIER  (template: "matching-game")
   ════════════════════════════════════════════════════════════════════════ */

function J6Matching({ density, qrVisible, scoreVisible, scale = 1 }) {
  // Anglais 6e — vocabulaire animaux + maison
  const left = [
    ["1", "Bedroom"],
    ["2", "Kitchen"],
    ["3", "Living room"],
    ["4", "Bathroom"],
    ["5", "Garden"],
    ["6", "Garage"],
  ];
  const right = [
    ["A", "Le jardin"],
    ["B", "Le salon"],
    ["C", "La salle de bain"],
    ["D", "La chambre"],
    ["E", "Le garage"],
    ["F", "La cuisine"],
  ];

  return (
    <PageShell
      matiere="Anglais" niveau="6ᵉ" page={"Jeux · Page 18"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="ang-6-matching-house"
      qrLabel="Vérifier les paires"
      scale={scale}
    >
      <PageTitle
        eyebrow="Relier · English vocabulary"
        title="Les pièces de la maison"
        subtitle="Trace une ligne entre chaque mot anglais et sa traduction française."
      />

      <GameMeta niveau="6ᵉ — collège" duree="10 min" joueurs="Seul" difficulte={1} />

      <ExerciseCard number={1} consigne="Trace une ligne droite, sans croiser inutilement.">
        <div style={{
          display: "grid",
          gridTemplateColumns: "1fr 0.6fr 1fr",
          alignItems: "center",
          gap: "0.1in",
        }}>
          {/* English column */}
          <div style={{ display: "flex", flexDirection: "column", gap: "0.08in" }}>
            <div style={{
              fontSize: "var(--ae-fs-eyebrow)", textTransform: "uppercase",
              letterSpacing: "0.08em", color: "var(--ae-primary)",
              fontWeight: 800,
            }}>English</div>
            {left.map(([n, w]) => (
              <div key={n} style={{
                display: "flex", alignItems: "center", gap: "0.1in",
                padding: "0.1in 0.14in",
                border: "1.5px solid var(--ae-primary)",
                borderRadius: "0.1in",
                background: "var(--ae-primary-soft)",
                fontFamily: "var(--ae-font-title)", fontWeight: 600,
                fontSize: "var(--ae-fs-body)",
                color: "var(--ae-primary-deep)",
              }}>
                <NumChip n={n} />
                <span style={{ flex: 1 }}>{w}</span>
                <span style={{
                  width: "0.12in", height: "0.12in", borderRadius: "999px",
                  background: "var(--ae-primary)", flex: "0 0 auto",
                }} />
              </div>
            ))}
          </div>
          {/* Connector lane */}
          <div style={{ position: "relative", minHeight: "100%" }}>
            <svg viewBox="0 0 100 380" preserveAspectRatio="none" style={{ width: "100%", height: "100%", display: "block" }}>
              {[60, 120, 180, 240, 300, 360].map((y, i) => (
                <circle key={i} cx={i % 2 ? 30 : 70} cy={y - 30} r="2" fill="var(--ae-border)" />
              ))}
            </svg>
            <div style={{
              position: "absolute", top: "50%", left: 0, right: 0,
              transform: "translateY(-50%)", textAlign: "center",
              fontFamily: "var(--ae-font-title)",
              color: "var(--ae-muted)", fontSize: "0.11in",
              letterSpacing: "0.08em", textTransform: "uppercase",
              background: "white",
              padding: "0.04in 0",
              borderTop: "1px dashed var(--ae-border)",
              borderBottom: "1px dashed var(--ae-border)",
            }}>relier</div>
          </div>
          {/* French column */}
          <div style={{ display: "flex", flexDirection: "column", gap: "0.08in" }}>
            <div style={{
              fontSize: "var(--ae-fs-eyebrow)", textTransform: "uppercase",
              letterSpacing: "0.08em", color: "var(--ae-orange)",
              fontWeight: 800, textAlign: "right",
            }}>Français</div>
            {right.map(([ch, t]) => (
              <div key={ch} style={{
                display: "flex", alignItems: "center", gap: "0.1in",
                padding: "0.1in 0.14in",
                border: "1.5px solid var(--ae-orange)",
                borderRadius: "0.1in",
                background: "var(--ae-orange-soft)",
                fontSize: "var(--ae-fs-body)",
              }}>
                <span style={{
                  width: "0.12in", height: "0.12in", borderRadius: "999px",
                  background: "var(--ae-orange)", flex: "0 0 auto",
                }} />
                <span style={{
                  display: "grid", placeItems: "center",
                  width: "0.26in", height: "0.26in",
                  borderRadius: "0.05in",
                  background: "white",
                  border: "1.5px solid var(--ae-orange)",
                  fontFamily: "var(--ae-font-title)", fontWeight: 700,
                  color: "var(--ae-orange-dark)", fontSize: "0.12in",
                }}>{ch}</span>
                <span style={{ flex: 1 }}>{t}</span>
              </div>
            ))}
          </div>
        </div>

        <div style={{ marginTop: "0.16in" }}>
          <div className="bk-col-head" style={{ marginBottom: "0.06in" }}>Reporte tes paires ici</div>
          <TableBlock
            columns={["1 →", "2 →", "3 →", "4 →", "5 →", "6 →"]}
            rows={[["…", "…", "…", "…", "…", "…"]]}
          />
        </div>
      </ExerciseCard>

      <KettyBubble pose="thumbsup" tone="primary" label="Tip from Ketty">
        Lis chaque paire à voix haute en anglais puis en français. Le son de la
        traduction t'aide à mémoriser bien plus vite que la lecture silencieuse.
      </KettyBubble>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   7 · REMETTRE DANS L'ORDRE  (template: "ordering-game")
   ════════════════════════════════════════════════════════════════════════ */

function OrderCard({ letter, text }) {
  return (
    <div style={{
      display: "flex", alignItems: "center", gap: "0.1in",
      padding: "0.1in 0.14in",
      border: "1.5px solid var(--ae-border)",
      borderTop: "3px solid var(--ae-primary)",
      borderRadius: "0.1in",
      background: "white",
      boxShadow: "0 2px 0 rgba(15,26,61,.04)",
    }}>
      <span style={{
        display: "grid", placeItems: "center",
        width: "0.32in", height: "0.32in",
        borderRadius: "0.06in",
        background: "var(--ae-primary)", color: "white",
        fontFamily: "var(--ae-font-title)", fontWeight: 700,
        fontSize: "0.16in",
      }}>{letter}</span>
      <span style={{ flex: 1, fontSize: "var(--ae-fs-body)", lineHeight: 1.35 }}>{text}</span>
    </div>
  );
}

function J7Ordering({ density, qrVisible, scoreVisible, scale = 1 }) {
  const cards = [
    ["A", "L'eau s'évapore au-dessus des océans, des lacs et des rivières."],
    ["B", "Les nuages se déplacent avec le vent et se refroidissent."],
    ["C", "Les gouttes d'eau retombent sous forme de pluie ou de neige."],
    ["D", "L'eau ruisselle, s'infiltre dans les sols et rejoint les nappes."],
    ["E", "Les fleuves transportent l'eau jusqu'aux mers et aux océans."],
    ["F", "La vapeur d'eau monte et se condense en nuages."],
  ];

  return (
    <PageShell
      matiere="Sciences" niveau="6ᵉ" page={"Jeux · Page 31"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="svt-6-ordre-cycleeau"
      qrLabel="Voir le bon ordre"
      scale={scale}
    >
      <PageTitle
        eyebrow="Remets dans l'ordre · Le cycle de l'eau"
        title="Reconstitue le cycle, étape par étape"
        subtitle="Numérote les 6 cartes de 1 à 6 et reporte la suite des lettres dans la frise du bas."
      />

      <Box tone="green" label="Mini-exemple" iconKey="icon-info">
        <div style={{ fontSize: "var(--ae-fs-body)", lineHeight: 1.4 }}>
          Pour la cuisson d'un œuf : <b>casser</b> → <b>battre</b> → <b>cuire</b> →
          <b> servir</b>. La logique : tu commences par l'action qui rend possible la suivante.
        </div>
      </Box>

      <ExerciseCard number={1} consigne="Lis les 6 cartes mélangées, écris ton numéro d'ordre dans la case bleue.">
        <div style={{
          display: "grid", gridTemplateColumns: "1fr 1fr",
          gap: "0.1in",
        }}>
          {cards.map(([l, t]) => (
            <div key={l} style={{
              display: "grid",
              gridTemplateColumns: "1fr 0.4in",
              gap: "0.08in", alignItems: "center",
            }}>
              <OrderCard letter={l} text={t} />
              <div style={{ display: "grid", placeItems: "center" }}>
                <AnswerSlot width="0.36in" height="0.36in" />
              </div>
            </div>
          ))}
        </div>

        <div style={{ marginTop: "0.18in" }}>
          <div className="bk-col-head" style={{ marginBottom: "0.06in" }}>Frise · ordre des étapes</div>
          <div style={{ display: "flex", alignItems: "center", gap: "0.06in" }}>
            {[1, 2, 3, 4, 5, 6].map((n, i) => (
              <J_F key={n}>
                <div style={{
                  display: "flex", flexDirection: "column", alignItems: "center", gap: "0.04in",
                }}>
                  <span style={{
                    fontFamily: "var(--ae-font-title)", fontWeight: 700,
                    color: "var(--ae-primary)", fontSize: "0.13in",
                  }}>{n}</span>
                  <span style={{
                    width: "0.5in", height: "0.5in",
                    borderRadius: "0.08in",
                    border: "1.5px solid var(--ae-primary)",
                    background: "white",
                    display: "grid", placeItems: "center",
                    fontFamily: "var(--ae-font-title)", fontWeight: 700,
                    color: "var(--ae-primary-deep)", fontSize: "0.22in",
                  }}>?</span>
                </div>
                {i < 5 && <Icon k="icon-fleche" style={{ color: "var(--ae-primary)", fontSize: "0.22in" }} />}
              </J_F>
            ))}
          </div>
        </div>
      </ExerciseCard>

      <Cols ratio="1fr 1fr">
        <ScoreBox label="Cycle reconstitué" message="6 lettres dans le bon ordre = 10 / 10." />
        <KettyBubble pose="pointing" tone="yellow" label="Indice Ketty">
          Cherche d'abord <b>l'étape de départ</b> : celle qui ne dépend
          d'aucune autre. C'est ton point d'ancrage pour tout reconstituer.
        </KettyBubble>
      </Cols>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   8 · LABYRINTHE ÉDUCATIF  (template: "maze-game")
   ════════════════════════════════════════════════════════════════════════ */

function MazeSVG() {
  // Schematic 11×7 maze. Walls drawn as line segments. Path from (0,3)→(10,3)
  return (
    <svg viewBox="0 0 440 280" style={{ width: "100%", height: "100%" }}>
      {/* outer */}
      <rect x="2" y="2" width="436" height="276" fill="white" stroke="var(--ae-text)" strokeWidth="2" rx="6" />
      {/* internal walls */}
      <g stroke="var(--ae-text)" strokeWidth="2" strokeLinecap="round">
        <line x1="40" y1="2" x2="40" y2="80" />
        <line x1="40" y1="80" x2="120" y2="80" />
        <line x1="80" y1="80" x2="80" y2="200" />
        <line x1="80" y1="240" x2="80" y2="278" />
        <line x1="120" y1="40" x2="200" y2="40" />
        <line x1="160" y1="40" x2="160" y2="160" />
        <line x1="160" y1="160" x2="240" y2="160" />
        <line x1="200" y1="200" x2="200" y2="278" />
        <line x1="240" y1="2" x2="240" y2="120" />
        <line x1="280" y1="40" x2="280" y2="200" />
        <line x1="280" y1="200" x2="360" y2="200" />
        <line x1="320" y1="40" x2="400" y2="40" />
        <line x1="360" y1="80" x2="360" y2="160" />
        <line x1="360" y1="240" x2="360" y2="278" />
        <line x1="400" y1="80" x2="400" y2="240" />
      </g>
      {/* question stops */}
      {[
        { x: 60, y: 60, n: 1 },
        { x: 140, y: 120, n: 2 },
        { x: 220, y: 80, n: 3 },
        { x: 320, y: 140, n: 4 },
        { x: 380, y: 220, n: 5 },
      ].map(({ x, y, n }) => (
        <g key={n}>
          <circle cx={x} cy={y} r="14" fill="var(--ae-yellow)" stroke="var(--ae-yellow-dark)" strokeWidth="1.5" />
          <text x={x} y={y + 5} textAnchor="middle"
                fontFamily="var(--ae-font-title)" fontWeight="700"
                fontSize="14" fill="var(--ae-primary-deep)">{n}</text>
        </g>
      ))}
      {/* start / end */}
      <g>
        <circle cx="20" cy="140" r="13" fill="var(--ae-success)" />
        <text x="20" y="145" textAnchor="middle" fontFamily="var(--ae-font-title)" fontSize="11" fill="white" fontWeight="700">DÉPART</text>
      </g>
      <g>
        <circle cx="420" cy="140" r="13" fill="var(--ae-orange)" />
        <text x="420" y="145" textAnchor="middle" fontFamily="var(--ae-font-title)" fontSize="11" fill="white" fontWeight="700">FIN</text>
      </g>
    </svg>
  );
}

function J8Maze({ density, qrVisible, scoreVisible, scale = 1 }) {
  const questions = [
    ["Combien de continents sur Terre ?", "5", "6", "7"],
    ["7 × 8 = ?", "54", "56", "64"],
    ["Capitale du Portugal ?", "Madrid", "Lisbonne", "Porto"],
    ["Synonyme de « rapide » ?", "Lent", "Vif", "Long"],
    ["Périmètre d'un carré de 4 cm ?", "8 cm", "12 cm", "16 cm"],
  ];

  return (
    <PageShell
      matiere="Cahier de vacances" niveau="CM2" page={"Jeux · Page 7"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="vacances-cm2-laby"
      qrLabel="Tracé corrigé"
      scale={scale}
    >
      <PageTitle
        eyebrow="Labyrinthe éducatif · Logique"
        title="Aide Ketty à rejoindre la sortie"
        subtitle="Trace le bon chemin et réponds aux 5 questions rencontrées."
      />

      <GameMeta niveau="CM2 — 6ᵉ" duree="15 min" joueurs="Seul" difficulte={2} />

      <FigureBlock label="Plan" caption="Pastilles jaunes = stations question. Vert = départ · Orange = arrivée." height="3in">
        <MazeSVG />
      </FigureBlock>

      <ExerciseCard number={1} tone="orange" consigne="À chaque pastille, entoure la bonne réponse pour pouvoir continuer.">
        <div style={{ display: "flex", flexDirection: "column", gap: "0.06in" }}>
          {questions.map(([q, ...opts], i) => (
            <div key={i} style={{
              display: "grid", gridTemplateColumns: "0.32in 1.4fr 1fr",
              alignItems: "center", gap: "0.1in",
              padding: "0.06in 0",
              borderBottom: i < questions.length - 1 ? "1px dashed var(--ae-border)" : "none",
              fontSize: "var(--ae-fs-body)",
            }}>
              <NumChip n={i + 1} tone="yellow" />
              <span>{q}</span>
              <div style={{ display: "flex", gap: "0.08in", flexWrap: "wrap" }}>
                {opts.map((o, j) => (
                  <span key={j} style={{
                    display: "inline-flex", alignItems: "center", gap: "0.05in",
                    padding: "0.04in 0.1in",
                    border: "1.4px solid var(--ae-orange)",
                    borderRadius: "999px",
                    background: "white",
                    fontFamily: "var(--ae-font-title)", fontWeight: 600,
                    color: "var(--ae-orange-dark)",
                  }}>
                    <span style={{
                      width: "0.12in", height: "0.12in",
                      border: "1.2px solid var(--ae-orange)", borderRadius: "999px",
                    }} />
                    {o}
                  </span>
                ))}
              </div>
            </div>
          ))}
        </div>
      </ExerciseCard>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   9 · COLORIAGE MAGIQUE  (template: "magic-coloring-game")
   ════════════════════════════════════════════════════════════════════════ */

const COLORING_PALETTE = [
  { n: 1, name: "Bleu",   hex: "var(--ae-primary)",     soft: "var(--ae-primary-soft)" },
  { n: 2, name: "Orange", hex: "var(--ae-orange)",      soft: "var(--ae-orange-soft)" },
  { n: 3, name: "Jaune",  hex: "var(--ae-yellow-dark)", soft: "var(--ae-yellow-soft)" },
  { n: 4, name: "Vert",   hex: "var(--ae-success)",     soft: "var(--ae-success-soft)" },
  { n: 5, name: "Rouge",  hex: "var(--ae-error)",       soft: "var(--ae-error-soft)" },
];

function ButterflyZones({ outlined = true }) {
  // Simple butterfly with 12 numbered zones
  // Layout: cols 1-12 mapped to wing zones (we keep schematic shapes)
  const zones = [
    // Body
    { d: "M 215 60 Q 225 90 215 200 Q 205 90 215 60 Z", n: 1 },
    // Left top wing
    { d: "M 210 90 Q 130 60 90 110 Q 130 140 210 130 Z", n: 2 },
    { d: "M 205 110 Q 150 100 130 130 Q 165 135 205 130 Z", n: 3 },
    // Left bottom wing
    { d: "M 210 140 Q 150 150 110 200 Q 165 200 210 180 Z", n: 4 },
    { d: "M 205 165 Q 165 175 150 195 Q 180 195 205 185 Z", n: 5 },
    // Right top wing
    { d: "M 220 90 Q 300 60 340 110 Q 300 140 220 130 Z", n: 1 },
    { d: "M 225 110 Q 280 100 300 130 Q 265 135 225 130 Z", n: 2 },
    // Right bottom wing
    { d: "M 220 140 Q 280 150 320 200 Q 265 200 220 180 Z", n: 3 },
    { d: "M 225 165 Q 265 175 280 195 Q 250 195 225 185 Z", n: 4 },
    // Antennae
    { d: "M 215 60 Q 200 40 195 30", n: 5, stroke: true },
    { d: "M 215 60 Q 230 40 235 30", n: 5, stroke: true },
  ];
  return (
    <svg viewBox="0 0 430 230" style={{ width: "100%", height: "100%" }}>
      <rect x="0" y="0" width="430" height="230" fill="white" />
      {zones.filter(z => !z.stroke).map((z, i) => (
        <g key={i}>
          <path d={z.d} fill={outlined ? "white" : "var(--ae-bg-cool)"} stroke="var(--ae-text)" strokeWidth="1.5" />
        </g>
      ))}
      {zones.filter(z => z.stroke).map((z, i) => (
        <path key={i} d={z.d} fill="none" stroke="var(--ae-text)" strokeWidth="1.5" strokeLinecap="round" />
      ))}
      {/* Numbers */}
      {[
        { x: 215, y: 130, n: 1 },
        { x: 150, y: 100, n: 2 },
        { x: 170, y: 125, n: 3 },
        { x: 160, y: 175, n: 4 },
        { x: 180, y: 190, n: 5 },
        { x: 280, y: 100, n: 1 },
        { x: 270, y: 125, n: 2 },
        { x: 270, y: 175, n: 3 },
        { x: 255, y: 190, n: 4 },
      ].map((p, i) => (
        <text key={i} x={p.x} y={p.y + 4} textAnchor="middle"
              fontFamily="var(--ae-font-title)" fontWeight="700"
              fontSize="14" fill="var(--ae-text)">{p.n}</text>
      ))}
    </svg>
  );
}

function J9MagicColoring({ density, qrVisible, scoreVisible, scale = 1 }) {
  const calcs = [
    ["6 + 2",   "= ___",   "→ Bleu (1)"],
    ["12 − 6",  "= ___",   "→ Orange (2)"],
    ["3 × 3",   "= ___",   "→ Jaune (3)"],
    ["20 ÷ 5",  "= ___",   "→ Vert (4)"],
    ["7 + 8",   "= ___",   "→ Rouge (5)"],
  ];

  return (
    <PageShell
      matiere="Maths" niveau="CE2" page={"Jeux · Page 4"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="maths-ce2-coloriagemagique"
      qrLabel="Modèle colorié"
      scale={scale}
    >
      <PageTitle
        eyebrow="Coloriage magique · Calcul"
        title="Le papillon des opérations"
        subtitle="Résous les calculs pour découvrir la couleur de chaque zone numérotée."
      />

      <Box tone="primary" label="Comment jouer" iconKey="icon-info">
        <ol className="bk-steps">
          <li><span className="n">1</span><span className="t">Calcule le résultat de chaque opération.</span></li>
          <li><span className="n">2</span><span className="t">Reporte le numéro indiqué (1 à 5) dans le code couleur.</span></li>
          <li><span className="n">3</span><span className="t">Colorie chaque zone du dessin avec la couleur correspondante.</span></li>
        </ol>
      </Box>

      <Cols ratio="1.4fr 1fr" gap="0.18in">
        <FigureBlock label="Dessin" caption="Garde un trait propre. Ne déborde pas — c'est aussi noté." height="2.5in">
          <ButterflyZones />
        </FigureBlock>

        <div style={{ display: "flex", flexDirection: "column", gap: "0.12in" }}>
          <Box tone="yellow" label="Code couleur" iconKey="icon-etoile">
            <div style={{ display: "flex", flexDirection: "column", gap: "0.05in" }}>
              {COLORING_PALETTE.map(c => (
                <div key={c.n} style={{
                  display: "flex", alignItems: "center", gap: "0.08in",
                  fontSize: "var(--ae-fs-body)",
                }}>
                  <span style={{
                    width: "0.22in", height: "0.22in",
                    borderRadius: "0.04in",
                    background: c.soft,
                    border: `1.5px solid ${c.hex}`,
                    flex: "0 0 auto",
                  }} />
                  <span style={{ fontFamily: "var(--ae-font-title)", fontWeight: 700, color: "var(--ae-primary-deep)" }}>{c.n}</span>
                  <span>{c.name}</span>
                </div>
              ))}
            </div>
          </Box>
        </div>
      </Cols>

      <ExerciseCard number={1} tone="orange" consigne="Calcule, écris le résultat, applique la couleur sur le dessin.">
        <div style={{
          display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr",
          gap: "0.1in",
        }}>
          {calcs.map(([op, eq, hint], i) => (
            <div key={i} style={{
              border: "1.5px solid var(--ae-border)",
              background: "white", borderRadius: "0.1in",
              padding: "0.08in 0.1in",
              display: "flex", flexDirection: "column", gap: "0.04in",
              alignItems: "center", textAlign: "center",
            }}>
              <span style={{ fontFamily: "var(--ae-font-mono)", fontWeight: 600, fontSize: "0.15in", color: "var(--ae-primary-deep)" }}>{op}</span>
              <span style={{ fontFamily: "var(--ae-font-mono)", fontSize: "0.13in", color: "var(--ae-muted)" }}>{eq}</span>
              <span style={{ fontSize: "var(--ae-fs-eyebrow)", color: "var(--ae-orange-dark)", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em" }}>{hint}</span>
            </div>
          ))}
        </div>
      </ExerciseCard>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   10 · PIXEL ART PAR COORDONNÉES  (template: "coordinate-pixel-art")
   ════════════════════════════════════════════════════════════════════════ */

function CoordinateGrid({ cols = 12, rows = 10, marked = [] }) {
  const CELL = 18;
  const ORIGIN_X = 22, ORIGIN_Y = 22;
  const W = ORIGIN_X + cols * CELL + 4;
  const H = ORIGIN_Y + rows * CELL + 4;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", height: "100%" }}>
      <rect width={W} height={H} fill="white" />
      {/* Grid lines */}
      {Array.from({ length: cols + 1 }).map((_, i) => (
        <line key={`v${i}`}
          x1={ORIGIN_X + i * CELL} y1={ORIGIN_Y}
          x2={ORIGIN_X + i * CELL} y2={ORIGIN_Y + rows * CELL}
          stroke="var(--ae-border)" strokeWidth="0.7" />
      ))}
      {Array.from({ length: rows + 1 }).map((_, i) => (
        <line key={`h${i}`}
          x1={ORIGIN_X} y1={ORIGIN_Y + i * CELL}
          x2={ORIGIN_X + cols * CELL} y2={ORIGIN_Y + i * CELL}
          stroke="var(--ae-border)" strokeWidth="0.7" />
      ))}
      {/* Axes */}
      <line x1={ORIGIN_X} y1={ORIGIN_Y} x2={ORIGIN_X} y2={ORIGIN_Y + rows * CELL}
            stroke="var(--ae-text)" strokeWidth="1.5" />
      <line x1={ORIGIN_X} y1={ORIGIN_Y + rows * CELL}
            x2={ORIGIN_X + cols * CELL} y2={ORIGIN_Y + rows * CELL}
            stroke="var(--ae-text)" strokeWidth="1.5" />
      {/* X labels (1..cols) */}
      {Array.from({ length: cols }).map((_, i) => (
        <text key={`xl${i}`}
          x={ORIGIN_X + i * CELL + CELL / 2}
          y={ORIGIN_Y + rows * CELL + 12}
          textAnchor="middle"
          fontSize="9" fontFamily="var(--ae-font-mono)"
          fill="var(--ae-text-soft)">{i + 1}</text>
      ))}
      {/* Y labels (1..rows from bottom) */}
      {Array.from({ length: rows }).map((_, i) => (
        <text key={`yl${i}`}
          x={ORIGIN_X - 6}
          y={ORIGIN_Y + (rows - 1 - i) * CELL + CELL / 2 + 3}
          textAnchor="end"
          fontSize="9" fontFamily="var(--ae-font-mono)"
          fill="var(--ae-text-soft)">{i + 1}</text>
      ))}
      {/* Marked cells (preview hint, light fill) */}
      {marked.map(([x, y, color], i) => (
        <rect key={i}
          x={ORIGIN_X + (x - 1) * CELL + 1}
          y={ORIGIN_Y + (rows - y) * CELL + 1}
          width={CELL - 2} height={CELL - 2}
          fill={color || "var(--ae-primary-soft)"}
          opacity="0.6" />
      ))}
    </svg>
  );
}

function J10PixelArt({ density, qrVisible, scoreVisible, scale = 1 }) {
  // Preview hints — student fills in the rest. Star shape coords (1-indexed)
  const hints = [
    [6, 9, "var(--ae-yellow-soft)"],
    [7, 9, "var(--ae-yellow-soft)"],
    [6, 8, "var(--ae-yellow-soft)"],
    [7, 8, "var(--ae-yellow-soft)"],
  ];
  const coords = [
    "(6 ; 10) · (7 ; 10)",
    "(5 ; 9) · (6 ; 9) · (7 ; 9) · (8 ; 9)",
    "(4 ; 8) · (5 ; 8) · (6 ; 8) · (7 ; 8) · (8 ; 8) · (9 ; 8)",
    "(3 ; 7) · (4 ; 7) · (10 ; 7) · (11 ; 7)",
    "(5 ; 6) · (6 ; 6) · (7 ; 6) · (8 ; 6)",
    "(6 ; 5) · (7 ; 5)",
    "(5 ; 4) · (8 ; 4)",
    "(4 ; 3) · (9 ; 3)",
  ];

  return (
    <PageShell
      matiere="Maths" niveau="6ᵉ" page={"Jeux · Page 26"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="maths-6-pixelart-etoile"
      qrLabel="Image finale"
      scale={scale}
    >
      <PageTitle
        eyebrow="Pixel art · Repérage dans le plan"
        title="L'étoile à reconstituer"
        subtitle="Colorie les cases indiquées par leurs coordonnées (x ; y) pour faire apparaître l'image."
      />

      <Box tone="green" label="Mini-rappel" iconKey="icon-info">
        <div style={{ fontSize: "var(--ae-fs-body)", lineHeight: 1.4 }}>
          On lit les coordonnées <b>(x ; y)</b> dans cet ordre :
          <b> x</b> = abscisse, axe <i>horizontal</i> (de gauche à droite). <b>y</b> =
          ordonnée, axe <i>vertical</i> (de bas en haut). On part toujours de
          l'origine, en bas à gauche.
        </div>
      </Box>

      <Cols ratio="1.4fr 1fr" gap="0.2in">
        <FigureBlock label="Repère" caption="2 cases déjà colorées comme amorce. Continue avec la liste de coordonnées." height="3in">
          <CoordinateGrid cols={12} rows={10} marked={hints} />
        </FigureBlock>

        <Box tone="primary" label="Coordonnées à colorier" iconKey="icon-cible">
          <ol style={{
            margin: 0, paddingLeft: "0.18in",
            display: "flex", flexDirection: "column", gap: "0.05in",
            fontSize: "var(--ae-fs-body)", fontFamily: "var(--ae-font-mono)",
            lineHeight: 1.35, color: "var(--ae-primary-deep)",
          }}>
            {coords.map((c, i) => <li key={i}>{c}</li>)}
          </ol>
          <div style={{
            marginTop: "0.1in",
            fontSize: "var(--ae-fs-eyebrow)",
            textTransform: "uppercase", letterSpacing: "0.06em",
            color: "var(--ae-muted)", fontWeight: 700,
          }}>Couleur conseillée : jaune.</div>
        </Box>
      </Cols>

      <Cols ratio="1fr 1fr">
        <ChallengeBox title="Défi pixel">
          Une fois l'étoile terminée, dessine ta propre image (5 cases minimum)
          et écris la liste de coordonnées en dessous. Ton voisin la testera.
        </ChallengeBox>
        <KettyBubble pose="thumbsup" tone="primary" label="Tip Ketty">
          Marque toujours <b>l'abscisse en premier</b>. Une coordonnée
          inversée fait basculer le pixel de l'autre côté du plan.
        </KettyBubble>
      </Cols>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   11 · MEMORY · PAIRES À RETROUVER  (template: "memory-pairs-game")
   ════════════════════════════════════════════════════════════════════════ */

function MemoryCard({ children, accent = "primary", small = false }) {
  const color = accent === "orange" ? "var(--ae-orange)"
    : accent === "green" ? "var(--ae-success)"
    : "var(--ae-primary)";
  return (
    <div style={{
      background: "white",
      border: `1.5px solid ${color}`,
      borderRadius: "0.1in",
      padding: small ? "0.08in 0.1in" : "0.12in",
      minHeight: small ? "0.5in" : "0.7in",
      display: "grid", placeItems: "center",
      textAlign: "center",
      fontFamily: "var(--ae-font-title)", fontWeight: 600,
      fontSize: small ? "0.13in" : "0.14in",
      color: "var(--ae-primary-deep)",
      boxShadow: "0 1.5px 0 rgba(15,26,61,.05)",
      lineHeight: 1.25,
    }}>{children}</div>
  );
}

function J11Memory({ density, qrVisible, scoreVisible, scale = 1 }) {
  // 6 paires anglais ↔ français — verbes irréguliers
  const pairs = [
    { en: "to go", fr: "aller" },
    { en: "to be", fr: "être" },
    { en: "to have", fr: "avoir" },
    { en: "to do", fr: "faire" },
    { en: "to see", fr: "voir" },
    { en: "to take", fr: "prendre" },
  ];
  // Shuffle deterministic for visual interest
  const order = [0, 5, 2, 1, 3, 4, 5, 0, 4, 3, 1, 2];
  const cards = order.map((i, idx) => idx % 2 === 0
    ? { txt: pairs[i].en, t: "en" }
    : { txt: pairs[i].fr, t: "fr" });

  return (
    <PageShell
      matiere="Anglais" niveau="6ᵉ" page={"Jeux · Page 35"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="ang-6-memory-verbes"
      qrLabel="Solution des 6 paires"
      scale={scale}
    >
      <PageTitle
        eyebrow="Memory · Vocabulaire en paires"
        title="6 verbes anglais à retrouver"
        subtitle="Découpe les 12 cartes, retourne-les face cachée, et associe chaque verbe anglais à sa traduction française."
      />

      <Box tone="primary" label="Règle du jeu" iconKey="icon-info">
        <ol className="bk-steps">
          <li><span className="n">1</span><span className="t">Découpe les 12 cartes le long des pointillés.</span></li>
          <li><span className="n">2</span><span className="t">Pose-les face cachée sur la table, en désordre.</span></li>
          <li><span className="n">3</span><span className="t">Retourne 2 cartes par tour. Si elles forment une paire (anglais / français), garde-les.</span></li>
          <li><span className="n">4</span><span className="t">Le joueur avec le plus de paires gagne. Solo : essaye en moins de 12 tours.</span></li>
        </ol>
      </Box>

      <ExerciseCard number={1} tone="orange" iconKey="icon-crayon" consigne="Cartes à découper — 6 paires (anglais ↔ français).">
        <div style={{
          display: "grid", gridTemplateColumns: "repeat(4, 1fr)",
          gap: "0.1in",
          padding: "0.08in",
          background: "var(--ae-bg-cool)",
          borderRadius: "var(--ae-r-md)",
          border: "1px dashed var(--ae-border)",
        }}>
          {cards.map((c, i) => (
            <MemoryCard key={i} accent={c.t === "en" ? "primary" : "orange"}>
              {c.txt}
            </MemoryCard>
          ))}
        </div>

        <div style={{
          marginTop: "0.14in",
          display: "grid", gridTemplateColumns: "1fr 1fr",
          gap: "0.1in",
        }}>
          <Box tone="cool" label="Score solo" iconKey="icon-trophee">
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", rowGap: "0.04in", fontSize: "var(--ae-fs-body)" }}>
              <span>Tours utilisés :</span><AnswerSlot width="0.5in" height="0.3in" />
              <span>Paires trouvées :</span><AnswerSlot width="0.5in" height="0.3in" />
              <span>Temps :</span><AnswerSlot width="0.5in" height="0.3in" />
            </div>
          </Box>
          <Box tone="green" label="Score à 2 joueurs" iconKey="icon-trophee">
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", rowGap: "0.04in", fontSize: "var(--ae-fs-body)" }}>
              <span>Joueur 1</span><AnswerSlot width="0.5in" height="0.3in" /><span>paires</span>
              <span>Joueur 2</span><AnswerSlot width="0.5in" height="0.3in" /><span>paires</span>
              <span>Vainqueur</span><span style={{ gridColumn: "2 / 4" }}><div style={{ height: "0.3in", borderBottom: "1px dotted var(--ae-text-soft)" }} /></span>
            </div>
          </Box>
        </div>
      </ExerciseCard>

      <KettyBubble pose="hello" tone="yellow" label="Astuce mémo">
        Avant de commencer le memory, lis la liste des 6 paires à voix haute
        une fois. Cela double tes chances de retrouver les paires rapidement.
      </KettyBubble>
    </PageShell>
  );
}

/* ════════════════════════════════════════════════════════════════════════
   12 · ÉNIGMES / DEVINETTES  (template: "riddle-game")
   ════════════════════════════════════════════════════════════════════════ */

function J12Riddles({ density, qrVisible, scoreVisible, scale = 1 }) {
  const riddles = [
    {
      n: 1, diff: 1,
      q: "Plus on m'enlève de matière, plus je grossis. Qui suis-je ?",
      hint: "Pense à un objet qu'on creuse dans la terre.",
    },
    {
      n: 2, diff: 2,
      q: "Je commence la nuit et je termine le matin. On me retrouve aussi au début et à la fin de l'année. Quelle lettre suis-je ?",
      hint: "Regarde la première et la dernière lettre des mots cités.",
    },
    {
      n: 3, diff: 2,
      q: "J'ai des villes, mais pas de maisons ; des forêts, mais pas d'arbres ; des rivières, mais pas d'eau. Qui suis-je ?",
      hint: "On me déplie sur une table.",
    },
    {
      n: 4, diff: 3,
      q: "Si tu me dis, je n'existe plus. Qui suis-je ?",
      hint: "Garde-le pour toi.",
    },
    {
      n: 5, diff: 3,
      q: "Mon père a 5 fils. Quatre s'appellent Lou, La, Li, Le. Comment s'appelle le cinquième ?",
      hint: "Lis bien la première phrase…",
    },
  ];

  return (
    <PageShell
      matiere="Logique" niveau="CM2 — 6ᵉ" page={"Jeux · Page 41"}
      density="std" qrVisible={qrVisible} scoreVisible={scoreVisible}
      qrSlug="logique-cm2-enigmes"
      qrLabel="Toutes les réponses"
      scale={scale}
    >
      <PageTitle
        eyebrow="Énigmes · Logique &amp; vocabulaire"
        title="5 devinettes à percer"
        subtitle="Lis chaque énigme, écris ta réponse, regarde l'indice si tu sèches plus de 2 minutes."
      />

      <GameMeta niveau="CM2 — 6ᵉ" duree="20 min" joueurs="Seul ou en équipe" difficulte={3} />

      <div style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr",
        gap: "0.16in",
      }}>
        {riddles.map(r => (
          <div key={r.n} style={{
            border: "1.5px solid var(--ae-border)",
            borderRadius: "var(--ae-r-lg)",
            background: "white",
            padding: "0.14in 0.16in",
            display: "flex", flexDirection: "column", gap: "0.08in",
            position: "relative",
          }}>
            <div style={{
              display: "flex", alignItems: "center", justifyContent: "space-between",
              gap: "0.1in",
            }}>
              <div style={{ display: "flex", alignItems: "center", gap: "0.08in" }}>
                <span style={{
                  display: "grid", placeItems: "center",
                  width: "0.34in", height: "0.34in",
                  borderRadius: "999px",
                  background: "var(--ae-primary)", color: "white",
                  fontFamily: "var(--ae-font-title)", fontWeight: 700,
                  fontSize: "0.16in",
                }}>{r.n}</span>
                <span style={{
                  fontFamily: "var(--ae-font-title)", fontWeight: 700,
                  fontSize: "var(--ae-fs-eyebrow)",
                  textTransform: "uppercase", letterSpacing: "0.08em",
                  color: "var(--ae-primary)",
                }}>Énigme {r.n}</span>
              </div>
              <Stars n={r.diff} max={3} />
            </div>

            <div style={{ fontSize: "var(--ae-fs-body)", lineHeight: 1.4, fontStyle: "italic", color: "var(--ae-text)" }}>
              « {r.q} »
            </div>

            <div style={{
              fontSize: "var(--ae-fs-eyebrow)",
              color: "var(--ae-orange)", fontWeight: 800,
              textTransform: "uppercase", letterSpacing: "0.08em",
              marginTop: "0.04in",
            }}>Ma réponse</div>
            <div style={{ height: "0.42in", border: "1px solid var(--ae-border)", borderRadius: "0.06in", background: "var(--ae-bg-cool)" }} />

            <div style={{
              borderTop: "1px dashed var(--ae-border)",
              paddingTop: "0.08in",
              fontSize: "var(--ae-fs-micro)",
              color: "var(--ae-muted)",
              display: "flex", alignItems: "flex-start", gap: "0.06in",
            }}>
              <Icon k="icon-info" />
              <span><b style={{ color: "var(--ae-text-soft)" }}>Indice :</b> {r.hint}</span>
            </div>
          </div>
        ))}
        {/* Last cell — Ketty bubble fills the grid */}
        <div style={{ gridColumn: "1 / -1" }}>
          <KettyBubble pose="thinking" tone="orange" label="Ketty te souffle">
            Une bonne énigme se résout rarement avec la première idée qui vient.
            Lis 2 fois, repère le mot piège, et essaye plusieurs interprétations
            avant de répondre. Le QR du footer dévoile les réponses détaillées.
          </KettyBubble>
        </div>
      </div>
    </PageShell>
  );
}

/* ────────────────────────────────────────────────────────────────────────
   Export
   ──────────────────────────────────────────────────────────────────────── */
Object.assign(window, {
  J1Crossword, J2WordSearch, J3SecretCode, J4OddOneOut,
  J5TrueFalse, J6Matching, J7Ordering, J8Maze,
  J9MagicColoring, J10PixelArt, J11Memory, J12Riddles,
});
