/*
 * design.css — Le langage visuel de CheckVR.
 *
 * Chargé en DERNIER dans l'en-tête de chaque page de l'application, après ses propres
 * styles : à spécificité égale la dernière règle gagne, et c'est voulu. Chaque page
 * garde sa mise en page (grilles, écrans, modales) ; ce fichier tient ce qui doit être
 * IDENTIQUE d'une page à l'autre — titres, boutons, cartes, champs, badges, tableaux,
 * états vides. Avant lui, neuf pages redéclaraient `.btn` et `.card` chacune à sa
 * façon, avec neuf rayons de bordure.
 *
 * ── L'identité : elle sort du fond, pas d'à côté ─────────────────────────────────
 *
 * Le fond historique de CheckVR (images/UI/00_BACKGROUND.png, servi réduit en
 * rayures.jpg) est le logo lui-même en grand : des bandes obliques, orange en haut,
 * sarcelle en bas, avec des rubans clairs. Il a été MESURÉ, et tout ce qui suit en
 * découle — les couleurs ci-dessous sont lues dans l'image, pas choisies à côté :
 *
 *   1. LA COULEUR. L'action est l'orange de sa bande la plus saturée (#FF8906) ; les
 *      cartes de chiffres et les zones prennent quatre hauteurs de son dégradé (orange,
 *      olive, vert, sarcelle) ; l'encre des titres est son sarcelle du bas, assombri
 *      (#163836) — la barre latérale aussi. Aucune couleur n'est étrangère à l'image.
 *   2. UN BANDEAU, SUR TOUTES LES PAGES. Chaque page s'ouvre sur la même chose : un
 *      bandeau vert-nuit pleine largeur qui porte l'intitulé, le titre, les actions et
 *      les ONGLETS de la page, fermé par un liseré des rayures. Quand la page a un LIEU,
 *      son panorama 360° s'affiche derrière le voile (`--hero-image`) — sinon les
 *      rayures. Il y avait auparavant un bandeau sur deux pages et un titre nu sur les
 *      autres, plus une barre fixe à part sur Scan & Play : la même application n'avait
 *      pas deux fois la même tête.
 *   3. DEUX MODES, UNE STRUCTURE. Clair par défaut — fond à peine teinté, cartes
 *      blanches — ou sombre, vert-nuit : `html[data-mode="sombre"]`, un réglage de
 *      l'instance. La barre et le panorama sont identiques dans les deux ; seul le
 *      fond de travail change.
 *
 * Et deux constantes de forme : des titres et des chiffres qui portent (graisse 800,
 * interlettrage serré) ; les DONNÉES en DM Mono — libellés de chiffres, codes,
 * en-têtes de tableau — pour distinguer ce qui se lit de ce qui se mesure.
 *
 * ── Ce que ce fichier NE fait PAS ──────────────────────────────────────────────
 * Il ne pose aucune couleur de marque en dur dans les composants : primaire, accent,
 * danger et succès viennent des variables de ThemeLoader.js, donc du thème de
 * l'instance, dont les DÉFAUTS (Utils/Identite.js) sont ces couleurs mesurées. Un client
 * qui s'habille à ses couleurs garde la grammaire — barre, panorama, titres gras — à
 * ses couleurs à lui. Seuls la barre, le repère or et les rayures sont constants.
 *
 * Deux surfaces, deux textes : rien ici ne pose de fond blanc sans dire de quelle
 * couleur est le texte dessus (`tests/unit/LisibiliteDesSurfaces.test.js`).
 */

:root {
    --font-ui: 'Plus Jakarta Sans', 'Poppins', system-ui, -apple-system, sans-serif;
    --font-data: 'DM Mono', ui-monospace, 'SF Mono', Consolas, monospace;

    /* ── Le fond, mesuré (00_BACKGROUND.png, moyenne par tranche de hauteur) ── */
    --f-orange:     #F28E26;   /* la tranche du haut */
    --f-orange-vif: #FF8906;   /* la bande la plus saturée : l'ACTION */
    --f-or:         #FFD876;   /* le ruban clair : les états, le repère de la barre */
    --f-olive:      #B4985C;   /* le milieu, là où chaud et froid se mélangent */
    --f-vert:       #619C8B;
    --f-sarcelle:   #419899;   /* la tranche du bas */
    --f-bleu:       #2FAAC1;   /* le coin bas gauche */
    --vert-nuit:    #163836;   /* le sarcelle du bas, assombri : l'encre et la barre */
    --vert-nuit-2:  #0D2725;

    /* Neutres — tous teintés du même vert que l'encre, pour qu'un gris soit choisi
       et non hérité. */
    --ink: var(--vert-nuit);      /* titres et intitulés sur surface claire */
    --ink-deep: var(--vert-nuit-2);
    /* Texte secondaire. Assombri : à #5F7975 il tombait à 4,2:1 sur une surface
       teintée, sous le seuil de lecture courante. */
    --muted: #59716D;
    --border: #D9E1DF;            /* filet des cartes, champs et boutons secondaires */
    --surface-tint: #F3F6F5;      /* fond léger sous une zone ; le fond de page du mode clair */
    --carte: #FFFFFF;             /* la surface des cartes — translucide en mode sombre */

    /* Le repère de marque : l'orange vif du fond, et l'or de ses rubans. */
    --brand-mark: var(--f-orange-vif);
    --brand-or: var(--f-or);

    /* La texture de marque : l'IMAGE, pas une imitation — un motif CSS uniforme n'en
       avait ni la profondeur ni les largeurs irrégulières. En liseré sous chaque tête
       de page, en grand sur la connexion et l'entrée du stagiaire. Pour les petites
       marques, un dégradé de ses couleurs mesurées (`--stripes-mark`). */
    /* Ces trois-là sont posées par ThemeLoader depuis l'identité RÉSOLUE de la
       surface : la charte du client sur ce qu'il montre à ses stagiaires, celle de
       CheckVR partout ailleurs et en repli obligatoire. Les valeurs ci-dessous sont
       donc ce que voit une page avant la réponse du réseau, et ce qui reste si une
       instance n'a jamais rien réglé. */
    --rayures: url('/images/UI/rayures.jpg');
    --voile-bandeau: linear-gradient(90deg, rgba(22,56,54,0.96) 0%, rgba(22,56,54,0.76) 52%, rgba(22,56,54,0.34) 100%);
    --stripes-mark: linear-gradient(165deg, #F28E26 0%, #FFD876 42%, #619C8B 72%, #419899 100%);

    /* ── Une couleur par zone de l'application ──────────────────────────────
       Posée par ChromeBoot.js via html[data-zone], AVANT la première peinture ;
       sous-menu, cartes de chiffres, états vides et filtres la prennent. Les teintes
       sont celles du FOND, lues dans le dégradé : rien n'est inventé pour l'écran.
       Aucune n'est l'action : l'action reste --color-primary.

       --zone-texte : la même teinte quand elle sert de TEXTE sur blanc — les claires
       (orange, olive, bleu, vert) s'assombrissent pour tenir 4,5:1. */
    --zone: var(--color-primary);
    --zone-texte: var(--zone);

    --radius-card: 14px;
    --radius-field: 10px;
    --radius-btn: 10px;

    --shadow-soft: 0 2px 8px rgba(22, 56, 54, 0.06), 0 1px 3px rgba(22, 56, 54, 0.04);
    --shadow-hover: 0 12px 28px rgba(22, 56, 54, 0.10), 0 2px 6px rgba(22, 56, 54, 0.05);
}

html[data-zone="dashboard"] { --zone: #F28E26; --zone-texte: #B85F00; } /* l'orange du haut */
html[data-zone="contenu"]   { --zone: #619C8B; --zone-texte: #3F7A69; } /* le vert */
html[data-zone="sessions"]  { --zone: #419899; --zone-texte: #2F7F80; } /* le sarcelle du bas */
html[data-zone="analyse"]   { --zone: #2FAAC1; --zone-texte: #1E7F92; } /* le bleu du coin */
html[data-zone="scan"]      { --zone: #FF8906; --zone-texte: #B85F00; } /* la bande la plus saturée */
html[data-zone="profil"]    { --zone: #B4985C; --zone-texte: #7A6430; } /* l'olive du milieu */
html[data-zone="reglages"]  { --zone: #5F7975; }                        /* le neutre teinté */

body {
    font-family: var(--font-ui);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Le ruban : 4 px des rayures du logo en haut de chaque page à menu. La marque,
   en couleur, sans prendre de place — et la même sur tous les écrans. */
html[data-chrome]::before {
    content: '';
    position: fixed;
    top: 0; left: var(--chrome-sidebar-width, 200px); right: 0;
    height: 4px;
    background: var(--rayures) center 45% / cover no-repeat;
    z-index: 960;
    pointer-events: none;
}
@media (max-width: 768px) {
    html[data-chrome]::before { left: 0; }
}

/* ---- Le bandeau de page ------------------------------------------------------
   TOUTE page à menu commence par lui, et il a toujours la même forme :

       intitulé (DM Mono, or)  ·  titre  ·  méta            [ actions ]
       [ onglets ]
       ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ liseré des rayures ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

   Il est pleine largeur : il déborde la gouttière du contenu par des marges négatives,
   d'où `--page-pad-*`, posé ici et appliqué aux conteneurs de page — c'est ce qui rend
   le débord calculable sans que chaque page ait à le savoir.

   Le fond est le panorama du lieu quand la page en a un (`--hero-image`, posé par la
   page), voilé de vert-nuit depuis la gauche pour porter le texte en blanc ; sinon les
   rayures du logo. La structure ne dépend donc pas du catalogue. */

html[data-chrome] {
    --page-pad-x: 32px;
    --page-pad-y: 24px;
}
@media (max-width: 768px) {
    html[data-chrome] { --page-pad-x: 16px; --page-pad-y: 16px; }
}

/* La gouttière des pages est normalisée ici : le bandeau la déborde exactement. */
html[data-chrome] .main-content,
html[data-chrome] .page-content,
html[data-chrome] .container,
html[data-chrome] .app-layout > main {
    padding: var(--page-pad-y) var(--page-pad-x);
}

html[data-chrome] .page-header {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "texte actions" "onglets onglets";
    /* Alignés en HAUT, et pas de hauteur minimale : le titre tombe alors au même
       endroit sur toutes les pages, qu'elles aient des onglets ou non. Avec
       `align-items: end` et un `min-height`, il glissait vers le bas sur une page sans
       onglets — Sessions et Paramètres n'avaient pas le même bandeau. */
    align-items: start;
    gap: 16px 24px;
    margin: 0 calc(-1 * var(--page-pad-x)) 26px;
    padding: 26px var(--page-pad-x) 24px;
    color: #fff;
    background:
        var(--voile-bandeau),
        var(--hero-image, var(--rayures)) center / cover no-repeat,
        var(--chrome-ink, var(--vert-nuit));
}
/* Avec des onglets, le bandeau descend jusqu'à eux : ils tiennent lieu de bas de page. */
html[data-chrome] .page-header.a-onglets { padding-bottom: 0; }

/* L'intitulé passe SOUS le titre. Il le précède dans le HTML — c'est son ordre de
   lecture pour une synthèse vocale, du général au particulier — mais à l'œil il se lit
   mieux en légende, une fois le titre posé. D'où un ordre d'affichage, pas un
   déplacement dans le balisage. */
html[data-chrome] .page-header > div:not(#app-subsidebar):not(.actions):not(.page-actions) {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
html[data-chrome] .page-header .eyebrow { order: 2; }
/* Le lien de retour ouvre le bloc — au-dessus du titre, à sa place dans la lecture.
   Il vivait hors du bandeau, où il formait une bande blanche pour lui tout seul. */
html[data-chrome] .page-header .retour {
    order: 0;
    align-self: start;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 2px;
    color: rgba(255,255,255,0.82);
    font-size: 0.82rem;
    font-weight: 600;
    text-decoration: none;
}
html[data-chrome] .page-header .retour:hover { color: #fff; }
html[data-chrome] .page-header .retour:focus-visible {
    outline: 2px solid var(--f-or);
    outline-offset: 3px;
    border-radius: 4px;
}
html[data-chrome] .page-header .meta,
html[data-chrome] .page-header .page-intro,
html[data-chrome] .page-header p { order: 3; }
html[data-chrome] .page-header h1,
html[data-chrome] .page-header .page-title { order: 1; }
/* Il ne remonte dans la gouttière que s'il ouvre la page : sur la page d'une session,
   un lien de retour le précède, et la marge négative le faisait passer par-dessus. */
html[data-chrome] .page-header:first-child { margin-top: calc(-1 * var(--page-pad-y)); }
/* Un conteneur centré et étroit couperait le bandeau en plein milieu de l'écran : sous
   le menu, la page occupe la largeur, et ce sont ses sections qui se bornent. */
html[data-chrome] .container { max-width: none; margin-inline: 0; }

/* Le texte : le bloc de gauche. Une DIV, et une seule — deux enfants dans la même
   zone de grille se superposent. */
html[data-chrome] .page-header > div:not(#app-subsidebar):not(.actions):not(.page-actions) {
    grid-area: texte;
    min-width: 0;
}
/* Les actions : le groupe s'il existe, sinon le bouton ou le lien posé nu. Sans cette
   seconde moitié, un bouton sans conteneur tombait sur le titre et l'effaçait. */
html[data-chrome] .page-header > .actions,
html[data-chrome] .page-header > .page-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* align-items : sans lui, le défaut `stretch` étirait un bouton à la hauteur de son
   voisin le plus haut — « Se déconnecter » devenait un pavé de 64 px à côté de
   l'initiale du profil. */
html[data-chrome] .page-header > .actions,
html[data-chrome] .page-header > .page-actions,
html[data-chrome] .page-header > .btn,
html[data-chrome] .page-header > a,
html[data-chrome] .page-header > button { grid-area: actions; justify-self: end; }

html[data-chrome] .page-header::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 8px;
    background: var(--rayures) center 40% / cover no-repeat;
}

/* Le texte du bandeau est blanc : il est sur une image. */
html[data-chrome] .page-header h1,
html[data-chrome] .page-header h2,
html[data-chrome] .page-header .page-title {
    color: #fff;
    text-shadow: 0 2px 8px rgba(13,39,37,0.45);
}
html[data-chrome] .page-header h1::before,
html[data-chrome] .page-header .page-title::before { display: none; }
html[data-chrome] .page-header p,
html[data-chrome] .page-header .meta,
html[data-chrome] .page-header .section-subtitle { color: rgba(255,255,255,0.84); }
html[data-chrome] .page-header .eyebrow { color: var(--f-or); }
html[data-chrome] .page-header .eyebrow::before { display: none; }
html[data-chrome] .page-header .meta strong { color: #fff; }
html[data-chrome] .page-header .btn-secondary,
html[data-chrome] .page-header .btn-ghost {
    background: rgba(255,255,255,0.94);
    color: var(--vert-nuit);
    border-color: transparent;
}
html[data-chrome] .page-header .badge { background: rgba(255,255,255,0.16); color: #fff; }

/* Une action secondaire dans le bandeau est une ICÔNE. Le bandeau porte déjà une
   image, un intitulé, un titre, une méta et des onglets : y aligner cinq boutons pleins
   les faisait passer à la ligne — le bandeau devenait plus haut que ceux des autres
   pages — et posait un mur de couleur sur une image. Le libellé n'est pas perdu : il
   vit dans `aria-label` et dans l'infobulle. Une seule action reste NOMMÉE par page,
   celle qu'on vient faire. */
html[data-chrome] .page-header .btn-icone {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1px solid rgba(255,255,255,0.28);
    border-radius: 10px;
    background: rgba(255,255,255,0.12);
    color: #fff;
    cursor: pointer;
    text-decoration: none;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
html[data-chrome] .page-header .btn-icone svg { width: 17px; height: 17px; }
html[data-chrome] .page-header .btn-icone:hover { background: rgba(255,255,255,0.22); border-color: rgba(255,255,255,0.5); }
html[data-chrome] .page-header .btn-icone:focus-visible { outline: 2px solid var(--f-or); outline-offset: 2px; }

/* Le texte du bandeau prend la largeur qu'on lui laisse : aucune borne ici. Une
   largeur maximale y coupait les titres et les chapôs en plein milieu, alors que la
   place est là. C'est la colonne des actions qui doit rétrécir, pas le texte. */
html[data-chrome] .page-header .page-intro,
html[data-chrome] .page-header p,
html[data-chrome] .page-header .meta,
html[data-chrome] .page-header h1 { max-width: none; }

@media (max-width: 768px) {
    html[data-chrome] .page-header {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas: "texte" "actions" "onglets";
        align-items: start;
        min-height: 0;
        padding-top: 18px;
    }
}

/* Une surface habillée aux rayures : le texte dessus est blanc, et seulement des
   titres — le fond est vif. */
.fond-raye {
    background: var(--rayures) center / cover no-repeat;
    color: #fff;
}

/* ---- Titres ----------------------------------------------------------------- */

.page-header h1,
.page-title {
    font-size: 1.9rem;
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.1;
}
.page-title { color: var(--color-title, inherit); }

/* Devant chaque titre de section hors bandeau, une languette aux couleurs du logo. */
.page-title::before {
    content: '';
    display: inline-block;
    width: 10px;
    height: 0.85em;
    border-radius: 3px;
    background: var(--stripes-mark);
    margin-right: 14px;
    vertical-align: -0.06em;
}

@media (max-width: 768px) {
    .page-header h1, .page-title { font-size: 1.45rem; }
}

.card-header h2,
.section-title,
.modal h2 {
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--color-title-on-surface, var(--ink));
}
.card-header h2 { font-size: 1.05rem; }
/* Un trait de la couleur de zone devant chaque titre de carte — le libellé de
   section coloré de Basecamp. */
.card-header h2::before {
    content: '';
    display: inline-block;
    width: 4px;
    height: 0.9em;
    border-radius: 2px;
    background: var(--zone);
    margin-right: 10px;
    vertical-align: -0.1em;
}

/* Libellé de DONNÉE : en DM Mono, capitales espacées, précédé d'une puce de zone.
   La mono dit « ceci se mesure » ; le texte courant reste en Plus Jakarta Sans. */
.eyebrow,
.kpi-card .kpi-label,
.session-stat .lbl,
th {
    font-family: var(--font-data);
    font-size: 0.7rem;
    font-weight: 500;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--muted);
}
.eyebrow::before,
.kpi-card .kpi-label::before {
    content: '';
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--zone);
    margin-right: 8px;
    vertical-align: 0;
}

/* Les cartes de chiffres : quatre hauteurs du dégradé du fond, de gauche à droite —
   orange, olive, vert, sarcelle —, chacune en trait de tête, la valeur en encre. Un
   tableau de bord dont on lit les blocs de loin, et qui est de la couleur du logo. */
.kpi-card { --zone: #F28E26; }
.kpi-grid .kpi-card:nth-child(4n+2) { --zone: #B4985C; }
.kpi-grid .kpi-card:nth-child(4n+3) { --zone: #619C8B; }
.kpi-grid .kpi-card:nth-child(4n+4) { --zone: #419899; }
.kpi-card {
    background: var(--carte);
    color: var(--color-text-on-surface, #333);
}
.kpi-card .kpi-label { color: var(--muted); }
.kpi-card .kpi-label::before { background: var(--zone); }
.kpi-card .kpi-value {
    font-size: 2.25rem;
    font-weight: 800;
    letter-spacing: -0.035em;
    line-height: 1.05;
    color: var(--color-title-on-surface, var(--ink));
}
.kpi-card .kpi-sub { color: var(--muted); }

/* ---- Cartes ----------------------------------------------------------------- */

.card,
.kpi-card,
.session-card,
.scan-card,
.media-card,
.project-card {
    background: var(--carte);
    color: var(--color-text-on-surface, #333);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
}
/* Le trait de tête des cartes de chiffres : APRÈS le raccourci `border` du bloc
   ci-dessus, qui le remettait à 1 px gris. Écrit avant lui, à spécificité égale, il ne
   s'est jamais affiché — ni en clair ni en sombre. */
.kpi-card { border-top: 4px solid var(--zone); }
.media-card:hover,
.project-card:hover {
    box-shadow: var(--shadow-hover);
    border-color: color-mix(in srgb, var(--color-primary) 30%, var(--border));
}

/* ---- Boutons ---------------------------------------------------------------- */

a.btn, a.btn:hover { text-decoration: none; }

.btn {
    font-family: var(--font-ui);
    font-weight: 700;
    font-size: 0.875rem;
    letter-spacing: -0.005em;
    line-height: 1.2;
    padding: 10px 18px;
    border: 1px solid transparent;
    border-radius: var(--radius-btn);
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease,
                border-color 0.15s ease, color 0.15s ease;
}
.btn-sm { padding: 6px 12px; font-size: 0.8rem; }

/* Fantôme : après `.btn`, sinon sa bordure transparente l'emporterait. */
.btn-ghost {
    background: transparent;
    color: var(--ink);
    border-color: var(--border);
}
.btn-ghost:hover { color: var(--color-primary-texte, var(--color-primary)); border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border)); }

/* Un aplat, pas un dégradé. Au survol, trois bandes claires en biais traversent le
   bouton — les rayures du logo, en un seul passage. La couche vit dans ::after pour
   ne pas toucher au fond, qu'un thème client peut avoir changé. */
.btn-primary {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    background: var(--color-primary);
    color: var(--color-on-primary, #fff);
    box-shadow: 0 4px 14px color-mix(in srgb, var(--color-primary) 30%, transparent);
}
.btn-primary::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(112deg,
        transparent 30%, rgba(255,255,255,0.22) 36%, transparent 40%,
        rgba(255,255,255,0.14) 46%, transparent 50%,
        rgba(255,255,255,0.22) 56%, transparent 62%);
    transform: translateX(-100%);
    transition: transform 0.55s ease;
}
.btn-primary:hover {
    background: color-mix(in srgb, var(--color-primary) 88%, #000);
    transform: translateY(-1px);
    box-shadow: 0 8px 20px color-mix(in srgb, var(--color-primary) 38%, transparent);
}
.btn-primary:hover::after { transform: translateX(100%); }
.btn-primary:active { transform: translateY(0); box-shadow: none; }

.btn-secondary {
    background: #fff;
    color: var(--ink);
    border-color: var(--border);
    box-shadow: var(--shadow-soft);
}
/* Un bouton « contour » : ce que les cartes de plan des crédits IA appellent
   `.btn-outline` — la classe n'existait pas, « Sélectionner » ressemblait à du texte. */
.btn-outline {
    background: #fff;
    color: var(--color-primary-texte, var(--color-primary));
    border-color: var(--color-primary);
}
.btn-outline:hover { background: color-mix(in srgb, var(--color-primary) 8%, #fff); color: var(--color-primary-texte, var(--color-primary)); }
.btn-secondary:hover {
    color: var(--color-primary-texte, var(--color-primary));
    border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border));
    transform: translateY(-1px);
    box-shadow: var(--shadow-hover);
}

.btn-danger {
    background: color-mix(in srgb, var(--color-danger) 9%, #fff);
    color: var(--color-danger-texte, var(--color-danger));
    border-color: color-mix(in srgb, var(--color-danger) 25%, transparent);
}
.btn-danger:hover {
    background: var(--color-danger);
    color: #fff;
    border-color: var(--color-danger);
}

.btn:disabled,
.btn[disabled] {
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

/* ---- Le focus clavier : un anneau qui se voit ------------------------------------
   Il était de l'orange à 35 % sur blanc (environ 1,4:1), à 2 px d'un bouton déjà
   orange plein ; les champs, eux, annulaient l'outline et ne gardaient qu'une bordure
   orange (2,3:1). WCAG demande 3:1 pour un indicateur de focus. Au clavier on ne savait
   pas quel bouton était actif.

   L'anneau est l'ENCRE (vert-nuit) sur les surfaces claires, l'OR des rubans sur tout ce
   qui est sombre : la barre, le bandeau, et la page entière en mode sombre — c'est le
   choix que le bandeau faisait déjà pour ses propres liens. Un jeton, `--focus-anneau`,
   que chaque surface sombre redéfinit au lieu d'énumérer ses contrôles.

   `!important` sur l'outline seulement : une douzaine de règles de page écrivent
   `outline: none` au focus avec des sélecteurs plus spécifiques (`.edit-section
   input:focus`, `.pp-champ:focus`). Un indicateur d'accessibilité ne doit pas perdre un
   concours de spécificité contre un style de champ. */
:root { --focus-anneau: var(--chrome-ink, var(--vert-nuit)); }
html[data-mode="sombre"],
html[data-chrome] .page-header,
#app-sidebar { --focus-anneau: var(--brand-or, #FFD876); }

:focus-visible {
    outline: 2px solid var(--focus-anneau) !important;
    outline-offset: 2px;
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible { outline-offset: 1px; }

/* Un interrupteur cache sa case (opacity: 0) : c'est le curseur visible qui porte
   l'anneau, sinon on tabule dessus sans rien voir. */
.toggle input:focus-visible + .toggle-slider,
.toggle-checkbox:focus-visible + .toggle-switch {
    outline: 2px solid var(--focus-anneau);
    outline-offset: 2px;
}

/* Filtres : l'actif en couleur de zone pleine, pas en teinte pâle. */
.filter-btn {
    font-family: var(--font-ui);
    font-weight: 600;
    background: #fff;
    color: var(--ink);
    border: 1px solid var(--border);
    border-radius: var(--radius-btn);
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.filter-btn:hover { border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border)); }
.filter-btn.active {
    background: var(--zone);
    color: #fff;
    border-color: var(--zone);
}

/* ---- Champs ----------------------------------------------------------------- */

/* Une liste déroulante prend la largeur de sa plus longue option. Avec les noms réels
   d'un client — « Formation sécurité incendie — équipe maintenance bâtiment B… » — un
   filtre de l'Analyse faisait 656 px dans une carte de 326 : la carte défilait de côté.
   Bornée, elle abrège l'option affichée et tient dans son conteneur. */
select { max-width: 100%; min-width: 0; }

.form-group label {
    font-weight: 600;
    color: var(--color-title-on-surface, var(--ink));
}

.form-group input:not([type="checkbox"]):not([type="radio"]):not([type="color"]),
.form-group select,
.form-group textarea,
.scan-input {
    font-family: var(--font-ui);
    background: #fff;
    color: var(--color-text-on-surface, #333);
    border: 1.5px solid var(--border);
    border-radius: var(--radius-field);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.scan-input:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary) 14%, transparent);
}

/* Safari iOS agrandit la page dès qu'un champ de moins de 16 px reçoit le focus, et ne
   la ramène pas : l'identifiant de connexion, l'invitation, la création de session et
   l'éditeur d'anomalie (12 px) zoomaient à chaque saisie. Un plancher, sur écran étroit
   ou tactile, pour TOUS les champs de saisie — énumérer les familles de champs
   (`.form-group input`, `.edit-section input`, `.hex-input`…) garantissait d'en oublier
   une. `!important`, parce qu'editor.css pose ses 12 px en `!important`. Le code de
   session (`.code-ligne`), volontairement plus grand, garde sa taille. Ne JAMAIS
   chercher le même effet par `maximum-scale=1` : cela interdit aussi le zoom voulu. */
@media (max-width: 768px), (pointer: coarse) {
    input:not([type="checkbox"], [type="radio"], [type="color"], [type="range"], [type="file"],
              [type="submit"], [type="button"], [type="reset"], [type="image"]):not(.code-ligne input),
    select,
    textarea { font-size: 16px !important; }
}

/* ---- Badges, tableaux ------------------------------------------------------- */

/* Badges : une puce colorée devant le libellé, comme les états de Notion. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border-radius: 999px;
    font-weight: 700;
    font-size: 0.66rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 3px 9px 3px 7px;
}
.badge::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

/* Les variantes : UNE déclaration pour toute l'application. Rôles et statuts étaient
   en couleurs Material (violet #7b1fa2, bleu #1565c0), en trois jeux différents selon
   la page — « Active » n'avait pas le même vert sur la liste des sessions et sur la page
   d'une session —, et restaient des pastels clairs sur le graphite du mode sombre.

   Chaque variante ne choisit qu'une TEINTE (`--badge`), prise dans le thème ou dans la
   palette mesurée du fond ; le fond et le texte en sont dérivés. Le fond se mélange à la
   carte et le texte à l'encre, que le mode sombre redéfinit tous deux : la variante
   sombre vient d'elle-même, et chaque pastille tient 4,5:1 dans les deux modes (mélange
   à 50 %, mesuré sur les sept teintes). */
.badge-active, .badge-ready                         { --badge: var(--color-success, #1B9C6B); }
.badge-expired, .badge-error, .badge-epuise         { --badge: var(--color-danger, #CF1A4A); }
.badge-attente, .badge-pending, .badge-processing,
.badge-publishing                                   { --badge: var(--f-orange); }
.badge-formateur, .badge-published                  { --badge: var(--f-sarcelle); }
.badge-stagiaire                                    { --badge: var(--f-olive); }
.badge-neutral, .badge-draft                        { --badge: var(--muted); }
.badge-active, .badge-ready, .badge-expired, .badge-error, .badge-epuise, .badge-attente, .badge-pending,
.badge-processing, .badge-publishing, .badge-formateur, .badge-published, .badge-stagiaire, .badge-neutral, .badge-draft {
    background: color-mix(in srgb, var(--badge) 14%, var(--carte, #fff));
    color: color-mix(in srgb, var(--badge) 50%, var(--ink));
}

th { border-bottom: 1px solid var(--border); }
td { border-bottom: 1px solid #EAF0EE; }
tbody tr:hover td { background: var(--surface-tint); }

/* ---- Sur téléphone, un tableau devient une pile de cartes -----------------------
   Chaque cellule porte le nom de sa colonne (`data-label`, posé par
   TableauxEnCartes.js). La carte qui contenait un tableau à six colonnes défilait de
   côté sans le dire : le score, la date, « Ouvrir » et « Certificat » étaient hors de
   l'écran, et personne ne devine qu'une carte défile horizontalement. */
@media (max-width: 640px) {
    table[data-cartes] { display: block; width: 100%; min-width: 0; }
    table[data-cartes] thead { display: none; }
    table[data-cartes] tbody { display: block; }
    table[data-cartes] tr { display: block; padding: 10px 0; border-bottom: 1px solid var(--border); }
    table[data-cartes] td {
        display: block; padding: 5px 2px; border: none; text-align: right; min-height: 0;
    }
    table[data-cartes] td::before {
        content: attr(data-label); float: left; margin-right: 12px; line-height: 1.9;
        font-family: var(--font-data); font-size: 0.7rem; letter-spacing: 0.06em;
        text-transform: uppercase; color: var(--muted);
    }
    table[data-cartes] td[data-label=""]::before { content: none; }
    table[data-cartes] td:first-child { font-weight: 700; }
    table[data-cartes] tbody tr:hover td { background: none; }
}

/* ---- États vides : un point de départ, pas un constat --------------------------
   L'icône était affichée à 30 % d'opacité : une interface qui a l'air en panne.
   Elle devient une pastille teintée, le message un titre, et l'action suit. */

.empty-state { color: var(--color-text-on-surface, #333); }
.empty-state .icon,
.empty-state-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    border-radius: 20px;
    background: color-mix(in srgb, var(--zone) 11%, #fff);
    color: var(--zone);
    font-size: 2rem;
    opacity: 1;
    margin-bottom: 16px;
}
.empty-state > p,
.empty-state-text {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-title-on-surface, var(--ink));
}

/* ---- Chargement et erreur : ni l'un ni l'autre n'est un état vide ----------------
   Posés par Shared/EtatsDeChargement.js. Une erreur de chargement s'affichait comme une
   liste vide (« Aucune session ») : le formateur concluait qu'il n'avait rien. Elle se
   dit désormais, en rouge sourd, avec « Réessayer ». Dans une grille, le bloc prend
   toute la largeur. */
.etat-chargement,
.etat-erreur {
    grid-column: 1 / -1;
    padding: 20px;
    border-radius: var(--radius-card);
    font-size: 0.9rem;
}
.etat-chargement { color: var(--muted); text-align: center; }
.etat-erreur {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    background: color-mix(in srgb, var(--color-danger, #CF1A4A) 8%, var(--carte, #fff));
    border: 1px solid color-mix(in srgb, var(--color-danger, #CF1A4A) 28%, transparent);
    color: color-mix(in srgb, var(--color-danger, #CF1A4A) 50%, var(--ink));
}
.etat-erreur p { margin: 0; font-weight: 600; min-width: 0; }

/* ---- Modales : une seule géométrie, et elle défile ------------------------------
   Elle était déclarée deux fois (editor.css, dashboard.html), sans padding ni
   défilement : un enfant flex centré plus haut que son conteneur fixe déborde des DEUX
   côtés, et rien ne défile — le défilement du corps ne déplace pas un élément fixe. La
   modale « Nouvelle anomalie », photo de référence comprise, dépasse 800 px : sur un
   téléphone, ou sur un portable en 1366 × 768, son titre et son bouton « Créer »
   sortaient de l'écran, sans moyen de les atteindre.

   C'est donc le VOILE qui défile, et la boîte se centre par `margin: auto` plutôt que
   par `align-items` : centrée tant qu'elle tient, calée en haut — jamais rognée — dès
   qu'elle dépasse. Un seul défilement, pas deux imbriqués. Les `max-width` posés en
   ligne sur une modale (420, 520 px) restent maîtres de sa largeur. */
.modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    background: rgba(13, 39, 37, 0.5);
    padding: 16px;
    overflow-y: auto;
    overscroll-behavior: contain;
    align-items: flex-start;
    justify-content: center;
}
.modal-overlay.open { display: flex; }
.modal-overlay > .modal {
    margin: auto;
    width: 100%;
    max-width: 480px;
    padding: 28px;
    background: var(--carte, #fff);
    color: var(--color-text-on-surface, #333);
    border-radius: 16px;
    box-shadow: 0 20px 60px rgba(13, 39, 37, 0.25);
}
.modal-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; justify-content: flex-end; }

/* Sur téléphone, les actions restent à portée de pouce : collées au bas de l'écran
   pendant qu'on fait défiler une longue modale, et non à 800 px sous le pli. Le voile
   est l'ancêtre qui défile — c'est ce qui rend `sticky` opérant. */
@media (max-width: 640px) {
    .modal-overlay > .modal { padding: 20px; }
    .modal-overlay > .modal .modal-actions {
        position: sticky;
        bottom: 0;
        margin: 20px -20px -20px;
        padding: 12px 20px;
        background: var(--carte, #fff);
        border-top: 1px solid var(--border);
        border-radius: 0 0 16px 16px;
    }
}

/* ---- Tactile : une cible reste à 44 px ------------------------------------------ */

@media (max-width: 768px) {
    .btn, .btn-sm, .filter-btn { min-height: 44px; }
    .kpi-card .kpi-value { font-size: 1.8rem; }
}

@media (prefers-reduced-motion: reduce) {
    .btn, .filter-btn, .session-card, .media-card, .project-card { transition: none; }
    .btn-primary:hover, .btn-secondary:hover { transform: none; }
    .btn-primary::after { display: none; }
}

/* ---- Le mode sombre ------------------------------------------------------------
   Un réglage de l'instance (Identite.js `mode`), posé sur <html> par ChromeBoot puis
   ThemeLoader. Même structure que le clair : la barre, le bandeau et le liseré ne
   bougent pas ; seul le fond de travail change.

   Il était VERT-NUIT plein (#163836), la couleur de la barre. Sur une page entière,
   cette saturation fatigue et donne une teinte d'aquarium ; la barre garde la couleur
   de marque, le fond passe à un GRAPHITE à peine verdi (#141A19), qui laisse la
   couleur aux images et aux chiffres. Ces valeurs sont reprises par ThemeLoader
   (SOMBRE) : les garder identiques.

   Les surfaces sont dérivées, pas énumérées : `--carte` gouverne .card, .kpi-card et
   consorts (plus haut), et les pages ne posent plus de blanc en dur — elles écrivent
   `var(--carte, #fff)`. Une carte ajoutée demain suit sans qu'on y pense. */

html[data-mode="sombre"] {
    --ink: #EDF2F0;
    --ink-deep: #FFFFFF;
    --muted: #9BADA9;
    --border: rgba(255,255,255,0.11);
    --surface-tint: rgba(255,255,255,0.05);
    --carte: #1B2322;
    --shadow-soft: none;
    --shadow-hover: 0 12px 28px rgba(0,0,0,0.35);
    color-scheme: dark;
}
html[data-mode="sombre"] body {
    background: #141A19;
    color: #EDF2F0;
}

/* Un avertissement : ambre sur crème en clair, crème sur ambre voilé en sombre. Il était
   écrit en dur dans la page, et restait un pavé clair au milieu d'un écran sombre. */
.avertissement { background: #FFF8E1; border-left: 3px solid #F59E0B; color: #7C5800; }
html[data-mode="sombre"] .avertissement { background: rgba(245, 158, 11, 0.12); color: #F5D08A; }
html[data-mode="sombre"] .card,
html[data-mode="sombre"] .kpi-card,
html[data-mode="sombre"] .session-card,
html[data-mode="sombre"] .scan-card,
html[data-mode="sombre"] .media-card,
html[data-mode="sombre"] .project-card,
html[data-mode="sombre"] .modal,
html[data-mode="sombre"] .theme-preview-card,
html[data-mode="sombre"] .stat-card,
html[data-mode="sombre"] .plan-card {
    background: var(--carte);
    color: #EDF2F0;
    border-color: var(--border);
}
/* `border-color` ci-dessus repeindrait aussi le trait de tête des cartes de chiffres. */
html[data-mode="sombre"] .kpi-card { border-top-color: var(--zone); }
/* Les surfaces creusées DANS une carte (mini-statistiques, encarts) : un cran plus
   clair que la carte, jamais du blanc. */
html[data-mode="sombre"] .session-stat,
html[data-mode="sombre"] .consigne,
html[data-mode="sombre"] .code-casque,
html[data-mode="sombre"] .form-section,
html[data-mode="sombre"] .theme-grid > div {
    background: rgba(255,255,255,0.05);
    color: #EDF2F0;
    border-color: var(--border);
}
html[data-mode="sombre"] .card-header h2,
html[data-mode="sombre"] .section-title,
html[data-mode="sombre"] .modal h2,
html[data-mode="sombre"] .kpi-card .kpi-value,
html[data-mode="sombre"] .stat-card .stat-value,
html[data-mode="sombre"] .empty-state > p,
html[data-mode="sombre"] .empty-state-text,
html[data-mode="sombre"] .form-group label { color: #FFFFFF; }
html[data-mode="sombre"] .stat-card .stat-label,
html[data-mode="sombre"] .session-stat .lbl,
html[data-mode="sombre"] .kpi-card .kpi-sub { color: var(--muted); }
html[data-mode="sombre"] .btn-secondary,
html[data-mode="sombre"] .btn-ghost,
html[data-mode="sombre"] .btn-outline,
html[data-mode="sombre"] .filter-btn {
    background: rgba(255,255,255,0.07);
    color: #EDF2F0;
    border-color: var(--border);
}
html[data-mode="sombre"] .btn-outline { color: var(--color-primary-texte, var(--color-primary)); border-color: var(--color-primary); }
/* Le bouton de suppression est une teinte de danger mélangée à du BLANC : en mode
   sombre il restait un aplat rose clair sous un texte clair (2,5:1). Le mélange se
   fait avec le fond de la carte, pas avec du blanc. */
html[data-mode="sombre"] .btn-danger {
    background: color-mix(in srgb, var(--color-danger) 20%, transparent);
    color: var(--color-danger-texte, var(--color-danger));
    border-color: color-mix(in srgb, var(--color-danger) 38%, transparent);
}
html[data-mode="sombre"] .btn-danger:hover {
    background: var(--color-danger);
    color: var(--color-on-primary, #fff);
}
html[data-mode="sombre"] .form-group input:not([type="checkbox"]):not([type="radio"]):not([type="color"]),
html[data-mode="sombre"] .form-group select,
html[data-mode="sombre"] .form-group textarea,
html[data-mode="sombre"] .scan-input,
html[data-mode="sombre"] .modal input,
html[data-mode="sombre"] .modal select,
html[data-mode="sombre"] .modal textarea,
html[data-mode="sombre"] input[type="text"],
html[data-mode="sombre"] input[type="email"],
html[data-mode="sombre"] input[type="number"],
html[data-mode="sombre"] input[type="date"],
html[data-mode="sombre"] select {
    background: rgba(255,255,255,0.07);
    color: #EDF2F0;
    border-color: var(--border);
}
html[data-mode="sombre"] th { color: var(--muted); border-bottom-color: var(--border); }
html[data-mode="sombre"] td { border-bottom-color: rgba(255,255,255,0.07); color: #EDF2F0; }
html[data-mode="sombre"] tbody tr:hover td { background: rgba(255,255,255,0.04); }
/* L'aperçu de /settings est une miniature du thème CLAIR : ce qu'il contient garde le
   style clair, sinon un bouton secondaire translucide disparaît sur son propre fond. */
html[data-mode="sombre"] #theme-preview .btn-secondary,
html[data-mode="sombre"] #theme-preview .btn-outline {
    background: #fff;
    color: #163836;
    border-color: #D9E1DF;
}
/* La teinte de zone en TEXTE est assombrie pour tenir sur du blanc ; sur une surface
   sombre elle s'efface (« Dans le casque » tombait à 3:1). On l'ÉCLAIRCIT au lieu de
   l'assombrir — même teinte, autre sens. */
html[data-mode="sombre"] { --zone-texte: color-mix(in srgb, var(--zone) 62%, #fff); }
/* Le filtre actif est un aplat de la zone : le blanc dessus ne tient pas sur les
   teintes claires (3,4:1 sur le sarcelle). En clair il prend la zone assombrie ; en
   sombre, la zone pleine avec l'encre dessus. */
.filter-btn.active { background: var(--zone-texte); border-color: var(--zone-texte); }
html[data-mode="sombre"] .filter-btn.active { background: var(--zone); border-color: var(--zone); color: #10201E; }
html[data-mode="sombre"] .empty-state .icon,
html[data-mode="sombre"] .empty-state-icon { background: rgba(255,255,255,0.06); }
