/* =============================================================================
   Assistant conversationnel — feuille de style commune (chat public + administration).

   Structure seulement : la charte du site servi (couleurs de marque, teintes, polices,
   hauteur du logotype) vient du pack client, clients/<id>/theme.css, servie sur
   /client/theme.css et chargée APRÈS cette feuille, dont elle ne redéfinit que des
   variables de :root (docs/PACK-CLIENT.md). Les valeurs par défaut de :root forment une
   palette neutre, contrastes AA vérifiés, pour un pack sans theme.css.

   Un seul fichier, servi à /style.css pour les deux interfaces : un seul :root,
   pas de scission par page, pas de thème sombre commuté (prefers-color-scheme).
   Un pack peut en revanche poser le chat sur un fond sombre ou en dégradé par les
   variables --chat-*, --entete-*, --bulle-*, --composeur-* et --saisie-* (section 2) :
   l'administration, elle, reste en clair, seul son en-tête suit --entete-*.

   Sommaire
     1. Polices
     2. Jetons de conception (:root)
     3. Réinitialisation et bases
     4. Accessibilité (focus, contenu masqué, lien d'évitement)
     5. Composants communs (en-tête, marque, boutons, badges, étiquettes)
     6. Chat public (panneau de conversation, sources, composeur et barre à icônes ; modes embarqué et rapide)
     6 bis. Mode vocal (pages /vocal et /vocal/embed : mention, sous-titres, réponses écrites, indicateur, commandes)
     7. Administration (chrome, cartes, tableaux)
     8. Administration — pondération des contenus
     9. Utilitaires
    10. Responsive
    11. Impression
   ============================================================================= */

/* =============================================================================
   1. POLICES
   ============================================================================= */

/* Jost (SIL OFL 1.1, The Jost Project Authors) : police libre auto-hébergée, police
   par défaut du moteur, servie par /fonts/ (jamais par un CDN). Fichiers variables (axe
   wght) : une seule déclaration couvre les graisses 400 à 800. Un pack la précède de ses
   propres polices dans son theme.css (--font-texte, --font-titre, fichiers sous
   /client/fonts/). Provenance, licence et mode d'emploi : docs/polices.md + public/fonts/OFL.txt */
@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/jost-v20-latin-variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/jost-v20-latin-ext-variable.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* =============================================================================
   2. JETONS DE CONCEPTION
   ============================================================================= */

:root {
  /* ---- Couleurs de marque : palette neutre par défaut, redéfinie par le theme.css du pack ---- */
  --blanc: #ffffff;
  --primaire: #1f5aa6; /* action, repère de lecture ; 6,84:1 sous texte blanc */
  --primaire-actif: #17467f; /* état actif du primaire, 9,46:1 sur blanc */
  --secondaire: #2e3f5c; /* hiérarchie, état, zones fortes ; 10,60:1 sur blanc */
  --tertiaire: #f2c14e; /* attention, sur fond sombre uniquement ; 8,63:1 sur --encre */
  --quaternaire: #2f6b8a; /* usage rare (étiquette « type ») ; 5,01:1 sur sa teinte */
  --encre: #1f2a3a; /* texte de base, surfaces sombres ; 14,48:1 sur blanc */
  --fond-page: #f4f5f7;
  --divider: #dee2ec; /* filet décoratif */

  /* ---- Alias fonctionnels (noms historiques, référencés dans toute la feuille) */
  --accent: var(--primaire);
  --accent-dark: var(--secondaire); /* survol du primaire */
  --bg: var(--fond-page);
  --card: var(--blanc);
  --border: var(--divider);

  /* ---- Texte -------------------------------------------------------------- */
  --text: var(--encre);
  --muted: #575b61; /* 6,26:1 sur --bg */
  --placeholder: #696e7a; /* texte indicatif des champs, ouvert aux packs le 24/09/2026 ; 4,68:1 sur --bg */

  /* ---- Teintes de fond (redéfinies par le theme.css du pack) --------------- */
  --teinte-secondaire: #eef1f5; /* bulle assistant */
  --teinte-primaire: #e8eff8; /* surlignage de source, badge accent, ligne choisie */
  --teinte-tertiaire: #fff3cc; /* ligne modifiée non enregistrée */
  --teinte-quaternaire: #e6eff3; /* fond de l'étiquette « Type » */

  /* ---- Filets porteurs d'information (WCAG 1.4.11 : 3:1 minimum) ---------- */
  --border-strong: #828da4; /* 3,34:1 sur blanc, 3,06:1 sur --bg */

  /* ---- États fonctionnels (hors charte : ni vert ni orange de marque) ------ */
  --focus: var(--secondaire); /* anneau de focus sur la page */
  --focus-invert: var(--tertiaire); /* anneau sur surface sombre ou primaire */
  --ok: #12703a;
  --ok-bg: #e3f5e9;
  --ok-border: #b5ddc4;
  --warn: #8a5200;
  --warn-bg: #fdf1dc;
  --warn-border: #ecd4a6;
  --err: #b3001b; /* rouge profond, volontairement distinct du primaire */
  --err-bg: #fde8ea;
  --err-border: #f2c3c9;
  --accent-border: #c3d4ea; /* filet du badge accent */
  --secondaire-filet: #d5dbe5; /* filet du badge secondaire */
  --gris-inactif: #eaeef4; /* champ en lecture seule */
  --sur-encre-doux: rgba(255, 255, 255, 0.82); /* surtitre sur la bulle sombre */
  /* Chevron des listes déroulantes (data-URI), trait de la couleur de --encre (%231f2a3a) : une variable ne
     s'interpole pas dans url(), un theme.css qui redéfinit --encre redéfinit donc aussi cette image. */
  --chevron-liste: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%231f2a3a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.75 6 6.25l5-4.5'/%3E%3C/svg%3E");

  /* ---- Rayons (4px champs, 10px bulles et cartes, 30px conteneur, 100px pastilles) */
  --radius-xs: 4px; /* champs, étiquettes, bandeaux */
  --radius: 10px; /* bulles, cartes, tableaux */
  --radius-lg: 30px; /* grandes surfaces — réserve, non employé aujourd'hui */
  --radius-pill: 100px; /* badges, pastilles, barres de chronométrage */

  /* ---- Forme des boutons (.btn) ------------------------------------------
     Séparée des rayons : un pack dont les boutons sont des rectangles arrondis
     aplatit --bouton-radius sans toucher aux badges (--radius-pill), et retire la
     capitale sans toucher aux surtitres (--casse-surtitre). .btn--tiny garde son
     bas-de-casse et son remplissage : la capitale y serait illisible. */
  --bouton-radius: var(--radius-pill);
  --bouton-padding: 10px 30px;
  --bouton-fs: var(--fs-sm);
  --bouton-casse: uppercase;
  --bouton-ls: 0.02em;

  /* ---- Bouton principal (.btn--primary) : --accent par défaut --------------
     Séparé de --primaire pour qu'un pack dont la couleur d'action ne convient pas
     au texte des liens la réserve au bouton, sans la répandre sur toute la feuille. */
  --bouton-primaire-fond: var(--accent);
  --bouton-primaire-texte: var(--blanc);
  /* Texte du survol et de l'état actif : le même que celui du repos par défaut. Un pack le sépare
     quand son fond de survol demande l'autre couleur pour tenir le contraste (charte EFREI). */
  --bouton-primaire-texte-survol: var(--bouton-primaire-texte);
  --bouton-primaire-survol: var(--accent-dark);
  --bouton-primaire-actif: var(--primaire-actif);

  /* ---- Typographie (piles redéfinies par le theme.css du pack) ------------ */
  --font-texte: Jost, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-titre: Jost, system-ui, -apple-system, Arial, sans-serif;
  --font: var(--font-texte); /* alias historique : ne pas retirer */
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Échelle d'affichage : resserrée pour un outil dense. */
  --fs-display-1: 1.75rem; /* 28px — titre d'application */
  --fs-display-2: 1.375rem; /* 22px — titre de section */
  --fs-display-3: 1.125rem; /* 18px — titre de panneau */
  --fs-body: 1rem; /* 16px */
  --fs-sm: 0.9375rem; /* 15px — tableaux, boutons, listes */
  --fs-xs: 0.8125rem; /* 13px — légendes */
  --fs-micro: 0.75rem; /* 12px — surtitres, métadonnées */
  --ls-display: -0.02em;
  --ls-surtitre: 0.08em;
  /* Casse des deux familles de libellés, à côté de leur interlettrage : une charte sans
     capitales pose `none` (le texte est alors rendu tel qu'il est écrit dans le balisage). */
  --casse-display: uppercase; /* titres d'affichage (.admin-section h2, .card__value…) */
  --casse-surtitre: uppercase; /* surtitres et libellés : rôle, en-tête de colonne, étiquette de carte */

  /* Interlignage de base, hérité par tout le chrome. Le texte LU garde le sien
     (.msg__body, .admin-help, .disclaimer, cellules de tableau) : un interligne serré
     convient à une étiquette, pas à une réponse de plusieurs paragraphes. */
  --interligne: 1.5;

  /* ---- Logotype du pack (hauteur affichée ; largeur déduite de ses proportions) */
  --logo-hauteur: 28px;
  --logo-hauteur-mobile: 24px;

  /* ---- Espacement (base 8) ------------------------------------------------ */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* ---- Mouvement ---------------------------------------------------------- */
  --ease: cubic-bezier(0.19, 1, 0.22, 1); /* décélération franche */
  --t-fast: 0.2s ease-in-out; /* couleurs, survols */
  --t-move: 0.4s var(--ease); /* déplacements, apparitions */

  /* ---- Divers ------------------------------------------------------------- */
  --shadow: 0 1px 2px rgba(29, 39, 58, 0.04), 0 1px 3px rgba(29, 39, 58, 0.06);
  --max-width: 736px; /* colonne de lecture : ~90 signes par ligne */
  --wrap-chat: 1040px; /* en-tête du chat : marque + actions sur une rangée */
  --wrap-max: 1424px; /* largeur maximale du contenu */
  --header-h: 72px; /* hauteur minimale de l'en-tête */
  --header-h-mobile: 60px; /* la même sous 768px (section 10) */

  /* ---- Chat et en-tête : surfaces, panneau, bulles, saisie (ouverts aux packs le 24/09/2026) ----
     Chaque défaut est l'expression que la règle portait avant l'ouverture : un pack qui ne les redéfinit pas
     garde un rendu identique au pixel près. Branchés sur les sélecteurs du chat seulement (body.page-chat,
     .chat, .msg, .composer, pages vocale, rapide et embarquée), jamais sur ceux de l'administration, sauf
     l'en-tête (--entete-*, --titre-app-*), commun aux deux interfaces. Un pack peut ainsi poser le chat sur
     un fond sombre ou en dégradé ; l'administration, elle, reste en clair (--bg, --card, --text inchangées). */
  /* Surfaces posées sur le fond de page du chat */
  --chat-fond: var(--bg); /* fond de body.page-chat (dégradé admis) */
  --chat-surface: var(--card); /* fond de la mention (.disclaimer) */
  --chat-surface-filet: var(--border); /* filet entre la conversation et le pied de saisie */
  --chat-texte: var(--text); /* titre et état du vocal, posés sur le fond */
  --chat-texte-doux: var(--muted); /* mention, « rédaction en cours », aides, minuteur vocal */
  --chat-erreur: var(--err); /* erreur du vocal, posée sur le fond (#vocal-error) */
  --chat-alerte: var(--warn); /* minuteur du vocal en fin proche, posé sur le fond */
  /* Panneau de conversation : .chat (coins hauts) et .composer-wrap (coins bas) forment un seul panneau */
  --chat-panneau-fond: var(--card);
  --chat-panneau-attache: scroll; /* fixed : un dégradé se raccorde d'une moitié à l'autre */
  --chat-panneau-rayon: 0;
  --chat-panneau-ombre: none;
  --chat-panneau-largeur: none; /* largeur maximale, centrée ; none = pleine largeur */
  /* Étiquette décorative à gauche du panneau (écran large) : texte (chaîne CSS) et icône (url()) ; none = aucune */
  --chat-etiquette: none;
  --chat-etiquette-icone: none;
  --defilement-couleur: auto; /* scrollbar-color de la conversation (pouce piste) */
  /* En-tête (chat ET administration) */
  --entete-fond: var(--card);
  --entete-fond-attache: scroll; /* fixed : raccord avec un fond de page en dégradé */
  --entete-texte: var(--text);
  --entete-texte-doux: var(--muted);
  --entete-filet: var(--border);
  --titre-app-font: var(--font-titre);
  --titre-app-fs: var(--fs-sm);
  --titre-app-fs-grand: 2rem; /* à partir de 1520px (section 10) */
  --titre-app-graisse: 800;
  /* Bulles : grands coins, coin réduit côté émetteur, couleurs par rôle, surtitre (« Vous », nom de l'assistant) */
  --bulle-rayon: var(--radius);
  --bulle-coin: var(--radius-xs);
  --bulle-utilisateur-fond: var(--encre);
  --bulle-utilisateur-texte: var(--blanc);
  --bulle-utilisateur-surtitre: var(--sur-encre-doux);
  --bulle-assistant-fond: var(--teinte-secondaire);
  --bulle-assistant-texte: var(--text);
  --bulle-assistant-surtitre: var(--secondaire);
  --bulle-surtitre-fs: var(--fs-micro);
  --bulle-surtitre-graisse: 600;
  /* Barre de saisie : le formulaire (.composer, en pilule chez un pack) et le champ (.composer__input) */
  --composeur-fond: transparent;
  --composeur-rayon: 0;
  --composeur-filet: none; /* bordure complète (ex. 1px solid …) ; none = aucune */
  --composeur-padding: 0;
  --saisie-fond: var(--bg);
  --saisie-filet: var(--border-strong);
  --saisie-rayon: var(--radius-xs);
  /* Icône d'envoi de la barre à icônes (#send.composer__icon-btn, option ui.composerIcons), ouverte aux packs le
     28/09/2026 pour qu'Indigo et l'EFREI la posent sur la couleur de leur bouton principal. Défauts = expression
     d'avant (icône à la couleur du texte sur fond transparent, --accent au survol et au focus) : Guy Hoquet inchangé. */
  --composeur-envoi-fond: transparent;
  --composeur-envoi-texte: var(--text);
  --composeur-envoi-fond-survol: transparent;
  --composeur-envoi-texte-survol: var(--accent);
  --composeur-envoi-rayon: var(--radius-pill);
  --composeur-envoi-texte-desactive: var(--text); /* désactivée (réponse en cours, pack pas chargé), opacité 0,6 en plus */
  /* Boutons et focus sur fond sombre */
  --bouton-secondaire-fond: transparent;
  --bouton-graisse: 600;
  --bouton-danger-fond: transparent;
  --focus-sur-sombre: var(--focus); /* anneau de ce qui est posé sur le fond : en-tête, actions, commandes vocales */
  /* Survols et focus : un pack dont l'en-tête est sombre y pose des boutons clairs, que le survol garde clairs
     (l'inversion par défaut, fond --accent ou --secondaire, se fondrait dans un en-tête sombre). */
  --bouton-neutre-texte: var(--muted); /* texte de .btn--ghost (modes, bascule, liens de l'administration) */
  --bouton-neutre-survol: var(--bg); /* fond de .btn--ghost au survol */
  --bouton-secondaire-survol: var(--accent); /* fond de .btn--secondary au survol */
  --bouton-secondaire-texte-survol: var(--blanc);
  --bouton-bascule-survol: var(--secondaire); /* fond de la bascule enfoncée (.btn--ghost[aria-pressed="true"]) au survol */
  --bouton-bascule-texte-survol: var(--blanc);
  /* Texte du lien d'évitement au survol et au focus, sur son fond --accent. Défaut : var(--blanc), texte du repos, depuis
     le 28/09/2026 (décision d'Arthur) ; avant, --secondaire, que la règle a:focus lui donnait : 1,82:1 chez Indigo et
     1,63:1 chez l'EFREI, contre 6,15:1 et 7,24:1 en blanc. */
  --lien-evitement-texte-focus: var(--blanc);
  --saisie-filet-focus: var(--secondaire); /* filet du champ de saisie au focus, sous l'anneau */
  --defilement-marge: auto; /* scroll-padding-block de la conversation : place de l'anneau de focus au bord */
  /* Finitions (même jour, second tour de la revue de la charte GUY-A), défauts toujours égaux à l'expression d'avant.
     --placeholder (section « Texte », plus haut) est ouvert aux packs le même jour. */
  --bulle-surtitre-marge: var(--sp-1); /* espace entre le surtitre d'une bulle (.msg__role) et son texte */
  --chat-mention-marge: 0; /* marge basse de la mention (.disclaimer) : écart entre son texte et le panneau */
  --saisie-fond-lecture: var(--gris-inactif); /* fond du champ de saisie en lecture seule (réponse en cours) */
  --chat-focus-decalage: 2px; /* outline-offset de l'anneau de la conversation (#chat) ; négatif : dans le panneau */
  --chat-puce: var(--accent); /* puces des listes posées sur le panneau (aide du vocal) */
  --chrono-resume-retour: nowrap; /* white-space du résumé de « Temps de réponse » (premier mot à…) */
  --bouton-bascule-fond: var(--teinte-secondaire); /* bascule enfoncée (.btn--ghost[aria-pressed="true"]) : fond */
  --bouton-bascule-texte: var(--secondaire); /* … son texte */
  --bouton-bascule-filet: var(--secondaire); /* … son filet */
  --chiffres-font: var(--font-titre); /* chiffres-clés de l'administration (.card__value) */
}

/* =============================================================================
   3. RÉINITIALISATION ET BASES
   ============================================================================= */

* {
  box-sizing: border-box;
}

/* L'attribut `hidden` doit l'emporter sur toute règle auteur `display: flex/grid/…`
   (pied de message, indicateur de rédaction, bandeau d'erreur admin, bouton Stop). */
[hidden] {
  display: none !important;
}

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

body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--interligne);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Le chat occupe la fenêtre entière : le défilement est porté par #chat
   (app.js lit et écrit scrollTop / scrollHeight dessus). Ne pas déplacer. */
body.page-chat {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Fond de page du chat seulement (--chat-fond, --bg par défaut) : celui de l'administration ne suit pas. */
  background: var(--chat-fond);
}

body.page-admin {
  min-height: 100dvh;
}

a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color var(--t-fast);
}

a:hover,
a:focus {
  color: var(--secondaire);
}

::selection {
  background: var(--teinte-secondaire);
  color: var(--text);
}

h1,
h2,
h3,
h4 {
  overflow-wrap: anywhere;
}

/* Les titres d'affichage reprennent le quadruplet identitaire du site :
   condensé, 800, majuscules (--casse-display), interlettrage négatif
   (--ls-display). Jamais sous 16px (illisible), jamais dans un tableau, un
   bouton ou un badge. */
.admin-section h2,
.admin-panel__title,
.weights-preview__grid h4,
.card__value {
  font-family: var(--font-titre);
  font-weight: 800;
  text-transform: var(--casse-display);
  letter-spacing: var(--ls-display);
  /* Jamais sous 1.05 : avec Jost, le « À » majuscule déborde de sa ligne. */
  line-height: 1.1;
}

/* =============================================================================
   4. ACCESSIBILITÉ
   ============================================================================= */

/* Anneau de focus franc, à deux tons : un pointillé 1px est insuffisant pour AA.
   Ton par défaut = --secondaire. Grâce à `outline-offset`, l'anneau se pose sur
   le fond de page (--card ou --bg) même autour d'un bouton --primaire : c'est
   donc sur ces fonds que la charte doit le rendre lisible (3:1 au moins ;
   palette par défaut : 10,60:1 sur blanc). C'est la couleur correcte pour la
   quasi-totalité des cas. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* Garde-fou (aucun élément focalisable n'est produit aujourd'hui dans une
   bulle utilisateur) : inversion en --tertiaire pour ce qui est focalisé sur
   la surface sombre de la bulle, où --secondaire se confondrait avec le fond
   (palette par défaut : --tertiaire à 8,63:1 sur --encre). --tertiaire serait
   au contraire illisible sur blanc : ne jamais l'étendre à un élément
   simplement posé sur la page. */
.msg--user :focus-visible {
  outline-color: var(--focus-invert);
}

/* Ce qui est posé directement sur le fond de page ou du panneau, et non sur une
   carte : en-tête (chat et administration), lien d'évitement (qui s'affiche sur
   l'en-tête), action du mode embarqué, commandes et récapitulatif du vocal (leur
   anneau déborde sur le fond), et la conversation elle-même (#chat : Chrome rend
   focalisable une zone de défilement sans contenu focalisable, cas de /vocal à
   l'accueil ; son anneau se pose sur le fond de page, autour du panneau).
   --focus-sur-sombre vaut --focus par défaut ; un
   pack dont le chat est sombre y met un ton clair, --focus restant le ton des
   surfaces claires (cartes, bulles, champ de saisie, administration). */
.skip-link:focus-visible,
.app-header :focus-visible,
.embed-actions :focus-visible,
.vocal__controls:focus-visible,
.vocal__controls :focus-visible,
.vocal__summary:focus-visible,
.chat:focus-visible {
  outline-color: var(--focus-sur-sombre);
}

/* Décalage de l'anneau de la conversation (--chat-focus-decalage : 2px par défaut, celui de :focus-visible). Un pack dont
   le panneau touche les bords de l'écran et le pied de saisie (écran étroit) le rentre dans le panneau par une valeur
   négative : posé autour, l'anneau y sortait de l'écran et passait sous le pied. */
.chat:focus-visible {
  outline-offset: var(--chat-focus-decalage);
}

/* Deux classes identiques héritées, l'une par page : ne pas fusionner,
   les deux sont référencées dans le balisage. #weights-import doit rester
   focalisable (ni display:none ni visibility:hidden). */
.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

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

.skip-link {
  position: absolute;
  left: var(--sp-2);
  top: -60px;
  z-index: 100;
  padding: var(--sp-2) var(--sp-3);
  background: var(--accent);
  color: var(--blanc);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-transform: var(--bouton-casse);
  letter-spacing: var(--bouton-ls);
  text-decoration: none;
  border-radius: var(--radius-xs);
  transition: top var(--t-move);
}

.skip-link:focus {
  top: var(--sp-2);
}

/* Sans cette règle, a:hover/a:focus (plus spécifique que .skip-link) donnait au texte la couleur
   --secondaire sur le fond --accent, peu lisible. --lien-evitement-texte-focus vaut par défaut
   var(--blanc), texte du repos ; un pack peut la changer. */
.skip-link:hover,
.skip-link:focus {
  color: var(--lien-evitement-texte-focus);
}

/* =============================================================================
   5. COMPOSANTS COMMUNS
   ============================================================================= */

/* --- En-tête et marque ---------------------------------------------------- */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  /* Dans body.page-chat (flex colonne), seule la zone de conversation doit se
     comprimer : sans ce flex 0 0 auto, l'en-tête est écrasé sous la hauteur de
     son contenu dès que les boutons passent sur deux lignes (< 768px). */
  flex: 0 0 auto;
  min-height: var(--header-h);
  padding: var(--sp-3) var(--sp-4);
  /* En-tête commun au chat et à l'administration (--entete-*, section 2). L'attache « fixed »
     raccorde un fond en dégradé à celui de la page du chat. */
  background: var(--entete-fond);
  background-attachment: var(--entete-fond-attache);
  color: var(--entete-texte);
  border-bottom: 1px solid var(--entete-filet);
}

/* Alignement du contenu sur la grille large du site lorsque le balisage
   interpose un conteneur ; sans lui, .app-header fait office de rangée. */
.app-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  width: 100%;
  max-width: var(--wrap-max);
  margin: 0 auto;
}

.app-header__brand,
.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
}

/* Logotype du pack client (ui.logo de client.json, servi sur /client/…) ; hauteur réglée par le theme.css du pack. */
.brand__logo {
  display: block;
  height: var(--logo-hauteur);
  width: auto;
  flex: 0 0 auto;
}

/* Filet de séparation entre le logotype et le nom de l'outil. */
.brand__text {
  min-width: 0;
  padding-left: var(--sp-3);
  border-left: 1px solid var(--entete-filet);
}

/* Réserve : repli monogramme. Aucun balisage ne l'emploie depuis le
   passage au logotype ; conservé pour un en-tête sans image. */
.logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-xs);
  background: var(--accent);
  color: var(--blanc);
  font-family: var(--font-titre);
  font-weight: 800;
  font-size: 1.125rem;
  letter-spacing: 0.02em;
}

/* Le logotype porte déjà le nom : le titre l'accompagne en surtitre au
   lieu de le doubler en 28px, et l'en-tête retombe sous les 72px de la charte. */
.app-title {
  margin: 0;
  font-family: var(--titre-app-font);
  font-size: var(--titre-app-fs);
  font-weight: var(--titre-app-graisse);
  line-height: 1.2;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--entete-texte);
}

.app-subtitle {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: var(--entete-texte-doux);
}

.app-header__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

/* Mention légale : alignée sur la colonne de lecture, sur la même surface
   blanche que la conversation par défaut (une bande grise intercalée se lisait
   comme une couture entre deux blocs blancs). --chat-surface : un pack dont le
   chat est sombre la pose à même le fond de page (transparent). */
.disclaimer {
  flex: 0 0 auto;
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  /* Marge basse (--chat-mention-marge, 0 par défaut) : un pack qui détache le panneau du fond y écarte la mention. */
  padding: var(--sp-3) var(--sp-4) var(--chat-mention-marge);
  background: var(--chat-surface);
  color: var(--chat-texte-doux);
  font-size: var(--fs-xs);
  line-height: 1.4;
  text-align: left;
}

/* Réserve : surtitre générique, prêt pour un futur intertitre de section. */
.section-label {
  display: block;
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.25;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--secondaire);
}

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

/* Forme réglée par le pack (--bouton-*, section 2), valeurs par défaut de la
   charte historique : pilule, majuscules, 600, remplissage 10/30 du site. Le
   site impose 54px de hauteur ; réduit à 42px, nos barres d'outils portent
   jusqu'à cinq boutons. 42px et non 40 pour que les variantes à filet 1px
   (.btn--ghost) gardent la même hauteur que celles à filet 2px : min-height
   reste au moteur, un remplissage de pack plus généreux le dépasse simplement. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  font-family: inherit;
  font-size: var(--bouton-fs);
  font-weight: var(--bouton-graisse);
  line-height: 1.2;
  text-transform: var(--bouton-casse);
  letter-spacing: var(--bouton-ls);
  text-decoration: none;
  padding: var(--bouton-padding);
  min-height: 42px;
  border-radius: var(--bouton-radius);
  border: 2px solid transparent;
  background: transparent;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: background-color var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}

.btn:disabled,
.btn[disabled] {
  opacity: 0.6;
  cursor: not-allowed;
  pointer-events: none;
}

.btn--primary {
  background: var(--bouton-primaire-fond);
  color: var(--bouton-primaire-texte);
  border-color: var(--bouton-primaire-fond);
}

.btn--primary:not(:disabled):hover {
  background: var(--bouton-primaire-survol);
  border-color: var(--bouton-primaire-survol);
  color: var(--bouton-primaire-texte-survol);
}

.btn--primary:not(:disabled):active {
  background: var(--bouton-primaire-actif);
  border-color: var(--bouton-primaire-actif);
  color: var(--bouton-primaire-texte-survol);
}

/* Fond transparent par défaut ; un pack dont l'en-tête ou le panneau est sombre lui
   donne un fond clair (--bouton-secondaire-fond), le texte --accent restant lisible. */
.btn--secondary {
  background: var(--bouton-secondaire-fond);
  color: var(--accent);
  border-color: var(--accent);
}

/* Survol inversé par défaut (fond --accent, texte blanc) ; sur un en-tête sombre, un pack le garde clair
   (--bouton-secondaire-survol, --bouton-secondaire-texte-survol). */
.btn--secondary:not(:disabled):hover {
  background: var(--bouton-secondaire-survol);
  color: var(--bouton-secondaire-texte-survol);
}

.btn--secondary:not(:disabled):active {
  background: var(--primaire-actif);
  border-color: var(--primaire-actif);
  color: var(--blanc);
}

/* Bouton neutre et bascule : fond --card, conçu pour un fond clair (l'en-tête est blanc par défaut) ;
   posé sur un en-tête sombre, il s'y lit comme un bouton blanc. Texte et fond de survol :
   --bouton-neutre-texte, --bouton-neutre-survol ; bascule enfoncée survolée : --bouton-bascule-*. */
.btn--ghost {
  background: var(--card);
  color: var(--bouton-neutre-texte);
  border: 1px solid var(--border-strong);
}

.btn--ghost:not(:disabled):hover {
  background: var(--bouton-neutre-survol);
  color: var(--text);
  border-color: var(--text);
}

/* L'état actif se marque en positif ; l'état inactif est le repos.
   (Pas d'opacité sur l'état « false » : elle dégraderait le contraste.) Fond, texte et filet : --bouton-bascule-*
   (teinte, --secondaire par défaut) ; un pack dont les boutons de repos sont blancs sur un en-tête sombre marque
   l'état enfoncé par un fond plein, et non par une seule nuance de fond (WCAG 1.4.1). */
.btn--ghost[aria-pressed="true"] {
  background: var(--bouton-bascule-fond);
  color: var(--bouton-bascule-texte);
  border-color: var(--bouton-bascule-filet);
}

.btn--ghost[aria-pressed="true"]:not(:disabled):hover {
  background: var(--bouton-bascule-survol);
  color: var(--bouton-bascule-texte-survol);
}

/* Rouge et non --primaire : la couleur de marque ne sert jamais à un état.
   --bouton-danger-fond (transparent par défaut) : fond clair sur un panneau sombre
   (« Terminer » du vocal), où le rouge seul ne se lirait pas. */
.btn--danger {
  background: var(--bouton-danger-fond);
  color: var(--err);
  border-color: var(--err);
}

.btn--danger:not(:disabled):hover {
  background: var(--err);
  color: var(--blanc);
}

/* Micro-contrôles (Copier, 👍, 👎) : la capitale y serait illisible. */
.btn--tiny {
  font-size: var(--fs-xs);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  padding: 4px 10px;
  min-height: 28px;
  background: var(--card);
  color: var(--muted);
  border: 1px solid var(--border-strong);
}

.btn--tiny:not(:disabled):hover {
  color: var(--text);
  border-color: var(--text);
}

/* Boutons de l'en-tête à icône (.btn--icone : modes, « Détails techniques », « Nouvelle conversation », liens et
   « Rafraîchir » de l'administration). Décision d'Arthur du 28/09/2026 : sous 768px, l'icône seule, pour que l'en-tête
   tienne sur une rangée (section 10) ; au-dessus, le libellé seul, rendu inchangé. Le libellé (.btn__libelle) reste
   dans le DOM à toutes les tailles : c'est le nom accessible, repris par l'infobulle (title). Icône décorative
   (aria-hidden), au trait, à la couleur du texte du bouton : états (survol, bascule enfoncée) inchangés. */
.btn__icone {
  display: none;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
}

/* --- Badges --------------------------------------------------------------- */

.badge {
  display: inline-block;
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.5;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--muted);
  white-space: nowrap;
}

.badge--ok {
  color: var(--ok);
  background: var(--ok-bg);
  border-color: var(--ok-border);
}

.badge--warn {
  color: var(--warn);
  background: var(--warn-bg);
  border-color: var(--warn-border);
}

.badge--err {
  color: var(--err);
  background: var(--err-bg);
  border-color: var(--err-border);
}

.badge--accent {
  color: var(--accent);
  background: var(--teinte-primaire);
  border-color: var(--accent-border);
}

.badge--secondaire {
  color: var(--secondaire);
  background: var(--teinte-secondaire);
  border-color: var(--secondaire-filet);
}

.badge--gris {
  color: var(--muted);
  background: var(--bg);
  border-color: var(--border);
}

/* =============================================================================
   6. CHAT PUBLIC
   ============================================================================= */

/* Sur la page de chat, l'en-tête est ramené près de la colonne de lecture :
   la grille large de 1424px y laisserait la marque flotter seule à gauche.
   1040px est la largeur minimale qui garde la marque et les deux boutons sur
   une seule rangée (72px de haut, comme la charte) ; en dessous, la rangée
   passe à deux lignes. */
.page-chat .app-header__inner {
  max-width: var(--wrap-chat);
}

/* Mode embarqué (iframe, page servie par /embed) : la mise en page reste celle
   de .page-chat — hauteur du cadre, défilement porté par #chat — et seul ce qui
   n'a pas lieu d'être dans une iframe disparaît (en-tête, bascule des détails
   techniques). Les gouttières se resserrent : dans un cadre étroit posé sur un
   site, la marge de la page hôte s'ajoute déjà à la nôtre. */
.page-embed .chat {
  padding: var(--sp-4) var(--sp-3);
}

.page-embed .disclaimer {
  padding-top: var(--sp-2);
}

/* Ligne d'action au-dessus de la saisie : « Nouvelle conversation » en
   micro-contrôle, aligné à droite sur la colonne de lecture, pour ne pas
   concurrencer le bouton d'envoi. */
.embed-actions {
  display: flex;
  justify-content: flex-end;
  max-width: var(--max-width);
  margin: 0 auto var(--sp-2);
}

/* Mode texte rapide (page /fast, src/server/fast-ui/fast.html) : le chat tel
   quel, seul s'ajoute le badge « Mode rapide » à côté du titre. Badge accent
   existant (couple --accent sur --teinte-primaire, déjà vérifié par chaque
   pack), aucune variable nouvelle. Titre et badge partagent une rangée qui se
   replie : faute de place (logotype large, écran étroit), le badge passe sous
   le titre, jamais de défilement horizontal. */
.brand__title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
}

.brand__mode {
  flex: 0 0 auto;
}

/* Panneau de conversation. Par défaut, surface blanche posée sur le fond de page,
   comme le conteneur de chat du site : la bulle assistant (--teinte-secondaire) et
   le fond de page (--fond-page) sont quasi indiscernables (autour de 1,1:1), ils ne
   doivent jamais se toucher.
   Sans changer le balisage, .chat et le pied de saisie (.composer-wrap, plus bas)
   forment un seul panneau : même fond (--chat-panneau-fond), coins hauts ici et bas
   là-bas (--chat-panneau-rayon), même ombre, même largeur maximale centrée
   (--chat-panneau-largeur ; width + max-width + marges automatiques : la largeur
   reste celle de la fenêtre tant que la variable vaut none). Un dégradé se raccorde
   d'une moitié à l'autre avec --chat-panneau-attache: fixed (fond calé sur la
   fenêtre ; iOS Safari l'ignore et le rend « scroll »). Les défauts (0, none, scroll)
   rendent exactement l'ancienne surface. */
.chat {
  flex: 1 1 auto;
  width: 100%;
  max-width: var(--chat-panneau-largeur);
  margin-left: auto;
  margin-right: auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--sp-5) var(--sp-4);
  background: var(--chat-panneau-fond);
  background-attachment: var(--chat-panneau-attache);
  border-radius: var(--chat-panneau-rayon) var(--chat-panneau-rayon) 0 0;
  box-shadow: var(--chat-panneau-ombre);
  scrollbar-color: var(--defilement-couleur);
  /* Marge de défilement (auto par défaut) : un élément focalisé au bord de la zone y est amené assez loin
     pour que son anneau (3px + 2px de décalage) ne soit pas rogné par le bord. */
  scroll-padding-block: var(--defilement-marge);
}

/* Étiquette de l'assistant à gauche du panneau (écran large seulement), sans
   balisage : pseudo-élément de la mention, qui précède immédiatement le panneau.
   Aucune boîte tant que --chat-etiquette vaut none (content invalide → normal).
   - Place : position absolue sans ancêtre positionné (bloc conteneur = fenêtre, donc
     ni rognée par le défilement de .chat ni entraînée par lui) ; `top` laissé à sa
     position statique, sous la dernière ligne de la mention (sans marge basse), soit
     le haut du panneau ; à gauche, décalée de sa largeur et d'un espace depuis le
     bord du panneau centré (--chat-panneau-largeur : une longueur, ou une expression
     dont les pourcentages valent la largeur de la fenêtre, comme dans .chat, enfant du
     corps ; sans largeur de panneau, calée sur la mention).
   - Décorative : « / "" » lui donne un texte de remplacement vide, elle sort de
     l'arbre d'accessibilité (le nom de l'assistant est déjà le surtitre de chaque
     réponse). Un navigateur qui ignore cette syntaxe n'affiche pas l'étiquette.
   - Couleurs de la bulle assistant, ses rayons, coin réduit en bas à droite (vers le
     panneau) ; icône (--chat-etiquette-icone, chemin absolu /client/…) à gauche. */
@media (min-width: 1024px) {
  body.page-chat .disclaimer::after {
    content: var(--chat-etiquette) / "";
    /* block explicite : la position statique est alors celle d'un bloc placé sous la dernière
       ligne (en ligne, ce serait le haut de cette dernière ligne). */
    display: block;
    position: absolute;
    /* La position statique est le bas du TEXTE de la mention : la marge basse de celle-ci (--chat-mention-marge) est
       reportée ici, pour que l'étiquette reste alignée sur le haut du panneau. */
    margin-top: var(--chat-mention-marge);
    left: calc(50% - var(--chat-panneau-largeur) / 2);
    translate: calc(-100% - var(--sp-4)) 0;
    padding: var(--sp-1) var(--sp-4) var(--sp-1) calc(var(--sp-3) + 24px + var(--sp-2));
    border-radius: var(--bulle-rayon) var(--bulle-rayon) var(--bulle-coin) var(--bulle-rayon);
    background-color: var(--bulle-assistant-fond);
    background-image: var(--chat-etiquette-icone);
    background-repeat: no-repeat;
    background-position: var(--sp-3) center;
    background-size: 24px 24px;
    box-shadow: var(--shadow);
    color: var(--bulle-assistant-texte);
    font-size: var(--fs-micro);
    font-weight: 700;
    line-height: 24px;
    white-space: nowrap;
    pointer-events: none;
  }
}

.messages {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* Le site est plat : pas d'ombre sur les bulles. Le coin réduit côté émetteur
   distingue les rôles sans recourir à la seule couleur. Rayons et couleurs par
   rôle : --bulle-* (section 2) ; le corps du texte, lui, garde 16px et son
   interlignage 1.5 (.msg__body). */
.msg {
  border-radius: var(--bulle-rayon);
  padding: var(--sp-3) var(--sp-4);
  max-width: 92%;
  box-shadow: none;
  overflow-wrap: anywhere;
}

.msg--user {
  align-self: flex-end;
  max-width: 80%;
  background: var(--bulle-utilisateur-fond);
  color: var(--bulle-utilisateur-texte);
  border-radius: var(--bulle-rayon) var(--bulle-rayon) var(--bulle-coin) var(--bulle-rayon);
}

.msg--user .msg__role {
  color: var(--bulle-utilisateur-surtitre);
}

.msg--assistant {
  align-self: flex-start;
  width: 100%;
  max-width: 100%;
  background: var(--bulle-assistant-fond);
  color: var(--bulle-assistant-texte);
  border: none;
  border-radius: var(--bulle-rayon) var(--bulle-rayon) var(--bulle-rayon) var(--bulle-coin);
}

.msg__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.msg__role {
  margin-bottom: var(--bulle-surtitre-marge);
  font-size: var(--bulle-surtitre-fs);
  font-weight: var(--bulle-surtitre-graisse);
  line-height: 1.2;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--muted);
}

.msg--assistant .msg__role {
  color: var(--bulle-assistant-surtitre);
}

.msg__meta {
  font-size: var(--fs-micro);
  color: var(--muted);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 1px 10px;
}

body.hide-meta .meta-badge {
  display: none !important;
}

/* Interlignage 1.5 pour tout texte lu, en dur et non --interligne : le 1.2 des
   chartes de site (Indigo comme Efrei) étoufferait des réponses de plusieurs
   paragraphes. Un pack resserre le chrome par --interligne, jamais la réponse. */
.msg__body {
  line-height: 1.5;
}

/* Le gras par défaut du navigateur (700) est la seule graisse 700 de
   l'interface, et c'est la plus visible : on la ramène à 600. */
.msg__body strong,
.msg__body b {
  font-weight: 600;
}

.msg__body p {
  margin: 0 0 0.6em;
}

.msg__body p:last-child,
.msg__body ul:last-child,
.msg__body ol:last-child {
  margin-bottom: 0;
}

.msg__body ul,
.msg__body ol {
  margin: 0 0 0.6em;
  padding-left: 1.4em;
}

.msg__body li {
  margin: 0.15em 0;
}

.msg--assistant .msg__body li::marker {
  color: var(--accent);
}

.msg__body .md-heading {
  margin-top: 0.4em;
  font-size: 1rem;
  font-weight: 600;
}

.msg--assistant[aria-busy="true"] .msg__body:empty::after {
  content: "…";
  color: var(--muted);
}

/* Appel de citation : repère de lecture, donc --accent. */
.cite {
  font-size: max(0.75em, 0.75rem); /* jamais sous 12px, quelle que soit la base héritée */
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  vertical-align: super;
  padding: 0 1px;
  transition: color var(--t-fast);
}

.cite:hover,
.cite:focus {
  color: var(--secondaire);
  text-decoration: underline;
}

/* L'erreur ne repose jamais sur la seule couleur : symbole + libellé. */
.msg__error {
  margin-top: var(--sp-2);
  color: var(--err);
  font-weight: 600;
}

.msg__error::before {
  content: "⚠ ";
}

/* --- Sources citées ------------------------------------------------------- */

.msg__sources {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}

.msg__sources-title {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.2;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--muted);
}

.src-list {
  margin: 0;
  padding-left: 0;
  list-style: none;
  font-size: var(--fs-sm);
}

.src {
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-xs);
  transition: background-color 0.3s;
}

/* Le filet --accent garde le repère visible même désaturé. */
.src--highlight {
  background: var(--teinte-primaire);
  box-shadow: inset 3px 0 0 var(--accent);
}

.src__n {
  color: var(--accent);
  font-weight: 600;
  margin-right: 2px;
}

.src__meta {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.msg__footer {
  margin-top: var(--sp-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.msg__usage {
  font-size: var(--fs-micro);
  color: var(--muted);
}

/* visibility et non display : la hauteur du pied est conservée. */
body.hide-meta .msg__usage {
  visibility: hidden;
}

.msg__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.feedback {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

.feedback__thanks {
  font-size: var(--fs-xs);
  color: var(--ok);
  font-weight: 600;
}

.feedback__error {
  font-size: var(--fs-xs);
  color: var(--err);
}

/* --- Retour détaillé sur une réponse (👎) ---------------------------------

   Le panneau occupe sa propre ligne du pied (.msg__footer est un flex à retour à
   la ligne). Surface blanche posée sur la bulle assistant #f2eef4 : les deux ne
   sont pas à 3:1, d'où le filet --border. Aucune animation d'apparition : le champ
   doit être utilisable à l'instant du clic.

   Le panneau n'est PAS un descendant de .feedback : le préfixe .feedback__ garde le
   vocabulaire, aucune règle ne suppose d'ancêtre (même parti pris que .timings__*).

   Aucune règle de masquage `body.hide-meta` ne porte sur ce panneau, contrairement
   à .timings et .msg__usage : le retour utilisateur n'est pas un détail technique,
   c'est son canal à lui — il ne suit pas la bascule « Détails techniques ». */

.feedback__panel {
  flex: 1 0 100%;
  margin-top: var(--sp-2);
}

.feedback__form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}

.feedback__label {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--secondaire);
}

.feedback__hint,
.feedback__privacy {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* Filet --secondaire : repère de lecture, pas un état — la phrase se suffit à elle-même. */
.feedback__privacy {
  border-left: 3px solid var(--secondaire);
  padding-left: var(--sp-2);
}

/* 16px obligatoire : en dessous, iOS zoome à la prise de focus (cf. .composer__input). */
.feedback__field {
  font: inherit;
  font-size: var(--fs-body);
  line-height: 1.4;
  width: 100%;
  min-height: 84px;
  max-height: 240px;
  resize: vertical;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--bg);
  color: var(--text);
  transition: border-color var(--t-fast);
}

.feedback__field::placeholder {
  color: var(--placeholder);
}

.feedback__field:focus {
  border-color: var(--secondaire);
}

.feedback__field:read-only {
  background: var(--gris-inactif);
  color: var(--muted);
}

.feedback__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.feedback__count {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--muted);
}

/* Jamais la couleur seule : à la limite, le texte du compteur change aussi. */
.feedback__count--limite {
  color: var(--warn);
  font-weight: 600;
}

.feedback__actions {
  display: flex;
  gap: var(--sp-2);
}

/* min-height : la ligne de statut réserve sa place, l'apparition du message ne fait
   pas sauter le pied sous le curseur. */
.feedback__status {
  margin: 0;
  min-height: 1.2em;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.feedback__status--err {
  color: var(--err);
  font-weight: 600;
}

/* Même convention que .msg__error::before : l'erreur ne repose pas sur la couleur. */
.feedback__status--err::before {
  content: "⚠ ";
}

.feedback__done {
  padding: var(--sp-3);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}

/* Le conteneur reçoit le focus (tabindex="-1") au retour du serveur : l'anneau doit s'y voir. */
.feedback__done:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.feedback__done-text {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ok);
}

/* Rappel de ce qui a été enregistré. Texte de visiteur : toujours inséré échappé. */
.feedback__echo {
  margin: var(--sp-2) 0 0;
  padding-left: var(--sp-2);
  border-left: 3px solid var(--border-strong);
  font-size: var(--fs-xs);
  color: var(--muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- Chronométrage de la réponse ------------------------------------------

   Un même vocabulaire de classes sert DEUX rendus :
     - le chat : une cascade chronologique sous la réponse, chaque barre posée à
       son abscisse réelle (`left`) et large de sa durée (`width`) ;
     - l'administration : des barres de proportion dans un tableau agrégé, toutes
       calées à gauche (`left: 0`).
   Aucune règle ci-dessous ne suppose donc un élément parent : __row, __label,
   __kind, __ms, __share, __bar, __bar-fill, __mark, __rest et --child sont
   autonomes ; seules les variantes de l'en-tête sont portées par « .timings__head … ».
   L'administration ne pose PAS la classe de bloc .timings sur ses tableaux : elle
   importerait l'échelle typographique du chat et le masquage « Détails techniques ».

   Aucune animation ici : le bloc est écrit une seule fois, en fin de tour. La règle
   globale de la section 10 (prefers-reduced-motion) neutralise de toute façon toute
   transition qui serait ajoutée plus tard. */

.timings {
  /* Repère des .sr-only du tableau (position: absolute) : sans lui, ils se plaçaient par rapport à la page et
     l'allongeaient une fois « Temps de réponse » ouvert (document de 1 502 px au lieu de 812 chez Indigo en
     375 × 812, défilement dans le vide ; revue Guy Hoquet du 24/09/2026, correctif appliqué le 28/09/2026). */
  position: relative;
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs);
}

/* Le chronométrage est un détail technique : il disparaît avec le badge. */
body.hide-meta .timings {
  display: none !important;
}

/* --- En-tête repliable ---------------------------------------------------- */

.timings__head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
  /* Le résumé (« premier mot à 7,89 s… ») est long : sans cette césure il se faisait rogner
     silencieusement sous 400 px, sans barre de défilement pour le signaler. */
  overflow-wrap: anywhere;
  cursor: pointer;
  color: var(--muted);
  list-style: none; /* Firefox */
}

.timings__head::-webkit-details-marker {
  display: none; /* Safari, Chrome */
}

/* Chevron décoratif : l'état est déjà porté par aria-expanded, natif de <summary>. */
.timings__head::before {
  content: "▸";
  color: var(--accent);
}

details[open] > .timings__head::before {
  content: "▾";
}

.timings__head:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

.timings__head .timings__label {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
}

.timings__head .timings__ms {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}

/* Pastille de branche : --secondaire = hiérarchie et état, jamais --primaire (réservé à l'action). */
.timings__head .timings__kind {
  padding: 1px 10px;
  border-radius: var(--radius-pill);
  background: var(--card);
  border: 1px solid var(--secondaire-filet);
  color: var(--secondaire);
  font-size: var(--fs-micro);
  font-weight: 600;
}

/* Résumé (« premier mot à 812 ms (… au navigateur) ») : insécable par défaut (--chrono-resume-retour), il passe sous
   la ligne du titre d'un bloc. Un pack dont la police est plus large le laisse se replier (normal) : sur 375px, en
   Montserrat, il débordait de la bulle. */
.timings__head .timings__share {
  white-space: var(--chrono-resume-retour);
}

/* --- Tableau de la cascade (chat) ----------------------------------------- */

.timings table {
  width: 100%;
  margin-top: var(--sp-2);
  border-collapse: collapse;
}

.timings th,
.timings td {
  padding: 3px var(--sp-2);
  text-align: left;
  vertical-align: baseline;
  font-weight: 400;
}

.timings thead th {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--muted);
  white-space: nowrap;
}

/* La colonne des libellés porte le sens : elle prend la moitié de la largeur,
   celle de la cascade absorbe une bonne part du reste. */
.timings thead th:first-child {
  width: 46%;
}

.timings thead th:last-child {
  width: 30%;
}

/* --- Lignes (chat ET administration) -------------------------------------- */

.timings__row {
  border-top: 1px solid var(--border);
}

/* `overflow-wrap: break-word` et non `anywhere` (hérité de .msg) : `anywhere` entre
   dans le calcul de largeur minimale et laisse l'algorithme de tableau réduire cette
   colonne à un caractère de large sur petit écran. */
.timings__label {
  color: var(--text);
  overflow-wrap: break-word;
}

/* Sous-étape : décalage et atténuation. La relation au parent est aussi écrite en
   toutes lettres dans un span .sr-only du libellé — l'indentation seule serait
   invisible pour un lecteur d'écran. */
.timings--child .timings__label {
  padding-left: var(--sp-5);
  color: var(--muted);
}

.timings__ms {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.timings__share {
  white-space: nowrap;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Teinte par nature, posée sur la LIGNE (ou sur un élément de légende), consommée
   par la barre et par le libellé de nature. Le libellé est toujours écrit : la
   couleur ne fait que le redoubler (WCAG 1.4.1). */
.timings__row[data-kind="openai"],
.timings__legend [data-kind="openai"] {
  --timings-teinte: var(--primaire);
}

.timings__row[data-kind="db"],
.timings__legend [data-kind="db"] {
  --timings-teinte: var(--secondaire);
}

.timings__row[data-kind="cpu"],
.timings__legend [data-kind="cpu"] {
  --timings-teinte: var(--quaternaire);
}

.timings__row[data-kind="client"],
.timings__legend [data-kind="client"] {
  --timings-teinte: var(--muted);
}

.timings__kind {
  color: var(--timings-teinte, var(--muted));
  font-size: var(--fs-micro);
  font-weight: 600;
  white-space: nowrap;
}

/* --- Barres --------------------------------------------------------------- */

.timings__bar {
  position: relative;
  display: block;
  width: 100%;
  min-width: 80px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--border);
  overflow: hidden;
}

/* `left` et `width` sont posés en style en ligne : ce sont des valeurs calculées. */
.timings__bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  min-width: 2px;
  border-radius: var(--radius-pill);
  background: var(--timings-teinte, var(--quaternaire));
}

/* Repère instantané (premier jeton) : un trait --encre, lisible sur toutes les teintes. */
.timings__mark .timings__label {
  font-weight: 600;
}

.timings__mark .timings__kind {
  color: var(--muted);
}

.timings__mark .timings__bar-fill {
  background: var(--encre);
}

/* Reste non imputé : filet pointillé, texte atténué — ce n'est pas une étape. */
.timings__rest {
  border-top: 1px dashed var(--border-strong);
}

.timings__rest .timings__label,
.timings__rest .timings__kind {
  color: var(--muted);
}

/* --- Légende des couleurs (chat) ------------------------------------------ */

.timings__legend {
  margin: var(--sp-2) 0 0;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  font-size: var(--fs-micro);
  color: var(--muted);
}

.timings__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

.timings__swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--timings-teinte, var(--quaternaire));
}

/* --- Indicateur de rédaction --------------------------------------------- */

.typing {
  max-width: var(--max-width);
  margin: var(--sp-3) auto 0;
  font-size: var(--fs-sm);
  color: var(--chat-texte-doux);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.typing__dots {
  display: inline-flex;
  gap: 3px;
}

.typing__dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: typing-bounce 1.2s infinite ease-in-out;
}

.typing__dots i:nth-child(2) {
  animation-delay: 0.15s;
}

.typing__dots i:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes typing-bounce {
  0%,
  80%,
  100% {
    transform: translateY(0);
    opacity: 0.4;
  }
  40% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

/* --- Zone de saisie ------------------------------------------------------- */

/* Moitié basse du panneau de conversation (voir .chat) : même fond, même attache,
   même largeur, coins bas. Le filet haut (--chat-surface-filet) sépare les deux
   moitiés ; transparent, elles se lisent comme une seule surface. */
.composer-wrap {
  flex: 0 0 auto;
  width: 100%;
  max-width: var(--chat-panneau-largeur);
  margin-left: auto;
  margin-right: auto;
  background: var(--chat-panneau-fond);
  background-attachment: var(--chat-panneau-attache);
  border-top: 1px solid var(--chat-surface-filet);
  border-radius: 0 0 var(--chat-panneau-rayon) var(--chat-panneau-rayon);
  box-shadow: var(--chat-panneau-ombre);
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom));
}

/* Le formulaire : transparent et sans forme par défaut ; un pack en fait une
   pilule qui porte le champ et les boutons (--composeur-*), le champ devenant
   alors transparent et sans filet (--saisie-*). */
.composer {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  gap: var(--sp-2);
  align-items: flex-end;
  padding: var(--composeur-padding);
  background: var(--composeur-fond);
  border: var(--composeur-filet);
  border-radius: var(--composeur-rayon);
}

/* Champ sur --bg au rayon 4px des champs du site — mais bordé : un gris de
   fond de page sur blanc reste sous 1,1:1, en échec de WCAG 1.4.11 (éléments
   non textuels). Un pack qui borde la pilule (--composeur-filet) peut retirer
   ce filet-ci (--saisie-filet) : la limite du champ reste alors visible. */
.composer__input {
  flex: 1 1 auto;
  font: inherit;
  font-size: var(--fs-body);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--saisie-filet);
  border-radius: var(--saisie-rayon);
  resize: none;
  min-height: 44px;
  max-height: 200px;
  line-height: 1.4;
  background: var(--saisie-fond);
  color: var(--text);
  transition: border-color var(--t-fast);
}

.composer__input::placeholder {
  color: var(--placeholder);
}

/* Filet du focus (--saisie-filet-focus, --secondaire par défaut) : un pack dont le champ est sans filet
   dans une pilule le garde transparent, l'anneau de focus suffit (sinon double contour). */
.composer__input:focus {
  border-color: var(--saisie-filet-focus);
}

/* Lecture seule (réponse en cours, pack pas encore chargé) : fond --saisie-fond-lecture (--gris-inactif par défaut) ;
   un pack dont le champ est posé dans une pilule le garde transparent (sinon un rectangle gris dans la pilule). */
.composer__input:read-only {
  background: var(--saisie-fond-lecture);
  color: var(--muted);
}

.composer__actions {
  display: flex;
  gap: var(--sp-2);
}

/* 48px = hauteur réelle du champ (44px + remplissage + filet) : sans cela le
   bouton, calé en bas, laisse une marche de 4px. */
.composer__actions .btn {
  min-height: 48px;
}

.composer__hint {
  max-width: var(--max-width);
  margin: var(--sp-2) auto 0;
  font-size: var(--fs-micro);
  color: var(--chat-texte-doux);
}

/* --- Barre de saisie à icônes (option ui.composerIcons de client.json) -----

   Balisage produit par app.js SEULEMENT quand le pack active l'option : aucune de
   ces règles ne touche une page dont le pack ne l'active pas (ni classe, ni élément).
   - #send perd .btn/.btn--primary pour .composer__icon-btn : icône d'envoi, libellé
     « Envoyer » réservé aux lecteurs d'écran (.sr-only) et repris en infobulle ;
   - un lien #composer-voice (.composer__icon-btn, icône micro) mène à /vocal, révélé
     comme le lien « Mode vocal » de l'en-tête (vocal ouvert, jamais en mode embarqué) ;
   - body.composer-icons masque ce lien de l'en-tête, qui ferait doublon.
   #stop garde son bouton texte (.btn--danger). Icônes à la couleur du texte du champ
   (--text), qui se lit sur le fond du formulaire, sauf l'icône d'envoi quand le pack
   la pose sur son bouton principal (--composeur-envoi-*, Indigo et EFREI) ; cible
   de 44px (WCAG 2.5.5). */
.composer__icon-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: color var(--t-fast);
}

.composer__icon-btn:hover,
.composer__icon-btn:focus {
  color: var(--accent);
}

/* Même convention que .btn:disabled (pack pas chargé, réponse en cours). */
.composer__icon-btn:disabled {
  opacity: 0.6;
  color: var(--text);
  cursor: not-allowed;
}

/* Icône d'envoi (#send) : fond, couleur et rayon réglés par le pack (--composeur-envoi-*, section 2) ; un pack la pose
   sur la couleur de son bouton principal (Indigo, EFREI). Défauts égaux aux règles ci-dessus, dont ces règles
   reprennent les états : rendu inchangé sans réglage. Le micro garde les règles communes. */
#send.composer__icon-btn {
  background: var(--composeur-envoi-fond);
  color: var(--composeur-envoi-texte);
  border-radius: var(--composeur-envoi-rayon);
}

#send.composer__icon-btn:hover,
#send.composer__icon-btn:focus {
  background: var(--composeur-envoi-fond-survol);
  color: var(--composeur-envoi-texte-survol);
}

#send.composer__icon-btn:disabled {
  background: var(--composeur-envoi-fond);
  color: var(--composeur-envoi-texte-desactive);
}

.composer__icon {
  display: block;
  width: 20px;
  height: 20px;
}

body.composer-icons #voice-mode {
  display: none;
}

/* Réponse en cours (setBusy d'app.js : champ en lecture seule marqué aria-busy="true", #stop révélé) : le bouton Stop
   prend la place de l'icône d'envoi, désactivée, au lieu de s'intercaler entre elle et le micro. Le champ en lecture
   seule d'un pack pas encore chargé n'a pas aria-busy : l'icône d'envoi (désactivée) y reste. */
body.composer-icons .composer__input[aria-busy="true"] ~ .composer__actions #send {
  display: none;
}

/* Texte indicatif sur une seule ligne, rogné au bord du champ : dans un champ d'une ligne, un texte
   indicatif replié sur deux lignes était coupé à mi-hauteur (écran de 375px, icônes à droite).
   overflow: hidden est indispensable : la feuille par défaut de Chrome ne rogne pas le texte indicatif
   d'un textarea (valeur calculée : visible), qui déborderait sur la marge droite et ferait apparaître
   une barre de défilement horizontale dans le champ (mesuré par le banc à 375px, revue finale du
   24/09/2026). Pas de text-overflow : Chrome ne l'applique pas à ::placeholder (valeur calculée : clip),
   le texte trop long est coupé net, sans points de suspension. Sous 480px (section 10), réduit à 14px
   et marges latérales du champ resserrées, le texte saisi gardant 16px (pas de zoom d'iOS). Limité à la
   barre à icônes : sans l'option, rien ne change. */
body.composer-icons .composer__input::placeholder {
  white-space: nowrap;
  overflow: hidden;
}

/* =============================================================================
   6 bis. MODE VOCAL (page /vocal, src/server/vocal-ui/vocal.html + public/vocal.js ;
          version compacte du cadre /vocal/embed, vocal-embed.html, en fin de section)

   Même mise en page que le chat (.page-chat : en-tête, #chat qui défile, pied
   fixe) et mêmes bulles (.msg) : la page ne fait qu'ajouter la mention d'avant
   démarrage, les sous-titres, les cartes de réponse écrite, un indicateur d'état
   et les commandes. Aucune variable propre au vocal n'est ouverte aux packs : la
   page emploie celles du chat (section 2) pour ce qui est posé à même le fond de
   page ou du panneau — titre et état (--chat-texte), aide et minuteur
   (--chat-texte-doux), puces de l'aide (--chat-puce), erreur (--chat-erreur),
   minuteur en fin proche (--chat-alerte), anneau des commandes
   (--focus-sur-sombre) et de la conversation (--chat-focus-decalage) — et les jetons
   communs pour les cartes (mention, réponses écrites, récapitulatif). --vocal-niveau
   est une variable LOCALE de l'indicateur (0 à 1, niveau sonore de la voix posé par
   vocal.js), jamais lue d'un theme.css.

   L'indicateur est décoratif (aria-hidden) : l'état est toujours écrit en
   toutes lettres dans #vocal-status. Mouvement réduit : plus d'animation ni de
   mesure du niveau (vocal.js ne l'active pas), l'état reste lisible à l'arrêt.
   ============================================================================= */

/* --- Avant le démarrage : titre, mention du pack, mode d'emploi ----------- */

.vocal__intro {
  max-width: var(--max-width);
  margin: 0 auto var(--sp-5);
}

.vocal__intro-title {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-titre);
  font-size: var(--fs-display-3);
  font-weight: 800;
  line-height: 1.2;
  color: var(--chat-texte);
}

/* Mention (voice.json, ui.notice) : repère de lecture --accent, texte courant sur
   la teinte primaire (couple déjà vérifié pour le badge accent et la ligne choisie). */
.vocal__notice {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--teinte-primaire);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-xs);
  color: var(--text);
  line-height: 1.5;
}

.vocal__help {
  margin: 0;
  padding-left: 1.4em;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--chat-texte-doux);
}

/* Puces : --chat-puce (--accent par défaut) ; un pack dont le panneau est sombre les éclaircit. */
.vocal__help li::marker {
  color: var(--chat-puce);
}

/* --- Transcription -------------------------------------------------------- */

/* Réplique de l'assistant dite à voix haute (sous-titres) : surface blanche filetée,
   pour la distinguer de la réponse écrite, qui garde la bulle du chat. */
.msg--assistant.vocal__line {
  background: var(--card);
  border: 1px solid var(--border);
}

.vocal__line .msg__body {
  white-space: pre-wrap;
}

/* Réponse écrite d'une délégation : la bulle du chat, cernée d'un pointillé tant que la
   recherche court. Un contour (outline) et non un filet : la carte ne bouge pas quand la
   réponse arrive, et le filet imprimé des bulles (section 11) reste le sien. */
.vocal__answer[aria-busy="true"] {
  outline: 1px dashed var(--border-strong);
  outline-offset: -1px;
}

/* Question reconstituée par le serveur : aide à repérer une transcription ratée. */
.vocal__question {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* Recherche en cours dans la carte : l'indicateur de rédaction du chat, sans sa marge de colonne, et
   dans le ton atténué des cartes (--muted) : .typing suit --chat-texte-doux, prévu pour le fond. */
.vocal__pending {
  margin: var(--sp-1) 0 0;
  max-width: none;
  color: var(--muted);
}

/* --- Récapitulatif de fin ------------------------------------------------- */

.vocal__summary {
  max-width: var(--max-width);
  margin: var(--sp-5) auto 0;
  padding: var(--sp-4);
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}

.vocal__summary-title {
  margin: 0 0 var(--sp-2);
  font-family: var(--font-titre);
  font-size: var(--fs-display-3);
  font-weight: 800;
  line-height: 1.2;
  color: var(--text);
}

.vocal__summary-reason {
  margin: 0;
  line-height: 1.5;
}

.vocal__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  margin: var(--sp-3) 0 0;
}

.vocal__facts dt {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--muted);
}

.vocal__facts dd {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.vocal__summary-links {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-sm);
}

/* --- Pied : erreur, annonce, indicateur, état, minuteur, commandes -------- */

.vocal__panel {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* Même convention que .msg__error : symbole + texte, jamais la couleur seule. */
.vocal__error {
  max-width: var(--max-width);
  margin: 0 auto var(--sp-2);
  color: var(--chat-erreur);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.vocal__error::before {
  content: "⚠ ";
}

/* Annonce (fin proche, inactivité, plafond) : couple du badge d'avertissement. Le son
   bloqué passe par .vocal__error (role="alert") : la voix, muette, ne peut pas le dire. */
.vocal__alert {
  max-width: var(--max-width);
  margin: 0 auto var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: var(--radius-xs);
  color: var(--warn);
  font-size: var(--fs-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.vocal__indicator {
  --vocal-niveau: 0;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
}

/* Pastille : repos gris ; le halo (--teinte-primaire) grossit avec la voix. */
.vocal__orb {
  position: relative;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--border-strong);
  box-shadow: 0 0 0 calc(3px + var(--vocal-niveau) * 14px) var(--teinte-primaire);
  transition: box-shadow 0.08s linear, background-color var(--t-fast);
}

.vocal__dots {
  display: none;
}

.vocal__indicator[data-phase="connecting"] .vocal__orb,
.vocal__indicator[data-phase="closing"] .vocal__orb {
  animation: vocal-pulse 1.2s ease-in-out infinite;
}

.vocal__indicator[data-phase="listening"] .vocal__orb {
  background: var(--accent);
  animation: vocal-respire 2.4s ease-in-out infinite;
}

.vocal__indicator[data-phase="speaking"] .vocal__orb {
  background: var(--secondaire);
}

/* Recherche : les trois points du chat remplacent la pastille. */
.vocal__indicator[data-phase="searching"] .vocal__orb {
  display: none;
}

.vocal__indicator[data-phase="searching"] .vocal__dots {
  display: inline-flex;
}

/* Micro coupé : pastille évidée et barrée (le texte d'état le dit aussi). */
.vocal__indicator[data-phase="muted"] .vocal__orb {
  background: var(--card);
  border: 2px solid var(--err);
}

.vocal__indicator[data-phase="muted"] .vocal__orb::after {
  content: "";
  position: absolute;
  top: 50%;
  left: -3px;
  right: -3px;
  height: 2px;
  background: var(--err);
  transform: rotate(-45deg);
}

@keyframes vocal-pulse {
  0%,
  100% {
    opacity: 0.45;
  }
  50% {
    opacity: 1;
  }
}

@keyframes vocal-respire {
  0%,
  100% {
    transform: scale(0.92);
  }
  50% {
    transform: scale(1);
  }
}

.vocal__state {
  flex: 1 1 200px;
  min-width: 0;
}

.vocal__status {
  margin: 0;
  font-weight: 600;
  line-height: 1.4;
  color: var(--chat-texte);
}

.vocal__timer {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--chat-texte-doux);
  font-variant-numeric: tabular-nums;
}

/* Fin proche : l'annonce écrite (#vocal-alert) porte l'information, la couleur la redouble. */
.vocal__timer[data-alerte="true"] {
  color: var(--chat-alerte);
  font-weight: 600;
}

.vocal__controls {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

/* --- Mode embarqué (page /vocal/embed, src/server/vocal-ui/vocal-embed.html) ---
   Version compacte pour le cadre de la bulle (380 × 508 px) : ni en-tête ni logotype
   (la marque est celle du site hôte), gouttières de .page-embed, mention courte du
   cadre visible en entier avant « Démarrer » ; l'aide et la transcription défilent
   dans #chat. « Revenir à l'écrit » reprend la ligne d'action de /embed
   (.embed-actions). Chaque sélecteur porte .page-vocal ET .page-embed : aucune autre
   page n'est touchée, et aucune variable n'est ouverte aux packs. */
.page-vocal.page-embed .chat {
  padding-top: var(--sp-3);
  padding-bottom: var(--sp-3);
}

.page-vocal.page-embed .vocal__intro {
  margin-bottom: var(--sp-3);
}

.page-vocal.page-embed .vocal__intro-title {
  margin-bottom: var(--sp-2);
  font-size: var(--fs-body);
}

.page-vocal.page-embed .vocal__notice {
  margin-bottom: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.page-vocal.page-embed .vocal__help {
  font-size: var(--fs-xs);
}

.page-vocal.page-embed .embed-actions {
  margin-bottom: var(--sp-1);
}

.page-vocal.page-embed .vocal__panel {
  gap: var(--sp-2) var(--sp-3);
}

.page-vocal.page-embed .vocal__status {
  font-size: var(--fs-sm);
}

/* Cadre bas (moins de 475px de haut : fenêtre basse, la bulle mesurant au plus
   100vh − 116px, ou iframe posée à la main) : la mention courte ne tiendrait plus
   en entier au-dessus de « Démarrer » (Guy Hoquet : 15px coupés à 460px, 43px à
   432px). La page se compacte : titre réservé aux lecteurs d'écran (il nomme
   toujours la section), mention en 13px, indicateur réduit, pied resserré.
   Mention entière jusqu'à 400px de haut au moins (relevé dans Chrome). */
@media (max-height: 474px) {
  .page-vocal.page-embed .chat {
    padding-top: var(--sp-2);
  }

  .page-vocal.page-embed .vocal__intro-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .page-vocal.page-embed .vocal__notice {
    font-size: var(--fs-xs);
    line-height: 1.4;
  }

  .page-vocal.page-embed .composer-wrap {
    padding-top: var(--sp-2);
    padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom));
  }

  .page-vocal.page-embed .vocal__indicator {
    width: 32px;
    height: 32px;
  }

  .page-vocal.page-embed .vocal__orb {
    width: 20px;
    height: 20px;
  }
}

/* =============================================================================
   7. ADMINISTRATION
   ============================================================================= */

.admin-main {
  max-width: var(--wrap-max);
  margin: 0 auto;
  padding: var(--sp-5) var(--sp-4);
}

.admin-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--muted);
}

/* Le filet gauche remplace l'icône ronde du bandeau d'alerte du site :
   l'erreur ne repose pas sur la seule couleur. */
.admin-error {
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-xs);
  background: var(--err-bg);
  border: 1px solid var(--err-border);
  border-left: 4px solid var(--err);
  color: var(--err);
  font-size: var(--fs-sm);
}

.admin-error ul {
  margin: var(--sp-1) 0 0;
  padding-left: 1.2em;
}

.admin-section {
  margin: 0 0 var(--sp-6);
}

.admin-section h2 {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-display-2);
  color: var(--text);
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

/* Le compteur reste en texte courant : le condensé majuscule « crie » dès
   qu'on le répète, il est réservé aux titres eux-mêmes. */
.admin-section h2 .count {
  font-family: var(--font);
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: 1.4;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
}

.admin-help {
  max-width: 76ch;
  margin: 0 0 var(--sp-4);
  color: var(--muted);
  line-height: 1.6;
}

.admin-help strong {
  color: var(--text);
  font-weight: 600;
}

/* Même surface que les cartes et les tableaux : sans elle, « Tester une
   question » et le bloc des messages flottent à nu sur le gris. */
.admin-panel {
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.admin-panel__title {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-display-3);
  color: var(--text);
}

.admin-header-link {
  font-size: var(--fs-sm);
}

/* --- Cartes de statistiques ----------------------------------------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-3);
}

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-4);
  box-shadow: var(--shadow);
  min-width: 0;
}

.card__label {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.2;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  color: var(--muted);
}

/* Chiffres-clés en --accent, titre condensé (.card__value, section 3).
   Tous de la même couleur : c'est un système, pas du bariolage. */
.card__value {
  margin: 0;
  /* Police des chiffres (--chiffres-font, --font-titre par défaut, déjà posée section 3) : un pack dont la police de
     titre n'a que des capitales y met son texte courant (« 3 min 15 s » et non « 3 MIN 15 S »). */
  font-family: var(--chiffres-font);
  font-size: var(--fs-display-1);
  color: var(--accent);
  overflow-wrap: anywhere;
}

/* Une chaîne technique (version de corpus, horodatage) reste neutre. */
.card__value--small {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: 1.4;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
}

/* Interlignage 1.5 en dur, comme .msg__body : ces détails de carte sont du texte
   courant qui court sur plusieurs lignes en écran étroit (« 2026-09 · plafond
   100 € · 0,7 % utilisé · 334 appels API… »), que le 1.2 d'une charte de site
   étoufferait. C'est la valeur par défaut de --interligne : rendu inchangé pour
   un pack qui ne la resserre pas (Indigo). */
.card__detail {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--muted);
}

.card__detail ul {
  margin: 0;
  padding-left: 0;
  list-style: none;
}

.card__detail li {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
}

/* --- Tableaux : pas de remplissage, des filets (modèle du site) ------------ */

/* `overflow: auto` et non `overflow-x` : avec overflow-x seul, overflow-y est
   calculé à `auto` mais le conteneur ne défile pas verticalement, et l'en-tête
   collant du tableau suit la page au lieu de rester visible. */
.table-wrap {
  overflow: auto;
  max-height: min(70vh, 720px);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.table th,
.table td {
  padding: 10px var(--sp-4);
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  line-height: 1.4;
}

/* Fond opaque obligatoire pour position: sticky. Le filet --encre est posé en
   ombre interne : un border sur un th collant se décroche au défilement. */
.table th {
  background: var(--card);
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 2;
  border-bottom: 0;
  box-shadow: inset 0 -2px 0 var(--encre);
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table--clickable tbody tr {
  cursor: pointer;
}

.table--clickable tbody tr:hover,
.table--clickable tbody tr:focus-within {
  background: var(--bg);
}

.table--clickable tbody tr.is-selected {
  background: var(--teinte-primaire);
}

.table--clickable tbody tr.is-selected td:first-child {
  box-shadow: inset 3px 0 0 var(--accent);
}

.table td.num,
/* Colonne « Poids » : le champ et son message d'erreur tiennent sans casser. */
.table--weights td:nth-child(7) {
  min-width: 160px;
}

.table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.table .mono {
  font-family: var(--mono);
  font-size: var(--fs-xs);
}

.table .truncate {
  max-width: 420px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table .empty {
  color: var(--muted);
  font-style: normal;
  text-align: center;
  padding: var(--sp-5);
}

.table .nowrap {
  white-space: nowrap;
}

.src-inline {
  display: block;
  font-size: var(--fs-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 240px;
  color: var(--muted);
}

/* =============================================================================
   8. ADMINISTRATION — PONDÉRATION DES CONTENUS
   ============================================================================= */

.weights-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}

.weights-toolbar label.btn {
  cursor: pointer;
}

.weights-toolbar label.btn.is-disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* Le champ fichier (masqué) précède son libellé : le focus clavier s'affiche sur « Importer un CSV ». */
.weights-toolbar #weights-import:focus-visible + label.btn {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.weights-meta {
  margin: var(--sp-1) 0;
  font-size: var(--fs-sm);
}

/* Emploi de marque de --tertiaire (pastille) : « non enregistré ». Doublé d'un libellé, jamais
   porté par la seule couleur. */
.weights-meta.is-dirty {
  color: var(--warn);
  font-weight: 600;
}

.weights-meta.is-dirty::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--tertiaire);
  vertical-align: baseline;
}

.weights-status {
  margin: var(--sp-1) 0 var(--sp-3);
  min-height: 1.2em;
  font-size: var(--fs-sm);
  font-weight: 600;
}

.weights-status--ok {
  color: var(--ok);
}

.weights-status--warn {
  color: var(--warn);
}

.weights-status--err {
  color: var(--err);
}

/* --- Filtres et champs de formulaire -------------------------------------- */

.weights-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  margin: var(--sp-2) 0 var(--sp-3);
  font-size: var(--fs-sm);
}

.weights-filters label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text);
}

.weights-filters select,
.weights-filters input[type="search"],
.weights-preview__form input {
  font: inherit;
  font-size: var(--fs-sm);
  padding: var(--sp-2) var(--sp-3);
  min-height: 40px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--bg);
  color: var(--text);
  transition: border-color var(--t-fast);
}

.weights-filters select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 36px;
  /* Chevron en data-URI (pas de police d'icônes) : image entière --chevron-liste,
     trait de la couleur de --encre, redéfinie par theme.css avec sa charte. */
  background-image: var(--chevron-liste);
  background-repeat: no-repeat;
  background-position: right 12px center;
}

.weights-filters input[type="search"] {
  width: 16em;
}

.weights-filters select::placeholder,
.weights-filters input::placeholder,
.weights-preview__form input::placeholder {
  color: var(--placeholder);
}

.weights-filters select:focus,
.weights-filters input:focus,
.weights-preview__form input:focus {
  border-color: var(--secondaire);
}

.weights-filters select:focus-visible,
.weights-filters input:focus-visible,
.weights-preview__form input:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.weights-filters__check input[type="checkbox"] {
  accent-color: var(--accent);
  width: 16px;
  height: 16px;
  cursor: pointer;
}

/* --- Tableau de pondération ----------------------------------------------- */

.table--weights td,
.table--weights th {
  vertical-align: middle;
}

/* --secondaire marque le niveau structurel (en-tête de groupe). */
.table--weights tr.weights-group th {
  background: var(--bg);
  color: var(--secondaire);
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: var(--casse-surtitre);
  letter-spacing: var(--ls-surtitre);
  border-bottom: 1px solid var(--border);
  box-shadow: none;
  position: static;
}

/* Un <tbody> par niveau : on garde le filet sous la dernière ligne de chaque groupe, sauf le dernier. */
.table--weights tbody tr:last-child td {
  border-bottom: 1px solid var(--border);
}

.table--weights tbody:last-child tr:last-child td {
  border-bottom: 0;
}

.table--weights tr.is-modified td {
  background: var(--teinte-tertiaire);
}

.table--weights tr.is-modified td:first-child {
  box-shadow: inset 3px 0 0 var(--tertiaire);
}

.table--weights tr.is-orphan td {
  color: var(--muted);
  font-style: italic;
}

.weight-input {
  width: 6.5em;
  font: inherit;
  font-size: var(--fs-sm);
  text-align: right;
  padding: 6px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--card);
  color: var(--text);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--t-fast);
}

.weight-input::placeholder {
  color: var(--placeholder);
  font-size: 0.85em;
}

.weight-input:focus {
  border-color: var(--secondaire);
}

.weight-input:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.weight-input.is-invalid {
  border-color: var(--err);
  border-width: 2px;
  background: var(--err-bg);
}

/* Message d'erreur de saisie, relié au champ par aria-describedby. */
/* Pas de max-width : dans une colonne de 98px, le message se cassait en trois
   lignes et faisait doubler la hauteur de la ligne (la colonne « Poids » est
   élargie plus bas). */
.weight-error {
  display: block;
  margin: var(--sp-1) 0 0 auto;
  color: var(--err);
  font-size: var(--fs-micro);
  line-height: 1.3;
  text-align: right;
  white-space: normal;
}

/* Étiquettes de niveau : hiérarchie décroissante --secondaire > --quaternaire > --accent > --muted.
   La couleur n'est qu'un renfort, le niveau est toujours écrit en toutes lettres. */
.level-tag {
  display: inline-block;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  border: none;
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.5;
  background: var(--teinte-secondaire);
  color: var(--secondaire);
  white-space: nowrap;
}

.level-tag--type {
  background: var(--teinte-quaternaire);
  color: var(--quaternaire);
}

.level-tag--subtype {
  background: var(--teinte-primaire);
  color: var(--accent);
}

.level-tag--page {
  background: var(--bg);
  color: var(--muted);
}

/* --- Panneau de test ------------------------------------------------------ */

.weights-preview {
  margin-top: var(--sp-4);
}

.weights-preview__form {
  display: flex;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
}

.weights-preview__form input {
  flex: 1;
  min-width: 0;
}

.weights-preview__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-5);
  margin-top: var(--sp-2);
}

.weights-preview__grid h4 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-display-3);
  color: var(--text);
}

.preview-list {
  margin: 0;
  padding-left: 1.6em;
}

/* Marqueur numérique condensé en --accent, comme les listes ordonnées du site. */
.preview-list li::marker {
  font-family: var(--font-titre);
  font-weight: 800;
  color: var(--accent);
}

.preview-list li {
  margin-bottom: var(--sp-3);
}

.preview-hit__head a {
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
}

.preview-hit__head a:hover,
.preview-hit__head a:focus {
  color: var(--accent);
  text-decoration: underline;
}

.preview-hit__meta {
  font-size: var(--fs-xs);
  color: var(--muted);
}

.preview-hit__excerpt {
  font-size: 0.875rem;
  color: var(--text);
}

/* =============================================================================
   9. UTILITAIRES
   ============================================================================= */

.text-muted {
  color: var(--muted);
}

/* =============================================================================
   10. RESPONSIVE — points de rupture alignés sur le thème du site
   ============================================================================= */

/* Tablette */
@media (max-width: 1023px) {
  .weights-preview__grid {
    grid-template-columns: 1fr;
  }
}

/* Mobile */
@media (max-width: 767px) {
  .app-header {
    min-height: var(--header-h-mobile);
    padding: var(--sp-2) var(--sp-3);
  }
  .app-subtitle {
    display: none;
  }
  .app-header__actions .btn {
    font-size: var(--fs-xs);
    padding: 8px 14px;
    min-height: 36px;
  }
  /* En-tête en icônes (décision d'Arthur du 28/09/2026) : les boutons passent en icônes de 44 × 44px (.btn--icone,
     section 5), si bien que marque et actions tiennent sur UNE rangée dès que le plus long mot du titre y tient à côté
     du logotype : la marque prend la place restante (flex: 1 1 0) sans descendre sous sa largeur minimale de contenu
     (min-width: min-content : logotype, filet et plus long mot du titre, ou badge « Mode rapide »), et le titre ne se
     coupe qu'entre deux mots (overflow-wrap: normal ici, au lieu du anywhere des titres). Sinon, les actions passent
     seules sur une seconde rangée, calées à droite (margin-left: auto), plutôt que d'écraser le titre en le coupant au
     milieu des mots (revue du 28/09/2026 : chez Indigo en 375 × 812, « ASSISTANT » sur quatre lignes, et le badge de
     /fast sous l'icône « Mode normal »). Mesuré en 375 × 812 avant ce réglage, chez Guy Hoquet : 133px d'en-tête sur
     `/`, 156px sur `/fast` (trois boutons de texte sur deux rangées). Le libellé est masqué à l'écran seulement (même
     technique que .composer__hint), le bouton lui servant de repère (position: relative) : aucun débordement. */
  .app-header__brand {
    flex: 1 1 0;
    min-width: min-content;
  }
  .app-header__brand .app-title {
    overflow-wrap: normal;
  }
  .app-header__actions {
    flex: 0 0 auto;
    flex-wrap: nowrap;
    margin-left: auto;
  }
  .app-header__actions .btn--icone {
    position: relative;
    width: 44px;
    height: 44px;
    min-height: 44px;
    padding: 0;
    gap: 0;
  }
  .btn--icone .btn__icone {
    display: block;
  }
  .btn--icone .btn__libelle {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  /* Bascule enfoncée (« Détails techniques ») en icône : fond plein, sans quoi l'état ne tenait qu'au filet de 1px
     (fond et icône à 1,15:1 et 1,64:1 de l'état de repos chez Indigo, 1,13:1 et 1,73:1 chez l'EFREI ; revue du
     28/09/2026). Couleurs de la bascule enfoncée survolée (--bouton-bascule-survol, --bouton-bascule-texte-survol) :
     blanc sur --secondaire par défaut, 11,20:1 chez Indigo, 11,82:1 chez l'EFREI. Au-dessus de 768px, le libellé
     porte l'état : rendu inchangé. */
  .app-header__actions .btn--icone[aria-pressed="true"] {
    background: var(--bouton-bascule-survol);
    border-color: var(--bouton-bascule-survol);
    color: var(--bouton-bascule-texte-survol);
  }
  .brand__logo {
    height: var(--logo-hauteur-mobile);
  }
  .chat {
    padding: var(--sp-4) var(--sp-3);
  }
  .messages {
    gap: var(--sp-4);
  }
  .msg {
    max-width: 100%;
    padding: var(--sp-3);
  }
  .msg--user {
    max-width: 92%;
  }
  /* La cascade saute : sur 320 px elle n'apprend rien. Les chiffres, eux, restent —
     ils portaient déjà toute l'information (la barre est aria-hidden). Le reste de
     la largeur revient aux libellés, et la nature a le droit de passer à la ligne :
     à quatre colonnes, « Attente client » insécable ferait déborder la page. */
  .timings {
    font-size: var(--fs-micro);
  }
  .timings thead th:last-child,
  .timings__row > td:last-child {
    display: none;
  }
  .timings th,
  .timings td {
    padding: 3px var(--sp-1);
  }
  .timings thead th:first-child {
    width: auto;
  }
  .timings__kind {
    white-space: normal;
  }
  .timings--child .timings__label {
    padding-left: var(--sp-3);
  }
  /* Les deux boutons se partagent la largeur, à 40px de haut : les .btn--tiny (28px)
     sont trop petits pour le pouce. Le compteur passe seul sur sa ligne. Le champ garde
     --fs-body (16px), seule valeur qui empêche le zoom iOS à la prise de focus. */
  .feedback__form,
  .feedback__done {
    padding: var(--sp-2);
  }
  .feedback__foot {
    align-items: flex-start;
  }
  .feedback__actions {
    width: 100%;
  }
  .feedback__actions .btn {
    flex: 1 1 auto;
    min-height: 40px;
  }
  .composer-wrap {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
  }
  /* Mode vocal : l'indicateur et l'état sur une ligne, les commandes se partagent
     la largeur sur la suivante (cibles au pouce, jamais de défilement horizontal). */
  .vocal__controls {
    width: 100%;
  }
  .vocal__controls .btn {
    flex: 1 1 auto;
    min-height: 44px;
    padding: 8px 14px;
  }
  .vocal__intro {
    margin-bottom: var(--sp-4);
  }
  /* Masquage VISUEL seulement : #composer-hint est la cible de
     aria-describedby du champ de saisie ; display:none le retirerait de
     l'arbre d'accessibilité. */
  .composer__hint {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .admin-main {
    padding: var(--sp-4) var(--sp-3);
  }
  .cards {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }
  /* :not() indispensable : cette règle est postérieure à .card__value--small
     et de spécificité égale, elle lui reprenait sa réduction de corps. */
  .card__value:not(.card__value--small) {
    font-size: 1.375rem;
  }
  .table th,
  .table td {
    padding: 8px var(--sp-3);
  }
  .table .truncate {
    max-width: 200px;
  }
  .weights-filters input[type="search"] {
    width: 100%;
  }
  .weights-filters label {
    flex: 1 1 100%;
  }
}

/* Très petits écrans */
@media (max-width: 479px) {
  .composer__actions .btn {
    padding: 8px 14px;
  }
  /* Barre à icônes : le champ rend à ses icônes la marge droite que leur cible de 44px laisse déjà
     autour du dessin (12px), et le texte indicatif passe à 14px. Avec deux icônes (envoi et micro),
     il lui reste environ 170 à 200px de 360 à 393px de large (183px à 375px) : un texte plus large
     est coupé au bord du champ (section 6, « Texte indicatif sur une seule ligne »). */
  body.composer-icons .composer__input {
    padding-left: var(--sp-3);
    padding-right: var(--sp-1);
  }
  body.composer-icons .composer__input::placeholder {
    font-size: 0.875rem;
  }
  .weights-toolbar .btn {
    flex: 1 1 auto;
  }
}

/* Confort sur grand écran (--titre-app-fs-grand, 2rem par défaut) */
@media (min-width: 1520px) {
  .app-title {
    font-size: var(--titre-app-fs-grand);
  }
}

/* Mouvement réduit : neutralise animations et transitions, y compris celles
   ajoutées ultérieurement. L'indicateur de rédaction reste lisible à l'arrêt. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .typing__dots i {
    animation: none;
    opacity: 1;
  }
  /* Indicateur du mode vocal : immobile, la couleur et le texte d'état suffisent. */
  .vocal__orb {
    animation: none !important;
    transition: none;
    transform: none;
  }
}

/* =============================================================================
   11. IMPRESSION
   ============================================================================= */

@media print {
  /* Papier blanc, fonds souvent non imprimés : un pack dont le chat ou l'en-tête est sombre (textes clairs
     posés sur le fond) retrouve ici les tons des surfaces claires. Ce sont les défauts de la section 2 :
     rien ne change pour un pack qui ne les redéfinit pas. html:root, plus spécifique que le :root du
     theme.css chargé après cette feuille, l'emporte sur lui. */
  html:root {
    --chat-texte: var(--text);
    --chat-texte-doux: var(--muted);
    --chat-erreur: var(--err);
    --chat-alerte: var(--warn);
    --entete-fond: var(--card);
    --entete-texte: var(--text);
    --entete-texte-doux: var(--muted);
    --entete-filet: var(--border);
  }
  body,
  body.page-chat,
  body.page-admin {
    height: auto;
    min-height: 0;
    overflow: visible;
    background: #fff;
    color: #000;
    font-size: 11pt;
  }
  .chat {
    overflow: visible;
    background: #fff;
  }
  .app-header__actions,
  .composer-wrap,
  .weights-toolbar,
  .weights-filters,
  .skip-link,
  .msg__actions,
  .feedback__panel,
  .vocal__controls,
  .vocal__indicator {
    display: none !important;
  }
  /* Étiquette décorative du panneau (--chat-etiquette) : sans objet sur papier. */
  body.page-chat .disclaimer::after {
    content: none;
  }
  .app-header {
    border-bottom: 1pt solid #000;
  }
  .msg,
  .card,
  .table-wrap {
    box-shadow: none;
    border: 1pt solid #999;
    break-inside: avoid;
  }
  .timings {
    break-inside: avoid;
  }
  .msg--user,
  .msg--assistant {
    background: #fff;
    color: #000;
  }
  .table th {
    position: static;
    box-shadow: none;
    border-bottom: 1pt solid #000;
  }
  a {
    color: #000;
  }
}
