/*
 * Le projet et son assistant, sur une seule page.
 *
 * La forme est celle d'une conversation : ce qu'on a écrit se détache à droite, ce que
 * l'assistant répond occupe la colonne sans cadre — un texte qu'on lit, pas une réplique dans
 * une bulle. Une pastille annonce ses réponses et tient la marge sur laquelle elles s'alignent.
 * La barre de saisie reste sous la main, le fil défile derrière elle.
 *
 * Rien n'est écrit en dur : chaque couleur, chaque espacement et chaque rayon vient d'un alias
 * du design system, ce qui fait suivre les cinq palettes sans une règle de plus.
 *
 * **Ce fichier ne dessine plus la page, seulement ce qu'elle a de propre.** Les deux colonnes,
 * la barre d'onglets, les panneaux, le cadre de droite et les champs qui se corrigent là où ils
 * se lisent vivent dans `sheet.css` : la fiche d'un papier s'en sert aussi. Ce qui reste ici
 * demande un projet pour avoir un sens — la conversation, le fil, les pièces jointes, les
 * mémoires, l'issue et la barre d'étapes.
 */


/* ═══ Ce que le résumé porte de propre au projet ═══════════════════════════════════════════ */


/*
 * Ce que l'assistant retient de ce projet. Une phrase par ligne, et la croix qui l'oublie —
 * elle ne s'allume qu'au survol de sa ligne : c'est un geste sans retour, et une croix offerte
 * en permanence sous chaque souvenir se lit comme une invitation.
 */
.summary-memories {
    display: flex;
    flex-direction: column;
    gap: var(--app-space-xs);
    margin: 0;
    padding: 0;
    list-style: none;
}

.summary-memory {
    display: flex;
    align-items: start;
    gap: var(--app-space-xs);
    padding: var(--app-space-xs) var(--app-space-2xs);
    border-radius: var(--app-radius-ui);
    font-size: var(--app-text-sm);
    line-height: 1.5;
}

.summary-memory:hover {
    background: var(--app-hover);
}

.summary-memory-fact {
    flex: 1 1 auto;
    min-width: 0;
    /* Une adresse ou un jeton sans espace déborderait de la colonne plutôt que d'y revenir. */
    overflow-wrap: anywhere;
}

.summary-forget {
    display: inline-flex;
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    color: var(--app-ink-muted);
    cursor: pointer;
    opacity: 0;
}

.summary-memory:hover .summary-forget,
.summary-forget:focus-visible {
    opacity: 1;
}

.summary-forget:hover {
    color: var(--app-danger, var(--app-ink));
}

/* Ce qui explique plutôt que ce qui se lit : l'état vide, et le renvoi vers les mémoires qui ne
   parlent pas de ce projet. */
.summary-note {
    margin: var(--app-space-xs) 0 0;
    font-size: var(--app-text-sm);
    line-height: 1.5;
    color: var(--app-ink-muted);
}


/*
 * Le fil défile pour lui-même, avec son fondu haut et bas : deux zones de défilement l'une dans
 * l'autre feraient buter la molette sur un bord invisible. Le panneau ne garde donc que le
 * retrait latéral, et le fil prend la hauteur.
 */
.sheet-pane:has(.chat-thread) {
    padding: 0 var(--app-space-lg);
    overflow: hidden;
}

/* Le tiroir s'aère : ses pastilles ne sont pas des lignes de liste, et se serrer les rendrait
   illisibles. */
.sheet-pane .attachment-list {
    gap: var(--app-space-sm);
}


/*
 * La boîte de dialogue des réglages. Le navigateur pose le fond et la centre lui-même ; il ne
 * reste qu'à lui donner l'allure d'une carte et une largeur de lecture.
 */
.settings-dialog {
    max-width: 32rem;
    border: none;
    border-radius: var(--app-radius);
    padding: var(--app-space-lg);
    background: var(--app-surface);
    color: var(--app-ink);
    box-shadow: var(--app-shadow-pop);
}

/* Le voile est celui du paquet, et non un noir écrit ici : le tiroir du menu latéral pose le
   même, et une palette qui l'éclaircit les emporte tous les deux. */
.settings-dialog::backdrop {
    background: var(--app-backdrop);
}

/*
 * Ce que la conversation ajoute au cadre de droite — la charpente dessine le cadre lui-même.
 *
 * Une teinte voisine de l'accent, obtenue en tournant sa nuance plutôt qu'en inventant une
 * couleur. Le liseré de la barre de saisie et le bouton d'envoi y prennent leur dégradé, et
 * suivent les palettes comme le reste.
 *
 * Une palette qui veut choisir cette seconde teinte plutôt que de la laisser calculer pose
 * `--app-accent-far` — et `--app-accent-far-hover` avec elle : c'est alors la sienne qui sert,
 * sans qu'une règle d'ici ne bouge. Sans cela, la rotation reste la réponse.
 */
.sheet-content {
    --chat-accent-far: var(--app-accent-far, oklch(from var(--app-accent) l c calc(h + 40)));
    --chat-accent-far-hover: var(--app-accent-far-hover, oklch(from var(--app-accent-hover) l c calc(h + 40)));

    /* La distance sur laquelle le fil s'efface en haut et en bas de son panneau. Elle sert deux
       fois : au fondu, et à la marge intérieure qui empêche la dernière ligne d'y entrer. */
    --chat-fade: var(--app-space-lg);
}

.chat-thread {
    flex: 1 1 auto;
    /* Sans cette ligne, un enfant flexible refuse de rétrécir sous sa hauteur naturelle, et
       c'est la page qui défile au lieu du fil. */
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--app-space-lg);
    /* Les marges hautes et basses valent le fondu : la première et la dernière ligne s'arrêtent
       là où il commence, et restent donc pleines quand le fil est au bout de sa course. */
    padding: var(--chat-fade) var(--app-space-xs) var(--chat-fade) 0;
    /* La barre de défilement se fait discrète : elle traverse une colonne de lecture. */
    scrollbar-width: thin;
    scrollbar-color: var(--app-line-strong) transparent;
    /* Ce qui sort du cadre s'efface au lieu d'être tranché : le fil passe alors derrière le
       titre et sous la barre de saisie, au lieu de s'arrêter à un bord invisible. */
    mask-image: linear-gradient(
        to bottom,
        transparent 0,
        #000 var(--chat-fade),
        #000 calc(100% - var(--chat-fade)),
        transparent 100%
    );
}

/*
 * Une conversation courte se pose au bas du fil, comme partout ailleurs — un échange qui
 * commence en haut d'une grande page vide se lit comme un document, pas comme une conversation.
 * Cette marge fait le travail de `justify-content: flex-end`, qui rendrait le haut du fil
 * inatteignable dès que la conversation dépasse la hauteur visible.
 */
.chat-thread > :first-child {
    margin-top: auto;
}

/*
 * Deux colonnes : la pastille, puis ce qui est dit. La seconde absorbe la largeur restante, ce
 * qui pose toutes les réponses sur la même marge quelle que soit leur longueur.
 */
.chat-turn {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--app-space-md);
    align-items: start;
}

/* Ce qu'on a écrit soi-même n'a pas de pastille : une seule colonne, poussée contre le bord
   droit, où elle rejoint la fin des lignes de l'assistant. */
.chat-turn--mine {
    grid-template-columns: minmax(0, 1fr);
    justify-items: end;
}

.chat-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--app-radius-pill);
    background: var(--app-accent-soft);
    color: var(--app-accent);
    font-size: var(--app-text);
}

.chat-bubble {
    line-height: 1.6;
    /* Une adresse ou un jeton sans espace déborderait de la colonne plutôt que d'y revenir. */
    overflow-wrap: anywhere;
}

/*
 * Ce qu'on a écrit soi-même prend la forme d'une réplique : un fond, un coin rentrant du côté
 * d'où elle vient. Ce que l'assistant répond n'en prend pas — un paragraphe encadré se lit mal
 * dès qu'il dépasse trois lignes, et les réponses en font souvent trente.
 */
.chat-turn--mine .chat-bubble {
    max-width: 85%;
    padding: var(--app-space-sm) var(--app-space-md);
    border-radius: var(--app-radius);
    border-bottom-right-radius: var(--app-radius-ui);
    background: var(--app-accent-soft);
    color: var(--app-accent-ink);
}

.chat-bubble > :first-child {
    margin-top: 0;
}

.chat-bubble > :last-child {
    margin-bottom: 0;
}

.chat-bubble[data-failed] {
    color: var(--app-ink-muted);
    font-style: italic;
}

/*
 * Une réponse arrive en Markdown : paragraphes, titres, listes, code, citations, tableaux. Le
 * socle pose ces balises sans marge, ce qui convient à une interface mais colle un texte suivi.
 * Ce qui suit lui rend un rythme de lecture, à l'intérieur de la bulle seulement.
 */
.chat-bubble > * + * {
    margin-top: var(--app-space-md);
}

.chat-bubble :is(h1, h2, h3, h4) {
    font-size: var(--app-text-lg);
}

/* Un titre appelle ce qu'il annonce : l'écart au-dessus reste, celui du dessous se resserre. */
.chat-bubble > :is(h1, h2, h3, h4) + * {
    margin-top: var(--app-space-xs);
}

.chat-bubble :is(ul, ol) {
    margin: 0;
    /* De quoi loger la puce, et rien de plus : le retrait du navigateur décalerait la liste
       loin de la marge où le reste de la réponse s'aligne. */
    padding-left: 1.25em;
}

.chat-bubble li + li {
    margin-top: var(--app-space-2xs);
}

.chat-bubble pre {
    margin: 0;
    padding: var(--app-space-md);
    border-radius: var(--app-radius-ui);
    background: var(--app-raised);
    overflow-x: auto;
}

/* Dans un bloc, le fond et le rayon du code en ligne feraient double emploi. */
.chat-bubble pre code {
    padding: 0;
    background: none;
}

.chat-bubble blockquote {
    margin: 0;
    padding-left: var(--app-space-md);
    border-left: 2px solid var(--app-line-strong);
    color: var(--app-ink-soft);
}

/* Un tableau plus large que la colonne défile pour lui-même, au lieu d'élargir la conversation. */
.chat-bubble table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
    font-size: var(--app-text-sm);
}

.chat-bubble :is(th, td) {
    padding: var(--app-space-xs) var(--app-space-sm);
    border-bottom: 1px solid var(--app-line);
    text-align: left;
    vertical-align: top;
}

/*
    L'attente : trois points qui respirent, et à côté ce que l'assistant fait et depuis combien
    de temps. Posée sous la bulle, qui est réécrite à chaque fragment de réponse et emporterait
    tout ce qu'on y aurait glissé.
*/
.chat-activity {
    display: flex;
    align-items: baseline;
    gap: var(--app-space-2xs, 0.25rem);
    color: var(--app-ink-muted);
    font-size: var(--app-text-sm);
}

.chat-activity-dots {
    letter-spacing: 0.2em;
    animation: chat-breathing 1.4s ease-in-out infinite;
}

/*
    Le dessin de ce qui se fait, entre les points et la phrase.

    Aligné sur le texte et non sur la ligne de base, où une icône carrée pend sous les lettres.
    Il double la phrase et n'apprend rien de plus : c'est la couleur de l'attente, prise d'un
    coup d'œil.
*/
.chat-activity-sign {
    display: inline-flex;
    align-self: center;
}

/* La bulle reste vide tant que rien n'est écrit : sans cela, un cadre vide flotte sous l'attente. */
.chat-bubble:empty {
    display: none;
}

/* La reprise, après un échec. Sous la phrase qui l'explique, et non à sa place. */
.chat-retry {
    align-self: flex-start;
    margin-block-start: var(--app-space-xs);
}

/* Ce qui a été coupé en route. Discret : c'est une note sur la réponse, pas une panne. */
.chat-stopped {
    margin-block-start: var(--app-space-xs);
    color: var(--app-ink-muted);
    font-size: var(--app-text-sm);
}

@keyframes chat-breathing {
    0%, 100% { opacity: 0.25; }
    50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .chat-activity-dots {
        animation: none;
        opacity: 0.6;
    }
}

.chat-empty {
    margin: auto;
    max-width: 26rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--app-space-xs);
}

/* La même pastille que devant les réponses, à la taille d'une page d'accueil : ce qui attend
   là est celui qui répondra ensuite. */
.chat-empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.25rem;
    height: 3.25rem;
    margin-bottom: var(--app-space-xs);
    border-radius: var(--app-radius-pill);
    background: var(--app-accent-soft);
    color: var(--app-accent);
    font-size: var(--app-text-2xl);
}

.chat-empty-title {
    font-size: var(--app-text-lg);
    margin-bottom: var(--app-space-2xs);
}

.chat-empty form {
    margin-top: var(--app-space-sm);
}

/*
 * La saisie et ses commandes ne font qu'un à l'œil : un seul contour, qui s'allume quand le
 * curseur entre dans le champ. Au clavier, elles restent des éléments distincts, chacun
 * atteignable.
 *
 * Le liseré est un dégradé, ce qu'une bordure ne sait pas porter : le cadre reçoit donc deux
 * fonds superposés — le sien, arrêté à la marge intérieure, et le dégradé, arrêté à la bordure
 * qu'il vient remplir.
 */
/*
 * La saisie n'appartient à aucun onglet : elle est ancrée sous eux, et reste sous la main quel
 * que soit le panneau ouvert. Son retrait est le sien — le cadre n'en a pas, pour que la barre
 * d'onglets en tienne le haut.
 */
.sheet-content > .chat-composer {
    margin: var(--app-space-md) var(--app-space-lg) 0;
}

.sheet-content > .chat-note {
    margin: var(--app-space-xs) var(--app-space-lg) var(--app-space-md);
}

.chat-composer {
    display: flex;
    flex-direction: column;
    gap: var(--app-space-sm);
    padding: var(--app-space-md);
    border: 1px solid transparent;
    border-radius: var(--app-radius);
    /* Le fond de la page, et non celui de la carte : sans ce contraste, la barre de saisie se
       fondrait dans le panneau qui la porte. */
    background:
        linear-gradient(var(--app-bg), var(--app-bg)) padding-box,
        linear-gradient(135deg,
            color-mix(in srgb, var(--app-accent) 40%, transparent),
            color-mix(in srgb, var(--chat-accent-far) 40%, transparent)) border-box;
    /* Une ombre douce la décolle du panneau : posée à plat, elle se lisait comme une bande de
       plus dans le fil au lieu du plan où l'on écrit. */
    box-shadow: var(--app-shadow);
    transition: box-shadow 150ms var(--app-ease);
}

.chat-composer:hover {
    background:
        linear-gradient(var(--app-bg), var(--app-bg)) padding-box,
        linear-gradient(135deg,
            color-mix(in srgb, var(--app-accent) 60%, transparent),
            color-mix(in srgb, var(--chat-accent-far) 60%, transparent)) border-box;
}

/* Le champ n'a pas de halo à lui : c'est le cadre entier qui s'allume, liseré plein et ombre
   creusée, faute de quoi rien ne dirait où le curseur se trouve. */
.chat-composer:focus-within {
    background:
        linear-gradient(var(--app-bg), var(--app-bg)) padding-box,
        linear-gradient(135deg, var(--app-accent), var(--chat-accent-far)) border-box;
    box-shadow: var(--app-shadow-focus), var(--app-shadow-pop);
}

.chat-input {
    border: none;
    background: none;
    box-shadow: none;
    padding: var(--app-space-2xs) 0;
    resize: none;
    /*
     * Le champ grandit avec ce qu'on y écrit, jusqu'à cette hauteur ; au-delà il défile. C'est
     * le navigateur qui mesure, sans une ligne de script : un vieux navigateur garde la
     * hauteur de départ, ce qui reste utilisable.
     */
    field-sizing: content;
    max-height: 12rem;
    color: inherit;
    font: inherit;
    line-height: 1.5;
}

.chat-input:focus,
.chat-input:focus-visible {
    outline: none;
    box-shadow: none;
}

/* Sous le champ : ce qu'on ajoute au message d'un côté, l'envoi de l'autre. */
.chat-tools {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--app-space-sm);
}

.chat-tools-start {
    display: flex;
    align-items: center;
    gap: var(--app-space-2xs);
}

/* Une commande discrète : sans fond, elle ne s'allume qu'au survol. */
.chat-tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: none;
    border-radius: var(--app-radius-ui);
    background: none;
    color: var(--app-ink-muted);
    font-size: var(--app-text-lg);
    cursor: pointer;
    transition: background 150ms var(--app-ease), color 150ms var(--app-ease);
}

.chat-tool:hover {
    background: var(--app-hover);
    color: var(--app-ink);
}

.chat-send {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    border: none;
    border-radius: var(--app-radius-pill);
    background: linear-gradient(135deg, var(--app-accent), var(--chat-accent-far));
    /*
     * « sur accent », et non « encre d'accent » : la seconde est une teinte sombre, faite pour
     * écrire EN accent sur un fond clair. Employée ici, elle donnait du sombre sur du sombre.
     */
    color: var(--app-on-accent);
    font-size: var(--app-text-lg);
    line-height: 1;
    cursor: pointer;
    transition: background 150ms var(--app-ease), transform 150ms var(--app-ease);
}

.chat-send:hover:not(:disabled) {
    background: linear-gradient(135deg, var(--app-accent-hover), var(--chat-accent-far-hover));
    transform: scale(1.06);
}

.chat-send:disabled {
    opacity: 0.5;
    cursor: default;
}

@media (prefers-reduced-motion: reduce) {
    .chat-send:hover:not(:disabled) {
        transform: none;
    }
}

.chat-note {
    margin: 0;
    text-align: center;
    font-size: var(--app-text-xs);
    color: var(--app-ink-muted);
}

/*
 * Le fil du projet : ce qui s'y accumule, dans l'ordre où c'est arrivé.
 *
 * Une entrée porte une pastille qui dit sa nature, ce qu'elle contient, et ce qu'on peut en
 * faire. Trois colonnes fixes de part et d'autre du contenu : ce qui s'ajoutera plus tard —
 * notes, tâches, échéances — entrera dans la même forme sans qu'une règle soit à écrire.
 */
.feed {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--app-space-sm);
}

.feed-entry {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--app-space-sm);
    align-items: start;
    padding-top: var(--app-space-sm);
    border-top: 1px solid var(--app-line);
}

.feed-entry:first-child {
    padding-top: 0;
    border-top: none;
}

.feed-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--app-radius-pill);
    background: var(--app-accent-soft);
    color: var(--app-accent);
    font-size: var(--app-text);
}

.feed-body {
    min-width: 0;
}

.feed-fact {
    margin: 0;
    line-height: 1.5;
    /* Une adresse ou un jeton sans espace déborderait de la colonne plutôt que d'y revenir. */
    overflow-wrap: anywhere;
}

.feed-meta {
    margin: var(--app-space-2xs) 0 0;
    font-size: var(--app-text-xs);
    color: var(--app-ink-muted);
}

/*
 * Ce que l'assistant propose de retenir, posé dans le fil de la conversation.
 *
 * Ni une réplique ni une bulle : un encart qui attend une réponse. Il occupe la largeur, se
 * distingue par un fond d'accent, et disparaît dès qu'on a tranché — remplacé par la phrase
 * que la décision a écrite.
 */
.memory-offer {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--app-space-md);
    align-items: start;
    padding: var(--app-space-md);
    border: 1px solid var(--app-accent-soft);
    border-radius: var(--app-radius);
    background: var(--app-accent-soft);
    color: var(--app-accent-ink);
}

.memory-offer[data-failed] {
    border-color: var(--app-line-strong);
}

.memory-offer-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--app-radius-pill);
    background: var(--app-surface);
    color: var(--app-accent);
    font-size: var(--app-text);
}

.memory-offer-body {
    min-width: 0;
}

.memory-offer-fact {
    margin: 0 0 var(--app-space-sm);
    line-height: 1.5;
    overflow-wrap: anywhere;
}

.memory-offer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--app-space-xs);
}

/*
 * Une décision consignée dans le fil : retenir un fait, le refuser.
 *
 * Centrée, discrète, sans bulle ni pastille — elle n'appartient ni à la personne ni à
 * l'assistant du point de vue de l'œil, alors qu'elle appartient bien à la personne du point de
 * vue du modèle. C'est exactement ce qu'il faut montrer : la phrase a été écrite par un clic,
 * pas au clavier.
 */
/*
 * Les trois pistes proposées à l'ouverture.
 *
 * Une grille qui se replie plutôt qu'une rangée fixe : le panneau de discussion n'est large que
 * de la moitié de l'écran sur un poste, et de tout l'écran sur un téléphone. Trois cadres côte à
 * côte y deviendraient trois colonnes de deux mots.
 *
 * `1fr` et non `auto` : les trois font la même largeur quelle que soit la longueur de leur titre,
 * sans quoi le plus bavard écraserait les deux autres et l'ensemble cesserait de se lire comme un
 * choix entre trois choses de même rang.
 *
 * Retirés des bords et espacés entre eux : trois cadres qui se touchent et touchent la marge se
 * lisent comme un bandeau, alors qu'on veut trois objets distincts qu'on choisit.
 *
 * Une seule colonne en dessous du point où le projet passe en deux colonnes — celui de la page
 * entière, et non un seuil inventé pour ce bloc. Sur un téléphone, trois cadres empilés se lisent
 * mieux que trois colonnes de deux mots, et le pouce les atteint tous.
 */
.chat-suggestions {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--app-space-lg);
    margin-block: var(--app-space-sm);
    margin-inline: var(--app-space-xl);
}

@media (min-width: 64rem) {
    .chat-suggestions {
        grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    }
}

/*
 * Au milieu du fil, tant qu'il n'y a qu'eux.
 *
 * Le fil colle ses messages en bas — c'est ce que fait la marge automatique posée sur son premier
 * enfant, et c'est juste pour une conversation. Mais à l'ouverture il n'y a que ces trois cadres,
 * qui tombent alors contre le champ de saisie sous une demi-page blanche.
 *
 * `:only-child` répond exactement à « il n'y a qu'eux », et la marge automatique des deux côtés
 * partage le vide au lieu de le laisser tout entier au-dessus. Aucune hauteur n'est écrite : le
 * navigateur mesure la place restante, et le résultat tient quel que soit l'écran, le nombre de
 * cadres ou la longueur de leurs raisons.
 *
 * La règle cesse d'elle-même dès la première réponse : les cadres cessent d'être seuls, retrouvent
 * leur place en tête de fil, et rien ne creuse de trou au milieu d'une conversation.
 */
.chat-thread > .chat-suggestions:only-child {
    margin-block: auto;
}

/*
 * Un cadre se clique en entier, titre et raison comprises : viser un lien de trois mots demande
 * une précision que rien ne justifie ici.
 *
 * Le fond est un mélange de la couleur d'accent du thème et de la surface, et non le jeton
 * `soft` : celui-ci vaut la teinte la plus pâle de la palette, qui à l'écran ne se distingue pas
 * du blanc. Le mélange suit la palette retenue comme le jeton l'aurait fait, en tenant la
 * promesse d'une couleur qu'on voit. L'encre reste celle prévue pour ce fond.
 */
.chat-suggestion {
    display: flex;
    flex-direction: column;
    gap: var(--app-space-2xs);
    padding: var(--app-space-md);
    border: 1px solid var(--app-accent);
    border-radius: var(--app-radius);
    background: color-mix(in oklch, var(--app-accent) 14%, var(--app-surface));
    color: var(--app-accent-ink);
    text-align: left;
    cursor: pointer;
    transition: background .12s ease;
}

/*
 * Au survol, le fond s'éclaircit vers la surface plutôt que de s'assombrir : le cadre s'allume
 * au lieu de s'enfoncer, et l'encre d'accent y gagne du contraste au lieu d'en perdre.
 *
 * Rien n'est ajouté au focus : le socle y pose déjà un liseré, et un second trait par-dessus
 * ferait ressembler le cadre qui a le curseur à celui qu'on aurait choisi.
 */
.chat-suggestion:hover {
    background: var(--app-surface);
}

.chat-suggestion-title {
    font-weight: var(--app-weight-bold);
    /* Un titre de cinq mots ne porte pas de mot à rallonge, mais un nom recopié du projet peut
       en porter un : il passe à la ligne au lieu d'élargir sa colonne. */
    overflow-wrap: anywhere;
}

/*
 * La raison, plus discrète que le titre sans quitter l'encre d'accent : l'estomper la ferait
 * passer sous le rapport de contraste que le fond coloré exige.
 */
.chat-suggestion-why {
    font-size: var(--app-text-sm);
    overflow-wrap: anywhere;
}

.chat-decision {
    margin: 0 auto;
    max-width: 90%;
    padding: var(--app-space-2xs) var(--app-space-md);
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius-pill);
    background: var(--app-bg);
    color: var(--app-ink-muted);
    font-size: var(--app-text-sm);
    text-align: center;
    overflow-wrap: anywhere;
}

/* ═══ Les pièces jointes ═══════════════════════════════════════════════════════════════ */

/*
 * Une pastille, et la même partout : dans le tiroir du projet, sous un message du fil, et dans
 * la barre de saisie avant l'envoi. Un fichier ne change pas de nature selon l'endroit où on le
 * regarde, et le reconnaître d'un coup d'œil vaut mieux que trois dessins à apprendre.
 *
 * Le gabarit `_attachment.html.twig` et le contrôleur `conversation` produisent exactement ce
 * balisage : c'est ce qui fait qu'un fichier déposé à l'instant ressemble à un fichier
 * rechargé.
 */
.attachment-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--app-space-xs);
}

.attachment {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--app-space-xs);
    max-width: 100%;
    padding: var(--app-space-2xs);
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    background: var(--app-bg);
}

/* Une entrée qu'on peut emmener vers l'assistant le dit par le curseur : rien d'autre ne
   l'annonce, et un glissé qu'on ne soupçonne pas n'est jamais tenté. */
.attachment[draggable="true"] {
    cursor: grab;
}

.attachment-link {
    display: inline-flex;
    align-items: center;
    gap: var(--app-space-2xs);
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.attachment-link:hover .attachment-name {
    text-decoration: underline;
}

/* La vignette montre l'image, elle ne la signale pas : un nom de capture ne dit rien de ce
   qu'elle contient, et reconnaître son écran vaut mieux que lire « capture-17h23 ». Le nom
   passe en survol, et la place qu'il prenait revient au dessin. Faute de miniatures, elle
   charge l'image entière — à revoir le jour où un projet en portera cinquante. */
.attachment-thumb {
    flex: 0 0 auto;
    width: 5rem;
    height: 5rem;
    border-radius: var(--app-radius-ui);
    object-fit: cover;
}

/* Ce qui n'est pas une image occupe la même place : une rangée où chaque pastille a sa hauteur
   se lit deux fois moins bien. */
.attachment-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: var(--app-radius-ui);
    background: var(--app-accent-soft);
    color: var(--app-accent);
    font-size: var(--app-text-lg);
}

/* Un nom de fichier est souvent long et sans espace : il se coupe plutôt que de pousser la
   pastille hors de la colonne. */
.attachment-name {
    overflow: hidden;
    max-width: 14rem;
    font-size: var(--app-text-sm);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * Un fichier dont il ne reste que la ligne en base.
 *
 * Ni lien ni glissé — il n'y a rien à ouvrir et rien à envoyer. La tuile prend la place qu'aurait
 * la vignette, le trait discontinu dit qu'il manque quelque chose, et la phrase attend au survol :
 * écrite, elle occuperait plus de place que ce qu'elle remplace.
 */
.attachment--missing {
    border-style: dashed;
    cursor: help;
}

.attachment--missing .attachment-icon {
    width: 5rem;
    height: 5rem;
    background: var(--app-bg);
    border: 1px dashed var(--app-line-strong);
    color: var(--app-danger, var(--app-ink-muted));
    font-size: var(--app-text-lg);
}

/* Ne s'allume qu'au survol : retirer ne doit pas s'offrir avant qu'on le cherche. */
.attachment-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: none;
    border-radius: var(--app-radius-pill);
    background: none;
    color: var(--app-ink-muted);
    font-size: var(--app-text-sm);
    cursor: pointer;
    transition: background 150ms var(--app-ease), color 150ms var(--app-ease);
}

.attachment-remove:hover {
    background: var(--app-hover);
    color: var(--app-danger, var(--app-ink));
}

/*
 * Sur une tuile — une image, ou ce qu'il reste d'un fichier perdu —, retirer se pose dans le
 * coin : posé à côté, il élargirait de moitié une pastille qui n'a plus de texte. Un fond plein
 * le garde lisible quelle que soit l'image en dessous.
 */
.attachment:has(.attachment-thumb) form,
.attachment--missing form {
    position: absolute;
    top: var(--app-space-2xs);
    right: var(--app-space-2xs);
}

.attachment:has(.attachment-thumb) .attachment-remove,
.attachment--missing .attachment-remove {
    background: var(--app-surface);
    box-shadow: var(--app-shadow-pop);
}

/*
 * Sous un message du fil. La grille du tour a deux colonnes — la pastille de l'assistant, puis
 * ce qui est dit — et cette liste appartient à la seconde ; ce qu'on a écrit soi-même n'en a
 * qu'une.
 */
.chat-turn .chat-attachments {
    grid-column: 2;
    margin-top: var(--app-space-2xs);
}

.chat-turn--mine .chat-attachments {
    grid-column: 1;
    justify-content: flex-end;
}

/* Ce qui partira avec le prochain message. Rien à montrer tant qu'on n'a rien joint : la barre
   de saisie ne doit pas garder une ligne vide en réserve. */
.chat-pending:empty {
    display: none;
}

/*
 * L'assistant accepte un dépôt : des fichiers du système, ou une pastille venue du tiroir. Le
 * cadre entier s'allume, faute de quoi rien ne dirait où lâcher.
 */
.sheet-content[data-over] {
    outline: 2px dashed var(--app-accent);
    outline-offset: -4px;
}

/*
 * Le tiroir du projet accepte un dépôt sur toute sa surface, et le dit comme le fil : le cadre
 * entier s'allume. Une bande en pointillés au fond ne recevait que ce qu'on visait juste.
 */
.project-files[data-over] {
    outline: 2px dashed var(--app-accent);
    outline-offset: -4px;
}

/*
 * Choisir un fichier sans le glisser : une ligne en tête du cadre, de la hauteur de son texte.
 * Les pointillés restent — ils disent aussi ce qu'on peut lâcher — mais la surface qu'ils
 * entouraient revient aux fichiers.
 *
 * L'écart la sépare de ce qui est déjà rangé dessous : sans lui, la commande se lirait comme le
 * premier des fichiers.
 */
.attachment-add {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--app-space-2xs);
    width: 100%;
    margin-bottom: var(--app-space-md);
    padding: var(--app-space-2xs) var(--app-space-xs);
    border: 1px dashed var(--app-line-strong);
    border-radius: var(--app-radius-ui);
    background: none;
    color: var(--app-ink-muted);
    font: inherit;
    font-size: var(--app-text-sm);
    cursor: pointer;
    transition: border-color 150ms var(--app-ease), background 150ms var(--app-ease);
}

.attachment-add:hover {
    border-color: var(--app-accent);
    background: var(--app-accent-soft);
    color: var(--app-ink);
}

/* Le temps d'un envoi : la commande cesse de répondre à l'œil comme elle cesse d'accepter. */
.project-files[data-busy] .attachment-add {
    opacity: .6;
    cursor: progress;
}

.attachment-add-icon {
    display: inline-flex;
    color: var(--app-accent);
    font-size: var(--app-text);
}

/*
 * Un refus, dit dans le fil : un fichier trop lourd, d'un type qu'on ne range pas.
 *
 * La forme d'une décision — centrée, discrète, sans bulle : ce n'est ni une parole ni une
 * réponse, c'est l'application qui parle. La teinte d'alerte fait la différence, et suffit :
 * la phrase dit déjà ce qui s'est passé.
 */
.chat-notice {
    margin: 0 auto;
    max-width: 90%;
    padding: var(--app-space-2xs) var(--app-space-md);
    border: 1px solid var(--app-danger, var(--app-line-strong));
    border-radius: var(--app-radius-pill);
    background: var(--app-bg);
    color: var(--app-danger, var(--app-ink));
    font-size: var(--app-text-sm);
    text-align: center;
    overflow-wrap: anywhere;
}

/* Ce qui est arrivé sans rien refuser : un fichier retrouvé dans le coffre. */
.chat-notice[data-tone="info"] {
    border-color: var(--app-line-strong);
    color: var(--app-ink-muted);
}

/*
 * Ce qui a été modifié sans qu'on l'ait vu.
 *
 * L'assistant modifie le projet pendant qu'il répond, une autre personne de l'espace pendant
 * qu'on n'était pas là : sans ce signal, une échéance posée ou une tâche déplacée s'inscrivent
 * en silence.
 *
 * **Un fond posé, et non une animation qui s'éteint.** La marque tient tant que la ligne n'a pas
 * été montrée — des jours s'il le faut. Une animation lancée au chargement de la page se serait
 * consumée dans un onglet fermé, et l'ouvrir n'aurait plus rien montré.
 *
 * L'extinction est portée par la ligne elle-même : le comportement retire la marque une fois le
 * signal éteint côté serveur, et c'est ce retrait que la transition adoucit. Rien ne se déplace —
 * la ligne ne doit pas bouger sous l'œil de qui est en train de la lire.
 */
[data-entry-id] {
    transition: background 600ms var(--app-ease);
}

[data-unseen] {
    border-radius: var(--app-radius-ui);
    background: var(--app-accent-soft);
}

/* Pour qui a demandé moins de mouvement : le fond disparaît d'un coup. Le signal est rendu,
   sans transition. */
@media (prefers-reduced-motion: reduce) {
    [data-entry-id] {
        transition: none;
    }
}

/* ═══ Ce que fait un outil ═════════════════════════════════════════════════════════════ */

/*
 * Une ligne par outil, et sa description derrière un point d'interrogation.
 *
 * Les descriptions font deux à trois lignes chacune : posées à côté des noms, elles allongent
 * la liste jusqu'à ce qu'on n'y trouve plus rien. Mises en retrait, elles restent à un survol
 * de qui se demande ce que fait celui-là.
 *
 * Aucun JavaScript : l'infobulle s'ouvre au survol et à la mise au point du bouton. Elle est
 * donc atteignable au clavier — on tabule jusqu'au bouton — et au doigt, où une pression vaut
 * mise au point.
 */
.ability-help {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
}

.ability-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: none;
    border-radius: var(--app-radius-pill);
    background: none;
    color: var(--app-ink-muted);
    cursor: help;
    transition: color 150ms var(--app-ease), background 150ms var(--app-ease);
}

.ability-info:hover,
.ability-info:focus-visible {
    background: var(--app-hover);
    color: var(--app-accent);
}

/*
 * La bulle, posée sous le bouton et alignée sur son bord droit : celui-ci termine la ligne, et
 * une bulle qui partirait vers la droite sortirait de la boîte de dialogue.
 *
 * Vers le bas et jamais vers le haut, même pour la première ligne : au-dessus, elle
 * recouvrirait le titre du cadre, ce qui se lit comme un défaut d'affichage. En dessous, elle
 * cache une ligne de la liste — qu'on retrouve en relâchant.
 *
 * `visibility` en plus de `opacity` : sans elle, une bulle transparente reste sous le doigt et
 * sous le curseur, et intercepte ce qui passe derrière.
 */
.ability-tip {
    position: absolute;
    right: 0;
    top: calc(100% + var(--app-space-2xs));
    z-index: 2;
    width: max-content;
    max-width: 18rem;
    padding: var(--app-space-xs) var(--app-space-sm);
    border-radius: var(--app-radius-ui);
    /* L'encre en fond et la surface en texte : les deux s'échangent d'un thème à l'autre, et
       la bulle reste lisible sans qu'une couleur soit écrite ici. */
    background: var(--app-ink);
    color: var(--app-surface);
    font-size: var(--app-text-xs);
    font-weight: var(--app-weight-normal);
    line-height: 1.5;
    text-align: left;
    box-shadow: var(--app-shadow-pop);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-.25rem);
    transition: opacity 120ms var(--app-ease), transform 120ms var(--app-ease), visibility 120ms;
    /* Rien ne passe par elle, même ouverte. Le bouton garde le foyer après un clic, donc la
       bulle reste affichée — posée au-dessus de la liste, elle avalait le clic suivant, et
       l'écran paraissait bloqué. Une explication ne se clique pas : elle se lit. */
    pointer-events: none;
}

/* La pointe, qui rattache la bulle à son bouton. Sans elle, la bulle flotte sans qu'on sache
   ce qu'elle explique. */
.ability-tip::after {
    content: "";
    position: absolute;
    bottom: 100%;
    right: .5rem;
    border: .3rem solid transparent;
    border-bottom-color: var(--app-ink);
}

.ability-info:hover + .ability-tip,
.ability-info:focus + .ability-tip {
    opacity: 1;
    visibility: visible;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .ability-tip {
        transition: none;
        transform: none;
    }
}


/*
 * Les trois issues d'un projet, côte à côte.
 *
 * Des boutons dessinés comme les pastilles qu'ils deviendront : ce qu'on clique et ce qu'on
 * lira ensuite dans la liste ont la même forme, si bien que le geste montre son résultat.
 *
 * Celle qui est en cours est désactivée plutôt que masquée — c'est l'état courant, et le
 * retirer de la rangée décalerait les deux autres sous les doigts à chaque changement.
 */
.summary-outcomes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--app-space-2xs);
}

.summary-outcome {
    border: 1px solid transparent;
    cursor: pointer;
    font: inherit;
}

.summary-outcome:disabled {
    border-color: var(--app-border);
    cursor: default;
}

/*
 * ── La barre des étapes ──────────────────────────────────────────────────────────────────────
 * Un filet de 2px reste visible entre deux chevrons — la pointe (6px) ne comble pas tout à fait
 * l'encoche qui la reçoit. `gap: 3px` laissait un interstice bien plus large ; -4px de marge (la
 * pointe moins le filet voulu) donne le même écart, uniforme sur toute la hauteur de la jointure.
 *
 * 24px de haut : la barre porte une ligne d'information, pas deux. Le rentrant et le sortant du
 * chevron valent 6px, et le texte doit les franchir avant de commencer — d'où 14px là où le
 * chevron mord, 8px ailleurs.
 */
.project-steps {
    display: flex;
    flex-wrap: wrap;
    /* 20px, la mesure du CRM : l'échelle du paquet n'a que 16 et 24, et la barre se colle au
       contenu à 16. */
    margin: 0 0 20px;
    padding: 0;
    list-style: none;
}

.project-steps li {
    flex: 1 1 130px;
    min-width: 0;
}

.project-steps li:not(:first-child) {
    margin-left: -4px;
}

.project-steps form {
    margin: 0;
}

.project-steps button {
    overflow: hidden;
    width: 100%;
    height: 24px;
    padding: 0 14px;
    border: 0;
    /* Un fond relevé, et non `--app-line` : le paquet dit d'un filet qu'il « sépare, il ne
       délimite aucun contrôle » — l'employer en aplat de bouton sort de son rôle, et sur la
       palette à fort contraste il rendait un segment blanc sous un texte gris pâle. */
    background: var(--app-raised);
    /* `ink-soft` et non `ink-muted` : en 12px sur ce fond relevé, le second tombait à 4,37 de
       contraste en thème sombre, sous les 4,5 exigés. */
    color: var(--app-ink-soft);
    clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 50%, calc(100% - 6px) 100%, 0 100%, 6px 50%);
    cursor: pointer;
    font: inherit;
    font-size: 12px;
    font-weight: 450;
    letter-spacing: .01em;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-steps li:first-child button {
    padding-left: 8px;
    border-radius: 8px 0 0 8px;
    clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 50%, calc(100% - 6px) 100%, 0 100%);
}

.project-steps li:last-child button {
    padding-right: 8px;
    border-radius: 0 8px 8px 0;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 6px 50%);
}

.project-steps li:only-child button {
    padding: 0 8px;
    border-radius: 8px;
    clip-path: none;
}

/*
 * L'étape courante ET les étapes déjà franchies en plein : sur un projet avancé, tout le chemin
 * parcouru se lit d'un coup, et seules celles à venir restent grises.
 */
/*
 * Le chemin parcouru en plein, l'étape ATTEINTE plus soutenue encore.
 *
 * Les deux partageaient exactement le même aplat : rien ne marquait où le projet se trouve, sinon
 * « le dernier vert » — et le seul autre indice, l'état désactivé, ne se voit pas.
 */
.project-steps [data-done] button {
    background: var(--app-success);
    color: var(--app-on-success);
    opacity: .72;
}

.project-steps [data-now] button {
    background: var(--app-success);
    color: var(--app-on-success);
    font-weight: 600;
}

.project-steps button:hover {
    filter: brightness(.94);
}

.project-steps button:focus-visible {
    outline: 2px solid var(--app-ink);
    outline-offset: 2px;
}

.project-steps button:disabled {
    cursor: default;
    filter: none;
}

/* L'issue et ce qui l'accompagne, sur une ligne qui passe à la suivante si la colonne se
   resserre. */
.summary-outcome {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--app-space-xs);
}


/* Le refus d'un déplacement, sous la barre qu'il concerne. */
.project-steps-refusal {
    margin: -12px 0 20px;
}

/*
 * ── Le fil sur un écran qu'on tient d'une main ────────────────────────────────────────────────
 *
 * Les commandes de la barre de saisie sont dessinées à la taille du texte qu'elles accompagnent :
 * deux rems de côté, ce qui se vise sans peine à la souris et se manque une fois sur trois au
 * pouce. Elles prennent la mesure du doigt, et la barre s'élève d'autant.
 */
@media (max-width: 37.5rem) {
    .chat-tool,
    .chat-send {
        width: var(--app-touch);
        height: var(--app-touch);
    }

    /* Le champ où l'on écrit à l'assistant tenait sur une ligne de vingt-neuf pixels : c'est la
       plus grande cible de la barre, et c'était la plus basse. */
    .chat-input {
        min-height: var(--app-touch);
    }

    /*
     * Les étapes du parcours sont des chevrons de vingt-quatre pixels de haut : c'est la mesure
     * d'un fil d'Ariane, et elle convient tant qu'on les vise à la souris. Au doigt, deux étapes
     * voisines se touchent, et l'on déplace le projet là où l'on n'allait pas.
     */
    .project-steps button {
        height: var(--app-touch);
    }
}


/* ═══ Les conversations ════════════════════════════════════════════════════════════════ */

/*
 * La barre au-dessus du fil : le nom de la conversation montrée, et ses deux gestes — la liste
 * des autres, et une nouvelle. Elle ne défile pas avec le fil.
 */
.chat-head {
    display: flex;
    align-items: center;
    gap: var(--app-space-xs);
    flex: 0 0 auto;
    padding: var(--app-space-xs) 0;
    border-bottom: 1px solid var(--app-line);
}

/* Un nom long se coupe plutôt que de pousser les deux boutons hors du cadre. */
.chat-head-title {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: var(--app-text-sm);
    font-weight: var(--app-weight-semibold);
    color: var(--app-ink-soft);
}

.chat-head-tools {
    display: flex;
    flex: 0 0 auto;
    gap: var(--app-space-2xs);
}

/* Le dessin à la taille de ceux de la barre de saisie : ce sont les mêmes sortes de gestes. La
   double classe passe devant la petite taille du paquet, qui réduit le texte et donc le dessin. */
.chat-head-tools .chat-head-tool {
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    font-size: var(--app-text-lg);
}

/*
 * Le tiroir des conversations, contre le bord droit : c'est là que se tient la discussion, et
 * la liste s'ouvre par-dessus sans quitter le côté où l'on regardait. Sur un téléphone, il prend
 * toute la largeur.
 */
/* La largeur se prend sur la fenêtre moins sa barre de défilement : mesurée sur la fenêtre
   entière, le tiroir débordait d'autant du côté gauche. */
.conversations-drawer {
    width: min(26rem, 100%);
    max-width: none;
    height: 100dvh;
    max-height: none;
    margin: 0 0 0 auto;
    padding: 0;
    border: none;
    border-radius: 0;
    background: var(--app-surface);
    color: var(--app-ink);
    box-shadow: var(--app-shadow-pop);
}

/* La mise en colonne ne vaut qu'ouvert : posée sans condition, elle écraserait la règle qui
   cache une boîte fermée. */
.conversations-drawer[open] {
    display: flex;
    flex-direction: column;
}

.conversations-drawer::backdrop {
    background: var(--app-backdrop);
}

.conversations-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--app-space-sm);
    padding: var(--app-space-sm) var(--app-space-sm) var(--app-space-sm) var(--app-space-lg);
    border-bottom: 1px solid var(--app-line);
}

.conversations-heading {
    display: inline-flex;
    align-items: center;
    gap: var(--app-space-xs);
    margin: 0;
    font-size: var(--app-text-lg);
}

.conversations-start {
    padding: var(--app-space-md) var(--app-space-lg) 0;
}

/* La liste défile seule sous la barre : ce qui referme reste sous la main. */
.conversations-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--app-space-lg);
    min-height: 0;
    overflow-y: auto;
    padding: var(--app-space-md) var(--app-space-lg) var(--app-space-lg);
    scrollbar-width: thin;
    scrollbar-color: var(--app-line-strong) transparent;
}

.conversations-body[aria-busy="true"] {
    opacity: .6;
}

/* Le repère d'une période, à la mesure des intitulés d'un panneau : il range, il ne titre pas. */
.conversations-period {
    margin: 0 0 var(--app-space-xs);
    font-size: var(--app-text-xs);
    font-weight: var(--app-weight-semibold);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--app-ink-muted);
}

.conversations-list {
    display: flex;
    flex-direction: column;
    gap: var(--app-space-2xs);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Le liseré de gauche est réservé à la conversation montrée ; il tient sa place partout, pour
   que les noms s'alignent. */
.conversations-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--app-space-2xs);
    border-left: 3px solid transparent;
    border-radius: var(--app-radius-ui);
}

.conversations-item:hover {
    background: var(--app-hover);
}

/* La conversation montrée : l'encre pleine et la graisse la distinguent, l'accent en tient le
   trait. */
.conversations-item[aria-current="true"] {
    border-left-color: var(--app-accent);
    background: var(--app-hover);
}

.conversations-open {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    min-width: 0;
    min-height: var(--app-touch);
    padding: var(--app-space-xs) var(--app-space-sm);
    border: none;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.conversations-name {
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--app-ink-soft);
}

.conversations-item[aria-current="true"] .conversations-name {
    font-weight: var(--app-weight-semibold);
    color: var(--app-ink);
}

.conversations-when {
    font-size: var(--app-text-xs);
    color: var(--app-ink-muted);
}

.conversations-tools {
    display: flex;
    flex: 0 0 auto;
    gap: var(--app-space-2xs);
    padding-right: var(--app-space-2xs);
}

/* Le champ du nom prend la place de la ligne, à une taille qu'un téléphone n'agrandit pas à la
   saisie. */
.conversations-rename {
    flex: 1 1 0;
    min-width: 0;
    min-height: var(--app-touch);
    font-size: max(1rem, var(--app-text));
}

.conversations-none,
.conversations-note {
    margin: 0;
    color: var(--app-ink-muted);
}

.conversations-note {
    font-size: var(--app-text-sm);
}

.conversations-notice {
    margin: 0;
    padding: var(--app-space-sm) var(--app-space-md);
    border-radius: var(--app-radius-ui);
    background: var(--app-hover);
}

.conversations-error {
    flex-basis: 100%;
    margin: 0;
    padding: 0 var(--app-space-sm) var(--app-space-xs);
    font-size: var(--app-text-sm);
    color: var(--app-danger);
}
