/* ============================================================
   Calcul Mental V3 — Interface élève
   ============================================================

   Le langage visuel est celui de V2 : fond dégradé indigo → violet,
   colonne étroite, cartes blanches à coins très arrondis posées sur une
   ombre douce. V3 avait remplacé tout cela par un fond gris plat et des
   cartes bordées d'un trait fin — c'est ce qui se voyait au premier
   coup d'œil.
   ============================================================ */

:root {
    /* Partagee entre l'enonce et la bonne reponse du retour d'erreur. */
    --taille-enonce: clamp(2rem, 10vw, 3.5rem);
    --primaire:      #4F46E5;
    --primaire-fonce:#3730A3;
    --fond-haut:     #667eea;
    --fond-bas:      #764ba2;
    --texte:         #1E293B;
    --surface:       #FFFFFF;
    --rayon:         16px;
    --ombre:         0 4px 24px rgba(0,0,0,.12);
}

html { overflow-y: scroll; }

body {
    /* Une couleur PLEINE sous le dégradé : elle sert de repli si le
       dégradé ne rend pas, et c'est elle que mesure l'audit de
       contraste — sans elle, un texte blanc sur dégradé se lit
       « blanc sur blanc » et le vrai défaut se noie dans le bruit. */
    background-color: #6d5fb4;
    background-image: linear-gradient(135deg, var(--fond-haut) 0%, var(--fond-bas) 100%);
    background-attachment: fixed;
    min-height: 100vh;
    color: var(--texte);
    /* La colonne de V2. */
    max-width: 480px;
    margin-inline: auto;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
}

h1 { font-size: 1.35rem; font-weight: 700; margin: 4px 0 2px; color: #fff; }
/* Sur le dégradé, le sous-titre est blanc translucide ; dans une carte,
   il reprend l'ardoise. */
.sous { font-size: .85rem; color: rgba(255,255,255,.85); margin: 0 0 14px; }
.carte .sous, .carte .titre-section + .sous { color: var(--ardoise); }

.carte {
    background: var(--surface);
    border-radius: var(--rayon);
    padding: 20px;
    box-shadow: var(--ombre);
    margin-bottom: 14px;
}

/**
 * Le titre d'une carte.
 *
 * V2 posait un titre de 1,25 rem en bleu nuit, souligné d'un filet clair
 * — c'est ce qui donne son rythme à une page empilée de cartes. V3 avait
 * mis à la place une petite ligne grise en capitales, qui se lit comme
 * une étiquette de formulaire et non comme un titre.
 *
 * La règle est ici, et non dans chaque écran : les dix écrans élève la
 * reprennent d'un coup, sans que je passe dans dix fichiers.
 */
.titre-section {
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--texte);
    margin: 0 0 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid #F1F5F9;
}

.gros {
    font-size: 2.6rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

/* ---- Boutons ---- */
.actions { display: flex; gap: 10px; margin-top: 6px; flex-wrap: wrap; }
.actions a, .actions button {
    flex: 1;
    min-width: 130px;
    min-height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 12px;
    border-radius: 12px;
    text-decoration: none;
    font-weight: 600;
    font-size: 1rem;
    font-family: inherit;
    cursor: pointer;
    border: 1px solid #E2E8F0;
    background: var(--surface);
    color: var(--texte);
}
.actions a.plein, .actions button.plein {
    background: linear-gradient(135deg, var(--primaire), #7C3AED);
    color: #fff;
    border-color: transparent;
}

/**
 * LES DEUX BOUTONS DE V2 — `.btn-outline` et `.btn-start`.
 *
 * `.contour` : fond transparent, filet BLEU de 2 px, texte bleu. C'est
 * ce qui rendait le retour visible sur la carte blanche ; V3 le posait
 * en blanc sur blanc avec un filet gris de 1 px.
 *
 * `.demarrage` : le dégradé vert-cyan du bouton « Nouvelle séance » de
 * l'accueil, pour que le même geste porte la même couleur d'un écran à
 * l'autre.
 */
.actions a.contour, .actions button.contour {
    background: transparent;
    color: var(--primaire);
    border: 2px solid var(--primaire);
}
.actions a.contour:hover { background: #EEF2FF; }
.actions a.demarrage, .actions button.demarrage {
    background: linear-gradient(135deg, #059669, #06B6D4);
    color: #fff;
    border-color: transparent;
}
/* Les icônes de V2 : 18 px, jamais comprimées par le texte. */
.actions svg { width: 18px; height: 18px; flex: none; margin-right: 8px; }

/* Cible tactile : 44 px de haut, le minimum pour un doigt sur iPad.
   Le lien de retour mesurait 55×18 — il fallait viser. */
/**
 * LE RETOUR — dans l'en-tête d'une carte, jamais sur le dégradé.
 *
 * Repris du `btn-back-solo` de V2 : flèche, le mot « Retour », en indigo,
 * à gauche du titre et séparé de lui par un filet.
 *
 * Il était blanc translucide, parce qu'il flottait au-dessus de la carte
 * sur le fond dégradé. Posé DANS la carte, il lui faut la couleur du
 * lien — c'est exactement pourquoi `solo.php` avait dû se définir un
 * `.retour-liste` de son côté, faute de quoi le sien aurait été blanc
 * sur blanc. Cette règle-ci le remplace, pour tous les écrans.
 */
.entete-carte .retour {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    font-size: .88rem;
    font-weight: 600;
    color: var(--primaire);
    text-decoration: none;
    flex: none;
    border-right: 1px solid #E2E8F0;
    padding-right: 14px;
    margin-right: 2px;
}
.entete-carte .retour svg { width: 15px; height: 15px; flex: none; }

/* ---- L'en-tête d'une carte ----
   Titre à gauche, mention à droite, filet en dessous. C'est le rythme
   de V2 ; le petit titre gris en capitales était une invention de V3. */
.entete-carte {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid #F1F5F9;
}
.entete-carte .titre { font-size: 1.25rem; font-weight: 700; color: var(--texte); }
.entete-carte .mention { font-size: .75rem; color: var(--ardoise); }

/* ---- La carte d'accueil ----
   Centrée, avec le prénom, la dernière séance, puis les grands boutons
   empilés. V3 avait remplacé tout cela par une grille de tuiles. */
.bienvenue { text-align: center; }
.bienvenue h1 { font-size: 1.5rem; color: var(--texte); margin: 0 0 4px; }
.bienvenue p  { color: var(--ardoise); margin: 0 0 20px; }

/**
 * =============================================================
 * LES BOUTONS DE DÉPART — une hiérarchie, pas une pile
 * =============================================================
 *
 * Quatre barres pleine largeur, de même taille, ne se distinguaient que
 * par la couleur. Trois choses en découlaient :
 *
 *   — le geste QUOTIDIEN — lancer une séance — pesait autant qu'une
 *     référence qu'on ouvre une fois par mois ;
 *   — le libellé flottait au milieu d'une barre de 480 px, sans point
 *     d'accroche pour l'œil ;
 *   — rien ne disait ce que chaque mode COÛTE : cinq minutes, à deux,
 *     sans chronomètre. Un élève choisit mieux quand on le lui dit.
 *
 * Trois niveaux désormais. Un bloc principal haut, avec sa pastille
 * d'icône et sa ligne d'explication. Une grille de modes en tuiles
 * carrées, où la couleur reste le repère de loin. Une ligne calme en bas
 * pour les fiches, qui ne se lancent pas — elles se consultent.
 *
 * Le contenu est ALIGNÉ À GAUCHE dans le bloc principal : c'est là que
 * l'œil arrive, et cela laisse la place à la ligne d'explication.
 */
.demarrer {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 16px 18px;
    border: 0;
    border-radius: 14px;
    color: #fff;
    font-size: 1.15rem;
    font-weight: 700;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: transform .08s, filter .15s;
    /**
     * LES DÉGRADÉS DE V2, À L'IDENTIQUE.
     *
     * J'avais foncé les extrémités claires pour passer le seuil de
     * contraste ; l'auteur a demandé les couleurs de V2, qu'il connaît et
     * qui distinguent les modes d'un coup d'œil au fond de la classe.
     * Ce sont donc celles-ci — décision du 30 juillet 2026, mesures en
     * regard dans `.tests/test_couleurs.php`.
     *
     * La couleur PLEINE sert de repli et permet la mesure : un dégradé
     * n'est pas mesurable.
     *
     * Séance : le vert-cyan de `.btn-start`.
     */
    background-color: #059669;
    background-image: linear-gradient(135deg, #059669, #06B6D4);
    /* Le blanc sur le cyan clair est juste : une ombre portée discrète
       le décolle sans changer la couleur voulue. */
    text-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}
/* La pastille d'icône : un rond translucide sur la couleur du mode.
   C'est le point d'accroche de l'œil, et il tient la hauteur du bloc. */
.demarrer .rond {
    flex: none;
    width: 46px; height: 46px;
    border-radius: 14px;
    background: rgba(255, 255, 255, .22);
    display: flex; align-items: center; justify-content: center;
}
.demarrer .rond svg { width: 24px; height: 24px; }
.demarrer .texte { flex: 1; min-width: 0; }
.demarrer .texte b { display: block; font-weight: 700; line-height: 1.2; }
/* La ligne d'explication : ce que le mode coûte, en un mot. */
.demarrer .texte span {
    display: block;
    font-size: .78rem;
    font-weight: 500;
    opacity: .85;
    margin-top: 2px;
}
/* Le chevron a ete retire : sur un bouton pleine largeur et colore, il
   n'apprenait rien — tout le bloc est deja manifestement cliquable. La
   regle reste, au cas ou un ecran en porterait encore un. */
.demarrer .fleche { display: none; }

/**
 * LES MODES SECONDAIRES, en tuiles.
 *
 * Deux colonnes : trois tuiles de 480 px de large seraient trop étroites
 * pour leur libellé, deux respirent. La couleur reste pleine — c'est elle
 * qu'on reconnaît du fond de la classe, et la réduire à un liseré
 * annulerait tout l'intérêt d'avoir repris les teintes de V2.
 */
.modes {
    display: grid;
    /* Autant de colonnes que de modes ouverts : `data-n` est pose par la
       page, qui seule sait ce que le professeur a active. Une grille figee
       a deux colonnes laissait un trou quand il n'y en avait qu'un, ou
       renvoyait le troisieme a la ligne. */
    grid-template-columns: repeat(var(--n, 2), 1fr);
    gap: 10px;
    margin-top: 12px;
}
.modes[data-n="1"] { --n: 1; }
.modes[data-n="2"] { --n: 2; }
.modes[data-n="3"] { --n: 3; }
.modes .demarrer {
    flex-direction: column;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    min-height: 104px;
    padding: 14px;
    font-size: 1rem;
}
.modes .demarrer .rond { width: 38px; height: 38px; border-radius: 11px; }
.modes .demarrer .rond svg { width: 20px; height: 20px; }
.modes .demarrer .texte { flex: none; }
.modes .demarrer .fleche { display: none; }

/* Solo : l'indigo-violet, tel qu'il est écrit en dur dans l'accueil de V2. */
.demarrer.solo {
    background-color: #4F46E5;
    background-image: linear-gradient(135deg, #4F46E5, #A855F7);
}
/**
 * Défi : LE ROUGE SEUL, du clair au foncé.
 *
 * V2 finissait ce dégradé sur du violet (#9333EA) : un saut de teinte que
 * les autres boutons ne font pas — le tournoi va d'orange à orange, les
 * fiches de bleu à bleu. À côté du solo, déjà indigo-violet, ce violet
 * brouillait en plus la distinction entre les deux modes.
 *
 * On garde le rouge de V2 comme départ et on descend dans sa propre
 * famille. Mesuré : 4,8:1 et 7,6:1 en blanc.
 */
.demarrer.defi {
    background-color: #DC2626;
    background-image: linear-gradient(135deg, #DC2626, #991B1B);
}
/**
 * Tournoi : L'ORANGE DE V2, que j'avais manqué.
 *
 * Le tournoi n'a pas de bouton d'accueil en V2, donc pas de dégradé — mais
 * il a bien une identité de couleur, dans `tournoi_match.php` :
 * `.tournoi-badge { background:#FFF7ED; color:#C2410C }`. C'est cet
 * orange-là. J'avais cherché un dégradé et conclu qu'il n'y avait rien.
 *
 * #C2410C donne 5,4:1 en blanc, #9A3412 7,3:1 : lisible aux deux bouts.
 */
.demarrer.tournoi {
    background-color: #C2410C;
    background-image: linear-gradient(135deg, #C2410C, #9A3412);
}
/**
 * LES FICHES PASSENT AU BLEU.
 *
 * Elles etaient en gris ardoise, seules en bas de carte : la couleur
 * disait « ce n'est pas un mode de jeu ». Maintenant qu'elles rejoignent
 * la ligne du solo et du defi, ce gris se lit comme un bouton DESACTIVE
 * a cote de deux tuiles colorees.
 *
 * Le bleu franc est la seule teinte franche encore libre : la seance
 * occupe le vert-cyan, le solo l'indigo-violet, le defi le rouge-violet,
 * le tournoi l'orange. Il se distingue de l'indigo du solo par sa
 * saturation, et c'est deja la couleur de l'interface du professeur.
 * Mesure : 6,7:1 et 10,4:1 en blanc.
 */
.demarrer.fiches {
    background-color: #1D4ED8;
    background-image: linear-gradient(135deg, #1D4ED8, #1E3A8A);
}

.demarrer:hover  { filter: brightness(.92); }
.demarrer:active { transform: scale(.985); }

/* Plus de regle a 560 px : le nombre de colonnes vient desormais de
   `data-n`, donc du nombre de modes reellement ouverts. Cette media query
   forcait trois colonnes et rendait la grille indifferente aux reglages —
   deux modes se partageaient deux tiers de la largeur, le dernier tiers
   restait vide. */

/* ---- Le podium ----
   Or, argent, bronze : les trois marches se distinguent par la COULEUR
   de leur colonne, pas seulement par leur hauteur. V3 les faisait
   toutes indigo. */
/**
 * =============================================================
 * LE PODIUM — OR, ARGENT, BRONZE EN APLAT
 * =============================================================
 *
 * Trois versions ont précédé celle-ci ; ce qu'elles ont appris :
 *
 *   — un DÉGRADÉ du clair au saturé délave le haut de la marche, celui
 *     qu'on regarde d'abord ;
 *   — une ARÊTE colorée sur un fond presque blanc ne suffit pas : le
 *     podium disparaît sur une carte blanche ;
 *   — le MONOCHROME est cohérent, mais un podium sans or ne dit plus rien
 *     à un élève de sixième.
 *
 * Donc : les trois métaux, en APLAT — une seule teinte par marche, aucun
 * dégradé, aucune bordure. Ils sont pris CLAIRS ET SATURÉS.
 *
 * Clairs, parce que c'est ce qui les rend reconnaissables : un or foncé
 * devient brun, un argent foncé devient gris de béton. Saturés, parce
 * qu'une première série — #FCD34D, #CBD5E1, #DDA15E — sortait terne : le
 * gris-bleu de l'argent et le tan du bronze n'avaient aucun éclat sur une
 * carte blanche.
 *
 * Les deux teintes voisines sont ÉCARTÉES à dessein : l'or tire au jaune
 * (#FFC61A), le cuivre au rouge (#E6793B). Prises toutes deux dans
 * l'orange, elles se seraient confondues.
 *
 * Le score est en bleu nuit — 9,3:1 sur l'or, 8,1:1 sur l'argent, 5:1 sur
 * le cuivre. Aucun éclat n'a été payé en lisibilité.
 *
 * Les aplats clairs se détachent mal du blanc de la carte (1,4:1) : c'est
 * l'ombre portée qui pose les marches, pas un trait.
 */
.podium { display: flex; align-items: flex-end; justify-content: center; gap: 10px; }
.podium .place { display: flex; flex-direction: column; align-items: center; flex: 1;
                 min-width: 0; }
.podium .medaille { font-size: 1.9rem; line-height: 1; margin-bottom: 8px; }

.podium .marche {
    width: 100%;
    border-radius: 14px 14px 0 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 12px;
    /* Pas de bordure : une ombre douce, qui détache la marche du blanc de
       la carte sans ajouter de ligne. */
    box-shadow: 0 2px 10px rgba(15, 23, 42, .10);
}
.podium .points { font-size: 1.4rem; font-weight: 800; color: var(--texte);
                  font-variant-numeric: tabular-nums; }

/**
 * 1er enfant = 2ᵉ place, 2e enfant = 1ᵉʳ, 3e enfant = 3ᵉ — l'ordre de la
 * vraie estrade, le plus haut au milieu. C'est celui de V2.
 */
.podium .place:nth-child(1) .marche { background: #AFC3DE; }   /* argent */
.podium .place:nth-child(2) .marche { background: #FFC61A; }   /* or     */
.podium .place:nth-child(3) .marche { background: #E6793B; }   /* cuivre */

/* Le prénom SOUS la marche, sur la ligne de base commune. Il flottait
   au-dessus de barres de hauteurs différentes : rien ne se comparait. */
.podium .qui { font-size: .92rem; font-weight: 700; text-align: center; margin-top: 8px;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* ---- Les progressions ---- */
.progres { display: flex; align-items: center; gap: 12px; padding: 10px 0;
           border-bottom: 1px solid #F1F5F9; }
.progres:last-child { border-bottom: 0; }
.progres .medaille { font-size: 1.3rem; }
.progres .qui { flex: 1; min-width: 0; }
.progres .qui b { display: block; font-weight: 700; font-size: .95rem; }
.progres .qui span { font-size: .76rem; color: var(--ardoise); font-variant-numeric: tabular-nums; }
.progres .gain { background: #F0FDF4; color: #047857; font-weight: 700; font-size: .85rem;
                 padding: 4px 10px; border-radius: 20px; white-space: nowrap;
                 font-variant-numeric: tabular-nums; }

/* ---- Les écrans de résultat et de bilan ----
   V2 les centrait, avec un très grand score en tête. */
.resultat, .entete-bilan { text-align: center; }
.resultat .emoji { font-size: 2.8rem; }
.resultat h2 { font-size: 1.4rem; font-weight: 800; margin: 6px 0 2px; }
.entete-bilan .gros { color: var(--texte); }
.entete-bilan .pct { font-size: 1.1rem; font-weight: 600; color: var(--primaire); }

/**
 * LES ACTIONS D'UNE CARTE — V2 : `.bilan-actions`, margin-top 20 px,
 * centrées, DANS la carte.
 *
 * V3 laissait ces boutons flotter sous les cartes, sans fond, sans bord
 * de carte : le seul endroit de l'application où un bouton
 * n'appartenait à rien. On y revient — 20 px du contenu qui précède,
 * centrés, et bornés en largeur pour qu'ils ne s'étirent pas sur la
 * pleine laize d'un écran d'ordinateur.
 */
.carte .actions { margin-top: 20px; justify-content: center; }
/* Les boutons de sortie d'un écran de fin sont bornés en largeur : ils ne
   doivent pas s'étirer sur la pleine laize d'un ordinateur. Les boutons
   d'une salle d'attente, eux, restent pleine largeur — c'est le
   `width:100%` de V2 sur « Lancer le défi ». */
.actions.compactes a, .actions.compactes button { max-width: 260px; }

/* ---- Les tuiles de l'accueil ---- */
.tuiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.tuiles a {
    display: block;
    padding: 20px 14px;
    border-radius: var(--rayon);
    text-decoration: none;
    text-align: center;
    background: var(--surface);
    color: var(--texte);
    box-shadow: var(--ombre);
}
.tuiles a.plein {
    background: linear-gradient(135deg, #4F46E5, #A855F7);
    color: #fff;
}
.tuiles b { display: block; font-size: 1.05rem; }
.tuiles span { font-size: .78rem; opacity: .75; }

/* ---- La navigation de l'élève ----
   Une CARTE EN HAUT de la colonne, comme V2 (`order:-1`), et non une
   barre fixée au bas de la fenêtre. La barre fixe s'étirait sur toute
   la largeur d'un écran d'ordinateur, très loin sous une colonne de
   480 px : les quatre entrées se retrouvaient aux quatre coins, sans
   rapport visible avec la page. */
.barre-bas {
    display: flex;
    justify-content: space-around;
    background: var(--surface);
    border-radius: var(--rayon);
    padding: 8px;
    box-shadow: var(--ombre);
    margin-bottom: 16px;
}
.barre-bas a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 56px;
    padding: 8px 16px;
    border-radius: 10px;
    text-decoration: none;
    color: var(--ardoise);
    font-size: .75rem;
    font-weight: 600;
}
.barre-bas a.actif, .barre-bas a:hover { background: #EEF2FF; color: var(--primaire); }
.barre-bas svg { width: 22px; height: 22px; }


/* ============================================================
   Les composants de V2, repris pour les écrans restants.
   Les noms de classe sont ceux de V3 ; c'est le TRAITEMENT qui
   revient à V2 — cela évite de renommer dans dix fichiers.
   ============================================================ */

/* ---- Les chiffres clés (stats) ----
   V2 en faisait des pastilles grises sur DEUX colonnes, valeur en
   1,75 rem indigo. V3 les alignait à plat, sans fond. */
/* Autant de colonnes que la largeur en permet : l'accueil en affiche
   TROIS, les statistiques quatre. Une grille figée à deux colonnes
   laissait le troisième chiffre seul sur sa ligne, avec un trou à
   côté. */
.chiffres { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 12px; }
.chiffres div {
    background: #F1F5F9;
    border-radius: 12px;
    padding: 16px 12px;
    text-align: center;
    min-width: 0;
}
.chiffres b {
    display: block;
    font-size: 1.75rem;
    font-weight: 800;
    color: var(--primaire);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
.chiffres span {
    display: block;
    font-size: .72rem;
    /* --ardoise sur la pastille #F1F5F9 : 4,34:1, juste sous la barre. */
    color: #566072;
    margin-top: 4px;
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* ---- Une ligne « par type de calcul » ----
   Nom à gauche sur largeur fixe, barre au milieu, pourcentage et temps
   empilés à droite. V3 laissait le nom pousser la barre. */
.type { display: flex; align-items: center; gap: 10px; padding: 10px 0;
        border-bottom: 1px solid #F1F5F9; border-top: 0; }
.type:last-child { border-bottom: 0; }
/**
 * LE NOM ET LA JAUGE SE PARTAGENT LA LIGNE.
 *
 * V2 figeait le libellé à 130 px, et V3 avait repris la valeur :
 * « Multiplication de relatifs (facteur négatif) » s'arrêtait à
 * « Multiplication de re… », et deux lignes voisines portaient le même
 * texte tronqué. La carte disait de travailler quelque chose sans dire
 * quoi.
 *
 * 252 px logent le plus long libellé du bundle — 246 px mesurés — et la
 * jauge prend TOUT le reste : 294 px sur un ordinateur, contre 110 px
 * quand le nom absorbait l'espace.
 *
 * LA BASE DU NOM EST FIXE, ET C'EST ESSENTIEL. Quand elle valait `auto`,
 * chaque ligne réservait la largeur de SON texte : sur un téléphone, les
 * barres n'avaient plus la même longueur d'une ligne à l'autre, et deux
 * taux de réussite ne se comparaient plus d'un coup d'œil — ce qui est
 * pourtant la seule chose que cette carte a à faire. Avec une base
 * identique partout, toutes les lignes rétrécissent ensemble.
 */
.type b { flex: 0 1 252px; min-width: 0; font-size: .82rem; font-weight: 600;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.jauge { flex: 1 1 110px; min-width: 70px; height: 10px; background: #E2E8F0;
         border-radius: 5px; overflow: hidden; }
.jauge i { display: block; height: 100%; border-radius: 5px; }
/* Le `.type-stat-right` de V2 : les deux chiffres EMPILES, cales a
   droite. Cote a cote, « 78 % » et « 4,4 s » se lisaient comme une seule
   valeur, et la ligne s allongeait d autant. */
.type .droite { display: flex; flex-direction: column; align-items: flex-end;
                flex: none; min-width: 44px; }
.type .pct { font-size: .875rem; font-weight: 700; text-align: right; }
.type .tps { font-size: .68rem; color: var(--ardoise); text-align: right; }
/* Dans « À travailler en priorité », chaque ligne EST un lien vers le
   type : c'est ce qui justifie que la carte vive sur l'écran du solo
   plutôt que dans l'historique. Elle doit donc se voir cliquable, et
   offrir une cible d'au moins 44 px. */
.type.priorite {
    text-decoration: none;
    color: inherit;
    min-height: 56px;
    border-radius: 10px;
    padding: 10px 8px;
    margin: 0 -8px;
}
.type.priorite:hover  { background: #F8FAFC; }
.type.priorite:active { background: #F1F5F9; }
/* Le nom prend la largeur disponible : `.type b` est calé à 130 px pour
   la grille des statistiques, où il fait colonne. Ici il est seul sur sa
   ligne, au-dessus de la barre — le tronquer à 130 px couperait
   « Écriture simplifiée (soustraction) » au tiers. */
.type.priorite b { width: auto; color: var(--primaire); font-size: .9rem; }

/* Le chiffre, et sous lui ce qu'il compte : « 21 réussis sur 40 ». Sans
   cette seconde ligne, « 21/40 » n'était qu'une paire de nombres. */
.type .compte { text-align: right; flex: none; min-width: 92px; }
.type .compte b { display: block; width: auto; font-size: .95rem; font-weight: 700; }
.type .compte em { display: block; font-style: normal; font-size: .68rem;
                   color: var(--ardoise); white-space: nowrap; }

/* ---- Le graphique des dernières parties ----
   V2 : 100 px de haut, barres indigo translucides. */
.graphe { display: flex; align-items: flex-end; gap: 5px; height: 100px; padding: 0 4px; }
.graphe .colonne { flex: 1; display: flex; flex-direction: column; align-items: center;
                   height: 100%; justify-content: flex-end; gap: 3px; min-width: 0; }
.graphe .val  { font-size: .62rem; font-weight: 700; color: var(--ardoise); }
.graphe .tige { width: 100%; border-radius: 4px 4px 0 0; background: var(--primaire);
                opacity: .6; min-height: 4px; }
.graphe .jour { font-size: .6rem; color: var(--ardoise); white-space: nowrap; }

/* ---- Le bilan ----
   Score en 3 rem indigo, puis la liste des calculs. */
.entete-bilan .gros { font-size: 3rem; color: var(--primaire); }
.entete-bilan .pct  { color: var(--ardoise); font-weight: 400; margin-top: 2px; }

.ligne { display: flex; align-items: center; gap: 8px; padding: 10px 0;
         border-bottom: 1px solid #F1F5F9; border-top: 0; font-size: .9rem; }
.ligne:last-child { border-bottom: 0; }
.ligne .puce { font-size: 1.4rem; font-weight: 700; flex: none; }
.ligne .duree { font-size: .75rem; color: var(--ardoise); white-space: nowrap;
                min-width: 3em; text-align: right; flex: none; margin-left: auto; }
.calcul .donnee { color: var(--faux); font-weight: 600; }

/* ---- Le salon de défi ----
   Le code se lit à voix haute depuis l'autre bout de la classe. */
.code { font-size: 2.6rem; font-weight: 800; letter-spacing: .3em; text-align: center;
        font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
        color: var(--primaire); background: #EEF2FF; border-radius: 12px;
        padding: 14px 20px; border: 0; width: 100%; cursor: pointer; }
.joueurs li { background: #F8FAFC; border-radius: 10px; padding: 10px 14px;
              margin-bottom: 8px; border: 0; font-weight: 600; }

/* ---- L'écran de connexion ----
   La COMPOSITION de V2, pas seulement ses couleurs : carte centrée dans
   la page, titre et onglets DEDANS, onglets en piste segmentée, bouton
   plat, champs à bordure de 2 px qui vire à l'indigo au focus.

   Reprendre les jetons de V2 sans reprendre sa mise en page donnait la
   bonne matière et la mauvaise page — c'est ce qui se voyait au premier
   coup d'œil sur cet écran. */
body.connexion {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    max-width: none;
}
.connexion .carte { width: 100%; max-width: 400px; padding: 28px; margin: 0; }
.connexion h1 { font-size: 1.75rem; font-weight: 800; color: var(--texte); margin: 0 0 6px; }
.connexion .sous { color: var(--ardoise); margin: 0 0 26px; }

/* La piste segmentée : le fond gris porte les onglets, l'actif ressort
   en pastille blanche posée dessus. */
.onglets {
    display: flex;
    gap: 4px;
    margin-bottom: 22px;
    background: #F1F5F9;
    border-radius: 10px;
    padding: 4px;
}
.onglets a, .onglets button {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    border-radius: 8px;
    text-decoration: none;
    font-size: .9rem;
    font-weight: 500;
    /**
     * UN BOUTON N'EST PAS UN LIEN.
     *
     * Ces onglets étaient des liens `<a>` ; les passer en `<button>` pour
     * supprimer le rechargement leur a rendu les décorations par défaut du
     * navigateur — bordure grise en relief, fond gris, police système.
     * Un lien n'en a aucune, d'où le trait noir apparu autour des deux
     * onglets. On les efface explicitement.
     */
    border: 0;
    background: none;
    font-family: inherit;
    cursor: pointer;
    padding: 0;
    appearance: none;
    /* --ardoise (#64748B) sur la piste #F1F5F9 : 4,34:1, juste sous la
       barre. L'onglet INACTIF est du texte, pas un décor. */
    color: #566072;
}
.onglets a.actif, .onglets button.actif {
    background: var(--surface);
    color: var(--primaire);
    font-weight: 600;
    box-shadow: 0 1px 4px rgba(0,0,0,.1);
}

.champ { margin-bottom: 16px; }
.champ label {
    display: block;
    font-size: .85rem;
    font-weight: 600;
    margin-bottom: 6px;
    color: var(--texte);
}
.champ input {
    width: 100%;
    /* HAUTEUR FIXE. Le champ du code est en 1,4 rem monospace : sans
       hauteur imposée, il rendait la carte « Famille » six pixels plus
       haute, et tout sautait en changeant d'onglet. */
    height: 54px;
    padding: 0 14px;
    border: 2px solid #E2E8F0;
    border-radius: 10px;
    font-size: 1rem;
    font-family: inherit;
    outline: none;
    transition: border-color .15s;
}
.champ input:focus { border-color: var(--primaire); }
/**
 * LE CHAMP DU CODE EST UN CHAMP, PAS UN AFFICHAGE.
 *
 * `.code` sert d'abord à MONTRER le code d'une classe sur l'écran du
 * professeur : 2,6 rem, fond lavande, cliquable pour le copier, celui
 * qu'on vidéoprojette le jour de la distribution. Le champ de saisie de
 * l'onglet Famille porte la même classe pour hériter de sa police
 * chasse fixe et de son espacement — et il héritait aussi du FOND, qui
 * n'a aucun sens sur un formulaire : à côté de « Votre prénom », resté
 * blanc, il avait l'air d'être déjà rempli ou désactivé.
 *
 * On ne reprend donc de `.code` que ce qui concerne le TEXTE, et l'on
 * rend au champ l'apparence des autres.
 */
.champ input.code {
    text-align: center;
    letter-spacing: .2em;
    text-transform: uppercase;
    font-size: 1.4rem;
    font-family: ui-monospace, Menlo, monospace;
    background: var(--surface);
    color: var(--texte);
    cursor: text;
}

/* Plat, comme V2 — le dégradé était une invention. */
.connexion .entrer {
    width: 100%;
    margin-top: 20px;
    padding: 14px;
    background: var(--primaire);
    color: #fff;
    border: 0;
    border-radius: 10px;
    font-size: 1rem;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}
.connexion .entrer:hover { background: var(--primaire-fonce); }
.connexion .erreur { text-align: center; margin-bottom: 16px; }

/**
 * L'AVIS DE BASCULE — temporaire, à retirer à la rentrée.
 *
 * Mêmes teintes que le bandeau d'erreur de `prof.css` : fond #FEE2E2,
 * texte #991B1B, filet de 4 px. Ce n'est pas une erreur, mais c'est un
 * avertissement, et la maison n'a qu'un rouge — en inventer un seul pour
 * cet écran ferait un deuxième vocabulaire pour trois semaines.
 *
 * Contraste #991B1B sur #FEE2E2 : 6,80:1, le même que le badge d'erreur.
 *
 * Le texte est aligné à gauche alors que la carte est centrée : cinq
 * lignes centrées se lisent mal, chaque ligne repartant d'une abscisse
 * différente.
 */
.connexion .avis-bascule {
    background: #FEE2E2;
    color: #991B1B;
    border-left: 4px solid var(--faux);
    border-radius: 8px;
    padding: 12px 14px;
    margin: 0 0 22px;
    font-size: .8125rem;
    line-height: 1.45;
    text-align: left;
}
.connexion .avis-bascule strong { display: block; margin-bottom: 4px; font-weight: 700; }

/* L'adresse hérite du rouge du bandeau : l'indigo des liens du site
   passerait mal sur ce fond, et le soulignement suffit à dire que c'est
   cliquable. `break-word` parce qu'une adresse ne se coupe pas toute
   seule et déborderait de la carte sur un téléphone étroit. */
.connexion .avis-bascule a {
    color: inherit;
    text-decoration: underline;
    word-break: break-word;
}

/* ---- L'écran de jeu ----
   La composition de V2 : le MÊME fond dégradé que le reste de
   l'application — V3 l'avait remplacé par un gris plat, si bien que
   l'élève changeait d'univers en commençant à jouer. L'en-tête est une
   carte, l'énoncé une carte, le pavé un clavier à trois colonnes. */
body.jeu {
    padding: 12px;
    padding-bottom: calc(12px + env(safe-area-inset-bottom));
    overflow: hidden;
    /**
     * TROIS HAUTEURS, DE LA PLUS ANCIENNE À LA MEILLEURE.
     *
     * `100vh` ment sur mobile : il vaut la hauteur SANS la barre
     * d'adresse, donc la page déborde tant qu'elle est affichée — la
     * dernière rangée du pavé passait sous l'écran.
     *
     * `--vh` est posé par JavaScript depuis `innerHeight`. Exact, mais il
     * ne vaut que ce que valent les évènements qui le rafraîchissent.
     *
     * `100svh` est la bonne unité, et `dvh` était un faux ami.
     *
     * `dvh` suit la barre — mais cette page a `overflow: hidden`, donc
     * elle ne défile JAMAIS. Or c'est le défilement qui escamote la barre
     * sur Android : elle reste donc affichée en permanence, pendant que
     * `dvh` continue de valoir la hauteur SANS elle. Le corps dépassait
     * ainsi d'une centaine de pixels et la dernière rangée du pavé
     * passait sous l'écran — le défaut qu'on croyait corrigé.
     *
     * `svh` est la hauteur quand l'interface du navigateur est AFFICHÉE,
     * c'est-à-dire le pire cas. Elle ne dépend d'aucun évènement et ne
     * varie pas : rien ne peut plus être coupé.
     */
    height: 100vh;
    height: var(--vh, 100vh);
    height: 100svh;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/**
 * L'EN-TÊTE EN TROIS COLONNES, pour que le score soit VRAIMENT centré.
 *
 * `justify-content: space-between` ne centre rien : il répartit l'espace
 * entre trois blocs de largeurs différentes, si bien que le score se
 * décalait du côté du bloc le plus étroit. Une grille `1fr auto 1fr`
 * garantit le centrage quelles que soient les largeurs — un bouton de
 * plus à droite ne le déplacera pas.
 */
body.jeu .entete {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    background: var(--surface);
    border-radius: var(--rayon);
    padding: 12px 16px;
    box-shadow: var(--ombre);
    flex: none;
}
body.jeu .entete > :last-child { justify-self: end; }

/**
 * LES OUTILS DE L'EN-TÊTE — pavé, signalement, sortie.
 *
 * Sans bordure ni fond, en gris, comme `.numpad-toggle-lbl` et
 * `.seance-logout` de V2. Trois rectangles bordés à côté d'un score en
 * gros chiffres attiraient l'œil vers ce qui compte le moins.
 *
 * Plus larges qu'un carré de 44 px : ce sont des cibles qu'on touche du
 * pouce, tablette en main, et sur lesquelles on ne veut pas viser.
 * #64748B ne donnant que 4,34:1 sur blanc, le gris est une nuance plus
 * sombre — la même que partout ailleurs.
 */
.entete .outil {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 58px;
    min-height: 50px;
    padding: 0 14px;
    border: 0;
    background: none;
    border-radius: 10px;
    color: #566072;
    font-family: inherit;
    font-size: .92rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: background .15s, color .15s;
}
.entete .outil svg { width: 22px; height: 22px; }
.entete .outil:hover  { background: #EEF2FF; color: var(--primaire); }
.entete .outil:active { background: #E0E7FF; }

/**
 * « CE CALCUL EST SIGNALÉ » — jusqu'au calcul suivant.
 *
 * Le ✓ REMPLACE la coccinelle le temps du calcul en cours : deux
 * pictogrammes côte à côte se liraient comme deux boutons. Le vert dit
 * que c'est parti, sans texte à lire pendant un chronomètre.
 *
 * Le dessin est caché par `visibility`, pas par `display` : le bouton
 * garde ainsi exactement la même largeur, et la barre du haut ne bouge
 * pas au moment où l'élève tape dessus.
 */
#signaler { position: relative; }
#signaler.signale { color: var(--juste-texte); }
#signaler.signale svg { visibility: hidden; }
#signaler.signale::after {
    content: "✓";
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.35rem;
    font-weight: 700;
}

/**
 * LE MINUTEUR, COLLÉ À GAUCHE.
 *
 * Deux erreurs successives, pour la même raison — la première colonne de
 * la grille fait `1fr`, donc elle est LARGE, et ce qu'on y écrit flotte
 * si on ne le cale pas.
 *
 *   — `text-align: center` centrait l'étiquette et la valeur dans toute
 *     cette largeur : le minuteur se retrouvait au milieu du tiers
 *     gauche, loin du bord ;
 *   — sans rien, l'étiquette « TEMPS RESTANT », plus large que « 3:49 »,
 *     décalait la valeur sous son propre titre.
 *
 * `justify-self: start` fait rétrécir le bloc à son contenu et le pose au
 * bord ; `text-align: left` aligne l'étiquette et le chiffre sur la même
 * verticale. Les deux ensemble, sinon l'un des deux défauts revient.
 */
.entete > :first-child {
    justify-self: start;
    text-align: left;
}

/* L'unité d'arrivée, posée après le champ de réponse. */
.unite {
    flex: none;
    font-size: clamp(1.6rem, 6vw, 2.6rem);
    font-weight: 800;
    color: var(--texte);
    margin-left: 4px;
}
/* La sortie vire au rouge au survol, comme en V2 : c'est le seul de ces
   trois gestes qui interrompe la séance. */
.entete a.outil:hover { background: #FEE2E2; color: #B91C1C; }
.minuteur { font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums;
            color: var(--primaire); transition: color .3s; }
/* Le clignotement de la dernière minute — il se voit du fond de la classe. */
.minuteur.urgent { color: var(--faux); animation: battement 1s infinite; }
@keyframes battement { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

.legende { font-size: .72rem; color: var(--ardoise); text-transform: uppercase;
           letter-spacing: .05em; text-align: center; }
.bloc-score { text-align: center; }
.bloc-score .valeur { font-size: 2rem; font-weight: 700; color: var(--primaire);
                      font-variant-numeric: tabular-nums; }

/**
 * =============================================================
 * LA CARTE D'ÉNONCÉ NE BOUGE PLUS
 * =============================================================
 *
 * Elle était dimensionnée par son contenu, avec un simple `min-height` —
 * comme en V2. Une fraction empilée fait deux lignes : la carte grandissait
 * d'un coup, poussait le champ de réponse vers le bas, puis rétrécissait au
 * calcul suivant. Sur une séance de cinq minutes, tout l'écran respirait à
 * chaque changement de type.
 *
 * La carte ABSORBE désormais l'espace libre (`flex: 1`). Sa hauteur ne
 * dépend plus que de l'écran : elle est la même pour « 4 × 5 » et pour une
 * somme de fractions, et le champ de réponse reste exactement où il est.
 *
 * C'est aussi ce qui fait que masquer le pavé agrandit vraiment l'énoncé,
 * au lieu de laisser un blanc en dessous.
 */
.zone { flex: 1; min-height: 0; display: flex; flex-direction: column;
        justify-content: stretch; gap: 16px;
        /* GARDE STRUCTURELLE. Sur un ecran tres court, cette zone se fait
           ecraser a quelques pixels par le pave, qui garde sa taille — et
           ses cartes, dimensionnees par leur contenu, debordaient
           PAR-DESSUS les touches, qui devenaient inatteignables. Elles
           sont desormais rognees : le pave reste toujours cliquable, quoi
           qu'il arrive a la hauteur disponible. */
        overflow: hidden; }

/**
 * LA CARTE GARDE LA TAILLE DE V2 — et ne bouge plus.
 *
 * Premiere tentative : `flex: 1`, pour qu'elle absorbe l'espace libre.
 * Sa hauteur devenait bien constante, mais ENORME — 510 px sur un iPad,
 * la moitie de l'ecran pour « 4 × 5 ». Ce n'est pas ce que faisait V2.
 *
 * La bonne place pour reserver la hauteur est l'ENONCE lui-meme, en `em` :
 * une fraction empilee fait deux lignes, donc on reserve de quoi les
 * tenir. La carte reste donc a l'echelle de V2, la meme pour un entier et
 * pour une somme de fractions, et elle suit la police si elle change.
 */
.carte-enonce {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    padding: 30px 24px; margin: 0;
    flex: none;
}
.carte-saisie { flex: none; }
.enonce {
    font-size: var(--taille-enonce);
    font-weight: 700; text-align: center; color: var(--texte);
    letter-spacing: -.02em; user-select: none;
    /* 2,4 em : de quoi tenir une fraction empilee — deux lignes a 1,15 de
       hauteur — sans que la carte grandisse quand elle apparait. En `em`
       et non en pixels, pour suivre le `clamp` ci-dessus. */
    min-height: 2.4em;
    display: flex; align-items: center; justify-content: center;
    width: 100%;
}

/* Le retour visuel occupe TOUTE la carte, sur fond blanc, en très
   grand : c'est ce qui le rend lisible sans regarder l'écran de près. */
.retour-verdict {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    border-radius: var(--rayon);
    background: #fff;
    font-weight: 800; text-align: center;
    /* 4 px et non 12 : le contenu est centré dans une carte, il n’a pas
       besoin d’une marge intérieure. Les 24 px que prenaient les deux
       côtés suffisaient à faire déborder la coche. */
    padding: 4px;
    /* La boîte de ligne colle au signe. Par défaut elle le dépasse de
       quelques pixels en haut et en bas, ce qui suffisait à faire
       déborder la coche de sa carte sur deux tailles d’écran. */
    line-height: 1;
    opacity: 0; transition: opacity .2s; pointer-events: none; z-index: 10;
}
/**
 * LES VERTS ET ROUGES DE V2, à l'identique — décision de l'auteur du
 * 30 juillet 2026. Ce sont ceux qu'il connaît et que la classe associe
 * à juste et faux.
 *
 * Ils sont sous le seuil de contraste : #10B981 sur blanc donne 2,54:1,
 * #EF4444 3,76:1. Le signe fait ici 9,5 rem — c'est un pictogramme plus
 * qu'un texte, et il occupe toute la carte. Mesurés dans
 * `.tests/test_couleurs.php`, exemptés nommément de l'audit.
 */
/* Borne aussi par la HAUTEUR : en téléphone couché, la carte de
   l’énoncé ne fait plus que quelques dizaines de pixels, et un signe
   calculé sur la seule largeur la débordait. Le `min()` retient la plus
   petite des deux mesures. */
.retour-verdict.juste { color: #10B981; font-size: min(clamp(4rem, 22vw, 9.5rem), 42vh); }
/* 5,5 rem, la taille de V2, pour la CROIX seule. */
.retour-verdict.faux  { color: #EF4444; font-size: min(clamp(3rem, 14vw, 5.5rem), 22vh); }

/**
 * LA BONNE RÉPONSE, À LA TAILLE DE L'ÉNONCÉ.
 *
 * V2 posait tout le retour à 5,5 rem, croix et réponse comprises — mais
 * sa réponse était une ligne de texte : « 1/3 ». Depuis que les
 * fractions s'empilent, la même valeur occupe DEUX lignes, et la carte
 * se remplit d'un seul nombre.
 *
 * La croix garde donc les 5,5 rem de V2 ; la réponse reprend
 * `clamp(2rem, 10vw, 3.5rem)`, exactement la taille à laquelle l'élève
 * vient de lire l'énoncé — c'est la comparaison qu'il doit faire.
 */
.retour-verdict .attendue {
    font-size: var(--taille-enonce);
    margin-left: .25em;
}
.retour-verdict.forme { color: var(--primaire); font-size: clamp(1.1rem, 4.5vw, 2.2rem); }

.carte-saisie { display: flex; align-items: center; gap: 16px; padding: 18px 22px; margin: 0; }
.egal { font-size: clamp(2rem, 9vw, 3rem); font-weight: 700; color: var(--texte); flex: none; }

/* La réponse est une BOÎTE, pas un trait souligné : elle se voit, et on
   sait où regarder. */
.saisie {
    flex: 1; min-width: 0;
    font-size: clamp(2rem, 10vw, 3.5rem);
    font-weight: 700; line-height: 1.2; min-height: 1.6em;
    background: #F1F5F9;
    border: 2px solid var(--primaire);
    border-radius: 12px;
    display: flex; align-items: center; justify-content: center;
    color: var(--primaire); letter-spacing: .05em;
}
.indice { font-size: .78rem; color: var(--ardoise); margin-top: 6px; text-align: center; }
.indice b { color: var(--primaire); }

/* Le pavé : TROIS colonnes, comme un clavier de téléphone. V3 en
   alignait cinq, ce qui donnait des touches deux fois plus petites. */
.pave {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    width: 100%; max-width: 340px; margin: 0 auto;
    flex: none;
}
.pave.masque { display: none; }
/**
 * LES TOUCHES — centrées verticalement, pour de bon.
 *
 * Un `<button>` centre son texte sur la LIGNE DE BASE, pas sur la boîte :
 * avec une hauteur imposée de 68 px, les chiffres se posaient un peu bas,
 * et le décalage se voyait d'autant plus que « ⌫ » et « ✓ » n'ont pas le
 * même dessin que les chiffres. Un conteneur flex centre la boîte, ce qui
 * est ce qu'on veut ici.
 *
 * La police est celle de l'interface, en CHIFFRES TABULAIRES : sans cela,
 * le « 1 » est plus étroit que le « 8 » et la colonne du pavé ondule.
 */
.pave button {
    /**
     * LES TOUCHES RESPIRENT AVEC LA HAUTEUR — jamais sous 44 px.
     *
     * À 68 px fixes, cinq rangées et leurs écarts réclament 380 px. Sur
     * un téléphone dont la barre d'adresse mange 115 px, il ne restait
     * plus de quoi les poser toutes : `overflow: hidden` en coupait une.
     *
     * Elles se contractent donc avec l'écran, et s'arrêtent à 44 px —
     * le plancher du doigt, que l'audit de V2 avait déjà refusé de
     * franchir. `68px` reste déclaré au-dessus pour les navigateurs qui
     * ignorent `svh`.
     */
    height: 68px;
    height: clamp(44px, 7.4svh, 68px);
    padding: 0;
    display: flex; align-items: center; justify-content: center;
    line-height: 1;
    font-size: 1.5rem; font-weight: 600; font-family: inherit;
    font-variant-numeric: tabular-nums;
    border: 0; border-radius: 14px;
    background: var(--surface); color: var(--texte);
    box-shadow: 0 2px 8px rgba(0,0,0,.1);
    cursor: pointer; user-select: none;
    transition: transform .08s, box-shadow .08s;
}
.pave button:active { transform: scale(.94); box-shadow: 0 1px 3px rgba(0,0,0,.1); }
/* Chaque touche particulière a sa teinte — on ne cherche pas la virgule. */
.t-eff     { background: #FEE2E2 !important; color: #B91C1C !important; }
.t-barre   { background: #F0FDF4 !important; color: #15803D !important; font-size: 1.6rem; }
.t-moins   { background: #FFF7ED !important; color: #C2410C !important; font-size: 1.6rem; }
.t-virgule { font-size: 1.6rem; }
.t-valider { background: var(--primaire) !important; color: #fff !important; font-size: 1.35rem; }
.t-valider:active { background: var(--primaire-fonce) !important; }

.decompte { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.decompte span { font-size: 3rem; font-weight: 800; color: var(--primaire); }
.decompte em   { font-size: .85rem; color: var(--ardoise); font-style: normal; }


/* ============================================================
   LES PALIERS DE V2 — les neuf, cette fois
   ============================================================

   V3 n'avait AUCUNE media query côté élève : la colonne de 480 px valait
   pour un téléphone comme pour un écran de 27 pouces. Sur un ordinateur,
   l'accueil s'affichait donc en tout petit au milieu d'un grand vide.

   V2 en avait NEUF. J'en avais d'abord porté deux — celles de
   l'ordinateur, parce que c'était le symptôme signalé. Les autres
   traitaient un cas que je n'avais pas envisagé et qui est le plus
   grave : LES ÉCRANS COURTS.

   L'écran de jeu est en `height: var(--vh)` avec `overflow: hidden`.
   Empilé, il demande environ 700 px : en-tête, carte énoncé, carte
   saisie, puis cinq rangées de touches de 68 px. Sur un téléphone
   couché — 390 px de haut — ou sur un petit téléphone debout, le bas du
   pavé sortait de la fenêtre et se trouvait COUPÉ. La touche de
   validation devenait inatteignable en pleine séance chronométrée.
   V2 réduisait les touches à 56 px puis à 48 px pour cette raison.
   ============================================================ */

/* ---- Petit téléphone (≤ 360 px de large) ---- */
@media (max-width: 360px) {
    .pave button { height: 60px; font-size: 1.25rem; }
    body { --taille-enonce: 1.8rem; }
    .enonce, .saisie { font-size: 1.8rem; }
}

/**
 * ============================================================
 * SUR TÉLÉPHONE, L'ÉNONCÉ ABSORBE LE MOU
 * ============================================================
 *
 * `.zone` prend toute la place libre (`flex: 1`) mais ses deux cartes
 * sont en `flex: none` : le surplus tombait donc en BAS de la zone, en
 * un vide de 250 px entre le champ de réponse et le pavé. Et quand la
 * barre d'adresse revenait, ce même surplus manquait — la dernière
 * rangée du pavé sortait de l'écran.
 *
 * L'énoncé grandit donc et rétrécit avec la place disponible. Il est
 * BORNÉ À 30 % de la hauteur : sans borne, `flex: 1` avait donné 510 px
 * sur un iPad pour un « 4 × 5 » — la moitié de l'écran, ce que V2 ne
 * faisait pas. La borne se calcule sur `--vh`, la même mesure que celle
 * qui dimensionne la page, donc elle suit la barre d'adresse elle aussi.
 *
 * Restreint aux écrans de moins de 1024 px : au-delà, la mise en page
 * passe en deux colonnes et l'énoncé n'a plus de mou à absorber.
 */
@media (max-width: 1023px) {
    body.jeu .carte-enonce {
        flex: 1 1 auto;
        max-height: calc(var(--vh, 100vh) * .30);
        max-height: calc(100svh * .30);
    }
    /* Le champ de réponse se rapproche du pavé : 14 px suffisent à les
       séparer, et les 4 px gagnés vont à l'énoncé. */
    body.jeu { gap: 10px; }
    body.jeu .zone { gap: 12px; }

    /**
     * PAS DE BASCULE « PAVÉ » SUR TÉLÉPHONE — la règle de V2, retrouvée.
     *
     * V2 ne montrait ce bouton QU'AU-DELÀ DE 1024 px : son
     * `.numpad-toggle-lbl` était en `display: none`, et seul le bloc
     * « Ordinateur » le rallumait. C'est cohérent — sur un téléphone il
     * n'y a pas de clavier physique, donc masquer le pavé ne laisse
     * aucun moyen de répondre.
     *
     * Il coûtait en plus les 58 px qui faisaient passer « TEMPS RESTANT »
     * et le score sur deux lignes, en écrasant l'en-tête.
     */
    body.jeu #bascule-pave { display: none; }

    /* L'en-tête se resserre : c'est une bande d'information, pas une
       carte à contempler, et chaque pixel rendu va au pavé. */
    body.jeu .entete { padding: 8px 12px; gap: 8px; }
    body.jeu .entete .outil { min-width: 48px; min-height: 44px; padding: 0 8px; }
}

/* ---- Écran COURT : petit téléphone debout, ou n'importe lequel couché
        (hauteur < 700 px). C'est ici que le pavé était coupé. ---- */
@media (max-width: 1023px) and (max-height: 700px) {
    body.jeu { gap: 10px; }
    body.jeu .pave { gap: 8px; }
    body.jeu .pave button { height: 56px; }
    /* SUR UN ECRAN COURT, LA CARTE RETRECIT au lieu de grandir.
       `flex: 1` la faisait occuper toute la place libre — et sur un
       téléphone couché, 390 px de haut ne contiennent pas l'en-tete, la
       carte, le champ ET le pave : elle débordait donc PAR-DESSUS le
       pave, dont les touches devenaient inatteignables. `min-height: 0`
       l'autorise a ceder la place. */
    body.jeu .carte-enonce { padding: 16px 24px; }
    body.jeu .enonce { min-height: 1.4em; }
    body.jeu .carte-saisie { padding: 14px 20px; }
    body.jeu .zone { gap: 10px; }
    body.jeu .entete { padding: 8px 14px; }
    body.jeu .minuteur, body.jeu .bloc-score .valeur { font-size: 1.5rem; }
}

/* ---- Très court (hauteur < 620 px) : téléphone couché ---- */
@media (max-width: 1023px) and (max-height: 620px) {
    body.jeu { gap: 8px; padding: 8px; }
    body.jeu .pave { gap: 6px; }
    body.jeu .pave button { height: 48px; font-size: 1.3rem; }
    /**
     * TRES COURT — un téléphone couché. Le pave prend a lui seul 264 px
     * sur les 390 disponibles : il ne reste qu'une centaine de pixels
     * pour l'en-tete, l'enonce et le champ. On reduit donc aussi la
     * POLICE de l'enonce et de la saisie, en `vh` — c'est la hauteur qui
     * contraint ici, pas la largeur. Sans cela, l'enonce débordait
     * par-dessus le pave et masquait ses touches.
     */
    /**
     * TRES COURT — un téléphone couché, 390 px de haut.
     *
     * Le pave a lui seul en prenait 264 : il ne restait rien pour
     * l'enonce et le champ. Ses touches descendent donc a 40 px, et tout
     * le reste est mesure en `vh` — c'est la HAUTEUR qui contraint ici,
     * pas la largeur, et un `clamp` en `vw` ne le voit pas.
     *
     * On ne reserve plus de hauteur a l'enonce (`min-height: 0`) : sur cet
     * ecran, garder une fraction sur deux lignes coute plus cher que le
     * saut qu'on cherchait a eviter.
     */
    /**
     * LES 44 PX SONT INTOUCHABLES, on serre tout le reste autour.
     *
     * Premiere tentative : descendre les touches a 40 px et les outils a
     * 38. L'audit a eu raison de le refuser — 44 px est le minimum pour un
     * doigt, et une touche de validation qu'on rate en pleine seance
     * chronometree coute plus cher qu'un enonce serre.
     *
     * Le budget de 390 px, pose noir sur blanc :
     *   en-tete 44 + enonce 38 + saisie 38 + pave (5x44 + 4x4 = 236)
     *   + trois interstices de 8 = 380. Il reste dix pixels.
     *
     * Tout le reste est mesure en `vh` : c'est la HAUTEUR qui contraint
     * ici, et un `clamp` en `vw` ne la voit pas.
     */
    body.jeu { gap: 8px; }
    body.jeu .zone { gap: 8px; }
    body.jeu .pave { gap: 4px; }
    body.jeu .pave button { height: 44px; font-size: 1.15rem; }
    body.jeu .entete { padding: 4px 12px; }
    body.jeu .legende { font-size: .58rem; }
    body.jeu .minuteur, body.jeu .bloc-score .valeur { font-size: 1.1rem; }
    body.jeu .entete .outil { min-height: 44px; min-width: 46px; padding: 0 8px; }
    body.jeu .entete .outil svg { width: 18px; height: 18px; }
    body.jeu .carte-enonce { padding: 4px 12px; min-height: 0; }
    body.jeu { --taille-enonce: clamp(1rem, 5.5vh, 1.6rem); }
    body.jeu .enonce { min-height: 0; font-size: var(--taille-enonce); }
    body.jeu .carte-saisie { padding: 4px 10px; gap: 8px; }
    body.jeu .saisie { font-size: clamp(1rem, 5.5vh, 1.6rem); min-height: 1.2em; }
    body.jeu .egal { font-size: clamp(1rem, 4.5vh, 1.4rem); }
    body.jeu .unite { font-size: clamp(.95rem, 4.5vh, 1.3rem); }
    body.jeu .carte-saisie { padding: 10px 18px; }
    body.jeu { --taille-enonce: clamp(1.6rem, 7vw, 2.4rem); }
    body.jeu .enonce, body.jeu .saisie { font-size: var(--taille-enonce); }
    /* Le verdict tient DANS la carte, qui ne fait plus que 59 px de haut
       sur un téléphone couché : le signe y est calculé sur la meme base
       que l’énoncé, pas sur la largeur de l’écran. */
    body.jeu .retour-verdict.juste { font-size: calc(var(--taille-enonce) * 1.3); }
    body.jeu .retour-verdict.faux  { font-size: calc(var(--taille-enonce) * 1.1); }
    body.jeu .zone { gap: 8px; }
}

/* ---- Tablette ---- */
@media (min-width: 640px) {
    body { max-width: 600px; padding: 20px; }

    /* L'écran de jeu respire : ce sont les tailles de V2. */
    .pave { max-width: 420px; }
    .pave button { height: 82px; font-size: 1.6rem; }
    body { --taille-enonce: clamp(2.5rem, 7vw, 4.5rem); }
    .enonce, .saisie { font-size: var(--taille-enonce); }
    .minuteur, .bloc-score .valeur { font-size: 2.5rem; }
    body.jeu .entete { padding: 16px 28px; }
}

/* ---- Ordinateur ----
   La navigation devient une colonne de gauche qui suit le défilement,
   le contenu occupe celle de droite : `grid-template-columns: 220px 1fr`,
   la disposition de V2 reprise telle quelle. */
@media (min-width: 1024px) {

    /**
     * `.connexion` EST EXCLUE DE LA GRILLE.
     *
     * Elle a sa propre mise en page — une carte centrée dans la fenêtre,
     * en hauteur comme en largeur. Sans cette exclusion, `body:not(.jeu)`
     * l'attrapait : la grille remplaçait le centrage vertical par
     * `align-items: start`, et la carte remontait dans le coin haut
     * gauche. Je n'avais d'abord exclu `.connexion` que de la règle de
     * REPLI plus bas, pas de la grille elle-même — le symptôme est resté
     * entier.
     */
    body:not(.jeu):not(.connexion) {
        display: grid;
        grid-template-columns: 220px 1fr;
        column-gap: 24px;
        max-width: 1000px;
        padding: 32px;
        align-items: start;
    }

    /* Tout ce qui n'est pas la navigation va en colonne 2. Le `span 100`
       est le procédé de V2 : le nombre de cartes n'est pas connu ici. */
    body:not(.jeu):not(.connexion) > *:not(.barre-bas) { grid-column: 2; }

    body:not(.jeu):not(.connexion) .barre-bas {
        grid-column: 1;
        grid-row: 1 / span 100;
        position: sticky;
        top: 32px;
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
        gap: 4px;
        padding: 20px 14px;
        align-self: start;
        margin-bottom: 0;
    }
    body:not(.jeu):not(.connexion) .barre-bas a {
        flex-direction: row;
        justify-content: flex-start;
        gap: 12px;
        font-size: .95rem;
        padding: 10px 14px;
        min-height: 44px;
    }
    body:not(.jeu):not(.connexion) .barre-bas svg { width: 20px; height: 20px; flex: none; }

    /* Sans navigation — écran de résultat, compte famille — la colonne
       centrée reprend la main : une grille à deux colonnes dont la
       première est vide décalerait tout vers la droite.
       `.connexion` est EXCLUE : elle a sa propre mise en page, une carte
       centrée dans la fenêtre en hauteur comme en largeur. Sans cette
       exception, cette règle l'écrasait et la carte remontait dans le
       coin haut gauche. */
    body:not(.jeu):not(.connexion):has(.barre-bas[hidden]) {
        display: block;
        max-width: 600px;
    }

    /* ---- L'écran de jeu : calcul à gauche, pavé à droite ----
       Empilé sur la hauteur d'un écran d'ordinateur, l'énoncé se
       retrouvait en haut et le pavé tout en bas, loin l'un de l'autre.
       V2 les mettait côte à côte. */
    /**
     * QUAND LE PAVÉ EST MASQUÉ, L'ÉNONCÉ PREND TOUTE LA PLACE.
     *
     * La grille réservait 420 px à la colonne de droite, même vide :
     * cacher le pavé laissait donc l'énoncé dans sa moitié gauche, avec
     * un grand blanc à côté — alors que c'est justement pour agrandir
     * l'énoncé qu'on masque le pavé.
     */
    body.jeu:has(.pave.masque) { grid-template-columns: 1fr; }

    body.jeu {
        display: grid;
        grid-template-columns: 1fr 420px;
        grid-template-rows: auto 1fr;
        column-gap: 24px;
        row-gap: 20px;
        max-width: 1080px;
        margin-inline: auto;
        padding: 28px;
        align-items: stretch;
    }
    body.jeu .entete { grid-column: 1 / -1; }
    body.jeu .zone   { grid-column: 1; grid-row: 2; justify-content: flex-start; }
    body.jeu .pave {
        grid-column: 2; grid-row: 2;
        max-width: none;
        align-self: start;
        background: rgba(255, 255, 255, .15);
        border-radius: var(--rayon);
        padding: 20px;
        gap: 10px;
    }
    body.jeu .pave button { height: 88px; font-size: 1.75rem; }
    body.jeu { --taille-enonce: clamp(3rem, 4vw, 5rem); }
    body.jeu .enonce, body.jeu .saisie { font-size: var(--taille-enonce); }
    body.jeu .minuteur,
    body.jeu .bloc-score .valeur { font-size: 2.75rem; }
}


/* =============================================================
   LE CATALOGUE DES TYPES DE CALCUL
   =============================================================

   Partagé par l'entraînement libre et les fiches méthode : ce sont deux
   fois la même liste, avec la même bascule et les mêmes teintes. Ces
   règles vivaient dans `eleve/solo.php` ; les fiches méthode les auraient
   recopiées, et les deux écrans auraient divergé au premier ajustement.
   ============================================================= */
/* L INTERRUPTEUR DU CATALOGUE, en haut a droite de sa carte.
   `margin-left:auto` le pousse a droite ; `float` aurait suffi mais
   sortirait du flux et laisserait les categories remonter dessous. */
.bascule-solo { display:flex; align-items:center; justify-content:flex-end; gap:10px;
    margin:-4px 0 16px; font-size:.82rem; color:#566072; }
.bascule-solo span { font-weight:600; }
.bascule-solo span.actif { color:var(--primaire); }
.bascule-solo .interrupteur { position:relative; display:inline-block; width:46px; height:26px;
    flex:none; border-radius:26px; background:#CBD5E1; transition:background .15s; }
.bascule-solo .interrupteur.on { background:var(--primaire); }
.bascule-solo .glissiere { position:absolute; top:3px; left:3px; width:20px; height:20px;
    border-radius:50%; background:#fff; box-shadow:0 1px 3px rgba(0,0,0,.25);
    transition:transform .15s; }
.bascule-solo .interrupteur.on .glissiere { transform:translateX(20px); }
/* La cible tactile fait bien 44 px, sans grossir le dessin. */
.bascule-solo .interrupteur::after { content:""; position:absolute; inset:-9px -4px; }
/* La liste des types — celle de V2 : une carte, des étiquettes de
   catégorie, et des lignes compactes. V3 en faisait une carte PAR
   catégorie, remplie de pavés de 76 px portant chacun « choisir le
   niveau » — la même phrase quarante-deux fois, qui n\'apprenait rien
   et doublait la hauteur de chaque tuile. */
/* La catégorie est un INTERTITRE, pas une étiquette : elle structure la
   liste, donc elle se lit avant les tuiles. La barre de couleur qui la
   précédait doublait l\'accent que porte déjà chaque tuile. */
.cat { margin:26px 0 12px; font-size:1.05rem; font-weight:700;
       color:var(--texte); }
.cat:first-child { margin-top:0; }
/* Une seule colonne sur téléphone, comme V2 ; plusieurs dès qu\'il y a
   la place, parce que quarante-deux lignes empilées font un long
   défilement sur un écran large. */
.types { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:8px; }
/* La tuile reprend le vocabulaire du `.palier` de l\'écran suivant :
   fond blanc, filet léger, et un accent coloré à gauche. Les deux écrans
   du solo se ressemblent enfin, au lieu de deux styles sans rapport.
   Le gris sur blanc de la version précédente était lavé — un pavé qui
   n\'avait l\'air ni cliquable ni éteint. */
.types a { position:relative; display:flex; align-items:center; min-height:48px;
           padding:11px 30px 11px 18px; background:#fff;
           border:1px solid #E2E8F0; border-radius:12px; text-decoration:none;
           color:var(--texte); font-size:.95rem; font-weight:600; line-height:1.25;
           transition:border-color .15s, box-shadow .15s, transform .1s; }
.types a::before { content:""; position:absolute; left:0; top:0; bottom:0; width:4px;
                   background:var(--accent,var(--primaire));
                   border-radius:12px 0 0 12px; }
/* Le chevron dit que la tuile MÈNE quelque part — c\'est ce que « choisir
   le niveau » disait en toutes lettres, en quarante-deux exemplaires. */
.types a::after { content:"›"; position:absolute; right:13px; top:50%;
                  transform:translateY(-50%); color:#94A3B8; font-size:1.25rem;
                  line-height:1; }
.types a:hover  { border-color:var(--accent,var(--primaire)); box-shadow:0 2px 10px rgba(15,23,42,.07); }
.types a:active { transform:scale(.99); }

/* Une teinte par catégorie. Elle ne sert QU\'aux accents — jamais au
   texte : l\'ambre et l\'orange ne passent pas le seuil de contraste sur
   blanc, et cet écran se lit aussi au vidéoprojecteur. */
.c-operation   { --accent:#4F46E5; }
.c-primaire    { --accent:#10B981; }
.c-decimaux    { --accent:#0EA5E9; }
.c-astucieux   { --accent:#F59E0B; }
.c-relatifs    { --accent:#8B5CF6; }
.c-conversions { --accent:#14B8A6; }
.c-fraction    { --accent:#F43F5E; }
.c-puissance   { --accent:#F97316; }
.c-autres      { --accent:#64748B; }
