/* =============================================================================
   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 ; au-dessus du panneau d'une page embarquée sans mention */
  --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) */
  /* Pages embarquées compactes (/embed et /vocal/embed), ouvertes aux packs le 29/09/2026 pour Guy Hoquet, dont ces pages
     sont chargées dans la WebView d'une application mobile. Branchées sur body.page-embed seulement (section 6,
     « Mode embarqué ») : l'échelle du texte et les espacements y sont redéfinis. Défauts = valeurs des pages pleines :
     un pack qui ne les redéfinit pas garde des pages embarquées identiques au pixel. Les champs de saisie (question,
     commentaire) gardent --fs-body, 16px : en dessous, iOS zoome la page à la prise de focus. */
  --embarque-fs-body: var(--fs-body); /* corps du texte : bulles, sous-titres, cartes de réponse, titre du vocal */
  --embarque-fs-sm: var(--fs-sm); /* remplace --fs-sm : sources, « rédaction en cours », mention et état du vocal */
  --embarque-fs-xs: var(--fs-xs); /* remplace --fs-xs : détails des sources, micro-contrôles, aides, minuteur */
  --embarque-fs-micro: var(--fs-micro); /* remplace --fs-micro : titre « Sources », métadonnées, libellés du retour */
  --embarque-surtitre-fs: var(--bulle-surtitre-fs); /* surtitres des bulles (.msg__role) */
  --embarque-bouton-fs: var(--bouton-fs); /* libellé des boutons (.btn : commandes du vocal, Envoyer, Stop ; pas .btn--tiny) */
  /* Facteur des espacements --sp-1 à --sp-5, de la marge des surtitres, des icônes de la saisie et de l'indicateur
     vocal ; les cibles tactiles qu'il réduit ne descendent jamais sous 40px. */
  --embarque-espacement: 1;
  /* Contenu des bulles, sous-surfaces de la bulle de l'assistant, saisie et cadre du panneau (ouverts aux packs le
     12/10/2026 pour la charte de nuit de FORVIA : bulle de l'assistant sombre, barre de saisie sombre). Jusque-là, ces
     éléments prenaient des couleurs du moteur partagées avec l'administration, qui reste claire (--muted, --accent,
     --err, --ok, --card, --text, --placeholder, --focus…) : un pack ne pouvait pas les régler pour le chat seul.
     Même principe que les ouvertures précédentes : chaque défaut est l'expression que la règle portait avant
     l'ouverture (Indigo, l'EFREI et Guy Hoquet identiques au style calculé et au pixel près, à l'écran comme à
     l'impression : banc de non-régression du 12/10/2026), et chaque variable n'est branchée
     que sur les sélecteurs du chat (bulles, sources, questions suggérées, carte de rendez-vous, retour détaillé,
     micro-contrôles, barre de saisie, panneau), jamais sur ceux de l'administration. */
  /* Bulles : bordure complète (ex. 1px solid …) ; none = aucune */
  --bulle-assistant-filet: none;
  --bulle-utilisateur-filet: none;
  /* Posé à même la bulle de l'assistant : liens et appels de citation, texte atténué (« … » d'attente, détails des
     sources, pied de réponse), puces des listes, erreur, remerciement du vote, filet au-dessus des sources, anneau de
     focus de ce qui y est posé */
  --bulle-lien: var(--accent);
  --bulle-lien-survol: var(--secondaire); /* survol et focus d'un lien, d'un appel de citation */
  --bulle-texte-doux: var(--muted);
  --bulle-puce: var(--accent);
  --bulle-erreur: var(--err);
  --bulle-succes: var(--ok);
  --bulle-filet: var(--border);
  --bulle-focus: var(--focus);
  /* Badge technique de la bulle (.msg__meta : intention, langue, voie ; détails techniques) */
  --bulle-badge-fond: var(--card);
  --bulle-badge-texte: var(--muted);
  --bulle-badge-filet: var(--border);
  /* Sources citées : titre « Sources », numéros [n], source surlignée au clic sur un appel de citation (fond et repère) */
  --sources-titre: var(--muted);
  --sources-numero: var(--accent);
  --sources-surlignage: var(--teinte-primaire);
  --sources-surlignage-repere: var(--accent);
  /* Micro-contrôles (.btn--tiny : « Copier », 👍, 👎, boutons du retour, « Nouvelle conversation » de /embed,
     « Revenir à l'écrit » de /vocal/embed) ; le fond survolé admet la technique du dégradé de bordure
     (… padding-box, … border-box, avec un filet transparent) */
  --micro-controle-fond: var(--card);
  --micro-controle-texte: var(--muted);
  --micro-controle-filet: var(--border-strong);
  --micro-controle-fond-survol: var(--micro-controle-fond);
  --micro-controle-texte-survol: var(--text);
  --micro-controle-filet-survol: var(--text);
  /* Questions suggérées sous l'accueil (.suggestion, ui.suggestions du pack) : leurs propres couleurs, séparées de
     celles des micro-contrôles (même technique admise pour le fond survolé) */
  --suggestion-fond: var(--card);
  --suggestion-texte: var(--accent);
  --suggestion-filet: var(--accent);
  --suggestion-fond-survol: var(--teinte-primaire);
  --suggestion-texte-survol: var(--accent);
  --suggestion-filet-survol: var(--accent);
  /* Carte « Prendre rendez-vous » (.booking-cta) : surface (fond, dégradé de bordure admis), texte, filet, liens et
     anneau de focus du texte affiché au clic, bouton (au lieu du bouton principal, partagé avec l'administration) */
  --carte-rdv-fond: var(--card);
  --carte-rdv-texte: var(--text);
  --carte-rdv-filet: var(--accent-border);
  --carte-rdv-lien: var(--accent);
  --carte-rdv-lien-survol: var(--secondaire);
  --carte-rdv-focus: var(--focus);
  --carte-rdv-bouton-fond: var(--bouton-primaire-fond);
  --carte-rdv-bouton-texte: var(--bouton-primaire-texte);
  --carte-rdv-bouton-survol: var(--bouton-primaire-survol);
  --carte-rdv-bouton-texte-survol: var(--bouton-primaire-texte-survol); /* survol et état actif */
  --carte-rdv-bouton-actif: var(--bouton-primaire-actif);
  /* Formulaire de rendez-vous dans la carte (.booking-form, visiteurs sans identité, ui.bookingForm du pack) : libellés
     et légende, champs (fond, texte, filet, filet au focus, texte indicatif), erreurs, textes atténués (mention des
     champs obligatoires, note « rien n'a été transmis ») ; le bouton d'envoi est celui de la carte (--carte-rdv-bouton-*),
     l'anneau de focus aussi (--carte-rdv-focus). Défauts : ceux de la carte claire et du retour détaillé. */
  --carte-rdv-champ-libelle: var(--carte-rdv-texte);
  --carte-rdv-champ-fond: var(--bg);
  --carte-rdv-champ-texte: var(--text);
  --carte-rdv-champ-filet: var(--border-strong);
  --carte-rdv-champ-filet-focus: var(--secondaire);
  --carte-rdv-champ-placeholder: var(--placeholder);
  --carte-rdv-champ-erreur: var(--err);
  --carte-rdv-texte-doux: var(--muted);
  /* Retour détaillé (👎) : formulaire et vue « enregistré » (fond, filet), libellé et repère de la mention de
     confidentialité, textes atténués (aide, mention, compteur, statut, rappel, champ en lecture seule), champ du
     commentaire, alerte du compteur, erreur, confirmation, anneau de focus de ce qui est posé dans le formulaire */
  --retour-fond: var(--card);
  --retour-filet: var(--border);
  --retour-libelle: var(--secondaire);
  --retour-texte-doux: var(--muted);
  --retour-champ-fond: var(--bg);
  --retour-champ-texte: var(--text);
  --retour-champ-filet: var(--border-strong); /* aussi le repère du rappel enregistré (.feedback__echo) */
  --retour-champ-filet-focus: var(--secondaire);
  --retour-champ-fond-lecture: var(--gris-inactif);
  --retour-placeholder: var(--placeholder);
  --retour-alerte: var(--warn);
  --retour-erreur: var(--err);
  --retour-succes: var(--ok);
  --retour-focus: var(--focus);
  /* Barre de saisie : texte saisi, texte en lecture seule (réponse en cours), texte indicatif, anneau de focus de ce
     qui est posé dans le formulaire (couleur, puis épaisseur depuis le 13/10/2026), icônes (micro), filet de l'icône
     d'envoi (dégradé de bordure admis avec --composeur-envoi-fond), bouton Stop (au lieu de .btn--danger, partagé avec
     l'administration ; son rayon depuis le 13/10/2026) */
  --saisie-texte: var(--text);
  --saisie-texte-lecture: var(--muted);
  --saisie-placeholder: var(--placeholder);
  --composeur-focus: var(--focus);
  --composeur-focus-epaisseur: 3px; /* celle de :focus-visible ; --composeur-padding doit couvrir épaisseur + décalage (2px) */
  --composeur-icone: var(--text);
  --composeur-icone-survol: var(--accent); /* survol et focus */
  --composeur-envoi-filet: 0;
  --composeur-stop-fond: var(--bouton-danger-fond);
  --composeur-stop-texte: var(--err);
  --composeur-stop-filet: var(--err);
  --composeur-stop-fond-survol: var(--err);
  --composeur-stop-texte-survol: var(--blanc);
  --composeur-stop-rayon: var(--bouton-radius); /* celui de .btn */
  /* Cadre du panneau de conversation : bordure des côtés extérieurs des deux moitiés (.chat sans son bas,
     .composer-wrap sans son haut, qui garde --chat-surface-filet) ; un dégradé se pose par --chat-panneau-fond
     (… padding-box, … border-box) avec un filet transparent et --chat-panneau-attache: fixed */
  --chat-panneau-filet: none;
  --chat-redaction-points: var(--accent); /* points de « rédaction en cours » du chat (#typing), décoratifs */
  /* Chronométrage des détails techniques (« Temps de réponse », bloc .timings posé dans la bulle de l'assistant) et
     bascule enfoncée en icône de l'en-tête, ouverts aux packs le 13/10/2026 pour la charte de nuit de FORVIA : jusque-là,
     le bloc prenait --text, --muted, --border, --accent, les couleurs de marque… que FORVIA garde sombres pour
     l'administration claire, et qui disparaissaient sur sa bulle nuit. Même principe que les ouvertures précédentes :
     chaque défaut est l'expression que la règle portait avant (Indigo, l'EFREI et Guy Hoquet identiques au style calculé
     et au pixel près, à l'écran comme à l'impression : banc de non-régression du 13/10/2026). Branchées sur le bloc
     .timings du chat seulement : les classes que l'administration emploie aussi (__row, __label, __kind, __share, __bar,
     --child, __rest), sans la classe de bloc, les reçoivent par un relais --timings-* posé sur .timings et lu avec
     l'expression d'avant en repli (section 6, « Chronométrage de la réponse ») : l'administration garde ses couleurs. */
  --chrono-texte: var(--text); /* libellés des étapes, durée totale de l'en-tête */
  --chrono-texte-doux: var(--muted); /* en-tête replié, résumé, têtes de colonnes, sous-étapes, détails, parts, repères, reste, légende */
  --chrono-filet: var(--border); /* filet au-dessus du bloc, filets entre les lignes */
  --chrono-filet-fort: var(--border-strong); /* filet pointillé du reste non imputé */
  --chrono-barre-fond: var(--border); /* piste des barres de la cascade */
  --chrono-chevron: var(--accent); /* chevron ▸ ▾ de l'en-tête (décoratif) */
  --chrono-focus: var(--focus); /* anneau de focus de l'en-tête (summary) */
  --chrono-pastille-fond: var(--card); /* pastille de la branche (« réponse générée ») : fond */
  --chrono-pastille-texte: var(--secondaire); /* … son texte */
  --chrono-pastille-filet: var(--secondaire-filet); /* … son filet */
  --chrono-teinte-openai: var(--primaire); /* teinte par nature : libellé de nature, barre, pastille de la légende */
  --chrono-teinte-db: var(--secondaire);
  --chrono-teinte-cpu: var(--quaternaire);
  --chrono-teinte-client: var(--muted);
  --chrono-repere: var(--encre); /* trait des repères instantanés (premier jeton…) */
  /* Bascule enfoncée en icône, sous 768px (.app-header__actions .btn--icone[aria-pressed="true"], section 10) : fond,
     filet et icône ; défauts = couleurs de la bascule enfoncée survolée, celles que la règle portait. */
  --bouton-bascule-icone-fond: var(--bouton-bascule-survol);
  --bouton-bascule-icone-filet: var(--bouton-bascule-survol);
  --bouton-bascule-icone-texte: var(--bouton-bascule-texte-survol);
  /* Finitions du chat de FORVIA (13/10/2026, liste validée par Arthur le même jour) : largeur de la bulle de
     l'assistant, anneau des questions suggérées, bouton « Nouvelle conversation » de l'en-tête et bascule relâchée.
     Même principe que les ouvertures précédentes : chaque défaut est l'expression que la règle portait avant (Indigo,
     l'EFREI, Guy Hoquet et le pack sans theme.css identiques au style calculé et au pixel près, à l'écran comme à
     l'impression : banc de non-régression du 13/10/2026), branchées sur des sélecteurs du chat seulement. */
  --bulle-assistant-largeur: 100%; /* width et max-width de .msg--assistant, en part de la colonne (fixe : la bulle ne bouge pas pendant l'écriture) */
  --suggestion-focus-epaisseur: 3px; /* épaisseur de l'anneau d'une question suggérée (.suggestion:focus-visible), celle de :focus-visible */
  /* Bouton « Nouvelle conversation » de l'en-tête du chat (.btn--secondary de .page-chat .app-header__actions : / et
     /fast ; celui de /embed est un micro-contrôle) : texte et filet (--accent de .btn--secondary), fond et texte au
     survol (ceux de .btn--secondary) ; l'état actif reste celui de .btn--secondary. L'administration (« Rafraîchir »
     et ses boutons secondaires) garde les règles de .btn--secondary. */
  --bouton-nouvelle-conversation-texte: var(--accent);
  --bouton-nouvelle-conversation-filet: var(--accent);
  --bouton-nouvelle-conversation-survol: var(--bouton-secondaire-survol);
  --bouton-nouvelle-conversation-texte-survol: var(--bouton-secondaire-texte-survol);
  /* Bascule relâchée (.btn--ghost[aria-pressed="false"] : « Détails techniques », « Couper le micro »), à libellé et en
     icône sous 768px : fond, texte et filet, ceux du bouton neutre (.btn--ghost) ; survolée, elle prend ceux de
     .btn--ghost:hover (--bouton-neutre-survol). L'administration ne pose aucune bascule. */
  --bouton-bascule-relachee-fond: var(--card);
  --bouton-bascule-relachee-texte: var(--bouton-neutre-texte);
  --bouton-bascule-relachee-filet: var(--border-strong);
}

/* =============================================================================
   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 focalisé dans la bulle de l'assistant (liens, appels de citation, micro-contrôles, questions suggérées,
   remerciement du vote) : l'anneau se pose sur la bulle. --bulle-focus vaut --focus par défaut ; un pack dont la bulle
   est sombre y met un ton clair. Les surfaces posées dans la bulle gardent le leur (section 6) : carte de rendez-vous
   (--carte-rdv-focus) et formulaire du retour (--retour-focus) ; l'en-tête repliable du chronométrage garde --focus. */
.msg--assistant :focus-visible {
  outline-color: var(--bulle-focus);
}

/* 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;
}

/* Marque et actions sur UNE rangée tant que le plus long mot de la marque y tient (décisions d'Arthur du 28/09/2026
   sous 768px et du 29/09/2026 au-dessus) : 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, du sous-titre ou du
   badge « Mode rapide »), si bien que son sous-titre, puis son titre, se replient entre les mots au lieu de renvoyer
   les boutons à la ligne ; le titre ne se coupe jamais au milieu d'un mot (overflow-wrap: normal, au lieu du anywhere
   des titres). Faute de place même ainsi, les actions passent seules à la ligne, calées à droite (margin-left: auto).
   Mesuré en 1440 × 900 chez Indigo : 117px d'en-tête avant ce réglage (deux rangées : marque de 494px avec son
   sous-titre, trois boutons de 679px, pour 1 040px utiles) depuis l'ouverture du mode rapide, 81px après (une rangée,
   sous-titre sur deux lignes ; sur /fast, le badge passe en plus sous le titre). */
.app-header__brand {
  flex: 1 1 0;
  min-width: min-content;
}

.app-header__brand .app-title {
  overflow-wrap: normal;
}

/* 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;
  margin-left: auto;
}

/* 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);
}

/* Bouton « Nouvelle conversation » de l'en-tête du chat (/ et /fast) : texte et filet --bouton-nouvelle-conversation-*
   (13/10/2026), --accent par défaut, comme ci-dessus. :where() garde la spécificité de .btn--secondary : ses états
   survolé (règle suivante) et actif l'emportent comme avant ; l'administration (body.page-admin) n'est pas visée. */
:where(.page-chat .app-header__actions) .btn--secondary {
  color: var(--bouton-nouvelle-conversation-texte);
  border-color: var(--bouton-nouvelle-conversation-filet);
}

/* 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);
}

/* Le même survolé (13/10/2026) : --bouton-nouvelle-conversation-survol et -texte-survol, ceux de la règle précédente
   par défaut ; même spécificité qu'elle, placé avant .btn--secondary:active, qui l'emporte toujours. */
:where(.page-chat .app-header__actions) .btn--secondary:not(:disabled):hover {
  background: var(--bouton-nouvelle-conversation-survol);
  color: var(--bouton-nouvelle-conversation-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).
   Bascule relâchée (13/10/2026) : --bouton-bascule-relachee-*, fond, texte et filet du bouton neutre par défaut (règle
   .btn--ghost) ; un pack dont la bascule enfoncée est blanche y pose un fond sombre. Moins spécifique que
   .btn--ghost:hover, qui garde son survol. */
.btn--ghost[aria-pressed="false"] {
  background: var(--bouton-bascule-relachee-fond);
  color: var(--bouton-bascule-relachee-texte);
  border-color: var(--bouton-bascule-relachee-filet);
}

.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. Couleurs : --micro-controle-* (12/10/2026),
   défauts égaux à l'expression d'avant (fond --card, texte --muted, filet --border-strong ; survolé : texte et filet
   --text, fond inchangé). Ils ne se posent que dans le chat (bulle, retour, actions de l'embarqué et du vocal
   embarqué), jamais dans l'administration. */
.btn--tiny {
  font-size: var(--fs-xs);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  padding: 4px 10px;
  min-height: 28px;
  background: var(--micro-controle-fond);
  color: var(--micro-controle-texte);
  border: 1px solid var(--micro-controle-filet);
}

.btn--tiny:not(:disabled):hover {
  background: var(--micro-controle-fond-survol);
  color: var(--micro-controle-texte-survol);
  border-color: var(--micro-controle-filet-survol);
}

/* 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);
}

/* Pages embarquées compactes (variables --embarque-*, section 2 ; décision d'Arthur du 29/09/2026 pour Guy Hoquet, dont
   /embed et /vocal/embed sont chargées directement dans la WebView d'une application mobile). L'échelle du texte et les
   espacements sont redéfinis sur le corps des deux pages embarquées : toute règle qui emploie var(--fs-sm),
   var(--fs-xs), var(--fs-micro), var(--bouton-fs), var(--bulle-surtitre-fs) ou var(--sp-1) à var(--sp-5) y prend sa
   valeur compacte, et le texte courant hérite du corps. --fs-body n'est PAS redéfinie : le champ de saisie et celui du
   commentaire l'emploient et gardent 16px (en dessous, iOS zoome la page à la prise de focus, WKWebView comprise). Les
   --sp-* reprennent les valeurs de :root, multipliées par --embarque-espacement. Défauts neutres : pages embarquées
   d'Indigo et de l'EFREI identiques au pixel et au style calculé près (les jetons --sp-* s'y écrivent calc(… * 1)),
   pages pleines jamais touchées (elles n'ont pas la classe page-embed). */
body.page-embed {
  font-size: var(--embarque-fs-body);
  --fs-sm: var(--embarque-fs-sm);
  --fs-xs: var(--embarque-fs-xs);
  --fs-micro: var(--embarque-fs-micro);
  --bulle-surtitre-fs: var(--embarque-surtitre-fs);
  --bouton-fs: var(--embarque-bouton-fs);
  --sp-1: calc(4px * var(--embarque-espacement));
  --sp-2: calc(8px * var(--embarque-espacement));
  --sp-3: calc(12px * var(--embarque-espacement));
  --sp-4: calc(16px * var(--embarque-espacement));
  --sp-5: calc(24px * var(--embarque-espacement));
}

/* Ce que les jetons n'atteignent pas : l'intertitre d'une réponse (1rem en dur, le corps du texte des pages pleines),
   la marge des surtitres (--bulle-surtitre-marge, résolue par le pack sur :root), la hauteur minimale du champ et du
   bouton Stop (44 et 48px en dur), avec un plancher de 40px pour ces cibles tactiles. */
.page-embed .msg__body .md-heading {
  font-size: var(--embarque-fs-body);
}

.page-embed .msg__role {
  margin-bottom: calc(var(--bulle-surtitre-marge) * var(--embarque-espacement));
}

.page-embed .composer__input {
  min-height: max(40px, calc(44px * var(--embarque-espacement)));
}

.page-embed .composer__actions .btn {
  min-height: max(40px, calc(48px * var(--embarque-espacement)));
}

/* Texte indicatif du champ de commentaire (👎) : jamais plus grand que le corps du texte (défaut : 16px, celui du
   champ) ; le texte saisi garde 16px. Celui de la barre à icônes : section 10, sous 480px. */
.page-embed .feedback__field::placeholder {
  font-size: min(1em, var(--embarque-fs-body));
}

/* Mention du haut retirée par le pack (ui.embedDisclaimer: false, app.js et vocal.js) : le panneau garde au-dessus de
   lui l'écart qu'il avait sous la mention (--chat-mention-marge, 0 par défaut), au lieu de toucher le haut de la page. */
.page-embed .disclaimer[hidden] + .chat {
  margin-top: var(--chat-mention-marge);
}

/* 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 »). Un cadre (--chat-panneau-filet,
   12/10/2026) borde les côtés extérieurs des deux moitiés ; en dégradé, il se pose par
   le fond (couche « padding-box » unie, couche « border-box » en dégradé, filet
   transparent) et se raccorde de même par l'attache fixed (sur iOS Safari, chaque
   moitié porte son propre dégradé : changement de teinte là où elles se rejoignent).
   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);
  /* Cadre (--chat-panneau-filet, none par défaut) sur les côtés extérieurs : le bas revient au pied de saisie. */
  border: var(--chat-panneau-filet);
  border-bottom: 0;
  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), sauf dans les pages embarquées, où un pack peut
   le réduire (--embarque-fs-body, section 6, « Mode embarqué »). */
.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: var(--bulle-utilisateur-filet);
  border-radius: var(--bulle-rayon) var(--bulle-rayon) var(--bulle-coin) var(--bulle-rayon);
}

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

/* Largeur : --bulle-assistant-largeur (13/10/2026), 100% par défaut ; une largeur fixe et non ajustée au contenu, pour
   que la bulle ne change pas de taille pendant l'écriture de la réponse (bulle « … » comprise). */
.msg--assistant {
  align-self: flex-start;
  width: var(--bulle-assistant-largeur);
  max-width: var(--bulle-assistant-largeur);
  background: var(--bulle-assistant-fond);
  color: var(--bulle-assistant-texte);
  border: var(--bulle-assistant-filet);
  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);
}

/* Liens posés dans la bulle de l'assistant (texte de la réponse, titres des sources, appels de citation) :
   --bulle-lien et --bulle-lien-survol, --accent et --secondaire par défaut, les couleurs de la règle a. Plus
   spécifiques que a et a:hover : un pack dont la bulle est sombre les éclaircit sans toucher aux liens de
   l'administration. La carte de rendez-vous, posée dans la bulle, a les siens (--carte-rdv-lien). */
.msg--assistant a {
  color: var(--bulle-lien);
}

.msg--assistant a:hover,
.msg--assistant a:focus {
  color: var(--bulle-lien-survol);
}

.msg__meta {
  font-size: var(--fs-micro);
  color: var(--bulle-badge-texte);
  background: var(--bulle-badge-fond);
  border: 1px solid var(--bulle-badge-filet);
  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(--bulle-puce);
}

.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(--bulle-texte-doux);
}

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

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

/* L'erreur ne repose jamais sur la seule couleur : symbole + libellé. */
.msg__error {
  margin-top: var(--sp-2);
  color: var(--bulle-erreur);
  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(--bulle-filet);
}

.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(--sources-titre);
}

.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 (--sources-surlignage-repere, --accent par défaut) garde le repère visible même désaturé. */
.src--highlight {
  background: var(--sources-surlignage);
  box-shadow: inset 3px 0 0 var(--sources-surlignage-repere);
}

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

.src__meta {
  color: var(--bulle-texte-doux);
  font-size: var(--fs-xs);
}

/* --- Questions suggérées sous l'accueil (ui.suggestions du pack) -----------

   Boutons posés sur leur propre surface (--card, texte --accent : 6,84:1 par défaut) :
   lisibles sur une bulle claire comme sur une bulle sombre de pack. Retour à la ligne
   permis (120 caractères au plus), aligné en début de ligne. */

.suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}

/* Couleurs propres (--suggestion-*, 12/10/2026), séparées de celles des micro-contrôles dont elles prennent la
   forme : défauts égaux à l'expression d'avant (fond --card du micro-contrôle, texte et filet --accent). */
.suggestion {
  white-space: normal;
  text-align: start;
  font-size: var(--fs-sm);
  background: var(--suggestion-fond);
  color: var(--suggestion-texte);
  border-color: var(--suggestion-filet);
}

.suggestion:not(:disabled):hover {
  background: var(--suggestion-fond-survol);
  color: var(--suggestion-texte-survol);
  border-color: var(--suggestion-filet-survol);
}

/* Anneau de focus d'une question suggérée : couleur de la bulle (--bulle-focus, .msg--assistant :focus-visible),
   épaisseur --suggestion-focus-epaisseur (13/10/2026), 3px par défaut, celle de :focus-visible ; décalage du moteur. */
.suggestion:focus-visible {
  outline-width: var(--suggestion-focus-epaisseur);
}

/* --- Carte « Prendre rendez-vous » (meta.booking.suggest) ------------------

   Sous la réponse et ses sources, sur sa propre surface (--card) : ni détail technique
   (elle ne suit pas « Détails techniques »), ni pied de message (elle n'en décale pas
   les boutons). Après le clic, le texte du pack la remplace (.booking-cta__text). */

.booking-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  background: var(--carte-rdv-fond);
  color: var(--carte-rdv-texte);
  border: 1px solid var(--carte-rdv-filet);
  border-radius: var(--radius-xs);
}

/* Liens et anneau de focus de la carte (texte du pack affiché au clic, bouton) : --carte-rdv-*, aux valeurs de la
   règle a et de :focus-visible par défaut. Après les règles de la bulle (.msg--assistant a, .msg--assistant
   :focus-visible), de même spécificité : la carte, surface propre, garde les siens. */
.booking-cta a {
  color: var(--carte-rdv-lien);
}

.booking-cta a:hover,
.booking-cta a:focus {
  color: var(--carte-rdv-lien-survol);
}

.booking-cta :focus-visible {
  outline-color: var(--carte-rdv-focus);
}

.booking-cta__lead {
  margin: 0;
  font-size: var(--fs-sm);
}

.booking-cta__btn {
  white-space: normal;
}

/* Bouton de la carte (.btn--primary) : --carte-rdv-bouton-*, aux valeurs du bouton principal par défaut, que
   l'administration partage ; plus spécifiques que les règles de .btn--primary, états compris. */
.booking-cta .booking-cta__btn {
  background: var(--carte-rdv-bouton-fond);
  color: var(--carte-rdv-bouton-texte);
  border-color: var(--carte-rdv-bouton-fond);
}

.booking-cta .booking-cta__btn:not(:disabled):hover {
  background: var(--carte-rdv-bouton-survol);
  border-color: var(--carte-rdv-bouton-survol);
  color: var(--carte-rdv-bouton-texte-survol);
}

.booking-cta .booking-cta__btn:not(:disabled):active {
  background: var(--carte-rdv-bouton-actif);
  border-color: var(--carte-rdv-bouton-actif);
  color: var(--carte-rdv-bouton-texte-survol);
}

.booking-cta__text {
  flex: 1 0 100%;
  font-size: var(--fs-sm);
}

.booking-cta__text > :first-child {
  margin-top: 0;
}

.booking-cta__text > :last-child {
  margin-bottom: 0;
}

/* --- Formulaire de rendez-vous (meta.booking.form, ui.bookingForm du pack) -------

   Dans la carte, sous l'introduction du pack : légende, mention des champs obligatoires, champs
   en une ou deux colonnes selon la largeur (libellé au-dessus, erreur dessous), bouton de la carte.
   Champs à --fs-body (16px : pas de zoom d'iOS au focus) et 44px de haut au moins. Après l'envoi,
   le texte du pack et la note .booking-form__note le remplacent (.booking-cta__text). */

.booking-form {
  flex: 1 0 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
  min-width: 0;
  margin: 0;
}

.booking-form__intro {
  margin-bottom: 0;
}

.booking-form__fieldset {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: var(--sp-2) var(--sp-3);
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.booking-form__legend {
  padding: 0;
  margin-bottom: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--carte-rdv-champ-libelle);
}

.booking-form__hint {
  grid-column: 1 / -1;
  margin: 0;
}

.booking-form__hint,
.booking-form__note {
  font-size: var(--fs-xs);
  color: var(--carte-rdv-texte-doux);
}

.booking-form__field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.booking-form__label {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--carte-rdv-champ-libelle);
}

.booking-form__input {
  font: inherit;
  font-size: var(--fs-body);
  line-height: 1.4;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--carte-rdv-champ-filet);
  border-radius: var(--radius-xs);
  background: var(--carte-rdv-champ-fond);
  color: var(--carte-rdv-champ-texte);
  transition: border-color var(--t-fast);
}

.booking-form__input::placeholder {
  color: var(--carte-rdv-champ-placeholder);
}

.booking-form__input:focus {
  border-color: var(--carte-rdv-champ-filet-focus);
}

.booking-form__input[aria-invalid="true"] {
  border-color: var(--carte-rdv-champ-erreur);
}

.booking-form__error {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--carte-rdv-champ-erreur);
}

.booking-form__note {
  margin: var(--sp-2) 0 0;
}

.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(--bulle-texte-doux);
}

/* 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(--bulle-succes);
  font-weight: 600;
}

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

/* --- 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(--retour-fond);
  border: 1px solid var(--retour-filet);
  border-radius: var(--radius-xs);
}

/* Anneau de ce qui est posé dans le formulaire (champ, boutons) : --retour-focus (--focus par défaut), après celui de
   la bulle (.msg--assistant :focus-visible, section 4), de même spécificité : le formulaire, surface propre, a le sien. */
.feedback__form :focus-visible {
  outline-color: var(--retour-focus);
}

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

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

/* Filet --retour-libelle (--secondaire par défaut) : repère de lecture, pas un état — la phrase se suffit à elle-même. */
.feedback__privacy {
  border-left: 3px solid var(--retour-libelle);
  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(--retour-champ-filet);
  border-radius: var(--radius-xs);
  background: var(--retour-champ-fond);
  color: var(--retour-champ-texte);
  transition: border-color var(--t-fast);
}

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

.feedback__field:focus {
  border-color: var(--retour-champ-filet-focus);
}

.feedback__field:read-only {
  background: var(--retour-champ-fond-lecture);
  color: var(--retour-texte-doux);
}

.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(--retour-texte-doux);
}

/* Jamais la couleur seule : à la limite, le texte du compteur change aussi. */
.feedback__count--limite {
  color: var(--retour-alerte);
  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(--retour-texte-doux);
}

.feedback__status--err {
  color: var(--retour-erreur);
  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(--retour-fond);
  border: 1px solid var(--retour-filet);
  border-radius: var(--radius-xs);
}

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

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

/* 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(--retour-champ-filet);
  font-size: var(--fs-xs);
  color: var(--retour-texte-doux);
  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 ».

   Couleurs (13/10/2026) : celles du chat sont des variables --chrono-* (section 2), qu'un pack règle pour une bulle
   sombre. Les classes propres au chat (__head, __legend, __swatch, __mark, en-tête du tableau) les emploient
   directement ; celles que l'administration emploie aussi les lisent par un relais --timings-* que seul le bloc
   .timings pose, avec en repli l'expression d'avant : var(--timings-texte, var(--text)). Hors du bloc (administration),
   le relais n'existe pas, le repli s'applique, rien ne change ; dans le bloc, le relais vaut la variable --chrono-*,
   dont le défaut est cette même expression (aucune des variables qu'elles nomment n'est redéfinie ailleurs que sur
   :root, sauf à l'impression, où le bloc html:root ramène aussi les --chrono-* à leurs défauts).

   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(--chrono-filet);
  font-size: var(--fs-xs);
  /* Relais vers les règles partagées avec l'administration (voir l'en-tête de cette section) */
  --timings-texte: var(--chrono-texte);
  --timings-texte-doux: var(--chrono-texte-doux);
  --timings-filet: var(--chrono-filet);
  --timings-filet-fort: var(--chrono-filet-fort);
  --timings-barre-fond: var(--chrono-barre-fond);
  --timings-teinte-openai: var(--chrono-teinte-openai);
  --timings-teinte-db: var(--chrono-teinte-db);
  --timings-teinte-cpu: var(--chrono-teinte-cpu);
  --timings-teinte-client: var(--chrono-teinte-client);
}

/* 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(--chrono-texte-doux);
  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(--chrono-chevron);
}

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

.timings__head:focus-visible {
  outline: 2px solid var(--chrono-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(--chrono-texte);
}

/* Pastille de branche : --secondaire = hiérarchie et état, jamais --primaire (réservé à l'action) ; --chrono-pastille-*. */
.timings__head .timings__kind {
  padding: 1px 10px;
  border-radius: var(--radius-pill);
  background: var(--chrono-pastille-fond);
  border: 1px solid var(--chrono-pastille-filet);
  color: var(--chrono-pastille-texte);
  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(--chrono-texte-doux);
  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(--timings-filet, 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(--timings-texte, 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(--timings-texte-doux, var(--muted));
}

/* Détail d'une étape entre parenthèses (« (12 extraits) ») : .text-muted, utilitaire de l'administration (--muted),
   posé ici sur le texte doux du chronométrage. */
.timings .text-muted {
  color: var(--chrono-texte-doux);
}

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

.timings__share {
  white-space: nowrap;
  color: var(--timings-texte-doux, 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). Dans le chat, --chrono-teinte-* (relais --timings-teinte-*). */
.timings__row[data-kind="openai"],
.timings__legend [data-kind="openai"] {
  --timings-teinte: var(--timings-teinte-openai, var(--primaire));
}

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

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

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

.timings__kind {
  color: var(--timings-teinte, var(--timings-texte-doux, 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(--timings-barre-fond, 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), chat seulement : un trait --chrono-repere (--encre par défaut), lisible sur la piste. */
.timings__mark .timings__label {
  font-weight: 600;
}

.timings__mark .timings__kind {
  color: var(--chrono-texte-doux);
}

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

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

.timings__rest .timings__label,
.timings__rest .timings__kind {
  color: var(--timings-texte-doux, 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(--chrono-texte-doux);
}

.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;
}

/* Points du chat posés sur le panneau (#typing) : --chat-redaction-points (--accent par défaut). Ceux de l'indicateur
   vocal et de la recherche en cours d'une carte vocale gardent --accent. */
#typing .typing__dots i {
  background: var(--chat-redaction-points);
}

.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);
  /* Cadre du panneau (--chat-panneau-filet) sur les côtés et le bas ; le haut reste le filet entre les deux moitiés. */
  border: var(--chat-panneau-filet);
  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(--saisie-texte);
  transition: border-color var(--t-fast);
}

.composer__input::placeholder {
  color: var(--saisie-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(--saisie-texte-lecture);
}

/* Anneau de ce qui est posé dans le formulaire (champ, icônes, bouton Stop) : --composeur-focus (--focus par défaut) ;
   un pack dont la barre de saisie est sombre y met un ton clair. Épaisseur : --composeur-focus-epaisseur (13/10/2026),
   3px par défaut, celle de :focus-visible (rendu inchangé sans réglage) ; FORVIA y pose les 2px de sa charte. */
.composer :focus-visible {
  outline-color: var(--composeur-focus);
  outline-width: var(--composeur-focus-epaisseur);
}

/* Bouton Stop (#stop, .btn--danger) dans la barre : --composeur-stop-* (12/10/2026), aux valeurs de .btn--danger par
   défaut, que l'administration partage (« Valeurs par défaut » de la pondération) ; plus spécifiques que ses règles.
   Rayon : --composeur-stop-rayon (13/10/2026), --bouton-radius par défaut, celui de .btn (rendu inchangé sans réglage). */
.composer .btn--danger {
  background: var(--composeur-stop-fond);
  color: var(--composeur-stop-texte);
  border-color: var(--composeur-stop-filet);
  border-radius: var(--composeur-stop-rayon);
}

.composer .btn--danger:not(:disabled):hover {
  background: var(--composeur-stop-fond-survol);
  color: var(--composeur-stop-texte-survol);
}

.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, couleurs --composeur-stop-*). Icônes à la
   couleur du texte du champ (--composeur-icone, --text par défaut ; survol et focus :
   --composeur-icone-survol, --accent par défaut), 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(--composeur-icone);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: color var(--t-fast);
}

.composer__icon-btn:hover,
.composer__icon-btn:focus {
  color: var(--composeur-icone-survol);
}

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

/* Icône d'envoi (#send) : fond, couleur, filet 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), ou la cercle d'un dégradé (FORVIA : filet transparent,
   fond « … padding-box, … border-box »). Défauts égaux aux règles ci-dessus (filet 0, comme .composer__icon-btn), 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: var(--composeur-envoi-filet);
  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;
}

/* Pages embarquées compactes (--embarque-espacement, section 6, « Mode embarqué ») : cibles et icônes resserrées en
   proportion, jamais sous 40px de cible (Guy Hoquet : cibles de 40px, icônes de 15px). Défaut : 44 et 20px. */
body.composer-icons.page-embed .composer__icon-btn {
  width: max(40px, calc(44px * var(--embarque-espacement)));
  height: max(40px, calc(44px * var(--embarque-espacement)));
}

body.composer-icons.page-embed .composer__icon {
  width: calc(20px * var(--embarque-espacement));
  height: calc(20px * var(--embarque-espacement));
}

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);
}

/* --- Invitation à parler : grand micro (29/09/2026) --------------------------
   La voix ne parle plus la première : pendant l'écoute et jusqu'à la première phrase
   de l'utilisateur, un grand micro aux couleurs du bouton principal (couple déjà
   contrasté dans chaque pack), entouré d'ondes, lui dit que c'est à lui.
   Décoratif (#vocal-status dit l'état) ; en mouvement réduit, les ondes s'arrêtent
   et le micro reste affiché. */
.vocal__invite {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  max-width: var(--max-width);
  margin: var(--sp-5) auto;
  animation: vocal-invite-entree 0.3s ease-out;
}

.vocal__invite-micro {
  position: relative;
  display: grid;
  place-items: center;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: var(--bouton-primaire-fond);
  color: var(--bouton-primaire-texte);
}

.vocal__invite-micro svg {
  position: relative;
  width: 36px;
  height: 36px;
}

/* Deux ondes décalées qui partent du micro, couleur du texte posé sur le fond
   (--chat-texte) : visibles sur le panneau sombre de Guy Hoquet comme sur un fond clair. */
.vocal__invite-micro::before,
.vocal__invite-micro::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--chat-texte);
  animation: vocal-ondes 1.8s ease-out infinite;
}

.vocal__invite-micro::after {
  animation-delay: 0.9s;
}

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

.page-vocal.page-embed .vocal__invite {
  gap: var(--sp-2);
  margin: var(--sp-4) auto;
}

.page-vocal.page-embed .vocal__invite-micro {
  width: 60px;
  height: 60px;
}

.page-vocal.page-embed .vocal__invite-micro svg {
  width: 28px;
  height: 28px;
}

.page-vocal.page-embed .vocal__invite-texte {
  font-size: var(--fs-body);
}

@keyframes vocal-ondes {
  0% {
    transform: scale(1);
    opacity: 0.6;
  }
  100% {
    transform: scale(1.9);
    opacity: 0;
  }
}

@keyframes vocal-invite-entree {
  from {
    transform: translateY(6px);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

/* --- 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 déclarée ici. Depuis le 29/09/2026, la page
   suit en plus les variables des pages embarquées compactes (--embarque-*, section 6,
   « Mode embarqué »), neutres par défaut. */
.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(--embarque-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);
}

/* Pages embarquées compactes : titre du récapitulatif (« Conversation terminée ») resserré en proportion, pour ne pas
   rester le plus grand texte de la page (défaut : --fs-display-3 inchangé). */
.page-vocal.page-embed .vocal__summary-title {
  font-size: calc(var(--fs-display-3) * var(--embarque-espacement));
}

/* Pages embarquées compactes (--embarque-espacement, section 6, « Mode embarqué ») : indicateur resserré en proportion
   (le cadre bas, plus bas, le réduit encore) ; commandes : section 10, sous 768px. */
.page-vocal.page-embed .vocal__indicator {
  width: calc(48px * var(--embarque-espacement));
  height: calc(48px * var(--embarque-espacement));
}

.page-vocal.page-embed .vocal__orb {
  width: calc(28px * var(--embarque-espacement));
  height: calc(28px * var(--embarque-espacement));
}

/* 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 --fs-xs (13px par défaut) mais jamais sous
   12px (pages embarquées compactes), 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: max(var(--fs-xs), 0.75rem);
    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) et restent sur une rangée entre eux ; la marque se replie à côté d'eux (règles de la section 5), et
     sinon ils passent seuls à la ligne, calés à droite. 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__actions {
    flex: 0 0 auto;
    flex-wrap: nowrap;
  }
  .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 --bouton-bascule-icone-* (13/10/2026), par défaut celles de la bascule enfoncée survolée
     (--bouton-bascule-survol, --bouton-bascule-texte-survol) : blanc sur --secondaire, 11,20:1 chez Indigo, 11,82:1 chez
     l'EFREI. Un pack dont la bascule survolée se fond dans un en-tête sombre (FORVIA : bleu profond sur la nuit,
     1,05:1) y pose les couleurs de sa bascule enfoncée. Au-dessus de 768px, le libellé porte l'état : rendu inchangé. */
  .app-header__actions .btn--icone[aria-pressed="true"] {
    background: var(--bouton-bascule-icone-fond);
    border-color: var(--bouton-bascule-icone-filet);
    color: var(--bouton-bascule-icone-texte);
  }
  .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 embarqué compact (--embarque-espacement, section 6, « Mode embarqué ») : commandes resserrées en proportion,
     jamais sous 40px de haut. Défaut : 44px, comme ci-dessus. */
  .page-vocal.page-embed .vocal__controls .btn {
    min-height: max(40px, calc(44px * var(--embarque-espacement)));
  }
  .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;
  }
  /* Pages embarquées compactes : texte indicatif jamais plus grand que le corps du texte (--embarque-fs-body ; défaut
     16px, donc 14px comme ci-dessus). Guy Hoquet, 13px : « Comment puis-je vous aider ? » entier dès 375px. Le texte
     saisi garde 16px. */
  body.composer-icons.page-embed .composer__input::placeholder {
    font-size: min(0.875rem, var(--embarque-fs-body));
  }
  .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;
  }
  /* Invitation à parler : ondes masquées, le grand micro et son texte suffisent. */
  .vocal__invite-micro::before,
  .vocal__invite-micro::after {
    display: none;
  }
  /* 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);
    /* Ce que l'impression garde des bulles, qu'elle pose sur du blanc (règle .msg--assistant plus bas) : liens, texte
       atténué, puces, erreur, filet des sources, badge technique, titre et numéros des sources, carte de rendez-vous
       (variables du 12/10/2026), ramenés eux aussi à leurs défauts, survol des liens compris (un lien focalisé au moment
       d'imprimer garde sa couleur de survol) ; depuis le 13/10/2026, aussi le texte doux de la carte (note « pas encore
       transmises » affichée après l'envoi du formulaire de rendez-vous ; le formulaire, lui, ne s'imprime pas). Les
       micro-contrôles, les questions suggérées, le retour et la saisie ne s'impriment pas. */
    --bulle-lien: var(--accent);
    --bulle-lien-survol: var(--secondaire);
    --bulle-texte-doux: var(--muted);
    --bulle-puce: var(--accent);
    --bulle-erreur: var(--err);
    --bulle-filet: var(--border);
    --bulle-badge-fond: var(--card);
    --bulle-badge-texte: var(--muted);
    --bulle-badge-filet: var(--border);
    --sources-titre: var(--muted);
    --sources-numero: var(--accent);
    --carte-rdv-fond: var(--card);
    --carte-rdv-texte: var(--text);
    --carte-rdv-filet: var(--accent-border);
    --carte-rdv-lien: var(--accent);
    --carte-rdv-lien-survol: var(--secondaire);
    --carte-rdv-texte-doux: var(--muted);
    /* Chronométrage des détails techniques (13/10/2026), imprimé quand il est ouvert : toutes ses couleurs reviennent
       aux défauts de la section 2, pensés pour la bulle claire que pose l'impression. */
    --chrono-texte: var(--text);
    --chrono-texte-doux: var(--muted);
    --chrono-filet: var(--border);
    --chrono-filet-fort: var(--border-strong);
    --chrono-barre-fond: var(--border);
    --chrono-chevron: var(--accent);
    --chrono-focus: var(--focus);
    --chrono-pastille-fond: var(--card);
    --chrono-pastille-texte: var(--secondaire);
    --chrono-pastille-filet: var(--secondaire-filet);
    --chrono-teinte-openai: var(--primaire);
    --chrono-teinte-db: var(--secondaire);
    --chrono-teinte-cpu: var(--quaternaire);
    --chrono-teinte-client: var(--muted);
    --chrono-repere: var(--encre);
  }
  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,
  .suggestions,
  .booking-cta__btn,
  .booking-form,
  .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;
  }
  /* Liens en noir, ceux de la bulle et de la carte de rendez-vous compris (.msg--assistant a, .booking-cta a, plus
     spécifiques que a) ; les appels de citation (.cite, classe plus spécifique que a) gardent leur couleur de repère.
     :where() laisse à ces deux sélecteurs la spécificité des règles d'écran qu'ils recouvrent (.msg--assistant a,
     .booking-cta a : 0,1,1) : un lien survolé ou focalisé garde sa couleur de survol, comme avant le 12/10/2026
     (a:hover l'emportait alors sur a ; banc de non-régression : impression identique). */
  a,
  .msg--assistant a:where(:not(.cite)),
  .booking-cta a:where(:not(.cite)) {
    color: #000;
  }
}
