/* =========================================================================
   design.css — Corpus linguistique national (Togo)
   Un seul fichier. Pas de préprocesseur, pas d'étape de build, pas de CDN.

   Cible principale : poste de travail, ~1280 px, clavier et souris.
   Les postes partagés de cybercafé en 1024×768 sont le plancher réel ;
   360 px reste lisible, en dégradation (§18), pas comme point de départ.

   Sommaire
   01. Jetons — thème clair
   02. Jetons — thème sombre
   03. Polices locales
   04. Réinitialisation et base
   05. Focus clavier
   06. Typographie
   07. Gabarit de page (en-tête, contenu, pied)
   08. Boutons et indices clavier
   09. Formulaires
   10. Coquille de tâche (anti-saut de mise en page)
   11. Progression
   12. Rangée de caractères spéciaux
   13. Audio
   14. Échelles de notation
   15. Options de vote
   16. Cartes, listes, choix de langue
   17. Messages, erreurs, file vide
   18. Utilitaires et dégradation tactile
   ========================================================================= */


/* == 01. Jetons — thème clair ============================================
   Contraste mesuré sur fond --bg :
   --fg 15,8:1 (AAA) · --fg-muted 7,6:1 (AAA) · --primary 7,1:1
   Fond blanc très légèrement chaud : moins d'éblouissement en plein soleil
   sans perte de contraste. Aucun gris clair ne porte de texte.
   ------------------------------------------------------------------------ */
:root {
  --bg:            #fdfcfa;
  --surface:       #ffffff;
  --surface-sunk:  #f2f0ea;
  --fg:            #14140f;
  --fg-muted:      #4a4a42;
  --border:        #c9c6bc;
  --border-strong: #6e6b62;

  --primary:       #10553a;
  --primary-hover: #0a3d29;
  --on-primary:    #ffffff;
  --primary-soft:  #e4efe8;

  --accent:        #8a5a00;   /* ocre : attention, secondaire */
  --accent-soft:   #f8efdc;

  --danger:        #96201a;
  --danger-hover:  #741612;
  --danger-soft:   #fbe9e6;

  /* Bandeau d'environnement. Jetons dédiés et non --danger : celui-ci
     s'éclaircit en mode sombre (#ff9585) et ne porte plus de texte blanc.
     Le bandeau garde le même rouge dans les deux thèmes — 9,2:1 sous du
     blanc — parce qu'il doit se voir, pas s'intégrer. */
  --banner-bg:     #96201a;
  --banner-fg:     #ffffff;

  --focus:         #0b57d0;

  /* Espacement, base 4 px */
  --s1: 0.25rem;  --s2: 0.5rem;   --s3: 0.75rem;  --s4: 1rem;
  --s5: 1.5rem;   --s6: 2rem;     --s7: 3rem;

  /* Échelle typographique.
     Grand pour ce qui se lit et se relit, normal pour ce qui se clique. */
  --t-small: 0.8125rem;  /* 13 px : indices clavier, méta de tableau */
  --t-label: 0.875rem;   /* 14 px : étiquettes, légendes d'échelle */
  --t-body:  1rem;       /* 16 px : plancher du texte courant */
  --t-md:    1.25rem;    /* 20 px : saisie et contribution en langue locale */
  --t-lg:    1.5rem;     /* 24 px */
  --t-xl:    1.9375rem;  /* 31 px : phrase source */
  --t-2xl:   2.4375rem;  /* 39 px : titres de page */

  --radius: 4px;
  --line-task: 1.45;
  --hit: 32px;           /* cible de pointage souris (44 px en tactile, §18) */
  --measure: 36rem;      /* largeur de lecture */
}


/* == 02. Jetons — thème sombre ============================================
   Conçu séparément, pas dérivé par inversion : le vert institutionnel donne
   2,1:1 sur fond sombre, inutilisable — il est remonté en clarté et
   désaturé. Le fond n'est pas noir pur : sur les dalles bas de gamme le noir
   pur augmente le halo du texte clair.
   Contraste : --fg 15,2:1 · --fg-muted 8,9:1 · --primary 8,4:1
   ------------------------------------------------------------------------ */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:            #101210;
    --surface:       #191c19;
    --surface-sunk:  #232722;
    --fg:            #f3f3ec;
    --fg-muted:      #c2c5bb;
    --border:        #45483f;
    --border-strong: #8b8f83;

    --primary:       #5fc492;
    --primary-hover: #8ddcb2;
    --on-primary:    #06170f;
    --primary-soft:  #1b2c23;

    --accent:        #e0a33c;
    --accent-soft:   #2c2415;

    --danger:        #ff9585;
    --danger-hover:  #ffb5a9;
    --danger-soft:   #331915;

    --focus:         #8ab4ff;
  }
}


/* == 03. Polices locales =================================================
   Une police web, un fichier, un poids : Charis SIL sous-ensemblée, servie
   depuis static/fonts/. Elle ne sert qu'au texte de tâche.
   La pile de repli est délibérément à empattements (Noto Serif, serif) :
   Charis et Gentium sont des polices à empattements, et si le sous-ensemble
   manque, le rendu doit rester du même genre, pas basculer en linéale.
   ----------------------------------------------------------------------- */
@font-face {
  font-family: "Charis Subset";
  src: url("../fonts/charis-subset.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0110-0113, U+0189-018B, U+0192, U+0194,
                 U+0196-0197, U+019B, U+01B2, U+0254, U+0256, U+025B,
                 U+014A-014B, U+028B, U+0269, U+0300-036F, U+25CC, U+2000-206F;
}

:root {
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans",
             "Droid Sans", sans-serif;
  --font-task: "Charis Subset", "Charis SIL", "Gentium Plus",
               "Gentium Book Plus", "Doulos SIL", "Noto Serif",
               "Liberation Serif", serif;
  --font-mono: ui-monospace, "Cascadia Mono", "Roboto Mono", monospace;
}


/* == 04. Réinitialisation et base ======================================== */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

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

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  line-height: 1.5;
}

img, svg { display: block; max-width: 100%; }
/* Les icônes du site sont dessinées au trait, sans attributs de présentation.
   Le code QR, lui, porte les siens — fond blanc, modules noirs, trait d'un
   module d'épaisseur — et cette règle les écrasait : fond effacé, modules
   soudés deux à deux, motif réduit à un pavé noir qu'aucun lecteur ne
   déchiffre. Un code QR n'est pas une icône : on l'exclut. */
svg:not(.qr) { fill: none; stroke: currentColor; stroke-width: 2; }
button, input, select, textarea { font: inherit; color: inherit; }

:where(a) { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
:where(a):hover { color: var(--primary-hover); text-decoration-thickness: 2px; }
:where(a):visited { color: var(--primary); }

.skip { position: absolute; left: -9999px; background: var(--surface);
  color: var(--fg); padding: var(--s3) var(--s4); z-index: 10; }
.skip:focus { left: var(--s3); top: var(--s3); }

.htmx-request .is-busy { visibility: visible; }
.is-busy { visibility: hidden; color: var(--fg-muted); font-size: var(--t-label); }


/* == 05. Focus clavier ===================================================
   Le parcours d'un item se fait au clavier : l'anneau de focus est un
   élément de premier plan, pas un détail. Contour de 3 px, plus un liseré
   de la couleur du fond pour qu'il reste visible sur une surface teintée.
   ----------------------------------------------------------------------- */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--bg);
}
.btn:focus-visible, .option:has(:focus-visible),
.scale-row label:has(input:focus-visible) {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}
textarea:focus-visible, input:focus-visible {
  border-color: var(--focus);
}


/* == 06. Typographie ==================================================== */
h1, h2, h3 { line-height: 1.2; font-weight: 700; text-wrap: balance; }
h1 { font-size: var(--t-2xl); }
h2 { font-size: var(--t-lg); }
h3 { font-size: var(--t-body); }

p, li { text-wrap: pretty; }
.prose { max-width: var(--measure); display: grid; gap: var(--s4); }
.prose h2 { margin-top: var(--s4); }
.prose ul, .prose ol { padding-left: var(--s5); display: grid; gap: var(--s2); }

.lead { font-size: var(--t-md); }
.meta { font-size: var(--t-label); color: var(--fg-muted); }
.mono { font-family: var(--font-mono); }

/* Étiquette de bloc : petites capitales, sert à distinguer les blocs sans
   recourir à un filet coloré (réservé au bloc source, §10). */
.label {
  font-size: var(--t-small); font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--fg-muted);
}

/* Texte en langue locale : police de tâche, pas de ligature */
.lg, [lang]:not([lang^="fr"]) {
  font-family: var(--font-task);
  font-variant-ligatures: none;
}


/* == 07. Gabarit de page ================================================ */
.wrap { width: 100%; max-width: 76rem; margin-inline: auto; padding-inline: var(--s5); }

/* Bandeau d'environnement : au-dessus de tout, y compris de l'en-tête, et
   jamais masqué au défilement — sa raison d'être est qu'on ne puisse pas le
   manquer. Le texte vient du réglage SITE_BANNER. */
.env-banner {
  margin: 0; padding-block: var(--s1);
  background: var(--banner-bg); color: var(--banner-fg);
  font-size: var(--t-label); font-weight: 700;
  letter-spacing: 0.08em; text-align: center; text-transform: uppercase;
}

.site-header { background: var(--surface); border-bottom: 2px solid var(--border-strong); }
.site-header .wrap {
  display: flex; align-items: center; gap: var(--s5);
  min-height: 48px; padding-block: var(--s2); flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: var(--s2); font-weight: 700;
  color: var(--fg); text-decoration: none; }
.brand-mark { width: 24px; height: 24px; flex: none; background: var(--primary); border-radius: 2px; }
/* En-tête repliable. Sur téléphone, la navigation d'un contributeur connecté
   compte jusqu'à cinq entrées plus la déconnexion : dépliée, elle prenait trois
   lignes avant même le contenu. Elle se replie donc derrière un « Menu », et
   redevient une rangée dès qu'il y a la place. `details` s'en charge sans une
   ligne de JavaScript et reste utilisable au clavier. */
.site-menu { margin-left: auto; }
/* Déplié, la liste prend toute la largeur de l'en-tête et s'aligne à gauche :
   poussée à droite par le `margin-left` du bouton, elle se lisait de travers et
   les zones touchables se réduisaient au bord de l'écran. Le bouton, lui, reste
   à droite. */
.site-menu[open] { flex: 1 0 100%; margin-left: 0; }
.site-menu[open] > summary { margin-left: auto; width: max-content; }
.site-menu > summary {
  display: flex; align-items: center; min-height: var(--hit);
  padding-inline: var(--s2); font-weight: 600; color: var(--primary);
  cursor: pointer; list-style: none;
}
.site-menu > summary::-webkit-details-marker { display: none; }
.site-menu > summary::marker { content: ""; }
.site-menu > summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Déplié sur téléphone : une entrée par ligne, pleine largeur, chacune assez
   haute pour être touchée sans viser. */
.site-nav { display: flex; flex-direction: column; }
.site-nav a, .site-nav .nav-user, .nav-logout button {
  display: flex; align-items: center; min-height: var(--hit);
  padding-inline: var(--s2);
}
.site-nav a { text-decoration: none; font-weight: 600; }
.site-nav a:hover, .site-nav a[aria-current="page"] { text-decoration: underline; }

@media (min-width: 48rem) {
  /* La place ne manque plus : le bouton disparaît et la navigation se remet en
     rangée — y compris `details` fermé, ce qu'il est toujours au chargement.

     Il y faut deux règles, pour deux générations de navigateurs. `display` sur
     l'enfant suffisait tant que le repli était une affaire d'affichage. Depuis
     Chrome 131 et Safari 18.4, un `details` fermé masque son contenu par
     `content-visibility: hidden` posé sur `::details-content`, que `display` ne
     défait pas : la navigation avait disparu de l'en-tête sur ordinateur. Les
     navigateurs qui ignorent ce pseudo-élément ignorent aussi la seconde règle
     et gardent l'ancien comportement. */
  .site-menu > summary { display: none; }
  .site-menu::details-content { content-visibility: visible; block-size: auto; }
  .site-menu > .site-nav {
    display: flex; flex-direction: row; align-items: center;
    gap: var(--s4); flex-wrap: wrap;
  }
  .site-nav a { padding-inline: var(--s1); }
}

main { display: block; padding-block: var(--s5) var(--s7); }

.site-footer { border-top: 1px solid var(--border); padding-block: var(--s5);
  color: var(--fg-muted); font-size: var(--t-label); }
.site-footer .wrap { display: grid; gap: var(--s2); }

.page-head { display: grid; gap: var(--s3); margin-bottom: var(--s5); max-width: var(--measure); }


/* == 08. Boutons et indices clavier ======================================
   Échelle de bureau : 32 px de haut, 40 px pour l'action principale d'un
   item. Chaque action porte un libellé écrit ; jamais une icône seule,
   jamais la couleur seule. L'indice clavier est visible dans le bouton.
   ----------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: var(--hit);
  padding: var(--s2) var(--s4);
  border: 1px solid transparent; border-radius: var(--radius);
  font-size: var(--t-body); font-weight: 600;
  text-align: center; text-decoration: none; cursor: pointer;
  background: var(--surface-sunk); color: var(--fg);
}
.btn:hover { border-color: var(--border-strong); }
.btn--primary { background: var(--primary); color: var(--on-primary); }
.btn--primary:hover { background: var(--primary-hover); border-color: transparent; }
.btn--ghost { background: transparent; border: 1px solid var(--border-strong); }
.btn--ghost:hover { background: var(--surface-sunk); }
.btn--danger { background: var(--surface); color: var(--danger); border: 1px solid var(--danger); }
.btn--danger:hover { background: var(--danger-soft); }
.btn--rec { background: var(--danger); color: #fff; }
.btn--rec:hover { background: var(--danger-hover); border-color: transparent; }
.btn--lg { min-height: 40px; padding: var(--s2) var(--s5); }
.btn--block { width: 100%; }
.btn[disabled], .btn[aria-disabled="true"] {
  background: var(--surface-sunk); color: var(--fg-muted);
  border-color: var(--border); cursor: not-allowed;
}
.btn svg { width: 18px; height: 18px; flex: none; }

/* Indice clavier — dans un bouton, ou seul à côté d'une action */
.kbd {
  font-family: var(--font-mono); font-size: var(--t-small); font-weight: 600;
  line-height: 1; padding: 3px 5px; border-radius: 3px;
  border: 1px solid currentColor; opacity: 0.75;
  white-space: nowrap;
}
.kbd-hint { display: inline-flex; align-items: center; gap: var(--s2);
  font-size: var(--t-label); color: var(--fg-muted); }

/* Rangée d'actions : ordre identique sur les cinq files, jamais empilée. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
.actions--end { justify-content: flex-end; }
.actions .sep { margin-left: auto; }
.actions--auth-code > .btn { flex: 1 1 10rem; }
.auth-code-change { margin: 0; padding-top: var(--s1); }


/* == 09. Formulaires ==================================================== */
.field { display: grid; gap: var(--s2); margin-bottom: var(--s4); max-width: 30rem; }
.field > label { font-weight: 600; }
.hint { font-size: var(--t-label); color: var(--fg-muted); }

input[type="text"], input[type="email"], input[type="password"], select, textarea {
  width: 100%;
  min-height: var(--hit);
  padding: var(--s2) var(--s3);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}
textarea { resize: vertical; }

/* Zone de saisie de traduction : police de tâche, 20 px, largeur pleine */
.answer {
  width: 100%;
  font-family: var(--font-task);
  font-size: var(--t-md);
  line-height: var(--line-task);
  min-height: 6.5rem;
  padding: var(--s3);
}

[aria-invalid="true"], .field--error input, .field--error textarea {
  border-color: var(--danger); border-width: 2px; background: var(--danger-soft);
}
.field-error { color: var(--danger); font-weight: 600; font-size: var(--t-label); }

.check { display: flex; gap: var(--s3); align-items: flex-start;
  min-height: var(--hit); padding: var(--s1) 0; }
.check input { width: 18px; height: 18px; flex: none; margin-top: 4px; accent-color: var(--primary); }


/* == 10. Coquille de tâche ===============================================
   Entre deux items rien ne bouge : la zone d'item a une hauteur réservée,
   le bloc source une hauteur minimale, la rangée d'actions est toujours au
   même endroit. À 1280 px un item tient sans défilement.

   Le filet vertical coloré est réservé au bloc source : il signifie « ceci
   vous est donné ». Tout le reste se différencie par le fond et l'étiquette.
   ----------------------------------------------------------------------- */
.task { max-width: 62rem; }

.task-bar {
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
  padding-bottom: var(--s2); border-bottom: 1px solid var(--border);
  margin-bottom: var(--s4);
}
/* La langue de travail se lit d'un coup d'œil : c'est l'information qui dit
   quoi faire, et quelqu'un qui contribue dans deux langues passe de l'une à
   l'autre sans changer d'écran. */
.task-bar .lang { font-weight: 700; font-size: var(--t-md); }
.task-bar .lang .lg { font-size: 1.08em; }
.task-bar .spacer { margin-left: auto; }

.task-item {
  min-height: 21rem;                        /* hauteur réservée : anti-saut */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: var(--s4);
}
.task-item > * { min-width: 0; }

/* Deux colonnes quand l'écran le permet : source à gauche, travail à droite.
   La rangée d'actions reste sur toute la largeur, en bas, inchangée. */
@media (min-width: 68rem) {
  .task-item--split {
    grid-template-columns: minmax(0, 24rem) minmax(0, 1fr);
    grid-template-rows: 1fr auto;
  }
  .task-item--split > .actions { grid-column: 1 / -1; }
}

.source {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 6px solid var(--primary);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  min-height: 8rem;
  display: grid; align-content: center; gap: var(--s3);
}
.source .sentence { font-family: var(--font-task); font-size: var(--t-xl); line-height: 1.25; }
.source--sm .sentence { font-size: var(--t-md); }
.source [lang="fr"].sentence { font-family: var(--font-ui); }

/* Contribution relue : fond enfoncé et étiquette, sans filet coloré. */
.contrib {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  display: grid; gap: var(--s2);
}
.contrib .text { font-family: var(--font-task); font-size: var(--t-md); line-height: var(--line-task); }


/* == 11. Progression =====================================================
   Décompte de ce qui a été fait, sans total : le service n'a ni session ni
   lot, et un dénominateur inventé serait un compteur qui ment.
   ----------------------------------------------------------------------- */
.progress { display: flex; align-items: baseline; gap: var(--s2); margin-bottom: var(--s4); }
.progress .count { font-weight: 700; font-variant-numeric: tabular-nums; font-size: var(--t-md); }
.progress .unit { color: var(--fg-muted); font-size: var(--t-label); }


/* == 12. Rangée de caractères spéciaux ===================================
   Cellules teintées sans bordure, pilotées par la langue en cours. Défile
   horizontalement, piste masquée, ne se replie jamais : un repli
   déplacerait la zone de saisie.
   ----------------------------------------------------------------------- */
.charbar {
  display: flex; gap: var(--s2);
  overflow-x: auto; overscroll-behavior-x: contain;
  padding: var(--s3) 0;
  scrollbar-width: none;
}
.charbar::-webkit-scrollbar { display: none; }
.charbar button {
  flex: none;
  min-width: 36px; height: 36px;
  padding: 0 var(--s2);
  font-family: var(--font-task); font-size: var(--t-md); font-weight: 400;
  background: var(--surface-sunk); color: var(--fg);
  border: 0; border-radius: var(--radius); cursor: pointer;
}
.charbar button:hover { background: var(--primary-soft); }
.charbar .tone { color: var(--fg-muted); letter-spacing: 0.08em; }


/* == 13. Audio ===========================================================
   Lecteur natif du navigateur : robuste, gratuit en bande passante. On ne
   lui impose que la largeur, l'alignement et la marge.
   ----------------------------------------------------------------------- */
audio { display: block; width: 100%; max-width: 30rem; height: 40px; }

.audio {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  display: grid; gap: var(--s3); align-content: center;
  min-height: 8rem;
}
.rec { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.rec-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--danger); flex: none; }
.rec-state { font-weight: 600; font-variant-numeric: tabular-nums; }

/* Hiérarchie pilotée par l'état servi dans data-rec-box : tant qu'aucun clip
   n'existe, « Enregistrer » est le seul bouton plein et « Envoyer » est
   inactif. L'ordre visuel suit l'ordre réel des actions. */
[data-rec-box="idle"] ~ .actions [value="submit"],
[data-rec-box="recording"] ~ .actions [value="submit"] {
  background: var(--surface-sunk); color: var(--fg-muted);
  border-color: var(--border); pointer-events: none;
}
[data-rec-box="recording"] [data-rec="start"],
[data-rec-box="ready"] [data-rec="start"] { display: none; }
[data-rec-box="idle"] [data-rec="stop"],
[data-rec-box="ready"] [data-rec="stop"],
[data-rec-box="idle"] [data-rec="redo"] { display: none; }
[data-rec-box="idle"] audio, [data-rec-box="recording"] audio { display: none; }
[data-rec-box="idle"] .rec-dot { background: var(--border-strong); }


/* == 14. Échelles de notation ============================================
   1 à 5, chiffres cliquables ou touches 1 à 5. Le chiffre porte le sens,
   pas un dégradé de couleur.
   ----------------------------------------------------------------------- */
.scale { display: grid; gap: var(--s2); }
.scale > .legend { font-weight: 600; }
.scale-row { display: flex; gap: var(--s2); max-width: 22rem; }
.scale-row label {
  flex: 1 1 0; min-height: 36px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); font-weight: 600;
  font-variant-numeric: tabular-nums; cursor: pointer;
}
.scale-row input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.scale-row label:has(input:checked) {
  background: var(--primary); color: var(--on-primary); border-color: var(--primary);
}
.scale-ends { display: flex; justify-content: space-between; max-width: 22rem;
  font-size: var(--t-label); color: var(--fg-muted); }


/* == 15. Options de vote =================================================
   « Aucune n'est correcte » utilise le même composant, la même taille et la
   même cible que les propositions. Reléguée, elle pousserait les votants
   vers la moins mauvaise proposition et une erreur deviendrait la vérité de
   référence du corpus. Elle est en dernier parce qu'on lit avant de juger.
   ----------------------------------------------------------------------- */
.options { display: grid; gap: var(--s2); }
.option {
  display: grid; grid-template-columns: auto auto 1fr; gap: var(--s3);
  align-items: start;
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  cursor: pointer;
}
.option:hover { background: var(--primary-soft); }
.option input { width: 18px; height: 18px; margin: 4px 0 0; accent-color: var(--primary); }
.option:has(input:checked) { border-color: var(--primary); border-width: 2px;
  background: var(--primary-soft); padding: calc(var(--s3) - 1px) calc(var(--s4) - 1px); }
.option > .body { display: grid; gap: var(--s1); min-width: 0; }
.option .text { font-family: var(--font-task); font-size: var(--t-md); line-height: var(--line-task); }
.option--none .text { font-family: var(--font-ui); font-weight: 700; }


/* == 16. Cartes, listes, choix de langue ================================= */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--s5);
  display: grid; gap: var(--s3); align-content: start;
}
.card h2 { font-size: var(--t-body); }

/* Le bloc qui renvoie vers l'application : le texte, et le code QR à côté.
   Une seule colonne sur téléphone — l'appareil qui lit la page est déjà celui
   qui installerait l'application, et le code n'y sert alors à rien : il passe
   sous le texte plutôt que de lui voler la moitié de la largeur. */
.app-card { grid-template-columns: 1fr; }
.app-card__text { display: grid; gap: var(--s3); align-content: start; }
/* Le fond blanc appartient au code, pas à la page : un motif sombre sur fond
   sombre ne se scanne pas, et le site suit le thème du système. */
.app-card__qr {
  background: #fff; border-radius: var(--radius); padding: var(--s3);
  width: max-content; max-width: 100%; justify-self: start;
}
.app-card__qr .qr { display: block; width: 160px; height: auto; }

@media (min-width: 48rem) {
  .app-card { grid-template-columns: 1fr auto; gap: var(--s5); align-items: start; }
  .app-card__qr { justify-self: end; }
  .app-card__qr .qr { width: 180px; }
}

.grid { display: grid; gap: var(--s4); }
@media (min-width: 52rem) {
  .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.langlist { display: grid; gap: var(--s2); list-style: none; padding: 0; }
@media (min-width: 52rem) { .langlist { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.langlist a {
  display: flex; align-items: center; gap: var(--s3);
  min-height: 40px; padding: var(--s2) var(--s4);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); text-decoration: none; color: var(--fg);
}
.langlist a:hover { background: var(--primary-soft); }
.langlist .name { font-family: var(--font-task); font-size: var(--t-md); }
.langlist .count { margin-left: auto; font-size: var(--t-label);
  color: var(--fg-muted); font-variant-numeric: tabular-nums; }

.tasklist { display: grid; gap: var(--s4); list-style: none; padding: 0; }
@media (min-width: 52rem) { .tasklist { grid-template-columns: repeat(2, minmax(0, 1fr)); } }


/* == 17. Messages, erreurs, file vide ====================================
   Fond teinté et préfixe écrit (« Erreur — »), sans filet latéral : le
   filet reste le signe du bloc source. Lisible en niveaux de gris.
   ----------------------------------------------------------------------- */
.msg {
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  max-width: 46rem;
  margin-bottom: var(--s4);
}
.msg strong { font-weight: 700; }
.msg--ok    { background: var(--primary-soft); border-color: var(--primary); }
.msg--warn  { background: var(--accent-soft);  border-color: var(--accent); }
.msg--error { background: var(--danger-soft);  border-color: var(--danger); }
.msg--error strong { color: var(--danger); }

/* File vide : même hauteur réservée qu'un item. */
.empty {
  min-height: 21rem;
  display: grid; align-content: center; justify-items: start; gap: var(--s4);
  padding: var(--s5) var(--s6);
  background: var(--surface); border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
}
.empty h2 { font-size: var(--t-lg); }
.empty p { max-width: 34rem; color: var(--fg-muted); }

/* Écran non raccordé au service : signalé explicitement, pas déguisé. */
.mockup-note {
  display: inline-block; padding: var(--s1) var(--s3);
  font-size: var(--t-label); font-weight: 600;
  background: var(--accent-soft); border: 1px dashed var(--accent);
  border-radius: var(--radius); margin-bottom: var(--s3);
}


/* == 18. Utilitaires et dégradation tactile ============================== */
.stack   { display: grid; gap: var(--s4); }
.stack-5 { display: grid; gap: var(--s5); }
.row     { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: center; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; }
.tnum { font-variant-numeric: tabular-nums; }

/* Dégradation tactile : sous 52 rem on repasse aux cibles de 44 px et à une
   colonne unique. Ce n'est pas la cible principale, c'est un repli correct. */
@media (max-width: 52rem) {
  :root { --hit: 44px; --t-2xl: 1.9375rem; --t-xl: 1.5625rem; }
  .wrap { padding-inline: var(--s4); }
  .btn { padding: var(--s3) var(--s4); }
  .btn--lg { min-height: 48px; }
  .charbar button { min-width: 44px; height: 44px; }
  .scale-row label { min-height: 44px; }
  .langlist a { min-height: 48px; }
  .actions > .btn--primary { flex: 1 1 12rem; }
  .kbd { display: none; }            /* aucun clavier physique à annoncer */
}

@media (max-width: 30rem) {
  .actions--auth-code > .btn { flex-basis: 100%; width: 100%; }
}

@media print {
  .site-nav, .charbar, .actions { display: none; }
  body { background: #fff; color: #000; }
}


/* == 19. Ajustements d'intégration =======================================
   Ajouté après relecture sur l'application réelle, pas dans la maquette.   */

/* Le bandeau de titre était borné à la largeur de lecture (36 rem) pendant
   que les cartes s'étiraient jusqu'à 76 rem : l'écart se lisait comme un
   défaut d'alignement plutôt que comme un contrôle de la longueur de ligne.
   Les pages informatives sont ramenées à une seule colonne commune. */
.page-head { max-width: 46rem; }
.stack-5 { max-width: 60rem; }

/* La marque était un aplat vert sans forme : elle occupait la place d'un
   logo sans en être un. Elle porte maintenant une lettre de l'alphabet éwé —
   l'identité du projet tient à ces écritures, autant le dire dès l'en-tête. */
.brand-mark {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 3px;
  color: #fff;
  font-family: var(--font-task);
  font-size: 17px;
  line-height: 1;
}
.brand-mark::after { content: "ɖ"; }

/* Identité et déconnexion : deux choses distinctes. Un nom n'est pas une
   action et ne doit pas peser autant qu'un lien de navigation ; la
   déconnexion reste atteignable, car une session laissée ouverte sur un poste
   partagé est un vrai risque. */
.nav-user {
  display: flex; align-items: center; min-height: var(--hit);
  padding-inline: var(--s1);
  color: var(--fg-muted); font-weight: 400;
}
.nav-logout { display: flex; }
.nav-logout button {
  border: 0; background: none; padding-inline: var(--s1);
  min-height: var(--hit); font: inherit; font-weight: 400;
  color: var(--fg-muted); cursor: pointer;
}
.nav-logout button:hover { text-decoration: underline; color: var(--fg); }

/* Un accusé de réception n'a pas à peser autant qu'une erreur. L'encadré est
   réservé à ce qui demande une décision ; la confirmation passagère se lit et
   s'oublie. */
.msg--ok {
  background: none;
  border: 0;
  padding: 0 0 var(--s3);
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.msg--ok strong { color: var(--primary); font-weight: 600; }
