/* ===== Le chat — la conversation à gauche, le tableau de bord à droite =====
   Deux états dans une seule page : `est-vide` (la barre seule, centrée) puis
   `est-actif` (le rail et le tableau de bord). Les composants du tableau de bord
   reprennent ceux de la veille : chiffres en serif sur bande à filets, barres
   arrondies, histogramme à colonne creuse. Tokens --mr-* : clair et sombre. */

/* La largeur de la colonne des conversations vit sur :root — la barre du haut
   en a besoin pour aligner sa navigation sur le premier filet, et elle est hors
   de .ch. (chat.css n'est chargée que sur cette page, la portée reste locale.) */
:root { --ch-side: 252px; }

/* ===== L'ECHELLE DE TAILLES DE FLAIR ==================================
   SIX TAILLES, ET PLUS AUCUNE VALEUR ECRITE A LA MAIN. Avant ce jeu, les sept
   feuilles de Flair portaient 97 declarations de `font-size` pour 33 valeurs
   distinctes, dont des series que personne ne peut distinguer : 0,72 / 0,74 /
   0,76 / 0,78 / 0,79 / 0,80 rem, puis 0,82 / 0,83 / 0,84 / 0,85 / 0,86 / 0,87 /
   0,875 / 0,88 rem. Quatorze tailles de texte courant sur un meme ecran ne
   font pas une hierarchie : elles font du bruit, parce que l'oeil ne lit une
   difference de taille comme un rang que si elle se voit.

   Les valeurs tombent sur des pixels entiers a 16 px de base : un demi pixel de
   hauteur de police se rend differemment d'une machine a l'autre.

   LE PLANCHER EST 12 PX, et c'est la raison d'etre du premier echelon. Sept
   regles descendaient plus bas, jusqu'a 0,66 rem, soit 10,6 px : sous 12 px un
   texte se devine plus qu'il ne se lit, et les etiquettes de ce produit portent
   des chiffres, ou une lecture approximative est pire qu'une absence.

   Les libelles disent le ROLE et non la taille : passer un titre de section de
   18 a 20 px doit se faire ici, une fois, et non dans onze feuilles. */
:root {
    --ch-fs-2xs: 0.75rem;    /* 12 px — etiquettes, unites, sources, meta */
    --ch-fs-xs: 0.8125rem;   /* 13 px — texte secondaire, legendes, tableaux */
    --ch-fs-sm: 0.875rem;    /* 14 px — le texte d'interface courant */
    --ch-fs-md: 1rem;        /* 16 px — ce qui se lit vraiment : la saisie */
    --ch-fs-lg: 1.125rem;    /* 18 px — titres de bloc */
    --ch-fs-xl: 1.375rem;    /* 22 px — titres de panneau et grands chiffres */

    /* LE TROISIEME NIVEAU DE TEXTE, ET IL REMPLACE DE L'OPACITE.
       Cinq regles disaient « moins important » avec `opacity`, ce qui parait
       equivalent et ne l'est pas : l'opacite melange le texte AU FOND, donc le
       contraste tombe sans que personne l'ait choisi. Mesure sur `--ch-second`
       a 0,55 : 2,74:1 en theme clair, quand AA demande 4,5. A 0,70 : 3,86. A
       0,75 : 4,37, soit juste en dessous. Aucune de ces valeurs n'a ete decidee
       en regardant un chiffre, et c'est bien le probleme.

       Un ton pose EST un chiffre. Celui ci tient de 4,81 a 6,57 selon le fond et
       le theme, donc il passe partout, et il reste franchement plus clair que
       `--ch-second` (8,4) : la hierarchie se voit toujours, elle ne se paie
       simplement plus en lisibilite.

       Sur `:root` et non sur `.ch` : le mur de connexion est un `dialog` pose
       hors de `.ch`, il n'aurait pas vu la variable. */
    --ch-tiers: #6a6252;
}

:root[data-theme="dark"] { --ch-tiers: #a49c8d; }

.ch {
    /* Largeur du fil de conversation : réglable à la souris, retenue d'une visite
       à l'autre (localStorage). Le défaut est large — c'est la colonne qu'on lit. */
    --ch-rail: 440px;
    /* La hauteur à laquelle commence le premier élément de chaque colonne.
       Les trois en-têtes (bouton « Nouvelle conversation », titre du fil, nom
       du marché) s'y accrochent : une seule valeur à bouger pour qu'ils
       restent sur la même ligne. */
    --ch-haut: 1.2rem;
    /* Le ton du second niveau, partagé par les deux colonnes. --mr-text-muted
       est calibré pour du texte courant ; sur le micro-texte de cet écran il
       tombe sous 5:1 en thème sombre. Local à .ch : le reste de l'application
       garde exactement ses couleurs. */
    --ch-second: #4a4335;
    font-family: var(--mr-sans);
    color: var(--mr-text);
    background: var(--mr-surface);
    height: calc(100vh - var(--topbar-height, 64px));
    display: grid;
    grid-template-columns: var(--ch-side) minmax(0, 1fr);
    overflow: hidden;
}

:root[data-theme="dark"] .ch { --ch-second: #b9b1a2; }

.ch *, .ch *::before, .ch *::after { box-sizing: border-box; }

.ch-main { display: flex; flex-direction: column; min-width: 0; overflow: hidden; position: relative; }

/* ── La colonne des conversations ────────────────────────────────────── */
/* Tant qu'il n'y a rien dedans, elle n'existe pas : une colonne vide avec un
   bouton et une phrase d'excuse, c'est de la place prise pour rien. Elle
   apparaît à la première conversation, sans rechargement. */
.ch.sans-liste { grid-template-columns: minmax(0, 1fr); }
.ch.sans-liste .ch-side { display: none; }

/* Sur grand ecran la colonne est dans la grille : ni voile ni bouton. Les deux
   n'apparaissent que dans la media query plus bas. `sans-liste` les eteint
   aussi : sans conversation, il n'y a rien a ouvrir. */
.ch-side-voile, .ch-liste-bouton { display: none; }
.ch.sans-liste .ch-side-voile, .ch.sans-liste .ch-liste-bouton { display: none; }

.ch-side {
    display: flex; flex-direction: column; min-height: 0;
    border-right: 1px solid var(--mr-border);
    background: var(--mr-surface-sunken);
}

/* Le bouton porte ses propres 0,6rem de padding + 1px de bordure : on les
   retranche pour que son TEXTE tombe sur la ligne des deux autres titres. */
.ch-side-haut { padding: calc(var(--ch-haut) - 0.6rem - 1px) 0.8rem 0.6rem; }

.ch-neuf {
    display: flex; align-items: center; justify-content: center; gap: 0.5rem;
    padding: 0.6rem 0.9rem; border-radius: 999px;
    border: 1px solid var(--mr-border-strong);
    color: var(--mr-text-strong); text-decoration: none;
    font-size: var(--ch-fs-sm); font-weight: 500;
    transition: background 0.15s ease;
}
.ch-neuf:hover { background: var(--mr-surface-hover); }

.ch-side-liste { flex: 1; overflow-y: auto; padding: 0 0.5rem 1rem; }

/* Dernier libellé en mono capitales de l'écran alors que le tableau de bord
   est passé en Inter : il détonnait, et 9 px sur le ton atténué se lisaient
   mal en thème sombre. Même famille que le reste, plus grand, plus contrasté. */
.ch-side-groupe {
    display: flex; align-items: center; gap: 0.45rem;
    font-size: var(--ch-fs-xs); font-weight: 600;
    color: var(--ch-second);
    margin: 0.8rem 0 0.45rem 0.6rem;
}
.ch-side-groupe svg { flex: none; width: 14px; height: 14px; }

.ch-side-ligne {
    display: flex; align-items: center; border-radius: var(--mr-radius-sm);
    transition: background 0.15s ease, opacity 0.15s ease;
}
.ch-side-ligne:hover { background: var(--mr-surface-hover); }
.ch-side-ligne.est-actif { background: var(--mr-surface-card); }
.ch-side-ligne.est-effacee { opacity: 0.4; pointer-events: none; }

/* Retrait calé sur le texte de « Mes chats », pas sur son icône : 0,6rem de
   marge + 14px d'icône + 0,45rem d'écart font 1,9rem. Les titres démarrent
   donc exactement sous le libellé qui les annonce, au lieu de partir avant. */
.ch-side-item {
    flex: 1; min-width: 0; display: block; padding: 0.5rem 0.7rem 0.5rem 1.9rem;
    font-size: var(--ch-fs-xs); color: var(--mr-text-muted); text-decoration: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    transition: color 0.15s ease;
}
.ch-side-ligne:hover .ch-side-item { color: var(--mr-text-strong); }
.ch-side-ligne.est-actif .ch-side-item { color: var(--mr-text-strong); font-weight: 500; }

/* La corbeille n'apparaît qu'au survol : une croix permanente sur chaque ligne
   transforme la colonne en champ de mines. Au clavier, le focus la révèle aussi. */
.ch-side-suppr {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; margin-right: 0.3rem; padding: 0; opacity: 0;
    border: 0; border-radius: var(--mr-radius-sm);
    background: none; color: var(--mr-text-muted); cursor: pointer;
    transition: opacity 0.15s ease, color 0.15s ease;
}
.ch-side-ligne:hover .ch-side-suppr, .ch-side-suppr:focus-visible { opacity: 1; }
.ch-side-suppr:hover { color: var(--mr-text-strong); background: var(--mr-surface); }

/* Au doigt, il n'y a pas de survol : la corbeille reste visible. */
@media (hover: none) { .ch-side-suppr { opacity: 1; } }

/* ── La poignée entre la conversation et le tableau de bord ──────────── */
/* Elle doit se lire comme le filet qui sépare la liste du fil : un trait de
   1 px, pas une bande. Peinte sur toute sa largeur, elle formait un ruban
   délavé face à des filets nets. Les 7 px restent — invisibles — parce qu'un
   trait de 1 px est impossible à attraper à la souris. */
.ch-poignee {
    width: 7px; cursor: col-resize; flex-shrink: 0;
    background: transparent; position: relative;
}
.ch-poignee::before {
    content: '';
    position: absolute; inset: 0 3px;
    background: var(--mr-border);
    transition: background 0.15s ease;
}
.ch-poignee:hover::before,
.ch-poignee:focus-visible::before,
.ch.est-tiree .ch-poignee::before { background: var(--mr-border-strong); }
.ch.est-tiree { user-select: none; cursor: col-resize; }

/* ── État 1 : la barre seule ─────────────────────────────────────────── */
/* Centré verticalement, mais en `safe` : sur une fenêtre courte, ou si le
   contenu regrossit un jour, le centrage repasse tout seul en haut de page.
   Un `center` simple avec débordement coupe le HAUT du contenu, c'est à dire
   le titre, et rien ne permet de remonter. */
.ch-vide {
    flex: 1; display: flex; flex-direction: column;
    align-items: center; justify-content: safe center;
    /* Le bas plus généreux que le haut : dans un bloc centré, c'est ce qui
       remonte l'ensemble. Un centre géométrique paraît toujours trop bas. */
    gap: 2.4rem; padding: min(7vh, 3.5rem) 1.5rem 8rem; text-align: center;
    overflow-y: auto;
    /* `position: relative` + `z-index` viennent du décor topographique posé
       plus bas sur `.ch-main::before` : sans eux le contenu passerait dessous. */
    position: relative; z-index: 1;
}

/* Le filigrane topographique de la landing, seulement sur l'écran d'arrivée.
   Masque en vignette : les courbes vivent sur les bords et s'effacent au
   centre, là où se lisent le titre et la barre — un motif derrière du texte
   se paie toujours sur la lisibilité.
   Porté par .ch-main et non .ch-vide : celui-ci passe en display:none dès que
   la conversation démarre, ce qui ferait disparaître le décor d'un coup sec.
   Ici il s'efface, avec la durée et la courbe communes à tout l'écran.

   En `absolute` et non `fixed` : le décor doit s'arrêter au filet de la colonne
   des conversations, pas la traverser. Du même coup le centre de la vignette
   tombe sur celui de la zone de lecture — donc sur le texte, qui est centré
   dans .ch-main et non dans l'écran. Et quand la colonne n'existe pas
   (.sans-liste), le décor reprend seul toute la largeur. */
.ch-main::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 700' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%231a1712' stroke-opacity='0.13' stroke-width='1.5'%3E%3Cpath d='M0,90 C240,40 480,150 720,100 C960,50 1200,140 1440,85'/%3E%3Cpath d='M0,220 C260,160 500,280 760,220 C1020,160 1240,270 1440,210'/%3E%3Cpath d='M0,360 C240,295 520,425 790,355 C1060,290 1280,415 1440,345'/%3E%3Cpath d='M0,510 C260,440 540,575 820,500 C1100,430 1320,565 1440,490'/%3E%3Cpath d='M0,640 C260,580 540,700 820,625 C1100,555 1320,680 1440,610'/%3E%3C/g%3E%3C/svg%3E");
    background-size: 100% 100%;
    -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 45%, transparent 35%, #000 100%);
    mask-image: radial-gradient(ellipse 62% 58% at 50% 45%, transparent 35%, #000 100%);
    pointer-events: none;
    z-index: 0;
    transition: opacity var(--ch-lent, 0.3s) var(--ch-elan, ease);
}

:root[data-theme="dark"] .ch-main::before {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 700' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%23f7f2e9' stroke-opacity='0.15' stroke-width='1.5'%3E%3Cpath d='M0,90 C240,40 480,150 720,100 C960,50 1200,140 1440,85'/%3E%3Cpath d='M0,220 C260,160 500,280 760,220 C1020,160 1240,270 1440,210'/%3E%3Cpath d='M0,360 C240,295 520,425 790,355 C1060,290 1280,415 1440,345'/%3E%3Cpath d='M0,510 C260,440 540,575 820,500 C1100,430 1320,565 1440,490'/%3E%3Cpath d='M0,640 C260,580 540,700 820,625 C1100,555 1320,680 1440,610'/%3E%3C/g%3E%3C/svg%3E");
}

/* La conversation a commencé : le décor s'en va, l'écran est au travail. */
.ch.est-actif .ch-main::before { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
    .ch-main::before { transition: none; }
}

.ch.est-actif .ch-vide { display: none; }
.ch:not(.est-actif) .ch-travail { display: none; }

.ch-titre {
    font-family: var(--mr-serif); font-size: clamp(2.2rem, 5.5vw, 3.6rem);
    font-weight: 500; line-height: 1.05; margin: 0; color: var(--mr-text-strong);
}

.ch-sous { font-size: var(--ch-fs-md); color: var(--mr-text-muted); margin: 0; max-width: 46ch; }

/* Trois pastilles sur la première rangée, la quatrième dessous et centrée sur
   l'axe de la page, comme le titre et le champ. 38rem laisse tenir les trois
   (596 px) sans marge morte, ce qui garde la répartition 3 + 1. */
.ch-exemples {
    display: flex; flex-wrap: wrap; gap: 0.5rem;
    justify-content: center; align-items: center;
    max-width: 38rem;
}

.ch-exemple {
    font: inherit; font-size: var(--ch-fs-xs); padding: 0.45rem 0.9rem;
    border: 1px solid var(--mr-border); border-radius: 999px;
    background: var(--mr-surface-card); color: var(--mr-text-muted); cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease;
}

.ch-exemple:hover { color: var(--mr-text-strong); border-color: var(--mr-border-strong); }

/* ── La barre de saisie (les deux états) ─────────────────────────────── */
.ch-saisie {
    display: flex; align-items: flex-end; gap: 0.6rem; width: 100%;
    padding: 0.7rem 0.7rem 0.7rem 1.1rem;
    background: var(--mr-surface-card);
    border: 1px solid var(--mr-border-strong); border-radius: 22px;
}

.ch-vide .ch-saisie { max-width: 40rem; border-radius: 26px; }

.ch-saisie textarea {
    flex: 1; font: inherit; font-size: var(--ch-fs-md); line-height: 1.5;
    color: var(--mr-text); background: transparent; border: 0; outline: none;
    resize: none; max-height: 8.5rem; padding: 0.35rem 0;
}

.ch-saisie textarea::placeholder { color: var(--mr-text-muted); }

.ch-envoyer {
    flex-shrink: 0; width: 36px; height: 36px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%; cursor: pointer;
    background: var(--mr-btn-bg); color: var(--mr-btn-text);
    transition: opacity 0.15s ease;
}

.ch-envoyer:disabled { opacity: 0.35; cursor: default; }

/* ── Le « ? » du champ : l'explication, pour qui la cherche ───────────── */
/* Même gabarit que le bouton d'envoi (36 px, rond) mais en creux : c'est une
   aide, elle ne doit pas rivaliser avec l'action. */
.ch-aide { position: relative; flex-shrink: 0; display: flex; }

.ch-aide-bouton {
    width: 36px; height: 36px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--mr-border); border-radius: 50%; cursor: pointer;
    background: transparent; color: var(--ch-second);
    font: inherit; font-size: var(--ch-fs-sm); font-weight: 600; line-height: 1;
    transition: color 0.15s ease, border-color 0.15s ease;
}
.ch-aide-bouton:hover { color: var(--mr-text-strong); border-color: var(--mr-border-strong); }

.ch-aide-bulle {
    position: absolute; bottom: calc(100% + 0.6rem); right: 0;
    width: max-content; max-width: 21rem;
    padding: 0.7rem 0.85rem; border-radius: 12px;
    border: 1px solid var(--mr-border);
    background: var(--mr-surface-card); color: var(--mr-text);
    font-size: var(--ch-fs-xs); line-height: 1.55; text-align: left;
    box-shadow: var(--mr-shadow, 0 10px 30px rgba(0,0,0,0.12));
    opacity: 0; visibility: hidden; transform: translateY(4px);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
    z-index: 3;
}

/* :focus-within couvre le clavier ET le tactile, où il n'y a pas de survol. */
.ch-aide:hover .ch-aide-bulle,
.ch-aide:focus-within .ch-aide-bulle {
    opacity: 1; visibility: visible; transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .ch-aide-bulle { transition: none; }
}

/* La bulle est ancree au bouton « ? », qui vit pres du bord droit de la barre.
   A 375px sa largeur de 21rem la faisait demarrer a -42px, donc coupee a
   gauche. On la rattache a la barre de saisie et on l'etale dessus : c'est la
   zone lisible, et aucun nombre magique ne depend de la position du bouton. */
@media (max-width: 600px) {
    .ch-saisie { position: relative; }
    .ch-aide { position: static; }
    .ch-aide-bulle { left: 0; right: 0; width: auto; max-width: none; }
}

/* ── État 2 : rail + tableau de bord ─────────────────────────────────── */
.ch-travail {
    flex: 1; display: grid; min-height: 0;
    grid-template-columns: var(--ch-rail) auto minmax(0, 1fr);
}

.ch-rail {
    display: flex; flex-direction: column; min-height: 0;
    background: var(--mr-surface-sunken);
}

.ch-rail-haut {
    display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
    padding: var(--ch-haut) 1rem 0.8rem;
    border-bottom: 1px solid var(--mr-border);
}

.ch-rail-titre {
    font-size: var(--ch-fs-sm); font-weight: 500; color: var(--mr-text-strong);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.ch-rail-actions { display: flex; gap: 0.35rem; flex-shrink: 0; }

.ch-fil { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.6rem; }

.ch-bulle {
    font-size: var(--ch-fs-sm); line-height: 1.6; padding: 0.7rem 0.9rem;
    border: 1px solid var(--mr-border); border-radius: 14px;
    background: var(--mr-surface-card); color: var(--mr-text);
    overflow-wrap: anywhere;
}

.ch-bulle--moi {
    align-self: flex-end; max-width: 92%;
    background: var(--mr-btn-bg); color: var(--mr-btn-text); border-color: transparent;
}

/* Le journal de travail : ce qui a été mesuré, coché au fur et à mesure.
   Il portait aussi un raisonnement écrit par le modèle ; celui-ci a été retiré,
   il doublait la réponse et se retrouvait seul à l'écran quand elle restait
   vide. Reste ce qui ne coûte pas un jeton et qu'aucune phrase ne remplace.

   Il se pose SOUS le message : on lit ce qui est dit, la mécanique vient après.
   D'où le liseré à gauche gardé mais adouci, et un décalage qui le rattache
   visuellement à la bulle du dessus au lieu d'en faire un bloc de plus. */
.ch-pensee {
    border-left: 2px solid var(--mr-border);
    padding: 0.15rem 0 0.15rem 0.8rem;
    margin: -0.15rem 0 0.35rem 0.9rem;
    font-size: var(--ch-fs-xs);
    line-height: 1.55;
    color: var(--mr-text-muted);
    animation: ch-journal 0.34s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Il ARRIVE, il ne clignote pas : le liseré se dessine de haut en bas pendant
   que le bloc glisse. C'est la seule animation de l'écran qui dise « quelque
   chose tourne » sans occuper de place. */
@keyframes ch-journal {
    from { opacity: 0; transform: translateY(-4px); clip-path: inset(0 0 100% 0); }
    to   { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }
}

@media (prefers-reduced-motion: reduce) {
    .ch-pensee { animation: none; }
}

/* La pastille qui bat tant que la mesure tourne, et s'éteint quand elle finit. */
.ch-pensee.est-active .ch-pensee-nom::after {
    content: '';
    display: inline-block;
    width: 5px; height: 5px;
    margin-left: 0.4rem;
    vertical-align: 0.1em;
    border-radius: 50%;
    background: currentColor;
    animation: ch-battement 1.1s ease-in-out infinite;
}

/* Dernier libellé en mono capitales de l'écran : aligné sur le reste, qui est
   passé en Inter. Le ton partagé le rend aussi lisible en thème sombre. */
.ch-pensee-titre {
    display: flex; align-items: center; gap: 0.4rem;
    font-size: var(--ch-fs-2xs); font-weight: 500;
    color: var(--ch-second); cursor: pointer;
    background: none; border: 0; padding: 0.15rem 0;
}

.ch-pensee-titre:hover { color: var(--mr-text); }

/* Le chevron porte seul l'ouverture : « voir le détail » disait en cinq mots ce
   qu'une flèche dit d'un coup d'œil, et allongeait une ligne déjà chargée du
   récapitulatif. Tout le titre reste cliquable, la flèche n'est que le repère. */
.ch-pensee-titre::before {
    content: '▾';
    font-size: 1.05em; line-height: 1;
    transition: transform 0.15s ease;
}
.ch-pensee.est-repliee .ch-pensee-titre::before { transform: rotate(-90deg); }
.ch-pensee.est-repliee .ch-pensee-corps { display: none; }
.ch-pensee-corps { padding-top: 0.25rem; }
.ch-pensee-corps p { margin: 0 0 0.4rem; }
.ch-pensee-corps p:last-child { margin-bottom: 0; }
.ch-pensee-corps strong { font-weight: 600; color: var(--mr-text); }
.ch-pensee-recap { margin-left: 0.15rem; color: var(--ch-tiers); }
.ch-pensee.est-active .ch-pensee-titre { color: var(--mr-text); }

/* Le journal des étapes : elles s'inscrivent et RESTENT. Une étape en cours porte
   une puce qui bat, une étape faite une coche et son temps — la trace de ce qui a
   réellement été fait, plutôt qu'un spinner qui ne dit rien puis s'efface. */
.ch-etapes { list-style: none; margin: 0.45rem 0 0; padding: 0; }

/* Même bloc que .ch-pensee-titre, donc même famille : le titre en Inter et ses
   étapes en mono se lisaient comme deux composants sans rapport. */
.ch-etape {
    display: flex; align-items: baseline; gap: 0.45rem; padding: 0.11rem 0;
    font-size: var(--ch-fs-2xs);
    color: var(--ch-second);
    font-variant-numeric: tabular-nums;
    animation: ch-venue 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.ch-etape-puce {
    flex: none; width: 0.42rem; height: 0.42rem;
    border: 1px solid currentColor; border-radius: 50%;
}
.ch-etape.est-en-cours { color: var(--mr-text); }
.ch-etape.est-en-cours .ch-etape-puce {
    background: currentColor; animation: ch-battement 1.1s ease-in-out infinite;
}
.ch-etape.est-faite .ch-etape-puce { border: 0; width: auto; height: auto; border-radius: 0; }
.ch-etape.est-faite .ch-etape-puce::before { content: '✓'; font-size: var(--ch-fs-xs); }
.ch-etape.est-faite .ch-etape-nom { color: var(--ch-tiers); }
/* Ni `em` ni `opacite` : la duree est du texte, elle prend un echelon de
   l'echelle et un ton mesure comme le reste. En `0.92em` elle tombait a 11 px,
   sous le plancher, et `0.55` la posait a 2,74:1. */
.ch-etape-duree { margin-left: auto; color: var(--ch-tiers); font-size: var(--ch-fs-2xs); }

@keyframes ch-battement {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.25; }
}

/* Seul rescapé du mono, et volontairement : c'est une trace de ce que la
   machine a lancé, pas du texte à lire — la chasse fixe le dit d'un coup d'œil.
   Il prend en revanche le ton lisible comme le reste. */
.ch-bulle--outil {
    font-family: var(--mr-mono); font-size: var(--ch-fs-2xs); letter-spacing: 0.02em;
    background: transparent; border-style: dashed; color: var(--ch-second);
    display: flex; align-items: center; gap: 0.5rem;
}

/* L'échec, et sa sortie.
   Pas de rouge : rien n'est cassé du côté de la personne, et une bulle alarmante
   au milieu d'une conversation se lit comme une faute qu'elle aurait commise.
   Un liseré marqué suffit à la distinguer d'une réponse. Ce qui compte ici,
   c'est le bouton — un message d'erreur sans issue laisse devant un choix qu'on
   ne sait pas faire. */
.ch-bulle--souci {
    border-style: dashed;
    border-color: var(--mr-border-strong);
    background: var(--mr-surface-sunken);
}
.ch-bulle--souci p { margin: 0; }

.ch-reessayer {
    margin-top: 0.6rem;
    /* 44 px de haut : la cible tactile minimale. C'est sur telephone que la
       connexion lache, donc c'est la que ce bouton sert. */
    min-height: 44px; padding: 0.5rem 1rem;
    font-family: inherit; font-size: var(--ch-fs-xs); cursor: pointer;
    color: var(--mr-btn-text); background: var(--mr-btn-bg);
    border: 1px solid var(--mr-btn-bg); border-radius: 999px;
}
.ch-reessayer:hover { opacity: 0.88; }

/* Le modèle envoie par paquets de ~100 caractères ; le navigateur les écoule
   lettre par lettre. L'apparition de la bulle est adoucie pour que le passage
   « je réfléchis… » → texte ne clignote pas. */
.ch-bulle { animation: ch-venue 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }

@keyframes ch-venue {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

/* Le curseur ne vit que pendant l'écriture, collé au dernier paragraphe. */
.ch-bulle.est-ecrit > *:last-child::after,
.ch-pensee-texte.est-ecrit > *:last-child::after {
    content: '';
    display: inline-block;
    width: 0.45em; height: 1em;
    margin-left: 0.12em;
    vertical-align: -0.15em;
    background: currentColor;
    opacity: 0.55;
    animation: ch-curseur 1s steps(2, start) infinite;
}

@keyframes ch-curseur { 0%, 100% { opacity: 0.55; } 50% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
    .ch-bulle { animation: none; }
    .ch-bulle.est-ecrit > *:last-child::after,
.ch-pensee-texte.est-ecrit > *:last-child::after { animation: none; opacity: 0.4; }
}

.ch-bulle p { margin: 0 0 0.6rem; }
.ch-bulle p:last-child { margin-bottom: 0; }
.ch-bulle ul, .ch-bulle ol { margin: 0 0 0.6rem 1.1rem; padding: 0; }
.ch-bulle strong { font-weight: 600; }
.ch-bulle a { color: inherit; }

.ch-point {
    width: 6px; height: 6px; border-radius: 50%;
    background: currentColor; flex-shrink: 0;
    animation: ch-battement 1.1s ease-in-out infinite;
}

@keyframes ch-battement { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ch-point { animation: none; opacity: 0.6; } }

.ch-rail-bas { padding: 0.8rem 1rem 1rem; border-top: 1px solid var(--mr-border); }

/* Le bouton de depliage n'existe que sous 1100px, ou le tableau de bord est
   replie. Au-dessus, la colonne de droite est en permanence a l'ecran et un
   bouton pour l'ouvrir n'aurait aucun sens. */
.ch-bascule { display: none; }

/* ── La boîte de confirmation ────────────────────────────────────────── */
/* `confirm()` sortait du produit : titre « localhost:5000 », boutons système.
   Ici tout reste dans la même langue visuelle — et `<dialog>` donne Échap, le
   piège à focus et le fond inerte sans une ligne de JavaScript. */
.ch-modale {
    width: min(30rem, calc(100vw - 2.5rem));
    padding: 1.4rem 1.5rem 1.2rem;
    border: 1px solid var(--mr-border-strong);
    border-radius: var(--mr-radius, 20px);
    background: var(--mr-surface-card); color: var(--mr-text);
    font-family: var(--mr-sans);
}
.ch-modale::backdrop { background: rgba(0, 0, 0, 0.42); }

.ch-modale-titre {
    font-family: var(--mr-serif); font-size: var(--ch-fs-xl); font-weight: 500;
    line-height: 1.25; margin: 0 0 0.5rem; color: var(--mr-text-strong);
}
.ch-modale-texte { font-size: var(--ch-fs-sm); line-height: 1.6; margin: 0 0 1.2rem; color: var(--mr-text-muted); }
.ch-modale-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }

.ch-modale-actions button {
    font: inherit; font-size: var(--ch-fs-sm); padding: 0.5rem 1.1rem;
    border-radius: 999px; cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.ch-modale-annuler {
    border: 1px solid var(--mr-border-strong);
    background: transparent; color: var(--mr-text);
}
.ch-modale-annuler:hover { background: var(--mr-surface-hover); }
.ch-modale-ok { border: 0; background: var(--mr-btn-bg); color: var(--mr-btn-text); }
.ch-modale-ok:hover { opacity: 0.88; }

/* La phrase dite avant la recherche : dans le journal, pas en bulle a part.
   Un tour = un seul message a lire. */
.ch-pensee-annonce {
    margin: 0.5rem 0 0; padding-top: 0.5rem;
    border-top: 1px solid var(--mr-border);
    font-size: var(--ch-fs-xs); line-height: 1.5; color: var(--mr-text);
}

/* Léa travaille : le champ le dit, ET il se ferme. Griser le bouton seul ne se
   voit pas sur un écran sombre — on tapait dans le vide, ou on attendait alors
   que la main était rendue. La bordure bat doucement pour dire que ça avance ;
   le champ éteint dit que ce n'est pas encore le tour de la personne. */
.ch-saisie.est-occupe { border-color: var(--mr-accent, var(--mr-border-strong)); }
.ch-saisie.est-occupe textarea::placeholder { opacity: 0.85; }
/* Assez pâle pour se lire comme « pas maintenant », assez lisible pour qu'on
   voie encore ce qu'on avait commencé à taper. */
.ch-saisie textarea:disabled { opacity: 0.55; cursor: default; background: transparent; }
@media (prefers-reduced-motion: no-preference) {
    .ch-saisie.est-occupe { animation: ch-attente-saisie 1.8s ease-in-out infinite; }
    @keyframes ch-attente-saisie {
        0%, 100% { border-color: var(--mr-border-strong); }
        50%      { border-color: var(--mr-text-muted); }
    }
}

/* ── La navigation de la barre du haut cale sur le premier filet ──────── */
/* Elle chevauchait la colonne des conversations : le logo et les onglets se
   lisaient comme un seul bloc, alors qu'ils appartiennent à deux colonnes.
   Le logo occupe désormais la largeur de la colonne, la navigation commence
   exactement au filet. La mesure se déduit de --ch-side : si la colonne
   change de largeur, la navigation suit.
   Au-delà de 1150 px seulement — en dessous la colonne devient un bandeau
   horizontal et il n'y a plus de filet vertical sur quoi s'aligner. */
@media (min-width: 1151px) {
    body[data-page="chat"] .app-topbar { gap: 0; }
    body[data-page="chat"] .topbar-logo {
        width: calc(var(--ch-side) - 24px);
        box-sizing: border-box;
    }
    body[data-page="chat"] .topbar-nav { margin-left: 0; }
}

/* ══════════ Le chiffre dont on parle ══════════
   L'assistant cite « 27 100 recherches/mois » et le tableau en affiche six
   autres : le rapprochement entre la phrase et la valeur restait à la charge
   de la personne, alors que c'est le travail qu'elle délègue. Le serveur relève
   les nombres cités, l'écran les pointe.

   TEMPORAIRE, et c'est le point : le surlignage attire l'œil une fois puis
   s'efface. Laissé en place, il deviendrait une marque de plus à interpréter,
   et on ne saurait plus lequel des six chiffres surlignés compte aujourd'hui. */
.est-pointe {
    position: relative;
    animation: ch-pointe 4.2s ease-out both;
    border-radius: 5px;
}

@keyframes ch-pointe {
    0%   { background: transparent; box-shadow: 0 0 0 0 var(--ch-marque-fond); }
    12%  { background: var(--ch-marque-fond); box-shadow: 0 0 0 4px var(--ch-marque-fond); }
    75%  { background: var(--ch-marque-fond); box-shadow: 0 0 0 4px var(--ch-marque-fond); }
    100% { background: transparent; box-shadow: 0 0 0 0 transparent; }
}

/* L'onglet vers lequel on vient de basculer : un trait qui pulse une fois, pour
   que le changement se remarque au lieu de se subir. */
@media (prefers-reduced-motion: reduce) {
    .est-pointe { animation: none; background: var(--ch-marque-fond); }
}

/* ══════════ Le renvoi vers le tableau, sur écran étroit ══════════
   Là, le tableau recouvre la conversation. L'ouvrir tout seul à chaque réponse
   poserait un panneau par-dessus le texte qu'on est en train de lire — c'est
   le genre d'aide qui dérange. On pose un bouton sous la réponse ; l'onglet est
   déjà le bon quand on arrive, et les chiffres cités s'y soulignent. */
.ch-renvoi {
    align-self: flex-start;
    display: inline-flex; align-items: center; gap: 0.4rem;
    margin: -0.15rem 0 0.2rem;
    padding: 0.42rem 0.8rem;
    font: inherit; font-size: var(--ch-fs-xs);
    color: var(--mr-text);
    background: var(--mr-surface-card);
    border: 1px solid var(--mr-border);
    border-radius: 999px;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}

.ch-renvoi b { font-weight: 600; color: var(--mr-text-strong); }
.ch-renvoi svg { opacity: 0.55; }

.ch-renvoi:hover {
    border-color: var(--mr-border-strong);
    background: var(--mr-surface-hover);
    transform: translateX(2px);
}

.ch-renvoi:focus-visible { outline: 2px solid var(--mr-text-strong); outline-offset: 2px; }

/* 44 px au doigt : ce bouton n'existe QUE sur les écrans où l'on touche. */
@media (max-width: 1100px) {
    .ch-renvoi { min-height: 44px; padding: 0.55rem 0.95rem; font-size: var(--ch-fs-sm); }
}

@media (prefers-reduced-motion: reduce) {
    .ch-renvoi { transition: none; }
}

/* ── Replier la colonne des conversations ────────────────────────────────
   Le bouton vit dans la rangee du bouton d'aide (chat-repli.js) : c'est le seul
   coin qui flotte deja au-dessus de l'app sans appartenir a une colonne, et une
   barre d'outils pour un seul bouton n'en vaut pas la peine.

   Il reprend exactement le dessin de son voisin, `.mr-aide-bouton` : deux
   boutons colles qui ne se ressembleraient pas se liraient comme deux choses
   sans rapport. Carre parce qu'il ne porte qu'une icone. */
.ch-repli {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex: none;
    padding: 0;
    border: 1px solid var(--mr-border);
    border-radius: 999px;
    background: var(--mr-surface-card);
    color: var(--mr-text-muted);
    cursor: pointer;
    transition: color .15s ease, border-color .15s ease, background .15s ease;
}

.ch-repli:hover {
    color: var(--mr-text-strong);
    border-color: var(--mr-border-strong);
    background: var(--mr-surface-hover);
}

/* Replie, le bouton reste ALLUME : c'est le seul rappel que la colonne existe
   encore. Sans lui, une personne qui a replie hier rouvre le chat aujourd'hui
   et croit ses conversations perdues. */
.ch-repli.est-actif {
    color: var(--mr-text-strong);
    border-color: var(--mr-border-strong);
    background: var(--mr-surface-sunken);
}

/* LA COLONNE GLISSE, ELLE NE SE COMPRIME PAS. Premiere version : la piste de
   grille passait de 252 px a 0 et c'est tout. Le texte etait toujours DANS la
   colonne pendant qu'elle retrecissait, donc les titres se remettaient en page
   a chaque image — ils s'ecrasaient, se coupaient, passaient a la ligne. Une
   largeur qu'on anime sur du texte, c'est du texte qui bouge.

   La reponse etait deja dans cette feuille : sous 1150 px la colonne est un
   tiroir qui sort en `translateX(-100%)`. Une translation ne touche pas a la
   mise en page, donc rien ne se recompose : le panneau part, il ne se plie pas.
   Le grand ecran fait desormais pareil.

   Les deux animations doivent partager duree ET courbe, sinon la colonne quitte
   l'ecran avant que le fil ait pris sa place, et on voit le fond entre les deux.

   Une largeur FIXE sur la colonne : sans elle, elle vaudrait 0 comme sa piste et
   son contenu se remettrait en page malgre la translation. Elle deborde alors
   de sa piste, et c'est `overflow: hidden` sur `.ch` qui la coupe. */
@media (min-width: 1151px) {
    .ch { transition: grid-template-columns .25s var(--mr-ease); }
    .ch-side {
        width: var(--ch-side);
        transition: transform .25s var(--mr-ease);
    }
    .ch.est-repliee { grid-template-columns: 0 minmax(0, 1fr); }
    .ch.est-repliee .ch-side { transform: translateX(-100%); }
}

/* LA PLACE DE LA RANGEE DU BAS SE REND EN HAUTEUR, ET SUR LA COLONNE ENTIERE.
   « Une idee ? Un bug ? » et le repli sont fixes au coin bas gauche de
   l'ECRAN, pas de la colonne. Liste ouverte, ils flottent sur le bas vide de
   celle-ci ; repliee, la colonne descend jusqu'au bord et lui passe dessus :
   deux zones cliquables au meme endroit, dont celle du dessus n'est pas celle
   qu'on vise.

   EN HAUTEUR ET NON EN LARGEUR : la rangee fait 206 px a cause du TEXTE de son
   bouton, donc de la langue et de ce que quelqu'un y ajoutera. Sa hauteur, 32
   px sommet a 46 px du bas, ne bouge pas. On degage 56 px, soit 10 px d'ecart.

   SUR `.ch-rail` ET NON SUR LA BARRE DE SAISIE : ce n'est pas toujours elle qui
   touche le bas. Des qu'une question est posee, `chat-question.css` replie la
   barre a zero et c'est la CARTE qui descend au bord, avec son champ libre et
   son bouton d'envoi par-dessus la rangee. Premiere version faite sur la barre
   seule : elle laissait passer exactement le cas de la capture. Reserver la
   place sur la colonne vaut pour ce qui s'y trouve en bas aujourd'hui et pour
   ce qu'on y mettra. Le fil est en `flex: 1` : c'est lui qui rend la hauteur,
   rien d'autre ne bouge. */
@media (min-width: 1151px) {
    .ch.est-repliee .ch-rail { padding-bottom: 3.5rem; }
}
