/* IDeal Evolution Studio — Copyright © 2026 Resonance IA — Par Stéphane HAYAT — Tous droits réservés. */
/* =================================================================================================
   LANGAGE D'INTERACTION (#353, SL1) — feuille COMMUNE du socle, mutualisée entre les deux mondes.

   RÉFÉRENCE CONTRACTUELLE : docs/references-design/reference-langage-interaction-v1.0.html
   C'est ELLE qui fait foi. Toute valeur ci-dessous en vient ; tout écart est consigné au compte rendu
   du lot, jamais réconcilié en silence.

   ⚠️ RÈGLE DE COHABITATION, NON NÉGOCIABLE — TOUT sélecteur de cette feuille descend de [data-li].
   Aucune déclaration sur :root, html, body, *, ni sur une classe du produit. Raison mesurée : l'attribut
   data-theme est DÉJÀ pris sur <html> par le design system en place (data-theme = dark|light, posé par
   l'anti-flash d'App.razor et ideale-theme.js). Réutiliser cet attribut, ou déclarer ces tokens sur :root,
   ne casserait pas « un écran » : ça casserait l'application entière. D'où des attributs NAMESPACÉS
   (data-li-theme, data-li-accent) portés par la racine du socle.
   📌 MISE À JOUR #369 (SL4) — la raison citée ici mentionnait aussi « data-accent = sarcelle|creme|azur,
   consommé par app-saas.css et app-locataire.css ». Ce n'est PLUS vrai : #369 a retiré les six blocs
   [data-accent] de chaque monde, et cet attribut, encore posé par idealeTheme.applyAccent, ne pilote plus
   aucune règle. La règle de cohabitation, elle, ne change pas — elle repose désormais sur le seul
   data-theme, et sur le fait qu'un vocabulaire séparé reste ce qui rend les deux contrats lisibles.

   Les NOMS et VALEURS des tokens restent ceux du contrat §C1 et de la référence — seul leur POINT
   D'APPLICATION diffère (racine du socle au lieu de :root).

   🔑 LE POINT D'APPLICATION EST DEVENU LE PRODUIT ENTIER (#369, SL4) — c'était « l'affaire de SL4 », et
   c'est fait : l'ancre [data-li] est écrite en dur sur <html> (Components/App.razor), ses deux attributs de
   valeur sont posés dès l'anti-flash puis tenus par idealeTheme.applyMode / applyTeinteSocle.
   ⚠️ CE GESTE RETIRE À CETTE FEUILLE SA GARANTIE D'INERTIE. « Tout sélecteur descend de [data-li] » ne
   prouvait l'absence d'effet sur les écrans de production que tant que l'attribut ne vivait que sur les
   boîtes. Le préfixe est maintenant satisfait PARTOUT. Ce qui garantit l'inertie est une AUTRE propriété,
   mesurée et tenue par AncrageSocleLangage369Tests : au-delà du préfixe, tout sélecteur exige EN PLUS une
   classe .li-*, sauf CINQ génériques énumérés (le reset de boîte et l'anneau de focus). Avant d'écrire ici
   un sélecteur qui atteindrait un élément sans classe .li-*, lire ce banc : il rougit en le nommant.

   Cette feuille ne référence AUCUN token --ds-*, AUCUNE classe .mud-*, AUCUN sélecteur de monde ou de
   layout : le socle n'attend rien de l'UI existante, et l'UI existante ne voit rien de lui.
   ================================================================================================= */

/* =================================================================================================
   1 · TOKENS — apparence CLAIRE (base)
   ================================================================================================= */
[data-li] {
    /* Pile de polices DÉCLARATIVE, type système (aucune ressource embarquée) : chaque poste rend avec
       la police de son système. Les noms cités ne sont que des familles demandées au système. */
    --font: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display',
            'Segoe UI Variable Display', 'Segoe UI', system-ui, sans-serif;

    /* Surfaces et encres */
    --fond: #F4F4F5;
    --carte: #FFFFFF;
    --carte-2: #FAFAFB;
    --encre: #1B1C1E;
    --encre-2: #6B6C70;
    --encre-3: #9A9AA1;
    --bord: #E4E4E7;
    --bord-2: #D5D5D9;

    /* Rouge = destructif et erreur UNIQUEMENT. Il ne fait jamais partie du nuancier d'accent. */
    --rouge: #C6402F;
    --rouge-doux: rgba(198, 64, 47, .14);

    /* Statuts : couleurs de SENS, indépendantes de l'accent choisi par l'utilisateur. */
    --st-info: #4A6DA7;
    --st-info-doux: rgba(74, 109, 167, .13);
    --st-ok: #1E7A34;
    --st-ok-doux: rgba(30, 122, 52, .13);
    --st-att: #B45309;
    --st-att-doux: rgba(180, 83, 9, .13);

    /* Voile des modales et matériau translucide des barres */
    --voile: rgba(15, 16, 20, .24);
    --verre: rgba(255, 255, 255, .75);

    /* Ombres : posée, flottante, projetée */
    --ombre-1: 0 1px 3px rgba(0, 0, 0, .08);
    --ombre-2: 0 12px 36px rgba(0, 0, 0, .15);
    --ombre-3: 0 26px 70px rgba(0, 0, 0, .26);

    /* Mouvements — durées canoniques (§C1) */
    --mvt-entree: 350ms;   /* entrée des feuilles */
    --mvt-sortie: 250ms;   /* sorties */
    --mvt-secousse: 450ms; /* secousse de refus */
    --mvt-micro: 150ms;    /* focus, survols, interrupteurs */

    /* Mouvements — courbes canoniques (§C1) */
    --c-feuille: cubic-bezier(0.32, 0.72, 0, 1); /* feuilles, toasts */
    --c-std: cubic-bezier(0.25, 0.1, 0.25, 1);   /* dialogues, micro-transitions */
    --c-sortie: cubic-bezier(0.4, 0, 1, 1);      /* sorties */

    /* Paliers de largeur des boîtes (§C1). ⚠️ L'alerte de la référence se rend à 430 px et non au
       palier S de 420 px : la RÉFÉRENCE fait foi (§C4), l'écart est consigné au compte rendu et
       matérialisé — visiblement — sur .li-dialogue--s. --palier-s / --palier-l servent SL2+. */
    --palier-s: 420px;
    --palier-m: 560px;
    --palier-l: 760px;
    --palier-etendue: 92vw;

    /* Empilement (§C1) — mesuré dans la référence, du plus bas au plus haut. */
    --z-infobulle: 50;
    --z-menu: 600;   /* menus, popovers, suggestions */
    --z-ctx: 900;    /* menu contextuel */
    --z-modale: 1000;
    --z-alerte: 1100;
    --z-apercu: 1150;
    --z-toast: 1200;
}

/* =================================================================================================
   2 · TOKENS — apparence SOMBRE
   Même spécificité que le bloc clair au sens de la référence, mais ancrée sur [data-li] : la
   surcharge est explicite plutôt que dépendante du seul ordre de déclaration.
   ================================================================================================= */
[data-li][data-li-theme="sombre"] {
    --fond: #151518;
    --carte: #1F1F23;
    --carte-2: #27272C;
    --encre: #F2F2F4;
    --encre-2: #A6A6AE;
    --encre-3: #77777F;
    --bord: #32323A;
    --bord-2: #41414A;

    --rouge: #E5604C;
    --rouge-doux: rgba(229, 96, 76, .20);

    --st-info: #7FA6E0;
    --st-info-doux: rgba(127, 166, 224, .18);
    --st-ok: #4CC38A;
    --st-ok-doux: rgba(76, 195, 138, .16);
    --st-att: #E8A13C;
    --st-att-doux: rgba(232, 161, 60, .16);

    --voile: rgba(0, 0, 0, .52);
    --verre: rgba(30, 30, 35, .72);

    --ombre-1: 0 1px 3px rgba(0, 0, 0, .5);
    --ombre-2: 0 12px 36px rgba(0, 0, 0, .5);
    --ombre-3: 0 26px 70px rgba(0, 0, 0, .65);
}

/* =================================================================================================
   3 · NUANCIER D'ACCENT — neuf teintes, réglage du profil utilisateur.

   L'accent colore les boutons principaux, interrupteurs, sélections, liens d'action et halos de
   focus. Il ne touche NI les surfaces, NI les encres, NI les couleurs de sens (rouge, statuts).
   Anthracite est le défaut : l'interface telle qu'elle est aujourd'hui.
   ================================================================================================= */

/* --- Anthracite (défaut) --- */
[data-li][data-li-accent="anthracite"] { --accent: #1B1C1E; --accent-fg: #FFFFFF; --accent-doux: rgba(27, 28, 30, .10); }
[data-li][data-li-theme="sombre"][data-li-accent="anthracite"] { --accent: #F2F2F4; --accent-fg: #151518; --accent-doux: rgba(242, 242, 244, .14); }

/* --- Argent --- */
[data-li][data-li-accent="argent"] { --accent: #6E7278; --accent-fg: #FFFFFF; --accent-doux: rgba(110, 114, 120, .14); }
[data-li][data-li-theme="sombre"][data-li-accent="argent"] { --accent: #C6C9CE; --accent-fg: #16171A; --accent-doux: rgba(198, 201, 206, .18); }

/* --- Bleu --- */
[data-li][data-li-accent="bleu"] { --accent: #0A66E8; --accent-fg: #FFFFFF; --accent-doux: rgba(10, 102, 232, .13); }
[data-li][data-li-theme="sombre"][data-li-accent="bleu"] { --accent: #3D8BFF; --accent-fg: #FFFFFF; --accent-doux: rgba(61, 139, 255, .20); }

/* --- Bleu profond --- */
[data-li][data-li-accent="bleuprofond"] { --accent: #1D4E9E; --accent-fg: #FFFFFF; --accent-doux: rgba(29, 78, 158, .13); }
[data-li][data-li-theme="sombre"][data-li-accent="bleuprofond"] { --accent: #5B8AE0; --accent-fg: #FFFFFF; --accent-doux: rgba(91, 138, 224, .20); }

/* --- Vert --- */
[data-li][data-li-accent="vert"] { --accent: #1E7A34; --accent-fg: #FFFFFF; --accent-doux: rgba(30, 122, 52, .13); }
[data-li][data-li-theme="sombre"][data-li-accent="vert"] { --accent: #2FA365; --accent-fg: #FFFFFF; --accent-doux: rgba(47, 163, 101, .20); }

/* --- Violet --- */
[data-li][data-li-accent="violet"] { --accent: #6D5BD0; --accent-fg: #FFFFFF; --accent-doux: rgba(109, 91, 208, .14); }
[data-li][data-li-theme="sombre"][data-li-accent="violet"] { --accent: #8E7BFF; --accent-fg: #FFFFFF; --accent-doux: rgba(142, 123, 255, .20); }

/* --- Ambre --- */
[data-li][data-li-accent="ambre"] { --accent: #B45309; --accent-fg: #FFFFFF; --accent-doux: rgba(180, 83, 9, .14); }
[data-li][data-li-theme="sombre"][data-li-accent="ambre"] { --accent: #E8A13C; --accent-fg: #1B1C1E; --accent-doux: rgba(232, 161, 60, .20); }

/* --- Or --- */
[data-li][data-li-accent="or"] { --accent: #A8811C; --accent-fg: #FFFFFF; --accent-doux: rgba(168, 129, 28, .15); }
[data-li][data-li-theme="sombre"][data-li-accent="or"] { --accent: #EDC85A; --accent-fg: #1B1C1E; --accent-doux: rgba(237, 200, 90, .20); }

/* --- Terracotta --- */
[data-li][data-li-accent="terracotta"] { --accent: #A85B38; --accent-fg: #FFFFFF; --accent-doux: rgba(168, 91, 56, .14); }
[data-li][data-li-theme="sombre"][data-li-accent="terracotta"] { --accent: #E08A63; --accent-fg: #1B1C1E; --accent-doux: rgba(224, 138, 99, .20); }

/* =================================================================================================
   4 · BASE — la racine du socle et ce qu'elle impose à ses descendants (à eux SEULS)
   ================================================================================================= */

/* box-sizing SCOPÉ : la référence l'applique à « * », ce qui, ici, redéfinirait la boîte de toute
   l'application. Il ne descend que de la racine du socle. */
[data-li],
[data-li] *,
[data-li] *::before,
[data-li] *::after { box-sizing: border-box; }

.li-racine {
    background: var(--fond);
    color: var(--encre);
    font-family: var(--font);
    line-height: 1.55;
    transition: background var(--mvt-micro), color var(--mvt-micro);
}

/* =================================================================================================
   L'ANNEAU DE FOCUS — et pourquoi il vit dans une COUCHE (@layer), depuis #369 (SL4).

   🔑 DANS LA RÉFÉRENCE, CETTE RÈGLE EST UN REPLI, PAS UNE SURCHARGE — et c'est mesurable. Elle s'y écrit
   « :focus-visible{…;border-radius:6px} » (l. 71), soit une spécificité (0,1,0), et elle est déclarée AVANT
   « .btn{…border-radius:9px} » (l. 74), de spécificité identique. Un bouton garde donc SON rayon au focus ;
   seuls les éléments qui n'en déclarent aucun héritent des 6 px, pour que l'anneau suive un tracé arrondi.

   ⚠️ LA TRANSPOSITION AVAIT PERDU CETTE PROPRIÉTÉ, ET ON NE LE VOYAIT PAS TANT QUE LA RACINE VIVAIT SUR LES
   BOÎTES. Le préfixe « [data-li] » ajoute une spécificité que la référence n'a pas : (0,2,0) au lieu de
   (0,1,0). À l'intérieur du socle, cela reste sans effet — « [data-li] .li-btn » vaut aussi (0,2,0) et vient
   plus loin dans la feuille, donc il gagne, exactement comme dans la référence. Mais depuis que l'attribut
   est sur <html>, la règle affronte les feuilles du PRODUIT, chargées AVANT celle-ci : « .mud-button »
   (0,1,0) perdait alors son rayon au profit des 6 px, à chaque focus clavier. Mesuré le 26/08/2026 sur les
   écrans d'entrée : 10 px ramenés à 6 sur .mud-button et .ideale-langue-combo. Aucune capture ne l'aurait
   montré — le balayage visuel ne photographie jamais un élément au focus.

   ⇒ La couche rétablit la sémantique du contrat sans toucher à une seule valeur : une règle NON couchée
   l'emporte toujours sur une règle couchée, quelle que soit sa spécificité. Tout rayon d'auteur gagne donc,
   comme dans la référence ; un élément qui n'en déclare aucun reçoit les 6 px, comme dans la référence.
   ⛔ NE PAS « simplifier » en retirant la couche pour gagner en spécificité : ce serait remplacer un repli
   par une surcharge, c'est-à-dire faire dire à la référence le contraire de ce qu'elle fait.
   📌 Corollaire assumé : les règles de focus PROPRES à un composant (kit, volet IDeal, page de connexion)
   l'emportent elles aussi — ce qui est le comportement voulu, et déjà le leur avant #369 par spécificité.
   ================================================================================================= */
@layer socle-focus {
    /* Anneau de focus (§C1) : 3 px sur base --accent-doux, teinte de l'accent à 45 %, décalage 2 px. */
    [data-li] :focus-visible {
        outline: 3px solid var(--accent-doux);
        outline-color: color-mix(in srgb, var(--accent) 45%, transparent);
        outline-offset: 2px;
        border-radius: 6px;
    }

    /* ⚠️ …SAUF ce que seul le PROGRAMME peut atteindre (tabindex="-1"). Sans cette exception, le titre de la
       page s'entoure d'un cadre à la simple arrivée : le routeur y pose le focus (FocusOnNavigate Selector="h1")
       pour les lecteurs d'écran, et l'anneau se dessine sur un élément que l'utilisateur n'a jamais visé. Un
       anneau de focus dit « vous êtes ici au clavier » ; le poser ailleurs, c'est lui faire dire n'importe quoi.
       L'accessibilité n'y perd rien : le focus programmatique reste posé, il cesse seulement d'être peint. */
    [data-li] [tabindex="-1"]:focus-visible { outline: none; }
}

/* =================================================================================================
   5 · BOUTONS — standard / principal / destructif / désactivé / envoi verrouillé
   ================================================================================================= */
[data-li] .li-btn {
    font-family: var(--font);
    font-size: 13.5px;
    font-weight: 500;
    color: var(--encre);
    background: var(--carte);
    border: 1px solid var(--bord-2);
    border-radius: 9px;
    padding: 7px 15px;
    cursor: pointer;
    transition: background var(--mvt-micro), border-color var(--mvt-micro),
                transform var(--mvt-micro), opacity var(--mvt-micro);
}
[data-li] .li-btn:hover { background: var(--carte-2); }
[data-li] .li-btn:active { transform: scale(.97); }

[data-li] .li-btn--principal { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
[data-li] .li-btn--principal:hover { opacity: .88; background: var(--accent); }

[data-li] .li-btn--destructif { background: var(--rouge); border-color: var(--rouge); color: #FFFFFF; }
[data-li] .li-btn--destructif:hover { opacity: .88; }

[data-li] .li-btn:disabled { opacity: .5; cursor: default; }

/* L'attente honnête : le bouton montre son travail pendant qu'il est verrouillé. */
[data-li] .li-spin {
    display: inline-block;
    width: 13px;
    height: 13px;
    border: 2px solid color-mix(in srgb, var(--accent-fg) 40%, transparent);
    border-top-color: var(--accent-fg);
    border-radius: 50%;
    vertical-align: -2px;
    margin-right: 7px;
    animation: li-rot .8s linear infinite;
}
@keyframes li-rot { to { transform: rotate(360deg); } }

/* =================================================================================================
   6 · CHAMPS — libellé, aide, état fautif
   ================================================================================================= */
[data-li] .li-champ { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
[data-li] .li-champ > label { font-size: 12.5px; font-weight: 600; color: var(--encre-2); }

[data-li] .li-champ input[type=text],
[data-li] .li-champ input[type=email],
[data-li] .li-champ input[type=password],
[data-li] .li-champ input[type=search],
[data-li] .li-champ textarea {
    font-family: var(--font);
    font-size: 13.5px;
    color: var(--encre);
    background: var(--carte);
    border: 1px solid var(--bord-2);
    border-radius: 9px;
    padding: 8px 11px;
    width: 100%;
    transition: border-color var(--mvt-micro), box-shadow var(--mvt-micro);
}
[data-li] .li-champ input:focus,
[data-li] .li-champ textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-doux); }

/* ⚠️ LA ZONE DE TEXTE EST INCLUSE ICI DEPUIS LE #368, ET ELLE MANQUAIT. La feuille transcrivait bien
   « textarea » pour le repos et le focus (l. 256, 268) — la référence les regroupe — mais le halo du
   champ FAUTIF ne visait que « input ». Personne ne l'avait vu parce qu'aucun composant du socle ne
   rendait de zone de texte avant ce lot : une saisie longue en erreur n'aurait donc rien montré. */
[data-li] .li-champ--fautif input,
[data-li] .li-champ--fautif textarea { border-color: var(--rouge); box-shadow: 0 0 0 3px var(--rouge-doux); }

[data-li] .li-aide { font-size: 12px; color: var(--encre-3); }

/* =================================================================================================
   7 · VOILE, DIALOGUE, ALERTE
   ================================================================================================= */
[data-li] .li-voile {
    position: fixed;
    inset: 0;
    background: var(--voile);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    display: none;
    align-items: flex-start;
    justify-content: center;
    z-index: var(--z-modale);
}
[data-li] .li-voile--ouvert { display: flex; }
[data-li] .li-voile--niveau2 { z-index: var(--z-alerte); }

[data-li] .li-dialogue {
    background: var(--carte);
    border: 1px solid var(--bord);
    border-radius: 16px;
    box-shadow: var(--ombre-3);
    margin-top: 10vh;
    display: flex;
    flex-direction: column;
    max-height: 85vh;
    overflow: hidden;
    width: var(--palier-m);
    max-width: var(--palier-etendue);
    animation: li-dlg 220ms var(--c-std);
}

/* ⚠️ ÉCART §C4 ASSUMÉ, CONSIGNÉ AU COMPTE RENDU : le palier S du contrat vaut 420 px (--palier-s), mais
   l'alerte de la RÉFÉRENCE se rend à 430 px. La référence fait foi pour le rendu — la valeur est donc en
   dur ici, VISIBLE, plutôt que réconciliée en silence derrière le token. */
[data-li] .li-dialogue--s { width: 430px; }
[data-li] .li-dialogue--l { width: var(--palier-l); }

/* Corps d'une alerte destructive : la conséquence, dite franchement, en second plan visuel — c'est le
   TITRE qui pose la question, le message n'en donne que le prix. */
[data-li] .li-alerte-message { margin: 2px 0 0; font-size: 13.5px; color: var(--encre-2); }

@keyframes li-dlg {
    from { transform: scale(1.08); opacity: 0; }
    to   { transform: none; opacity: 1; }
}

[data-li] .li-dlg-tete { padding: 20px 24px 6px; flex: none; }
[data-li] .li-dlg-tete h4 { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -.2px; }

/* Seul le CORPS défile — en-tête et pied restent visibles, jamais poussés hors cadre. Et jamais
   horizontalement : c'est ce qui tient « zéro ascenseur parasite ». */
[data-li] .li-dlg-corps {
    padding: 10px 24px 20px;
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

[data-li] .li-dlg-pied {
    padding: 14px 24px;
    border-top: 1px solid var(--bord);
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    flex: none;
    background: var(--carte-2);
}

/* Message d'erreur en TÊTE de corps.
   ⚠️ ÉCART D'IMPLÉMENTATION CONSIGNÉ : la référence le laisse en place à opacity:0 et le révèle par
   transition. Ici il est MONTÉ À LA DEMANDE. Un élément présent mais transparent reste « visible » pour
   les outils d'accessibilité ET pour Playwright : le laisser en place rendrait vert un test qui vérifie
   qu'un message d'erreur s'affiche, même quand il n'y en a aucun. Le fondu est conservé à l'apparition. */
[data-li] .li-dlg-err {
    font-size: 12.5px;
    color: var(--rouge);
    animation: li-fondu 200ms var(--c-std);
}

/* =================================================================================================
   8 · FEUILLE — elle glisse depuis la barre de la fenêtre
   ================================================================================================= */
[data-li] .li-f-voile {
    position: absolute;
    inset: 46px 0 0 0;
    background: var(--voile);
    display: none;
    justify-content: center;
    align-items: flex-start;
    z-index: 5;
}
[data-li] .li-f-voile--ouvert { display: flex; }

/* La feuille d'un ÉCRAN DE PRODUCTION est attachée à la fenêtre du NAVIGATEUR (#354, SL2), là où celle du
   bac-à-sable l'est à une mini-fenêtre dessinée dans la page. Trois écarts, tous mesurés :
     · « fixed » et non « absolute » — sans ancêtre positionné à portée, « absolute » s'accroche au premier
       venu et le voile atterrit n'importe où (le même défaut silencieux que la racine [data-li] manquante) ;
     · palier MODALE et non 5 — l'ouverture d'une boîte abaisse le châssis à 900 ; une feuille à 5 s'ouvrirait
       DERRIÈRE la barre et le tiroir, ce qui est précisément ce que l'abaissement voulait éviter ;
     · « overflow: hidden » — c'est la mini-fenêtre qui CLIPPAIT la feuille pendant son glissement ; sans
       cadre, la feuille passerait par-dessus la barre pendant les 350 ms de son entrée.
   Tout le reste de la feuille est INCHANGÉ : même largeur, même glissement, même sortie. */
[data-li] .li-f-voile--fenetre {
    position: fixed;
    inset: 0;
    z-index: var(--z-modale);
    overflow: hidden;
}

[data-li] .li-feuille {
    background: var(--carte);
    width: min(400px, 92%);
    border: 1px solid var(--bord);
    border-top: 0;
    border-radius: 0 0 15px 15px;
    box-shadow: var(--ombre-2);
    display: flex;
    flex-direction: column;
    max-height: 88%;
    overflow: hidden;
    animation: li-feuille-entree var(--mvt-entree) var(--c-feuille);
}
@keyframes li-feuille-entree {
    from { transform: translateY(-104%); }
    to   { transform: none; }
}
[data-li] .li-feuille--sortie { animation: li-feuille-sortie var(--mvt-sortie) var(--c-sortie) forwards; }
@keyframes li-feuille-sortie { to { transform: translateY(-104%); } }

/* =================================================================================================
   9 · TOASTS — empilés en haut à droite, avec leur action « Annuler »
   ================================================================================================= */
[data-li] .li-toasts {
    position: fixed;
    top: 18px;
    right: 18px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    z-index: var(--z-toast);
}
[data-li] .li-toast {
    display: flex;
    align-items: center;
    gap: 14px;
    background: var(--encre);
    color: var(--fond);
    font-size: 13px;
    border-radius: 11px;
    padding: 11px 16px;
    box-shadow: var(--ombre-2);
    animation: li-toast-entree 320ms var(--c-feuille);
}
[data-li] .li-toast button {
    border: 0;
    background: transparent;
    color: inherit;
    text-decoration: underline;
    font-family: var(--font);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 0;
}
@keyframes li-toast-entree {
    from { transform: translateX(120%); opacity: 0; }
    to   { transform: none; opacity: 1; }
}

/* =================================================================================================
   10 · LA SECOUSSE DE REFUS
   Déclarée APRÈS les boîtes : à spécificité égale, c'est l'ordre qui décide, et le refus doit
   l'emporter sur l'animation d'entrée de la boîte qu'il secoue.
   ================================================================================================= */
@keyframes li-secousse {
    0%   { transform: translateX(0); }
    15%  { transform: translateX(-12px); }
    30%  { transform: translateX(10px); }
    45%  { transform: translateX(-8px); }
    60%  { transform: translateX(6px); }
    75%  { transform: translateX(-3px); }
    100% { transform: translateX(0); }
}
[data-li] .li-refuse { animation: li-secousse var(--mvt-secousse) ease-out; }

/* Fondus — utilisés à l'apparition des messages, et en remplacement des mouvements en apparence réduite. */
@keyframes li-fondu { from { opacity: 0; } to { opacity: 1; } }
@keyframes li-fondu-sortie { to { opacity: 0; } }

/* =================================================================================================
   11 · MOUVEMENT RÉDUIT — plus rien ne bouge, mais tout reste lisible

   Va plus loin que la référence, qui se contente de ramener les durées à 1 ms : une translation de
   1 ms reste une translation. Ici les entrées deviennent de VRAIS fondus (aucune transformation dans
   les keyframes), l'enfoncement des boutons ne réduit plus l'échelle, et la secousse cède la place à
   un halo rouge. Surensemble assumé du contrat §C5, consigné au compte rendu.
   ================================================================================================= */
@media (prefers-reduced-motion: reduce) {
    [data-li] .li-dialogue { animation-name: li-fondu; }
    [data-li] .li-feuille { animation-name: li-fondu; }
    [data-li] .li-feuille--sortie { animation-name: li-fondu-sortie; }
    [data-li] .li-toast { animation-name: li-fondu; }
    [data-li] .li-btn:active { transform: none; }

    /* Déclaré en dernier : à spécificité égale, le refus l'emporte sur l'entrée de la boîte. */
    [data-li] .li-refuse {
        animation: none;
        outline: 3px solid var(--rouge);
        outline-offset: 2px;
    }
}

/* =================================================================================================
   12 · CONTRÔLES §2 — BASCULES ET VALEURS (#368, SL3)

   Transcription du §2 de la référence contractuelle (l. 88-90 et 110-156). C'est ELLE qui fait foi :
   chaque valeur ci-dessous en vient, y compris les durées littérales (200 ms, 220 ms) que le contrat
   n'a pas tokenisées — on ne les remplace PAS par un token du socle, sous peine de faire diverger la
   feuille de sa référence sans que rien ne le dise.

   ⚠️ POURQUOI CHAQUE CONTRÔLE ENVELOPPE UNE SAISIE NATIVE INVISIBLE (opacity:0) plutôt qu'un <div>
   stylé : le natif apporte gratuitement le focus clavier, la barre d'espace, le rattachement au
   <label> (donc une zone de clic qui couvre le libellé), l'état désactivé et l'annonce au lecteur
   d'écran. Le dépôt a déjà mesuré le prix de l'inverse — cinq div[role="button"] du châssis
   consomment la barre d'espace sans que personne ne l'ait voulu (#368).

   ⚠️ « .li-ligne-ctrl » et « .li-seg » ARRIVENT DE LA SECTION 12 (décor du bac-à-sable), sans une
   valeur changée. Ils y étaient légitimes tant que le segmenté n'était que la bascule d'apparence de
   la galerie ; ils ne le sont plus depuis que le contrat les prescrit comme contrôles à part entière.
   ================================================================================================= */

/* Ligne de contrôle — l'enveloppe commune : la bascule, puis son libellé, cliquables ensemble. */
[data-li] .li-ligne-ctrl { display: flex; align-items: center; gap: 11px; font-size: 13.5px; padding: 7px 0; }

/* Filet de séparation entre deux groupes de contrôles d'une même carte (référence l. 70). */
[data-li] hr.li-fine { border: 0; border-top: 1px solid var(--bord); margin: 18px 0; }

/* Interrupteur — DEUX courbes distinctes, à dessein : la piste change de couleur (200 ms, --c-std),
   la poulie glisse (220 ms, --c-feuille). Le léger décalage EST le ressort ; les unifier l'aplatit. */
[data-li] .li-inter { position: relative; display: inline-block; width: 42px; height: 25px; flex: none; }
[data-li] .li-inter input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
[data-li] .li-inter i {
    position: absolute;
    inset: 0;
    background: var(--bord-2);
    border-radius: 999px;
    transition: background 200ms var(--c-std);
}
[data-li] .li-inter i::after {
    content: "";
    position: absolute;
    top: 2.5px;
    left: 2.5px;
    width: 20px;
    height: 20px;
    background: var(--carte);
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
    transition: transform 220ms var(--c-feuille);
}
[data-li] .li-inter input:checked + i { background: var(--accent); }
[data-li] .li-inter input:checked + i::after { transform: translateX(17px); }

/* Case à cocher — et son état MIXTE, le seul moyen honnête de dire « une partie est cochée ». */
[data-li] .li-coche { position: relative; width: 17px; height: 17px; flex: none; }
[data-li] .li-coche input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
[data-li] .li-coche i {
    position: absolute;
    inset: 0;
    border: 1.5px solid var(--bord-2);
    border-radius: 5px;
    background: var(--carte);
    transition: all var(--mvt-micro);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--accent-fg);
    font-size: 11px;
    font-weight: 700;
}
[data-li] .li-coche input:checked + i { background: var(--accent); border-color: var(--accent); }
[data-li] .li-coche input:checked + i::after { content: "✓"; }
[data-li] .li-coche.li-mixte i { background: var(--accent); border-color: var(--accent); }
[data-li] .li-coche.li-mixte i::after { content: "–"; font-size: 13px; margin-top: -1px; }

/* Radio — l'état coché n'ajoute AUCUN élément : il épaissit la bordure à 5.5 px, et le « point »
   qu'on croit voir est le fond de la carte laissé au centre (17 − 2 × 5.5 = 6 px). */
[data-li] .li-radio { position: relative; width: 17px; height: 17px; flex: none; }
[data-li] .li-radio input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
[data-li] .li-radio i {
    position: absolute;
    inset: 0;
    border: 1.5px solid var(--bord-2);
    border-radius: 50%;
    background: var(--carte);
    transition: all var(--mvt-micro);
}
[data-li] .li-radio input:checked + i { border: 5.5px solid var(--accent); }

/* Curseur — la largeur FIXE de l'afficheur et les chiffres tabulaires ne sont pas cosmétiques :
   sans eux, passer de « 9 % » à « 10 % » élargit le texte, rétrécit la piste, et DÉPLACE la poignée
   sous le doigt qui la tient. */
[data-li] .li-curseur { display: flex; align-items: center; gap: 12px; }
[data-li] .li-curseur input[type=range] { flex: 1; accent-color: var(--accent); height: 22px; }
[data-li] .li-curseur output {
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--encre-2);
    width: 44px;
    text-align: right;
}
[data-li] .li-gradu {
    display: flex;
    justify-content: space-between;
    font-size: 10.5px;
    color: var(--encre-3);
    padding: 0 2px;
    margin-top: -4px;
}

/* Stepper — les flèches sont du TEXTE (▲▼), pas des icônes : le socle n'a pas le droit de dépendre
   d'une bibliothèque d'icônes (#353). À ne pas confondre avec « MudStepper », qui est un assistant
   à étapes et n'a rien à voir (homonymie mesurée et nommée au #368). */
/* ⚠️ « align-self: flex-start » N'EST PAS UN DÉTAIL DE MISE EN PAGE — mesuré au #368 après coup.
   « .li-champ » est un conteneur flex en COLONNE : ses enfants s'étirent sur toute la largeur par défaut,
   « inline-flex » compris. Un stepper posé dans une colonne large s'étalait donc sur 377 px pour deux
   chiffres, là où la référence le veut à sa largeur PROPRE (56 px de saisie + 22 de flèches). Le défaut
   était déjà présent sur les trois boîtes converties avant ce jour — invisible parce que leurs colonnes
   étaient étroites. Il ne s'est vu qu'en posant le même contrôle dans une colonne pleine. */
[data-li] .li-stepper {
    align-self: flex-start;
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--bord-2);
    border-radius: 9px;
    overflow: hidden;
    background: var(--carte);
}
/* ⚠️ « input[type=text] » ET NON « input » — question de SPÉCIFICITÉ, mesurée au #368 et antérieure.
   « [data-li] .li-champ input[type=text] » pèse 0,2,1 et impose « width: 100% ». Écrit
   « [data-li] .li-stepper input » (0,1,1), ce bloc PERDAIT : la saisie du stepper s'étirait à toute la
   largeur de sa colonne — 168 px mesurés — au lieu des 56 px du contrat. Le défaut vivait depuis SL1 et
   ne s'était jamais vu, parce que tous les steppers posés jusqu'ici l'étaient dans des colonnes étroites.
   Le sélecteur qualifié met les deux blocs à égalité (0,2,1) ; l'ordre du fichier tranche, et c'est
   celui-ci qui vient en dernier. */
[data-li] .li-stepper input[type=text] {
    width: 56px;
    border: 0;
    text-align: center;
    font-family: var(--font);
    font-size: 13.5px;
    color: var(--encre);
    background: transparent;
    font-variant-numeric: tabular-nums;
}
[data-li] .li-stepper input:focus { outline: none; }
[data-li] .li-fl { display: flex; flex-direction: column; border-left: 1px solid var(--bord); }
[data-li] .li-fl button {
    border: 0;
    background: var(--carte-2);
    color: var(--encre-2);
    width: 22px;
    flex: 1;
    cursor: pointer;
    font-size: 9px;
    line-height: 1;
    padding: 0;
}
[data-li] .li-fl button:hover { color: var(--encre); }
[data-li] .li-fl button:first-child { border-bottom: 1px solid var(--bord); }

/* ⚖️ ÉTAT DÉSACTIVÉ — SURENSEMBLE ASSUMÉ DE LA RÉFÉRENCE, consigné (comme la section 11).

   🔴 POURQUOI ON AJOUTE CE QUE LE CONTRAT NE DIT PAS. La référence §2 ne montre AUCUN contrôle
   désactivé : elle n'en décrit donc pas l'apparence. Transcrite au pied de la lettre, un interrupteur
   inerte se peint EXACTEMENT comme un interrupteur actif — mesuré à l'écran sur la galerie du #368,
   et c'est précisément le défaut que les standards UX du dépôt interdisent (§5 « aucun message ne
   ment », §10 « prévention plutôt que correction ») : un contrôle qui a l'air actionnable et ne
   l'est pas fait cliquer dans le vide, puis douter de l'écran.

   La règle est donc UNE seule, posée sur la ligne de contrôle et sur les enveloppes : atténuation +
   curseur d'interdiction. Aucune couleur nouvelle, aucun token inventé — l'inertie se lit, elle ne
   crie pas. L'écart est ici, daté, plutôt que réconcilié en silence. */
[data-li] .li-ligne-ctrl:has(input:disabled),
[data-li] .li-curseur:has(input:disabled),
[data-li] .li-stepper:has(input:disabled),
[data-li] .li-seg:has(button:disabled) { opacity: .45; }

[data-li] .li-inter input:disabled,
[data-li] .li-coche input:disabled,
[data-li] .li-radio input:disabled,
[data-li] .li-curseur input:disabled,
[data-li] .li-fl button:disabled,
[data-li] .li-seg button:disabled { cursor: not-allowed; }

/* ⚖️ QUANTITÉ DÉCIMALE — ÉCART À LA RÉFÉRENCE, consigné (voir l'en-tête de ChampQuantite.razor).
   Le contrat ne se tait pas ici : il dit AUTRE CHOSE — son stepper est entier par construction. Deux
   écrans du produit estiment des heures en décimal, et les convertir aurait tronqué une demi-heure en
   silence. La peau est celle du stepper, au caractère près : même bordure, même rayon, mêmes flèches.
   Seules la largeur (une décimale prend de la place) et l'unité intégrée diffèrent. */
[data-li] .li-quantite {
    align-self: flex-start;
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--bord-2);
    border-radius: 9px;
    overflow: hidden;
    background: var(--carte);
}
[data-li] .li-quantite input[type=text] {
    width: 74px;
    border: 0;
    text-align: right;
    font-family: var(--font);
    font-size: 13.5px;
    color: var(--encre);
    background: transparent;
    font-variant-numeric: tabular-nums;
    padding: 0 2px 0 8px;
}
[data-li] .li-quantite input:focus { outline: none; }
[data-li] .li-quantite-unite {
    display: flex;
    align-items: center;
    padding: 0 8px 0 3px;
    font-size: 12.5px;
    color: var(--encre-3);
}
[data-li] .li-quantite:has(input:disabled) { opacity: .45; }
[data-li] .li-quantite input:disabled { cursor: not-allowed; }

/* Contrôle segmenté — le segment actif est POSÉ (fond carte + ombre), jamais coloré à l'accent :
   c'est ce qui le distingue d'un groupe de boutons. On n'y lit pas une action à déclencher, mais
   un état où l'on se trouve. */
[data-li] .li-seg {
    display: inline-flex;
    background: var(--carte-2);
    border: 1px solid var(--bord);
    border-radius: 10px;
    padding: 3px;
    gap: 2px;
}
[data-li] .li-seg button {
    font-family: var(--font);
    font-size: 13px;
    font-weight: 500;
    color: var(--encre-2);
    border: 0;
    background: transparent;
    border-radius: 7px;
    padding: 5px 14px;
    cursor: pointer;
    transition: all var(--mvt-micro);
}
[data-li] .li-seg button.li-on { background: var(--carte); color: var(--encre); box-shadow: var(--ombre-1); }

/* =================================================================================================
   13 · CONTRÔLES §2 — CHOIX, TEXTE ET TEMPS : LES FLOTTANTS (#368, SL3)

   Transcription du §2 de la référence (l. 158-230). Quatre objets partagent la même matière — verre
   translucide, bord, ombre-2, rayon, et la même entrée « menu-pop » — parce que ce sont quatre
   variantes d'une même idée : une surface qui se pose PAR-DESSUS, le temps d'un choix.

   ⚠️ L'EMPILEMENT CONSOMME ENFIN SON TOKEN. La référence écrit « z-index:600 » en dur ; le socle avait
   déclaré « --z-menu: 600 » dès le #353 et ne le consommait NULLE PART (mesuré au #368 : quatre tokens
   d'empilement déclarés, zéro consommateur). La valeur est identique, la source devient unique.

   ⚠️ CE QUI FERME UN FLOTTANT, ET OÙ CHAQUE MOITIÉ VIT. Le clic à côté est en JS — c'est le seul
   endroit d'où l'on voit un clic qui ne traverse aucun gestionnaire Blazor (voir « surveillerFlottant »).
   Échap, lui, reste en .NET : mesuré au #368, le veilleur du socle n'intercepte QUE « Entrée dans une
   zone de texte » et « Tab » — Échap parvient donc intact à sa cible. Le flottant l'arrête sur son
   enveloppe (« @onkeydown:stopPropagation »), et la boîte qui l'héberge ne le voit jamais : sans cela,
   un Échap destiné à fermer une liste fermerait le dialogue derrière, emportant la saisie.
   ================================================================================================= */

/* Enveloppe déclencheur + panneau : c'est ELLE qui délimite le « dedans » (clic et clavier). */
[data-li] .li-declencheur { position: relative; display: inline-block; }

/* Le bouton d'un menu pop-up : la valeur choisie à gauche, le chevron à droite, largeur stable. */
[data-li] .li-popup-btn { display: inline-flex; align-items: center; gap: 9px; min-width: 170px; justify-content: space-between; }
[data-li] .li-chevron { font-size: 10px; color: var(--encre-3); }

[data-li] .li-menu {
    position: absolute;
    min-width: 180px;
    background: var(--verre);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid var(--bord);
    border-radius: 12px;
    box-shadow: var(--ombre-2);
    padding: 5px;
    z-index: var(--z-menu);
    animation: li-menu-pop 160ms var(--c-std);
}
@keyframes li-menu-pop {
    from { opacity: 0; transform: scale(.97) translateY(-3px); }
    to { opacity: 1; transform: none; }
}
[data-li] .li-menu button {
    display: flex;
    width: 100%;
    align-items: center;
    gap: 9px;
    border: 0;
    background: transparent;
    font-family: var(--font);
    font-size: 13.5px;
    color: var(--encre);
    border-radius: 8px;
    padding: 7px 11px;
    cursor: pointer;
    text-align: left;
}
[data-li] .li-menu button:hover { background: var(--accent); color: var(--accent-fg); }
[data-li] .li-menu button.li-rouge { color: var(--rouge); }
[data-li] .li-menu button.li-rouge:hover { background: var(--rouge); color: #FFF; }
[data-li] .li-menu .li-sep { height: 1px; background: var(--bord); margin: 5px 8px; }
[data-li] .li-menu .li-v { width: 14px; text-align: center; flex: none; }

/* ⚖️ ÉTAT VIDE D'UN MENU — SURENSEMBLE ASSUMÉ, consigné (comme l'état désactivé de la section 12).

   🔴 La référence ne montre jamais un menu sans éléments : elle n'en décrit donc pas l'apparence, et une
   transcription littérale rend une carte de verre VIDE de 180 px, sans un mot. L'utilisateur a cliqué,
   quelque chose s'est ouvert, et rien ne s'y trouve : il ne sait pas si c'est en train de charger, si
   c'est cassé, ou s'il n'y a réellement rien. Les standards du dépôt tranchent — un état vide n'est
   jamais un blanc muet (§E7), et un écran ne ment pas (§5).
   La référence DIT pourtant comment le peindre : elle a « .suggestions .vide » (l. 215) pour la
   recherche sans résultat. On reprend exactement cette encre et ces retraits — l'écart porte sur le
   PÉRIMÈTRE d'application, pas sur l'apparence. */
[data-li] .li-menu .li-vide,
[data-li] .li-suggestions .li-vide { font-size: 12.5px; color: var(--encre-3); padding: 7px 11px; }

/* --- Popover — la même matière que le menu, mais un CONTENU, pas une liste (référence l. 174-182). --- */
[data-li] .li-ancre { position: relative; display: inline-block; }
[data-li] .li-popover {
    position: absolute;
    top: calc(100% + 11px);
    left: 50%;
    transform: translateX(-50%);
    background: var(--verre);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid var(--bord);
    border-radius: 14px;
    box-shadow: var(--ombre-2);
    padding: 16px;
    z-index: var(--z-menu);
    width: 280px;
    animation: li-menu-pop 180ms var(--c-std);
}
[data-li] .li-popover::before {
    content: "";
    position: absolute;
    top: -6px;
    left: 50%;
    width: 11px;
    height: 11px;
    transform: translateX(-50%) rotate(45deg);
    background: var(--verre);
    border-left: 1px solid var(--bord);
    border-top: 1px solid var(--bord);
}
[data-li] .li-popover h5 { margin: 0 0 10px; font-size: 13px; font-weight: 600; }

/* La flèche est dessinée au milieu du PANNEAU. Dès que le panneau cesse d'être centré sur son ancre,
   elle pointe autre chose — donc on la retire : une flèche qui désigne le vide est pire qu'aucune.
   La replacer exactement suppose de mesurer l'ancre après rendu (JS) — même lot d'outillage que le
   recentrage au bord de fenêtre. */
[data-li] .li-popover.li-sans-fleche::before { content: none; }

/* --- Champ à jetons — « Entrée crée le jeton · Retour arrière sur champ vide retire le dernier » (l. 192-200). --- */
[data-li] .li-jetons {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    border: 1px solid var(--bord-2);
    border-radius: 9px;
    background: var(--carte);
    padding: 6px 8px;
    cursor: text;
}
[data-li] .li-jetons:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-doux); }
[data-li] .li-jeton {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--accent-doux);
    color: var(--encre);
    font-size: 12.5px;
    font-weight: 500;
    border-radius: 7px;
    padding: 3px 8px;
    animation: li-menu-pop 150ms var(--c-std);
}
[data-li] .li-jeton b { cursor: pointer; font-weight: 600; opacity: .6; }
[data-li] .li-jeton b:hover { opacity: 1; }
[data-li] .li-jetons input {
    border: 0;
    background: transparent;
    font-family: var(--font);
    font-size: 13.5px;
    color: var(--encre);
    flex: 1;
    min-width: 90px;
    padding: 3px 2px;
}
[data-li] .li-jetons input:focus { outline: none; }

/* ⚖️ CHAMP À JETONS PLEIN — SURENSEMBLE ASSUMÉ, consigné.
   🔴 La référence n'impose aucun plafond, donc ne dit rien de ce qui arrive quand on en atteint un. Un
   champ qui refuserait la frappe sans un mot ferait taper dans le vide — le mode d'échec exact que le
   dépôt refuse (§5, §10). Quand le plafond est atteint, la saisie disparaît et une phrase prend sa
   place : on ne laisse jamais un champ qui n'accepte plus rien AVOIR L'AIR d'accepter. */
[data-li] .li-jetons .li-plein { font-size: 12.5px; color: var(--encre-3); padding: 3px 2px; }

/* --- Recherche + suggestions (l. 202-215). Le ✕ n'apparaît QUE si le champ porte du texte. --- */
[data-li] .li-recherche { position: relative; }
[data-li] .li-recherche .li-loupe {
    position: absolute;
    left: 11px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--encre-3);
    font-size: 13px;
    pointer-events: none;
}
[data-li] .li-recherche input { padding-left: 32px !important; }
[data-li] .li-recherche .li-efface {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    border: 0;
    background: var(--bord-2);
    color: var(--carte);
    width: 17px;
    height: 17px;
    border-radius: 50%;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    display: none;
}
[data-li] .li-recherche input:not(:placeholder-shown) ~ .li-efface { display: block; }
[data-li] .li-suggestions {
    position: absolute;
    top: calc(100% + 7px);
    left: 0;
    right: 0;
    background: var(--verre);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid var(--bord);
    border-radius: 12px;
    box-shadow: var(--ombre-2);
    padding: 5px;
    z-index: var(--z-menu);
    display: none;
}
[data-li] .li-suggestions.li-ouvert { display: block; }
[data-li] .li-suggestions button {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    font-family: var(--font);
    font-size: 13.5px;
    color: var(--encre);
    border-radius: 8px;
    padding: 7px 11px;
    cursor: pointer;
}
[data-li] .li-suggestions button:hover { background: var(--accent); color: var(--accent-fg); }

/* --- Sélecteur de date — « Aujourd'hui est cerclé, le choix est plein » (l. 217-230). --- */
[data-li] .li-calendrier { width: 266px; }
[data-li] .li-cal-tete { display: flex; align-items: center; justify-content: space-between; margin: 0 0 10px; }
[data-li] .li-cal-tete b { font-size: 13.5px; font-weight: 600; text-transform: capitalize; }
[data-li] .li-cal-tete button {
    border: 0;
    background: transparent;
    color: var(--encre);
    font-size: 14px;
    cursor: pointer;
    border-radius: 6px;
    padding: 2px 8px;
}
[data-li] .li-cal-tete button:hover { background: var(--accent-doux); }
[data-li] .li-cal-grille { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; text-align: center; }
[data-li] .li-cal-grille .li-j {
    font-size: 10.5px;
    font-weight: 600;
    color: var(--encre-3);
    padding: 4px 0;
    text-transform: uppercase;
}
[data-li] .li-cal-grille button {
    border: 0;
    background: transparent;
    font-family: var(--font);
    font-size: 12.5px;
    color: var(--encre);
    border-radius: 8px;
    padding: 6px 0;
    cursor: pointer;
    font-variant-numeric: tabular-nums;
}
[data-li] .li-cal-grille button:hover { background: var(--accent-doux); }
[data-li] .li-cal-grille button.li-autre { color: var(--encre-3); }
[data-li] .li-cal-grille button.li-jour { box-shadow: inset 0 0 0 1.5px var(--accent); }
[data-li] .li-cal-grille button.li-choisi { background: var(--accent); color: var(--accent-fg); }

/* ⚖️ JOUR INTERDIT — SURENSEMBLE ASSUMÉ, consigné.
   🔴 La référence ne montre aucune date hors plage : son calendrier accepte tout. Un produit, non — une
   échéance ne se place pas avant sa date de création, un renouvellement pas dans le passé. Sans état
   visible, la journée refusée se peint comme les autres et le clic ne fait rien : c'est le « bouton muet »
   que le standard UX §1 interdit nommément. On atténue, on barre le curseur, et on retire le survol —
   le refus se lit AVANT le clic, jamais après. */
[data-li] .li-cal-grille button:disabled { opacity: .3; cursor: not-allowed; }
[data-li] .li-cal-grille button:disabled:hover { background: transparent; }

/* =================================================================================================
   13 bis · L'ACCORDÉON — un objet du §4, posé PAR EXCEPTION (#368, SL3)

   ⚖️ EXCEPTION ARBITRÉE PAR LE PILOTAGE, le 12/08/2026, et écrite ici pour que la famille « listes et
   données » sache qu'un de ses objets est DÉJÀ POSÉ quand elle ouvrira.

   Ce qui l'a provoquée : la mesure de la boîte pilote de S2 (BoiteProduitCatalogue) a montré qu'elle
   porte un « MudExpansionPanel » — donc un objet du §4, pas du §2. Le pilotage a tranché : on garde ce
   pilote, parce que c'est le SEUL écran qui réunit un état mixte, un jeton et une valeur effaçable —
   les cas qui font mal. Un pilote facile validerait un patron qui ne prouve rien, et il faudrait tout
   revalider ensuite sur un écran difficile.

   ⚠️ TROIS CONDITIONS, POSÉES AVEC L'EXCEPTION : l'accordéon est traité SEUL — on n'ouvre pas les listes
   en avance ; l'exception est écrite (elle l'est ici et au compte rendu) ; et s'il s'était avéré
   indissociable du reste de sa famille, il fallait s'arrêter et remonter plutôt que tirer le fil.
   Mesuré : il ne l'est pas. Ses 11 déclarations ne dépendent d'aucun autre objet du §4 — ni tableau, ni
   sélection, ni aperçu — et son composant ne partage rien avec eux.

   ⚠️ LE DÉPLIAGE PASSE PAR « grid-template-rows: 0fr → 1fr », pas par une hauteur. C'est ce que fait la
   référence, et c'est ce qui permet d'animer vers une hauteur INCONNUE : une transition sur « height »
   exige de connaître la cible, donc de la mesurer en JS à chaque ouverture.
   ================================================================================================= */
[data-li] .li-accordeon { border: 1px solid var(--bord); border-radius: 12px; overflow: hidden; }
[data-li] .li-accordeon > div + div { border-top: 1px solid var(--bord); }
[data-li] .li-acc-tete {
    display: flex;
    width: 100%;
    align-items: center;
    gap: 10px;
    border: 0;
    background: var(--carte);
    font-family: var(--font);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--encre);
    padding: 13px 16px;
    cursor: pointer;
    text-align: left;
}
[data-li] .li-acc-tete .li-chev { transition: transform 240ms var(--c-std); font-size: 11px; color: var(--encre-3); }
[data-li] .li-acc-ouvert .li-acc-tete .li-chev { transform: rotate(90deg); }
[data-li] .li-acc-corps { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 280ms var(--c-std); }
[data-li] .li-acc-ouvert .li-acc-corps { grid-template-rows: 1fr; }
[data-li] .li-acc-corps > div { overflow: hidden; }
[data-li] .li-acc-corps p { font-size: 13px; color: var(--encre-2); margin: 0; padding: 0 16px 14px; }

/* =================================================================================================
   14 · DÉCOR DU BAC-À-SABLE   (ex-section 12 — renumérotée au #368, qui insère les contrôles §2 avant elle)

   Ce qui suit n'est PAS le socle : c'est la mise en scène de la galerie (page, cartes, contrôle
   segmenté, nuancier, mini-fenêtre). On le garde ici plutôt que dans un CSS isolé de composant pour
   que la galerie consomme exactement les mêmes tokens que les primitives — un décor qui dériverait
   de la palette rendrait la comparaison à la référence trompeuse.
   ================================================================================================= */
/* Hôte du bac-à-sable : colonne pleine hauteur, le pied de page ancré en bas (règle immuable du dépôt),
   la galerie prenant tout le reste. Le pied vit HORS de la racine du socle et n'en consomme aucun token. */
.li-hote-bac { display: flex; flex-direction: column; min-height: 100vh; }
.li-hote-pied { margin-top: auto; }

.li-page { flex: 1 0 auto; }
.li-contenu { max-width: 1080px; margin: 0 auto; padding: 44px 32px 120px; }

[data-li] .li-tete {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    border-bottom: 1px solid var(--bord);
    padding-bottom: 24px;
    margin-bottom: 8px;
}
[data-li] .li-titre { font-size: 27px; font-weight: 600; margin: 0; letter-spacing: -.4px; }
[data-li] .li-sous { font-size: 13.5px; color: var(--encre-2); margin: 6px 0 0; }
[data-li] .li-section { font-size: 20px; font-weight: 600; margin: 58px 0 4px; letter-spacing: -.3px; }
[data-li] .li-section small { font-weight: 400; font-size: 13px; color: var(--encre-3); letter-spacing: 0; }
[data-li] .li-intro { font-size: 14px; color: var(--encre-2); max-width: 78ch; margin: 0 0 18px; }

[data-li] .li-carte {
    background: var(--carte);
    border: 1px solid var(--bord);
    border-radius: 18px;
    padding: 24px;
    margin: 0 0 16px;
    box-shadow: var(--ombre-1);
}
[data-li] .li-etq {
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1.4px;
    color: var(--encre-3);
    margin: 0 0 14px;
}
[data-li] .li-rang { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }
[data-li] .li-compteurs { display: flex; gap: 24px; flex-wrap: wrap; margin-top: 16px; font-size: 12.5px; color: var(--encre-2); }
[data-li] .li-compteurs b { font-variant-numeric: tabular-nums; color: var(--encre); }

/* ⚖️ « .li-ligne-ctrl » et « .li-seg » ONT QUITTÉ CETTE SECTION au #368 (SL3) — voir la section 13.
   Ils y avaient leur place tant que le segmenté ne servait qu'à la bascule d'apparence de la galerie ;
   ils ne l'avaient plus depuis que le §2 de la référence les prescrit comme CONTRÔLES. Déplacés au
   caractère près, sans une valeur changée. */

/* Nuancier — les neuf teintes, appliquées en direct. */
[data-li] .li-nuancier { display: flex; gap: 18px; flex-wrap: wrap; }
[data-li] .li-teinte {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    cursor: pointer;
    border: 0;
    background: transparent;
    font-family: var(--font);
    padding: 2px;
}
[data-li] .li-teinte i {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: block;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
    transition: transform var(--mvt-micro);
}
[data-li] .li-teinte:hover i { transform: scale(1.1); }
[data-li] .li-teinte span { font-size: 11.5px; color: var(--encre-2); }
[data-li] .li-teinte.li-on i { box-shadow: 0 0 0 2px var(--carte), 0 0 0 4.5px var(--accent); }
[data-li] .li-teinte.li-on span { color: var(--encre); font-weight: 600; }

/* Pastilles du nuancier : chaque teinte montre SA couleur, pas l'accent courant. */
[data-li] .li-sw-anthracite i { background: #1B1C1E; }
[data-li][data-li-theme="sombre"] .li-sw-anthracite i { background: #F2F2F4; }
[data-li] .li-sw-argent i { background: #6E7278; }
[data-li][data-li-theme="sombre"] .li-sw-argent i { background: #C6C9CE; }
[data-li] .li-sw-bleu i { background: #0A66E8; }
[data-li][data-li-theme="sombre"] .li-sw-bleu i { background: #3D8BFF; }
[data-li] .li-sw-bleuprofond i { background: #1D4E9E; }
[data-li][data-li-theme="sombre"] .li-sw-bleuprofond i { background: #5B8AE0; }
[data-li] .li-sw-vert i { background: #1E7A34; }
[data-li][data-li-theme="sombre"] .li-sw-vert i { background: #2FA365; }
[data-li] .li-sw-violet i { background: #6D5BD0; }
[data-li][data-li-theme="sombre"] .li-sw-violet i { background: #8E7BFF; }
[data-li] .li-sw-ambre i { background: #B45309; }
[data-li][data-li-theme="sombre"] .li-sw-ambre i { background: #E8A13C; }
[data-li] .li-sw-or i { background: #A8811C; }
[data-li][data-li-theme="sombre"] .li-sw-or i { background: #EDC85A; }
[data-li] .li-sw-terracotta i { background: #A85B38; }
[data-li][data-li-theme="sombre"] .li-sw-terracotta i { background: #E08A63; }

/* Mini-fenêtre — le cadre d'où glisse la feuille. Position relative : c'est elle que le voile de la
   feuille recouvre, et personne d'autre. */
[data-li] .li-fenetre {
    border: 1px solid var(--bord);
    border-radius: 14px;
    overflow: hidden;
    background: var(--fond);
    position: relative;
    box-shadow: var(--ombre-1);
}
[data-li] .li-f-barre {
    display: flex;
    align-items: center;
    gap: 12px;
    background: var(--verre);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--bord);
    height: 46px;
    padding: 0 14px;
}
[data-li] .li-feux { display: flex; gap: 7px; }
[data-li] .li-feux i { width: 12px; height: 12px; border-radius: 50%; }
[data-li] .li-feux i:nth-child(1) { background: #FF5F57; }
[data-li] .li-feux i:nth-child(2) { background: #FEBC2E; }
[data-li] .li-feux i:nth-child(3) { background: #28C840; }
[data-li] .li-f-titre { font-size: 13px; font-weight: 600; }
[data-li] .li-f-corps { display: flex; min-height: 230px; }
[data-li] .li-f-lat {
    width: 168px;
    background: var(--verre);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-right: 1px solid var(--bord);
    padding: 12px 8px;
    flex: none;
}
[data-li] .li-f-lat button {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    font-family: var(--font);
    font-size: 13px;
    color: var(--encre);
    border-radius: 8px;
    padding: 7px 11px;
    cursor: pointer;
    margin-bottom: 2px;
}
[data-li] .li-f-lat button.li-on { background: var(--accent-doux); color: var(--encre); font-weight: 600; }
[data-li] .li-f-zone { flex: 1; padding: 16px; font-size: 13px; color: var(--encre-2); }

@media (max-width: 720px) {
    [data-li] .li-f-lat { display: none; }
    .li-contenu { padding: 28px 16px 90px; }
}

/* ==============================================================================================
   §4 — LISTES ET DONNÉES : LE TABLEAU (lot #368, SL3, S5)

   Transcription de la référence contractuelle (l. 232-243 pour le CSS, l. 609-624 pour le balisage).
   Le tableau du langage est un VRAI <table> : la référence en emploie un, avec thead/tbody — ce n'est
   donc pas un choix d'implémentation, c'est le contrat.

   ⚖️ TROIS ÉCARTS AU CONTRAT, RATIFIÉS PAR LE PILOTAGE LE 26/08/2026 — ils sont écrits ICI parce que
      c'est ici qu'on les lira :

   ① LE PAGER N'A AUCUN CONTRAT. La référence ne pagine pas : sa démonstration tient cinq lignes.
      Le produit, lui, en a besoin sur 60 de ses 62 listes — et sans pager une liste TRONQUE SANS LE
      DIRE. C'est donc une EXTENSION ASSUMÉE, décidée sur la mesure, et non une transcription.
      Elle est dessinée avec les seuls tokens du contrat, pour ne pas inventer un second vocabulaire.

   ② LE TRI EST À UNE SEULE COLONNE, et c'est le contrat qui le dit : le JS de la référence efface
      TOUTES les autres flèches avant de poser la sienne (l. 993). Le tri MULTIPLE de la grille
      précédente n'est pas porté — fonction dormante, aucun usage d'empilement attesté sur les 62
      écrans. Si un écran en avait besoin un jour, ce serait une DEMANDE PRODUIT, pas une régression.

   ③ « .li-actions-survol » EST TRANSCRITE MAIS LA GRILLE DU PRODUIT NE L'EMPLOIE PAS. La référence
      cache les actions de ligne jusqu'au survol (opacité 0). Sur un produit, une action qui n'existe
      qu'au survol est INATTEIGNABLE au clavier et au doigt — §10 des standards, qui est un invariant
      du dépôt. La règle est donc transcrite (et la galerie la montre, pour qu'elle soit éprouvée),
      mais les actions des listes restent VISIBLES.
   ============================================================================================== */

[data-li] .li-tableau { width: 100%; border-collapse: collapse; font-size: 13.5px; }

[data-li] .li-tableau th {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .9px;
    color: var(--encre-3);
    text-align: left;
    padding: 9px 12px;
    border-bottom: 1px solid var(--bord);
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
[data-li] .li-tableau th:hover { color: var(--encre); }

/* ⚠️ ÉCART : la référence n'a QUE des colonnes triables — toutes ses « th » portent « cursor:pointer ».
   Le produit en a d'autres (actions, sélection, colonnes déclarées non triables) : leur laisser la main
   du contrat annoncerait un tri qui n'arrivera pas. Un curseur qui ment est un défaut d'interface. */
[data-li] .li-tableau th.li-tab-fige { cursor: default; }
[data-li] .li-tableau th.li-tab-fige:hover { color: var(--encre-3); }

[data-li] .li-tableau td { padding: 9px 12px; border-bottom: 1px solid var(--bord); }
[data-li] .li-tableau tbody tr { cursor: default; transition: background var(--mvt-micro); }
[data-li] .li-tableau tbody tr:hover { background: var(--carte-2); }
[data-li] .li-tableau tbody tr.sel { background: var(--accent-doux); }
[data-li] .li-tableau .num { text-align: right; font-variant-numeric: tabular-nums; }

/* Ligne cliquable — le produit ouvre une fiche au clic. La référence ne le décrit pas (ses lignes
   portent « cursor:default ») : écart minime, assumé, et il ne coûte qu'un curseur. */
[data-li] .li-tableau tbody tr.li-tab-cliquable { cursor: pointer; }

[data-li] .li-actions-survol {
    opacity: 0;
    transition: opacity var(--mvt-micro);
    display: flex;
    gap: 4px;
    justify-content: flex-end;
}
[data-li] tr:hover .li-actions-survol { opacity: 1; }
[data-li] li:hover .li-actions-survol { opacity: 1; }

/* Actions de ligne du PRODUIT : même géométrie, TOUJOURS visibles (écart ③ ci-dessus). */
[data-li] .li-actions-ligne { display: flex; gap: 4px; justify-content: flex-end; }

[data-li] .li-btn-ico {
    border: 0;
    background: transparent;
    color: var(--encre-2);
    border-radius: 6px;
    width: 26px;
    height: 26px;
    cursor: pointer;
    font-size: 13px;
}
[data-li] .li-btn-ico:hover { background: var(--bord); color: var(--encre); }
[data-li] .li-btn-ico:disabled { opacity: .5; cursor: default; }
[data-li] .li-btn-ico:disabled:hover { background: transparent; color: var(--encre-2); }

[data-li] .li-compte-sel { font-size: 12.5px; color: var(--encre-2); margin-top: 10px; }

/* ⭐ HORS ÉCRAN — visible des seuls lecteurs d'écran (« LibelleMasque » de la case).
   ⚠️ Ni « display:none » ni « visibility:hidden » : les deux retirent l'élément de l'ARBRE D'ACCESSIBILITÉ,
   c'est-à-dire exactement ce qu'on cherche à préserver. Le rectangle d'un pixel écrêté est le seul procédé
   qui laisse le texte annoncé tout en le retirant de l'écran. */
[data-li] .li-hors-ecran {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---- EXTENSION ASSUMÉE ① : le pied de tableau (pager). Aucun contrat — voir l'en-tête. ---- */
[data-li] .li-tab-pied {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 14px;
    padding: 10px 12px 0;
    font-size: 12.5px;
    color: var(--encre-2);
}
[data-li] .li-tab-pied-lignes { display: flex; align-items: center; gap: 8px; }
[data-li] .li-tab-pied-nav { display: flex; align-items: center; gap: 2px; }

/* Barre d'actions groupées — visible quand une sélection existe. Même famille que « .li-compte-sel ». */
[data-li] .li-tab-groupe {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 0 0 10px;
    font-size: 12.5px;
    color: var(--encre-2);
}

/* En-tête de tableau : la zone qui porte la recherche intégrée, à droite. */
[data-li] .li-tab-tete { display: flex; align-items: center; gap: 12px; padding: 0 0 10px; }
[data-li] .li-tab-tete-fin { margin-inline-start: auto; }

/* ==============================================================================================
   GARDE DE DÉVELOPPEMENT — la boîte rendue SANS ancêtre [data-li].

   ⚠️ CETTE RÈGLE EST LA SEULE DE TOUTE LA FEUILLE À NE PAS DESCENDRE DE « [data-li] », et c'est
   NÉCESSAIRE : elle doit précisément atteindre les éléments qui n'ont PAS cet ancêtre. Ne pas la
   « corriger » en la préfixant — ce serait la rendre inopérante par construction.

   Elle n'est posée que par « verifierRacine » en développement (classe ajoutée depuis le JS) ; en
   production, la classe n'apparaît jamais. Le défaut qu'elle rend visible est autrement SILENCIEUX :
   la boîte s'ouvre et fonctionne, mais son voile reste dans le flux au lieu de couvrir la fenêtre.
   ============================================================================================== */
.li-sans-racine {
    outline: 3px dashed #E5484D;
    outline-offset: 2px;
    position: relative;
}
.li-sans-racine::before {
    content: "⚠ Boîte du langage sans racine [data-li] — voir la console";
    position: absolute;
    inset-inline: 0;
    top: -1.6em;
    font: 600 12px/1.4 system-ui, sans-serif;
    color: #E5484D;
    text-align: center;
}
