/*
 * LA COQUILLE APPLICATIVE — visuel de la maquette validee par le responsable produit, a l'identique.
 *
 * ── POURQUOI CE FICHIER N'ETEND PAS base.css ────────────────────────────────
 *
 * `base.css` porte une palette SOMBRE (--bg: #10141a). La maquette est CLAIRE (#F6F5F2,
 * texte #16224A). Les superposer serait une guerre de specificite ou le dernier fichier
 * charge gagne. base.css reste la feuille des pages d'AMORCAGE (connexion, erreurs,
 * explorateur) ; celle-ci est la feuille de l'APPLICATION.
 *
 * ── AUCUNE POLICE EXTERNE, ET C'EST LA CSP QUI L'IMPOSE ─────────────────────
 *
 * La maquette charge « IBM Plex Sans » depuis fonts.googleapis.com ; la CSP du projet
 * (`style-src 'self'`) BLOQUE la requete. La police est donc AUTO-HEBERGEE
 * (public/assets/fonts/, six woff2 officiels, accord du responsable produit du 05/08/2026) et declaree
 * en @font-face ci-dessous — la CSP n'a pas bouge d'un caractere.
 *
 * ── AUCUN style= EN LIGNE : la maquette en etait pleine, la CSP les refuse tous. ──
 *
 * ── CACHE : PLUS AUCUN PARAMETRE DE VERSION, ET C'EST UNE DECISION ──────────
 *
 * Le parametre de version qui suivait autrefois ce chemin dans les gabarits a ete
 * RETIRE de tout le projet le 10/08/2026 sur decision du responsable produit. La phrase qui vivait ici
 * demandait de l'incrementer : elle etait doublement fausse, le parametre n'existe plus
 * et cette feuille a desormais plus d'un consommateur.
 *
 * CONSEQUENCE MESUREE SUR LE SERVEUR, pas supposee : les actifs statiques sortent avec
 * `Cache-Control: max-age=3600, must-revalidate, public` (releve le 11/08/2026 sur
 * `/assets/css/app.css`). Une feuille retouchee peut donc rester en cache jusqu'a UNE
 * HEURE, et un rechargement FORCE est necessaire pour la voir. C'est une consequence
 * assumee : qui voit un style qui « ne s'applique pas » a son explication ici.
 * Piege mesure en plus : fermer le navigateur et renavigateur ne suffit PAS toujours —
 * servir depuis une AUTRE origine (un autre port) est le contournement, la cle de cache
 * incluant le port. Il n'ajoute rien au depot.
 *
 * ── QUI CHARGE CETTE FEUILLE AUJOURD'HUI — TROIS GABARITS ───────────────────
 *
 *   templates/shell/app.html.twig   la coquille applicative, pour TOUT le visuel ;
 *   templates/error/error.html.twig 404, 405 et 500, pour ses jetons et ses polices ;
 *   templates/auth/login.html.twig  la page de connexion, pour ses polices (11/08/2026).
 *
 * Les deux derniers n'en prennent QUE `:root` et les six @font-face, et c'est sur
 * cette feuille qu'on l'a verifie : aucun selecteur ne commence par un element nu, le
 * compose de GAUCHE est partout une classe `.app-*`, `.searchable-*` ou `body.app`.
 * Sur un corps qui ne porte aucune de ces classes, elle n'applique donc rien d'autre.
 * REFAIRE CETTE VERIFICATION avant d'ajouter un quatrieme consommateur, et surtout
 * avant d'ecrire ici la moindre regle sur un element nu : ce serait un effet de bord
 * sur la page de connexion et sur les trois pages d'erreur, en une seule ligne.
 */


/*
 * IBM PLEX, AUTO-HEBERGEE — accord du responsable produit du 05/08/2026.
 *
 * Fichiers woff2 officiels (github.com/IBM/plex v6.4.0, licence OFL), verifies a la
 * signature wOF2 au telechargement. Servis par /assets/** comme tout actif : la CSP
 * (`default-src 'self'`) est INCHANGEE — c'est toute la raison de l'auto-hebergement.
 * `font-display: swap` : le texte rend en pile systeme pendant le chargement plutot
 * que d'etre invisible.
 */
@font-face {
    font-family: "IBM Plex Sans";
    src: url("/assets/fonts/IBMPlexSans-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("/assets/fonts/IBMPlexSans-Medium.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("/assets/fonts/IBMPlexSans-SemiBold.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("/assets/fonts/IBMPlexSans-Bold.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("/assets/fonts/IBMPlexMono-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("/assets/fonts/IBMPlexMono-Medium.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}

/* ── LA PALETTE A ÉTÉ ALIGNÉE SUR L'INTRANET DU GROUPE LE 12/08/2026 ──────────
 *
 * Le responsable produit : « voila un exemple de l'intranet qui a les bonnes couleurs ect...
 * esssaye de revoir l'inventaire avec cette vision là surtout niveau couleur ».
 * La référence est une capture de Modal Hub, l'intranet maison.
 *
 * CE QUI CHANGE EST LA TEMPÉRATURE DES NEUTRES, ET C'EST L'ESSENTIEL DU LOT.
 * L'ancienne palette était CHAUDE — fond #F6F5F2, lignes #E6E3DC, gris estompé
 * #9A9689, tous tirant vers le beige. L'intranet est FROID : blanc bleuté,
 * lignes gris ardoise, gris de texte neutres. Les deux ne se mélangent pas —
 * un beige à côté d'un blanc bleuté se lit comme une tache jaune, et c'est ce
 * qui donnait à cet écran son air de ne pas venir de la même maison.
 *
 * CE QUI NE CHANGE PAS : le bleu marine et l'ambre. Ce sont les couleurs de la
 * MARQUE, celles du sigle, et l'intranet les porte aussi. L'ambre garde en
 * particulier sa valeur vive : elle a été mesurée dans la maquette d'origine et
 * une version plus terne avait été refusée par le responsable produit — « pourquoi les couleurs
 * sont fade ? ». Le marine s'aligne sur celui du sigle, à six centièmes près de
 * l'ancien.
 *
 * CE QUI ARRIVE : un accent interactif indigo, et trois teintes de fond. Les
 * cartes d'indicateurs de l'intranet portent un filet de couleur à gauche et un
 * carré d'icône teinté ; les panneaux d'aide sont bleus. Ces trois couples
 * (fond clair, encre lisible) existent pour ça, et pour ça seulement.
 *
 * L'INDIGO EST LE SEUL AJOUT QUI NE VIENT PAS DE LA MARQUE. Il est signalé
 * comme tel parce qu'il se discute : l'intranet l'emploie pour ses liens et ses
 * états actifs, mais rien n'oblige l'inventaire à le suivre. Le retirer se fait
 * en une ligne — remettre `--app-accent` à la valeur du marine — et tout ce qui
 * le consomme redevient marine sans autre geste.
 */
:root {
    --app-bg: #F7F8FA;
    --app-surface: #FFFFFF;
    --app-surface-alt: #F1F4F8;
    --app-line: #E4E8EF;
    --app-line-strong: #CFD6E0;
    --app-side-width: 230px;

    /* LE TROISIEME CRAN DE LA FAMILLE DES FILETS, ET IL NAIT JETON — N129, 13/08/2026.
       Le responsable produit : « je m'en fiche fait au mieux ». La decision est donc prise ici, et elle
       est ecrite pour que dans six mois personne ne la prenne pour un gout.

       CE QU'IL REPARE. N123 a declare sans corriger : la bordure du bouton sobre tirait
       sur --app-line-strong, soit 1,46:1 sur --app-surface, sous les 3:1 qu'une limite de
       CONTROLE demande (element non textuel — le seuil n'est PAS 4,5). Le defaut n'etait
       pas celui d'un bouton : le balayage a trouve 150 bordures sous 3:1 dans les feuilles,
       dont 21 portees par un CONTROLE — champs, listes deroulantes, puces de filtre,
       boutons sobres, panneau flottant du champ cherchable. La cause est UNE seule : la
       famille des filets n'avait pas de cran pour une limite qu'on doit VOIR, donc les
       controles empruntaient le filet des cartes.

       POURQUOI --app-line-strong N'EST PAS SIMPLEMENT REACCORDE, et c'est la mesure qui
       l'interdit : il est AUSSI le fond des trois statuts de la famille `sorti` (`donne`,
       `recycle`, `depleted`, materiel.css), gele par N106 et N109 a 7,15:1 avec
       --app-ink-soft. Deplacer sa valeur bougeait un fond de pastille ET un contraste de
       TEXTE — exactement les deux choses que ce lot ne doit pas toucher. Il sert encore de
       fond au filet des tuiles d'indicateur et a la piste vide, qui ne sont pas des
       controles. Un cran neuf etait donc la seule voie.

       LES CINQ JETONS DEJA LA ONT ETE MESURES SUR LES QUATRE FONDS QU'UNE BORDURE DE
       CONTROLE RENCONTRE VRAIMENT — --app-surface, --app-bg, --app-surface-alt, --app-tint
       (pire cas d'abord, c'est --app-tint le fond le plus sombre des quatre) :
         --app-line        #E4E8EF   1,09  ->  1,23   ECHEC
         --app-line-strong #CFD6E0   1,29  ->  1,46   ECHEC (la valeur relevee par N123)
         --app-faint       #98A2B3   2,28  ->  2,58   ECHEC
         --app-steel       #7C89AE   3,07  ->  3,47   passe, mais ECARTE, voir plus bas
         --app-muted       #667085   4,40  ->  4,97   passe, mais c'est l'encre du texte
         --app-ink-soft    #344054   9,25  -> 10,46   une ENCRE, pas un filet
         --app-navy        #1B2A4A  12,58  -> 14,22   une ENCRE, pas un filet
       --app-navy et --app-ink-soft sont refuses PAR LE HAUT : une bordure a 7:1 ou plus sur
       un bouton sobre en fait un bouton plein, et detruit la distinction que ce bouton
       porte. --app-muted est l'encre des libelles secondaires : un filet ne prend pas la
       valeur d'une encre, sans quoi la prochaine retouche d'encre repeint les bordures.
       --app-steel EST LE CAS SERIEUX, et il est ecarte sur DEUX chiffres : il est deja
       l'accent nomme d'une categorie de tuile (.app-action.is-steel), donc le reprendre
       recreerait ICI le double emploi que ce jeton vient defaire ; et sa marge est de 0,07
       au-dessus du seuil sur --app-tint, ce qu'une retouche de fond suffirait a manger.

       LA VALEUR RETENUE, #7C8798, ET POURQUOI CELLE-LA. Meme teinte que le reste de la
       famille (215° comme --app-line-strong), chroma intermediaire (16 % contre 21 %), un
       cran de clarte en dessous. Contrastes : --app-tint 3,22 · --app-surface-alt 3,30 ·
       --app-bg 3,42 · --app-surface 3,64. Les quatre passent, le pire avec 7 % de marge, et
       aucun n'atteint le registre d'une encre. Ecarts perceptuels CIEDE2000 : 22,83 de
       --app-line-strong (c'est bien un cran, pas une nuance), 7,10 de --app-steel (donc pas
       un quasi-doublon — la variante a 21 % de chroma n'etait qu'a 4,26 et a ete ecartee
       pour ca), 8,99 de --app-faint, 9,09 de --app-muted.

       CE JETON EST POUR LES LIMITES DE CONTROLE ET RIEN D'AUTRE. Le filet d'une carte, d'un
       panneau, d'une rangee de tableau ou d'une fenetre reste --app-line : ce sont des
       bords de SURFACE, que la regle des 3:1 ne vise pas, et les repeindre transformerait le
       produit en filaire. */
    --app-line-control: #7C8798;

    --app-ink: #1B2A4A;
    --app-ink-soft: #344054;
    --app-muted: #667085;
    --app-faint: #98A2B3;

    --app-navy: #1B2A4A;
    --app-link: #4F46E5;
    --app-link-hover: #C97E00;

    /* L'ACCENT INTERACTIF ET SES DEUX COMPAGNONS DE FOND. `--app-accent` habille
       ce qu'on peut actionner : liens, état actif du menu, anneau de mise au
       point. `--app-accent-bg` est le carré teinté derrière une icône ou une
       pastille, jamais un fond de page. `--app-info` et son fond servent le
       panneau d'aide, qui n'est ni un avertissement ni une réussite. */
    --app-accent: #4F46E5;
    --app-accent-bg: #EEF0FE;
    --app-info: #1D6FD0;
    --app-info-bg: #EAF3FD;
    --app-good-bg: #E8F5EF;
    /* L'ambre de la maquette est #F5A200, MESURE dans ses donnees (accent et bouton de
       la carte « A faire ») — la premiere version employait #C97E00, plus terne, et
       Le responsable produit l'a vu : « pourquoi les couleurs sont fade ? ». Le texte sur fond ambre est
       #16224A (bouton) et l'encre ambre sur fond clair est #8A5A00 (pastilles). */
    --app-amber: #F5A200;
    --app-amber-ink: #8A5A00;
    --app-amber-bg: #FFF1DA;
    --app-steel: #7C89AE;
    --app-good: #1F6B4B;
    --app-tint: #EEF1F8;
    /* LE CYAN DE L'ÉTAPE « EN STOCK » — N97, 12/08/2026, ET IL EST NÉ JETON DÈS SA
       PREMIÈRE LIGNE. Le responsable produit : « Dans les arrivée en stock je ne veux pas qu'il soit de
       la meme couleur que a traiter ». Il fallait donc une CINQUIÈME teinte de pastille
       aux arrivées, et AUCUN des jetons déjà là ne pouvait la porter : `--app-info-bg`
       (#EAF3FD) et `--app-accent-bg` (#EEF0FE) sont à ΔE 2,58 et 3,33 de `--app-tint`,
       qui est DÉJÀ le fond de la pastille « commandé » — les prendre aurait CASSÉ la
       paire qu'on venait distinguer, et `--app-info` échoue en plus le seuil de texte
       (4,42:1). Les mesures deux à deux sont dans N97.
       LA PLACE DANS LE PARCOURS COMMANDE LA TEINTE : `à traiter → à commander →
       commandé → en stock → prêt`, donc « en stock » se lit ENTRE le bleu de
       « commandé » et le vert de « prêt ». Le cyan est exactement cet intervalle de
       teinte, et il refuse le vert que N71 interdit à cette étape — l'objet est là, il
       n'est pas préparé pour la personne.
       IL NAÎT JETON ET NON LITTÉRAL parce que N95 a fusionné quatre rouges dispersés
       pour ce motif précis, et qu'un cinquième langage de couleur écrit en dur dans
       arrivees.css aurait recommencé la même dette le jour où une deuxième feuille en
       aurait besoin. `--app-teal` est l'ENCRE (5,06:1 sur son propre fond, 5,62:1 sur
       le fond de page si un aplat venait à en avoir besoin) ; `--app-teal-bg` est un
       fond de PASTILLE et pas un fond de page — même règle que `--app-accent-bg`. */
    --app-teal: #0A6C88;
    --app-teal-bg: #BCF6FF;

    /* ── LE TRIPLET DE DANGER, ET C'EST LA DETTE DE N94 ET N95 QUI SE FERME ────────
       N94 : « :root NE PORTE PAS de triplet de danger (encre, fond, bord) là où il
       porte --app-good / --app-good-bg et --app-amber-ink / --app-amber-bg. C'est la
       seule raison pour laquelle cinq valeurs restent littérales » ; N95 : « Il manque
       un triplet --app-bad, --app-bad-bg, --app-bad-line dans app.css ». Les valeurs
       ne sont PAS choisies aujourd'hui : ce sont celles que N94 a déjà unifiées —
       quatre rouges (#9b2f25 / #a32920 / #a63227 / #a83c31) fondus en #B42318 — et
       elles étaient tenues identiques d'une feuille à l'autre EXPRÈS, « pour qu'un
       jeton futur se pose d'un seul balayage ». C'est ce balayage : dix lignes de
       règle dans materiel.css et records.css passent au jeton À VALEUR ÉGALE, donc
       ΔE 0,00 partout et aucun pixel ne bouge.
       LE VIF #D92D20 N'ENTRE PAS DANS LE TRIPLET, et c'est un choix : N94 le réserve
       aux MARQUES PLEINES (l'astérisque d'un champ requis), pas au danger. Un seul
       usage, un seul fichier ; lui donner un jeton serait nommer un rôle qui n'a pas
       encore deux consommateurs.
       CE QUI RESTE OUVERT, ET IL FAUT LE DIRE : arrivees.css porte encore #a83c31 et
       #fbedeb, à ΔE 5,15 (encre) et 1,66 (fond) de ce triplet. L'encre est donc
       VISIBLEMENT différente. N95 l'a laissée exprès hors du jeu pré-unifié ; la
       convertir change le rendu d'un écran qui n'est pas celui de ce lot, et c'est une
       décision du responsable produit, pas un effet de bord à provoquer en passant. */
    --app-bad: #B42318;
    --app-bad-bg: #FEF3F2;
    --app-bad-line: #FECDCA;

    /* ── LE BLEU DE L'APPROVISIONNEMENT ET LE LAVIS INDIGO PROFOND — N109 ──────────
       LES DEUX NAISSENT POUR LE TABLEAU DENSE DE /materiel, et pas pour le goût. Une
       pastille de statut y est posée sur TROIS fonds successifs — la ligne impaire
       #FFFFFF, la ligne paire var(--app-bg), le survol var(--app-surface-alt) — alors
       que la même pastille, dans la PWA, ne connaît qu'une carte blanche. Un lavis qui
       se détache sur du blanc peut donc DISPARAÎTRE au survol : c'est mesuré, --app-
       tint (#EEF1F8) est à ΔE 1,61 du survol et --app-line (#E4E8EF) à 2,91.
       --app-blue N'EST PAS --app-info, ET C'EST UNE MESURE, PAS UNE PRÉFÉRENCE :
       #1D6FD0 échoue le seuil de texte sur les trois lavis bleus essayés (3,75:1 sur
       #D3E1F8, 3,53:1 sur #C3DCFB, 4,23:1 sur #E3EEFD), exactement comme N97 l'avait
       déjà écarté à 4,42:1 sur son propre fond. #0F4C9C rend 5,90:1 sur --app-blue-bg,
       et ce lavis est à ΔE 12,20 du survol et 11,10 de --app-tint — donc il ne se
       confond ni avec le fond de la ligne ni avec la pastille « Attribué ».
       --app-accent-bg-strong EST --app-accent-bg D'UN CRAN PLUS PROFOND, et le suffixe
       est celui que ce bloc emploie déjà pour --app-line-strong. Il existe parce que
       --app-accent-bg (#EEF0FE) est à ΔE 3,33 de --app-tint : c'est LE chiffre que N97
       a mesuré pour l'écarter, et le même écran porte les deux. Son encre reste
       --app-accent (5,13:1), qui vaut #4F46E5 comme --app-link — sans risque de
       confusion ici, les liens de ce tableau étant en --app-navy et non en --app-link.
       LES DEUX VIENNENT DE LA PWA, où mobile.css les porte en littéral faute de voir
       ce bloc : #C3DCFB est son lavis de « Livré », #E7E6FD celui de « En prêt ». Le
       motif des sept familles est dans N106 et dans l'en-tête de `.mob-pill`. */
    --app-blue: #0F4C9C;
    --app-blue-bg: #C3DCFB;
    --app-accent-bg-strong: #E7E6FD;

    --app-radius: 12px;
    --app-radius-sm: 7px;

    --app-font: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
    --app-mono: "IBM Plex Mono", "Cascadia Mono", Consolas, ui-monospace, monospace;
}

/* ------------------------------------------------------------------ coquille */

body.app {
    margin: 0;
    min-height: 100vh;
    display: flex;
    background: var(--app-bg);
    color: var(--app-ink);
    font-family: var(--app-font);
    -webkit-font-smoothing: antialiased;
}

.app-side {
    width: var(--app-side-width);
    flex: none;
    background: var(--app-surface);
    border-right: 1px solid var(--app-line);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
}

/*
 * LA CARTE DU LOGO — demande du responsable produit du 10/08/2026, reference Modal Hub.
 *
 * Un bloc distinct, fond blanc, coins arrondis, logo centre sur toute la largeur de la
 * barre, pose au-dessus du reste de la navigation. Remplace le faux monogramme « INV »,
 * qui avait DEJA ete refuse une fois (journal du 07/08/2026) et etait revenu.
 *
 * .app-brand NE PORTE PLUS DE DISPOSITION : il ne contient qu'un enfant, la carte, qui
 * prend la largeur seule. Un premier jet le passait en COLONNE pour ranger le selecteur de
 * langue sous la carte — le responsable produit a vu le rendu le 10/08/2026 et l'a REFUSE : le bouton seul
 * sur sa ligne creusait une bande vide entre la carte et le titre « Operations ». Le
 * selecteur est donc DANS la carte (voir .app-brand-card), et ce bloc redevient un simple
 * conteneur d'espacement. Ne pas y remettre de `flex-direction: column` : c'est la
 * disposition refusee.
 *
 * CLASSES SUPPRIMEES ICI : .app-brand-mark, .app-brand-mark-v, .app-brand-name et
 * .app-brand-sub. Plus aucun gabarit ne les portait apres le retrait du monogramme et du
 * texte qui l'accompagnait — verifie au grep sur templates/ et sur les feuilles.
 *
 * LA REFERENCE PENDANTE EST FERMEE (10/08/2026, plus tard le meme jour). Ce paragraphe
 * disait que public/assets/favicon.svg renvoyait encore a .app-brand-mark et portait
 * TOUJOURS l'ancien sigle. Le SVG est SUPPRIME : l'icone d'onglet est desormais
 * public/assets/favicon.png, le SIGLE SEUL du vrai logo, decoupe a la gouttiere mesuree
 * du fichier (le sigle occupe les colonnes 0 a 142, le mot-symbole commence a 180). Sans
 * mot-symbole, delibere : « MODAL INVENTORY » est illisible a 16 px, taille reelle d'une
 * icone d'onglet — un sigle se reconnait, un mot ne se lit pas. Les deux jetons de la
 * maquette, #26356B et #F5A200, restent vrais et vivent dans les variables ci-dessus.
 */
.app-brand {
    padding: 15px 15px 13px;
    border-bottom: 1px solid var(--app-line);
}

/*
 * LE BLANC EST ECRIT EN DUR, ET C'EST DELIBERE — mais pas pour la raison qu'on croit.
 *
 * Le PNG servi est TRANSPARENT (type couleur 6, canal alpha) : le logo ne reclame aucun
 * fond pour rendre proprement, et un premier jet de ce fichier justifiait a tort le blanc
 * par un fond opaque « cuit dans l'image ». Ce n'est plus vrai. Ce qui reste mesure :
 * 7,1 % des pixels (4 614) sont en alpha PARTIEL, l'anticrenelage des bords, et ces
 * pixels ont ete calcules contre le quasi-blanc de l'image d'origine. Composes sur blanc
 * ils rendent le trace voulu ; composes sur une teinte ils frangent en clair.
 * Le blanc est donc la demande du responsable produit (reference Modal Hub) ET le fond pour lequel
 * l'anticrenelage de ce fichier a ete calcule. Ecrit en dur plutot que via
 * var(--app-surface) pour cette raison : une retouche de palette ne doit pas le teinter.
 *
 * LA BARRE EST ELLE-MEME BLANCHE (--app-surface) : c'est la BORDURE qui detache la carte,
 * pas un contraste de fond. Sans elle, carte et barre se confondent — la reference Modal
 * Hub a une barre teintee, ce projet non, et le detachement doit venir d'ailleurs.
 *
 * LE PADDING EST LE SEUL AIR AUTOUR DU LOGO. Le fichier est recadre au contenu : ses
 * quatre bords portent de l'encre, verifie pixel a pixel. Il n'y a donc aucune marge
 * integree qui s'ajouterait a ces valeurs — les regler ici les regle entierement.
 *
 * LA CARTE EST UNE RANGEE : logo a gauche, selecteur de langue a droite.
 *
 * POURQUOI UNE RANGEE ET PAS UNE SURIMPRESSION EN HAUT A DROITE, qui etait la piste
 * proposee : la mesure la refuse. Le logo prend toute la largeur disponible ; un bouton
 * de 26 px pose au-dessus de lui en `position: absolute` le recouvrirait, et pour degager
 * la place il faudrait un logo centre verticalement dans une carte de plus de 100 px de
 * haut (contre ~58 px ici) — soit reprendre toute la hauteur que la suppression de la
 * ligne dediee venait de gagner. En rangee, la hauteur de la carte est celle du LOGO
 * (~32 px), qui est PLUS HAUT que le bouton (26 px) : le selecteur ne coute aucune hauteur.
 * C'est la seule des deux formes qui tienne la contrainte « plus de ligne pour lui seul »
 * sans en payer le prix ailleurs.
 */
.app-brand-card {
    background: #FFFFFF;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    padding: 12px 12px;
    display: flex;
    align-items: center;
    gap: 8px;
}

/*
 * LE LIEN QUI RAMENE AU TABLEAU DE BORD — demande du responsable produit du 11/08/2026.
 *
 * IL N'ENTOURE QUE L'IMAGE, jamais la carte : le selecteur de langue vit dans la meme
 * carte, et c'est un formulaire avec un bouton — du contenu interactif, qui ne s'imbrique
 * pas dans un lien. Voir le commentaire du gabarit.
 *
 * CES TROIS DECLARATIONS SONT CELLES DE .app-brand-logo, DEPLACEES ET NON DUPLIQUEES.
 * L'enfant flexible de la carte est desormais le LIEN, plus l'image : `flex: 1` laisse a
 * l'image la largeur qui reste apres le selecteur de langue, et `min-width: 0` l'autorise a
 * descendre sous la largeur intrinseque de 529 px du fichier au lieu de pousser la carte en
 * debordement. Sur l'image, ces deux valeurs seraient devenues MORTES — un enfant de lien
 * n'est pas un enfant de la rangee — et le logo debordait, mesure au navigateur.
 *
 * `display: block` supprime l'espace sous la ligne de base : un <a> est un element EN LIGNE,
 * et laisse en ligne il ajoutait cet espace fantome sous l'image qu'il entoure.
 *
 * AUCUNE REGLE DE FOCUS ICI : elle est plus bas, ajoutee a la liste de selecteurs de
 * l'anneau commun de l'application. Un anneau propre a cette carte serait un second endroit
 * a tenir a jour pour le meme effet.
 */
.app-brand-link {
    display: block;
    flex: 1;
    min-width: 0;
}

/*
 * DIMENSIONNE PAR LA LARGEUR, JAMAIS PAR LA HAUTEUR. Le rapport est MESURE sur le
 * fichier : 529x123, soit 4,30:1 — tres horizontal. Fixer une hauteur donnerait une
 * largeur imprevisible et debordante dans une barre de 230 px ; la largeur contrainte plus
 * `height: auto` laisse le navigateur tenir le rapport seul. Meme facon de faire que
 * .signin-brand img dans auth.css.
 *
 * `width: 100%` a REMPLACE le couple `flex: 1` / `min-width: 0`, parti sur .app-brand-link
 * ci-dessus le 11/08/2026 : l'image remplit son lien, et c'est le lien qui negocie sa
 * largeur avec le selecteur de langue. Les boites mesurees sont les MEMES qu'avant le lien,
 * a trois largeurs de fenetre — c'etait la condition de la demande.
 *
 * AUCUN `background` ICI, ET C'EST UNE REGLE, PAS UN OUBLI : l'image est transparente, un
 * fond pose sur l'<img> reintroduirait le rectangle que le detourage a supprime. Le blanc
 * appartient a la CARTE, jamais a l'image.
 *
 * `display: block` supprime l'espace sous la ligne de base propre aux images en ligne, qui
 * decalerait le centrage vertical dans la carte.
 */
.app-brand-logo {
    display: block;
    width: 100%;
    height: auto;
}

/*
 * LE SELECTEUR DE LANGUE, DERNIER ELEMENT DE LA RANGEE DE LA CARTE.
 *
 * `margin-left: auto` a ete RETIRE : il servait a pousser le bouton a droite quand il
 * vivait seul sous la carte — la disposition que le responsable produit a refusee le 10/08/2026. C'est
 * desormais `flex: 1` du logo qui occupe la place et repousse ce formulaire a droite ;
 * une marge automatique en plus n'y ajouterait rien.
 *
 * `flex: none` : le bouton garde sa taille de cible (30x26) quoi qu'il arrive a la largeur
 * de la barre. Sans lui, le formulaire serait un element flexible que le logo pourrait
 * comprimer — et c'est le SEUL moyen de changer de langue dans toute l'application, donc
 * une cible qui rapetrisse serait un defaut d'accessibilite, pas un gain de place.
 */
.app-locale {
    flex: none;
}

.app-locale button {
    width: 30px;
    height: 26px;
    padding: 0;
    /* N129, 13/08/2026 : var(--app-line) #E4E8EF -> var(--app-line-control) #7C8798. C'etait
       la PIRE bordure de controle du produit, 1,23:1 sur --app-surface, et le cas le plus
       net : ce bouton n'a ni fond distinct de sa barre blanche ni libelle long — sa bordure
       est la SEULE chose qui dit ou l'on clique. Apres : 3,64:1. */
    border: 1px solid var(--app-line-control);
    border-radius: var(--app-radius-sm);
    background: var(--app-surface);
    color: var(--app-navy);
    font: 600 10px/1 var(--app-font);
    cursor: pointer;
}

.app-locale button:hover,
.app-locale button:focus-visible { border-color: var(--app-navy); }

/* ------------------------------------------------------------- navigation */

.app-nav {
    padding: 6px 10px 12px;
    overflow-y: auto;
    flex: 1;
}

/*
 * LES TITRES DE SECTION — « OPERATIONS », « SUIVI », « DONNEES », « ADMINISTRATION ».
 *
 * Petites capitales grises, comme la reference Modal Hub (« ACCUEIL », « SERVICES »,
 * « RESSOURCES »). La forme ne bouge pas ; seule l'encre change.
 *
 * L'ENCRE PASSE DE --app-faint A --app-muted LE 12/08/2026, ET C'EST UNE MESURE, PAS UN GOUT.
 * Ancienne valeur : var(--app-faint), #98A2B3 sur la barre blanche = 2,58:1.
 * Nouvelle valeur : var(--app-muted), #667085 sur la barre blanche = 4,97:1.
 * Un titre de section est du CONTENU — il nomme le groupe d'entrees en dessous — donc il
 * releve du plancher 4,5:1 de WCAG 1.4.3, que 2,58 ne tenait pas. Le refroidissement de la
 * palette n'a pas cree ce defaut (l'ancien beige #9A9689 mesurait 2,71:1 sur #F6F5F2, donc
 * il echouait deja) mais il etait l'occasion de le fermer. --app-faint N'A PAS CHANGE et
 * reste l'encre des elements DESACTIVES, exemptes de ce plancher — voir .is-pending.
 */
.app-nav-title {
    margin: 16px 4px 6px;
    font: 600 10px/1 var(--app-font);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--app-muted);
}

.app-nav ul {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.app-nav a,
.app-nav .is-pending {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 10px;
    border-radius: var(--app-radius-sm);
    font: 500 13px/1.3 var(--app-font);
}

.app-icon {
    width: 15px;
    height: 15px;
    flex: none;
}

.app-nav .app-icon {
    color: var(--app-muted);
}

/* L'icone de l'entree active est BLANCHE depuis le 12/08/2026 (ancienne valeur :
   var(--app-navy)) : la pastille est desormais un aplat marine, voir .app-nav a.is-active
   plus bas. Une icone marine sur un aplat marine mesure 1,00:1 — elle disparaitrait. */
.app-nav a.is-active .app-icon {
    color: #FFFFFF;
}

.app-nav a {
    color: var(--app-ink-soft);
    text-decoration: none;
}

.app-nav a:hover {
    background: var(--app-surface-alt);
    color: var(--app-ink);
}

/*
 * L'ENTREE ACTIVE EST UNE PASTILLE PLEINE MARINE A TEXTE BLANC — 12/08/2026, reference
 * Modal Hub. C'est l'ECART LE PLUS VISIBLE releve entre cet ecran et l'intranet.
 *
 * ANCIENNES VALEURS, conservees ici parce qu'elles expliquent le defaut :
 *     background: var(--app-tint);   (#EEF1F8)
 *     color: var(--app-navy);        (#1B2A4A)
 * Soit un aplat a 1,13:1 du blanc de la barre. MESURE : la pastille etait quasi invisible,
 * et l'etat actif ne se lisait en pratique que sur la graisse du libelle — un signal que
 * l'oeil ne trouve pas sans comparer deux lignes voisines. La reference, elle, pose un
 * aplat PLEIN : on voit ou l'on est sans lire.
 *
 * NOUVELLES VALEURS ET LEUR MESURE : blanc sur --app-navy #1B2A4A = 14,22:1, tres au-dessus
 * du plancher 4,5:1, et la pastille elle-meme se detache de la barre blanche a 14,22:1.
 *
 * `font-weight: 600` RESTE, et ce n'est pas une redondance avec la couleur : c'est le
 * second vehicule de l'information. La regle du projet est qu'aucun etat ne repose sur la
 * seule couleur — un ecran en niveaux de gris, ou une impression, garde la graisse.
 *
 * LE BLANC EST ECRIT EN DUR ET NON EN JETON : il n'y a pas de jeton « encre sur aplat
 * sombre » dans cette palette, et --app-surface (#FFFFFF) designe une SURFACE de fond, pas
 * une encre. Meme choix que .app-btn.is-primary et .session-avatar juste au-dessus, qui
 * ecrivent aussi leur blanc de texte en litteral pour la meme raison.
 */
.app-nav a.is-active {
    background: var(--app-navy);
    color: #FFFFFF;
    font-weight: 600;
}

/* LE COMPTEUR D'UNE ENTREE (« 812 » sur Materiels). L'encre passe de var(--app-faint)
   a var(--app-muted) le 12/08/2026 : ancienne mesure #98A2B3 sur #F1F4F8 = 2,33:1,
   nouvelle #667085 sur #F1F4F8 = 4,51:1. Un CHIFFRE est une donnee, jamais une
   decoration — c'est le seul volume du parc visible depuis toutes les pages. */
.app-nav-badge {
    margin-left: auto;
    padding: 2px 5px;
    border-radius: 5px;
    background: var(--app-surface-alt);
    color: var(--app-muted);
    font: 600 10px/1 var(--app-mono);
}

.app-nav-badge.is-amber { background: var(--app-amber-bg); color: var(--app-amber-ink); }

/*
 * UNE ENTREE DONT L'ECRAN N'EXISTE PAS ENCORE : presente (la maquette la montre),
 * visiblement inerte (un lien mort serait pire qu'un lien grise), et son infobulle
 * dit ce qui l'activera.
 */
.app-nav .is-pending {
    color: var(--app-faint);
    background: var(--app-surface-alt);
    opacity: .55;
    cursor: not-allowed;
}

.app-nav .is-pending .app-icon { color: var(--app-faint); }

/*
 * L'ANNEAU DE FOCUS COMMUN. `.app-brand-link` a ete AJOUTE a cette liste le 11/08/2026, et
 * non recopie dans une regle a lui : le logo devenu cliquable est le PREMIER element
 * focalisable de toute l'application — le premier Tab de chaque ecran — et il vit dans
 * .app-brand, que ni `.app-nav a` ni `.app-main a` n'atteignent. Sans cette ligne il ne
 * portait que l'anneau par defaut du navigateur, different de celui du reste de la barre.
 */
.app-brand-link:focus-visible,
.app-nav a:focus-visible,
.app-main a:focus-visible,
.app-btn:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}

.app-side-foot {
    margin-top: auto;
    padding: 10px 14px;
    border-top: 1px solid var(--app-line);
}

/* Le bandeau de session (gabarit partage auth/_session_bar) au style de la maquette.
 *
 * ── PIEGE VERIFIE AVANT DE TOUCHER QUOI QUE CE SOIT ICI (12/08/2026) ────────
 *
 * `auth.css` habille LUI AUSSI `.session-bar`, `.session-who`, `.session-role` et
 * `.session-logout button`, mais avec les jetons de `base.css` — `--line`, `--panel-alt`,
 * `--bg`, `--muted`, `--text`, `--accent`. Ces jetons N'EXISTENT PAS dans cette feuille.
 *
 * CE N'EST PAS UN DEFAUT, ET LA VERIFICATION EST CELLE DES GABARITS, PAS DES FEUILLES :
 *   - `templates/shell/app.html.twig` (l'APPLICATION) charge app.css SEULE. Les regles
 *     `.app-side-foot .session-*` ci-dessous sont donc les seules qui s'appliquent, et
 *     elles n'emploient que des jetons `--app-*`, tous definis.
 *   - `templates/explorer/index.html.twig` et `templates/session/index.html.twig` etendent
 *     `shell/layout.html.twig` (base.css) ET chargent auth.css : les jetons `--line`,
 *     `--bg` et compagnie y sont definis, et les regles d'auth.css s'y appliquent.
 *   - `templates/auth/login.html.twig` charge app.css ET auth.css, mais ne rend AUCUN
 *     bandeau (pas d'identite en contexte) : le cas ne se pose pas.
 *
 * CONCLUSION A NE PAS REFAIRE DE TRAVERS : reecrire les regles d'auth.css avec des jetons
 * `--app-*` les resoudrait en chaine VIDE sur l'explorateur et sur la page de session, qui
 * ne chargent pas cette feuille — deux ecrans casses pour un defaut qui n'existe pas. Les
 * deux jeux de regles sont deliberement disjoints ; ils ne se rencontrent jamais.
 */

.app-side-foot .session-bar {
    display: flex;
    align-items: center;
    gap: 9px;
}

.app-side-foot .session-avatar {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    flex: none;
    border-radius: 50%;
    background: var(--app-navy);
    color: #FFFFFF;
    font: 600 10px/1 var(--app-font);
}

.app-side-foot .session-who {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
    font: 600 12.5px/1.2 var(--app-font);
    color: var(--app-ink);
    flex: 1;
    min-width: 0;
}

.app-side-foot .session-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * LE ROLE (« ADMINISTRATEUR »). L'encre passe de var(--app-link-hover) a
 * var(--app-amber-ink) le 12/08/2026, pour DEUX raisons qui vont dans le meme sens.
 *
 * SEMANTIQUE : `--app-link-hover` est, par son nom, la couleur de SURVOL D'UN LIEN. Ce
 * libelle n'est ni un lien ni survolable — il empruntait un jeton pour sa teinte. Le jeton
 * de l'ambre-sur-fond-clair existe et se nomme `--app-amber-ink` ; l'en-tete de la palette
 * le dit mot pour mot (« l'encre ambre sur fond clair est #8A5A00 »).
 *
 * MESURE : ancienne valeur #C97E00 sur le pied blanc = 3,25:1 — sous le plancher 4,5:1 de
 * WCAG 1.4.3, et le texte fait 9,5 px, donc rien ne le fait passer pour du « gros texte ».
 * Nouvelle valeur #8A5A00 = 5,93:1. La FAMILLE de couleur ne bouge pas : c'est le meme
 * ambre, plus dense. L'ambre VIF (#F5A200) n'a pas ete touche — il est reserve aux aplats,
 * ou il porte de l'encre foncee, et le responsable produit a refuse qu'on le ternisse.
 */
.app-side-foot .session-role {
    font: 600 9.5px/1.2 var(--app-font);
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--app-amber-ink);
}

.app-side-foot .session-logout {
    margin: 0;
}

.app-side-foot .session-logout button {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: var(--app-radius-sm);
    background: transparent;
    /* L'encre passe de var(--app-faint) a var(--app-muted) le 12/08/2026. Ce bouton n'a
       AUCUN libelle — son icone EST sa cible, donc elle releve du plancher 3:1 de WCAG
       1.4.11 (contraste des elements non textuels). Ancienne mesure : #98A2B3 sur blanc
       = 2,58:1, sous le plancher. Nouvelle : #667085 = 4,97:1. Et c'est le seul moyen de
       fermer sa session depuis l'application : une cible qu'on ne voit pas est un defaut. */
    color: var(--app-muted);
    cursor: pointer;
}

.app-side-foot .session-logout button:hover {
    background: var(--app-surface-alt);
    color: var(--app-ink);
}

.app-side-foot .session-logout button:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}

.app-side-foot .session-logout-icon {
    width: 17px;
    height: 17px;
}

/* ------------------------------------------------------------------ contenu */

/*
 * L'AIR — 12/08/2026. « beaucoup d'air : la reference respire plus que notre ecran ».
 * Anciennes valeurs : padding 20px 26px 34px, gap 18px. Nouvelles : 24px 30px 38px, 20px.
 * Le pas est volontairement COURT (4 px et 2 px) : la demande est « revoir avec cette
 * vision la », pas recopier la maquette.
 */
.app-main {
    flex: 1;
    min-width: 0;
    padding: 24px 30px 38px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.app-topbar {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--app-line);
}

.app-crumbs {
    display: flex;
    align-items: center;
    gap: 7px;
    font: 400 13px/1 var(--app-font);
    color: var(--app-muted);
}

.app-crumbs strong {
    font-weight: 600;
    color: var(--app-ink);
}

/* Le chevron « › » entre deux miettes. Encre passee de var(--app-line-strong) a
   var(--app-faint) le 12/08/2026 : ancienne mesure #CFD6E0 sur #F7F8FA = 1,38:1, un
   separateur qu'on ne voyait plus une fois le fond refroidi ; nouvelle #98A2B3 = 2,42:1.
   --app-faint est LE bon jeton ici et il n'y a pas de plancher a tenir : ce chevron est
   une PONCTUATION decorative, le gabarit porte la hierarchie dans son balisage <nav>. */
.app-crumb-sep {
    color: var(--app-faint);
}

.app-search {
    margin-left: auto;
    width: min(400px, 100%);
    padding: 10px 15px;
    /* N129, 13/08/2026 : var(--app-line) #E4E8EF (1,23:1) -> var(--app-line-control)
       #7C8798 (3,64:1). Ce champ n'est PAS decoratif et il n'est pas desactive partout :
       il porte la recherche de l'ecran des arrivees et des trois ecrans du referentiel,
       relevees au gabarit. L'etat desactive juste dessous garde son propre fond. */
    border: 1px solid var(--app-line-control);
    border-radius: 10px;
    background: var(--app-surface);
    font: 400 13px/1.2 var(--app-font);
    color: var(--app-ink);
}

.app-search:disabled {
    background: var(--app-surface-alt);
    color: var(--app-faint);
    cursor: not-allowed;
}

/*
 * LA PASTILLE « N a faire ». DEUX BLOCS `.app-pill` FUSIONNES EN UN LE 12/08/2026 : le
 * premier posait `color: var(--app-amber)` (#F5A200), le second, identique en specificite
 * et juste en dessous, le remplacait par `var(--app-amber-ink)` (#8A5A00). Seule la
 * SECONDE valeur atteignait l'ecran ; la premiere etait morte depuis sa correction et
 * laissait croire que la pastille portait l'ambre vif.
 * LE RENDU NE CHANGE PAS D'UN PIXEL — c'est une suppression de ligne morte, pas une
 * retouche. La valeur retenue est celle qui rendait deja : #8A5A00 sur #FFF1DA = 5,32:1,
 * la ou l'ambre vif y mesurait 1,88:1 (illisible), ce qui est le motif du doublon.
 */
.app-pill {
    padding: 8px 13px;
    border-radius: 999px;
    font: 600 12px/1 var(--app-font);
    background: var(--app-amber-bg);
    color: var(--app-amber-ink);
}

.app-pill.is-pending {
    cursor: not-allowed;
}

/* ══ LE GROUPE DU HAUT PARTAGE — N133, 13/08/2026 ═══════════════════════════════════════
 *
 * Il habille `templates/shell/_topbar_actions.html.twig`, le fragment unique inclus par les
 * DOUZE ecrans qui portaient le compteur « a faire ». Les regles vivent dans app.css et non
 * dans materiel.css ou records.css parce que la coquille applicative sert app.css a TOUTES
 * ses pages : une seule feuille, comme il n'y a plus qu'un seul gabarit.
 *
 * `.equipment-todo` et `.records-todo` NE SONT PAS SUPPRIMEES DE LEURS FEUILLES par ce lot.
 * Elles n'ont plus de porteur dans templates/, donc elles ne rendent plus rien, mais un agent
 * travaille en parallele sur l'onglet Materiel et une suppression dans sa feuille se serait
 * croisee avec son travail. Le nettoyage est un geste separe, sans effet visible.
 *
 * ── LE PLACEMENT A DROITE TIENT DANS UNE DECLARATION, ET IL FALLAIT LES TROIS ────
 *
 * `margin-left: auto`, et c'est tout ce qui change les trois positions d'avant :
 *   `.app-topbar`          flex SANS justification — la pastille suivait donc le fil d'Ariane,
 *                          collee A GAUCHE. C'etait la plainte du responsable produit, mesuree.
 *   `.equipment-top-actions` flex, deja pousse a droite sur UN seul ecran (la liste des
 *                          articles, par une regle dediee). Les six autres dependaient du
 *                          nombre de voisins dans la rangee.
 *   `.records-topbar`      flex avec `space-between` — a droite, mais par un autre mecanisme,
 *                          donc a une autre distance des voisins.
 * La meme declaration donne le meme resultat dans les trois, et elle ne peut plus diverger.
 *
 * ── ZONES TACTILES : 44 px, MESUREES ET NON SUPPOSEES ───────────────────────────
 *
 * La pastille passe de 40 a 44 px de haut, la poignee de la cloche est un carre de 44 px, et
 * chaque ligne du panneau a `min-height: 44px`. C'etait la seule dimension a bouger.
 *
 * ── CONTRASTES, TOUS MESURES SUR LES JETONS EXISTANTS ───────────────────────────
 *
 * AUCUN JETON N'EST NE POUR CE LOT : la palette portait deja tout ce qu'il fallait, et les
 * valeurs ci-dessous ont ete relevees avant d'etre ecrites, pas apres.
 *
 *   TEXTES (plancher 4,5:1)
 *     --app-amber-ink sur --app-amber-bg      5,32:1   la pastille a faire, N > 0
 *     --app-ink-soft  sur --app-surface-alt   9,48:1   la pastille a faire, N = 0
 *     --app-ink       sur --app-surface      14,22:1   le titre du panneau
 *     --app-muted     sur --app-surface       4,97:1   l'aide et l'etat d'une ligne
 *     blanc           sur --app-bad           6,57:1   la pastille chiffree de la cloche
 *     --app-bad       sur --app-surface       6,57:1   l'echeance du jour J
 *     --app-amber-ink sur --app-surface       5,93:1   l'echeance a trois jours
 *
 *   LIMITES DE CONTROLE (plancher 3:1)
 *     --app-line-control sur --app-bg         3,42:1   le bord de la pastille calme
 *     --app-line-control sur --app-surface    3,64:1   le bord de la poignee de cloche
 *     --app-ink-soft     sur --app-surface   10,46:1   le trace de la cloche lui-meme
 *     --app-amber-ink    sur --app-bg         5,58:1   le bord de la pastille ambre
 *     --app-steel        sur --app-surface    3,47:1   le filet de gauche a sept jours
 *
 *   ECARTE APRES MESURE : --app-amber (#F5A200) en BORD vaut 1,97:1 sur le fond de page et
 *   2,09:1 sur le blanc — il ECHOUE aux deux endroits ou un bord doit se voir. Les bords
 *   prennent donc --app-amber-ink, et --app-amber ne reste QUE sur la puce ronde de la
 *   pastille (1,88:1 sur son fond ambre), qui est DECORATIVE : elle ne porte aucune
 *   information que le libelle a 5,32:1 juste a cote ne porte pas. C'est exactement le statut
 *   qu'elle avait deja dans les deux feuilles d'origine, et il n'a pas ete degrade.
 */
.shell-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

/* LA PASTILLE « N a faire » — UN LIEN SUR LES DOUZE ECRANS, JAMAIS UN `span`.
   Le tableau de bord la rendait non cliquable ; c'etait l'ecart le plus couteux du lot, parce
   qu'un decalage se voit et qu'un clic mort se decouvre. */
.shell-todo {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    min-height: 44px;
    padding: 0 16px;
    border: 1px solid var(--app-amber-ink);
    border-radius: 999px;
    background: var(--app-amber-bg);
    color: var(--app-amber-ink);
    font: 700 13px/1 var(--app-font);
    text-decoration: none;
    white-space: nowrap;
}

.shell-todo i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--app-amber);
}

/* ZERO RESTE VISIBLE ET CLIQUABLE, MAIS CESSE DE CRIER. La regle venait du tableau de bord et
   n'existait QUE la ; les onze autres ecrans rendaient « 0 a faire » en ambre plein, c'est-a-dire
   une alerte pour dire qu'il n'y a rien. Elle est generalisee, pas supprimee. */
.shell-todo.is-quiet {
    border-color: var(--app-line-control);
    background: var(--app-surface-alt);
    color: var(--app-ink-soft);
}

.shell-todo.is-quiet i {
    background: var(--app-muted);
}

/* LA CLOCHE. `position: relative` porte DEUX enfants absolus — la pastille chiffree et le
   panneau — et c'est ce qui garantit que le panneau ne pousse aucune mise en page en s'ouvrant. */
.shell-bell {
    position: relative;
}

/* LE MARQUEUR NATIF EST RETIRE EN DEUX DECLARATIONS, EXPLICITEMENT ET NON PAR EFFET DE BORD.
   `display: flex` sur un `summary` supprime deja le triangle sur les moteurs WebKit — piege
   mesure deux fois le 13/08/2026, ou une poignee etait devenue un rectangle indiscernable. Ici
   l'effacement est VOULU puisque la cloche remplace le triangle, mais s'y fier aurait laisse le
   marqueur en place sur Firefox, qui obeit a `list-style`. Les deux sont donc ecrites, et la
   poignee reste identifiable parce qu'elle porte un dessin, un bord et une zone de 44 px. */
.shell-bell > summary {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--app-line-control);
    border-radius: 999px;
    background: var(--app-surface);
    color: var(--app-ink-soft);
    cursor: pointer;
    list-style: none;
}

.shell-bell > summary::-webkit-details-marker {
    display: none;
}

.shell-bell > summary:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}

.shell-bell-icon {
    width: 20px;
    height: 20px;
}

/* ELLE NE PARAIT QUE S'IL Y A QUELQUE CHOSE. Une pastille « 0 » serait le meme bruit que la
   pastille ambre a zero juste a cote, et une cloche qui ne dit rien tant qu'on ne l'ouvre pas
   n'a aucun interet — c'est tout ce que la pastille apporte. */
.shell-bell-count {
    position: absolute;
    top: -3px;
    right: -3px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    box-sizing: border-box;
    border-radius: 999px;
    background: var(--app-bad);
    color: #fff;
    font: 700 11px/20px var(--app-font);
    text-align: center;
}

/* LE PANNEAU. `right: 0` l'ancre au bord DROIT de la cloche : ancre a gauche il sortirait de la
   page, la cloche etant elle-meme collee a droite. La largeur est bornee par la fenetre, donc
   aucun debordement horizontal quelle que soit la taille de l'ecran, et la hauteur est bornee
   avec son propre defilement — une liste longue ne peut pas depasser la page. */
.shell-bell-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 40;
    box-sizing: border-box;
    width: min(340px, calc(100vw - 40px));
    max-height: min(60vh, 420px);
    overflow-y: auto;
    padding: 14px 16px;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    background: var(--app-surface);
    box-shadow: 0 14px 30px color-mix(in srgb, var(--app-ink) 16%, transparent);
    text-align: left;
}

.shell-bell-title {
    margin: 0 0 10px;
    font: 700 13px/1 var(--app-font);
    color: var(--app-ink);
}

.shell-bell-empty {
    margin: 0;
    font: 600 13px/1.4 var(--app-font);
    color: var(--app-ink);
}

.shell-bell-help {
    margin: 4px 0 0;
    font: 400 12px/1.4 var(--app-font);
    color: var(--app-muted);
}

.shell-bell-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.shell-bell-row > a {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 3px;
    min-height: 44px;
    padding: 9px 12px;
    border: 1px solid var(--app-line);
    border-left: 3px solid var(--app-steel);
    border-radius: var(--app-radius-sm);
    text-decoration: none;
}

.shell-bell-row strong {
    font: 700 13px/1.3 var(--app-font);
    color: var(--app-ink);
}

.shell-bell-when {
    font: 600 12px/1.3 var(--app-font);
    color: var(--app-muted);
}

.shell-bell-state {
    font: 400 12px/1.3 var(--app-font);
    color: var(--app-muted);
}

/* LES TROIS SEUILS SE LISENT SANS LA COULEUR : chaque ligne ECRIT son echeance en toutes
   lettres (« arrive dans 5 jour(s) », « arrive aujourd'hui », « depassee depuis 2 jour(s) »).
   Le filet de gauche et l'encre ne font que hierarchiser ce que le texte dit deja, ce qui est
   la condition pour qu'un daltonien lise la meme urgence. */
.shell-bell-row.is-day > a {
    border-left-color: var(--app-bad);
}

.shell-bell-row.is-day .shell-bell-when {
    color: var(--app-bad);
}

.shell-bell-row.is-urgent > a {
    border-left-color: var(--app-amber-ink);
}

.shell-bell-row.is-urgent .shell-bell-when {
    color: var(--app-amber-ink);
}

.app-greeting {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.app-greeting h1 {
    margin: 0;
    font: 700 23px/1.2 var(--app-font);
    letter-spacing: -.01em;
    color: var(--app-ink);
}

.app-greeting p {
    margin: 0;
    font: 400 13.5px/1.5 var(--app-font);
    color: var(--app-muted);
}

/* ------------------------------------------------------------------ cartes */

/*
 * LA CARTE — « cartes blanches, coins arrondis, filet leger, ombre tres douce ».
 *
 * L'OMBRE EST L'AJOUT DU 12/08/2026 : il n'y en avait AUCUNE, et c'est ce qui donnait aux
 * cartes leur air de rectangles dessines plutot que poses. Elle est deliberement a la
 * limite du visible — 1 px de decalage, 2 px de flou, 6 % d'encre — parce que la reference
 * detache ses cartes par le FILET, l'ombre ne faisant qu'asseoir le blanc sur le fond
 * bleute. Une ombre plus marquee ferait flotter quatre tuiles cote a cote.
 *
 * ELLE EST DERIVEE DU JETON D'ENCRE, PAS ECRITE EN LITTERAL : une ombre a besoin d'un
 * canal alpha, et la palette ne porte pas de jeton de composantes separees. `color-mix`
 * resout le probleme sans en ajouter un — l'ombre suit --app-ink si sa valeur bouge, la ou
 * un `rgba(...)` recopie a la main aurait derive en silence. C'est exactement le defaut
 * qu'on vient de trouver plus bas sur .searchable-results, restee sur l'ANCIENNE encre.
 *
 * Le padding passe de 14px 15px a 15px 16px, et le gap de 7px a 8px : meme pas court que
 * .app-main, pour la meme demande d'air.
 */
.app-card {
    background: var(--app-surface);
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    padding: 15px 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--app-ink) 6%, transparent);
}

.app-actions {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
}

/*
 * LA CARTE D'ACTION — « cartes d'indicateurs avec un filet de couleur A GAUCHE et un carre
 * d'icone teinte a droite ». Le filet PASSE DU HAUT A LA GAUCHE le 12/08/2026.
 *
 * ANCIENNES DECLARATIONS, gardees parce qu'elles disent d'ou l'on vient :
 *     border-top-width: 3px; border-top-style: solid;
 *     .is-navy/.is-steel/.is-amber posaient `border-top-color`.
 *
 * POURQUOI LA GAUCHE PLUTOT QUE LE HAUT, au-dela de « la reference fait comme ca » : le
 * filet du haut se lit comme un COUVERCLE, il ferme la carte ; celui de gauche se lit
 * comme un ONGLET, il la classe. Trois cartes en rangee portant chacune sa couleur a
 * gauche donnent une colonne de reperes alignes sur le bord du contenu, ce que le haut ne
 * peut pas faire puisque les trois filets s'y confondent en une seule ligne brisee.
 *
 * LE PADDING GAUCHE EST COMPENSE, ET C'EST UNE MESURE : la bordure gauche passe de 1 px a
 * 3 px, donc le contenu se decalerait de 2 px vers la droite et les trois cartes de la
 * rangee ne s'aligneraient plus avec les tuiles en dessous. `padding-left` passe donc de
 * 18px a 16px — 16 + 3 = 19, contre 18 + 1 = 19 avant. Le bord interieur ne bouge PAS.
 * Les trois autres cotes suivent l'air des autres cartes (18px 17px a droite et en bas).
 */
.app-action {
    padding: 18px 18px 17px 16px;
    gap: 11px;
    border-left-width: 3px;
    border-left-style: solid;
}

.app-action.is-navy { border-left-color: var(--app-navy); }
.app-action.is-steel { border-left-color: var(--app-steel); }
.app-action.is-amber { border-left-color: var(--app-amber); }

.app-action-head {
    display: flex;
    align-items: center;
    gap: 11px;
}

.app-action-icon {
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: 9px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.app-action-icon svg,
.app-action-icon .app-icon {
    width: 17px;
    height: 17px;
}

/*
 * LES TROIS CARRES D'ICONE TEINTES. LE VARIANT « STEEL » PORTAIT LE DERNIER GRIS CHAUD DE
 * LA FEUILLE, ET C'EST LA TROUVAILLE DU RECENSEMENT DU 12/08/2026.
 *
 * ANCIENNES VALEURS, ECRITES EN DUR : `background: #F3F1EC; color: #4A4F5C;`
 *
 * #F3F1EC ETAIT L'ANCIEN `--app-surface-alt`, le beige d'avant le refroidissement. Ecrit
 * en litteral, il n'a pas suivi la reecriture de la palette : ses deux voisins de rangee
 * (is-navy sur --app-tint, is-amber sur --app-amber-bg) etaient passes au froid, lui non.
 * VU A L'ECRAN AVANT CORRECTION, capture de reference a 1440 px : le carre d'« Equiper un
 * utilisateur » tirait visiblement au jaune entre deux carres bleutes — exactement la
 * « tache jaune » que l'en-tete de la palette decrit, et le seul endroit de la coquille ou
 * elle subsistait.
 *
 * NOUVELLES VALEURS ET LEUR CHOIX : `var(--app-surface-alt)` (#F1F4F8) est le SUCCESSEUR
 * DIRECT de #F3F1EC dans la nouvelle palette — meme role, meme clarte, temperature froide.
 * `var(--app-ink-soft)` (#344054) remplace #4A4F5C, gris neutre orphelin : 9,48:1 sur le
 * nouveau fond, la ou l'ancien couple mesurait 7,26:1. Aucun jeton n'a ete ajoute — voir
 * le rapport, un couple `--app-steel-bg`/`--app-steel-ink` aurait ete le jeton « juste »,
 * mais deux jetons pour un seul carre d'icone ne se justifient pas.
 */
.app-action-icon.is-navy { background: var(--app-tint); color: var(--app-navy); }
.app-action-icon.is-steel { background: var(--app-surface-alt); color: var(--app-ink-soft); }
.app-action-icon.is-amber { background: var(--app-amber-bg); color: var(--app-amber-ink); }

.app-action h2 {
    margin: 0;
    flex: 1;
    font: 600 15.5px/1.25 var(--app-font);
    color: var(--app-ink);
}

.app-action p {
    margin: 0;
    font: 400 12.5px/1.45 var(--app-font);
    color: var(--app-muted);
    text-wrap: pretty;
    min-height: 36px;
}

/* ------------------------------------------------------------------ boutons */

/*
 * LE BOUTON CENTRE SON PROPRE LIBELLE — `align-items: center` ajoute le 11/08/2026 (N64).
 *
 * ── LE DEFAUT MESURE, ET IL EST NE DE `display: inline-flex` ────────────────
 *
 * Le responsable produit, le 11/08/2026 : « Le texte n'est pas centre sur les bouton ». Mesure au navigateur sur le
 * gabarit REEL des arrivees, feuille neuve prouvee, dans la cellule d'action d'une ligne de
 * check-list : boite du bouton 32,0 px de haut, boite du texte 13,6 px, et le texte a **-0,8 px du
 * bord HAUT pour 19,2 px du bord BAS** — donc 20,0 px d'ecart, le libelle colle en haut. A l'ecart
 * gauche/droite, 7,8 px des deux cotes : le defaut est PUREMENT vertical.
 *
 * LA CAUSE N'EST PAS LE `padding` NI UN `line-height` : ce bouton est un CONTENEUR EN LIGNE
 * (`inline-flex` ci-dessous) et il ne disait pas comment aligner son contenu, donc `align-items`
 * valait `normal`, c'est-a-dire `stretch`. Le texte d'un bouton est un element flexible ANONYME :
 * etire, il occupe toute la hauteur du bouton et sa ligne se pose EN HAUT de cette boite.
 *
 * ── POURQUOI LA CORRECTION EST ICI ET NON DANS LA FEUILLE DE L'ONGLET ───────
 *
 * Le defaut ne se VOIT que la ou une HAUTEUR est imposee au bouton — sans hauteur, la boite de
 * contenu vaut exactement la ligne, donc `stretch` et `center` tombent au meme pixel. Aujourd'hui un
 * seul endroit du depot impose cette hauteur (`.arv-needs-table td.arv-need-actions .app-btn`, 32 px),
 * mais la classe est employee par tous les ecrans : corriger dans l'onglet aurait laisse le piege
 * intact pour le prochain qui poserait une hauteur, un `min-height` ou un bouton etire par sa grille.
 *
 * L'IMPACT SUR LES AUTRES ECRANS EST MESURE ET IL EST NUL, sur DEUX faits verifies avant d'ecrire :
 * aucun `.app-btn` du depot ne porte autre chose qu'un LIBELLE (releve sur les vingt-six usages de
 * templates/), et aucune autre feuille ne lui impose de hauteur. Releve au navigateur sur les quatre
 * boutons de l'ecran des arrivees qui n'en portent pas — filtre, correction du dossier, archivage du
 * dossier, ajout d'un besoin — : boites et ecarts IDENTIQUES avant et apres, 7,4 px en haut et 7,9 px
 * en bas dans les deux cas. Apres, dans la cellule : 9,2 px et 9,3 px, soit centre a 0,1 px pres.
 *
 * `justify-content` N'EST PAS AJOUTE, et c'est deliberement : aucune largeur n'est imposee a un
 * bouton du projet, donc sa boite epouse son libelle et `flex-start` tombe deja au centre — mesure
 * 7,8 px de chaque cote. Une declaration qui ne change rien serait une regle a entretenir pour rien.
 */
.app-btn { display: inline-flex; text-decoration: none;
    align-self: flex-start;
    align-items: center;
    padding: 9px 14px;
    border-radius: var(--app-radius-sm);
    font: 600 12.5px/1 var(--app-font);
    border: 1px solid transparent;
    cursor: pointer;
}

.app-btn.is-primary {
    background: var(--app-ink);
    color: #fff;
}

.app .app-btn.is-primary,
.app .equipment-primary,
.app .equipment-create-link,
.app .records-primary-link,
.app .equipment-filter-actions button {
    color: #fff !important;
    -webkit-text-fill-color: #fff;
}

.searchable-combobox { position: relative; width: 100%; }
.searchable-combobox > input {
    box-sizing: border-box;
    width: 100%;
    height: 43px;
    padding: 0 38px 0 12px;
    /* N129, 13/08/2026 : var(--app-line-strong) #CFD6E0 (1,46:1 sur --app-surface, 1,29:1
       sur --app-tint) -> var(--app-line-control) #7C8798 (3,64:1 et 3,22:1). La LARGEUR
       reste 1 px, voir le commentaire de l'etat de mise au point juste dessous. */
    border: 1px solid var(--app-line-control);
    border-radius: 9px;
    outline: 0;
    background: var(--app-surface);
    color: var(--app-navy);
    font: 500 13px/1 var(--app-font);
}
/*
 * LA MISE AU POINT DU CHAMP CHERCHABLE — deux valeurs en dur remplacees le 12/08/2026.
 *
 * ANCIENNES : `border-color: var(--app-navy); box-shadow: 0 0 0 3px rgba(38,53,107,.1);`
 * rgba(38,53,107) est #26356B — LE MARINE DE LA MAQUETTE D'ORIGINE, plus celui de la
 * palette : elle porte #1B2A4A depuis le 12/08. L'anneau tirait donc sur un bleu que plus
 * rien d'autre dans la feuille n'employait, defaut invisible a l'oeil mais reel.
 *
 * NOUVELLES : l'accent interactif. L'en-tete de la palette designe `--app-accent` pour
 * « l'anneau de mise au point » et `--app-accent-bg` pour « le carre teinte derriere une
 * icone ou une pastille » — un halo de focus est ce carre-la, pas un fond de page, donc
 * l'emploi respecte la reserve ecrite sur le jeton. Cet anneau rejoint ainsi celui de
 * `.app-nav a:focus-visible` et de ses voisins, qui tirent deja sur --app-link (meme
 * valeur) : l'application n'a plus qu'UNE couleur de mise au point.
 *
 * LA HAUTEUR DE 43 px N'EST PAS TOUCHEE, ET C'EST DELIBERE : `ArrivalFormBenchTest`
 * la recopie dans sa constante FIELD_HEIGHT et son commentaire dit que la changer ici
 * rouvre un ecart de 3 px entre deux champs voisins du formulaire d'arrivee. La bordure
 * reste a 1 px et l'anneau est une OMBRE, qui ne participe pas a la boite : la geometrie
 * mesuree par ce banc est identique avant et apres.
 */
.searchable-combobox > input:focus { border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-accent-bg); }
.searchable-combobox > input::-webkit-search-cancel-button { appearance: none; }
.searchable-combobox::after { position: absolute; top: 50%; right: 13px; transform: translateY(-50%); color: var(--app-faint); content: '⌕'; pointer-events: none; }
.searchable-clear {
    position: absolute;
    z-index: 1;
    top: 50%;
    right: 5px;
    width: 32px;
    height: 32px;
    padding: 0;
    transform: translateY(-50%);
    border: 0;
    border-radius: 50%;
    background: var(--app-surface);
    color: var(--app-link);
    font: 20px/1 var(--app-font);
    cursor: pointer;
}
.searchable-combobox > input:placeholder-shown + .searchable-clear { display: none; }
.searchable-clear:focus-visible { outline: 2px solid var(--app-accent); outline-offset: 1px; }
.searchable-results {
    position: absolute;
    z-index: 100;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 280px;
    overflow-y: auto;
    padding: 4px;
    /* N129, 13/08/2026 : meme bascule que le champ au-dessus, #CFD6E0 -> #7C8798, et pour
       la meme raison — ce panneau flottant est la LISTE DEROULANTE du champ cherchable,
       donc un controle. Il flotte au-dessus d'un formulaire blanc et son fond est blanc :
       avant ce lot, seule son ombre le detachait. 1,29:1 -> 3,22:1 au pire fond. */
    border: 1px solid var(--app-line-control);
    border-radius: 9px;
    /* DEUX VALEURS EN DUR REMPLACEES LE 12/08/2026.
       Ancien fond : `#fff` — un blanc juste, mais c'est une SURFACE, et --app-surface est
       le jeton qui la nomme. Ecrit en litteral il echappait au recensement des surfaces.
       Ancienne ombre : `rgba(22,34,74,.16)`, soit #16224A — L'ANCIENNE ENCRE, celle d'avant
       le 12/08 (la palette porte #1B2A4A). C'est le cas d'ecole du litteral qui derive :
       la valeur etait juste le jour ou elle a ete ecrite et fausse le lendemain, sans que
       rien ne rougisse. `color-mix` la rattache au jeton, donc elle ne pourra plus deriver.
       L'ombre reste FRANCHE ici (14 px de decalage, 30 px de flou, 16 %) et non « tres
       douce » comme celle des cartes : ce panneau FLOTTE au-dessus du formulaire, il doit
       se detacher de ce qu'il recouvre — ce n'est pas la meme intention visuelle. */
    background: var(--app-surface);
    box-shadow: 0 14px 30px color-mix(in srgb, var(--app-ink) 16%, transparent);
}
.searchable-results[hidden] { display: none; }
.searchable-results button {
    display: block;
    width: 100%;
    padding: 10px 11px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--app-ink);
    font: 500 12px/1.25 var(--app-font);
    text-align: left;
    cursor: pointer;
}
.searchable-results button:hover, .searchable-results button.is-active { background: var(--app-surface-alt); }
.searchable-select-source {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    border: 0 !important;
    opacity: 0;
    pointer-events: none;
}

/* LE BOUTON SOBRE, ET SA BORDURE EST LE DEFAUT QUE N123 AVAIT DECLARE — N129, 13/08/2026.
   Ancienne valeur : var(--app-line-strong), #CFD6E0 = 1,46:1 sur --app-surface, 1,29:1 sur
   --app-tint. Nouvelle : var(--app-line-control), #7C8798 = 3,64:1 et 3,22:1. Le motif du
   choix vit une seule fois, sur le jeton, dans le bloc de palette — pas recopie ici.
   `color` N'EST PAS TOUCHE : le libelle reste --app-ink, 14,22:1, la mesure de N123.
   `background` non plus, ni la largeur du trait : le correctif est une COULEUR. La forme
   servie aux fenetres des arrivees ET des achats est donc la meme apres qu'avant — elles
   tirent toutes deux cette classe d'app.css, c'est ce que N123 a accorde. */
.app-btn.is-outline {
    background: var(--app-surface);
    color: var(--app-ink);
    border-color: var(--app-line-control);
}

.app-btn.is-amber {
    background: var(--app-amber);
    color: var(--app-ink);
}

/*
 * UN BOUTON VERS UN ECRAN QUI N'EXISTE PAS : il garde la couleur de la maquette —
 * c'est ce que le responsable produit a demande — mais il est visiblement eteint et son infobulle dit ce
 * qui l'activera. Un « Commencer » vivant vers un 404 casserait la confiance dans
 * tous les autres boutons.
 */
.app-btn:disabled {
    cursor: not-allowed;
}

/* ---------------------------------------------------------------- colonnes */

.app-columns {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 344px;
    gap: 16px;
    align-items: start;
}

.app-col-main,
.app-col-rail {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* ------------------------------------------------------------------ tuiles */

.app-kpis {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
}

.app-kpi-label {
    font: 500 11.5px/1.2 var(--app-font);
    color: var(--app-muted);
}

.app-kpi-value {
    font: 700 27px/1 var(--app-font);
    letter-spacing: -.02em;
    color: var(--app-ink);
    font-variant-numeric: tabular-nums;
}

/* « N/A » n'est pas un chiffre et ne doit pas en avoir l'air : plus petit, plus pale.
   L'encre passe de var(--app-faint) a var(--app-muted) le 12/08/2026 (2,58:1 -> 4,97:1).
   LA HIERARCHIE EST PRESERVEE et c'est ce qui rend le changement possible : le contraste
   avec le chiffre voisin est porte par la TAILLE (15 px contre 27) et par la GRAISSE (500
   contre 700), pas par la paleur seule. « N/A » reste visiblement moins fort qu'un 812,
   mais il redevient lisible — c'est une REPONSE (« la donnee n'existe pas », consigne
   du responsable produit), et une reponse doit se lire. */
.app-kpi-value.is-unavailable {
    font: 500 15px/1.8 var(--app-font);
    color: var(--app-muted);
    letter-spacing: 0;
    font-variant-numeric: normal;
}

/* La legende sous un chiffre (« Unites et lots vivants, hors archives »). Encre passee de
   var(--app-faint) a var(--app-muted) : 2,58:1 -> 4,97:1. C'est la phrase qui dit CE QUE
   COMPTE la tuile ; sans elle, le chiffre est ambigu. Donc du contenu, donc 4,5:1. */
.app-kpi-sub {
    font: 400 11.5px/1.4 var(--app-font);
    color: var(--app-muted);
    text-wrap: pretty;
}

/* ------------------------------------------------------------------ panneaux */

/* Meme ombre tres douce que .app-card, et pour le meme motif : le panneau et la carte
   sont deux tailles d'une meme surface, les separer visuellement n'aurait aucun sens.
   Padding passe de 15px 18px a 17px 19px (l'air demande le 12/08/2026). */
.app-panel {
    background: var(--app-surface);
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    padding: 17px 19px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--app-ink) 6%, transparent);
}

.app-panel-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.app-panel-head h2 {
    margin: 0;
    flex: 1;
    font: 600 15px/1.2 var(--app-font);
    color: var(--app-ink);
}

/*
 * LA NOTE D'UN PANNEAU. Encre passee de var(--app-faint) a var(--app-muted) le
 * 12/08/2026 : 2,58:1 -> 4,97:1 sur blanc.
 *
 * CETTE CLASSE EST SURCHARGEE, ET C'EST CE QUI REND LE CHANGEMENT OBLIGATOIRE plutot que
 * confortable. Elle sert (1) la note discrete d'un en-tete de panneau, (2) les deux
 * paragraphes d'explication du « Parc en un coup d'oeil », et surtout (3) LE MESSAGE DE
 * REFUS D'UN CHAMP du formulaire d'arrivee, ou le gabarit lui ajoute `role="alert"` —
 * verifie dans templates/arrivees/ et dans ArrivalFormBenchTest. Un message d'erreur a
 * 2,58:1 est un defaut d'accessibilite franc, pas une nuance de gris.
 *
 * ELLE N'EST PAS PASSEE AU BLEU DU PANNEAU D'AIDE de la reference, alors que la tentation
 * etait la : elle habille aussi des refus, et un message d'erreur bleu serait pire que
 * gris. Le panneau d'aide demande une classe a lui — voir le rapport, elle manque au
 * gabarit et n'a PAS ete ajoutee ici.
 */
.app-panel-note {
    font: 400 12px/1.35 var(--app-font);
    color: var(--app-muted);
}

.app-panel-link { text-decoration: none;
    font: 500 12.5px/1 var(--app-font);
    color: var(--app-link);
}

.app-panel-link.is-pending {
    color: var(--app-faint);
    cursor: not-allowed;
}

.app-empty {
    margin: 0;
    padding: 14px 0;
    font: 400 13px/1.5 var(--app-font);
    color: var(--app-muted);
    text-wrap: pretty;
}

/* Le « Parc en un coup d'oeil » : lignes libellé/valeur, comme la maquette. */

.app-glance {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
}

.app-glance li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 0;
    /* LE FILET PASSE DE var(--app-surface-alt) A var(--app-line) LE 12/08/2026. C'etait un
       jeton de FOND employe comme jeton de LIGNE : #F1F4F8 sur du blanc mesure 1,10:1,
       autrement dit un filet qu'on ne voit pas — les lignes du panneau paraissaient
       collees. --app-line (#E4E8EF) est le jeton des filets, ce qui reste
       tres discret mais existe (mesure : 1,23:1). Aucun plancher WCAG ici : separation decorative,
       la structure etant portee par la liste <ul>/<li> du gabarit. */
    border-bottom: 1px solid var(--app-line);
    font: 400 13px/1.3 var(--app-font);
    color: var(--app-ink-soft);
}

.app-glance li:last-child {
    border-bottom: 0;
}

.app-glance-value {
    font: 700 13px/1 var(--app-font);
    color: var(--app-ink);
    font-variant-numeric: tabular-nums;
}

/* Meme geste que .app-kpi-value.is-unavailable, et pour la meme raison : la graisse (500
   contre 700) porte deja l'affaiblissement, l'encre n'a pas besoin de le porter aussi.
   var(--app-faint) -> var(--app-muted), 2,58:1 -> 4,97:1. */
.app-glance-value.is-unavailable {
    font-weight: 500;
    color: var(--app-muted);
}

.app-main a {
    color: var(--app-link);
    text-decoration: none;
}

.app-main a:hover {
    color: var(--app-link-hover);
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.app-global-topbar {
    position: sticky;
    top: 0;
    z-index: 70;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 16px;
    margin: -24px -30px 0;
    padding: 11px 30px;
    border-bottom: 1px solid var(--app-line);
    background: rgba(255, 255, 255, .97);
    box-shadow: 0 8px 22px rgba(27, 42, 74, .05);
    backdrop-filter: blur(10px);
}

.app-global-topbar.is-compact {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
}

.app-global-topbar.is-compact > .global-search-simple {
    grid-column: 2;
}

.app-global-topbar.is-compact > .shell-actions {
    grid-column: 3;
}

.app-global-topbar__breadcrumb {
    min-width: 0;
    overflow: hidden;
}

.app-global-topbar > .shell-actions {
    justify-self: end;
    margin-left: 0;
}

.global-search-simple {
    position: relative;
    display: grid;
    grid-template-columns: minmax(260px, 1fr) auto;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: min(100%, 620px);
    max-width: 620px;
    margin: 0;
}

.global-search-simple__field {
    position: relative;
    min-width: 0;
}

.global-search-simple__field > .app-icon {
    position: absolute;
    z-index: 1;
    top: 50%;
    left: 14px;
    width: 17px;
    height: 17px;
    color: var(--app-muted);
    transform: translateY(-50%);
    pointer-events: none;
}

.global-search-simple input {
    width: 100%;
    min-height: 42px;
    border: 1px solid var(--app-line-control);
    border-radius: 10px;
    background: var(--app-surface-alt);
    color: var(--app-ink);
    font: 400 14px/1.3 var(--app-font);
    padding: 9px 48px 9px 42px;
    transition: border-color .16s ease, box-shadow .16s ease, background-color .16s ease;
}

.global-search-simple input:focus {
    outline: 0;
    border-color: var(--app-link);
    background: var(--app-surface);
    box-shadow: 0 0 0 3px var(--app-accent-bg-strong);
}

.global-search-primary input,
.global-search-filters input,
.global-search-filters select {
    min-height: 42px;
    border: 1px solid var(--app-line-control);
    border-radius: 9px;
    background: var(--app-surface);
    color: var(--app-ink);
    font: 400 14px/1.3 var(--app-font);
    padding: 9px 12px;
}

.global-search-filters select {
    width: 100%;
    min-width: 0;
}

.global-search-primary button,
.global-search-actions button {
    min-height: 42px;
    border: 0;
    border-radius: 9px;
    background: var(--app-navy);
    color: #fff;
    font: 600 14px/1 var(--app-font);
    padding: 0 16px;
    cursor: pointer;
}

.global-search-simple__field > button {
    position: absolute;
    top: 4px;
    right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--app-link);
    cursor: pointer;
}

.global-search-simple__field > button:hover {
    background: var(--app-accent-bg);
}

.global-search-simple__field > button .app-icon {
    width: 17px;
    height: 17px;
}

.global-search-simple__advanced {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 42px;
    border-radius: 9px;
    padding: 0 10px;
    color: var(--app-link);
    font: 600 13px/1.2 var(--app-font);
    white-space: nowrap;
}

.global-search-simple__advanced:hover {
    background: var(--app-accent-bg);
}

.global-search-simple__advanced .app-icon {
    width: 17px;
    height: 17px;
}

.global-search-suggestions {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    left: 0;
    z-index: 90;
    overflow: hidden;
    max-height: min(470px, calc(100vh - 92px));
    border: 1px solid var(--app-line-strong);
    border-radius: 12px;
    background: var(--app-surface);
    box-shadow: 0 18px 42px rgba(27, 42, 74, .18);
}

.global-search-suggestions__status {
    margin: 0;
    padding: 18px;
    color: var(--app-muted);
    text-align: center;
    font: 500 13px/1.4 var(--app-font);
}

.global-search-suggestions ul {
    overflow-y: auto;
    max-height: 390px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.global-search-suggestions li + li {
    border-top: 1px solid var(--app-line);
}

.global-search-suggestion {
    display: grid;
    gap: 5px;
    padding: 11px 14px;
    color: var(--app-ink) !important;
}

.global-search-suggestion:hover,
.global-search-suggestion.is-active {
    background: var(--app-surface-alt);
}

.global-search-suggestion__heading {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.global-search-suggestion__heading strong {
    overflow: hidden;
    font: 600 13px/1.3 var(--app-font);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.global-search-suggestion__type,
.global-search-suggestion__historical {
    flex: none;
    border-radius: 999px;
    padding: 4px 7px;
    font: 700 10px/1 var(--app-font);
    letter-spacing: .03em;
}

.global-search-suggestion__type {
    background: var(--app-accent-bg);
    color: var(--app-link);
}

.global-search-suggestion__historical {
    margin-left: auto;
    background: var(--app-surface-alt);
    color: var(--app-muted);
}

.global-search-suggestion small {
    overflow: hidden;
    color: var(--app-muted);
    font: 400 12px/1.3 var(--app-font);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.global-search-suggestions__all {
    display: block;
    border-top: 1px solid var(--app-line);
    padding: 11px 14px;
    background: var(--app-surface-alt);
    color: var(--app-link);
    font: 700 12px/1.2 var(--app-font);
    text-align: center;
}

.global-search-simple :focus-visible,
.global-search-panel :focus-visible,
.global-search-group a:focus-visible {
    outline: 3px solid var(--app-link);
    outline-offset: 2px;
}

.global-search-page {
    display: grid;
    gap: 20px;
}

.global-search-heading h1,
.global-search-section h2,
.global-search-group h3 {
    margin: 0;
    color: var(--app-navy);
}

.global-search-heading p,
.global-search-section header p {
    margin: 6px 0 0;
    color: var(--app-muted);
}

.global-search-kicker {
    color: var(--app-amber-ink) !important;
    font: 700 12px/1 var(--app-font);
    letter-spacing: .08em;
    text-transform: uppercase;
}

.global-search-panel,
.global-search-empty,
.global-search-group {
    border: 1px solid var(--app-line);
    border-radius: 14px;
    background: var(--app-surface);
    box-shadow: 0 8px 24px rgba(27, 42, 74, .05);
}

.global-search-panel { padding: 18px; }

.global-search-primary > label {
    display: block;
    margin-bottom: 7px;
    color: var(--app-ink-soft);
    font: 600 14px/1.2 var(--app-font);
}

.global-search-primary > div {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 8px;
}

.global-search-primary small {
    display: block;
    margin-top: 7px;
    color: var(--app-muted);
}

.global-search-advanced {
    margin-top: 16px;
    border-top: 1px solid var(--app-line);
    padding-top: 14px;
}

.global-search-advanced summary {
    color: var(--app-link);
    font: 600 14px/1.3 var(--app-font);
    cursor: pointer;
}

.global-search-filters {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
    margin-top: 16px;
}

.global-search-filters fieldset,
.global-search-filters > label {
    display: grid;
    align-content: start;
    gap: 8px;
    min-width: 0;
    margin: 0;
    border: 1px solid var(--app-line);
    border-radius: 10px;
    padding: 12px;
    color: var(--app-ink-soft);
    font: 500 13px/1.3 var(--app-font);
}

.global-search-filters legend {
    padding: 0 5px;
    color: var(--app-navy);
    font-weight: 700;
}

.global-search-filters fieldset > label { display: grid; gap: 5px; }
.global-search-filters fieldset:first-child > label { display: flex; align-items: center; }
.global-search-filters fieldset:first-child input { min-height: auto; }

.global-search-actions {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-top: 16px;
}

.global-search-empty { padding: 30px; text-align: center; }
.global-search-empty p { margin: 7px 0 0; color: var(--app-muted); }

.global-search-section {
    display: grid;
    gap: 12px;
}

.global-search-section.is-historical {
    border-top: 2px solid var(--app-line-strong);
    padding-top: 20px;
}

.global-search-groups {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}

.global-search-group { overflow: hidden; }

.global-search-group > header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    background: var(--app-surface-alt);
}

.global-search-group > header span {
    border-radius: 999px;
    background: var(--app-navy);
    color: #fff;
    font: 700 12px/1 var(--app-font);
    padding: 5px 8px;
}

.global-search-group > header a {
    margin-left: auto;
    color: var(--app-link);
    font-weight: 700;
}

.global-search-group ul { margin: 0; padding: 0; list-style: none; }
.global-search-group li + li { border-top: 1px solid var(--app-line); }
.global-search-group li:hover { background: var(--app-surface-alt); }

.global-search-group li a {
    display: grid;
    gap: 4px;
    padding: 14px 16px;
    color: var(--app-ink);
}

.global-search-group li strong { overflow-wrap: anywhere; }
.global-search-group li small { color: var(--app-muted); overflow-wrap: anywhere; }

.global-search-result-type,
.global-search-match {
    color: var(--app-muted);
    font: 500 11px/1.35 var(--app-font);
    letter-spacing: .02em;
}

.global-search-match {
    width: fit-content;
    margin-top: 3px;
    border-radius: 6px;
    background: var(--app-accent-bg);
    color: var(--app-link);
    padding: 4px 7px;
    overflow-wrap: anywhere;
}

.global-search-group-empty { margin: 0; padding: 18px 16px; color: var(--app-muted); }

@media (min-width: 1001px) {
    .global-search-simple {
        /* La topbar vit dans le main : la demi-sidebar compense son centre pour viser le viewport. */
        transform: translateX(calc(var(--app-side-width) / -2));
    }
}

@media (min-width: 1600px) {
    .global-search-simple {
        display: block;
        width: 620px;
        max-width: 620px;
    }

    .global-search-simple input {
        box-sizing: border-box;
    }

    .global-search-simple__advanced {
        position: absolute;
        top: 0;
        left: calc(100% + 10px);
    }
}

/* ------------------------------------------------------------------ etroit */

@media (max-width: 1240px) {
    .app-columns {
        grid-template-columns: 1fr;
    }

    .app-kpis {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 1000px) {
    .app-global-topbar {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: flex-start;
        gap: 10px;
    }

    .app-global-topbar__breadcrumb {
        flex: 1 1 100%;
    }

    .global-search-simple {
        grid-template-columns: minmax(0, 1fr) auto;
        min-width: 0;
        width: auto;
        max-width: none;
        flex: 1 1 420px;
    }

    .app-global-topbar > .shell-actions {
        flex: 0 0 auto;
        margin-left: auto;
    }
}

@media (max-width: 900px) {
    .app-actions {
        grid-template-columns: 1fr;
    }

    .global-search-filters,
    .global-search-groups {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 820px) {
    body.app {
        flex-direction: column;
    }

    .app-side {
        width: auto;
        height: auto;
        position: static;
        border-right: 0;
        border-bottom: 1px solid var(--app-line);
    }

    .app-side-foot {
        margin-top: 0;
    }

    .app-main {
        padding: 16px 16px 28px;
    }

    .app-global-topbar {
        margin: -16px -16px 0;
        padding: 9px 16px;
    }

    .app-kpis {
        grid-template-columns: 1fr;
    }

    .app-search {
        margin-left: 0;
    }

    .global-search-simple {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .global-search-simple__advanced {
        width: 42px;
        padding: 0;
        justify-content: center;
    }

    .global-search-simple__advanced-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
    }
}
