/* ============================================================================
 * RÉGLETTE — apps/shared/css/tokens.css
 * ============================================================================
 * Système de registres réglés, achromatique par principe. L'encre de couleur est
 * réservée au colis et au danger.
 *
 * Ce fichier est un REMPLACEMENT INTÉGRAL, pas une évolution. L'ancien fichier
 * (palette Oxide/Kraft, Switzer, rampe de rayons 3/5/9/13/21/33, huit courbes,
 * dix-huit durées, ombres à quatre couches) portait le monde qu'on abandonne.
 * Aucune de ses valeurs ne survit, y compris quand elle « marchait ».
 *
 * Sources, dans cet ordre d'autorité :
 *   DESIGN.md                          la charte
 *   /tmp/hmd-redesign/spec-color.md    amende la charte, A1 à A10  (couleur)
 *   /tmp/hmd-redesign/spec-type.md     (typographie)
 *   /tmp/hmd-redesign/spec-space.md    (espacement, géométrie, matière, z-index)
 *   /tmp/hmd-redesign/spec-motion.md   (mouvement)
 *
 * Arbitrage relevé : spec-color §6.1 et spec-space §9 donnaient deux encres
 * d'assombrissement sombres différentes (6% 0.006 / 8% 0.010) et deux alphas de
 * voile (0.72 / 0.62). spec-color tranche, parce qu'elle démontre le défaut
 * (§6.1 : un voile dérivé de --ink ÉCLAIRCIT la page en thème sombre) et en
 * calcule la conséquence. Toutes les ombres et le voile suivent --shade.
 *
 * Plan du fichier :
 *   1  Couleur — clair             7  Mouvement
 *   2  Couleur — sombre            8  Surfaces du navigateur
 *   3  Typographie                 9  Rôles typographiques .t-*
 *   4  Espacement                 10  COMPAT — transitoire, à supprimer
 *   5  Géométrie                  11  Mouvement réduit
 *   6  Matière et z-index
 *
 * Toute couleur naît en OKLCH. Les hex en commentaire sont le repli des moteurs
 * sans OKLCH et la valeur sur laquelle les ratios sont calculés. Les ratios sont
 * notés contre --ground du même thème, sauf « /inv » : contre --ink-inverse,
 * parce qu'une plaque est un fond et se mesure avec le texte qu'elle porte.
 * ========================================================================== */


/* ============================================================================
 * 1. COULEUR — « Papier », thème clair, défaut des trois surfaces
 * ========================================================================== */

:root,
[data-theme="light"] {
  color-scheme: light;                          /* les contrôles natifs suivent le thème déclaré, pas l'OS */

  /* --- Fonds : trois paliers plus un, un seul niveau d'imbrication ------- */
  --surface:      oklch(99.2% 0.002 255);       /* #FBFCFE  CE QUI FLOTTE, et rien d'autre : menu, modale, tiroir, toast */
  --ground:       oklch(96.5% 0.004 255);       /* #F2F4F6  page, registre, champ au repos */
  --ground-sunk:  oklch(93.8% 0.005 255);       /* #E8EBEE  en-tête de table, bloc de code, ligne survolée */
  --ground-deep:  oklch(90%   0.006 255);       /* #DBDEE2  A1 : puits imbriqué, ligne sélectionnée — le cran qui manquait */

  /* --- Encres : le papier est FROID (teinte 255), jamais crème ni ivoire -- */
  --ink:          oklch(21%   0.012 255);       /* #15191E  16.01:1  texte primaire */
  --ink-2:        oklch(36%   0.010 255);       /* #3A3D42   9.89:1  texte secondaire */
  --ink-3:        oklch(46%   0.009 255);       /* #55585D   6.48:1  méta, cartouche — PLANCHER du texte */
  --ink-4:        oklch(58%   0.008 255);       /* #777B7F   3.87:1  bord de contrôle, glyphe inactif — JAMAIS du texte */
  --ink-disabled: oklch(70%   0.006 255);       /* #9C9FA2   2.41:1  inactif, exempté de seuil */
  --ink-inverse:  oklch(97%   0.003 255);       /* #F4F5F7  /inv 16.18:1  texte sur plaque */

  /* --- Filets : trois graisses, aucune couleur -------------------------- */
  /* Un filet se dessine en border ou box-shadow inset, JAMAIS en
   * background-image : un filet qui est un fond disparaît en forced-colors et à
   * l'impression, et déclenche le détecteur de fond quadrillé. */
  --rule-hair:    oklch(21% 0.012 255 / 0.10);  /* séparation intra-groupe : ligne de table, item de menu */
  --rule:         oklch(21% 0.012 255 / 0.22);  /* limite de registre, bord de zone */
  --rule-strong:  oklch(21% 0.012 255 / 0.55);  /* rendu #787C7F, 3.82:1  limite structurelle */

  /* --- Films d'état ------------------------------------------------------ */
  --state-hover:      var(--ground-sunk);       /* #E8EBEE  1.09:1  en clair l'état de ligne est un palier de fond */
  --state-selected:   var(--ground-deep);       /* #DBDEE2  1.22:1 */
  --state-focus-halo: oklch(21% 0.012 255 / 0.18);  /* la COULEUR du halo ; sa géométrie est --focus-ring (§6) */
  --selection-bg:     oklch(21% 0.012 255 / 0.14);  /* rendu #D3D5D8, --ink dessus 12.00:1 */

  /* --- Assombrissement : voile et ombres --------------------------------- */
  /* A6 : --shade est un token dédié, JAMAIS dérivé de --ink. En sombre --ink
   * est blanc, et la lecture littérale de la charte donnait un voile qui
   * éclaircit la page et des ombres blanches. */
  --shade:  oklch(21% 0.012 255);               /* #15191E  l'encre d'assombrissement du thème clair */
  --scrim:  oklch(21% 0.012 255 / 0.44);        /* rendu #919497, --surface par-dessus 2.97:1 */

  /* --- Plaque d'encre : le seul « accent » du système -------------------- */
  /* Il n'y a pas de couleur d'accent. Il y a un aplat d'encre, petit et décisif
   * ou pleine largeur et final : bouton primaire, tampon delivered, soulignement
   * de navigation active, bande de clôture. Jamais en fond de carte. */
  --plaque:        oklch(21% 0.012 255);        /* #15191E  /inv 16.18:1 */
  --plaque-hover:  oklch(27% 0.012 255);        /* #23272C  /inv 13.77:1  un survol reste subtil */
  --plaque-active: oklch(17% 0.012 255);        /* #0C1015  /inv 17.49:1  l'appui s'enfonce, il ne s'éclaircit pas */

  /* --- Les sept pigments de statut --------------------------------------- */
  /* Une loi : la clarté DESCEND vers le sol du thème à mesure que le colis
   * avance. Lue en niveaux de gris, l'échelle reste une progression.
   * Trois rôles par statut : marque (3:1), mot (4.5:1), lavis (opaque, A5).
   * A4 : onze chromas de la charte sortaient du gamut sRGB — le navigateur les
   * écrasait en silence. Les valeurs ci-dessous sont les tenables (95 % du max).
   * Conséquence assumée : en clair, sarcelle et verts sont structurellement
   * moins saturés que bleu et rouge. C'est la forme du gamut, pas un réglage. */

  --status-preparing:             oklch(58% 0.018 262);    /* #757B85  3.86:1  glyphe paquet */
  --status-preparing-ink:         oklch(38% 0.020 262);    /* #3D434D  9.03:1 */
  --status-preparing-ring:        oklch(93.64% 0.005 258); /* #E8EAED  lavis OPAQUE : un lavis transparent change de contraste selon son support */

  --status-shipped:               oklch(52% 0.150 258);    /* #2A67BD  5.05:1  glyphe envoi */
  --status-shipped-ink:           oklch(38% 0.134 258);    /* #063E88  9.28:1 */
  --status-shipped-ring:          oklch(93.1% 0.013 252);  /* #E2E9F1 */

  --status-in-transit:            oklch(47% 0.076 200);    /* #0F676C  6.00:1  glyphe route */
  --status-in-transit-ink:        oklch(36% 0.058 200);    /* #084649  9.61:1 */
  --status-in-transit-ring:       oklch(92.78% 0.010 213); /* #E0E9EB */

  --status-out-for-delivery:      oklch(41% 0.096 155);    /* #0B5932  7.65:1  glyphe camion */
  --status-out-for-delivery-ink:  oklch(34% 0.080 155);    /* #064425 10.23:1 */
  --status-out-for-delivery-ring: oklch(92.44% 0.009 180); /* #E0E8E6 */

  --status-delivered:             oklch(34% 0.094 148);    /* #054419 10.31:1  glyphe coche — seul tampon à fond plein */
  --status-delivered-ink:         oklch(30% 0.083 148);    /* #043813 12.05:1 */
  --status-delivered-ring:        oklch(91.9% 0.008 177);  /* #DFE6E4 */

  --status-exception:             oklch(45% 0.173 25);     /* #9F111D  7.41:1  glyphe alerte — HORS échelle */
  --status-exception-ink:         oklch(40% 0.154 25);     /* #880C17  9.02:1 */
  --status-exception-ring:        oklch(92.07% 0.011 355); /* #EBE2E5 */

  --status-delayed:               oklch(52% 0.107 70);     /* #905C10  5.11:1  HORS échelle, awaiting_pickup uniquement */
  --status-delayed-ink:           oklch(38% 0.078 70);     /* #5C3A07  9.23:1 */
  --status-delayed-ring:          oklch(93.14% 0.006 85);  /* #EAE8E4 */

  /* --- Le pigment du danger ---------------------------------------------- */
  /* Le cramoisi d'exception est AUSSI le danger du chrome. Il n'existe pas de
   * vert de succès, pas de bleu d'information, pas d'ambre d'avertissement :
   * un toast de succès est en encre avec un glyphe de coche.
   * A7 : la plaque de danger a ses propres rangs, distincts de la marque —
   * la marque satisfait 3:1, mais elle ne porte pas de texte inverse. */
  --danger-plaque:        oklch(45% 0.173 25);  /* #9F111D  /inv 7.48:1 */
  --danger-plaque-hover:  oklch(51% 0.190 25);  /* #BA1D27  /inv 5.85:1 */
  --danger-plaque-active: oklch(41% 0.158 25);  /* #8C0D18  /inv 8.81:1 */
}


/* ============================================================================
 * 2. COULEUR — « Ardoise », thème sombre
 * ============================================================================
 * Re-dérivé valeur par valeur, jamais inversé. Le marchand travaille de longues
 * sessions, souvent le soir, avec l'admin Shopify (clair) dans l'onglet voisin ;
 * le client final ouvre la page depuis un e-mail, dehors comme dedans. Les deux
 * thèmes sont de premier rang. */

[data-theme="dark"] {
  color-scheme: dark;

  /* --- Fonds : l'ardoise est froide et minérale, jamais noire pure ------- */
  --surface:      oklch(22.5% 0.013 255);       /* #181C22  un objet flottant se distingue D'ABORD par son fond plus clair */
  --ground:       oklch(17.5% 0.012 255);       /* #0D1116 */
  --ground-sunk:  oklch(14%   0.011 255);       /* #06090E */
  --ground-deep:  oklch(10.5% 0.010 255);       /* #030406  A1 */

  --ink:          oklch(95%   0.004 255);       /* #EDEFF1  16.43:1 */
  --ink-2:        oklch(74%   0.006 255);       /* #A8ABAF   8.21:1 */
  --ink-3:        oklch(63%   0.008 255);       /* #86898E   5.40:1  A2 : 61 % donnait 4.49:1 sur surface flottante, sous le seuil */
  --ink-4:        oklch(53%   0.008 255);       /* #696C70   3.59:1  A3 : 50 % donnait 2.84:1 sur surface flottante */
  --ink-disabled: oklch(40%   0.008 255);       /* #45484C   2.06:1  exempté */
  --ink-inverse:  oklch(15%   0.012 255);       /* #080B10  /inv 17.09:1  texte sur plaque CLAIRE */

  --rule-hair:    oklch(95% 0.004 255 / 0.12);
  --rule:         oklch(95% 0.004 255 / 0.24);
  --rule-strong:  oklch(95% 0.004 255 / 0.48);  /* rendu #797C7F, 4.51:1 — c'est lui qui sépare la modale de son voile */

  /* A8 : en sombre, ground → ground-sunk vaut 1.05:1, invisible sur une ligne
   * de table. Les états de ligne MONTENT et deviennent des films d'encre.
   * Les alphas sont plafonnés par --ink-3, qui passe sous 4.5:1 au-delà de 6 %. */
  --state-hover:      color-mix(in oklab, var(--ink) 4%, var(--ground));  /* #161A1F  1.08:1 */
  --state-selected:   color-mix(in oklab, var(--ink) 6%, var(--ground));  /* #1A1E23  1.13:1 */
  --state-focus-halo: oklch(95% 0.004 255 / 0.24);
  --selection-bg:     oklch(95% 0.004 255 / 0.20);  /* rendu #3A3D42, --ink dessus 9.46:1 */

  /* A6 : --shade n'est JAMAIS --ink — en sombre --ink est blanc. */
  --shade:  oklch(6% 0.006 255);                /* #000101 */
  --scrim:  oklch(6% 0.006 255 / 0.72);         /* rendu #040507, surface par-dessus 1.19:1 : limite du thème, pas réglage raté */

  /* La plaque s'inverse : aplat CLAIR, texte sombre. */
  --plaque:        oklch(95% 0.004 255);        /* #EDEFF1  /inv 17.09:1 */
  --plaque-hover:  oklch(89% 0.005 255);        /* #D8DBDE  /inv 14.18:1 */
  --plaque-active: oklch(99% 0.003 255);        /* #FAFCFE  /inv 19.16:1  l'appui monte, parce que la plaque est claire */

  /* La clarté MONTE avec l'avancement du colis : le sens de l'échelle s'inverse. */
  --status-preparing:             oklch(58% 0.015 262);    /* #757B83  4.43:1 */
  --status-preparing-ink:         oklch(70% 0.014 262);    /* #9A9FA7  7.12:1 */
  --status-preparing-ring:        oklch(25%   0.011 254);  /* #1E2227  lavis à 16 %, opaque */

  --status-shipped:               oklch(61% 0.130 258);    /* #4F83D0  4.95:1 */
  --status-shipped-ink:           oklch(73% 0.110 258);    /* #7CA9EC  7.88:1 */
  --status-shipped-ring:          oklch(25.45% 0.036 259); /* #182334 */

  --status-in-transit:            oklch(64% 0.100 200);    /* #279EA4  5.87:1 */
  --status-in-transit-ink:        oklch(76% 0.090 200);    /* #64C3C8  9.18:1 */
  --status-in-transit-ring:       oklch(26.05% 0.030 214); /* #11282D */

  --status-out-for-delivery:      oklch(66% 0.120 155);    /* #4CA871  6.45:1 */
  --status-out-for-delivery-ink:  oklch(78% 0.110 155);    /* #7BCC98  9.86:1 */
  --status-out-for-delivery-ring: oklch(26.37% 0.025 179); /* #172925 */

  --status-delivered:             oklch(69% 0.130 148);    /* #5DB16C  7.19:1 */
  --status-delivered-ink:         oklch(80% 0.120 148);    /* #86D391 10.59:1 */
  --status-delivered-ring:        oklch(27.15% 0.026 167); /* #1A2B24 */

  --status-exception:             oklch(60% 0.170 25);     /* #D24C49  4.39:1 */
  --status-exception-ink:         oklch(72% 0.150 25);     /* #F47B74  7.15:1 */
  --status-exception-ring:        oklch(24.44% 0.031 6);   /* #2D1A1E */

  --status-delayed:               oklch(66% 0.120 70);     /* #C18434  5.97:1 */
  --status-delayed-ink:           oklch(78% 0.110 70);     /* #E5AB66  9.33:1 */
  --status-delayed-ring:          oklch(26.14% 0.018 71);  /* #2A231B */

  /* A7 : la plaque de danger sombre est PLUS CLAIRE que la marque, parce
   * qu'elle porte du texte. La marque à 60 % ne donnait que 4.57:1 en plaque,
   * ce qui interdisait tout état de survol plus sombre. */
  --danger-plaque:        oklch(68% 0.190 25);  /* #F75D59  /inv 6.25:1 */
  --danger-plaque-hover:  oklch(62% 0.190 25);  /* #E24947  /inv 4.95:1 */
  --danger-plaque-active: oklch(74% 0.150 25);  /* #FB817A  /inv 8.00:1 */
}


/* ============================================================================
 * 3. TYPOGRAPHIE
 * ============================================================================
 * LOI DES DEUX TOKENS : exactement deux noms portent l'identité typographique.
 * Aucun nom de police n'apparaît ailleurs — ni dans une règle de composant, ni
 * dans un style en ligne. Les seuls fichiers autorisés à écrire un nom de fonte
 * sont fonts.css (les @font-face) et ce bloc. Une règle qui écrit
 * `font-family: Archivo` viole la charte MÊME SI le rendu est identique.
 * Remplacer ces deux valeurs remplace l'identité du produit entier. */

:root {
  --font-display: "Archivo Expanded", "Repli Display", Arial, sans-serif;   /* chasse étendue : le display porte le contraste */
  --font-text:    "Archivo", "Repli Texte", Arial, system-ui, sans-serif;   /* chasse normale : on n'étend jamais du texte courant */
  /* Instrument de mesure, HORS identité et hors contrat de substitution :
   * réservé aux chaînes qu'on copie ou compare caractère par caractère
   * (n° de suivi, ID, clé API, secret, code de récupération). La monospace
   * comme costume « technique » est interdite : pas de libellé, pas de titre. */
  --font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* --- L'échelle : dix crans, FIXES ------------------------------------- */
  /* Jamais fluide, sauf le héros de la page tarifs. Un titre de route qui
   * rétrécit dans une fenêtre partagée est un défaut, pas une adaptation.
   * Le ratio n'est pas constant : serré en bas (l'UI a besoin de crans
   * voisins), ouvert en haut (le display a besoin d'écart). */
  --fs-11:   0.6875rem;                         /* cartouche, capitales suivies */
  --fs-12:   0.75rem;                           /* horodatage, méta de table, légende de jauge */
  --fs-13:   0.8125rem;                         /* DÉFAUT UI : contrôles, cellules, navigation, libellés */
  --fs-15:   0.9375rem;                         /* valeur de formulaire, corps dense, corps de tiroir */
  --fs-17:   1.0625rem;                         /* corps de lecture, chapô, texte d'état vide */
  --fs-21:   1.3125rem;                         /* titre de registre */
  --fs-26:   1.625rem;                          /* titre de modale */
  --fs-34:   2.125rem;                          /* titre de route, n° de commande, chiffre dominant */
  --fs-46:   2.875rem;                          /* display de section (tarifs, landing) */
  --fs-hero: clamp(2.875rem, 5.2vw, 6rem);      /* SEUL fluide du système : héros de la page tarifs */

  /* --- Interligne de flux ------------------------------------------------ */
  --lh-11: 1.2;   --lh-12: 1.35;  --lh-13: 1.45;  --lh-15: 1.5;
  --lh-17: 1.6;   --lh-21: 1.3;   --lh-26: 1.2;   --lh-34: 1.05;
  --lh-46: 1.08;  --lh-hero: 1;

  /* --- Boîte réglée : l'interligne quantifié à 4px ----------------------- */
  /* Un système bâti sur des filets exige que chaque ligne tombe sur la grille.
   * Un interligne en ratio produit des demi-pixels qui font vibrer les filets ;
   * une boîte en px les cale. Employée partout où le texte côtoie un filet. */
  --box-11: 16px; --box-12: 16px; --box-13: 20px; --box-15: 24px;
  --box-17: 28px; --box-21: 28px; --box-26: 32px; --box-34: 36px;

  /* --- Interlettrage : plancher −0.03em, rien ne descend plus bas -------- */
  --tr-cartouche: 0.08em;                       /* capitales suivies : le cartouche est partout */
  --tr-symbole:   0.10em;                       /* le mot-symbole HIDEMYDROP, et lui seul */
  --tr-micro:     0.01em;                       /* 12px : ouvre les glyphes sans les disperser */
  --tr-texte:     0;
  --tr-21:       -0.005em;
  --tr-26:       -0.01em;
  --tr-34:       -0.02em;
  --tr-hero:     -0.03em;

  /* --- Graisses : trois pour le texte, deux pour le display, plus le héros */
  --w-regular: 400;  --w-medium: 500;  --w-semibold: 600;  --w-hero: 700;

  /* --- Mesure ------------------------------------------------------------ */
  /* Le ch borne le caractère, le px borne la ligne : les deux ensemble, parce
   * qu'un ch d'Archivo étendu n'est pas un ch d'Archivo normal. */
  --measure-prose:   min(68ch, 640px);
  --measure-lede:    min(58ch, 560px);
  --measure-note:    min(52ch, 440px);
  --measure-display: min(20ch, 900px);
  --measure-form:    640px;
}

/* Compensation du thème sombre : sur fond noir, le texte clair grossit
 * optiquement (irradiation). L'interligne s'ouvre et le texte reprend un
 * micro-interlettrage positif pour ne pas se refermer. */
[data-theme="dark"] {
  --lh-13: 1.5;  --lh-15: 1.55;  --lh-17: 1.65;
  --tr-texte: 0.006em;
}
[data-theme="dark"] body {
  -webkit-font-smoothing: antialiased;          /* le seul endroit où le lissage est demandé : il compense l'irradiation */
  -moz-osx-font-smoothing: grayscale;
}

/* Écritures que la fonte ne couvre pas. Le repli n'est pas un accident : il est
 * déclaré, et il emporte son propre interligne, parce qu'un idéogramme ne tient
 * pas dans la boîte d'une grotesque latine. */
:root:lang(ja) {
  --font-text:    "Archivo", "Repli Texte", "Hiragino Sans", "Yu Gothic UI",
                  "Noto Sans JP", sans-serif;
  --font-display: "Archivo Expanded", "Repli Display", "Hiragino Sans",
                  "Yu Gothic UI", "Noto Sans JP", sans-serif;
}
:root:lang(zh) {
  --font-text:    "Archivo", "Repli Texte", "PingFang SC", "Microsoft YaHei",
                  "Noto Sans SC", sans-serif;
  --font-display: "Archivo Expanded", "Repli Display", "PingFang SC",
                  "Microsoft YaHei", "Noto Sans SC", sans-serif;
}
:root:lang(ja), :root:lang(zh) {
  --lh-13: 1.7;  --lh-15: 1.75; --lh-17: 1.85;  /* le CJK a besoin d'air vertical, pas d'interlettrage */
  --lh-21: 1.5;  --lh-26: 1.4;  --lh-34: 1.25;
  --box-13: 24px; --box-15: 28px; --box-17: 32px;
  --tr-micro: 0; --tr-21: 0; --tr-26: 0; --tr-34: 0; --tr-hero: 0;  /* serrer du CJK le rend illisible */
  --measure-prose: min(40em, 640px);            /* le ch n'a pas de sens en CJK : l'em le remplace */
}

/* L'ARABE. Troisième écriture non couverte, et la seule dont le RÉGLAGE compte
 * autant que la fonte — l'arabe s'attache.
 *
 * ARCHIVO RESTE EN TÊTE DES DEUX PILES, exactement comme en ja et en zh, et
 * c'est la ligne la plus importante du bloc. Une page arabe n'est pas écrite
 * qu'en arabe : le numéro de suivi (LX000000000FR), le numéro de commande
 * (#1042), l'heure, la date, le nom du marchand et le mot-symbole HIDEMYDROP y
 * sont LATINS et le restent (docs/_geo/POLICE-ARABE.md §7 : les chiffres
 * arabo-indiens sont exclus du produit, un numéro de suivi doit se coller dans
 * le formulaire du transporteur). Sans Archivo en tête, ces chaînes tombaient
 * dans la fonte arabe du système : MESURÉ au 2026-08-06 sur /track en `ar`,
 * `#1042` et `LX000000000FR` rendaient en SF Arabic — donc sans la chasse
 * étendue du titrage, et surtout SANS `tnum`, la fonctionnalité de chasse
 * tabulaire qui ne vit que dans archivo-latin.woff2 et dont le produit compte
 * 112 déclarations. Le sélecteur de fonte route par point de code : mettre
 * Archivo devant ne donne PAS un caractère arabe à Archivo (il n'en a aucun),
 * il rend au latin résiduel la fonte du produit. C'est aussi ce qui garde le
 * `ch` des --measure-* calibré sur Archivo, comme dans les douze autres locales.
 *
 * Aucune graisse « Expanded » n'existe EN ARABE, et c'est voulu : pour les
 * points de code arabes, --font-display et --font-text retombent sur la MÊME
 * famille. Le contraste de titrage de la charte est porté en latin par la
 * chasse étendue ; en arabe il n'y a pas de chasse étendue à emprunter, et en
 * fabriquer une en étirant les glyphes casserait les liaisons — l'arabe ne
 * s'élargit pas, il s'étire par la kashida, qui n'est pas une propriété CSS
 * (POLICE-ARABE §9.1). Le contraste repasse sur le corps et la graisse, qui
 * sont les deux axes que l'écriture accepte.
 *
 * LA FONTE RETENUE EST NOTO SANS ARABIC (POLICE-ARABE §4 : OFL sans RFN,
 * variable 100-900 en un seul fichier, donc toutes les graisses réelles sous
 * `font-synthesis: none` — Almarai est éliminée mécaniquement pour n'avoir ni
 * 500 ni 600). Elle est nommée ICI, en tête des candidates arabes, avant les
 * fontes système : là où elle est installée — tout le parc Android et ChromeOS,
 * où elle est la fonte de repli du système — le dessin retenu s'applique déjà,
 * sans un octet téléchargé. Ailleurs on retombe sur SF Arabic (Apple), Segoe UI
 * (Windows), Noto Naskh (le reste).
 *
 * L'auto-hébergement, qui rendrait ce dessin identique PARTOUT, est écrit et
 * prêt dans apps/shared/css/fonts.css, en commentaire, avec sa procédure : il
 * n'attend que le fichier woff2, qu'aucun agent ne télécharge sans ton accord.
 * Le jour où il est activé, ce bloc-ci ne change pas d'une ligne — sauf les
 * trois --box-* ci-dessous, et fonts.css dit pourquoi. */
:root:lang(ar) {
  --font-text:    "Archivo", "Repli Texte", "Noto Sans Arabic", "SF Arabic",
                  "Geeza Pro", "Noto Naskh Arabic", "Segoe UI", Tahoma,
                  sans-serif;
  --font-display: "Archivo Expanded", "Repli Display", "Noto Sans Arabic",
                  "SF Arabic", "Geeza Pro", "Noto Naskh Arabic", "Segoe UI",
                  Tahoma, sans-serif;

  /* L'INTERLETTRAGE PASSE À ZÉRO PARTOUT, ET CE N'EST PAS UN GOÛT.
   * L'arabe est cursif : ses lettres sont SOUDÉES dans le mot. Un
   * letter-spacing positif ne l'aère pas, il DÉCOUD les liaisons et rend le
   * mot illisible — c'est la faute de composition arabe la plus répandue sur
   * le web, et --tr-cartouche (0,08em) l'aurait produite sur chaque cartouche
   * du produit, c'est-à-dire partout. --tr-symbole est laissé de côté : il ne
   * sert qu'au mot-symbole HIDEMYDROP, qui reste écrit en latin dans toutes
   * les langues. */
  --tr-cartouche: 0;  --tr-micro: 0;  --tr-texte: 0;
  --tr-21: 0;  --tr-26: 0;  --tr-34: 0;  --tr-hero: 0;

  /* L'arabe monte et descend plus que le latin — hampes, jambages et signes
   * diacritiques sortent tous de la hauteur d'x. Aux corps de titrage, la
   * ligne serrée du latin (1,05 à 46px) fait mordre les jambages sur la ligne
   * suivante. On rouvre, sans aller jusqu'à l'air du CJK. */
  --lh-13: 1.6;  --lh-15: 1.65; --lh-17: 1.75;
  --lh-21: 1.45; --lh-26: 1.35; --lh-34: 1.25; --lh-46: 1.2; --lh-hero: 1.15;
  /* Calibrés sur l'encre NUE d'une naskh système : 1,242 em, soit 16,1 px à
   * 13 px, soit 2,9 px de blanc par côté. Le jour où le woff2 arabe est activé
   * dans fonts.css, size-adjust: 112 % porte cette encre à 18,1 px et ces trois
   * valeurs DOIVENT passer à 24/28/32px — sinon le jambage du م touche le filet
   * du dessous. fonts.css le redit à l'endroit où on décommente. */
  --box-13: 22px; --box-15: 26px; --box-17: 30px;

  /* L'ESPACE-MOT, RENDU À L'ARABE. Conséquence directe et mesurée d'Archivo en
   * tête de pile : le sélecteur de fonte route PAR POINT DE CODE, et U+0020 est
   * dans la plage latine — donc c'est Archivo qui dessine l'espace entre deux
   * mots arabes qu'il ne dessine pas. Mesuré à 13 px (Chrome, cette feuille) :
   * 3,52 px avec Archivo devant, 4,86 px avec la naskh seule. L'espace arabe
   * perd 28 %.
   * Ce n'est pas une nuance : en latin, la majuscule, le jambage et la
   * ponctuation découpent déjà les mots ; en arabe le blanc EST le seul
   * séparateur, et une naskh lui donne délibérément 0,37 em quand une grotesque
   * latine se contente de 0,27. Serré, « خارج للتسليم » se lit comme un seul
   * mot — vérifié à l'écran, pas déduit.
   * 0,1 em rend exactement le blanc mesuré — 4,81 px contre 4,86 px, à un
   * vingtième de pixel — et le rend à TOUTES les tailles puisque c'est un em.
   * (Reproduire : deux spans à 13 px, l'un « خارج للتسليم », l'autre le même
   * sans l'espace, chacun sur SA ligne et en inline-block — collés sur une même
   * ligne, Chrome façonne l'arabe PAR-DESSUS la frontière d'élément et la
   * mesure ment de 0,4 px.)
   * CE QUE ÇA COÛTE, et c'est assumé : la propriété est héritée, donc
   * les rares phrases LATINES d'une page arabe — un scan de transporteur en
   * anglais, « Paris CDG - Roissy, FR » — s'écartent d'autant. Desserrer une
   * citation en langue étrangère est un moindre mal que recoller les mots de la
   * langue du document. Aucune autre locale n'est touchée : la règle est dans
   * :lang(ar).
   * Cette ligne reste vraie quand le woff2 arabe s'active : U+0020 n'est PAS
   * dans sa plage (et ne doit pas y entrer — il ferait télécharger la fonte
   * arabe sur toutes les pages latines du produit). */
  word-spacing: 0.1em;

  /* LA MESURE, RECOMPTÉE PLUTÔT QU'ESTIMÉE. POLICE-ARABE §6 propose 60ch « à
   * recompter sur un paragraphe réel ». Recompté ici, sur le paragraphe le plus
   * long du produit (tracking.awaiting_updates) et sa traduction française, à
   * 15 px :
   *     largeur     arabe            français
   *     68ch 584px  99 car / 17,5 mots   92 car / 15,3 mots
   *     60ch 515px  88 car / 15,5 mots   82 car / 13,6 mots
   *     52ch 447px  75 car / 13,2 mots   70 car / 11,6 mots
   * À largeur égale, une ligne arabe emporte 8 % de caractères et 14 % de MOTS
   * de plus qu'une ligne française — la prémisse « les mots arabes sont bien
   * plus courts » est vraie mais modeste, et le chiffre en caractères (99) est
   * le mauvais juge : c'est le mot qu'on lit, pas le signe.
   * 60ch cale l'arabe sur le NOMBRE DE MOTS du français (15,5 contre 15,3),
   * c'est-à-dire sur la même charge de lecture par ligne, et pas sur une bande
   * de « 45 à 75 caractères » écrite pour l'alphabet latin. Le `ch` garde ici
   * tout son sens, contrairement au CJK : il vaut la chasse du `0`, qui reste
   * routé vers Archivo. */
  --measure-prose: min(60ch, 640px);

  /* LE CARTOUCHE, ET LE SEUL CRAN QUE L'ARABE RÉEMPLOIE. Le rôle est défini
   * 11 px / 600 / CAPITALES / +0,08 em. En arabe la casse n'existe pas (le
   * text-transform est un no-op) et l'interlettrage est interdit ci-dessus :
   * le cartouche perd ses DEUX signes distinctifs d'un coup, et il ne reste que
   * 11 px / 600 face au rôle micro à 12 px / 500. Un pixel et une graisse — dans
   * une écriture dont le corps est déjà 22 % plus petit à taille égale, donc un
   * 11 px arabe qui pèse optiquement 8,5 px de latin. Or le cartouche est
   * PARTOUT : gouttière de registre, crans de réglette, <th>, bande de jour.
   * On lui rend la seule chose que l'arabe accepte encore, un corps. Ce n'est
   * pas un onzième cran ajouté au système : c'est un cran existant réemployé,
   * pour une écriture, dans un bloc :lang() qui existe déjà pour ça. La loi des
   * dix crans et le ratio 11-34 restent vrais du système (POLICE-ARABE §9.2). */
  --fs-11: var(--fs-12);
}

/* ----------------------------------------------------------------------------
 * LE SENS DE LECTURE — les deux valeurs que les propriétés logiques ne
 * couvrent pas, et rien d'autre.
 * ----------------------------------------------------------------------------
 * QUI POSE `dir`. Personne ici. Cet attribut est écrit à deux endroits et deux
 * seulement, qui lisent tous les deux la MÊME autorité,
 * apps/api/src/lib/locale.js#RTL_LOCALES via dirFor() :
 *   - le SERVEUR, sur le document qu'il envoie (lib/head-meta.js), pour que le
 *     sens soit vrai avant le premier octet de JavaScript — un robot d'aperçu
 *     ne court aucun script, et un lecteur humain verrait sinon la page se
 *     retourner sous ses yeux ;
 *   - le SCRIPT d'i18n de chaque surface, à chaque changement de langue sans
 *     rechargement (adoptLocale / loadLocaleBundle).
 * Ajouter l'hébreu ou le persan se fait dans RTL_LOCALES, et NULLE PART
 * AILLEURS : cette feuille n'énumère aucune langue, elle ne lit que `dir`.
 *
 * POURQUOI SI PEU DE CHOSES ICI. Les trois surfaces sont écrites en propriétés
 * LOGIQUES d'un bout à l'autre — `inset-inline-start`, `border-inline-start`,
 * `padding-inline-end`, `margin-inline`, `text-align: start/end`,
 * `justify-content: flex-start` (mesuré : 138 occurrences dans le back-office,
 * 37 sur la page de suivi, 31 sur la page tarifs, contre 3 et 4 `left:`/`right:`
 * physiques résiduels, tous légitimes — des centrages à 50 %). Toutes basculent
 * SEULES quand <html> passe en `dir="rtl"` : la gouttière de registre change de
 * bord, les filets la suivent, les cartouches se rangent sur le bon aplomb.
 * Un `dir` posé sur la racine suffit à tout cela, et un bloc `[dir="rtl"]` qui
 * recopierait ces règles serait une deuxième chance de les faire diverger.
 *
 * IL RESTE EXACTEMENT DEUX CHOSES QUI NE SAVENT PAS SE RETOURNER, et ce sont
 * les deux qui vivent dans `transform`, un système de coordonnées PHYSIQUE que
 * `dir` ne touche pas :
 *
 *   --dir-flip          le SIGNE d'un translateX. `translateX(-50%)` déplace
 *                       vers la gauche dans les deux sens d'écriture. Les
 *                       cartouches de réglette sont posés par translation
 *                       (colonne de largeur nulle), donc en arabe le cartouche
 *                       du dernier cran sortirait du registre par la gauche au
 *                       lieu d'y rentrer.
 *   --dir-origin-start  le bord DE DÉPART d'un `transform-origin`. Les objets
 *                       qui se remplissent en `scaleX` — portion parcourue de
 *                       la réglette, décompte de reprise, filet du widget — ont
 *                       besoin de leur bord d'origine, et `transform-origin`
 *                       n'accepte aucun mot-clé logique : `inline-start`
 *                       n'existe pas. Sans cette valeur, la réglette progresse
 *                       à REBOURS de la lecture.
 *
 * Ces deux tokens étaient définis dans apps/tracking-page/index.html sous les
 * noms `--tp-flip` / `--tp-origin-start`, où seule cette surface pouvait les
 * lire — et le widget résolvait le même problème une seconde fois, à la main,
 * avec sa propre règle `[dir="rtl"] .reglette-scale::after`. Hissés ici, les
 * quatre surfaces lisent la même paire.
 *
 * `:root[dir="rtl"]` vaut (0,2,0) contre (0,1,0) pour `:root` : la surcharge
 * gagne par SPÉCIFICITÉ et non par ordre de déclaration, donc elle survit à un
 * déplacement de bloc et à une concaténation de feuilles.
 * -------------------------------------------------------------------------- */
:root {
  --dir-flip: 1;
  --dir-origin-start: left;
  --dir-origin-end: right;
}
:root[dir="rtl"] {
  --dir-flip: -1;
  --dir-origin-start: right;
  --dir-origin-end: left;
}

/* Les glyphes qui ont un SENS DE MARCHE se retournent ; les autres non, et la
 * distinction se DÉCLARE au dessin plutôt qu'elle ne se devine à la règle.
 * `data-mirror="rtl"` se pose sur une flèche, un camion, une pile de feuillets
 * — jamais sur une coche (une coche est une coche dans toutes les écritures et
 * retournée elle ne se reconnaît plus), jamais sur une horloge, jamais sur un
 * avion vu de dessus dont l'axe est vertical. La règle vivait sur la page de
 * suivi ; elle est ici pour que le back-office et la page tarifs l'aient aussi
 * dès qu'ils marquent un glyphe.
 *
 * `transform-origin` est ÉCRIT, alors qu'il vaut déjà cela par défaut, et
 * `transform-box` ne l'est PAS : sur un élément SVG la boîte de référence est
 * la view-box, donc `center` tombe sur le centre du carré rendu et le miroir ne
 * déplace rien. `transform-box: fill-box` prendrait la boîte de l'ENCRE — celle
 * d'un camion n'est pas centrée dans sa grille — et décalerait le glyphe en le
 * retournant. La valeur par défaut est la bonne : elle est nommée pour qu'on ne
 * la « corrige » pas. */
:root[dir="rtl"] svg[data-mirror="rtl"] {
  transform: scaleX(-1);
  transform-origin: center;
}


/* ============================================================================
 * 4. ESPACEMENT
 * ============================================================================
 * Base 4. Trois régimes, et le régime dit la valeur : à l'intérieur d'un
 * composant on va de 4 à 16, entre composants de 20 à 32, entre registres de
 * 48 à 128. Une valeur prise dans le mauvais régime se voit. */

:root {
  --sp-0:   0;
  --sp-2:   2px;                                /* RECALAGE OPTIQUE SEULEMENT — jamais en padding de composant ni en gap de layout */
  --sp-4:   4px;
  --sp-8:   8px;
  --sp-12:  12px;
  --sp-16:  16px;
  --sp-24:  24px;
  --sp-32:  32px;
  --sp-48:  48px;
  --sp-64:  64px;
  --sp-96:  96px;
  --sp-128: 128px;

  /* --- Régime A : intérieur d'un composant (4 → 16) ---------------------- */
  --gap-glyph-label:  var(--sp-8);
  --gap-label-field:  var(--sp-8);
  --gap-field-hint:   var(--sp-4);
  --gap-inline:       var(--sp-8);
  --gap-inline-tight: var(--sp-4);
  --gap-stack:        var(--sp-12);

  --pad-control-x:    calc(var(--sp-16) + var(--sp-2));  /* 18 : composé, pas inventé — 16 serre le libellé d'un bouton de 40px */
  --pad-control-x-sm: var(--sp-12);
  --pad-control-x-lg: var(--sp-24);
  --pad-field-x:      var(--sp-12);
  --pad-stamp-x:      var(--sp-8);
  --pad-cell-x:       var(--sp-16);
  --pad-cell-x-edge:  var(--sp-0);              /* première et dernière cellule affleurent le registre : la table n'est pas une carte */
  --pad-menu-item-x:  var(--sp-12);
  --pad-float:        var(--sp-8);
  --pad-tip:          var(--sp-12);
  --pad-modal:        var(--sp-24);
  --pad-modal-y:      var(--sp-16);
  --pad-drawer:       var(--sp-24);

  /* --- Régime B : entre composants d'un registre (20 → 32) --------------- */
  --gap-title-content: calc(var(--sp-16) + var(--sp-4));  /* 20 : la moitié optique de 48, le rapport de la loi de l'air au-dessus */
  --gap-field-field:   calc(var(--sp-16) + var(--sp-4));  /* 20 */
  --gap-block:         var(--sp-24);
  --gap-block-lg:      var(--sp-32);
  --gap-row:           var(--sp-0);             /* deux lignes de table ne sont pas espacées : elles sont séparées par un filet */

  /* --- Régime C : registres et sections (48 → 128) ----------------------- */
  --pad-registre-top:    var(--sp-24);
  --pad-registre-bottom: var(--sp-48);
  --gap-registre:        var(--sp-0);           /* le registre porte son propre rythme dans son padding */
  --gap-heading-before:  var(--sp-48);          /* LOI DE L'AIR AU-DESSUS : toujours plus d'espace au-dessus d'un titre qu'en dessous */
  --gap-heading-after:   var(--gap-title-content);        /* le rapport de référence est 48/20 */
  --gap-section:         var(--sp-64);
  --gap-section-lg:      var(--sp-96);
  --gap-section-hero:    var(--sp-128);

  --pad-page: var(--sp-24);                     /* gouttière de page : structurelle, par palier — voir les media queries du §6 */
}


/* ============================================================================
 * 5. GÉOMÉTRIE
 * ============================================================================
 * Quatre rayons plus un stade. L'ancienne rampe (3/5/9/13/21/33) et ses seize
 * alias sémantiques disparaissent. Un rayon plus grand que 8px n'existe pas. */

:root {
  --radius-none:    0;                          /* TOUTE la structure : registre, table, ligne, bande, séparateur, jauge */
  --radius-mark:    2px;                        /* tampon, tag, case à cocher, graduation */
  --radius-control: 4px;                        /* bouton, champ, select, item de menu, vignette */
  --radius-float:   8px;                        /* menu, popover, modale, tiroir, toast */
  --radius-stadium: 999px;                      /* interrupteur et avatar, UNIQUEMENT */

  /* --- Traits ------------------------------------------------------------ */
  --rule-w:         1px;                        /* le filet par défaut */
  --rule-w-mark:    1.5px;                      /* bord de tampon : il doit tenir contre son propre lavis */
  --rule-w-accent:  2px;                        /* soulignement de navigation active, graduation, barre de plafond */
  --rule-w-brand:   3px;                        /* filet de titre de la page de suivi : le SEUL aplat de couleur locataire */
  --hatch-pitch:    4px;                        /* pas de la hachure à 45° — un dispositif d'ingénierie, pas une décoration */
  --stroke-data:    2px;                        /* barre d'histogramme, arc de spinner */
  --icon-16:        16px;
  --icon-20:        20px;
  /* Le trait vit dans le token, jamais dans le SVG : une grille de 20 rendue à
   * 16 doit sur-déclarer son trait pour retomber sur 1.5px à l'écran. Bouts et
   * jointures CARRÉS : c'est une décision d'identité, elle accorde les glyphes
   * aux filets et distingue ce jeu du jeu arrondi omniprésent. */
  --icon-stroke-16: 1.875;                      /* 1.5px rendu sur une grille de 20 réduite à 16 */
  --icon-stroke-20: 1.5;

  /* --- Hauteurs déclarées ------------------------------------------------ */
  /* Une hauteur se DÉCLARE, elle ne se déduit pas d'un padding vertical :
   * deux contrôles au même padding et au corps différent ne s'alignent pas. */
  --h-mark:       24px;   --h-mark-dense: 20px;
  --h-control-sm: 36px;   --h-control:    40px;   --h-control-lg: 46px;
  --h-row:        46px;   --h-row-dense:  40px;
  --h-band-head:  56px;   --h-band-route: 72px;
  --h-track:      28px;                         /* piste de la jauge de facturation */
  --h-switch:     20px;   --w-switch:     36px;
  --h-dropzone:   128px;
  --h-hist:       24px;                         /* histogramme de 14 jours du bandeau du jour */
  --top-sticky:   calc(var(--h-band-head) + var(--h-band-route));  /* ce sous quoi un en-tête collant se colle */
  --tap-min:      max(44px, 2.75rem);           /* cible tactile : obtenue par du padding, pas par de la hauteur visible */

  /* --- Largeurs nommées --------------------------------------------------- */
  --w-content:      1360px;
  --w-prose:        68ch;
  --w-form:         640px;
  --w-form-narrow:  520px;
  --w-summary:      380px;                      /* colonne résumé de la page de suivi, à partir de 1100px */
  --w-drawer:       520px;
  --w-modal:        480px;
  --w-modal-lg:     720px;                      /* consentement de plan, modèles de règles */
  --w-auth:         420px;
  --w-tip:          280px;
  --w-logo-max:     180px;
  --w-gutter-registre: 0;                       /* la gouttière du cartouche : elle s'ouvre au §6, pas ici */
}


/* ============================================================================
 * 6. MATIÈRE, Z-INDEX, ET LA RESPONSIVITÉ STRUCTURELLE
 * ============================================================================
 * Le système est PLAT par principe. La profondeur vient des filets, d'un seul
 * palier de fond, et de rien d'autre. Deux ombres existent, pour les objets qui
 * flottent réellement. Tout le reste — registre, table, ligne, champ, bouton,
 * tampon, réglette, plan tarifaire, en-tête — n'a AUCUNE ombre. */

:root {
  --shadow-none: none;

  /* Chaque ombre porte un décalage vertical ET un flou. Un halo coloré sans
   * décalage est une décoration, pas une profondeur. */
  --shadow-float:   0 1px 2px       oklch(21% 0.012 255 / 0.06),
                    0 8px 16px -8px oklch(21% 0.012 255 / 0.14);   /* menu, popover, toast, barre de sauvegarde collante */
  --shadow-overlay: 0 2px 4px         oklch(21% 0.012 255 / 0.08),
                    0 24px 48px -16px oklch(21% 0.012 255 / 0.30); /* modale, tiroir */

  /* L'anneau de focus n'est PAS une ombre : c'est un outline (§8), qui survit à
   * forced-colors et qu'aucune box-shadow suivante n'efface. --focus-ring est
   * le halo interne des champs, qui l'accompagne sans le remplacer. */
  --focus-ring: 0 0 0 3px var(--state-focus-halo);

  /* --- Z-index : une échelle nommée, aucun littéral dans les feuilles ---- */
  --z-base:         0;
  --z-raised:       10;                         /* ligne active, cellule collante */
  --z-sticky:       30;                         /* en-tête de table collant */
  --z-band-route:   35;
  --z-appbar:       40;                         /* bande d'en-tête */
  --z-savebar:      50;
  --z-dropdown:     100;
  --z-scrim-drawer: 200;
  --z-drawer:       210;
  --z-scrim-modal:  300;
  --z-modal:        310;
  --z-popover:      400;
  --z-toast:        500;
  --z-tooltip:      600;                        /* toujours au-dessus : il décrit ce qui est en dessous */
  --z-skip-link:    700;                        /* rien ne passe devant le lien d'évitement */
  --z-debug:        900;
}

[data-theme="dark"] {
  /* Mêmes géométries, autre encre. L'encre est --shade (A6), jamais --ink, qui
   * est blanc en sombre. Les secondes couches montent à 0.30 et 0.55 : sur un
   * fond sombre il faut plus d'opacité pour la même lecture. */
  --shadow-float:   0 1px 2px       oklch(6% 0.006 255 / 0.14),
                    0 8px 16px -8px oklch(6% 0.006 255 / 0.30);
  --shadow-overlay: 0 2px 4px         oklch(6% 0.006 255 / 0.20),
                    0 24px 48px -16px oklch(6% 0.006 255 / 0.55);
}

/* La responsivité est STRUCTURELLE : une gouttière s'ouvre, une colonne
 * disparaît, une table se replie. Elle n'est jamais typographique — aucun corps
 * de texte ne change de taille avec la fenêtre. */
@media (min-width: 900px)  { :root { --pad-page: var(--sp-32); } }
@media (min-width: 1024px) { :root { --w-gutter-registre: 160px; } }   /* le cartouche passe à gauche du contenu */
@media (min-width: 1280px) { :root { --w-gutter-registre: 208px; } }
@media (min-width: 1600px) { :root { --pad-page: var(--sp-48); } }


/* ============================================================================
 * 7. MOUVEMENT
 * ============================================================================
 * Trois courbes, cinq durées, deux boucles. Aucun rebond, aucun ressort, aucun
 * dépassement : un objet réel décélère, il ne rebondit pas. Aucune règle de
 * composant n'écrit une cubic-bezier en dur. */

:root {
  /* Tout ce qui APPARAÎT ou se REMPLIT. Décélération exponentielle : 80 % de la
   * distance dans le premier tiers du temps, puis l'objet se pose. */
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  /* SORTIES uniquement. Une entrée sur cette courbe donne l'impression d'une
   * frame perdue au démarrage. */
  --ease-in:   cubic-bezier(0.7, 0, 0.84, 0);
  /* DÉPLACEMENT, aller comme retour : pouce d'interrupteur, chevron qui pivote,
   * filet qui balaye, respiration de squelette. Symétrique, sans dépassement. */
  --ease-move: cubic-bezier(0.33, 0, 0.15, 1);
  /* `linear` est le seul mot-clé toléré hors de ces trois tokens, réservé à la
   * rotation continue. Il s'écrit littéralement : ce n'est pas une courbe. */

  /* --- Durées : différenciées par la NATURE de ce qui bouge -------------- */
  --dur-state:     120ms;                       /* survol, focus, actif, sélection : au-delà de 150ms un survol se lit comme de la latence */
  --dur-control:   180ms;                       /* un contrôle qui prend une autre position ; le nom survit, la valeur passe de 200 à 180 */
  --dur-enter:     240ms;                       /* une surface qui n'était pas là : modale, tiroir, feuille, toast, menu */
  --dur-exit:      160ms;                       /* les mêmes en sortie : deux tiers de l'entrée, rapport constant dans tout le système */
  --dur-signature: 520ms;                       /* le moment authored, UN SEUL par surface */

  /* Les deux SEULES boucles du produit. Elles ne décrivent pas une transition
   * mais la période d'un instrument. Toute autre iteration-count: infinite est
   * un défaut, quel que soit son bon goût. */
  --dur-spin:     900ms;                        /* rotation du spinner et du glyphe de rafraîchissement */
  --dur-sk-pulse: 1400ms;                       /* respiration des blocs de squelette, en alternance — pas un balayage en dégradé */

  /* --- Cadence ------------------------------------------------------------ */
  /* Une seule cascade existe : les lignes de l'historique de la page de suivi.
   * 28ms et non 55 : la ligne parcourt 6px sur un seul axe, et à 55ms la
   * cascade dure plus longtemps que le geste qu'elle accompagne. */
  --stagger-step:  28ms;
  --enter-delay:   0ms;                         /* écrit par le JS sur chaque ligne, à l'intérieur de STAGGER_WINDOW */
  --delay-tooltip: 400ms;                       /* pas une cadence : empêche l'écran de clignoter quand le curseur traverse une rangée */
}


/* ============================================================================
 * 8. LES SURFACES DU NAVIGATEUR
 * ============================================================================
 * Ce que le navigateur dessine à notre place porte le design aussi. C'est le
 * signal le plus fiable qu'une page a été construite et non assemblée, et c'est
 * ce qu'on oublie. Tout est en :where() — spécificité zéro, donc une feuille de
 * page reprend la main sans !important. */

::selection { background: var(--selection-bg); color: var(--ink); }

:root {
  scrollbar-width: thin;                        /* l'ascenseur natif, aminci — jamais un ascenseur redessiné à la forme inventée */
  scrollbar-color: var(--rule-strong) transparent;
}

:where(input, textarea, select) {
  caret-color: var(--ink);                      /* le curseur de saisie est de l'encre, pas du bleu système */
  accent-color: var(--ink);                     /* cases et radios natives non habillées */
}

:where(a) {
  text-decoration-thickness: 1px;               /* un soulignement d'imprimeur : fin, décollé, qui esquive les jambages */
  text-underline-offset: 0.18em;
  text-decoration-skip-ink: auto;
}

:where(:focus-visible) {
  outline: 2px solid var(--ink);                /* un outline, pas une box-shadow : rien ne peut l'effacer silencieusement */
  outline-offset: 2px;
}


/* ============================================================================
 * 9. LES DOUZE RÔLES TYPOGRAPHIQUES
 * ============================================================================
 * La référence des NOUVELLES marques. Les composants existants lisent les mêmes
 * tokens dans leurs propres règles, sans changer un seul nom de classe ni d'id.
 * Une taille ne voyage jamais seule : un rôle emporte famille, corps, graisse,
 * interligne, interlettrage et régime de chiffres. */

.t-cartouche { font-family: var(--font-text); font-size: var(--fs-11);
  font-weight: var(--w-semibold); line-height: var(--box-11);
  letter-spacing: var(--tr-cartouche); text-transform: uppercase; color: var(--ink-3); }

.t-micro { font-family: var(--font-text); font-size: var(--fs-12);
  font-weight: var(--w-medium); line-height: var(--box-12);
  letter-spacing: var(--tr-micro); font-variant-numeric: tabular-nums lining-nums;
  color: var(--ink-3); }

.t-ui { font-family: var(--font-text); font-size: var(--fs-13);
  font-weight: var(--w-medium); line-height: var(--box-13);
  letter-spacing: var(--tr-texte); font-variant-numeric: tabular-nums lining-nums; }

.t-ui-2 { font-family: var(--font-text); font-size: var(--fs-13);
  font-weight: var(--w-regular); line-height: var(--box-13);
  letter-spacing: var(--tr-texte); color: var(--ink-2); }

.t-valeur { font-family: var(--font-text); font-size: var(--fs-15);
  font-weight: var(--w-regular); line-height: var(--box-15);
  letter-spacing: var(--tr-texte); font-variant-numeric: tabular-nums lining-nums; }

.t-corps { font-family: var(--font-text); font-size: var(--fs-17);
  font-weight: var(--w-regular); line-height: var(--lh-17);
  letter-spacing: var(--tr-texte); max-width: var(--measure-prose);
  text-wrap: pretty; }

.t-titre-registre { font-family: var(--font-display); font-size: var(--fs-21);
  font-weight: var(--w-semibold); line-height: var(--box-21);
  letter-spacing: var(--tr-21); text-wrap: balance; }

.t-titre-modale { font-family: var(--font-display); font-size: var(--fs-26);
  font-weight: var(--w-semibold); line-height: var(--box-26);
  letter-spacing: var(--tr-26); text-wrap: balance; }

/* titre-route et chiffre partagent le cran 34 : la GRAISSE les sépare, parce
 * qu'un titre de route et un n° de commande ne se disputent jamais l'écran. */
.t-titre-route { font-family: var(--font-display); font-size: var(--fs-34);
  font-weight: var(--w-medium); line-height: var(--box-34);
  letter-spacing: var(--tr-34); text-wrap: balance; }

.t-figure { font-family: var(--font-display); font-size: var(--fs-34);
  font-weight: var(--w-semibold); line-height: var(--box-34);
  letter-spacing: var(--tr-34); font-variant-numeric: tabular-nums lining-nums;
  hyphens: none; overflow-wrap: anywhere; }

.t-display { font-family: var(--font-display); font-size: var(--fs-46);
  font-weight: var(--w-medium); line-height: var(--lh-46);
  letter-spacing: var(--tr-34); text-wrap: balance; }

.t-hero { font-family: var(--font-display); font-size: var(--fs-hero);
  font-weight: var(--w-hero); line-height: var(--lh-hero);
  letter-spacing: var(--tr-hero); max-width: var(--measure-display);
  text-wrap: balance; }

.t-mono { font-family: var(--font-mono); font-size: var(--fs-13);
  font-weight: var(--w-regular); line-height: var(--box-13);
  letter-spacing: var(--tr-texte);
  font-variant-numeric: tabular-nums slashed-zero;   /* zéro barré : ces chaînes se recopient et se lisent à voix haute */
  hyphens: none; overflow-wrap: anywhere; }

/* L'unité suit son nombre d'un cran plus bas et en ink-3 : « 12,40 € » se lit
 * comme un montant, pas comme deux mots de même poids. */
.t-figure .unit, .t-display .unit {
  font-size: var(--fs-26); color: var(--ink-3);
  letter-spacing: 0; font-variant-numeric: normal;
}

/* L'interlettrage ne s'hérite jamais dans une colonne de chiffres ni dans un
 * contrôle : un cartouche à +0.08em qui déteint sur ses cellules casse
 * l'alignement que tabular-nums vient d'établir. */
th, td, input, select, textarea, button { letter-spacing: 0; }


/* ============================================================================
 * 10. COMPAT — TRANSITOIRE
 * ============================================================================
 * Ce bloc n'existe que pour une raison : les quatre surfaces consomment encore
 * 279 noms de l'ancien monde, et un nom absent ne casse pas bruyamment — il rend
 * une valeur vide et la page se décompose en silence. Chaque nom ci-dessous est
 * REMAPPÉ sur le nouveau système : le nom survit, la valeur change du tout au
 * tout. Les agents de construction retirent chaque ligne au fur et à mesure
 * qu'ils réécrivent la surface qui la consomme, et ce bloc disparaît entier.
 *
 * TROIS REMAPS À TRAITER DANS LA MÊME PASSE QUE LA RÉÉCRITURE, PAS APRÈS :
 *   --pad-control-y* → 0    tout élément qui tirait sa hauteur d'un padding
 *                           vertical s'aplatit d'un coup. C'est voulu (une
 *                           hauteur se déclare), mais il faut poser --h-control
 *                           sur chacun dans la même passe.
 *   --radius-card    → 0    dix-huit objets de l'admin perdent leur rayon d'un
 *                           coup. C'est la disparition de la carte, donc c'est
 *                           correct ; un objet qui avait besoin d'un rayon et
 *                           n'était PAS une carte se rebascule explicitement sur
 *                           --radius-control.
 *   --elev-1 / --shadow → none   la carte ne projette plus d'ombre. Ce qui
 *                           semble « perdre du relief » reçoit un filet, pas une
 *                           ombre de remplacement.
 *
 * Le contrat des primitives est préservé : dans l'ancien fichier, une primitive
 * (--color-*) ne bouge PAS avec le thème et un rôle (--text-*, --surface-*)
 * bouge. Les pages s'appuient dessus — l'admin écrit `--badge-ink:
 * var(--color-neutral-0)` en clair et `var(--color-neutral-900)` en sombre.
 * Inverser ce contrat mettrait du texte sombre sur un fond sombre. Les
 * primitives ci-dessous sont donc des LITTÉRAUX stables ; seuls les rôles sont
 * redéclarés dans le bloc sombre qui suit.
 * ========================================================================== */

:root {

  /* --- Primitives neutres : la rampe Oxide/Kraft devient de l'encre ------ */
  /* La couleur d'accent de marque n'existe plus. Le chrome est achromatique :
   * une teinte dans le chrome est un bug, quel que soit son bon goût. */
  --color-neutral-0:   oklch(99.2% 0.002 255);  /* ex-blanc pur → la surface flottante */
  --color-neutral-50:  oklch(96.5% 0.004 255);
  --color-neutral-100: oklch(93.8% 0.005 255);
  --color-neutral-200: oklch(90%   0.006 255);
  --color-neutral-300: oklch(80%   0.007 255);  /* palier clair : c'est lui que le thème sombre lit comme « encre » */
  --color-neutral-400: oklch(70%   0.006 255);
  --color-neutral-500: oklch(58%   0.008 255);
  --color-neutral-600: oklch(46%   0.009 255);
  --color-neutral-700: oklch(36%   0.010 255);
  --color-neutral-800: oklch(27%   0.012 255);
  --color-neutral-900: oklch(21%   0.012 255);

  /* Le vert oxide #1F4F46 et la rampe Kraft sont une pièce à conviction. Ils ne
   * reviennent pas : chaque palier retombe sur son équivalent de clarté. */
  /* --color-oxide-50 n'est pas ici non plus : ses quatre sites d'appel sont
   * tous des `background`. Un lavis est un rôle de FOND, il suit le thème —
   * figé en clair, il portait de l'encre claire sur du papier clair en thème
   * sombre (1.05:1 sur l'en-tête « HideMyDrop » du tableau comparatif).
   * --color-oxide-100 reste une primitive stable, lui : cinq de ses six sites
   * l'emploient comme ENCRE claire sur la carte de plan mise en avant, et un
   * seul comme fond. Surchargé, il suit son emploi dominant. */
  --color-oxide-100: oklch(93.8% 0.005 255);
  --color-oxide-200: oklch(90%   0.006 255);
  --color-oxide-300: oklch(74%   0.006 255);    /* lu comme encre EN SOMBRE (--ink-brand) : doit rester clair */
  --color-oxide-400: oklch(58%   0.008 255);
  --color-oxide-500: oklch(46%   0.009 255);
  --color-oxide-600: oklch(36%   0.010 255);
  --color-oxide-700: oklch(27%   0.012 255);    /* double emploi hérité : encre sur papier ET fond actif sur le rail sombre */
  --color-oxide-800: oklch(17.5% 0.012 255);    /* le rail de navigation, jusqu'à ce qu'il devienne une bande horizontale */
  --color-kraft-200: oklch(90%   0.006 255);
  --color-kraft-300: oklch(80%   0.007 255);
  --color-kraft-500: oklch(46%   0.009 255);

  /* --color-primary* et --color-secondary* ne sont PAS ici : ce sont des
   * couleurs d'ACTION, et une action est une plaque. Or la plaque s'inverse
   * entre les deux thèmes. Elles sont donc déclarées avec les rôles, plus bas.
   * Mesuré : figées en primitives, `.btn-primary` (fond --color-primary, texte
   * --text-on-brand) rendait de l'encre sombre sur une plaque sombre en thème
   * sombre — 1.12:1 sur « Choisir Pro ». Le fond doit tourner avec son texte. */

  /* Danger : le SEUL pigment de chrome. C'est le cramoisi d'exception. */
  --color-danger-50:  oklch(92.07% 0.011 355);
  --color-danger-200: oklch(80%    0.070 25);
  --color-danger-300: oklch(72%    0.150 25);   /* rang lu par le thème sombre */
  --color-danger-400: oklch(60%    0.170 25);
  --color-danger-500: oklch(51%    0.190 25);
  --color-danger-700: oklch(40%    0.154 25);   /* rang lu par le thème clair */
  --color-danger-800: oklch(34%    0.130 25);

  /* Succès, avertissement, information : ACHROMATIQUES. Il n'y a pas de vert de
   * succès ni d'ambre d'avertissement dans ce système — un toast de succès est
   * en encre avec un glyphe de coche, et réutiliser un pigment de statut pour un
   * sens qui n'est pas un statut de colis rend l'écran illisible pour l'œil
   * entraîné. */
  --color-success-50:  oklch(93.8% 0.005 255);
  --color-success-300: oklch(74%   0.006 255);
  --color-success-400: oklch(61%   0.008 255);
  --color-success-500: oklch(46%   0.009 255);
  --color-success-700: oklch(30%   0.011 255);
  --color-warning-50:  oklch(93.8% 0.005 255);
  --color-warning-200: oklch(88%   0.006 255);
  --color-warning-300: oklch(74%   0.006 255);
  --color-warning-400: oklch(61%   0.008 255);
  --color-warning-500: oklch(46%   0.009 255);
  --color-warning-700: oklch(30%   0.011 255);
  --color-warning-800: oklch(24%   0.012 255);
  --color-info-300:    oklch(74%   0.006 255);
  --color-info-400:    oklch(61%   0.008 255);
  --color-info-500:    oklch(46%   0.009 255);
  --color-info-700:    oklch(30%   0.011 255);

  /* Transit : la seule ancienne rampe colorée qui survit, parce qu'elle nomme un
   * état de COLIS. Elle rejoint son pigment de statut. */
  --color-transit-300: oklch(76% 0.090 200);
  --color-transit-500: oklch(47% 0.076 200);
  --color-transit-700: oklch(36% 0.058 200);

  /* --- Rôles de couleur (redéclarés sous [data-theme="dark"]) ------------ */
  --color-danger:  var(--status-exception);
  --color-success: var(--ink);
  --color-warning: var(--ink-2);
  --color-info:    var(--ink-2);
  --color-primary-50: var(--ground-sunk);
  --color-oxide-50:   var(--ground-sunk);       /* lavis de marque : un fond, donc un rôle */

  /* Les couleurs d'action suivent la plaque, donc elles s'inversent avec elle :
   * un fond d'action et son texte tournent ensemble ou ils se confondent. */
  --color-primary:         var(--plaque);
  --color-primary-hover:   var(--plaque-hover);
  --color-primary-pressed: var(--plaque-active);
  --color-secondary:       var(--plaque-hover);  /* le secondaire deviendra transparent à bord ink-4 ; en attendant il reste lisible */
  --color-secondary-hover: var(--plaque-active);

  --brand-primary:       var(--plaque);         /* la marque du produit EST l'encre */
  --brand-primary-hover: var(--plaque-hover);
  --brand-primary-wash:  var(--ground-sunk);

  --surface-base:    var(--ground);
  --surface-sunken:  var(--ground-sunk);
  --surface-alt:     var(--ground-sunk);
  /* CYCLE : l'admin écrit `--surface: var(--surface-raised)` dans son propre
   * :root. Écrire ici `--surface-raised: var(--surface)` referme la boucle, et
   * une propriété personnalisée cyclique ne rend pas une valeur de repli —
   * elle rend le vide. Mesuré : --surface résolvait à la chaîne vide sur
   * l'admin, le panneau de connexion ne peignait plus son fond et son texte
   * clair tombait sur le papier clair. Ces deux-là portent donc le LITTÉRAL de
   * --surface, pas une référence. Ils disparaissent avec ce bloc. */
  --surface-raised:  oklch(99.2% 0.002 255);
  --surface-overlay: oklch(99.2% 0.002 255);

  --text-primary:   var(--ink);
  --text-secondary: var(--ink-2);
  --text-muted:     var(--ink-3);
  --text-dim:       var(--ink-3);
  --text-disabled:  var(--ink-disabled);
  --text-link:      var(--ink);                 /* le lien est de l'encre soulignée, pas une couleur */
  --text-on-brand:  var(--ink-inverse);

  --border-default: var(--rule);
  --border-subtle:  var(--rule-hair);
  --border-control: var(--ink-4);               /* plancher de 3:1 pour un bord de contrôle */
  --border-strong:  var(--rule-strong);
  --focus-stroke:   var(--ink);

  /* Réglette : le stepper à cinq pastilles rondes devient des graduations. Ces
   * quatre noms ne lisent JAMAIS --tp-brand — la marque du marchand n'a pas le
   * droit d'affirmer où se trouve un colis. */
  --step-done-fill:    var(--plaque);
  --step-current-fill: var(--plaque);
  --step-current-ring: var(--state-focus-halo);
  --step-pending-ring: var(--ink-4);
  --track-bg:          var(--ground-sunk);
  --track-fill:        var(--plaque);

  /* --- Typographie héritée ----------------------------------------------- */
  --font-sans: var(--font-text);                /* un seul token de texte : la famille « sans » n'est pas un rôle */

  /* L'ancienne échelle était fluide (onze clamp) : un titre qui rétrécit dans
   * une fenêtre partagée. Chaque cran retombe sur le cran fixe le plus proche. */
  --font-size-2xs:  var(--fs-12);
  --font-size-xs:   var(--fs-13);
  --font-size-sm:   var(--fs-15);
  --font-size-base: var(--fs-17);
  --font-size-lg:   var(--fs-21);
  --font-size-xl:   var(--fs-26);
  --font-size-2xl:  var(--fs-26);
  --font-size-3xl:  var(--fs-34);
  --font-size-4xl:  var(--fs-46);
  --font-size-5xl:  var(--fs-46);
  --font-size-7xl:  var(--fs-hero);
  --font-size-ui-2xs: var(--fs-11);
  --font-size-ui-xs:  var(--fs-13);
  --font-size-ui-md:  var(--fs-13);
  --font-size-ui-lg:  var(--fs-15);
  --font-size-ui-xl:  var(--fs-21);
  --font-size-ui-2xl: var(--fs-26);
  --font-size-ui-3xl: var(--fs-34);
  /* Le SEUL max() du système, et il est justifié par un comportement matériel :
   * Safari iOS zoome un champ sous 16px effectifs, et le max() couvre le cas
   * d'un utilisateur ayant réduit sa taille de police racine. Les max() en px
   * sur les crans d'échelle (l'ancienne famille --font-size-ui-*) disparaissent :
   * ils annulaient le réglage utilisateur vers le bas en prétendant le respecter. */
  --font-size-input-nozoom: max(var(--fs-17), 16px);

  --font-weight-regular:  var(--w-regular);
  --font-weight-medium:   var(--w-medium);
  --font-weight-semibold: var(--w-semibold);
  --font-weight-bold:     var(--w-hero);        /* 700 n'existe QUE sur le héros de la page tarifs */

  --leading-none:     1;
  --leading-2xs:      var(--lh-12);
  --leading-xs:       var(--lh-13);
  --leading-sm:       var(--lh-15);
  --leading-base:     var(--lh-17);
  --leading-lg:       var(--lh-21);
  --leading-xl:       var(--lh-21);
  --leading-2xl:      var(--lh-26);
  --leading-3xl:      var(--lh-26);
  --leading-4xl:      var(--lh-34);
  --leading-5xl:      var(--lh-46);
  --leading-ui:       var(--lh-13);
  --leading-ui-2xs:   var(--lh-12);
  --leading-ui-xs:    var(--lh-13);
  --leading-ui-md:    var(--lh-13);
  --leading-ui-lg:    var(--lh-21);
  --leading-ui-xl:    var(--lh-26);
  --leading-ui-2xl:   var(--lh-26);
  --leading-ui-3xl:   var(--lh-34);
  --line-height-xs:   var(--lh-13);
  --line-height-sm:   var(--lh-15);
  --line-height-base: var(--lh-17);
  --line-height-lg:   var(--lh-21);
  --line-height-3xl:  var(--lh-26);
  --line-height-4xl:  var(--lh-34);
  --line-height-5xl:  var(--lh-46);
  --line-height-7xl:  var(--lh-hero);

  --tracking-2xs:      var(--tr-micro);
  --tracking-xs:       var(--tr-micro);
  --tracking-sm:       var(--tr-texte);
  --tracking-base:     var(--tr-texte);
  --tracking-normal:   var(--tr-texte);
  --tracking-lg:       var(--tr-21);
  --tracking-xl:       var(--tr-21);
  --tracking-2xl:      var(--tr-26);
  --tracking-3xl:      var(--tr-34);
  --tracking-5xl:      var(--tr-hero);
  --tracking-tight-1:  var(--tr-21);
  --tracking-tight-2:  var(--tr-26);
  --tracking-tight-3:  var(--tr-34);
  --tracking-caps:     var(--tr-cartouche);
  --tracking-upper:    var(--tr-cartouche);
  --tracking-logotype: var(--tr-symbole);
  --tracking-mono:     var(--tr-texte);         /* la mono ne s'ouvre pas : tabular-nums fait déjà le travail */
  --tracking-tabular:  0;                       /* tout tracking casse l'alignement d'une colonne chiffrée */

  /* --- Espacement hérité -------------------------------------------------- */
  --sp-1: 0;                                    /* 1px n'est plus un espacement : c'est un filet */
  --space-1:  var(--sp-2);
  --space-2:  var(--sp-4);
  --space-3:  var(--sp-8);                      /* 6px sortait de la base 4 */
  --space-4:  var(--sp-8);
  --space-5:  var(--sp-12);
  --space-6:  var(--sp-16);
  --space-7:  var(--gap-title-content);         /* 20 : la seule valeur composée qui reste */
  --space-8:  var(--sp-24);
  --space-9:  var(--sp-32);
  --space-10: var(--sp-32);                     /* 40 hors échelle : on resserre, on ne déborde jamais */
  --space-11: var(--sp-48);
  --space-12: var(--sp-64);
  --space-13: var(--sp-64);                     /* 80 hors échelle */
  --space-14: var(--sp-96);

  --gap-icon-label:   var(--gap-glyph-label);
  --gap-stack-tight:  var(--sp-8);
  --gap-group:        var(--gap-block);
  --gap-section-app:  var(--sp-48);
  --gap-heading-sub-before: var(--sp-32);
  --gap-heading-sub-after:  var(--sp-8);
  --gutter-inline:    var(--pad-page);          /* la gouttière n'est plus un clamp : elle change par palier */
  --pad-card:         var(--sp-24);
  --pad-card-compact: var(--sp-16);
  --pad-card-lg:      var(--sp-32);
  --pad-control-y:    0;                        /* voir l'avertissement en tête de bloc : poser --h-control dans la même passe */
  --pad-control-y-sm: 0;
  --pad-control-y-lg: 0;
  --pad-icon-button:  0;
  --row-py:           0;
  --cell-px:          var(--pad-cell-x);

  /* --- Rayons : vingt noms consommés, cinq valeurs ------------------------ */
  --radius-0: var(--radius-none);        --radius-1: var(--radius-mark);
  --radius-2: var(--radius-control);     --radius-3: var(--radius-control);
  --radius-4: var(--radius-float);       --radius-5: var(--radius-float);
  --radius-6: var(--radius-float);       --radius-full: var(--radius-stadium);
  --radius-xs: var(--radius-mark);       --radius-sm: var(--radius-control);
  --radius-md: var(--radius-control);    --radius-lg: var(--radius-float);
  --radius-xl: var(--radius-float);      --radius-2xl: var(--radius-float);
  --radius: var(--radius-control);       --r-md: var(--radius-control);
  --radius-checkbox: var(--radius-mark); --radius-tag-square: var(--radius-mark);
  --radius-badge: var(--radius-mark);    --radius-chip: var(--radius-mark);
  --radius-control-sm: var(--radius-control);
  --radius-control-lg: var(--radius-control);
  --radius-segment: var(--radius-control);
  --radius-thumb: var(--radius-control);
  --radius-card: var(--radius-none);            /* la carte n'existe plus : elle est devenue un registre */
  --radius-table: var(--radius-none);
  --radius-alert: var(--radius-none);
  --radius-menu: var(--radius-float);    --radius-popover: var(--radius-float);
  --radius-toast: var(--radius-float);   --radius-modal: var(--radius-float);
  --radius-drawer: var(--radius-float);  --radius-panel: var(--radius-float);
  --radius-sheet: var(--radius-float);   --radius-canvas: var(--radius-none);
  --radius-frame: var(--radius-none);    --radius-media-hero: var(--radius-none);
  --radius-pill: var(--radius-stadium);  --radius-avatar: var(--radius-stadium);
  --radius-dot: var(--radius-stadium);   --radius-switch: var(--radius-stadium);

  /* --- Hauteurs et icônes héritées ---------------------------------------- */
  --control-h-xs: var(--h-mark);         --control-h-sm: var(--h-control-sm);
  --control-h:    var(--h-control);      --control-h-lg: var(--h-control);
  --topbar-h:     var(--h-band-head);
  --icon-sm: var(--icon-16);             --icon-md: var(--icon-20);
  --icon-lg: var(--icon-20);             --icon-xl: var(--icon-20);   /* 32px n'existe plus : deux tailles d'icône, pas quatre */
  --icon-stroke-sm: var(--icon-stroke-16);
  --icon-stroke-md: var(--icon-stroke-20);
  --icon-stroke-lg: var(--icon-stroke-20);
  --icon-stroke-xl: var(--icon-stroke-20);

  /* --- Ombres : onze noms consommés, trois valeurs ------------------------ */
  /* Presque tout tombe à `none`. Un objet posé sur la page ne projette pas
   * d'ombre parce qu'il n'est pas soulevé. */
  --elev-0: var(--shadow-none);    --shadow-0: var(--shadow-none);
  --elev-1: var(--shadow-none);    --shadow-1: var(--shadow-none);
  --elev-2: var(--shadow-float);   --shadow-2: var(--shadow-float);
  --elev-3: var(--shadow-overlay); --shadow-3: var(--shadow-overlay);
  --elev-4: var(--shadow-overlay); --shadow-4: var(--shadow-overlay);
  --shadow-5: var(--shadow-overlay);
  --shadow:    var(--shadow-none);              /* l'ombre par défaut d'une carte : plus d'ombre du tout */
  --shadow-lg: var(--shadow-overlay);
  --elev-brand-1: var(--shadow-none);  --elev-brand-2: var(--shadow-none);
  --elevation-0: var(--shadow-none);   --elevation-1: var(--shadow-none);
  --elevation-2: var(--shadow-float);  --elevation-3: var(--shadow-overlay);
  --hairline-top: none;                         /* un filet dessiné en ombre redevient une bordure */
  --shadow-focus-ring: var(--focus-ring);

  /* --- Mouvement hérité ---------------------------------------------------- */
  /* Huit courbes deviennent trois. --ease-spring-soft portait la seule détection
   * du fichier (y1 = 1.26, un dépassement de 26 %) : un objet réel décélère. */
  --ease-spring-soft: var(--ease-out);
  --ease-spring:      var(--ease-out);
  --ease-entrance:    var(--ease-out);
  --ease-exit:        var(--ease-in);
  --ease-standard:    var(--ease-out);          /* la courbe Material n'appartient pas à ce monde */
  --ease-emphasis:    var(--ease-out);          /* l'emphase se fait par la durée, pas par une cinquième courbe */
  --ease-emphasized:  var(--ease-out);
  --ease-glide:       var(--ease-out);
  --ease-out-quad:    var(--ease-out);
  --ease-in-quad:     var(--ease-in);
  --ease-breathe:     var(--ease-move);
  --ease-spin:        linear;                   /* un mot-clé n'est pas une courbe : une rotation à vitesse variable lit comme un bug */

  --dur-micro:    var(--dur-state);
  --dur-instant:  var(--dur-state);
  --dur-icon:     var(--dur-state);
  --dur-fast:     var(--dur-state);
  --dur-chip:     var(--dur-state);
  --dur-card:     var(--dur-enter);
  --dur-banner:   var(--dur-enter);
  --dur-event:    var(--dur-enter);
  --dur-page:     var(--dur-enter);             /* il n'y a pas de transition de page */
  --dur-modal:    var(--dur-enter);
  --dur-rail:     var(--dur-signature);         /* le rail de chronologie disparaît avec les pastilles */
  --dur-hero:     var(--dur-signature);
  --dur-progress: var(--dur-signature);
  --dur-pulse:    var(--dur-sk-pulse);
  --dur-shimmer:  var(--dur-sk-pulse);          /* le balayage en dégradé disparaît, la période est conservée */

  --stagger-dot-offset: 0ms;                    /* la pastille de chronologie n'existe plus : plus rien à décaler */
  --stagger-cap:        0ms;                    /* le plafond est remplacé par STAGGER_WINDOW, côté JS */

  /* --- Z-index retirés ------------------------------------------------------ */
  --z-under:   0;
  --z-overlay: var(--z-scrim-drawer);
  --z-max:     var(--z-skip-link);
}

/* Les rôles hérités qui bougeaient avec le thème continuent de bouger. Les
 * primitives ci-dessus, elles, ne bougent pas : c'est le contrat sur lequel les
 * pages s'appuient pour retourner leur encre en sombre. */
[data-theme="dark"] {
  --color-danger:  var(--status-exception);
  --color-success: var(--ink);
  --color-warning: var(--ink-2);
  --color-info:    var(--ink-2);
  --color-primary-50: var(--ground-sunk);
  --color-oxide-50:   var(--ground-sunk);       /* lavis de marque : un fond, donc un rôle */

  /* Les couleurs d'action suivent la plaque, donc elles s'inversent avec elle :
   * un fond d'action et son texte tournent ensemble ou ils se confondent. */
  --color-primary:         var(--plaque);
  --color-primary-hover:   var(--plaque-hover);
  --color-primary-pressed: var(--plaque-active);
  --color-secondary:       var(--plaque-hover);  /* le secondaire deviendra transparent à bord ink-4 ; en attendant il reste lisible */
  --color-secondary-hover: var(--plaque-active);

  --brand-primary:       var(--plaque);
  --brand-primary-hover: var(--plaque-hover);
  --brand-primary-wash:  var(--ground-sunk);

  --surface-base:    var(--ground);
  --surface-sunken:  var(--ground-sunk);
  --surface-alt:     var(--ground-sunk);
  --surface-raised:  oklch(22.5% 0.013 255);    /* littéral, pas var(--surface) : voir le cycle documenté dans le bloc clair */
  --surface-overlay: oklch(22.5% 0.013 255);

  --text-primary:   var(--ink);
  --text-secondary: var(--ink-2);
  --text-muted:     var(--ink-3);
  --text-dim:       var(--ink-3);
  --text-disabled:  var(--ink-disabled);
  --text-link:      var(--ink);
  --text-on-brand:  var(--ink-inverse);

  --border-default: var(--rule);
  --border-subtle:  var(--rule-hair);
  --border-control: var(--ink-4);
  --border-strong:  var(--rule-strong);
  --focus-stroke:   var(--ink);

  --step-done-fill:    var(--plaque);
  --step-current-fill: var(--plaque);
  --step-current-ring: var(--state-focus-halo);
  --step-pending-ring: var(--ink-4);
  --track-bg:          var(--ground-sunk);
  --track-fill:        var(--plaque);

  --elev-1: var(--shadow-none);    --shadow-1: var(--shadow-none);
  --elev-2: var(--shadow-float);   --shadow-2: var(--shadow-float);
  --elev-3: var(--shadow-overlay); --shadow-3: var(--shadow-overlay);
  --elev-4: var(--shadow-overlay); --shadow-4: var(--shadow-overlay);
  --shadow-5: var(--shadow-overlay);
  --shadow:    var(--shadow-none);
  --shadow-lg: var(--shadow-overlay);
  --elevation-2: var(--shadow-float);
  --elevation-3: var(--shadow-overlay);
  --shadow-focus-ring: var(--focus-ring);
}


/* ============================================================================
 * 11. MOUVEMENT RÉDUIT — neutralisation au niveau des tokens
 * ============================================================================
 * Première des deux couches. Toute déclaration qui dépense un token est déjà
 * neutre avant d'être lue ; la seconde couche (le bloc de comportement) vit en
 * fin de chaque feuille de surface.
 *
 * Les durées tombent à 1ms, JAMAIS à 0 et jamais à `none` : les écouteurs
 * transitionend et animationend doivent continuer de se déclencher, sinon tout
 * code JS qui attend la fin d'une transition se bloque définitivement. Et le
 * fill-mode doit rester actif, sinon un élément animé en entrée reste invisible.
 *
 * Ce bloc est en FIN de fichier par construction : un garde placé au milieu ne
 * couvre que la moitié qu'il précède. */

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-state:     1ms;
    --dur-control:   1ms;
    --dur-enter:     1ms;
    --dur-exit:      1ms;
    --dur-signature: 1ms;
    --stagger-step:  0ms;
    --enter-delay:   0ms;                       /* non négociable avec fill-mode: backwards, sinon l'élément reste invisible pendant son délai */
    --delay-tooltip: 0ms;
  }
  /* --dur-spin et --dur-sk-pulse ne sont PAS collapsés ici : leurs deux
   * exceptions (le spinner et la respiration de squelette codent un état EN
   * COURS) s'expriment en fonction de leur valeur réelle, pas d'un littéral. */
}
