/* À l'origine minimaliste (comme demandé dans documentation/
   interface.md) -- modernisé (ADR 0063) vers un look "dashboard produit"
   plus visuel : couleur de marque, relief sur les cartes, rail de
   navigation permanent. Toujours aucune dépendance externe (police
   système, icônes en SVG écrites à la main dans js/icons.js, aucun CDN). */

:root {
  --bg: #0b0b0c;
  --surface: #17171a;
  --border: #2a2a2e;
  --text: #f2f2f0;
  --text-dim: #9a9a9e;
  --accent: #e8e6df;
  --accent-active: #ffffff;
  --danger: #d9736a;
  --radius: 18px;

  /* Couleur de marque (ADR 0063) : distincte de --accent/--accent-active
     (qui restent la teinte neutre texte/bordure) -- utilisée pour les
     actions primaires, l'état actif de la nav, les anneaux d'écoute. */
  --brand: #8b7bff;
  --brand-hover: #a396ff;
  --shadow-card: 0 6px 20px rgba(0, 0, 0, 0.35);
}

/* Thème clair : automatique si le système le demande (et qu'aucun choix
   manuel n'a été fait, js/theme.js pose data-theme sinon), ou forcé par le
   bouton switch même si le système est en sombre -- :not([data-theme="dark"])
   dans la media query laisse un choix manuel "dark" gagner sur le système
   côté clair ; le sélecteur [data-theme="light"] gagne sur le système côté
   sombre. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #f7f6f3;
    --surface: #ffffff;
    --border: #e2e0da;
    --text: #17171a;
    --text-dim: #6b6b6f;
    --accent: #17171a;
    --accent-active: #000000;
    --brand: #5741e0;
    --brand-hover: #4633c4;
    --shadow-card: 0 4px 16px rgba(23, 23, 26, 0.08);
  }
}
:root[data-theme="light"] {
  --bg: #f7f6f3;
  --surface: #ffffff;
  --border: #e2e0da;
  --text: #17171a;
  --text-dim: #6b6b6f;
  --accent: #17171a;
  --accent-active: #000000;
  --brand: #5741e0;
  --brand-hover: #4633c4;
  --shadow-card: 0 4px 16px rgba(23, 23, 26, 0.08);
}

/* Thème de couleur (ADR 0066, js/palette.js) : quelle teinte remplace le
   violet/indigo par défaut ("primary" -- déjà les valeurs de --brand
   ci-dessus, aucune règle supplémentaire nécessaire pour lui). Chaque
   palette suit EXACTEMENT la même structure à 3 blocs que --brand/
   --brand-hover plus haut (racine = sombre par défaut, media query
   "prefers-color-scheme: light" + sélecteur explicite [data-theme="light"]
   pour le clair) -- seule la teinte change, jamais la logique clair/sombre
   elle-même. Spécificité : [data-palette="x"] ajoute un sélecteur
   d'attribut de plus que les blocs --brand ci-dessus, donc gagne
   naturellement sans dépendre de l'ordre des règles. */
:root[data-palette="ocean"] { --brand: #5b93ff; --brand-hover: #7fabff; }
@media (prefers-color-scheme: light) {
  :root[data-palette="ocean"]:not([data-theme="dark"]) { --brand: #2955c9; --brand-hover: #1f45a8; }
}
:root[data-palette="ocean"][data-theme="light"] { --brand: #2955c9; --brand-hover: #1f45a8; }

:root[data-palette="forest"] { --brand: #4fcf8a; --brand-hover: #74dba3; }
@media (prefers-color-scheme: light) {
  :root[data-palette="forest"]:not([data-theme="dark"]) { --brand: #1f8f52; --brand-hover: #197340; }
}
:root[data-palette="forest"][data-theme="light"] { --brand: #1f8f52; --brand-hover: #197340; }

:root[data-palette="coral"] { --brand: #ff9466; --brand-hover: #ffab87; }
@media (prefers-color-scheme: light) {
  :root[data-palette="coral"]:not([data-theme="dark"]) { --brand: #d1502a; --brand-hover: #b03f20; }
}
:root[data-palette="coral"][data-theme="light"] { --brand: #d1502a; --brand-hover: #b03f20; }

/* "Liquid" (ADR 0068) : seule palette qui change plus que --brand -- fond
   en dégradé (--bg-image, le verre se lit mieux sur un fond vif qu'un
   --bg plat) et deux variables de translucidité (--glass-surface/
   --glass-border) utilisées plus bas par les règles glassmorphisme.
   Même structure à 3 blocs que les autres palettes pour ces 4 variables. */
:root[data-palette="liquid"] {
  --brand: #5ec8ff;
  --brand-hover: #82d6ff;
  --bg-image:
    radial-gradient(circle at 15% 15%, #4b3aa8 0%, transparent 55%),
    radial-gradient(circle at 85% 30%, #1c6f8f 0%, transparent 55%),
    radial-gradient(circle at 50% 90%, #7a2f6b 0%, transparent 55%),
    linear-gradient(160deg, #0a0a18, #12122a);
  --glass-surface: rgba(255, 255, 255, 0.08);
  --glass-border: rgba(255, 255, 255, 0.22);
}
@media (prefers-color-scheme: light) {
  :root[data-palette="liquid"]:not([data-theme="dark"]) {
    --brand: #1f8fd6;
    --brand-hover: #3aa6ea;
    --bg-image:
      radial-gradient(circle at 15% 15%, #ffd6ec 0%, transparent 55%),
      radial-gradient(circle at 85% 30%, #cdeaff 0%, transparent 55%),
      radial-gradient(circle at 50% 90%, #e4d6ff 0%, transparent 55%),
      linear-gradient(160deg, #f7f8ff, #eef3ff);
    --glass-surface: rgba(255, 255, 255, 0.55);
    --glass-border: rgba(255, 255, 255, 0.75);
  }
}
:root[data-palette="liquid"][data-theme="light"] {
  --brand: #1f8fd6;
  --brand-hover: #3aa6ea;
  --bg-image:
    radial-gradient(circle at 15% 15%, #ffd6ec 0%, transparent 55%),
    radial-gradient(circle at 85% 30%, #cdeaff 0%, transparent 55%),
    radial-gradient(circle at 50% 90%, #e4d6ff 0%, transparent 55%),
    linear-gradient(160deg, #f7f8ff, #eef3ff);
  --glass-surface: rgba(255, 255, 255, 0.55);
  --glass-border: rgba(255, 255, 255, 0.75);
}

/* Glassmorphisme CSS pur (ADR 0068) : translucidifie les surfaces
   flottantes/de chrome (cartes, panneaux, nav, modale, toast) --
   délibérément PAS les champs de saisie (moins lisible, peu d'intérêt
   pour du texte dense) ni les listes recréées dynamiquement en JS
   (conversations, graphe) autrement qu'via ces mêmes classes déjà
   partagées. Chaque sélecteur reprend exactement celui de la règle de
   base qu'il override, avec le préfixe [data-palette="liquid"] en plus
   -- garantit une spécificité toujours supérieure, sans !important, quel
   que soit l'ordre des règles dans ce fichier. La vraie réfraction JS
   (web/js/vendor/liquid-glass.js) s'ajoute par-dessus sur .mie-toggle
   UNIQUEMENT (web/index.html) -- ce bloc ne fournit que le "flou dépoli"
   que la lib elle-même documente comme habillage CSS à part. */
[data-palette="liquid"] .card,
[data-palette="liquid"] .msg.mie,
[data-palette="liquid"] .toast,
[data-palette="liquid"] .modal-box,
[data-palette="liquid"] .graph-panel,
[data-palette="liquid"] .nav,
[data-palette="liquid"] .auth-card {
  background: var(--glass-surface);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  border-color: var(--glass-border);
}
[data-palette="liquid"] .mie-toggle {
  background: var(--glass-surface);
  border-color: var(--glass-border);
  backdrop-filter: blur(6px) saturate(160%);
  -webkit-backdrop-filter: blur(6px) saturate(160%);
}
[data-palette="liquid"] .mie-toggle.listening {
  background: var(--brand);
  border-color: var(--brand);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* background-color + background-image séparés (au lieu du raccourci
     `background`) : --bg-image n'est défini QUE par la palette "Liquid"
     (ADR 0068) -- `none` par défaut (var() de repli) laisse --bg seul
     visible partout ailleurs, comme avant. */
  background-color: var(--bg);
  background-image: var(--bg-image, none);
  background-attachment: fixed;
  background-size: cover;
  color: var(--text);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.app {
  flex: 1;
  display: flex;
  flex-direction: column;
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
  /* padding-top: 64px (pas 24px) -- dégage `.nav-toggle-btn` (36px, fixed
     top:16), désormais TOUJOURS affiché (repli ou non du rail, voir plus
     bas) et donc toujours susceptible de chevaucher le titre h1 de la
     page sinon. */
  padding: 64px 20px 96px;
}

h1 { font-size: 1.5rem; font-weight: 700; margin: 0; letter-spacing: -0.01em; }

/* En-tête de page (ADR 0063) : remplace le <h1> nu -- titre affirmé +
   emplacement pour des actions (recherche, bouton principal de la page)
   sur la même ligne, comme le reste des dashboards produit. */
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 24px;
}
.page-header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Tuile de statistique (ADR 0063, résumé d'activité de l'accueil). */
.stat-tile { flex: 1; min-width: 120px; text-align: center; }
.stat-tile .stat-value { font-size: 1.3rem; font-weight: 700; }
.stat-tile .stat-label { color: var(--text-dim); font-size: 0.75rem; margin-top: 2px; }
.stat-row { display: flex; gap: 10px; margin: 20px 0; }
/* 3 tuiles à 120px min + gaps (380px) dépassent la largeur dispo sur
   TOUS les téléphones courants en portrait, pas juste les petits -- rail
   nav (64px) + padding .app (40px) ne laissent que ~270-330px sur un
   iPhone SE/12/14 (375-430px de large). Débordement horizontal trouvé en
   rendu mobile réel, pas visible en redimensionnant juste la fenêtre
   desktop. 640px (déjà utilisé ailleurs dans ce fichier comme seuil
   "petit écran") couvre large plutôt que de viser pile la largeur
   d'un appareil précis. */
@media (max-width: 640px) {
  .stat-row { flex-wrap: wrap; }
  .stat-tile { flex-basis: calc(50% - 5px); min-width: 0; }
}

/* Icônes SVG inline (js/icons.js, ADR 0063) : currentColor, héritent la
   couleur du texte environnant -- aucun asset séparé par thème. */
.icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.icon svg { display: block; width: 100%; height: 100%; }

/* --- Nav : rail permanent, à toutes les largeurs (ADR 0063) -- icônes
   seules et compact en mobile, icône + libellé en large écran. Remplace
   l'ancienne barre basse mobile / colonne desktop distinctes : un seul
   comportement, une seule règle de largeur par media query. */
.nav {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 20px 8px calc(16px + env(safe-area-inset-bottom));
  gap: 4px;
  z-index: 10;
  transition: transform 0.2s ease;
}
.nav a {
  position: relative; /* ADR 0193 : ancre .nav-mic-badge, positionnée sur l'icône de l'entrée "mie" */
  color: var(--text-dim);
  text-decoration: none;
  font-size: 0.7rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 4px;
  border-radius: 12px;
  width: 100%;
}
.nav a .icon { width: 22px; height: 22px; }
.nav a.active { color: var(--brand); font-weight: 600; background: color-mix(in srgb, var(--brand) 15%, transparent); }
.nav a:hover:not(.active) { color: var(--text); }
/* Icônes seules en dessous de 720px (voir commentaire au-dessus de .nav) :
   sans ça, le libellé complet ("Conversations", "Récapitulatifs"...) ne
   tient pas dans les 64px du rail compact et déborde hors de l'écran --
   trouvé en rendu mobile réel (390px), invisible en test desktop
   redimensionné. */
.nav-label { display: none; }
@media (min-width: 720px) {
  .nav-label { display: block; }
}

/* ADR 0193 : pastille micro sur l'entrée "mie" -- l'écoute tourne dans une
   fenêtre dédiée (web/ecoute.html), potentiellement pendant qu'une autre
   page est affichée ici. Positionnée sur le coin de l'icône (7px/10px :
   calé pour l'icône 22px mobile / 20px desktop ci-dessus, voir la media
   query plus bas). `var(--accent)` -- même couleur que le reste du site
   pour "quelque chose qui mérite l'attention" (avertissement de plafond
   payg, bordure du soufflage). Pulsation discrète, même patron que
   .speaker-chip.speaking (locuteur actif, mode Concentration) --
   cohérence visuelle "quelque chose est en train de se passer". -->
.nav-mic-badge {
  position: absolute;
  top: 4px;
  left: 34px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  animation: nav-mic-pulse 1.6s ease-in-out infinite;
}
.nav-mic-badge .icon { width: 11px; height: 11px; }
@keyframes nav-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 0%, transparent); }
}
@media (min-width: 720px) {
  .nav-mic-badge { top: 6px; left: 26px; }
}

/* ADR 0193 (revue 2026-08-26) : superposition plein écran -- affiche une
   AUTRE page du site par-dessus web/index.html (qui reste chargée en
   dessous, appel vivant) pendant une écoute. Couvre aussi le rail de nav
   (z-index 30 > 10 ci-dessus) : la page superposée affiche le SIEN
   normalement, à l'intérieur de l'iframe -- jamais deux rails visibles en
   même temps. `background` évite un flash transparent le temps que la
   page superposée charge. */
#page-overlay {
  display: none;
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  z-index: 30;
  background: var(--bg);
}

.theme-toggle {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin-top: auto;
}
.theme-toggle:hover { border-color: var(--brand); color: var(--brand); }

@media (min-width: 720px) {
  .nav {
    width: 220px;
    align-items: stretch;
    padding: 32px 14px;
  }
  .nav a { flex-direction: row; padding: 10px 12px; }
  .nav a .icon { width: 20px; height: 20px; }
  .app { max-width: 720px; padding: 64px 40px 40px; } /* padding-top 64px, même raison que la règle de base ci-dessus */
}

/* `.has-nav` posée sur <body> par nav.js (jamais présente sur les pages
   sans nav -- login/forgot-password/reset-password) : réserve la largeur
   du rail, jamais appliqué là où il n'y a pas de rail.

   Correctif centrage horizontal (signalé par 0x784c, 2026-08-23) : un
   `margin-left` posé directement sur `.app` fixait son bord gauche sans
   toucher au droit -- avec `margin: 0 auto` déjà en place sur `.app`
   (ci-dessus), le navigateur ne pouvait alors résoudre le "auto" que d'un
   seul côté (le droit), collant tout le contenu contre le rail au lieu de
   le centrer dans l'espace RESTANT après lui. `padding-left` ici, sur
   <body> (plutôt que `margin-left` sur `.app`), réduit la largeur
   disponible sans casser la symétrie : le `margin: 0 auto` de `.app`
   centre alors correctement DANS cet espace réduit, exactement comme il
   centre déjà tout seul (sans rail) sur les pages sans nav. */
body.has-nav { padding-left: 64px; transition: padding-left 0.2s ease; }
@media (min-width: 720px) { body.has-nav { padding-left: 220px; } }

/* Repli du rail de nav (0x784c, signalé "très voyante" en test réel sur
   téléphone, 2026-08-31) -- état retenu par appareil (localStorage,
   nav.js), pas par compte : un choix d'affichage local, pas une
   préférence à synchroniser entre appareils. */
body.nav-collapsed .nav { transform: translateX(-100%); }
body.nav-collapsed.has-nav { padding-left: 0; }

/* `.nav-toggle-btn` (0x784c, 2026-09-01 : "le bouton trois barres qui
   reste juste à gauche du volet" -- remplace l'ancienne paire
   .nav-collapse-btn (croix, DANS le rail) / .nav-expand-btn (hamburger,
   flottant, visible seulement replié) par UN SEUL bouton hamburger,
   toujours affiché, qui bascule les deux sens. Position fixe, collée au
   bord droit du rail : left:16px rail replié (le rail est hors écran,
   rien à sa gauche), left:76px (64px de rail + 12px) rail ouvert en
   dessous de 720px, left:232px (220px + 12px) au-delà -- voir media
   query plus bas/plus haut. Ne chevauche jamais les icônes du rail
   (toujours à sa DROITE), contrairement à une position fixe unique. */
.nav-toggle-btn {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 11; /* au-dessus du rail (10) */
  transition: left 0.2s ease;
}
body:not(.nav-collapsed) .nav-toggle-btn { left: 76px; }
/* Même sélecteur, même spécificité que la règle mobile ci-dessus -- DOIT
   rester après elle dans le fichier pour gagner à 720px+ (signalé par
   0x784c, 2026-09-01 : "sur pc, il ne décale pas assez et reste au
   milieu du volet" -- cette règle vivait avant dans le bloc @media
   desktop tout en haut du fichier, ligne ~345, où elle perdait toujours
   contre celle-ci malgré le @media, l'ordre source départageant deux
   règles de spécificité égale). */
@media (min-width: 720px) {
  body:not(.nav-collapsed) .nav-toggle-btn { left: 232px; } /* rail 220px + 12px */
}

/* --- Boutons --- */
/* ADR 0182 (0x784c : "une animation stylistique quand on survole un
   élément... comme le bouton Écouter") : `transition` posée ICI, sur la
   règle de base -- chaque bouton du site (des dizaines de `:hover` déjà
   en place plus bas, jusque-là instantanés faute de transition déclarée)
   devient animé d'un coup, sans toucher une seule de ces règles
   individuellement. `transform`/`box-shadow` inclus dans la liste même
   si la plupart des boutons ne les utilisent pas encore -- ne coûte rien
   pour ceux qui ne changent pas cette propriété, et évite d'avoir à
   revenir ici à chaque nouveau bouton animé (voir .mie-toggle,
   button.primary ci-dessous). */
button, .btn {
  font: inherit;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 12px;
  padding: 10px 16px;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
button:hover, .btn:hover { border-color: var(--accent); }
/* `a.primary` (ADR 0148) : même style que button.primary, pour un lien
   qui se comporte visuellement comme un bouton d'action (CTA de
   web/concept.html) -- `.btn` ci-dessus couvrait déjà `<a>`, seul le
   modificateur ".primary" restait scopé à `button`. */
button.primary, a.primary { background: var(--brand); color: #ffffff; border-color: var(--brand); font-weight: 600; text-decoration: none; display: inline-block; transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease; }
/* ADR 0182 : léger soulèvement au survol -- même langage déjà utilisé par
   .concept-grid .card (web/concept.html), étendu
   ici à TOUS les boutons d'action principaux du site (Souscrire, Envoyer,
   Activer...), pas seulement cette page. `:active` -- retour tactile
   "pressé" au clic, montre que le bouton a bien réagi avant même la
   réponse serveur. */
button.primary:hover, a.primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }
/* `:not(:disabled)` sur le soulèvement seulement (pas la couleur ci-dessus,
   déjà présente avant l'ADR 0182 et inchangée) -- plusieurs boutons
   .primary passent `disabled` le temps d'un appel réseau (planActionBtn,
   paygCapBtn, sendBtn via setButtonLoading...) ; `:hover` reste actif sur
   un bouton désactivé même si les clics n'ont plus d'effet -- sans cette
   garde, il aurait continué à "flotter" pendant l'attente, laissant croire
   à tort qu'il restait cliquable. */
button.primary:hover:not(:disabled), a.primary:hover { transform: translateY(-1px); box-shadow: 0 6px 16px color-mix(in srgb, var(--brand) 25%, transparent); }
button.primary:active:not(:disabled), a.primary:active { transform: translateY(0); box-shadow: none; }
button.danger { color: var(--danger); border-color: var(--danger); background: transparent; }
button:disabled { opacity: 0.4; cursor: default; }

/* Bouton icône seule (ex: corbeille de suppression, ADR 0032) -- compact,
   pas le padding généreux des boutons texte normaux. */
.icon-btn { padding: 6px 10px; font-size: 0.95rem; line-height: 1; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; }
/* ADR 0182 : légère teinte de fond au survol -- ces boutons n'ont souvent
   qu'une icône et une bordure fine (ex: #settings-menu-toggle), le simple
   changement de couleur de bordure générique ci-dessus passait facilement
   inaperçu. `:active` : réduction discrète, cohérente avec le retour
   tactile déjà posé sur button.primary ci-dessus. Placée AVANT les règles
   plus spécifiques déjà en place plus bas (.theme-toggle, .speaker-chip-
   mute-btn...) -- celles-ci gardent la priorité (même valeur de
   spécificité, ordre du fichier), cette règle ne s'applique donc que là
   où rien de plus précis n'est déjà défini. */
.icon-btn:hover { background: var(--bg); }
.icon-btn:active { transform: scale(0.92); }

/* Bouton "i" d'aide contextuelle au survol/focus (web/index.html, demandé
   par 0x784c) -- remplace un texte d'aide toujours affiché par une info
   disponible sur demande. Pur CSS (:hover/:focus-visible), pas de JS. */
.info-tip {
  position: relative;
  width: 20px; height: 20px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  cursor: help;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.info-tip:hover, .info-tip:focus-visible { color: var(--text); border-color: var(--text-dim); }
.info-tip .icon { width: 12px; height: 12px; }
.info-tip-bubble {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 240px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 0.8rem;
  line-height: 1.35;
  text-align: left;
  font-weight: 400;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
  pointer-events: none;
  z-index: 20;
}
.info-tip:hover .info-tip-bubble, .info-tip:focus-visible .info-tip-bubble { opacity: 1; visibility: visible; }
/* La bulle est centrée sur son bouton "i" (240px de large) -- sur un
   écran de téléphone, un bouton proche du bord droit de la colonne de
   contenu (ex: "Signaler : je parle") pousse la bulle hors écran (trouvé
   en rendu mobile réel : bulle allant jusqu'à x=419 sur une largeur de
   375px). Pas de repositionnement dynamique ici (pas de JS) -- on
   rétrécit la bulle sous 640px pour que la moitié de sa largeur ne
   dépasse plus la marge dispo de chaque côté, quel que soit le bouton. */
@media (max-width: 640px) {
  .info-tip-bubble { max-width: 150px; }
}
@media (max-width: 375px) {
  /* 150px suffisait à 375-390px (iPhone 12/13/14...) mais débordait
     encore de ~14px sur un écran de 360px (Android courant, Galaxy
     S21...) -- même bouton "Signaler : je parle", trouvé en rendu
     mobile réel à cette largeur précise. */
  .info-tip-bubble { max-width: 120px; }
}

/* Spinner de bouton (ADR 0063, js/loading.js) : remplace le contenu d'un
   bouton pendant une action asynchrone, sans changer sa largeur. */
@keyframes spin { to { transform: rotate(360deg); } }
.btn-spinner {
  display: inline-block;
  width: 15px; height: 15px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  opacity: 0.7;
  animation: spin 0.7s linear infinite;
}

/* --- Accueil : gros bouton central on/off (mockup interface.md, page 1) --- */
.mie-toggle {
  width: 180px;
  height: 180px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  margin: 24px auto;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
}
.mie-toggle.listening {
  border-color: var(--brand);
  background: var(--brand);
  color: #ffffff;
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--brand) 18%, transparent);
}
/* ADR 0182 (0x784c : "une animation stylistique quand on survole un
   élément... comme le bouton Écouter") : ce bouton n'avait AUCUN `:hover`
   propre jusqu'ici -- `transition: all` était déjà posée juste au-dessus
   mais rien ne déclenchait de changement au survol pour l'animer. Léger
   agrandissement + halo teinté marque, cohérent avec le cercle déjà
   utilisé pour `.listening` ci-dessus plutôt qu'un simple changement de
   bordure. `:active` -- léger retrait au clic, retour tactile immédiat
   avant même la réponse du serveur (session.start(), potentiellement
   lente le temps de la négociation WebRTC). */
.mie-toggle:hover {
  transform: scale(1.035);
  border-color: var(--brand);
  box-shadow: 0 10px 28px color-mix(in srgb, var(--brand) 22%, transparent);
}
.mie-toggle:active { transform: scale(0.97); }
/* En écoute, le halo `.listening` ci-dessus (indicateur d'état, PAS un
   effet de survol) doit rester visible même survolé -- combine les deux
   ombres plutôt que de laisser `:hover` l'écraser (même spécificité,
   `.mie-toggle:hover` gagnerait sinon simplement parce qu'il est déclaré
   après). */
.mie-toggle.listening:hover {
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--brand) 18%, transparent), 0 10px 28px color-mix(in srgb, var(--brand) 22%, transparent);
}

/* Signal manuel (ADR 0026) : même bouton rond réutilisé (.mie-toggle) mais
   en petit format pill, secondaire au bouton principal ci-dessus. */
.mie-toggle-secondary {
  width: auto;
  height: auto;
  border-radius: 999px;
  padding: 8px 18px;
  font-size: 0.82rem;
  margin: 12px auto 2px;
}
.mie-toggle-secondary.listening {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 18%, transparent);
}
/* ADR 0182 : soulèvement plutôt qu'agrandissement pour ce format pill --
   même langage que les boutons d'action de web/concept.html, l'échelle
   de .mie-toggle ci-dessus déformerait moins gracieusement un bouton
   large plutôt que circulaire. Remplace (pas cumule) le hover de
   .mie-toggle : déclarée après, même spécificité. `:not(:disabled)` --
   contrairement au bouton principal, CE bouton (Concentration, Signal
   manuel) démarre RÉELLEMENT désactivé (attribut HTML `disabled`, tant
   qu'aucune écoute n'est active) -- sans la garde, il aurait semblé
   cliquable avant même la connexion. */
.mie-toggle-secondary:hover:not(:disabled) {
  transform: translateY(-2px);
  border-color: var(--brand);
  box-shadow: 0 6px 16px color-mix(in srgb, var(--brand) 18%, transparent);
}
.mie-toggle-secondary:active:not(:disabled) { transform: translateY(0); }
.mie-toggle-secondary.listening:hover:not(:disabled) {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 18%, transparent), 0 6px 16px color-mix(in srgb, var(--brand) 18%, transparent);
}

.row { display: flex; justify-content: center; gap: 40px; margin-top: 8px; }
.io-config { text-align: center; }
.io-config label { display: block; font-size: 0.72rem; color: var(--text-dim); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.05em; }
/* `min-width: 0` -- un <select> est un enfant flex (.card-row et
   consorts) avec min-width:auto par défaut, qui l'empêche de rétrécir en
   dessous de son contenu ; `max-width: 100%` fait alors le travail de
   troncature. Repéré avec la liste des périphériques audio (noms réels
   du système, ex: "Odyssey G5 (DisplayPort)...", bien plus longs que nos
   propres libellés) qui débordait de 200px hors de l'écran sur mobile
   -- les autres select passent inaperçus tant que leur texte reste
   court, mais le risque existe pour n'importe quel select. */
select { font: inherit; background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 10px; padding: 6px 10px; min-width: 0; max-width: 100%; }

.toggle-row { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 32px; font-size: 0.85rem; color: var(--text-dim); }
.switch { position: relative; width: 40px; height: 22px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; inset: 0; background: var(--border); border-radius: 22px; cursor: pointer; transition: 0.2s; }
.slider::before { content: ""; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; background: white; border-radius: 50%; transition: 0.2s; }
input:checked + .slider { background: var(--accent); }
input:checked + .slider::before { transform: translateX(18px); }

/* --- Cartes / listes --- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 12px;
  box-shadow: var(--shadow-card);
  /* ADR 0182/0182bis : border-color puis transform ajoutées à la liste --
     les règles de survol plus bas changent aussi ces propriétés, qui
     sauteraient sinon instantanément pendant que l'ombre, elle, s'anime
     déjà normalement. */
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
/* Cartes cliquables (liste de conversations) : relief accentué au survol
   -- retour visuel qui manquait totalement avant (ADR 0063). */
.card[data-clickable]:hover, .card.clickable:hover { box-shadow: 0 8px 26px rgba(0, 0, 0, 0.16); border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); }
/* `.clickable` seul ne posait le curseur qu'au cas par cas (`card.style.cursor`
   posé en JS, web/conversations.html) -- ADR 0133 en a besoin sans ce
   code JS répété (tuiles de l'accueil) : posé une fois ici pour tout le
   monde, sans rien changer pour l'existant (même curseur). */
.card.clickable { cursor: pointer; }

/* ADR 0182bis (0x784c : "le même effet pour les catégories des
   paramètres, abonnement, mémoire") : les grandes cartes qui regroupent
   une SECTION entière sur ces trois pages (Compte/Langue/Reconnaissance
   de la voix/Zone dangereuse sur Paramètres ; formule/budgets/pay-as-
   you-go sur Abonnement ; stockage/export-import/graphe sur Mémoire) --
   distinct de `.card.clickable` ci-dessus : ces cartes ne mènent nulle
   part au clic (pas de `cursor: pointer`, le contenu à l'intérieur reste
   seul interactif), juste un relief au survol pour cohérence avec les
   boutons animés (ADR 0182). Plus discret qu'un `.clickable` (pas
   d'agrandissement de l'ombre au même degré) -- ne doit jamais laisser
   croire que toute la carte est cliquable. */
.card-section:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14);
  border-color: color-mix(in srgb, var(--brand) 25%, var(--border));
}
.card-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; }

/* En-tête de regroupement par date de la liste de conversations (ADR
   0130) : discret, jamais une carte -- juste un repère visuel entre
   deux groupes, cohérent avec ".muted" utilisé partout ailleurs pour
   du texte secondaire. */
.conv-date-heading {
  color: var(--text-dim);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 18px 4px 8px;
}
.conv-date-heading:first-child { margin-top: 4px; }

/* Indicateur "a écrit en mémoire" sur une carte de conversation (ADR
   0131) : même teinte que le libellé "Retenu" du badge par message
   (ADR 0113, --brand) -- juste une icône ici, pas de texte (la carte a
   moins de place qu'une bulle de message), le survol/aria-label porte
   l'explication. */
.conv-memory-badge { display: inline-flex; color: var(--brand); }
.muted { color: var(--text-dim); font-size: 0.82rem; }

/* Champ de recherche (ADR 0063) : icône loupe intégrée, même look que les
   autres champs mais compact -- utilisé dans .page-header-actions. */
.search-input {
  position: relative;
  display: flex;
  align-items: center;
}
/* top:50%/translateY (pas align-items du conteneur flex, ADR 0145) :
   `position: absolute` sort l'icône du flux -- flexbox ne l'aligne
   plus du tout, elle retombait en haut du champ (jamais centrée
   verticalement, signalé par 0x784c en usage réel). */
.search-input .icon {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  color: var(--text-dim); width: 15px; height: 15px; pointer-events: none;
}
.search-input input {
  font: inherit;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px 8px 32px;
  width: 200px;
  max-width: 100%;
}
.search-input input:focus { outline: none; border-color: var(--brand); }

/* Liste de résultats de la recherche du graphe (ADR 0128) : positionnée
   par rapport à .search-input (position: relative, ci-dessus) -- suit
   donc le champ partout où il va, y compris en grand écran (ADR 0127),
   sans règle CSS séparée pour ce cas. */
.search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-width: 320px;
  max-height: 260px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.16);
  z-index: 2;
  display: flex;
  flex-direction: column;
  padding: 4px;
}
/* `[hidden]` (attribut HTML standard, utilisé par web/memoire.html plutôt
   qu'une classe dédiée) a la même spécificité CSS que `.search-results`
   ci-dessus -- sans cette règle, `display: flex` (feuille AUTEUR)
   l'emporterait quand même sur `[hidden] { display: none }` (feuille
   UTILISATEUR-AGENT, priorité plus basse par défaut) : la liste restait
   visible malgré `hidden` posé en JS. Repéré en testant ADR 0128 en
   conditions réelles avant de le considérer terminé. */
.search-results[hidden] { display: none; }
.search-result-item {
  font: inherit;
  text-align: left;
  background: none;
  border: none;
  color: var(--text);
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* ADR 0152 : `overflow: hidden` ci-dessus (ellipse du texte trop long)
     ramène par effet de bord la hauteur minimale automatique de l'item à
     0 dans ce conteneur flex en colonne (.search-results, max-height +
     overflow-y: auto) -- sans `flex-shrink: 0`, dès que la liste dépasse
     260px (beaucoup de résultats, ex. filtre "7 derniers jours" sur un
     compte bien rempli, ADR 0151), le navigateur ÉCRASE chaque ligne au
     lieu de faire défiler, texte tassé/illisible (signalé par 0x784c en
     usage réel). `flex-shrink: 0` garde chaque item à sa hauteur
     naturelle -- c'est alors le conteneur qui défile, jamais les items
     qui rétrécissent. */
  flex-shrink: 0;
}
.search-result-item:hover, .search-result-item:focus-visible { background: var(--bg); outline: none; }

/* Filtre par type d'entité du graphe (ADR 0129) : puces cliquables,
   "actif" (type affiché) par défaut -- ".off" = type masqué, même
   distinction visuelle qu'un toggle désactivé ailleurs sur le site
   (opacité réduite + fond neutre), pas une couleur dédiée en plus. */
.type-filter-row { display: flex; flex-wrap: wrap; gap: 6px; }
.type-filter-chip {
  font: inherit;
  font-size: 0.8rem;
  background: color-mix(in srgb, var(--brand) 16%, var(--surface));
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
}
.type-filter-chip.off { background: var(--surface); color: var(--text-dim); opacity: 0.6; }
.type-filter-chip:hover { border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); }

/* États de chargement (ADR 0063, js/loading.js) : squelette animé, dérivé
   de --surface/--border -- fonctionne dans les deux thèmes sans variable
   dédiée. */
@keyframes skeleton-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
.skeleton {
  background: linear-gradient(90deg, var(--surface), var(--border), var(--surface));
  border-radius: 10px;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
.skeleton-row { height: 58px; margin-bottom: 12px; border-radius: var(--radius); }

/* Formulaire compte (ADR 0030), même look que .auth-card input mais dans
   une carte ordinaire (paramètres, pas un écran de connexion). */
.card input[type="text"], .card input[type="email"], .card input[type="password"] {
  display: block;
  width: 100%;
  font: inherit;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
}

/* --- Zone "+" ajout de fichier (mockup interface.md, page 2) --- */
.add-zone {
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  padding: 28px;
  text-align: center;
  color: var(--text-dim);
  cursor: pointer;
  margin-bottom: 20px;
}
.add-zone:hover { border-color: var(--brand); color: var(--text); }
.add-zone input[type="file"] { display: none; }
.add-zone .plus { font-size: 2rem; line-height: 1; display: block; margin-bottom: 6px; }
/* Upload en cours (ADR 0063) : remplace le "+" par un spinner, la zone
   reste non cliquable tant qu'un envoi est en cours (géré côté JS). */
.add-zone.uploading { cursor: default; opacity: 0.8; }
.add-zone.uploading .plus { display: none; }
.add-zone-spinner { display: none; margin: 0 auto 6px; }
.add-zone.uploading .add-zone-spinner { display: inline-block; width: 24px; height: 24px; border-width: 3px; }

.storage-bar { height: 6px; border-radius: 4px; background: var(--border); overflow: hidden; margin: 8px 0 4px; }
.storage-bar > div { height: 100%; background: var(--brand); transition: width 0.2s ease; }

/* --- Conversations --- */
.conv-thread { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.msg { position: relative; max-width: 80%; padding: 10px 14px; border-radius: 14px; font-size: 0.92rem; }
.msg.user { align-self: flex-end; background: var(--accent); color: var(--bg); }
.msg.mie { align-self: flex-start; background: var(--surface); border: 1px solid var(--border); }
.msg .attachment { display: block; margin-top: 6px; font-size: 0.8rem; text-decoration: underline; }

/* Signal d'intent par message (ADR 0065) : discret, jamais plus visible
   que le texte du message lui-même -- une information, pas une alerte. */
.msg-intent { display: flex; align-items: center; gap: 4px; margin-top: 6px; font-size: 0.72rem; opacity: 0.7; }
.msg-intent .icon { width: 12px; height: 12px; }

/* Suppression par message (ADR 0070) : discrète (opacité réduite en
   permanence, pleine seulement au survol), coin de la bulle -- jamais
   aussi visible que le contenu du message lui-même. `.msg.user` (fond
   coloré, ADR 0035) inverse la couleur pour rester lisible dessus. */
.msg-delete { position: absolute; top: 4px; right: 4px; padding: 3px; border: none; background: none; opacity: 0.35; }
.msg-delete:hover { opacity: 1; border-color: var(--danger); color: var(--danger); }
.msg.user .msg-delete { color: var(--bg); }

/* Indicateur "en cours de formation" (ADR 0114) : bulle temporaire, même
   habillage que .msg.mie (jamais confondue avec un vrai message côté
   utilisateur) -- trois points qui rebondissent en décalé le temps du
   chemin lent côté serveur (extraction/résolution/formulation,
   memory/queue.py), jusqu'à quelques dizaines de secondes de silence
   sinon (0x784c en usage réel : 24s sans aucun signal, un fait constaté
   plutôt qu'un bug de code, voir échange précédent). Ajoutée/retirée en
   JS (web/conversations.html), jamais rendue par loadThread() elle-même
   -- un rechargement du fil (SSE, ADR 0053) la fait disparaître tout
   seul, en même temps que tout le reste du contenu périmé.  */
.typing-indicator { display: flex; align-items: center; gap: 4px; }
.typing-indicator .dot {
  width: 7px; height: 7px; border-radius: 50%; background: currentColor; opacity: 0.4;
  animation: typing-bounce 1.2s ease-in-out infinite;
}
.typing-indicator .dot:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator .dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing-bounce {
  0%, 60%, 100% { opacity: 0.4; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .typing-indicator .dot { animation: none; opacity: 0.7; }
}

.compose { display: flex; gap: 8px; align-items: flex-end; }
.compose textarea { flex: 1; font: inherit; background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; resize: none; }

/* --- Graphe de mémoire éditable (ADR 0039, page Mémoire) --- */
.graph-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.graph-container {
  position: relative;
  height: 65vh;
  min-height: 420px;
  max-height: 720px;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden; /* jamais de scroll horizontal de page (viewBox contenu ici) */
  background: var(--bg);
  /* Redondant avec touch-action:none sur #graph-svg juste en dessous,
     posé ici aussi par prudence -- touch-action ne s'hérite pas des
     ancêtres au sens CSS classique, et le point de contact exact d'un
     pincement (bord du cadre, juste à côté du SVG) peut techniquement
     toucher ce conteneur avant d'atteindre le SVG lui-même. */
  touch-action: none;
}
/* touch-action: none -- retour en arrière sur le choix du 2026-08-31
   (`pinch-zoom` juste en dessous dans l'historique) : laisser le
   pincement au navigateur zoomait la PAGE entière (onglet/site), pas le
   graphe -- signalé en test réel sur iPhone Safari (2026-09-01), le
   pincement natif ne fait QUE du zoom visuel de page, il ne peut pas
   piloter le viewBox du SVG. Le vrai pincement du graphe est maintenant
   construit en JS (memory-graph.js, mêmes bornes MIN/MAX_ZOOM_WIDTH_RATIO
   que la molette) -- "none" lui laisse TOUS les événements pointer
   (down/move/up), y compris à deux doigts, sans interruption par un
   geste natif du navigateur. */
#graph-svg { width: 100%; height: 100%; display: block; touch-action: none; cursor: grab; }
#graph-svg.panning { cursor: grabbing; }
/* Ctrl/Cmd + glisser sur le fond (sélection en cadre, voir memory-graph.js
   _startMarquee) -- curseur distinct du glissé de fond normal (grabbing),
   pour montrer immédiatement qu'un cadre de sélection est en cours plutôt
   qu'un déplacement du graphe. */
#graph-svg.marquee-selecting { cursor: crosshair; }
.graph-marquee { fill: color-mix(in srgb, var(--brand) 15%, transparent); stroke: var(--brand); stroke-width: 1px; stroke-dasharray: 4 3; pointer-events: none; }

/* ADR 0125 : "grand écran" du graphe -- demandé par 0x784c après le
   correctif de lisibilité (ADR 0123/0124), "une vue dédiée au graphe,
   plus la page mémoire." Le CONTENEUR passe en position fixe plein
   écran (jamais la vraie Fullscreen API du navigateur -- pas de sortie
   surprise en cachant la barre d'adresse, juste la page qui s'efface
   derrière) -- z-index au-dessus du rail de nav (10) mais SOUS les
   toasts (100) et la modale (200, js/modal.js) : une confirmation
   ("supprimer cette entité ?") déclenchée en grand écran doit rester
   visible par-dessus, jamais masquée. */
.graph-container.graph-expanded {
  position: fixed;
  inset: 0;
  z-index: 50;
  height: 100vh;
  min-height: 0;
  max-height: none;
  border-radius: 0;
  border: none;
}
/* z-index: 2 -- au-dessus de `.search-filter-row` (z-index: 1 en grand
   écran, juste plus bas) : sur un écran étroit, cette rangée passe sur
   plusieurs lignes (flex-wrap) et s'étend jusque sous ce bouton, dont le
   tap n'atteignait alors plus jamais le bouton mais la rangée --
   signalé en usage réel sur mobile (0x784c, 2026-09-01). */
.graph-restore-btn { position: absolute; top: 12px; right: 12px; z-index: 2; display: none; }
.graph-container.graph-expanded .graph-restore-btn { display: inline-flex; }
/* Recherche + filtre de date (ADR 0063/0151) regroupés dans un même
   bloc -- flex row en usage normal (même rangée que le reste de
   .page-header-actions, dont c'est un enfant). */
.search-filter-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* ADR 0127 (généralisé ADR 0152bis) : la recherche ET le filtre de date
   restent accessibles en grand écran -- demande explicite de 0x784c
   ("juste le bouton de recherche dans le graphe, même en grand écran"),
   signalé de nouveau quand le filtre de date (ADR 0151), ajouté après
   coup, était resté caché derrière faute d'avoir suivi le même chemin.
   Déplacé dans le coin opposé au bouton de sortie (voir web/memoire.html
   ::setGraphExpanded) -- .search-filter-row garde son CSS de rangée
   flex habituel, seule sa POSITION change ici. */
.graph-container.graph-expanded .search-filter-row { position: absolute; top: 12px; left: 12px; z-index: 1; }
/* ADR 0135 : "Afficher les conversations dans le graphe" (ADR 0124)
   reste accessible en grand écran aussi -- même besoin que la recherche
   ci-dessus (ADR 0127), demande explicite de 0x784c.
   ADR 0194bis : "Afficher les sommets cachés" (ADR 0194) rejoint la même
   boîte -- 0x784c : "le bouton afficher [pourrait] aussi être visible en
   grand écran, à côté d'afficher les conversations". `margin-bottom`
   déplacé ici (plus d'inline dans web/memoire.html) : doit pouvoir être
   ANNULÉ une fois les deux bascules côte à côte (juste en dessous), ce
   qu'un style inline empêcherait sans !important (jamais utilisé dans ce
   projet, voir plus haut). Coin bas-gauche (la recherche et sa liste de
   résultats occupent déjà le coin haut-gauche, ADR 0128) -- fond +
   bordure ajoutés seulement ici (jamais en vue normale, où `.card` sert
   déjà d'arrière-plan) : sans ça, le texte flotterait à même le graphe,
   illisible par-dessus des nœuds/arêtes denses. */
#graph-show-conversations-row, #graph-show-hidden-row { margin-bottom: 12px; }
.graph-container.graph-expanded #graph-bottom-toggles {
  position: absolute;
  bottom: 12px;
  left: 12px;
  z-index: 1;
  display: flex;
  flex-direction: row;
  gap: 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 12px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.16);
}
/* Les deux bascules redeviennent des lignes de flex normales À
   L'INTÉRIEUR de cette boîte commune (plus de fond/bordure chacune, plus
   de marge entre elles -- côte à côte, pas empilées) -- `margin-top: 32px`
   de `.toggle-row` (base, usage hors graphe) neutralisé pour la même
   raison. */
.graph-container.graph-expanded #graph-bottom-toggles .toggle-row { margin: 0; }
/* ADR 0129 (correctif 2026-08-25) : le filtre par type restait dans la
   carte "Graphe de mémoire" en grand écran (jamais déplacé par
   setGraphExpanded, contrairement aux trois blocs ci-dessus qui avaient
   chacun reçu leur propre ADR de rattrapage) -- invisible/inaccessible
   une fois le graphe passé en `position: fixed`, alors que c'est le plus
   utile des quatre sur un graphe dense (le cas d'usage même du grand
   écran). Coin haut-DROIT, sous le bouton "Rétablir la taille" (haut-
   droit lui aussi, ADR 0125) pour ne pas le chevaucher -- les deux autres
   coins sont déjà pris (recherche ADR 0127, "Afficher les conversations"/
   "Afficher les sommets cachés" ADR 0135/0194bis). Pas de fond ici,
   contrairement à #graph-bottom-toggles ci-dessus : chaque puce
   (.type-filter-chip) porte déjà la sienne, lisible seule par-dessus le
   graphe. */
.graph-container.graph-expanded .type-filter-row {
  position: absolute;
  top: 54px;
  right: 12px;
  z-index: 1;
  max-width: min(50vw, 420px);
  justify-content: flex-end;
}

.graph-node circle {
  fill: var(--surface);
  stroke: var(--accent);
  stroke-width: 1.5px;
  cursor: pointer;
}
.graph-node text {
  fill: var(--text-dim);
  font-size: 0.7rem;
  pointer-events: none;
  user-select: none;
}
.graph-node.user circle { fill: var(--brand); stroke: var(--brand-hover); }
.graph-node.ambiguous circle { stroke: var(--danger); stroke-dasharray: 3 2; }
/* Sélection multiple (ADR 0069, Ctrl/Cmd-clic -- fusion de doublons) :
   contour plein et épais, jamais le même traitement que .ambiguous
   (pointillé rouge, déjà pris pour un tout autre sens -- une entité
   ambiguë ET sélectionnée pour fusion doit rester distinguable des deux). */
.graph-node.multi-selected circle { stroke: var(--brand); stroke-width: 3.5px; }
/* Messages/conversations (ADR 0070) : jamais confondus avec une entité
   factuelle extraite -- une conversation se repère par son icône (comme
   un fichier, même traitement), un message par sa taille réduite
   (MESSAGE_NODE_RADIUS, memory-graph.js) ET un contour plus discret,
   cohérent avec sa nature "journal", pas "fait". */
.graph-node.message circle { stroke: var(--text-dim); opacity: 0.8; }
.graph-node.conversation circle { stroke: var(--text-dim); }
/* ADR 0194 : sommet caché à la main (glisser-sélectionner sur le fond,
   voir memory-graph.js), affiché quand même parce que le bascule "Afficher
   les sommets cachés" est actif -- pointillé + estompé pour rester
   reconnaissable au premier coup d'œil, jamais le même traitement que
   .ambiguous (rouge, un tout autre sens : une entité à vérifier, pas une
   entité volontairement mise de côté). */
.graph-node.manually-hidden circle { stroke-dasharray: 2 4; opacity: 0.45; }
.graph-node-icon { color: var(--text-dim); pointer-events: none; }
/* Filtre de recherche du graphe (ADR 0063) : les nœuds qui ne
   correspondent pas à la recherche s'estompent plutôt que de disparaître
   -- la disposition (d3-force) reste stable, rien ne saute visuellement. */
.graph-node.dimmed { opacity: 0.18; }
.graph-edge.dimmed { opacity: 0.12; }

/* `<path>` (pas `<line>`, correctif 0x784c 2026-08-17) : des arêtes
   parallèles (même paire de nœuds, courant après une fusion, ADR 0069 --
   plusieurs relations d'un coup vers la même cible) se courbent pour
   rester distinguables au lieu de se superposer -- voir _edgeGroupKey/
   tick() dans memory-graph.js. `fill: none` indispensable : un <path>
   SVG est rempli en noir par défaut, contrairement à <line>. */
.graph-edge path.graph-edge-line { stroke: var(--border); stroke-width: 1.5px; fill: none; }
.graph-edge:hover path.graph-edge-line { stroke: var(--accent); }
.graph-edge-hit { stroke: transparent; stroke-width: 14px; fill: none; cursor: pointer; }
.graph-edge text {
  fill: var(--text-dim);
  font-size: 0.62rem;
  text-anchor: middle;
  pointer-events: none;
  user-select: none;
}

/* Panneau latéral d'édition (nœud/arête cliqués) -- carte flottante en
   bas du conteneur du graphe, jamais superposée à la nav (mobile). */
.graph-panel {
  position: absolute;
  left: 10px; right: 10px; bottom: 10px;
  max-height: 70%;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
}
.graph-panel-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.graph-panel-header h3 { margin: 0; font-size: 0.95rem; word-break: break-word; }

.graph-property-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.graph-property-row:last-of-type { border-bottom: none; }
/* `type="email"`/`type="password"` (ADR 0082, édition des champs du
   compte -- même schéma que les propriétés du graphe) : mêmes règles que
   `type="text"`, jamais stylées par défaut sinon. */
.graph-property-row input[type="text"],
.graph-property-row input[type="email"],
.graph-property-row input[type="password"] {
  font: inherit;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 8px;
}

/* --- Formulaires (login) --- */
/* Largeur relevée à 400px (signalé par 0x784c, 2026-08-24) : les
   placeholders les plus longs ("Nom d'utilisateur (3 caractères
   minimum)", et leurs équivalents en/es/it -- voir web/js/i18n.js)
   dépassaient largement l'ancienne largeur (360px), tronqués sans
   ellipse ni retour à la ligne. Mesuré : ~302px de texte au plus large
   (espagnol), il fallait ~330px de champ utile -- 400px de carte laisse
   une marge confortable sur toutes les langues supportées.

   `width: 100%` AJOUTÉ (pas seulement `max-width`, bug trouvé en
   vérifiant le correctif ci-dessus dans un vrai navigateur -- la largeur
   ne bougeait pas du tout après le changement de max-width) : `.app`
   (body) est `display:flex; flex-direction:column`, et `.auth-card` a
   des marges gauche/droite `auto` pour se centrer -- sur un item flex,
   des marges auto désactivent le comportement "stretch" par défaut, la
   largeur retombe alors sur un simple ajustement au contenu (bien en
   dessous de `max-width`, jamais dessus). Un `width: 100%` explicite
   (même motif déjà utilisé par `.app` elle-même, voir plus haut) rend la
   largeur DÉFINIE plutôt que dépendante du contenu, `max-width` peut
   alors vraiment la plafonner et `margin: auto` la centrer normalement. */
.auth-card { width: 100%; max-width: 400px; margin: 15vh auto; padding: 28px; }
.auth-card input { display: block; width: 100%; font: inherit; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; }
.error { color: var(--danger); font-size: 0.85rem; min-height: 1.2em; margin: 8px 0; }
.link { color: var(--text-dim); font-size: 0.85rem; text-align: center; margin-top: 14px; }
.link a { color: var(--brand); }

/* --- Notifications toast (ADR 0063, js/toast.js) : remplace les
   alert() natifs -- pile bas-centre, au-dessus du rail de nav (z-index),
   jamais superposée dessus (left offset via `has-nav`, voir media query
   plus bas si besoin). --- */
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.toast-container {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 100;
  width: min(360px, calc(100vw - 32px));
}
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
  box-shadow: var(--shadow-card);
  font-size: 0.85rem;
  animation: toast-in 0.15s ease;
}
.toast-message { flex: 1; }
.toast.error { border-color: var(--danger); }
.toast.error .icon { color: var(--danger); }
.toast.success { border-color: var(--brand); }
.toast.success .icon { color: var(--brand); }
.toast-close { background: none; border: none; color: var(--text-dim); cursor: pointer; padding: 2px; }
.toast-close:hover { color: var(--text); }

/* --- Modale interne (ADR 0064, js/modal.js) : remplace confirm()/
   prompt() natifs du navigateur -- non stylables, incohérents avec le
   thème clair/sombre du site. Au-dessus de tout le reste (toasts
   compris) : une décision bloquante ne doit jamais être masquée. --- */
@keyframes modal-in { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: scale(1); } }
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 200;
}
.modal-box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 20px;
  width: min(360px, 100%);
  animation: modal-in 0.12s ease;
}
.modal-message { font-size: 0.92rem; margin-bottom: 14px; white-space: pre-wrap; }
.modal-box input[type="text"] {
  display: block;
  width: 100%;
  font: inherit;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 14px;
}
.modal-box input[type="text"]:focus { outline: none; border-color: var(--brand); }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* selectDialog (ADR 0069, js/modal.js) : un bouton pleine largeur par
   choix, plutôt que les boutons alignés à droite de .modal-actions --
   distinct visuellement d'un simple confirmer/annuler. */
.modal-choice-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.modal-choice { width: 100%; text-align: left; padding: 10px 12px; }
.modal-choice:hover { border-color: var(--brand); }
.modal-choice .muted { margin-top: 2px; }

/* resolveImportConflictDialog (ADR 0102, js/modal.js) : les deux entités
   comparées (déjà en mémoire / du fichier importé) empilées plutôt que
   côte à côte -- .modal-box reste étroite (360px), une grille à deux
   colonnes y serait trop resserrée pour un libellé un peu long. */
.modal-compare-block { border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px; margin-bottom: 8px; }
.modal-compare-block .muted { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.02em; margin-bottom: 2px; }
.modal-checkbox-row { display: flex; align-items: center; gap: 8px; margin: 4px 0 14px; font-size: 0.85rem; cursor: pointer; }
.modal-checkbox-row input { margin: 0; }

/* --- Page "Concept" (ADR 0148/0149, web/concept.html) : page publique,
   hors authentification, qui présente mie à un public varié -- pas de
   rail nav.js ici (aucun compte requis pour la consulter), mise en page
   centrée à part, jamais réutilisée ailleurs. Inspirée des repères
   classiques d'une page de présentation produit (bandeau collant,
   accroche avec badge "eyebrow", section immersive, cartes à badge
   d'icône, cadre de capture d'écran façon fenêtre d'app) -- mais
   entièrement construite sur les jetons déjà en place (--brand,
   --surface, --border, .card...), donc automatiquement correcte en
   clair/sombre, sans image ni dépendance externe (ADR 0148 § contexte). */
/* `.public-*` (ADR 0148, généralisé ADR 0150) : chrome partagé par
   TOUTES les pages publiques hors authentification (concept, mentions
   légales, confidentialité, CGU...) -- bandeau collant flouté +
   marque, un seul jeu de règles réutilisé plutôt qu'une copie par
   page. Chaque page ajoute sa propre classe (`.concept-page`,
   `.legal-page`) uniquement pour sa largeur de contenu. */
/* `width: 100%` -- `.public-page` est un enfant flex de `body`
   (`display:flex`, plus haut), et ses marges horizontales `auto` (pour
   centrer la page sous `max-width: 760px` en grand écran) désactivent
   le stretch de align-items par défaut (règle flexbox : marge cross-axis
   auto => taille basée sur le contenu, pas sur le conteneur). Sans
   largeur explicite, l'élément grandissait jusqu'au min-content de son
   contenu le plus large (le tableau prestataires de
   confidentialite.html, 339px + padding) et poussait toute la page en
   débordement horizontal sur mobile, indépendamment de min-width.
   `width: 100%` fixe une largeur définie que `max-width` peut ensuite
   plafonner normalement, et que `margin: auto` peut centrer une fois
   cette largeur atteinte. */
.public-page { width: 100%; margin: 0 auto; padding: 0 24px 72px; }
.concept-page { max-width: 1040px; }
.legal-page { max-width: 760px; }

/* Historique (ADR 0149) : bandeau collant flouté avec fond semi-
   transparent. Deux défauts trouvés ensuite (ADR 0197) : fantôme du
   titre du hero au travers pendant le scroll (opacité insuffisante) et,
   sous la palette "liquid", un bloc quasi blanc/opaque qui jurait avec le
   dégradé de fond -- le dégradé "liquid" ne porte de la couleur qu'aux
   deux bords de l'écran (blobs à 15%/85%), une bande fine plein-largeur
   comme ce bandeau ne montrait donc presque que du blanc au milieu,
   perçu comme "un rectangle blanc" (0x784c). Plutôt que rapiécer une
   troisième fois, 0x784c a choisi l'option "sans séparation visible" :
   logo et liens flottent directement sur le dégradé, aucun fond/bordure
   à faire cohabiter avec 4 palettes différentes. `position: sticky`
   retiré avec le fond -- son seul rôle était de rester lisible par-
   dessus le contenu qui défile en dessous ; sans fond, un bandeau
   sticky se ferait chevaucher par ce contenu (illisible), donc il
   redevient un en-tête normal qui défile avec la page. */
.public-header {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding: 18px 0;
  margin: 0 0 8px;
}
.public-brand { display: inline-flex; align-items: center; gap: 8px; font-size: 1.2rem; font-weight: 700; text-decoration: none; color: var(--text); }
.public-brand .icon { color: var(--brand); }
.public-header-actions { display: flex; align-items: center; gap: 10px; }
.public-header-actions select { width: auto; }
.public-header-actions .link { margin: 0; font-size: 0.85rem; }

/* Accroche : badge "eyebrow" + gros titre en échelle fluide (clamp,
   jamais démesuré sur mobile) + halo dégradé en arrière-plan, contenu
   par overflow:hidden pour ne jamais dépasser la largeur de page (pas
   de scroll horizontal). */
.concept-hero { position: relative; overflow: hidden; text-align: center; padding: 72px 16px 48px; margin-bottom: 8px; }
.concept-hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(360px circle at 22% 12%, color-mix(in srgb, var(--brand) 24%, transparent), transparent 65%),
    radial-gradient(320px circle at 78% 6%, color-mix(in srgb, var(--brand) 16%, transparent), transparent 65%);
  filter: blur(30px);
}
/* Palette "liquid" (ADR 0068) : `--bg-image` fournit déjà tout le relief
   coloré de la page -- ce halo local en plus (pensé pour un fond --bg
   uni, ADR 0149) se superposait dessus en une tache trouble/grisâtre
   pile derrière le badge et le titre (défaut remonté par 0x784c,
   ADR 0197 v3). Coupé pour cette palette uniquement, pas touché pour
   les autres où il reste correct. */
[data-palette="liquid"] .concept-hero::before { display: none; }
.concept-eyebrow {
  display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--brand); background: color-mix(in srgb, var(--brand) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  padding: 5px 14px; border-radius: 999px; margin-bottom: 20px;
}
.concept-hero h1 { font-size: clamp(2rem, 1.3rem + 3vw, 3.1rem); line-height: 1.15; letter-spacing: -0.02em; margin: 0 0 18px; }
.concept-hero p { color: var(--text-dim); font-size: 1.08rem; line-height: 1.55; max-width: 580px; margin: 0 auto 30px; }
a.primary.concept-cta-btn, button.primary.concept-cta-btn { padding: 12px 26px; font-size: 1rem; box-shadow: 0 10px 28px color-mix(in srgb, var(--brand) 35%, transparent); }

/* Formulaire liste d'attente (ADR 0196) : remplace les deux boutons
   "Essayer mie"/"Se connecter" du hero -- le produit n'est pas ouvert à
   une inscription libre, la capture d'email devient le CTA principal.
   `.link` (déjà utilisé partout ailleurs sur le site) reste le style du
   lien "Déjà un compte ?" juste en dessous, pas de bouton secondaire
   dédié comme avant. */
.concept-waitlist-note { color: var(--text-dim); font-size: 0.85rem; margin: 0 0 14px; }
.concept-waitlist-form { display: flex; justify-content: center; align-items: stretch; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.concept-waitlist-form input[type="email"] {
  font: inherit; background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 14px; min-width: 240px; flex: 1 1 240px; max-width: 320px;
}
.concept-waitlist-form input[type="email"]:focus { outline: none; border-color: var(--brand); }
.concept-waitlist-consent { max-width: 420px; margin: 0 auto 22px; }

/* Rangée de réassurance -- trois repères courts plutôt qu'un paragraphe
   de plus, pour rassurer d'un coup d'œil un public qui ne connaît pas
   encore mie (gratuit, données supprimables, multilingue). */
.concept-trust { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; color: var(--text-dim); font-size: 0.85rem; }
.concept-trust span { display: inline-flex; align-items: center; gap: 6px; }
.concept-trust .icon { color: var(--brand); flex-shrink: 0; }

/* Emplacement de capture d'écran, façon fenêtre d'application (barre de
   titre à 3 points) -- donne du poids visuel à la place réservée même
   sans image réelle, cohérent avec le reste (.card pour le cadre
   extérieur, pointillés type .add-zone pour la zone encore vide). */
.concept-shot-frame { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card); overflow: hidden; margin-bottom: 64px; }
.concept-shot-bar { display: flex; align-items: center; gap: 6px; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.concept-shot-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--border); }
.concept-shot-body {
  margin: 16px; border: 2px dashed var(--border); border-radius: 12px;
  padding: 56px 20px; text-align: center; color: var(--text-dim);
}
.concept-shot-body .icon { margin-bottom: 8px; opacity: 0.55; }

.concept-section { padding: 56px 0; }
.concept-section-header { text-align: center; margin: 0 auto 32px; max-width: 560px; }
.concept-section-title { font-size: 1.6rem; letter-spacing: -0.01em; margin: 0 0 8px; }
.concept-section-subtitle { color: var(--text-dim); margin: 0; }

/* Panneau englobant (ADR 0149) : donne un rythme visuel entre les
   sections -- "Comment ça marche" posée sur un fond distinct (comme une
   bande de section classique), "Pour qui ?" restée sur le fond de page
   pour alterner plutôt que d'empiler des panneaux identiques. */
.concept-panel { background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--radius) + 8px); padding: 48px 32px; }

.concept-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 18px; }
.concept-grid .card { position: relative; margin-bottom: 0; overflow: hidden; transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; }
.concept-grid .card:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16); border-color: color-mix(in srgb, var(--brand) 30%, var(--border)); }
.concept-grid h3 { font-size: 1.02rem; margin: 0 0 6px; }
.concept-grid p { color: var(--text-dim); font-size: 0.88rem; line-height: 1.5; margin: 0; }

/* Badge d'icône -- pastille teintée plutôt qu'un pictogramme nu, motif
   standard de carte "feature" sur une page de présentation produit. */
.concept-icon-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: 12px; margin-bottom: 14px;
  background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand);
}
/* Grand numéro en filigrane (01, 02...) -- accent purement décoratif
   des cartes "Comment ça marche" (jamais posé sur "Pour qui ?", qui
   n'est pas une séquence d'étapes). */
.concept-step-num {
  position: absolute; top: 8px; right: 16px; font-size: 2.4rem; font-weight: 800;
  color: var(--border); line-height: 1; user-select: none; pointer-events: none;
}

.concept-cta-section {
  text-align: center; padding: 56px 32px; margin: 56px 0;
  border-radius: calc(var(--radius) + 8px);
  background:
    radial-gradient(480px circle at 50% -20%, color-mix(in srgb, var(--brand) 20%, transparent), transparent 70%),
    var(--surface);
  border: 1px solid var(--border);
}
.concept-cta-section h2 { font-size: 1.6rem; margin: 0 0 8px; }
.concept-cta-section p { color: var(--text-dim); margin: 0 0 24px; }

/* `.public-footer` (ADR 0148, généralisé ADR 0150) : partagé avec les
   pages légales -- rangée de liens optionnelle (`.public-footer-links`)
   pour relier mentions légales / confidentialité / CGU entre elles. */
.public-footer { text-align: center; color: var(--text-dim); font-size: 0.82rem; padding-top: 28px; border-top: 1px solid var(--border); }
.public-footer a { color: var(--brand); }
.public-footer-links { display: flex; justify-content: center; gap: 18px; flex-wrap: wrap; margin-bottom: 10px; }

@media (max-width: 640px) {
  .concept-hero { padding: 56px 12px 36px; }
  .concept-panel { padding: 32px 20px; }
  .concept-step-num { display: none; } /* trop proche du texte sur petit écran, retiré plutôt que chevauché */
}

/* --- Pages légales (ADR 0150, web/mentions-legales.html,
   confidentialite.html, cgu.html) : texte long à lire, pas une page de
   présentation -- typographie sobre, largeur resserrée (`.legal-page`
   ci-dessus, 760px) pour une longueur de ligne confortable, jamais de
   décor (halos/badges) qui distrairait de la lecture. --- */
.legal-content { padding-top: 32px; }
.legal-content h1 { font-size: 1.7rem; letter-spacing: -0.01em; margin: 0 0 6px; }
.legal-meta { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 12px; }
.legal-content h2 { font-size: 1.1rem; margin: 34px 0 12px; padding-top: 18px; border-top: 1px solid var(--border); }
.legal-content h2:first-of-type { margin-top: 28px; }
.legal-content p, .legal-content li { color: var(--text); line-height: 1.65; font-size: 0.94rem; }
.legal-content p { margin: 0 0 14px; }
.legal-content ul, .legal-content ol { padding-left: 22px; margin: 0 0 14px; }
.legal-content li { margin-bottom: 6px; }
.legal-content a { color: var(--brand); }
.legal-content strong { color: var(--text); }
/* Le tableau "prestataires" (confidentialite.html) ne rentre pas sous
   ~390px de large (3 colonnes de prose) -- ce wrapper le fait défiler
   seul plutôt que de laisser toute la page déborder horizontalement
   (trouvé en rendu mobile réel). */
.legal-table-scroll { overflow-x: auto; margin: 4px 0 18px; }
.legal-content table { width: 100%; border-collapse: collapse; margin: 0; font-size: 0.88rem; }
.legal-content th, .legal-content td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.legal-content th { color: var(--text-dim); font-weight: 600; }

/* Marqueur visuel des champs encore à compléter/vérifier -- volontairement
   voyant (teinte --danger) pour qu'un tel brouillon ne puisse jamais être
   confondu avec un texte final s'il est lu par erreur. */
.legal-todo { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); padding: 1px 7px; border-radius: 6px; font-weight: 600; font-size: 0.9em; white-space: normal; }

/* Bandeau "brouillon" en tête de chaque page légale -- se voit avant même
   de commencer à lire, rappelle que le texte n'est pas encore validé. */
.legal-draft-banner {
  display: flex; gap: 12px; align-items: flex-start;
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 32%, transparent);
  border-radius: var(--radius); padding: 14px 16px; margin: 24px 0 8px;
  color: var(--text); font-size: 0.88rem; line-height: 1.5;
}
.legal-draft-banner .icon { color: var(--danger); flex-shrink: 0; margin-top: 1px; }
.legal-draft-banner strong { color: var(--text); }

/* Mode "Concentration" (ADR 0164, phase 1) : une pastille par voix
   distincte, "en parole" mise en évidence par un halo pulsé (--brand,
   déjà theme-aware -- suit la palette choisie, ADR 0066) plutôt qu'une
   simple couleur pleine, pour rester lisible aussi bien en clair qu'en
   sombre. `.speaker-chip` reprend EXACTEMENT le couple --surface/--border
   de `.card` (jamais --accent : ce nom trompeur désigne en réalité un
   remplissage INVERSÉ -- foncé en thème clair, clair en thème sombre,
   voir `.msg.user` -- pas une pastille neutre ; bug de contraste réel
   trouvé en testant ce composant au navigateur avant de le considérer fini). */
.speaker-chip {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px 6px 16px; border-radius: 999px; background: var(--surface); color: var(--text);
  border: 2px solid var(--border); font-size: 0.85rem; font-weight: 600;
  transition: border-color 0.15s ease, opacity 0.15s ease;
}
.speaker-chip.speaking { border-color: var(--brand); animation: speaker-pulse 1.1s ease-in-out infinite; }
@keyframes speaker-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 55%, transparent); }
  50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--brand) 0%, transparent); }
}
/* Locuteur ignoré (ADR 0165, phase 2) : grisé (opacity, jamais display:none
   -- le pulse .speaking ci-dessus doit continuer à s'appliquer même
   ignoré, 0x784c : "que l'animation ... reste, même s'il est ignoré").
   L'opacity réduite s'applique à TOUTE la pastille (bouton micro compris)
   -- reste cliquable, juste visuellement en retrait. */
.speaker-chip.ignored { opacity: 0.5; }
.speaker-chip-mute-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%; border: none; background: transparent;
  color: inherit; cursor: pointer; padding: 0; flex-shrink: 0;
}
.speaker-chip-mute-btn:hover { background: var(--border); }
.speaker-chip-mute-btn .icon { width: 14px; height: 14px; }
/* Nommer un locuteur (ADR 0171, phase 3) : l'étiquette elle-même est
   cliquable -- pas d'icône crayon supplémentaire dans une pastille déjà
   compacte, juste un curseur pointer + un soulignement pointillé au
   survol comme seule affordance (le `title` posé en JS explicite le
   geste pour qui ne le devine pas). `.speaker-chip-name-input` reprend
   la police/taille de la pastille -- se substitue à l'étiquette en place
   (JS), jamais un champ à côté. */
.speaker-chip-label { cursor: pointer; border-bottom: 1px dotted transparent; }
.speaker-chip-label:hover, .speaker-chip-label:focus-visible { border-bottom-color: var(--text-dim); outline: none; }
.speaker-chip-name-input {
  font: inherit; font-weight: 600; color: var(--text); background: var(--bg);
  border: 1px solid var(--brand); border-radius: 6px; padding: 1px 6px; width: 110px;
}
