/* ══════════════════════════════════════════════════════════════════════════
   Sudoku · feuille unique (accueil public + application)
   --------------------------------------------------------------------------
   Direction : « encre et papier ». Une grille noire posée sur un fond crème,
   UNE seule couleur d'accent, beaucoup de blanc, et des chiffres en JetBrains
   Mono qui tombent tous à la même largeur (une grille dont les chiffres
   dansent d'une case à l'autre se lit deux fois moins vite).

   Quatre règles qui expliquent tout le reste :

   1. un seul motif de fond, la feuille : `--su-paper` pour le bureau,
      `--su-surface` pour ce qui est posé dessus. La grille elle-même est
      blanche, jamais crème : c'est ce qui lui donne son statut de page ;
   2. une seule couleur d'accent, la framboise, reprise par le point du
      wordmark, la case sélectionnée, les chiffres posés par le joueur et
      l'action principale. Rien d'autre n'est coloré ;
   3. aucune ombre portée molle. La profondeur vient d'un filet de 1px et
      d'un voile, jamais d'un halo gris de 40px ;
   4. trois rôles typographiques stricts : Space Grotesk pour la marque et les
      titres, Inter Tight pour le texte d'interface, JetBrains Mono pour tout
      ce qui est chiffre (grille, chronomètre, compteurs, temps, rangs).

   Polices : auto-hébergées par /static/fonts.css, chargé avant cette feuille.
   AUCUNE nouvelle famille, aucune requête tierce (la CSP `font-src 'self'`
   l'interdit de toute façon, et /confidentialite promet le contraire).

   Clair/sombre : piloté par `data-theme` sur <html>, que `theme-init.js` pose
   AVANT le premier rendu. Trois états comme partout dans le dépôt : clair,
   sombre, et l'absence de choix qui veut dire « suivre le système » (le
   script le résout lui-même). D'où UN SEUL bloc sombre ici, accroché à
   l'attribut : dupliquer les jetons dans une media query donnerait deux
   listes qui divergent, et la préférence de l'appareil finirait par battre le
   choix explicite de l'utilisateur.

   Plan du fichier :
     1 · Jetons              8 · Test de niveau
     2 · Socle               9 · Statistiques et trophées
     3 · Accueil public     10 · Classement
     4 · Coque              11 · Modales
     5 · Accueil joueur     12 · Animations
     6 · Les huit niveaux   13 · Téléphone
     7 · La partie          14 · Survol (toujours en fin de fichier)
   ══════════════════════════════════════════════════════════════════════════ */


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 1 · JETONS
   │ Les valeurs de la section 8 du contrat, à la lettre, puis les jetons
   │ DÉRIVÉS (préfixés du même `--su-`) : ils ne remplacent aucun jeton du
   │ contrat, ils le déclinent là où le contraste ou la géométrie l'exige.
   └────────────────────────────────────────────────────────────────────────── */

:root {
  /* Papier et surfaces */
  --su-paper: #f7f6f3;
  --su-surface: #ffffff;
  --su-surface-2: #f1efe9;

  /* Encres. Mesuré : ink-2 tient 7,7:1 sur le papier et 8,3:1 sur le blanc.
     ink-3 tombe à 4,4:1 sur le blanc et 4,1:1 sur le papier : il est donc
     RÉSERVÉ au texte large (24px, ou 18,7px en gras) et aux tracés
     décoratifs. Tout petit texte secondaire passe par ink-2. */
  --su-ink: #14151a;
  --su-ink-2: #4b4e59;
  --su-ink-3: #74788a;

  /* Filets. `line-fort` porte les séparations de blocs 3x3 : c'est de
     l'encre, pas un gris, sinon la grille perd sa structure. */
  --su-line: #e0ded6;
  --su-line-fort: #14151a;

  /* Accent */
  /* ── L'ACCENT EST RÉGLABLE, mais seulement par sa TEINTE ────────────────
     Le joueur choisit une couleur dans « Mon compte » ; le JS ne pose qu'une
     valeur, `--su-h`, le degré sur le cercle chromatique. La clarté et la
     saturation restent écrites ici.

     C'est ce qui permet d'ouvrir le réglage en grand sans jamais rendre un
     texte illisible : un choix de couleur vraiment libre finit toujours par
     produire du jaune pâle sur du blanc. En fixant la clarté, le rapport de
     contraste ne dépend plus de ce que le joueur clique. 350 est le framboise
     d'origine, et les valeurs reproduisent exactement les anciens codes. */
  --su-h: 350;
  --su-accent: oklch(0.587 0.182 var(--su-h));
  --su-accent-doux: oklch(0.950 0.021 var(--su-h));
  /* L'accent quand il porte du TEXTE (un bouton plein, un outil enfoncé).
     Mesuré au navigateur sur les 360 teintes : à la clarté de `--su-accent`,
     le blanc tombe à 3,4:1 autour du turquoise, parce que ces teintes sortent
     du gamut sRGB et se font éclaircir à la conversion. 0.50 tient 4,8:1 au
     pire. On n'assombrit pas `--su-accent` pour autant : là où il n'est
     qu'aplat, anneau ou tracé, sa clarté d'origine est la bonne. Un jeton par
     rôle, comme `--su-accent-texte`. */
  --su-accent-plein: oklch(0.500 0.182 var(--su-h));

  /* Couleurs de sens */
  --su-or: #c8912f;
  --su-vert: #2f8f66;
  --su-rouge: #cc3b3b;

  /* Rayons */
  --su-r-1: 6px;
  --su-r-2: 12px;
  --su-r-3: 18px;

  /* ── Jetons dérivés ────────────────────────────────────────────────────
     Trois besoins que la liste du contrat ne couvre pas, chacun mesuré :

     `--su-accent-texte` : l'accent posé en TEXTE sur un fond teinté. Un
     chiffre `--su-accent` sur `--su-accent-doux` (la case sélectionnée) ne
     donne que 3,9:1, et sur `--su-surface-2` 4,0:1. Or le chiffre du joueur
     vit précisément sur ces fonds-là. La déclinaison sombre de l'accent
     monte à 5,3:1 sur les mêmes teintes et laisse `--su-accent` intact pour
     tout ce qui est aplat, anneau ou tracé (le contraste graphique se juge
     à 3:1, ces usages passent largement).
     En thème sombre le problème ne se pose pas : l'accent y est clair et
     donne 6,5:1 sur sa propre teinte, donc les deux jetons se rejoignent.

     `--su-surface-3` : le fond « même chiffre que la sélection », qui doit
     se distinguer du fond des pairs sans écraser le chiffre posé dessus.

     `--su-sur-accent` : le texte posé SUR un aplat d'accent. Il bascule avec
     le thème (blanc sur framboise en clair, encre sur rose en sombre) :
     écrire du blanc sur l'accent sombre donnerait 2,1:1. */
  --su-accent-texte: oklch(0.509 0.171 var(--su-h));
  --su-surface-3: #e9e5d9;
  --su-sur-accent: #ffffff;

  /* Teintes de sens, pour les pastilles et les encarts. Le texte posé
     dessus reste en `--su-ink` : `--su-vert` (4,0:1) et `--su-or` (2,8:1 sur
     blanc) ne sont JAMAIS du texte, seulement des tracés, des aplats et des
     médaillons. Partout où l'un des deux dessine une icône, un libellé
     lisible la double : l'étoile a son compte écrit, le trophée son nom, la
     série son nombre de jours. */
  --su-tint-vert: #e7f3ed;
  --su-tint-rouge: #fbe8e8;
  --su-tint-or: #f8f0dd;

  /* Voiles. La profondeur du dépôt : un voile, pas un halo. */
  --su-voile: rgba(247, 246, 243, .88);
  --su-voile-fort: rgba(20, 21, 26, .46);

  /* Familles. Toujours une pile de repli terminée par une générique : si le
     woff2 n'arrive pas, la page reste lisible au lieu de tomber sur la
     police par défaut du navigateur. */
  --su-titre: 'Space Grotesk', 'Inter Tight', system-ui, sans-serif;
  --su-ui: 'Inter Tight', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --su-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;

  /* Géométrie partagée. La largeur de la grille est reprise telle quelle par
     l'en-tête de partie et par le pavé : les trois blocs doivent tomber sur
     la même colonne, sinon la page part de travers à chaque changement de
     hauteur d'écran. */
  --su-board: min(92vw, 62vh, 540px);
  --su-top-h: 56px;
  --su-nav: 0px;          /* largeur de la navigation latérale, nulle sur téléphone */

  /* Durées. Courtes, et toujours accrochées à un évènement réel. */
  --su-t: 140ms;
  --su-t-2: 220ms;

  color-scheme: light;
}

/* ── Le SEUL bloc sombre du fichier ───────────────────────────────────────── */
:root[data-theme="dark"] {
  --su-paper: #0d0e12;
  --su-surface: #16181f;
  --su-surface-2: #1d202a;

  --su-ink: #eef0f6;
  --su-ink-2: #a7abb8;
  --su-ink-3: #7f8496;

  --su-line: #262a35;
  --su-line-fort: #6b7180;

  --su-accent: oklch(0.727 0.178 var(--su-h));
  --su-accent-doux: oklch(0.232 0.036 var(--su-h));
  /* En sombre, le texte posé dessus est de l'encre : la clarté vive de
     l'accent est déjà la bonne (mesurée à 6,9:1 au pire). */
  --su-accent-plein: oklch(0.727 0.178 var(--su-h));

  --su-or: #e8b74d;
  --su-vert: #4bbd8b;
  --su-rouge: #ff7a7a;

  --su-accent-texte: oklch(0.727 0.178 var(--su-h));
  --su-surface-3: #232733;
  --su-sur-accent: #12131a;

  --su-tint-vert: #12271f;
  --su-tint-rouge: #2a1414;
  --su-tint-or: #2b2313;

  --su-voile: rgba(13, 14, 18, .88);
  --su-voile-fort: rgba(0, 0, 0, .64);

  color-scheme: dark;
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 2 · SOCLE
   │ Remise à zéro, typographie de base, et les quatre briques réutilisées
   │ partout : bouton, champ, pastille, icône.
   └────────────────────────────────────────────────────────────────────────── */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--su-paper);
  color: var(--su-ink);
  font: 400 15px/1.5 var(--su-ui);
  font-feature-settings: 'cv05' 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overscroll-behavior-y: none;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--su-titre);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.2;
}

p { margin: 0; }
ul, ol, dl { margin: 0; padding: 0; list-style: none; }

b, strong { font-weight: 600; }

/* La chasse fixe partout où il y a un nombre : un chronomètre qui change de
   largeur à chaque seconde fait bouger toute la ligne. */
b, .kv dd, time { font-variant-numeric: tabular-nums; }

::selection {
  background: var(--su-accent-doux);
  color: var(--su-ink);
}

/* Le parcours clavier doit rester visible sur les deux thèmes : l'anneau est
   en accent, jamais en couleur de système. */
:focus-visible {
  outline: 2px solid var(--su-accent);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* La bibliothèque d'icônes en <symbol> ne doit rien occuper. */
.icon-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ── Icônes ────────────────────────────────────────────────────────────────
   Le trait est posé ici, pas dans le markup : les <symbol> ne portent ni
   fill ni stroke, ce qui les rend thémables par `currentColor`. Les rares
   attributs de présentation posés sur un enfant d'un symbole (le point du
   viseur) l'emportent sur ces valeurs héritées, et c'est voulu. */
.ico {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: middle;
}

/* ── Boutons ───────────────────────────────────────────────────────────────
   `.btn` pose `display: inline-flex`, qui bat le `[hidden] { display: none }`
   du navigateur : sans la parade juste dessous, tout bouton masqué par
   attribut resterait affiché et inerte. C'est la règle qui crée le piège qui
   doit le refermer, jamais l'appelant. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 11px 18px;
  border: 1px solid var(--su-line-fort);
  /* Rayon court, celui des cases et des touches, et non le rayon moyen : un
     bouton très arrondi sur fond crème est la silhouette de tous les
     gabarits d'app. Ici rien n'est une pilule, sauf ce qui en est une pour
     de bon (la série de jours, l'avatar). */
  border-radius: var(--su-r-1);
  background: var(--su-surface);
  color: var(--su-ink);
  font: 500 15px/1.2 var(--su-ui);
  letter-spacing: -0.005em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--su-t) ease, color var(--su-t) ease,
              border-color var(--su-t) ease, transform var(--su-t) ease;
}
.btn[hidden] { display: none; }

.btn:active { transform: translateY(1px); }

.btn-main {
  background: var(--su-accent-plein);
  border-color: var(--su-accent-plein);
  color: var(--su-sur-accent);
  font-weight: 600;
}

.btn-ghost {
  background: transparent;
  border-color: var(--su-line);
  color: var(--su-ink-2);
}

.btn-sm {
  min-height: 38px;
  padding: 8px 13px;
  font-size: 14px;
}

.btn[disabled],
.btn[aria-disabled="true"] {
  cursor: default;
  opacity: .5;
  transform: none;
}

.btn .ico { width: 18px; height: 18px; }

/* Bouton icône seul : la cible reste carrée et pleine, le tracé seul ne se
   vise pas au pouce. */
.icon-btn {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--su-r-1);
  background: transparent;
  color: var(--su-ink-2);
  cursor: pointer;
  transition: background var(--su-t) ease, color var(--su-t) ease;
}
.icon-btn[hidden] { display: none; }

/* ── Liens ─────────────────────────────────────────────────────────────────
   Le lien reste en encre, souligné d'accent. L'accent en texte sur le papier
   ne donne que 4,2:1 : le mettre en couleur ferait un lien moins lisible que
   la phrase qui le porte. */
.link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--su-accent);
  text-underline-offset: 3px;
  text-decoration-thickness: 1.5px;
}

/* ── Champs ────────────────────────────────────────────────────────────────
   16px sur tous les champs de saisie : en dessous, iOS zoome de lui-même au
   premier tap et laisse la page agrandie. Ce n'est pas un goût, c'est le
   seuil exact de Safari. La section 13 le réaffirme en NOMMANT les porteuses
   (une règle sur `input` nu se fait battre par la moindre classe). */
.field {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.field-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--su-ink-2);
}

.field-help {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--su-ink-2);
}

.field input,
.auth-form input,
.auth-card input,
.modal-body input {
  width: 100%;
  min-width: 0;
  min-height: 46px;
  padding: 11px 13px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  background: var(--su-surface);
  color: var(--su-ink);
  font-family: var(--su-ui);
  font-size: 16px;
  transition: border-color var(--su-t) ease;
}

.field input:focus,
.auth-form input:focus,
.modal-body input:focus {
  border-color: var(--su-accent);
  outline: none;
  box-shadow: inset 0 0 0 1px var(--su-accent);
}

/* ── Pastilles ─────────────────────────────────────────────────────────────
   `display: inline-flex` là encore : parade [hidden] obligatoire, la
   pastille « Terminée » de la grille du jour vit justement masquée. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--su-line);
  border-radius: 999px;
  background: var(--su-surface-2);
  color: var(--su-ink-2);
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
}
.pill[hidden] { display: none; }

.pill .ico { width: 15px; height: 15px; }

.pill-done {
  background: var(--su-tint-vert);
  border-color: transparent;
  color: var(--su-ink);
}
.pill-done .ico { color: var(--su-vert); }

/* ── Wordmark ──────────────────────────────────────────────────────────────
   Un mot, une graisse, un point d'accent. Pas d'icône dans un carré arrondi :
   c'est la grille qui signe le produit, pas un logotype de plus. */
.wordmark {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  font-family: var(--su-titre);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--su-ink);
}
.wordmark[hidden] { display: none; }

.wordmark-dot {
  width: 6px;
  height: 6px;
  border-radius: 2px;
  background: var(--su-accent);
}

.wordmark-sm { font-size: 18px; }
.wordmark-sm .wordmark-dot { width: 5px; height: 5px; }

/* ── Bouton de thème de l'accueil public ───────────────────────────────── */
.theme-btn {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  background: var(--su-surface);
  color: var(--su-ink-2);
  cursor: pointer;
  transition: color var(--su-t) ease, border-color var(--su-t) ease;
}
.theme-btn[hidden] { display: none; }

/* ── La pastille « Admin » du Portfolio ────────────────────────────────────
   Elle flotte en bas à gauche au z-index maximal (2147483645). Sur téléphone
   une modale s'ancre en bas d'écran : sans cette règle, la pastille recouvre
   exactement la rangée de boutons qui porte les gestes de la modale. Le
   marqueur `has-modal` est posé sur <body> par UN SEUL helper côté JS. */
body.has-modal #mn-admin-pill { display: none; }

/* Et sur téléphone, la pastille du Portfolio ne s'affiche pas du tout ici.
   Ancrée à 16px du bas avec le z-index maximal (2147483645, posé par app.py),
   elle recouvrait « Accueil » et « Niveaux » : mesuré inatteignables aux trois
   largeurs de téléphone, `elementFromPoint` renvoyant le contenu de la
   pastille, et le pilote échouant à cliquer avec un timeout. Pire qu'une gêne,
   un cul-de-sac : arrivé sur « Niveaux » par le contournement de l'accueil,
   plus rien ne ramenait en arrière (la flèche de retour est masquée hors
   partie, et la mémoire d'écran réinstallait « Niveaux » au rechargement).

   La REMONTER ne réglait rien, elle déplaçait le problème : posée à 74px, elle
   couvrait en pleine partie les boutons Notes, Gomme et Annuler à 360px. Le
   bas de l'écran appartient à la barre de navigation, rien d'autre ne peut y
   flotter. On applique donc ici la parade que l'app utilise déjà pour les
   modales ancrées en bas. Antoine garde sa pastille partout ailleurs sur le
   site : cette feuille ne sert que /sudoku.

   Le sélecteur porte `body` parce que le <style> de la pastille est injecté
   APRÈS cette feuille : à spécificité égale, le dernier déclaré gagnerait.

   La borne est à 1099 px et non 899 : la contre-vérification a mesuré que la
   collision ne s'arrête pas avec la barre de navigation. Entre 900 et 1024 px,
   en pleine partie et page en haut, le lien « Voir en visiteur » recouvre le
   bouton Notes, et un tap à cet endroit ne bascule pas les notes : il RECHARGE
   la page en vue visiteur. Le premier découpage refermait la moitié du défaut
   et laissait l'autre, ce qui est précisément le reproche fait à la version
   précédente de ce correctif. */
@media (max-width: 1099px) {
  body #mn-admin-pill { display: none; }
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 3 · ACCUEIL PUBLIC
   │ Le pitch et la connexion. Sur téléphone la colonne du compte reste la
   │ première du document : qui arrive ici vient jouer, pas lire une
   │ présentation. La leçon est celle du Cahier, où le champ « Identifiant »
   │ se trouvait à 1246px sur un écran de 844.
   └────────────────────────────────────────────────────────────────────────── */

.landing {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
.landing[hidden] { display: none; }

.landing-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 20px;
  max-width: 1120px;
  width: 100%;
  margin: 0 auto;
}

.landing-main {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 34px;
  align-items: start;
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 8px 20px 56px;
}

.auth-side { min-width: 0; }

.auth-card {
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-3);
  padding: 20px;
  display: grid;
  gap: 18px;
}

/* Deux onglets, une languette d'accent sous l'actif. Pas de fond coloré : la
   carte est déjà une surface, un second aplat la ferait flotter. */
.auth-tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-bottom: 1px solid var(--su-line);
}

.auth-tab {
  min-height: 44px;
  padding: 10px 6px 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: transparent;
  color: var(--su-ink-2);
  font: 500 14.5px/1.2 var(--su-ui);
  cursor: pointer;
  transition: color var(--su-t) ease, border-color var(--su-t) ease;
}

.auth-tab.is-on {
  color: var(--su-ink);
  font-weight: 600;
  border-bottom-color: var(--su-accent);
}

.auth-form {
  display: grid;
  gap: 14px;
}
.auth-form[hidden] { display: none; }

.auth-form .btn { width: 100%; }

.auth-error {
  display: flex;
  gap: 8px;
  padding: 10px 12px;
  border-radius: var(--su-r-1);
  background: var(--su-tint-rouge);
  color: var(--su-ink);
  font-size: 13.5px;
  line-height: 1.45;
}
.auth-error[hidden] { display: none; }

/* ── Le discours ───────────────────────────────────────────────────────── */
.pitch {
  min-width: 0;
  display: grid;
  gap: 18px;
  align-content: start;
}

.kicker {
  font: 500 12.5px/1.2 var(--su-mono);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--su-ink-2);
}

/* La mesure tient les deux membres de la phrase (le markup les sépare d'un
   saut de ligne) : à 16ch le premier se recassait tout seul et laissait le
   mot « fait » seul sur une ligne. */
.pitch-title {
  font-size: clamp(30px, 6.4vw, 46px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.08;
  max-width: 25ch;
}

.pitch-sub {
  font-size: 16px;
  line-height: 1.6;
  color: var(--su-ink-2);
  max-width: 58ch;
}

.features {
  display: grid;
  gap: 14px;
  margin-top: 4px;
}

.feature {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  padding: 14px 16px;
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-2);
}

.feature > div { min-width: 0; }

.feature-ico {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--su-r-1);
  background: var(--su-accent-doux);
  color: var(--su-accent-texte);
}

.feature h2 {
  font-family: var(--su-ui);
  font-size: 15.5px;
  font-weight: 600;
  margin-bottom: 4px;
}

.feature p {
  font-size: 14px;
  line-height: 1.55;
  color: var(--su-ink-2);
}

.landing-foot {
  font-size: 13px;
  color: var(--su-ink-2);
  padding-top: 6px;
}

/* À partir de 920px, le discours passe à gauche et le compte à droite : le
   regard lit le pitch puis tombe sur le formulaire. Sur téléphone l'ordre du
   document (compte d'abord) l'emporte, et c'est le bon. */
@media (min-width: 920px) {
  .landing-main {
    grid-template-columns: minmax(0, 1fr) minmax(0, 400px);
    gap: 48px;
    padding-top: 24px;
  }
  .pitch { order: 1; }
  .auth-side {
    order: 2;
    position: sticky;
    top: 24px;
  }
  .features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 4 · COQUE
   │ Barre du haut collée, contenu, barre de navigation. Basse sur téléphone
   │ (elle porte le pouce), latérale à partir de 900px. La largeur de la
   │ colonne latérale vit dans `--su-nav`, posée sur `.shell` : la barre, le
   │ contenu et le bandeau d'alerte la lisent tous les trois, donc ils ne
   │ peuvent pas se désaligner.
   └────────────────────────────────────────────────────────────────────────── */

.shell {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
.shell[hidden] { display: none; }

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--su-top-h);
  padding: 8px 14px;
  padding-top: max(8px, env(safe-area-inset-top));
  background: var(--su-paper);
  border-bottom: 1px solid var(--su-line);
}

.top-back {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-left: -6px;
  padding: 0;
  border: 0;
  border-radius: var(--su-r-1);
  background: transparent;
  color: var(--su-ink);
  cursor: pointer;
}
.top-back[hidden] { display: none; }

.top-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  min-width: 0;
}

.streak-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 32px;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--su-tint-or);
  color: var(--su-ink);
  font: 600 14px/1 var(--su-mono);
}
.streak-chip[hidden] { display: none; }

.streak-chip .ico {
  width: 16px;
  height: 16px;
  color: var(--su-or);
}

.top-btn {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--su-line);
  border-radius: 999px;
  background: var(--su-surface);
  color: var(--su-ink-2);
  cursor: pointer;
  transition: color var(--su-t) ease, border-color var(--su-t) ease;
}
.top-btn[hidden] { display: none; }

/* ── Bandeau d'alerte ──────────────────────────────────────────────────── */
.banner {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 12px 16px 0;
  margin-left: calc(16px + var(--su-nav));
  padding: 12px 14px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-2);
  background: var(--su-surface);
  font-size: 14px;
  line-height: 1.45;
}
.banner[hidden] { display: none; }

.banner p { min-width: 0; flex: 1 1 auto; }
.banner .btn { flex: none; }

.banner-warn {
  background: var(--su-tint-or);
  border-color: transparent;
}
.banner-warn > .ico {
  flex: none;
  color: var(--su-or);
}

/* ── Contenu ───────────────────────────────────────────────────────────────
   La réserve du bas laisse passer la barre de navigation ET la zone
   gestuelle des iPhone : un bouton posé sous la barre ne se touche pas. */
.main {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  padding: 18px 16px calc(84px + env(safe-area-inset-bottom));
  padding-left: calc(16px + var(--su-nav));
}

/* ── Barre de navigation ───────────────────────────────────────────────── */
.tabbar {
  position: fixed;
  z-index: 40;
  left: 0;
  right: 0;
  bottom: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2px;
  padding: 6px 8px;
  padding-bottom: calc(6px + env(safe-area-inset-bottom));
  background: var(--su-paper);
  border-top: 1px solid var(--su-line);
}
.tabbar[hidden] { display: none; }

.tab {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 3px;
  min-height: 48px;
  padding: 5px 2px;
  border: 0;
  border-radius: var(--su-r-1);
  background: transparent;
  color: var(--su-ink-2);
  font: 500 11px/1.2 var(--su-ui);
  cursor: pointer;
  transition: color var(--su-t) ease, background var(--su-t) ease;
}
.tab[hidden] { display: none; }

.tab span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.tab.is-on {
  color: var(--su-accent-texte);
  font-weight: 600;
}

/* L'onglet courant est marqué SUR LE FILET de la barre, par une encoche
   d'accent : le repère d'une règle graduée, pas un fond coloré derrière le
   mot. `top: -7px` : les 6px de marge de la barre plus son filet, que
   l'encoche recouvre. Sur la colonne latérale (900px et plus), l'encoche
   passe sur le bord gauche de la fenêtre. */
.tab.is-on::after {
  content: "";
  position: absolute;
  top: -7px;
  left: 50%;
  width: 24px;
  height: 2px;
  margin-left: -12px;
  background: var(--su-accent);
}

/* ── Le message éphémère ───────────────────────────────────────────────────
   Posé au-dessus de la barre basse, jamais dessous : c'est le seul endroit
   où il reste lisible pendant qu'on joue. */
.toast {
  position: fixed;
  z-index: 90;
  left: 50%;
  bottom: calc(84px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: min(92vw, 460px);
  padding: 11px 16px;
  border-radius: 999px;
  background: var(--su-ink);
  color: var(--su-paper);
  font-size: 14px;
  font-weight: 500;
  text-align: center;
}
.toast[hidden] { display: none; }

/* À partir de 900px la navigation devient une colonne : le pouce n'est plus
   le sujet, la lisibilité de l'arborescence si. */
@media (min-width: 900px) {
  .shell { --su-nav: 232px; }

  .tabbar {
    top: var(--su-top-h);
    right: auto;
    width: var(--su-nav);
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    gap: 4px;
    padding: 16px 14px;
    border-top: 0;
    border-right: 1px solid var(--su-line);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .tab {
    grid-template-columns: 22px minmax(0, 1fr);
    justify-items: start;
    align-items: center;
    gap: 12px;
    min-height: 44px;
    padding: 8px 12px;
    font-size: 14.5px;
    text-align: left;
  }

  /* En colonne, l'entrée courante s'écrit en encre et se marque d'une barre
     d'accent sur le bord gauche : pas de fond arrondi derrière le mot. */
  .tab.is-on {
    background: transparent;
    color: var(--su-ink);
  }
  .tab.is-on::after {
    top: 50%;
    left: -14px;
    width: 3px;
    height: 22px;
    margin: -11px 0 0;
  }

  .main { padding: 26px 26px 48px calc(26px + var(--su-nav)); }
  .banner { margin-left: calc(26px + var(--su-nav)); margin-right: 26px; }
  /* Le message reste centré sur la fenêtre : il vit hors de `.shell`, donc
     `--su-nav` y vaut sa valeur de racine (zéro). Le décaler à la main
     l'aurait fait glisser sous la colonne de navigation. */
  .toast { bottom: 28px; }
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 5 · ACCUEIL JOUEUR
   │ Les écrans, les cartes, l'anneau de progression, les couples clé/valeur.
   └────────────────────────────────────────────────────────────────────────── */

.screen {
  display: grid;
  gap: 16px;
  align-content: start;
  width: 100%;
  max-width: 780px;
  margin: 0 auto;
  min-width: 0;
}
.screen[hidden] { display: none; }

/* L'écran de jeu et celui du test tiennent en une colonne centrée sur la
   largeur exacte de la grille : tout le reste (en-tête, pavé) s'y aligne. */
.screen-game {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 100%;
}

.screen-title {
  font-size: clamp(22px, 5vw, 28px);
  letter-spacing: -0.025em;
}

.screen-sub {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--su-ink-2);
  max-width: 62ch;
}

/* ── Cartes ────────────────────────────────────────────────────────────── */
.card {
  display: grid;
  gap: 14px;
  padding: 16px;
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-2);
  min-width: 0;
}
.card[hidden] { display: none; }

.card-head {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: start;
}

.card-head > div { min-width: 0; }

/* L'icône de la carte est un simple tracé posé devant le titre, sur sa
   première ligne, sans carré teinté derrière : la « pastille d'icône » en
   tête de chaque carte est la signature des gabarits générés, et elle ne
   portait rien que le titre ne dise déjà. */
.card-ico {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  color: var(--su-ink-2);
}

.card-title {
  font-family: var(--su-ui);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.card-sub {
  margin-top: 2px;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--su-ink-2);
}

.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.card-actions .btn { flex: 1 1 auto; min-width: 0; }

/* La grille du jour est la carte qui compte : elle porte l'accent, les
   autres restent en filet neutre. */
.card-daily {
  border-color: var(--su-accent);
  background: var(--su-accent-doux);
}
.card-daily .card-ico { color: var(--su-accent-texte); }

.card-test { border-color: var(--su-line-fort); }
.card-resume .card-ico { color: var(--su-accent-texte); }

.daily-body {
  display: grid;
  gap: 14px;
}

/* ── Couples clé / valeur ──────────────────────────────────────────────── */
.kv {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 12px;
}

.kv > div { min-width: 0; }

.kv dt {
  font-size: 12.5px;
  color: var(--su-ink-2);
  margin-bottom: 3px;
}

.kv dd {
  margin: 0;
  font: 600 19px/1.15 var(--su-mono);
  letter-spacing: -0.02em;
  color: var(--su-ink);
  overflow-wrap: anywhere;
}

.kv-tight dd { font-size: 17px; }

.kv-win {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  text-align: center;
  padding: 14px 0;
  border-top: 1px solid var(--su-line);
  border-bottom: 1px solid var(--su-line);
}

/* ── Anneau de progression ─────────────────────────────────────────────────
   Le trait mesure une part des grilles du niveau. Le rayon vaut 19, donc la
   circonférence vaut 119,38 : la valeur de départ vide le trait entièrement,
   et le JS n'a plus qu'à baisser `stroke-dashoffset`. */
.prog-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 18px;
  align-items: center;
}

.ring {
  position: relative;
  width: 82px;
  height: 82px;
  flex: none;
}

.ring svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.ring-bg,
.ring-fg {
  fill: none;
  stroke-width: 3.4;
}

.ring-bg { stroke: var(--su-line); }

.ring-fg {
  stroke: var(--su-accent);
  stroke-linecap: round;
  stroke-dasharray: 119.38;
  stroke-dashoffset: 119.38;
  transition: stroke-dashoffset var(--su-t-2) ease;
}

.ring b {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font: 600 17px/1 var(--su-mono);
  letter-spacing: -0.03em;
}

/* ── Bandeau des derniers trophées ─────────────────────────────────────────
   Une rangée qui défile plutôt qu'une grille qui casse : trois trophées de
   plus ne doivent pas repousser la carte suivante d'un écran. */
.trophy-strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: 4px;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}
.trophy-strip[hidden] { display: none; }

.trophy-strip > li {
  flex: none;
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  width: min(74vw, 230px);
  padding: 10px 12px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-2);
  background: var(--su-surface-2);
}

.trophy-strip > li > div { min-width: 0; }

.trophy-strip b {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
}

.trophy-strip small,
.trophy-strip p {
  display: block;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--su-ink-2);
}

.trophy-strip .ico {
  width: 26px;
  height: 26px;
  color: var(--su-or);
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 6 · LES HUIT NIVEAUX
   │ Le contenu de `#levelList` est construit par le JS. La feuille l'habille
   │ donc par la STRUCTURE (l'élément de liste, ses titres, ses boutons) et
   │ non par une classe par sous-élément : une carte de niveau reste lisible
   │ quelle que soit la façon dont elle est assemblée.
   │ Un niveau fermé n'est PAS traité à l'opacité : le texte y perdrait son
   │ contraste. Il change de surface et passe en encre secondaire.
   └────────────────────────────────────────────────────────────────────────── */

.level-list {
  display: grid;
  gap: 12px;
}
.level-list[hidden] { display: none; }

.level-list > li {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 14px 16px;
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-2);
  min-width: 0;
  transition: border-color var(--su-t) ease, background var(--su-t) ease;
}

.level-list > li > div { min-width: 0; }

/* Le numéro du niveau, en chasse fixe : c'est un repère, pas un titre. */
.level-list > li > b:first-child,
.level-list .pl-num {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: var(--su-r-1);
  background: var(--su-surface-2);
  font: 600 19px/1 var(--su-mono);
  color: var(--su-ink);
}

.level-list h2,
.level-list h3 {
  font-family: var(--su-ui);
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.level-list p {
  margin-top: 3px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--su-ink-2);
}

/* Les étoiles gagnées dans le niveau, en chasse fixe pour que la colonne ne
   bouge pas d'une ligne à l'autre. */
.level-list small {
  display: inline-block;
  margin-top: 6px;
  font: 500 12.5px/1 var(--su-mono);
  color: var(--su-ink-2);
}

.level-list .ico { color: var(--su-ink-2); }

/* Niveau en cours : c'est la seule ligne accentuée de la page. */
.level-list > li.is-on {
  border-color: var(--su-accent);
  background: var(--su-accent-doux);
}
.level-list > li.is-on > b:first-child,
.level-list > li.is-on .pl-num {
  background: var(--su-accent);
  color: var(--su-sur-accent);
}

/* Niveau bouclé : toutes les grilles du niveau terminées. */
.level-list > li.is-done > b:first-child,
.level-list > li.is-done .pl-num {
  background: var(--su-tint-vert);
  color: var(--su-ink);
}

/* Niveau fermé. Surface éteinte et encre secondaire, jamais d'opacité. */
.level-list > li.is-locked {
  background: var(--su-surface-2);
  border-style: dashed;
}
.level-list > li.is-locked h2,
.level-list > li.is-locked h3 { color: var(--su-ink-2); }
.level-list > li.is-locked > b:first-child,
.level-list > li.is-locked .pl-num {
  background: transparent;
  border: 1px dashed var(--su-line);
  color: var(--su-ink-2);
}

/* Une carte de niveau tient dans un bouton d'ouverture : cible pleine, pas
   un lien de trois pixels au bout de la ligne. */
.level-list button {
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--su-line-fort);
  border-radius: var(--su-r-1);
  background: var(--su-surface);
  color: var(--su-ink);
  font: 500 14px/1.2 var(--su-ui);
  cursor: pointer;
  white-space: nowrap;
}

.level-list button[disabled],
.level-list button[aria-disabled="true"] {
  border-color: var(--su-line);
  color: var(--su-ink-2);
  cursor: default;
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 7 · LA PARTIE
   │ L'écran qui compte. En-tête, grille, voile de pause, encart d'indice,
   │ pavé numérique. Les quatre blocs partagent la largeur `--su-board`.
   └────────────────────────────────────────────────────────────────────────── */

.game-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: var(--su-board);
  max-width: 100%;
  min-width: 0;
}

.game-id {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.game-level {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.game-index {
  font: 500 12.5px/1.2 var(--su-mono);
  color: var(--su-ink-2);
}

.game-meters {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
}

/* Les trois compteurs de l'en-tête. Les deux premiers sont des LECTURES :
   icône et nombre posés à même le papier, sans pilule autour (trois capsules
   bordées en rangée, c'est l'en-tête de n'importe quel gabarit). Seul le
   chronomètre garde un cadre, parce qu'il est un bouton (la pause) : il doit
   garder la même hauteur que ses voisins, sinon la ligne se tord. */
.meter {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 38px;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: var(--su-r-1);
  background: transparent;
  color: var(--su-ink-2);
  font: 500 13.5px/1 var(--su-mono);
}
.meter[hidden] { display: none; }

.meter b {
  color: var(--su-ink);
  font-weight: 600;
}

.meter .ico { width: 16px; height: 16px; }

/* Le chronomètre est un vrai bouton (la pause) : il monte au plancher des
   cibles isolées, pas à celui des compteurs qui l'entourent. */
.meter-btn {
  cursor: pointer;
  min-height: 42px;
  min-width: 88px;
  justify-content: center;
  border-color: var(--su-line);
  background: var(--su-surface);
}

/* Le compteur d'erreurs vire au rouge dès la première : c'est l'information
   qui doit sauter aux yeux en mode strict. */
.meter.is-on {
  background: var(--su-tint-rouge);
  border-color: transparent;
}
.meter.is-on .ico { color: var(--su-rouge); }

/* ── La grille ─────────────────────────────────────────────────────────────
   Le cadre extérieur et les séparations de blocs sont de l'encre pleine, les
   filets entre cases un gris de papier. La grille est BLANCHE sur le fond
   crème : c'est ce qui lui donne son statut de page posée. */
.board-wrap {
  position: relative;
  width: var(--su-board);
  max-width: 100%;
}

.board {
  display: grid;
  grid-template-columns: repeat(9, minmax(0, 1fr));
  grid-template-rows: repeat(9, minmax(0, 1fr));
  width: 100%;
  aspect-ratio: 1;
  background: var(--su-surface);
  border: 2px solid var(--su-line-fort);
  border-radius: var(--su-r-1);
  overflow: hidden;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}
.board[hidden] { display: none; }

.cell {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  padding: 0;
  border: 0;
  border-right: 1px solid var(--su-line);
  border-bottom: 1px solid var(--su-line);
  background: transparent;
  color: var(--su-ink);
  font: 500 clamp(20px, 5.2vw, 30px)/1 var(--su-mono);
  letter-spacing: -0.04em;
  cursor: pointer;
  appearance: none;
  transition: background var(--su-t) ease, color var(--su-t) ease;
}

/* Séparations de blocs. Le JS marque les colonnes 3 et 6 et les lignes 3 et
   6 ; les règles de bord juste après annulent le trait sur le pourtour, quoi
   qu'il marque : sans elles, une neuvième colonne marquée doublerait le
   cadre. Spécificité identique, c'est donc l'ORDRE qui tranche. */
.cell.is-c3 { border-right: 2px solid var(--su-line-fort); }
.cell.is-r3 { border-bottom: 2px solid var(--su-line-fort); }

.cell:nth-child(9n) { border-right: 0; }
.cell:nth-child(n + 73) { border-bottom: 0; }

/* Les trois états de chiffre. L'indice de départ est de l'encre grasse (il
   est donné, il ne bouge plus), le chiffre du joueur porte l'accent, une
   erreur signalée passe au rouge. */
.cell.is-given {
  font-weight: 700;
  color: var(--su-ink);
  cursor: default;
}

.cell.is-user {
  font-weight: 500;
  color: var(--su-accent-texte);
}

/* Le 700 n'est pas une coquetterie : posé sur un fond teinté (case
   sélectionnée, pair de la sélection), le rouge tombe à 4,2:1. Au-delà de
   18,7px ET en gras, le plancher applicable devient 3:1, et le chiffre faux
   reste lisible dans les six combinaisons possibles. */
.cell.is-wrong {
  color: var(--su-rouge);
  font-weight: 700;
}

/* Les fonds, du plus faible au plus fort. Toutes ces règles ont la MÊME
   spécificité : c'est donc l'ordre, et lui seul, qui les hiérarchise. Un
   pair, puis un même chiffre, puis une case fausse (elle doit se voir même
   quand elle est pair), puis le raisonnement de l'indice, puis la sélection,
   puis la case désignée par l'indice, qui bat tout. Déplacer une de ces
   lignes change le rendu sans rien changer d'autre. */
.cell.is-peer { background: var(--su-surface-2); }
.cell.is-same { background: var(--su-surface-3); }

.cell.is-wrong { background: var(--su-tint-rouge); }

.cell.is-ctx { background: var(--su-accent-doux); }

.cell.is-sel {
  background: var(--su-accent-doux);
  box-shadow: inset 0 0 0 2px var(--su-accent);
  z-index: 1;
}

/* La case désignée par l'indice, qui bat tout le reste : c'est la seule que
   le joueur doit regarder. */
.cell.is-hint {
  background: var(--su-accent-doux);
  box-shadow: inset 0 0 0 2px var(--su-accent);
  z-index: 2;
}

/* ── Les notes ─────────────────────────────────────────────────────────────
   Neuf places fixes dans la case, allumées une à une. La place est posée par
   la classe, pas par l'ordre du document : le JS ne rend pas forcément les
   neuf, et une note « 7 » ne doit jamais s'afficher là où va le « 1 ».
   `visibility` plutôt que `display` : la grille intérieure garde sa géométrie
   même quand une seule note est allumée. */
.notes {
  position: absolute;
  inset: 7%;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: repeat(3, minmax(0, 1fr));
  font: 500 clamp(8px, 1.9vw, 11px)/1 var(--su-mono);
  color: var(--su-ink-2);
  pointer-events: none;
}
.notes[hidden] { display: none; }

.notes > * {
  display: grid;
  place-items: center;
  visibility: hidden;
  font-style: normal;
}

.notes > .on { visibility: visible; }

.n1 { grid-area: 1 / 1; }
.n2 { grid-area: 1 / 2; }
.n3 { grid-area: 1 / 3; }
.n4 { grid-area: 2 / 1; }
.n5 { grid-area: 2 / 2; }
.n6 { grid-area: 2 / 3; }
.n7 { grid-area: 3 / 1; }
.n8 { grid-area: 3 / 2; }
.n9 { grid-area: 3 / 3; }

/* Une case qui porte un chiffre n'affiche plus ses notes. */
.cell.is-given .notes,
.cell.is-user .notes,
.cell.is-wrong .notes { display: none; }

/* ── Le voile de pause ─────────────────────────────────────────────────────
   Il couvre exactement la grille (d'où la largeur portée par `.board-wrap`)
   et masque les chiffres : une pause qui laisse la grille lisible ne met pas
   la partie en pause, elle arrête juste le chronomètre. */
.board-veil {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 14px;
  padding: 20px;
  border-radius: var(--su-r-1);
  background: var(--su-voile);
  backdrop-filter: blur(7px);
  text-align: center;
}
.board-veil[hidden] { display: none; }

.board-veil p {
  font-family: var(--su-titre);
  font-size: 18px;
  font-weight: 600;
}

/* ── L'encart d'indice ─────────────────────────────────────────────────────
   Il nomme la technique et raconte la déduction. Il ne donne la réponse que
   si on la lui demande, d'où les deux actions au pied. */
.hint-box {
  display: grid;
  gap: 12px;
  width: var(--su-board);
  max-width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--su-accent);
  border-radius: var(--su-r-2);
  background: var(--su-accent-doux);
  min-width: 0;
}
.hint-box[hidden] { display: none; }

.hint-main {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
}

.hint-main > div { min-width: 0; }

.hint-ico {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  color: var(--su-accent-texte);
}

.hint-title {
  font-family: var(--su-ui);
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.hint-text {
  margin-top: 3px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--su-ink-2);
}

.hint-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}
.hint-actions .btn { flex: 1 1 auto; }

/* ── Le pavé numérique et la réglette d'outils ─────────────────────────────
   Le pavé est la DIXIÈME LIGNE de la grille, et il en a le dessin : un cadre
   d'encre de 2px, des filets de papier entre les touches, aucune touche qui
   flotte seule dans son petit cadre arrondi. Neuf tuiles détachées, chacune
   avec son filet et son rayon, c'est la rangée que produit n'importe quel
   gabarit d'app ; une ligne réglée comme la grille est la signature de
   celle-ci. Le filet entre les cellules est l'ÉCART de la grille (`gap: 1px`)
   qui laisse voir le fond du conteneur, jamais une bordure de touche : la
   même règle sert au pavé de neuf sur une ligne et au pavé de trois sur
   trois, sans compter les enfants.
   Sous 560px les neuf touches passent en trois rangées de trois : alignées
   sur une seule ligne, chacune tomberait à 36px de large sur un téléphone de
   360px, très en dessous du plancher de 44. Un bloc de trois sur trois donne
   des touches de plus de 100px de large, et c'est le geste le plus répété de
   l'application. */
.pad {
  display: grid;
  gap: 8px;
  width: var(--su-board);
  max-width: 100%;
  min-width: 0;
}
.pad[hidden] { display: none; }

.pad-keys {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  padding: 0;
  background: var(--su-line);
  border: 2px solid var(--su-line-fort);
  border-radius: var(--su-r-1);
  overflow: hidden;
}

.pad-key {
  position: relative;
  display: grid;
  place-items: center;
  gap: 1px;
  min-height: 50px;
  padding: 6px 4px;
  border: 0;
  border-radius: 0;
  background: var(--su-surface);
  color: var(--su-ink);
  cursor: pointer;
  appearance: none;
  transition: background var(--su-t) ease, color var(--su-t) ease;
}
.pad-key[hidden] { display: none; }

/* L'anneau de focus se dessine DANS la cellule : le conteneur rogne ce qui
   dépasse (`overflow: hidden`, pour ses coins), et un anneau posé à
   l'extérieur passerait sous la touche voisine. Même parade pour la réglette
   et pour les contrôles segmentés, bâtis sur la même bande. */
.pad-key:focus-visible,
.tool:focus-visible,
.seg-btn:focus-visible { outline-offset: -3px; }

/* Le chiffre a la graisse et la chasse de ceux de la grille : c'est la même
   rangée, pas un clavier posé à côté. */
.pad-n {
  font: 500 clamp(20px, 5vw, 26px)/1 var(--su-mono);
  letter-spacing: -0.04em;
}

/* Le reste à poser pour ce chiffre. En chasse fixe et en encre secondaire :
   c'est une aide, pas une donnée à lire en premier. */
.pad-left {
  font: 500 10.5px/1 var(--su-mono);
  color: var(--su-ink-2);
  min-height: 11px;
}
.pad-left[hidden] { display: none; }

/* Chiffre posé neuf fois : la touche s'éteint, mais elle RESTE CLIQUABLE.
   `pointer-events: none` était un piège : `restants()` compte les chiffres
   posés sans regarder s'ils sont justes, donc trois 1 mal placés suffisaient à
   éteindre la touche du 1 alors qu'une case l'attendait encore. En mode libre,
   rien n'étant signalé, le joueur se retrouvait sans touche, sans signal, et
   sans clavier sur téléphone. Au clavier la même touche continuait de
   fonctionner (`poserChiffre` n'est pas gardé) : le pavé et le clavier ne
   disaient pas la même chose. L'opacité dit « c'est fait », elle n'interdit
   rien. Trouvé par la relecture d'août 2026. */
/* Quand le panneau d'indice s'ouvre, le JS ramène le pavé dans la vue. Sans
   cette marge, `scrollIntoView({block:"end"})` aligne le bas du pavé sur le bas
   de la FENÊTRE, donc sous la barre de navigation fixe : une touche restait
   inatteignable, et pire, un tap dessus tombait sur la barre et QUITTAIT la
   partie. Demander un indice puis poser le chiffre soi-même, le geste que la
   page d'accueil met en avant, ramenait à l'accueil. */
.pad {
  scroll-margin-bottom: calc(84px + env(safe-area-inset-bottom));
}

.pad-key.is-full {
  /* Pas d'opacité ici. La touche RESTE CLIQUABLE (cf. plus haut), donc ce
     n'est plus un contrôle désactivé : l'exemption de contraste des contrôles
     inertes est tombée avec `pointer-events`. Mesuré sur l'ancienne version à
     `opacity: .38`, le chiffre tombait à 2,4:1 en clair et 3,2:1 en sombre,
     pour un plancher de 4,5. L'état « terminé » se dit donc par la couleur et
     le fond, pas par un voile : encre secondaire sur la surface creusée,
     mesuré à 7,2:1 dans les deux thèmes, plus le compteur restant qui
     disparaît. Trouvé par la contre-vérification de mon propre correctif. */
  color: var(--su-ink-2);
  background: var(--su-surface-2);
}

/* Le chiffre du pavé accordé à la case sélectionnée : le même anneau
   intérieur que la case, puisque c'est la même rangée. */
.pad-key.is-on {
  background: var(--su-accent-doux);
  box-shadow: inset 0 0 0 2px var(--su-accent);
}

/* ── La réglette d'outils ──────────────────────────────────────────────────
   Cinq gestes dans UNE bande, séparés par le même filet que les touches, sous
   un cadre d'un pixel : le pavé garde le cadre d'encre parce que c'est lui
   qui prolonge la grille, la réglette ne fait que la servir. Sur téléphone
   l'icône reste au-dessus du mot, il n'y a pas la largeur pour les mettre
   côte à côte ; à partir de 560px ils se posent sur une ligne, comme une
   entrée de menu, et le raccourci clavier prend le coin. */
.pad-tools {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 1px;
  background: var(--su-line);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  overflow: hidden;
}

.tool {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 3px;
  min-height: 52px;
  padding: 7px 3px;
  border: 0;
  border-radius: 0;
  background: var(--su-surface);
  color: var(--su-ink-2);
  font: 500 11px/1.15 var(--su-ui);
  cursor: pointer;
  appearance: none;
  transition: background var(--su-t) ease, color var(--su-t) ease;
}
.tool[hidden] { display: none; }

.tool span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tool .ico { width: 19px; height: 19px; }

/* Le raccourci clavier de chaque outil, écrit dans son coin, en capsule de
   chasse fixe. Il n'existe que pour un appareil qui a probablement un
   clavier (cf. le bloc `pointer: fine` en fin de section) : sur téléphone,
   cinq lettres de plus dans une rangée de 360px seraient du bruit.
   `aria-hidden` dans le gabarit : le lecteur d'écran a `aria-keyshortcuts`
   sur le bouton, il n'a pas à lire « Notes N ». */
.tool-key {
  display: none;
  position: absolute;
  top: 4px;
  right: 5px;
  min-width: 17px;
  padding: 3px 4px 2px;
  border: 1px solid var(--su-line);
  border-radius: 4px;
  font: 500 10px/1 var(--su-mono);
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--su-ink-2);
  background: var(--su-surface);
}

/* Le mode notes est un interrupteur : il doit se voir sans lire l'étiquette,
   parce qu'on y revient toutes les dix secondes. */
.tool[aria-pressed="true"] {
  background: var(--su-accent-plein);
  color: var(--su-sur-accent);
}
.tool[aria-pressed="true"] .tool-key {
  color: inherit;
  background: transparent;
  border-color: currentColor;
  opacity: .8;
}

/* Rien à annuler, rien à refaire : l'outil s'éteint par sa couleur, pas par
   un voile d'opacité qui délaverait aussi le filet et la capsule. Le
   raccourci disparaît avec lui : une lettre qui invite à presser une touche
   inerte est une promesse fausse. */
.tool[disabled],
.tool[aria-disabled="true"] {
  color: var(--su-ink-3);
  cursor: default;
}
.tool[disabled] .tool-key,
.tool[aria-disabled="true"] .tool-key { visibility: hidden; }

/* La légende des touches que les boutons ne montrent pas (les flèches, les
   chiffres, la pause). Même réserve que la capsule : clavier probable
   seulement. */
.pad-legend {
  display: none;
  padding: 0 4px;
  font-size: 12px;
  line-height: 1.45;
  text-align: center;
  color: var(--su-ink-2);
}

/* À partir de 560px les neuf touches tiennent sur une ligne en gardant
   largement le plancher de 44px, et l'outil se lit d'une traite, icône puis
   mot. Le contenu est poussé sous la capsule du coin par la marge haute :
   mesuré à 48px de haut avec un contenu centré, la ligne du texte montait
   jusqu'à la capsule. */
@media (min-width: 560px) {
  .pad-keys { grid-template-columns: repeat(9, minmax(0, 1fr)); }
  .pad-key { min-height: 56px; }

  .tool {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 54px;
    padding: 12px 6px 8px;
    font-size: 13px;
  }
  .tool .ico { width: 18px; height: 18px; }
}

/* La capsule et la légende n'apparaissent qu'avec un clavier probable, un
   pointeur fin qui sait survoler. Une tablette sans clavier n'a pas à les
   voir. */
@media (hover: hover) and (pointer: fine) {
  .tool-key { display: inline-block; }
  .pad-legend { display: block; }
}

/* Écran court (petits modèles, paysage de téléphone) : on resserre le
   rythme vertical plutôt que la grille.

   Mesuré : à 390x844 et 414x896 l'écran de jeu tient EXACTEMENT dans la
   fenêtre (barre du haut, en-tête, grille, pavé, barre de navigation). À
   375x667 il défile d'une centaine de pixels, et c'est assumé : la largeur
   de la grille est une formule fixe (`--su-board`), et la seule autre façon
   de gagner cette hauteur serait de descendre les touches du pavé sous le
   plancher tactile. Un écran qui défile de cent pixels se remonte du pouce ;
   une touche de 34px se rate. */
@media (max-height: 720px) and (max-width: 559px) {
  .main { padding-top: 10px; }
  .screen-game { gap: 9px; }
  .pad { gap: 6px; }
  .pad-key { min-height: 44px; }
  .tool { min-height: 46px; }
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 8 · TEST DE NIVEAU
   │ Trois panneaux qui se relaient dans le même écran : l'annonce, la manche
   │ chronométrée, le résultat. Les trois portent la parade [hidden], ils se
   │ commutent par attribut.
   └────────────────────────────────────────────────────────────────────────── */

.pl-panel {
  display: grid;
  justify-items: center;
  gap: 16px;
  width: 100%;
  max-width: 560px;
  padding: 22px 18px;
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-3);
  text-align: center;
}
.pl-panel[hidden] { display: none; }

.pl-panel .card-actions {
  justify-content: center;
  width: 100%;
}

.pl-ico {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 999px;
  background: var(--su-accent-doux);
  color: var(--su-accent-texte);
}
.pl-ico[hidden] { display: none; }

.pl-ico .ico { width: 26px; height: 26px; }

.pl-ico-ok {
  background: var(--su-tint-or);
  color: var(--su-or);
}

/* Les trois règles du test. Le numéro est un repère de lecture, pas une
   décoration : chasse fixe, pastille pleine. */
.pl-rules {
  display: grid;
  gap: 10px;
  width: 100%;
  text-align: left;
}

.pl-rules > li {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  font-size: 14px;
  line-height: 1.5;
  color: var(--su-ink-2);
}

.pl-rules .pl-num {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background: var(--su-surface-2);
  color: var(--su-ink);
  font: 600 13px/1 var(--su-mono);
}

/* ── La manche ─────────────────────────────────────────────────────────── */
.pl-run {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
}
.pl-run[hidden] { display: none; }

/* La jauge du temps restant. Elle vide de la droite vers la gauche par une
   largeur que le JS pose : un décompte en chiffres seul ne se lit pas du
   coin de l'oeil pendant qu'on cherche un chiffre. */
.pl-bar {
  width: var(--su-board);
  max-width: 100%;
  height: 6px;
  border-radius: 999px;
  background: var(--su-surface-2);
  overflow: hidden;
}

.pl-bar > i {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background: var(--su-accent);
  transition: width 1s linear, background var(--su-t-2) ease;
}

/* Les dix dernières secondes : la jauge et le compteur virent au rouge. */
.pl-bar > i.is-on { background: var(--su-rouge); }

.meter.is-on b { color: var(--su-ink); }

/* ── Le résultat ───────────────────────────────────────────────────────── */
.pl-level {
  font-family: var(--su-titre);
  font-size: clamp(26px, 6vw, 34px);
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--su-accent-texte);
}

.pl-recap {
  display: grid;
  gap: 8px;
  width: 100%;
  text-align: left;
}

.pl-recap > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  border-radius: var(--su-r-1);
  background: var(--su-surface-2);
  font-size: 13.5px;
  line-height: 1.4;
}

.pl-recap b {
  font-family: var(--su-mono);
  font-size: 13px;
  color: var(--su-ink);
}

.pl-recap .ico { width: 18px; height: 18px; }
.pl-recap > li.is-on .ico { color: var(--su-vert); }


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 9 · STATISTIQUES ET TROPHÉES
   └────────────────────────────────────────────────────────────────────────── */

.stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
}

.stat-tile {
  display: grid;
  gap: 4px;
  min-width: 0;
  padding: 14px 16px;
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-2);
}

.stat-val {
  font: 600 26px/1.05 var(--su-mono);
  letter-spacing: -0.035em;
  color: var(--su-ink);
}

.stat-lab {
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--su-ink-2);
}

/* ── La courbe des trente jours ────────────────────────────────────────────
   Des barres, pas une courbe lissée : on compte des grilles entières, et une
   spline entre deux entiers raconte une progression qui n'a pas eu lieu.
   La hauteur vient de `--su-h`, posée par le JS sur chaque barre.

   Deux choses mesurées ici. La ligne de base appartient au GRAPHE, pas aux
   barres : posée sur chacune d'elles, elle se lisait comme un trait
   pointillé rouge sous le graphe, les intervalles faisant les blancs.
   Et la barre est en `--su-ink-3` (4,4:1 sur le blanc) et non en teinte
   d'accent : `--su-accent-doux` sur du blanc ne donne que 1,1:1, une barre
   qui porte une donnée serait alors invisible. L'accent est gardé pour la
   seule barre qui doit ressortir, celle du jour. */
.chart {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 120px;
  padding-top: 4px;
  min-width: 0;
  border-bottom: 1px solid var(--su-line);
}

.chart > * {
  flex: 1 1 0;
  min-width: 0;
  height: max(3px, var(--su-h, 0%));
  border-radius: 3px 3px 0 0;
  background: var(--su-ink-3);
}

/* La barre du jour : c'est celle qu'on vient chercher. */
.chart > .is-on { background: var(--su-accent); }

/* ── Le tableau par niveau ─────────────────────────────────────────────────
   Il défile latéralement sur téléphone, et il DOIT le dire : masque en
   dégradé sur le bord droit, colonne de repère collante. Sans elle, on lit
   « 3 étoiles » sans savoir de quel niveau il s'agit. Le conteneur est
   atteignable au clavier (tabindex et role posés dans le markup). */
.table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
}
.table-wrap[hidden] { display: none; }

.stat-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.stat-table th,
.stat-table td {
  padding: 10px 12px;
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--su-line);
}

.stat-table thead th {
  font: 500 11.5px/1.2 var(--su-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--su-ink-2);
}

.stat-table td { font-family: var(--su-mono); font-size: 13.5px; }

.stat-table th:first-child,
.stat-table td:first-child {
  position: sticky;
  left: 0;
  text-align: left;
  background: var(--su-surface);
  font-family: var(--su-ui);
  font-size: 14px;
  font-weight: 500;
}

.stat-table tbody tr:last-child th,
.stat-table tbody tr:last-child td { border-bottom: 0; }

/* ── Les vingt-trois trophées ──────────────────────────────────────────────
   Le palier (bronze, argent, or) est porté par le filet et par la teinte du
   médaillon, jamais par un aplat de couleur derrière du texte. Deux écritures
   sont acceptées, l'attribut de données et la classe : la couche qui construit
   la liste choisit, la feuille ne l'impose pas. */
.trophy-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}
.trophy-grid[hidden] { display: none; }

.trophy-grid > li {
  display: grid;
  gap: 8px;
  justify-items: center;
  text-align: center;
  min-width: 0;
  padding: 14px 12px;
  background: var(--su-surface-2);
  border: 1px solid transparent;
  border-radius: var(--su-r-2);
}

.trophy-grid .ico,
.trophy-grid > li > svg {
  width: 30px;
  height: 30px;
  color: var(--su-ink-3);
}

.trophy-grid b {
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
}

.trophy-grid small,
.trophy-grid p {
  font-size: 12px;
  line-height: 1.4;
  color: var(--su-ink-2);
}

/* Trophée obtenu. Le verrouillé garde son texte pleinement lisible : un
   trophée à conquérir doit se lire, c'est ce qui donne envie. */
.trophy-grid > li.is-on {
  background: var(--su-surface);
  border-color: var(--su-line-fort);
}

.trophy-grid > li.is-on .ico,
.trophy-grid > li.is-on > svg { color: var(--su-ink); }

.trophy-grid > li.is-on[data-palier="bronze"] .ico,
.trophy-grid > li.is-on.bronze .ico { color: var(--su-accent-texte); }

.trophy-grid > li.is-on[data-palier="argent"] .ico,
.trophy-grid > li.is-on.argent .ico { color: var(--su-ink-2); }

.trophy-grid > li.is-on[data-palier="or"] .ico,
.trophy-grid > li.is-on.or .ico { color: var(--su-or); }

.trophy-grid > li.is-on[data-palier="or"],
.trophy-grid > li.is-on.or { border-color: var(--su-or); }


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 10 · CLASSEMENT
   │ Un contrôle segmenté, deux listes ordonnées, un mot quand elles sont
   │ vides. Le segment sert aussi aux réglages du compte : une seule brique.
   └────────────────────────────────────────────────────────────────────────── */

/* Même dessin que la réglette du pavé : une bande sous un filet, des
   cellules séparées par le filet du fond, et le choix courant souligné
   d'accent comme un onglet. Plus de plateau gris avec un segment blanc
   surélevé dedans, qui est le contrôle segmenté de tous les systèmes. */
.seg {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  background: var(--su-line);
  overflow: hidden;
}
.seg[hidden] { display: none; }

.seg-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.seg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  min-height: 40px;
  padding: 9px 10px;
  border: 0;
  border-radius: 0;
  background: var(--su-surface);
  color: var(--su-ink-2);
  font: 500 14px/1.2 var(--su-ui);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background var(--su-t) ease, color var(--su-t) ease;
}
.seg-btn[hidden] { display: none; }

.seg-btn .ico { width: 17px; height: 17px; }

.seg-btn.is-on,
.seg-btn[aria-selected="true"],
.seg-btn[aria-checked="true"] {
  background: var(--su-accent-doux);
  color: var(--su-ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--su-accent);
}

/* ── Les tableaux de rang ──────────────────────────────────────────────────
   Trois colonnes : le rang en chasse fixe, le nom qui peut être long, la
   valeur alignée à droite. Le nom porte `min-width: 0`, sans quoi un pseudo
   de vingt caractères pousse la valeur hors de l'écran. */
.board-list {
  display: grid;
  gap: 2px;
}
.board-list[hidden] { display: none; }

.board-list > li {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 11px 12px;
  border-radius: var(--su-r-1);
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  min-width: 0;
}

.board-list > li > * { min-width: 0; }

.board-list > li > :first-child {
  font: 600 14px/1 var(--su-mono);
  color: var(--su-ink-2);
  text-align: center;
}

.board-list > li > :nth-child(2) {
  font-size: 14.5px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-list > li > :last-child {
  font: 600 14px/1 var(--su-mono);
  color: var(--su-ink);
  text-align: right;
  white-space: nowrap;
}

/* Ma ligne. Deux écritures acceptées, `is-me` et `is-on` : la couche qui
   construit la liste choisit son mot, la feuille reconnaît les deux. */
.board-list > li.is-me,
.board-list > li.is-on {
  background: var(--su-accent-doux);
  border-color: var(--su-accent);
}

.board-list > li.is-me > :first-child,
.board-list > li.is-on > :first-child { color: var(--su-accent-texte); }

/* Les trois premiers rangs sont marqués par la graisse du chiffre, pas par
   une couleur : l'or ne tient que 2,8:1 sur le blanc, il n'est jamais du
   texte dans cette feuille. */
.board-list > li:nth-child(-n + 3) > :first-child {
  color: var(--su-ink);
  font-weight: 700;
}

.empty {
  padding: 26px 18px;
  border: 1px dashed var(--su-line);
  border-radius: var(--su-r-2);
  text-align: center;
  font-size: 14px;
  line-height: 1.5;
  color: var(--su-ink-2);
}
.empty[hidden] { display: none; }


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 11 · MODALES
   │ Ancrées en bas d'écran sur téléphone (c'est là que vit le pouce),
   │ centrées au-delà. Le pied porte les gestes : il prend la zone de sécurité
   │ des iPhone, et le corps qui défile retient son défilement.
   └────────────────────────────────────────────────────────────────────────── */

.modal {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  align-items: end;
  justify-items: center;
  padding: 0;
  background: var(--su-voile-fort);
  overscroll-behavior: contain;
}
.modal[hidden] { display: none; }

/* Colonne flexible et non grille : c'est le corps qui doit absorber la place
   restante et défiler, le pied restant collé en bas de la carte. Une grille
   aurait donné une piste `auto` qui refuse de descendre sous son contenu, et
   la modale « choisir une grille » (cent pastilles) serait sortie de
   l'écran par le bas. */
.modal-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
  max-width: 520px;
  max-height: 92dvh;
  padding: 18px 18px 0;
  background: var(--su-surface);
  border: 1px solid var(--su-line);
  border-bottom: 0;
  border-radius: var(--su-r-3) var(--su-r-3) 0 0;
  overflow: hidden;
  text-align: center;
}

.modal-card > * { flex: none; min-width: 0; }

.modal-wide { max-width: 620px; }

.modal-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 44px;   /* 44 et non 40 : sous 760px
     le bouton de fermeture passe à 44px (plancher tactile) et débordait donc
     sa piste de 4px. Invisible parce que le padding de la carte l'absorbait,
     mais une piste plus étroite que ce qu'elle porte finit toujours par se
     voir le jour où le padding change. */
  gap: 10px;
  align-items: center;
  text-align: left;
}

.modal-head h2 {
  font-size: 19px;
  letter-spacing: -0.02em;
  min-width: 0;
}

.modal-sub {
  margin-top: -6px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--su-ink-2);
  text-align: left;
}

.modal-sub b { color: var(--su-ink); }

/* Le corps est la seule partie qui défile. `overscroll-behavior: contain`
   pour que lire une longue liste de grilles ne fasse pas défiler la page
   derrière la modale. */
.modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  min-height: 0;
  padding-bottom: 4px;
  text-align: left;
}
.modal-body[hidden] { display: none; }

.modal-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px 0;
  padding-bottom: calc(14px + env(safe-area-inset-bottom));
  margin-top: auto;
  border-top: 1px solid var(--su-line);
  background: var(--su-surface);
}
.modal-foot[hidden] { display: none; }

.modal-foot .btn { flex: 1 1 auto; min-width: 0; }

/* Trois actions au pied de la modale de victoire : sur téléphone elles
   passent en colonne, l'action principale en tête. */
.modal-foot-3 { flex-wrap: wrap; }

/* ── Choisir une grille dans le niveau ─────────────────────────────────────
   Cent pastilles. Elles font partie d'une grappe de micro-actions, donc le
   plancher est celui des grappes (38px) et non celui des cibles isolées. */
.chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
  gap: 6px;
}
.chips[hidden] { display: none; }

.chips button {
  display: grid;
  place-items: center;
  min-height: 42px;
  padding: 4px 2px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  background: var(--su-surface);
  color: var(--su-ink-2);
  font: 500 13px/1 var(--su-mono);
  cursor: pointer;
  appearance: none;
  transition: background var(--su-t) ease, border-color var(--su-t) ease;
}

/* Grille terminée : la pastille se remplit d'un vert tenu, le nombre
   d'étoiles se lit dessous. Grille en cours : filet d'accent. */
.chips button.is-done {
  background: var(--su-tint-vert);
  border-color: transparent;
  color: var(--su-ink);
  font-weight: 600;
}

.chips button.is-on {
  border-color: var(--su-accent);
  background: var(--su-accent-doux);
  color: var(--su-accent-texte);
  font-weight: 600;
}

.chips button[disabled] {
  opacity: .45;
  cursor: default;
}

/* ── Grille terminée ───────────────────────────────────────────────────── */
.win-stars {
  display: flex;
  justify-content: center;
  gap: 8px;
  padding-top: 6px;
}
.win-stars[hidden] { display: none; }

.win-stars > * {
  width: 40px;
  height: 40px;
  fill: none;
  stroke: var(--su-line);
  stroke-width: 1.7;
  stroke-linejoin: round;
  color: var(--su-line);
}

.win-stars > * > svg { width: 100%; height: 100%; }

.win-stars > .is-on {
  fill: var(--su-or);
  stroke: var(--su-or);
  color: var(--su-or);
}

.win-title {
  font-size: 22px;
  letter-spacing: -0.025em;
}

.win-sub {
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--su-ink-2);
}

.win-note {
  padding: 11px 13px;
  border-radius: var(--su-r-1);
  background: var(--su-surface-2);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--su-ink-2);
  text-align: left;
}
.win-note[hidden] { display: none; }

.win-note b { color: var(--su-ink); }

.lose-ico {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin: 6px auto 0;
  border-radius: 999px;
  background: var(--su-tint-rouge);
  color: var(--su-rouge);
}
.lose-ico[hidden] { display: none; }

.lose-ico .ico { width: 26px; height: 26px; }

/* ── Mon compte ────────────────────────────────────────────────────────── */
.set-block {
  display: grid;
  gap: 10px;
  padding: 16px 0;
  border-bottom: 1px solid var(--su-line);
}
.set-block:last-child { border-bottom: 0; }

.set-title {
  font-family: var(--su-ui);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--su-ink-2);
}

.set-help {
  font-size: 13px;
  line-height: 1.5;
  color: var(--su-ink-2);
}
.set-help[hidden] { display: none; }

/* Les interrupteurs. C'est le <label> qu'on touche, pas la case : c'est lui
   qui porte la cible de 44px, la case n'en fait que 22. */
.switch-list {
  display: grid;
  gap: 4px;
}

.switch {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  min-height: 46px;
  padding: 4px 0;
  cursor: pointer;
}

.switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.switch-box {
  position: relative;
  width: 40px;
  height: 23px;
  border-radius: 999px;
  background: var(--su-surface-2);
  border: 1px solid var(--su-line);
  transition: background var(--su-t) ease, border-color var(--su-t) ease;
}

.switch-box::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 17px;
  height: 17px;
  border-radius: 999px;
  background: var(--su-ink-2);
  transition: transform var(--su-t) ease, background var(--su-t) ease;
}

.switch input:checked + .switch-box {
  background: var(--su-accent);
  border-color: var(--su-accent);
}

.switch input:checked + .switch-box::after {
  transform: translateX(17px);
  background: var(--su-sur-accent);
}

.switch input:focus-visible + .switch-box {
  outline: 2px solid var(--su-accent);
  outline-offset: 2px;
}

.switch-lab {
  font-size: 14px;
  line-height: 1.4;
  color: var(--su-ink);
  min-width: 0;
}

/* Le pied de « Mon compte » ne porte qu'une sortie : elle reste discrète. */
#accountModal .modal-foot .btn { flex: 0 1 auto; }


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 12 · ANIMATIONS
   │ Cinq mouvements, pas un de plus, et chacun accroché à un évènement réel :
   │ poser un chiffre, terminer un chiffre, boucler une unité, finir la
   │ grille, faire apparaître un message. Rien ne bouge « pour faire joli » :
   │ une interface qui s'anime sans raison est exactement le signal qu'on
   │ cherche à ne pas donner.
   │ Le bloc « mouvement réduit » ferme la section : il neutralise TOUT, y
   │ compris les transitions du reste de la feuille.
   └────────────────────────────────────────────────────────────────────────── */

/* Pose d'un chiffre : une montée d'échelle de 0,82 à 1 en 140ms. La classe
   est transitoire, posée par le JS le temps de l'animation, puis retirée :
   accrochée à `.is-user` elle rejouerait à chaque rendu de la grille. */
@keyframes su-pose {
  from { transform: scale(.82); opacity: .35; }
  to   { transform: scale(1);   opacity: 1; }
}

.cell.is-pose { animation: su-pose var(--su-t) ease-out; }

/* Chiffre terminé (les neuf posés) : un souffle sur la touche du pavé. Elle
   se déclenche à l'ajout de `.is-full`, donc exactement une fois. */
@keyframes su-souffle {
  0%   { transform: scale(1);    box-shadow: inset 0 0 0 0 var(--su-accent); }
  45%  { transform: scale(1.07); box-shadow: inset 0 0 0 2px var(--su-accent); }
  100% { transform: scale(1);    box-shadow: inset 0 0 0 0 var(--su-accent); }
}

.pad-key.is-full { animation: su-souffle 320ms ease-out; }

/* Ligne, colonne ou bloc bouclé : une vague de surbrillance qui TRAVERSE
   l'unité en 260ms. Le décalage de chaque case vient de `--su-i` (son rang
   dans l'unité), posé par le JS : sans lui les neuf cases clignotent
   ensemble, ce qui reste juste, mais ne raconte plus le balayage. */
@keyframes su-vague {
  0%   { background: var(--su-accent-doux); }
  40%  { background: var(--su-accent); }
  100% { background: transparent; }
}

.cell.is-done {
  animation: su-vague 260ms ease-out;
  animation-delay: calc(var(--su-i, 0) * 26ms);
}

/* Grille finie : un liseré d'accent fait le tour de la grille. */
@keyframes su-tour {
  0%   { border-color: var(--su-line-fort); }
  35%  { border-color: var(--su-accent); }
  100% { border-color: var(--su-line-fort); }
}

.board.is-done { animation: su-tour 900ms ease-in-out; }

/* Les étoiles de la modale de victoire, l'une après l'autre, 120ms d'écart.
   Le décalage est écrit ici et pas dans le JS : c'est du rythme, pas de la
   logique. */
@keyframes su-etoile {
  from { transform: scale(.4) rotate(-14deg); opacity: 0; }
  to   { transform: scale(1) rotate(0);       opacity: 1; }
}

.win-stars > * { animation: su-etoile 260ms ease-out backwards; }
.win-stars > *:nth-child(2) { animation-delay: 120ms; }
.win-stars > *:nth-child(3) { animation-delay: 240ms; }

/* La modale monte du bas, le message éphémère aussi : les deux arrivent par
   le même bord, celui du pouce. */
@keyframes su-monte {
  from { transform: translateY(14px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

.modal-card { animation: su-monte var(--su-t-2) ease-out; }

@keyframes su-toast {
  from { transform: translate(-50%, 12px); opacity: 0; }
  to   { transform: translate(-50%, 0);    opacity: 1; }
}

.toast { animation: su-toast var(--su-t) ease-out; }

/* ── Mouvement réduit ──────────────────────────────────────────────────────
   L'utilisateur qui demande moins de mouvement le demande PARTOUT, pas
   seulement sur les cinq animations nommées ci-dessus : d'où le sélecteur
   universel et le `!important`, qui met aussi les transitions au repos. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ 13 · TÉLÉPHONE ET GRANDS ÉCRANS
   │ Les règles de cette section ne sont pas des préférences : chacune a été
   │ payée par un défaut mesuré au navigateur dans ce dépôt, et les redire
   │ ici, groupées, est ce qui permet de les auditer d'un coup d'oeil.
   └────────────────────────────────────────────────────────────────────────── */

/* ── À partir de 560px la modale se décolle du bord et se centre ────────── */
@media (min-width: 560px) {
  .modal {
    align-items: center;
    padding: 24px;
  }

  .modal-card {
    border-bottom: 1px solid var(--su-line);
    border-radius: var(--su-r-3);
    padding-bottom: 0;
    max-height: 86dvh;
  }

  .modal-foot { padding-bottom: 16px; }

  .modal-foot-3 { flex-wrap: nowrap; }
}

/* ── Grands écrans : la lecture ne doit pas s'étirer ────────────────────── */
@media (min-width: 720px) {
  .stat-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .kv-win { padding: 18px 0; }
  .card { padding: 18px 20px; }
  .screen { gap: 18px; }
}


/* ══════════════════════════════════════════════════════════════════════════
   PROFIL, COULEUR ET AMIS
   ══════════════════════════════════════════════════════════════════════════ */

/* Les teintes proposées. Chaque pastille se peint avec SA teinte mais la
   clarté de la charte : ce que le joueur voit dans la grille est exactement ce
   qu'il obtiendra, contraste compris. */
.accent-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
  gap: 8px;
  margin-bottom: 14px;
}

.accent-swatch {
  position: relative;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  /* La MÊME clarté que les boutons pleins : la pastille montre exactement ce
     que le joueur obtiendra, coche comprise. */
  background: oklch(0.500 0.182 var(--su-sw));
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease;
}

:root[data-theme="dark"] .accent-swatch { background: oklch(0.727 0.178 var(--su-sw)); }

.accent-swatch:hover { transform: translateY(-1px); }

.accent-swatch[aria-checked="true"] {
  box-shadow: 0 0 0 2px var(--su-paper), 0 0 0 4px var(--su-ink);
}

/* La coche de la pastille retenue : un tracé, jamais un glyphe. */
.accent-swatch[aria-checked="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 13px;
  height: 8px;
  border-left: 2.4px solid var(--su-sur-accent);
  border-bottom: 2.4px solid var(--su-sur-accent);
  transform: rotate(-45deg) translate(1px, -2px);
}

.field-slider { margin-bottom: 6px; }

.field-slider input[type="range"] {
  width: 100%;
  min-height: 44px;
  accent-color: var(--su-accent);
  /* Le tour du cercle chromatique, à la clarté de la charte : la glissière
     montre le choix au lieu de le décrire. */
  background: linear-gradient(to right,
    oklch(0.500 0.182 0), oklch(0.500 0.182 60), oklch(0.500 0.182 120),
    oklch(0.500 0.182 180), oklch(0.500 0.182 240), oklch(0.500 0.182 300),
    oklch(0.500 0.182 360));
  border-radius: 999px;
  height: 10px;
  -webkit-appearance: none;
  appearance: none;
}

:root[data-theme="dark"] .field-slider input[type="range"] {
  background: linear-gradient(to right,
    oklch(0.727 0.178 0), oklch(0.727 0.178 60), oklch(0.727 0.178 120),
    oklch(0.727 0.178 180), oklch(0.727 0.178 240), oklch(0.727 0.178 300),
    oklch(0.727 0.178 360));
}

.field-slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--su-surface);
  border: 3px solid var(--su-ink);
  cursor: pointer;
}

.field-slider input[type="range"]::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--su-surface);
  border: 3px solid var(--su-ink);
  cursor: pointer;
}

/* Le lien d'invitation. `min-width: 0` sur le <code> : c'est un enfant flex en
   nowrap, et sans lui il impose la largeur de l'URL ENTIÈRE à toute la carte,
   l'ellipsis ne se déclenchant jamais (leçon de .ec-link-box, août 2026). */
.invite-box {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--su-line);
  border-radius: var(--su-r-1);
  background: var(--su-surface-2);
  margin-bottom: 10px;
}

.invite-box code {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 500 13px/1.4 var(--su-mono);
  color: var(--su-ink-2);
}

.invite-banner {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  margin-bottom: 16px;
  border: 1px solid var(--su-accent);
  border-radius: var(--su-r-2);
  background: var(--su-accent-doux);
  color: var(--su-ink);
}

.invite-banner .ico { flex: none; color: var(--su-accent-texte); }
.invite-banner p { margin: 0; font-size: 14px; line-height: 1.45; }
.invite-banner[hidden] { display: none; }

.amis-liste { list-style: none; margin: 0; padding: 0; }

.amis-liste li {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 6px 0;
  border-bottom: 1px solid var(--su-line);
}

.amis-liste li:last-child { border-bottom: 0; }
.amis-nom { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.amis-chiffres { flex: none; font: 500 12.5px/1 var(--su-mono); color: var(--su-ink-2); }

.board-friends { margin-top: 18px; display: flex; justify-content: center; }
.board-friends[hidden] { display: none; }

.seg-cercle { margin-bottom: 10px; }

/* ══ TÉLÉPHONE ═══════════════════════════════════════════════════════════ */
@media (max-width: 760px) {

  /* 1 · AUCUN CHAMP SOUS 16px.
     En dessous, iOS zoome tout seul au premier tap et laisse la page
     agrandie. La règle NOMME LES PORTEUSES : un sélecteur sur `input` nu
     (0,0,1) se fait battre par la moindre classe qui pose une taille
     (0,1,1), et la correction existe alors dans le fichier sans jamais
     s'appliquer. C'est exactement le défaut trouvé dans `console.css`. */
  .field input,
  .auth-form input,
  .auth-card input,
  .modal-body input,
  .set-block input,
  .auth-card input[type="text"],
  .auth-card input[type="password"],
  .modal-body input[type="password"],
  .modal-body select,
  .modal-body textarea {
    font-size: 16px;
  }

  /* 2 · CIBLES TACTILES.
     44px pour une action isolée, 38px pour les micro-actions en grappe (les
     pastilles de grille, les compteurs de l'en-tête). Les liens au fil d'une
     phrase restent hors périmètre : les gonfler casserait le paragraphe
     (exemption WCAG 2.5.8).
     Une seule cible reste en dessous, et c'est la case de la grille :
     mesurée à 36px sur un écran de 360 et 32px sur un écran de 320. Elle
     n'est pas redimensionnable, le carré de neuf sur neuf EST la
     présentation (exemption « essentiel ») : l'élargir demanderait une
     grille plus large que l'écran. Toutes les autres façons d'entrer un
     chiffre, elles, sont au-dessus du plancher (le pavé, le clavier). */
  .icon-btn,
  .top-btn,
  .top-back {
    width: 44px;
    height: 44px;
  }

  .seg-btn { min-height: 44px; }
  .chips button { min-height: 40px; }
  .meter { min-height: 38px; }
  .meter-btn { min-height: 42px; }
  /* `.btn-sm` posait 38px, sous le plancher. Ses porteurs ne sont pas des
     micro-actions en grappe (la tolérance à 38px vise celles-là) mais des
     boutons isolés de 117 à 275px de large : « Placer le chiffre », l'action
     principale du panneau d'indice, et le bouton du bandeau de mot de passe. */
  .btn-sm { min-height: 44px; }

  /* Les autres cibles (bouton, outil du pavé, touche de chiffre, onglet,
     interrupteur, ouverture d'un niveau) sont déjà au-dessus du plancher par
     construction : les réécrire ici ne ferait que RABAISSER une valeur plus
     généreuse, ce qui est la façon la plus discrète de casser une règle. */

  /* 3 · ZONES DE SÉCURITÉ.
     Tout ce qui flotte ou se colle en bas d'écran doit réserver la barre
     gestuelle des iPhone : la barre de navigation, les pieds de modale (ce
     sont eux qui portent les gestes) et le message éphémère. Les valeurs
     sont posées à la déclaration de chaque bloc ; ce rappel-ci ajoute la
     seule qui manquait, le pied du panneau du test. */
  .pl-panel { padding-bottom: calc(22px + env(safe-area-inset-bottom)); }

  /* 4 · LES ZONES DÉFILANTES RETIENNENT LEUR DÉFILEMENT.
     Lire une longue liste de grilles ne doit pas emporter la page derrière. */
  .modal-body,
  .table-wrap,
  .trophy-strip { overscroll-behavior: contain; }

  /* 5 · Trois actions au pied de la modale de victoire : elles s'empilent,
     l'action principale devant. Trois boutons côte à côte sur 360px font
     des cibles de 104px de large pour des libellés de douze caractères. */
  .modal-foot-3 { flex-direction: column-reverse; }
  .modal-foot-3 .btn { width: 100%; }

  /* 6 · Resserrement d'ensemble. Rien ici ne masque d'information : on
     réduit des marges et des médaillons, jamais un libellé. */
  .landing-top { padding: 12px 16px; }
  .screen-title { font-size: clamp(21px, 6vw, 26px); }
  .card { padding: 15px; }
  .card-head { grid-template-columns: 20px minmax(0, 1fr) auto; }
  .card-ico { width: 20px; height: 20px; }
  .card-ico .ico { width: 18px; height: 18px; }
  .prog-row { gap: 14px; }
  .ring { width: 72px; height: 72px; }
  .level-list > li { grid-template-columns: 40px minmax(0, 1fr); }
  .level-list > li > b:first-child,
  .level-list > li .pl-num { width: 40px; height: 40px; font-size: 17px; }
  .level-list button { grid-column: 1 / -1; width: 100%; }
  .trophy-grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
  .stat-val { font-size: 23px; }
}

@media (max-width: 380px) {
  .tab { font-size: 10.5px; }
  /* Les pastilles se resserrent en largeur, jamais en hauteur : 40px reste
     au-dessus du plancher des grappes, et c'est la hauteur qui décide si le
     pouce attrape la bonne. */
  .chips { grid-template-columns: repeat(auto-fill, minmax(38px, 1fr)); }
  .chips button { font-size: 12px; }
  .tool { font-size: 10.5px; }
}


/* ┌──────────────────────────────────────────────────────────────────────────
   │ SURVOL
   │ Ce qui suit n'existe QUE pour un appareil qui a un pointeur. Les états
   │ de survol viennent en dernier parce qu'un `:hover` posé plus haut ne
   │ serait jamais annulé par le bloc qui les neutralise : une media query
   │ n'ajoute aucune priorité, seul l'ORDRE tranche. Un bloc `hover: none`
   │ placé au milieu du fichier perd contre tout `:hover` déclaré après lui,
   │ et l'état reste alors COLLÉ après un tap sur téléphone (mesuré dans ce
   │ dépôt sur le bouton d'ouverture du site d'un client).
   └────────────────────────────────────────────────────────────────────────── */

@media (hover: hover) and (pointer: fine) {
  .btn:hover { border-color: var(--su-line-fort); }
  .btn-main:hover { background: var(--su-accent-texte); border-color: var(--su-accent-texte); }
  .btn-ghost:hover { border-color: var(--su-line-fort); color: var(--su-ink); }

  .icon-btn:hover,
  .top-btn:hover,
  .top-back:hover,
  .theme-btn:hover { color: var(--su-ink); background: var(--su-surface-2); }

  .auth-tab:hover { color: var(--su-ink); }
  .link:hover { text-decoration-thickness: 2.5px; }

  .tab:hover { color: var(--su-ink); }
  .tab.is-on:hover { color: var(--su-ink); }

  .level-list > li:hover { border-color: var(--su-line-fort); }
  .level-list > li.is-locked:hover { border-color: var(--su-line); }
  .level-list button:hover { background: var(--su-surface-2); }

  .cell:hover { background: var(--su-surface-2); }
  .cell.is-given:hover { background: transparent; }
  .cell.is-sel:hover,
  .cell.is-hint:hover { background: var(--su-accent-doux); }

  .pad-key:hover { background: var(--su-surface-2); }
  .pad-key.is-on:hover { background: var(--su-accent-doux); }
  .tool:hover { color: var(--su-ink); background: var(--su-surface-2); }
  .tool[aria-pressed="true"]:hover { color: var(--su-sur-accent); background: var(--su-accent-plein); }
  .tool[disabled]:hover { color: var(--su-ink-3); background: var(--su-surface); }

  .seg-btn:hover { color: var(--su-ink); }
  .chips button:hover { border-color: var(--su-line-fort); }
  .meter-btn:hover { border-color: var(--su-line-fort); }
  .board-list > li:hover { border-color: var(--su-line-fort); }
  .trophy-strip > li:hover { border-color: var(--su-line-fort); }
}

/* Ceinture : sur un appareil sans pointeur fin, aucun état de survol ne peut
   se figer, même si une règle plus haut venait à en réintroduire un. */
@media (hover: none) {
  .btn:hover { border-color: var(--su-line-fort); }
  .btn-main:hover { background: var(--su-accent); border-color: var(--su-accent); }
  .btn-ghost:hover { border-color: var(--su-line); color: var(--su-ink-2); }
  .icon-btn:hover,
  .top-btn:hover,
  .top-back:hover,
  .theme-btn:hover { color: var(--su-ink-2); background: transparent; }
  .theme-btn:hover { border-color: var(--su-line); }
  .auth-tab:hover { color: var(--su-ink-2); }
  .auth-tab.is-on:hover { color: var(--su-ink); }
  .tab:hover { color: var(--su-ink-2); }
  .tab.is-on:hover { color: var(--su-accent-texte); }
  .level-list > li:hover { border-color: var(--su-line); }
  .level-list > li.is-on:hover { border-color: var(--su-accent); }
  .level-list button:hover { background: var(--su-surface); }
  .cell:hover { background: transparent; }
  .cell.is-peer:hover { background: var(--su-surface-2); }
  .cell.is-same:hover { background: var(--su-surface-3); }
  .cell.is-sel:hover,
  .cell.is-hint:hover,
  .cell.is-ctx:hover { background: var(--su-accent-doux); }
  .pad-key:hover { background: var(--su-surface); }
  .pad-key.is-full:hover { background: var(--su-surface-2); }
  .pad-key.is-on:hover { background: var(--su-accent-doux); }
  .tool:hover { color: var(--su-ink-2); background: var(--su-surface); }
  .tool[aria-pressed="true"]:hover { color: var(--su-sur-accent); background: var(--su-accent-plein); }
  .tool[disabled]:hover { color: var(--su-ink-3); }
  .seg-btn:hover { color: var(--su-ink-2); }
  .seg-btn.is-on:hover,
  .seg-btn[aria-selected="true"]:hover,
  .seg-btn[aria-checked="true"]:hover { color: var(--su-ink); }
  .chips button:hover { border-color: var(--su-line); }
  .chips button.is-on:hover { border-color: var(--su-accent); }
  .meter-btn:hover { border-color: var(--su-line); }
  .board-list > li:hover { border-color: var(--su-line); }
  .board-list > li.is-me:hover,
  .board-list > li.is-on:hover { border-color: var(--su-accent); }
  .trophy-strip > li:hover { border-color: var(--su-line); }
  .link:hover { text-decoration-thickness: 1.5px; }
}
