/*
 * Screen-level styles. Imported last by assets/app.js, after tokens, base, layout and components
 * (buttons, link-quiet, text-field, sheets, toast: components.css).
 */

.page-title {
    font: var(--font-headline);
}

/* Head of every member screen (layout/app.html.twig): the page heading, then the account menu at the top
   right, beside it and never over it; the heading takes what is left of the line, and wraps. The account
   menu's list opens under the head, along its right edge, within the content column. */
.page-head {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
}

.page-head__main {
    flex: 1 1 auto;
    min-width: 0;
}

.page-head__main > .page-title {
    overflow-wrap: anywhere;
}

/* Account menu (components/_account_menu.html.twig): the avatar is its button — the initial on one of the
   avatar colours (avatar--1 to avatar--5, components.css), no photo, sized as a touch target — and the list
   opens under the head of the page, above the content. */
.account-menu__button {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--touch-min);
    height: var(--touch-min);
    padding: 0;
    border: 0;
    border-radius: var(--radius-full);
    color: var(--color-on-ink);
    font: var(--font-button);
    font-weight: 800;
    cursor: pointer;
}

.account-menu__list {
    position: absolute;
    top: calc(100% + var(--space-2));
    right: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    /* Within the head of the page, hence the content column. */
    width: max-content;
    min-width: min(12rem, 100%);
    max-width: 100%;
    margin: 0;
    padding: var(--space-2);
    border: 2px solid var(--color-line-strong);
    border-radius: var(--radius-md);
    background: var(--color-surface-card);
    list-style: none;
}

.account-menu__list[hidden] {
    display: none;
}

.account-menu .sign-out {
    display: flex;
}

.account-menu__link {
    display: flex;
    /* The icon and the label stay side by side: a long label (320px, 200% text) wraps within itself. */
    flex-wrap: nowrap;
    flex: 1 1 auto;
    align-items: center;
    column-gap: var(--space-2);
    min-height: var(--touch-min);
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink);
    font: var(--font-button);
    font-weight: 800;
    text-align: left;
    text-decoration: none;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.account-menu__link > span {
    min-width: 0;
}

/* The decorative icon before each entry, as in the side navigation: dimmed, full strength on the current
   page's entry. */
.account-menu__icon {
    opacity: 0.6;
}

.account-menu__link[aria-current] .account-menu__icon {
    opacity: 1;
}

/* From 1024px the foot of the side navigation carries the same entries (layout.css): the account menu is
   not displayed, so that keyboards and screen readers meet them once. Same breakpoint as the side
   navigation: change both together. */
@media (min-width: 64rem) {
    .page-head > .account-menu {
        display: none;
    }
}

/* The number of friend requests next to « Amis » (account menu, side navigation): words, not a badge. */
.nav-count {
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

/* Pages without a session (layout/public.html.twig): the app name above the title, no navigation. */
.public-brand {
    color: var(--color-ink);
    font: var(--font-title-section);
}

.public-message {
    color: var(--color-ink);
    font: var(--font-body);
}

/* The discreet links under main (sign-in screen: « Mentions et confidentialité »), on the content column. */
.public-footer {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding: 0 var(--margin-mobile) calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 64rem) {
    .public-footer {
        padding-inline: var(--space-8);
    }
}

/* « Mentions et confidentialité »: the text of the locale, one section per h2. What is still to be completed
   before the site opens is highlighted, between brackets. */
.legal {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.legal__section {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.legal ul {
    margin: 0;
    padding-left: var(--space-5);
}

.legal a {
    overflow-wrap: anywhere;
}

.legal__updated {
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.legal-todo {
    padding-inline: 0.125em;
    border-radius: var(--radius-sm);
    background: var(--color-sun-soft);
    box-shadow: inset 0 -2px 0 var(--color-sun-border);
    color: var(--color-ink);
    font-weight: 700;
}

/* Sign-in screen: the neutral answer, the "too many attempts" alert, then the address form. */
.public-message--status,
.public-message--alert {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    background: var(--color-surface-card);
    font-weight: 700;
}

.public-message--alert {
    border-left: 6px solid var(--color-danger);
}

.sign-in-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* Settings: one save form (four sections, then the fixed save bar), then « Mentions et confidentialité »,
   outside the form. The Friends page reuses its sections. */
.me-screen,
.me-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.me-screen,
.dossier-screen {
    /* Room for the fixed save bar: at the very bottom of the page, the last section ends above it. The tab
       bar already has its own room (the bottom padding of .app-shell, layout.css); what the bar is lifted by
       beyond the tab bar's least height (the raised capture button, or the no-JavaScript allowance) is
       added. */
    padding-bottom: calc(var(--me-save-h) + max(0px, var(--me-tab-bar) - var(--tabbar-min-height)));
}

.me-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.me-section__title {
    font: var(--font-title-section);
}

.me-section__note {
    color: var(--color-ink-muted);
    font: var(--font-body);
}

/* Moi > Amis > Mes groupes and the page of a group (me/group.html.twig): the sections of the Friends page,
   each form stacked (its field, then its button, as wide as its label). */
.group-screen {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.group-form,
.group-add {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
}

.group-form > .text-field {
    align-self: stretch;
}

.group-form > .btn,
.group-add > .btn {
    max-width: 100%;
}

.group-add__choices {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

/* Moi > Paramètres > Notifications (notification/_push_section.html.twig): one state shown at a time by the
   push-subscription controller, its button under its text, as wide as its label (never wider than the
   column: a long label wraps). */
.push-settings__state {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
}

.push-settings__state[hidden] {
    display: none;
}

.push-settings__state > .btn {
    max-width: 100%;
}

/* A failed activation or deactivation: bold ink beside a danger rule, never carried by colour alone.
   Empty (and not displayed) until something failed. */
.push-settings__error {
    padding-left: var(--space-3);
    border-left: 4px solid var(--color-danger);
    color: var(--color-ink);
    font: var(--font-body);
    font-weight: 700;
}

.push-settings__error:empty {
    display: none;
}

/*
 * Measured on the root element:
 *  - --tab-bar-reach: how high the tab bar reaches, its raised capture button included (tab-bar controller;
 *    0 without a tab bar);
 *  - --me-save-height: the height of the save bar (me-form controller).
 * Without JavaScript, generous allowances stand in: the tab bar at its tallest (labels wrapped at 200%
 * text, the allowance of the scroll padding, base.css) and a button with room to spare.
 * Both feed the scroll padding, so that a focused field is never left under the save bar or the tab bar.
 */
html:has(.me-save) {
    --me-tab-bar: var(--tab-bar-reach, calc(var(--tabbar-min-height) + 2rem + env(safe-area-inset-bottom, 0px) + var(--space-8)));
    --me-save-h: var(--me-save-height, calc(var(--touch-min) + 2 * var(--space-3) + var(--space-4)));
    scroll-padding-bottom: calc(var(--me-tab-bar) + var(--me-save-h) + var(--space-4));
}

/*
 * « Enregistrer », fixed to the screen: it never moves while the page scrolls, not even at its very bottom.
 * On a phone it sits right above the tab bar (drawn above it, z-index) and, like the tab bar, spans the
 * whole width of the screen with the same top rule; its ground runs down behind the tab bar (::after), so
 * that no gap is left when the no-JavaScript allowance is taller than the tab bar. From 1024px it spans the
 * whole area right of the side navigation. The button itself stays on the content column: the inline
 * padding puts it on the content edges of .app-main (centred, at most --content-max wide, with the padding
 * of main). Percentages of a fixed box's padding refer to the width of the screen.
 */
.me-save {
    --me-save-offset: 0px;
    --me-save-gutter: var(--margin-mobile);
    position: fixed;
    bottom: var(--me-tab-bar);
    left: var(--me-save-offset);
    right: 0;
    z-index: 5;
    padding-block: var(--space-3);
    padding-inline: calc(max(0px, (100% - var(--me-save-offset) - var(--content-max)) / 2) + var(--me-save-gutter));
    border-top: 2px solid var(--color-line);
    background: var(--color-surface-ground);
}

.me-save::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: var(--me-tab-bar);
    background: var(--color-surface-ground);
}

/* The toasts stack above the save bar instead of covering it. */
html:has(.me-save) .toast-region {
    bottom: calc(var(--me-tab-bar) + var(--me-save-h) + var(--space-2));
}

/* Same breakpoint as the side navigation (layout.css): no tab bar, the bar sits at the bottom of the screen,
   under the content column. */
@media (min-width: 64rem) {
    html:has(.me-save) {
        --me-tab-bar: 0px;
    }

    .me-save {
        --me-save-offset: var(--sidenav-width);
        --me-save-gutter: var(--space-8);
    }
}

/* Home screen: its sections, in the order of the template, spaced as the blocks of main. */
/* « Mes dossiers » (story 2.15) stacks its sections as the home screen does. */
.home,
.my-dossiers {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

/* Story 2.14: every section of the home screen is a block, as « Héros » on the fiche (block-card): a card, its title
   in small bold capitals of the muted ink (as cast-role__title), its lines one under the other, separated by a rule —
   no card of their own. */
.home-section,
.hero-block {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
    padding: var(--space-4);
    border-radius: var(--radius-lg);
    background: var(--color-surface-card);
}

.home-section__title,
.hero-block__title {
    margin: 0;
    font: var(--font-caption);
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-ink-muted);
}

.home-section .list-rows {
    gap: 0;
}

.home-section .list-row,
.home-section .pending-item {
    padding-inline: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
}

.home-section .list-rows > li + li {
    border-top: 1px solid var(--color-line);
}

/* « Derniers dossiers » (story 3.9): its rows in their turbo-frame, then « Voir plus » centred under them. */
.home-recent {
    display: block;
}
.home-section__more--centered {
    display: flex;
    justify-content: center;
    margin: var(--space-2) 0 0;
}

/* « À valider » keeps the sun rule of pending-item at the start of each line: the one section that waits
   for the member's action, told at a glance (decision: Raphael, 2026-10-23; spec of story 2.14). Drawn
   apart from the borders, so that it never meets the separator of the lines: one rule per line, rounded,
   short of its top and bottom; painted in CanvasText in forced colours (as the active rules of the navigation).
   More specific than the reset of the block above. The title « À valider » carries the meaning: the rule is a
   cue only. */
.home .home-section .pending-item {
    position: relative;
    padding-inline-start: calc(6px + var(--space-3));
}

.home .home-section .pending-item::before {
    content: "";
    position: absolute;
    inset-block: var(--space-2);
    inset-inline-start: 0;
    width: 6px;
    border-radius: 3px;
    background: var(--color-sun-border);
}

@media (forced-colors: active) {
    .home .home-section .pending-item::before {
        forced-color-adjust: none;
        background: CanvasText;
    }
}

/* Focused by script once an item of « À valider » is decided (pending-decision): the next item or the
   next section title keeps a visible cue — and so does « Ta décision » on the fiche, reached from « Voir »
   (dossier-sheet) —, whatever the last input (mouse, touch or keyboard). The focus
   ring without its halo (subtler than on a control); an outline, so drawn in forced colours too. */
.home .pending-item:focus,
.home-section__title[tabindex="-1"]:focus,
.hero-decision.is-anchor-focused:focus {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: var(--focus-ring-offset);
    box-shadow: none;
}

/* install-reminder (home screen, story 1.10): a discreet dashed card at the head of the home screen, never
   an overlay, never tomato. 📲 beside the sentence, then « Comment faire » (an ink link) and « Plus tard »
   (quiet), wrapping under each other at 320px and 200% text. Hidden until the install-reminder controller
   reveals it. */
.install-reminder {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 2px dashed var(--color-line-strong);
    border-radius: var(--radius-lg);
    background: var(--color-surface-card);
    color: var(--color-ink);
    font: var(--font-body);
}

.install-reminder[hidden] {
    display: none;
}

.install-reminder__icon {
    flex: none;
    font-size: 1.5rem;
    line-height: 1.2;
}

.install-reminder__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

.install-reminder__text {
    overflow-wrap: anywhere;
}

.install-reminder__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--space-5);
}

/* « Comment faire »: a link to the steps (or the native prompt), in ink, bold and underlined, 44px target. */
.install-reminder__how {
    display: inline-flex;
    align-items: center;
    min-height: var(--touch-min);
    padding: 0;
    border: 0;
    background: none;
    color: var(--color-ink);
    font: var(--font-body);
    font-weight: 800;
    text-align: left;
    text-decoration: underline;
    text-underline-offset: 0.2em;
    overflow-wrap: anywhere;
    cursor: pointer;
}

/* The steps to add lldd to the home screen: in the sheet of the reminder and in Moi > Paramètres >
   Installation, only the way suited to the device shown. */
.install-steps,
.install-settings__state {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.install-steps[hidden],
.install-settings__state[hidden] {
    display: none;
}

.install-steps__then {
    color: var(--color-ink-muted);
}

/* Empty state: dashed frame, muted centred text, optional secondary action. */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-6) var(--space-4);
    border: 2px dashed var(--color-line-strong);
    border-radius: var(--radius-lg);
    color: var(--color-ink-muted);
    font: var(--font-body);
    text-align: center;
}

.empty-state__title {
    color: var(--color-ink);
    font: var(--font-title-section);
    overflow-wrap: anywhere;
}

/* Component showcase (/_composants, development and browser checks only). */
.showcase__section,
.showcase__stack {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
}

.showcase__section {
    align-items: stretch;
}

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

/* Admin screens: stacked sections. */
.admin-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.admin-section__title {
    font: var(--font-title-section);
}

/* Profile of a member: the avatar, then the first name (h1) and the friendship button under it. States
   (« Demande envoyée », « Amis ✓ ») are drawn as outlined labels, not as buttons: nothing to activate. */
.member-profile {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
}

.member-profile__identity {
    display: flex;
    /* Under the avatar when the line is too narrow (320px, 200% text). */
    flex: 1 1 12rem;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    min-width: 0;
}

/* The line of the first name (story 3.9): the h1 and, at its end, the round badge of the friendship; the badge goes
   under a name too long for the line. */
.member-profile__name-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    width: 100%;
}

.member-profile__name-line > .page-title {
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
}

.member-profile__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.member-profile__note {
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

/* « Retour » of a profile (story 3.9): the button-icon ‹ of the top bar of the fiche, above the avatar. */
.member-back {
    display: flex;
    margin-bottom: var(--space-3);
}

/* The badge of the friendship (story 3.9), round (btn--badge, badge-round: components.css), at the end of the line of
   the first name, its name hidden — its colours only here: « Demander en ami » a button on the primary (a person and a
   +), « Demande envoyée » a button on the soft sun (a person and an hourglass: it offers to take the request back),
   « Amis » a state on the soft olive (two people). Never told by its colour alone: its icon and its name tell it. */
.member-profile__badge {
    display: inline-flex;
    margin: 0 0 0 auto;
}
.friend-badge--ask {
    background: var(--color-primary);
    color: var(--color-on-primary);
}
.friend-badge--sent {
    border-color: var(--color-sun-border);
    background: var(--color-sun-soft);
    color: var(--color-on-sun-soft);
}
.friend-badge--friends {
    background: var(--color-olive-soft);
    color: var(--color-olive);
}

/* « Dossiers » of a profile (story 3.9): the title and the magnifier at its right (dossier-search), then a line with the
   number at the left and « Trier par » at its right — under it on a narrow screen or with a large text —, the rows,
   « Voir plus » centred under them. */
.member-dossiers {
    margin-top: var(--space-6);
}
.member-dossiers__head {
    display: flex;
    align-items: center;
    min-height: var(--touch-min);
}
.member-dossiers__title {
    margin: 0;
}
.member-dossiers .dossier-search__head {
    min-height: var(--touch-min);
}
.member-dossiers__list {
    display: block;
    margin-top: var(--space-3);
}
.member-dossiers__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
}
.member-dossiers__count {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}
.member-dossiers__more {
    display: flex;
    justify-content: center;
    margin-top: var(--space-4);
}

/* « Nouveau dossier » (dossier/new.html.twig): the capture-title, then the form — the title, the story (« Ton récit »
   and its bar); « Enregistrer » and « Soumettre » in the fixed action bar (.me-save). */
.page-title--capture {
    font: var(--font-capture-title);
}

.dossier-screen {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

/* The story of « Nouveau dossier » (story 2.21, the mockup « Récit »): one card of the same make as « Médias » and
   « Héros et témoins », headed « Récit », the field « Ton récit », then its bar — never a card inside the card. */
.story-card > .dossier-writing .audio-card {
    padding: 0;
    border-radius: 0;
    background: none;
}

.dossier-writing {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.dossier-writing[hidden] {
    display: none;
}

/* The title and « Ton récit » while a recording is made, sent or transcribed (story 2.21): read-only, greyed, out of
   reach of a tap (the text the server adds is drawn in them meanwhile). Told by the words under the text too. */
.dossier-screen .text-field__input[readonly] {
    border-color: var(--color-line);
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    pointer-events: none;
}

/* « L'IA écoute ton audio… », under the end of the text: a dot of the sun, then the sentence. */
.story-listening {
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
}

.story-listening__dot {
    width: 0.625rem;
    height: 0.625rem;
    flex: none;
    border-radius: var(--radius-full);
    background: var(--color-sun-strong);
}

.dossier-audio {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* What the AI makes of the story (story 2.5, dossier/_ai_status.html.twig), under the audio in the card of the
   story: lines of skeleton and a sentence while it listens or prepares, else its texts under tabs. No surface
   of its own: it belongs to the card. */
.ai-status {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

/* Where each video of the story stands (story 2.6), one line each, above the rest of the zone; and each photo of
   the story whose analysis failed (story 2.7), the same way. */
.ai-media-lines {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

/* What the AI heard of the audio goes with it (new-dossier): hidden on « Texte ». */
.ai-status[hidden],
.ai-tabs[hidden],
.ai-tabs [hidden] {
    display: none;
}

.ai-status__lines {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.ai-status__text {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

/* The tabs of « Récit » (story 2.22, dossier/_story_tabs.html.twig): « Ton récit », « IA fidèle », « IA enjouée », the
   radio buttons of the story published, drawn as the pill of the mockup — a sunken track holding three thirds, the one
   shown a raised light surface, told without colour too by its bolder label. A tab of the AI never asked is greyed. */
.ai-tabs {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.ai-tabs__list {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-1);
    min-width: 0;
    padding: var(--space-1);
    border-radius: var(--radius-full);
    background: var(--color-surface-sunken);
}

.ai-tabs__tab {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    min-width: 0;
    min-height: var(--touch-min);
    padding: var(--space-2) var(--space-1);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--color-ink-muted);
    font: var(--font-label);
    font-weight: 700;
    text-align: center;
    overflow-wrap: anywhere;
    cursor: pointer;
}

/* Hidden but still focusable and announced: its focus ring is drawn around its whole third. */
.ai-tabs__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}

.ai-tabs__tab:has(.ai-tabs__radio:checked) {
    background: var(--color-surface-card);
    color: var(--color-ink);
    font-weight: 900;
}

.ai-tabs__tab:has(.ai-tabs__radio:focus-visible) {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: calc(-1 * var(--focus-ring-width));
}

/* A version never asked, or waiting for a recording (story 2.22): greyed, out of reach. */
.ai-tabs__tab:has(.ai-tabs__radio:disabled) {
    opacity: 0.6;
    cursor: not-allowed;
}

/* « L'onglet affiché sera publié », right of the heading « Récit ». */
.story-card__published {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

/* With the tabs at work, the field of the tab shown is named by its tab: its label is left to the screen readers. */
.story-card--tabs .ai-tabs__panel .text-field__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* « Publier aussi l'audio » (story 2.22): its words, then a switch of the touch size, told by its knob's place and its
   colour together. */
.story-switch {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: var(--touch-min);
}

.story-switch__label {
    font: var(--font-label);
    font-weight: 700;
    cursor: pointer;
}

.story-switch__input {
    position: relative;
    flex: none;
    width: 3rem;
    height: 1.75rem;
    margin: 0;
    border: 2px solid var(--color-line-strong);
    border-radius: var(--radius-full);
    background: var(--color-surface-sunken);
    appearance: none;
    cursor: pointer;
}

.story-switch__input::before {
    content: "";
    position: absolute;
    top: 0.125rem;
    left: 0.125rem;
    width: 1.25rem;
    height: 1.25rem;
    border-radius: var(--radius-full);
    background: var(--color-ink-muted);
}

.story-switch__input:checked {
    border-color: var(--color-primary);
    background: var(--color-primary);
}

.story-switch__input:checked::before {
    left: auto;
    right: 0.125rem;
    background: var(--color-on-ink);
}

.story-switch__input:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: 2px;
}

@media (forced-colors: active) {
    .ai-tabs__tab:has(.ai-tabs__radio:checked) {
        background: Highlight;
        color: HighlightText;
        forced-color-adjust: none;
    }

    /* Told by the system colours themselves: the track and the knob never the same. */
    .story-switch__input {
        border-color: CanvasText;
        background: Canvas;
        forced-color-adjust: none;
    }

    .story-switch__input::before {
        background: CanvasText;
    }

    .story-switch__input:checked {
        border-color: Highlight;
        background: Highlight;
    }

    .story-switch__input:checked::before {
        background: HighlightText;
    }
}

/* Its mark (story 2.22): three dots while it is prepared, « ! » once it failed, the clock once « Ton récit » changed. */
.ai-tabs__dots,
.ai-tabs__mark,
.ai-tabs__stale {
    display: inline-flex;
    align-items: center;
    gap: 0.15em;
    margin-inline-start: var(--space-1);
    vertical-align: middle;
}

.ai-tabs__stale[hidden] {
    display: none;
}

.ai-tabs__dots > span {
    width: 0.3em;
    height: 0.3em;
    border-radius: var(--radius-full);
    background: currentColor;
}

@media (prefers-reduced-motion: no-preference) {
    .ai-tabs__dots > span {
        animation: ai-tabs-dot 1.2s ease-in-out infinite;
    }

    .ai-tabs__dots > span:nth-child(2) {
        animation-delay: 0.2s;
    }

    .ai-tabs__dots > span:nth-child(3) {
        animation-delay: 0.4s;
    }
}

@keyframes ai-tabs-dot {
    0%, 80%, 100% {
        opacity: 0.25;
    }

    40% {
        opacity: 1;
    }
}

.ai-tabs__mark {
    justify-content: center;
    min-width: 1.25em;
    padding: 0 0.3em;
    border-radius: var(--radius-full);
    background: var(--color-danger);
    color: var(--color-on-ink);
    font-weight: 800;
}

.ai-tabs__stale .icon {
    width: 1em;
    height: 1em;
}

.ai-tabs__panel[hidden] {
    display: none;
}

.ai-tabs__panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
    border-radius: 0.25rem;
}

.ai-tabs__hint {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

.ai-status__transcription {
    margin: 0 0 var(--space-2);
    font: var(--font-body);
    white-space: pre-line;
    overflow-wrap: anywhere;
}

/* A version of the story (story 2.5, deliverable 2): its text keeps the paragraphs the AI wrote. */
.ai-status__story {
    margin: 0 0 var(--space-2);
    font: var(--font-body);
    white-space: pre-line;
    overflow-wrap: anywhere;
}

/* An uncertain passage the AI flagged instead of guessing: the soft sun with a wavy underline, never the
   colour alone (a <mark>, said as such by the screen readers that tell it). */
.ai-status__uncertain {
    padding-inline: 0.125em;
    border-radius: 0.25rem;
    background: var(--color-sun-soft);
    color: var(--color-on-sun-soft);
    text-decoration: underline wavy var(--color-sun-border);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
}

@media (forced-colors: active) {
    .ai-status__uncertain {
        background: Mark;
        color: MarkText;
    }
}

/* « Embellir avec l'IA » (story 2.5, deliverable 2), under the zone: the button, the reason it is unavailable,
   its line, and the refusal of a request. */
.ai-narrate {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    min-width: 0;
}

.ai-narrate__reason,
.ai-narrate__hint {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

.ai-narrate__alert:empty {
    display: none;
}

.ai-narrate__alert {
    color: var(--color-danger);
    font: var(--font-caption);
    font-weight: 700;
    overflow-wrap: anywhere;
}

/* Where the AI failed (story 2.5, deliverable 3): its sentence, « Réessayer », and the refusal of a retry. The
   sentence is said in words, never by a colour alone. */
.ai-failed {
    align-items: flex-start;
}

.ai-failed__text {
    margin: 0;
    font: var(--font-body);
    overflow-wrap: anywhere;
}

.ai-failed__alert:empty {
    display: none;
}

.ai-failed__alert {
    color: var(--color-danger);
    font: var(--font-caption);
    font-weight: 700;
    overflow-wrap: anywhere;
}

.audio-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
    padding: var(--space-4);
    border-radius: var(--radius-xl);
    background: var(--color-surface-card);
}

.audio-card [hidden],
.audio-notice[hidden],
.audio-notice__actions[hidden] {
    display: none !important;
}

/* The confirm-sheets take no room in the card (their dialogs open above the page). */
.audio-card > .sheet-host {
    display: contents;
}

/* The bar under « Ton récit » (story 2.21): the player taking the room left, its bin, then the microphone — or, while
   recording, the red Stop button saying the whole duration and its limit. On two lines only when the text is too large
   for one (320px, 200%). */
.audio-card__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    min-width: 0;
}

.audio-card__bar > .audio-card__media {
    flex: 1 1 12rem;
}

.audio-card__bin,
.audio-card__mic {
    display: grid;
    place-items: center;
    width: var(--touch-min);
    height: var(--touch-min);
    flex: none;
    padding: 0;
    border-radius: var(--radius-full);
    cursor: pointer;
}

.audio-card__bin {
    border: 0;
    background: transparent;
    color: var(--color-ink-muted);
}

.audio-card__mic {
    border: 0;
    background: var(--color-primary);
    color: var(--color-on-primary);
}

/* Unavailable (2:00 in all; the bin while recording or transcribing): greyed, its reason said. */
.audio-card__bin[aria-disabled="true"],
.audio-card__mic[aria-disabled="true"] {
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    cursor: not-allowed;
}

.audio-card__bin[aria-disabled="true"] {
    background: transparent;
    opacity: 0.45;
}

/* « IA » (story 2.22), at the end of the bar: a pill outlined in the primary colour, greyed while unavailable (its reason
   said under the bar, in words). */
.audio-card__ai {
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    flex: none;
    padding: 0 var(--space-3);
    border: 1.5px solid var(--color-primary);
    border-radius: var(--radius-full);
    background: var(--color-surface-card);
    color: var(--color-primary-strong);
    font: var(--font-button);
    cursor: pointer;
}

.audio-card__ai[aria-disabled="true"] {
    border-color: var(--color-line-strong);
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    cursor: not-allowed;
}

.audio-card__ai:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: 2px;
}

/* Why « IA » is unavailable, a request it refused, and « plus qu'un essai » (story 2.22). */
.ai-request__reason {
    margin: var(--space-2) 0 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

.ai-request__reason[hidden],
.ai-request__alert:empty {
    display: none;
}

.ai-request__alert {
    margin-top: var(--space-2);
    color: var(--color-danger);
    font: var(--font-caption);
    font-weight: 700;
    overflow-wrap: anywhere;
}

.ai-request__warning {
    margin: 0;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-sun-border);
    border-radius: var(--radius-lg);
    background: var(--color-sun-soft);
    color: var(--color-on-sun-soft);
    font: var(--font-body);
    font-weight: 700;
    overflow-wrap: anywhere;
}

/* Never wider than the bar: at 320px and 200% text, « 0:24 / 2:00 » goes on two lines. */
.audio-card__stop {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-width: 0;
    max-width: 100%;
    min-height: var(--touch-min);
    flex: 0 1 auto;
    padding: var(--space-1) var(--space-4);
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-danger);
    color: var(--color-on-ink);
    font: var(--font-button);
    cursor: pointer;
}

.audio-card__dot {
    width: 0.75rem;
    height: 0.75rem;
    flex: none;
    border-radius: var(--radius-xs);
    background: var(--color-on-ink);
    animation: audio-blink 1s steps(2, jump-none) infinite;
}

@keyframes audio-blink {
    50% {
        opacity: 0.4;
    }
}

.audio-card__clock {
    min-width: 0;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

.audio-card__limit-reason {
    margin: 0;
    text-align: end;
}

.audio-player__toggle {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    flex: none;
    padding: 0;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-primary);
    color: var(--color-on-primary);
    box-shadow: 0 4px 12px var(--color-shadow-primary);
    cursor: pointer;
}

/* The player of the audio, greyed and out of reach while it is sent, « Envoi… » over it, centred. */
.audio-card__media {
    position: relative;
    min-width: 0;
}

.audio-card__media:focus {
    outline: none;
}

.audio-card__media:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: 2px;
}

.audio-card[data-state="sending"] .audio-card__player {
    opacity: 0.4;
    filter: grayscale(1);
}

.audio-card__overlay {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    pointer-events: none;
}

/* The pill: an orange dot turning, then « Envoi… ». Out of the sending state, only its (empty) status stays. */
.audio-card__pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    max-width: calc(100% - 2 * var(--space-2));
    padding: var(--space-1) var(--space-3);
    border: 2px solid var(--color-sun-border);
    border-radius: var(--radius-full);
    background: var(--color-sun-soft);
    color: var(--color-on-sun-soft);
    font: var(--font-button);
    overflow-wrap: anywhere;
}

.audio-card:not([data-state="sending"]) .audio-card__pill {
    display: contents;
}

.audio-card:not([data-state="sending"]) .audio-card__spinner {
    display: none;
}

.audio-card__spinner {
    width: 0.9rem;
    height: 0.9rem;
    flex: none;
    border: 3px solid var(--color-sun);
    border-top-color: var(--color-sun-strong);
    border-radius: var(--radius-full);
    animation: audio-spin 0.9s linear infinite;
}

@keyframes audio-spin {
    to {
        transform: rotate(360deg);
    }
}

/* The audio of the story the moderation refused (story 2.10, deliverable 2): said above its card, with why, a danger
   rule and « ! » — never by colour alone. */
.audio-card__refused {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
    padding: var(--space-3);
    border-left: 4px solid var(--color-danger);
    border-radius: var(--radius-xs);
    background: var(--color-surface-sunken);
    color: var(--color-ink);
    font: var(--font-body);
    overflow-wrap: anywhere;
}

.audio-card__refused-mark {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    flex: none;
    border-radius: var(--radius-full);
    background: var(--color-danger);
    color: var(--color-on-ink);
    font-size: 0.875rem;
    font-weight: 900;
    line-height: 1;
}

/* An upload that failed or was refused: its reason, what can be done. */
.audio-card__issue {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    border-left: 4px solid var(--color-danger);
    border-radius: var(--radius-xs);
    background: var(--color-surface-sunken);
}

.audio-card__issue-text {
    margin: 0;
    font: var(--font-body);
    overflow-wrap: anywhere;
}

.audio-card__issue-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

/* lldd's own player (audio-player): the <audio> loses its native controls once the controller runs. */
.audio-player__audio {
    display: block;
    width: 100%;
    min-height: var(--touch-min);
}

.audio-player__audio:not([controls]),
.audio-player__controls[hidden],
.audio-player__error[hidden] {
    display: none;
}

.audio-player {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
}

/* The round ▶ / ⏸ button (56px, as « Arrêter »), the position bar, then « 0:04 / 0:09 »; the time goes
   under the bar when it runs out of room (320px, 200% text). */
.audio-player__controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

.audio-player__toggle .icon {
    width: 1.5rem;
    height: 1.5rem;
    fill: currentColor;
}

.audio-player__pause,
.audio-player[data-playing="true"] .audio-player__play {
    display: none;
}

.audio-player[data-playing="true"] .audio-player__pause {
    display: block;
}

.audio-player__seek {
    --audio-player-progress: 0%;
    flex: 1 1 6rem;
    min-width: 0;
    height: var(--touch-min);
    margin: 0;
    background: transparent;
    cursor: pointer;
    appearance: none;
}

.audio-player__seek::-webkit-slider-runnable-track {
    height: 6px;
    border-radius: var(--radius-full);
    background: linear-gradient(to right, var(--color-primary) var(--audio-player-progress), var(--color-primary-soft) var(--audio-player-progress));
}

.audio-player__seek::-webkit-slider-thumb {
    width: 20px;
    height: 20px;
    margin-top: -7px;
    border: 2px solid var(--color-surface-card);
    border-radius: var(--radius-full);
    background: var(--color-primary);
    appearance: none;
}

.audio-player__seek::-moz-range-track {
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--color-primary-soft);
}

.audio-player__seek::-moz-range-progress {
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
}

.audio-player__seek::-moz-range-thumb {
    width: 16px;
    height: 16px;
    border: 2px solid var(--color-surface-card);
    border-radius: var(--radius-full);
    background: var(--color-primary);
}

.audio-player__time {
    font: var(--font-caption);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.audio-player__error {
    margin: 0;
    color: var(--color-danger);
    font: var(--font-caption);
}

/* The audio bubble of the fiche (story 3.1, deliverable 2): a card, the round button, the wave of the sound as its
   slider (played in tomato, the rest in waveform-idle), « Audio d'origine · 0:18 / 1:12 » at its right — under the
   wave below 360px, or once the text is enlarged —, then « Voir la transcription ▾ ». */
.audio-bubble {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
    padding: var(--space-3) var(--space-4) var(--space-3) var(--space-3);
    border-radius: var(--radius-xl);
    background: var(--color-surface-card);
}

.audio-player--wave .audio-player__controls {
    gap: var(--space-2) var(--space-3);
}

.audio-player__wave {
    display: flex;
    flex: 1 1 6rem;
    align-items: center;
    /* A gap that shrinks with the wave: the 64 bars always fit in it. */
    gap: 0.4%;
    min-width: 0;
    height: var(--touch-min);
    border-radius: var(--radius-xs);
    cursor: pointer;
    /* The lowest bar, the same for a silence drawn and for flat bars. */
    --bar-min: 4px;
    /* A horizontal drag moves the playing; a vertical one still scrolls the page; never a selection nor a menu. */
    touch-action: pan-y;
    position: relative;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}

/* The real control of the wave: a native range out of sight over it, reached by the keyboard and the screen readers;
   the pointer goes to the wave. Its focus is drawn around the wave. */
.audio-player__seek--wave {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
}

.audio-player__wave:has(.audio-player__seek--wave:focus-visible) {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: var(--focus-ring-offset);
    box-shadow: 0 0 0 var(--focus-halo-width) var(--color-focus-halo);
}

.audio-player__bar {
    flex: 1 1 0;
    min-width: 0;
    height: max(var(--bar-min), var(--bar, 0%));
    border-radius: var(--radius-full);
    background: var(--color-waveform-idle);
}

/* No wave drawn yet: flat bars, the audio playable all the same. */
.audio-player__wave--flat .audio-player__bar {
    height: var(--bar-min);
}

.audio-player__bar.is-played {
    background: var(--color-primary);
}

.audio-player--wave .audio-player__time {
    flex: 0 1 auto;
    max-width: 8.5rem;
    margin-left: auto;
    color: var(--color-ink-muted);
    font-weight: 600;
    text-align: right;
    white-space: normal;
}

/* Below 360px, under the wave, on its own line. */
@media (max-width: 359.98px) {
    .audio-player--wave .audio-player__time {
        flex-basis: 100%;
        max-width: none;
    }
}

.transcript {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* A link-button, at the right of the bubble. */
.transcript__toggle {
    display: inline-flex;
    align-self: flex-end;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--touch-min);
    padding: 0 var(--space-1);
    border: 0;
    background: transparent;
    color: var(--color-ink);
    font: var(--font-label);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.transcript__toggle[hidden] {
    display: none;
}

.transcript__arrow {
    display: inline-block;
    text-decoration: none;
}

.transcript__toggle[aria-expanded="true"] .transcript__arrow {
    transform: rotate(180deg);
}

.transcript__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding-top: var(--space-3);
    border-top: 2px dashed var(--color-line);
}

.transcript__body[hidden] {
    display: none;
}

.transcript__text {
    margin: 0;
    font: var(--font-body);
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.transcript__by {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

/* The message of an interruption, of the maximum reached, or of a microphone refused. */
.audio-notice {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-left: 4px solid var(--color-line-field);
    border-radius: var(--radius-xs);
    background: var(--color-surface-ground);
}

.audio-notice__text {
    margin: 0;
    font: var(--font-body);
}

.audio-notice__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

@media (forced-colors: active) {
    .audio-card__stop,
    .audio-card__mic,
    .audio-player__toggle {
        border: 2px solid ButtonText;
    }

    .audio-card__dot {
        forced-color-adjust: none;
        background: ButtonText;
    }

    /* The wave in the system colours: what is played in Highlight, the rest in CanvasText — two fills of full contrast,
       visible however thin the bars. */
    .audio-player__bar {
        forced-color-adjust: none;
        background: CanvasText;
    }

    .audio-player__bar.is-played {
        background: Highlight;
    }

    .audio-player__wave:has(.audio-player__seek--wave:focus-visible) {
        outline-color: Highlight;
    }

    /* The bar in the system colours: its track, what is played, and its thumb. */
    .audio-player__seek::-webkit-slider-runnable-track {
        forced-color-adjust: none;
        border: 1px solid CanvasText;
        background: linear-gradient(to right, Highlight var(--audio-player-progress), Canvas var(--audio-player-progress));
    }

    .audio-player__seek::-moz-range-track {
        forced-color-adjust: none;
        border: 1px solid CanvasText;
        background: Canvas;
    }

    .audio-player__seek::-moz-range-progress {
        forced-color-adjust: none;
        background: Highlight;
    }

    .audio-player__seek::-webkit-slider-thumb {
        forced-color-adjust: none;
        border-color: Canvas;
        background: ButtonText;
    }

    .audio-player__seek::-moz-range-thumb {
        forced-color-adjust: none;
        border-color: Canvas;
        background: ButtonText;
    }
}

/* « Médias » of « Nouveau dossier » (story 2.2, dossier/_photos.html.twig; deliverable 4, the mockup « Médias A »):
   one card, as the « Audio » one — « Médias » and what it holds on one line; three add buttons, a third of the
   width each, dashed, a large icon over the label, 88px tall while there is no medium, 72px once the gallery is
   there; under them the gallery, three square media-items by row (kept at 320px and 200% text: a media-item
   grows taller rather than hide what it says). Each media-item tells its type by a pill on its top left; its
   state is drawn over it, never by colour alone: « Envoi… » in a centred pill over a light veil, « Échec » and
   « Refusé » with a 3px danger outline and « ! », « Réessayer » for a failure, the word for a refusal. Its ✕ is
   a 44px target on its top right corner. */
/* The title of a dossier, in a card like the ones under it: the field itself carries no surface of its own. */
.dossier-title-card {
    gap: var(--space-2);
}

.media-card {
    /* The gallery counts its columns against the width the card leaves it, not the screen's: with large text
       the paddings and the touch targets grow while the screen does not. */
    container: media-card / inline-size;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
    padding: var(--space-4);
    border-radius: var(--radius-xl);
    background: var(--color-surface-card);
}

.media-card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    column-gap: var(--space-3);
    row-gap: var(--space-1);
    min-width: 0;
}

.media-card__title {
    margin: 0;
    font: var(--font-button);
}

.media-card__count {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    font-weight: 700;
    overflow-wrap: anywhere;
}

.media-card__count:empty {
    display: none;
}

/* Under the gallery: the sentence of each medium that failed or was refused. */
.media-card__alert {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    color: var(--color-danger);
    font: var(--font-caption);
    font-weight: 700;
    overflow-wrap: anywhere;
}

/* Empty, it stays in the accessibility tree (a live region must be there before it speaks), taking no room. */
.media-card__alert:empty {
    margin-top: calc(-1 * var(--space-3));
}

.media-card__alert > p {
    margin: 0;
}

/* The three add buttons, all labels (« + Photo », « + Vidéo », « + Audio »): their input covers them, visually
   hidden, its focus ring drawn around the button. */
.media-add {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
}

.add-media {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-width: 0;
    min-height: 88px;
    margin: 0;
    padding: var(--space-1);
    border: 2px dashed var(--color-line-field);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-ink);
    font: var(--font-button);
    font-size: 0.875rem;
    text-align: center;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.add-media[hidden] {
    display: none;
}

.media-card:has(.media-tray > .media-item) .add-media {
    min-height: 72px;
}

.add-media__icon {
    width: 1.5rem;
    height: 1.5rem;
}

.add-media__label {
    max-width: 100%;
}

.add-media__input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}

.add-media:has(.add-media__input:focus-visible) {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: var(--focus-ring-offset);
}

/* The gallery: hidden while it holds nothing. */
.media-tray {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.media-tray:not(:has(> .media-item)) {
    display: none;
}

/* A square that plays (a video, an audio) carries two targets of --touch-min: its ✕ on its top right corner
   and the tap that plays it, over the whole square. The ✕ is drawn last, so the play target keeps its middle
   only while the square is at least twice --touch-min. When the card is too narrow for three such squares —
   a narrow screen, or large text, which grows the targets without growing the card — the gallery falls back
   to two, then to one. A gallery of photos alone has one target and keeps its three columns. */
@container media-card (max-width: 17.5rem) {
    .media-tray:has(.media-item__open) {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container media-card (max-width: 11.5rem) {
    .media-tray:has(.media-item__open) {
        grid-template-columns: minmax(0, 1fr);
    }
}

.media-item {
    min-width: 0;
}

/* A square at least: what it says over it (a state, « Réessayer ») makes it taller rather than cut off. */
.media-item__frame {
    position: relative;
    display: grid;
    min-width: 0;
    aspect-ratio: 1 / 1;
    border-radius: var(--radius-sm);
    background: var(--color-surface-sunken);
}

.media-item[data-kind="audio"] .media-item__frame {
    background: var(--color-primary-soft);
}

.media-item[data-state="failed"] .media-item__frame,
.media-item[data-state="refused"] .media-item__frame {
    outline: 3px solid var(--color-danger);
    outline-offset: -3px;
}

.media-item__image {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    border-radius: inherit;
    object-fit: cover;
}

/* A photo the browser cannot show (HEIC outside Safari): a placeholder. */
.media-item__placeholder {
    display: none;
    position: absolute;
    inset: 0;
    margin: auto;
    width: 2rem;
    height: 2rem;
    color: var(--color-ink-muted);
}

.media-item--no-preview .media-item__image {
    visibility: hidden;
}

.media-item--no-preview .media-item__placeholder {
    display: block;
}

/* An audio: a tomato wave (decorative), and over it the same wave in ink, clipped to what has been played
   (story 2.3, deliverable 3: where the audio is, without any bar to move). */
.media-item__wave {
    position: absolute;
    /* The strip of bars itself, centred in the square: the played part is clipped on it, not on the square,
       so the progress matches the sound from its first second to its last. */
    inset: 0;
    width: max-content;
    height: max-content;
    margin: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 3px;
}

.media-item__wave > span {
    width: 4px;
    height: 10px;
    border-radius: 2px;
    background: var(--color-primary);
}

.media-item__wave > span:nth-child(2) {
    height: 18px;
}

.media-item__wave > span:nth-child(3) {
    height: 26px;
}

.media-item__wave > span:nth-child(4) {
    height: 14px;
}

.media-item__wave > span:nth-child(5) {
    height: 22px;
}

.media-item__wave > span:nth-child(6) {
    height: 30px;
}

.media-item__wave > span:nth-child(7) {
    height: 16px;
}

.media-item__wave > span:nth-child(8) {
    height: 24px;
}

.media-item__wave > span:nth-child(9) {
    height: 12px;
}

/* The played part, from the left; nothing shown while nothing has been played. Less motion asked for, it
   jumps to where it is: base.css cuts every transition down to nothing. */
.media-item__wave--played {
    clip-path: inset(0 calc(100% - var(--media-played, 0%)) 0 0);
    transition: clip-path var(--motion-duration-fast) linear;
}

.media-item__wave--played > span {
    background: var(--color-ink);
}

/* Its type, on its top left: decorative (its name says it). */
.media-item__kind {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 2;
    pointer-events: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-surface-card) 92%, transparent);
    color: var(--color-ink);
}

.media-item__kind-icon {
    width: 1rem;
    height: 1rem;
}

/* A video or an audio: ▶ and its duration, on its bottom left. */
.media-item__duration {
    position: absolute;
    bottom: 6px;
    left: 6px;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    max-width: calc(100% - 12px);
    padding: 3px var(--space-2);
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-ink) 72%, transparent);
    color: var(--color-surface-card);
    font: var(--font-caption);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.media-item__duration[hidden] {
    display: none;
}

.media-item__play {
    width: 0.75rem;
    height: 0.75rem;
    fill: currentColor;
    stroke: none;
}

/* The state, over the media-item. « Envoyé » draws nothing: its word is only said. */
.media-item__veil {
    position: relative;
    z-index: 1;
    grid-area: 1 / 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-width: 0;
    padding: var(--space-2) var(--space-1);
    border-radius: inherit;
}

.media-item[data-state="uploading"] .media-item__veil {
    background: color-mix(in srgb, var(--color-surface-card) 55%, transparent);
}

.media-item[data-state="failed"] .media-item__veil,
.media-item[data-state="refused"] .media-item__veil {
    background: color-mix(in srgb, var(--color-surface-card) 70%, transparent);
}

.media-item__label {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    max-width: 100%;
    color: var(--color-ink);
    font: var(--font-caption);
    font-weight: 800;
    text-align: center;
    overflow-wrap: anywhere;
}

/* Focused by the controller only (« Envoi… » after « Réessayer », or after ✕ on the one before): outlined. */
.media-item__label:focus {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: 2px;
}

/* « Envoi… »: a pill, an orange dot turning. */
.media-item[data-state="uploading"] .media-item__label {
    flex-direction: row;
    padding: var(--space-1) var(--space-2);
    border: 2px solid var(--color-line);
    border-radius: var(--radius-full);
    background: var(--color-surface-card);
}

.media-item[data-state="sent"] .media-item__label,
.media-item[data-state="failed"] .media-item__state,
.media-item__reason {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.media-item[data-state="refused"] .media-item__state {
    color: var(--color-danger);
}

.media-item__spinner {
    display: none;
    width: 0.9rem;
    height: 0.9rem;
    flex: none;
    border: 3px solid var(--color-sun);
    border-top-color: var(--color-sun-strong);
    border-radius: var(--radius-full);
    animation: audio-spin 0.9s linear infinite;
}

.media-item[data-state="uploading"] .media-item__spinner {
    display: block;
}

@media (prefers-reduced-motion: reduce) {
    .media-item__spinner {
        animation: none;
    }
}

.media-item__mark {
    display: none;
    place-items: center;
    width: 22px;
    height: 22px;
    flex: none;
    border-radius: var(--radius-full);
    background: var(--color-danger);
    color: var(--color-on-ink);
    font-size: 0.875rem;
    font-weight: 900;
    line-height: 1;
}

.media-item[data-state="failed"] .media-item__mark,
.media-item[data-state="refused"] .media-item__mark {
    display: grid;
}

.media-item__retry {
    display: none;
    align-items: center;
    justify-content: center;
    max-width: 100%;
    min-height: var(--touch-min);
    padding: 0 var(--space-3);
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-ink);
    color: var(--color-surface-card);
    font: var(--font-caption);
    font-weight: 800;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.media-item[data-state="failed"] .media-item__retry {
    display: flex;
}

.media-item__bar {
    display: none;
    position: absolute;
    right: var(--space-3);
    bottom: var(--space-2);
    left: var(--space-3);
    height: 4px;
    border-radius: var(--radius-full);
    background: var(--color-primary-soft);
    overflow: hidden;
}

.media-item[data-state="uploading"] .media-item__bar {
    display: block;
}

.media-item__progress {
    display: block;
    width: max(var(--media-progress, 0%), 8%);
    height: 100%;
    background: var(--color-primary);
    transition: width var(--motion-duration-fast) var(--motion-easing);
}

/* A video « Envoyé »: the whole square plays it (« Lire la vidéo n », story 2.3, deliverable 2), under its ✕. */
.media-item__open {
    display: none;
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: inherit;
    background: none;
    cursor: pointer;
}

.media-item[data-state="sent"] .media-item__open {
    display: block;
}

.media-item__open:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: calc(-1 * var(--focus-ring-width));
}

/* The video played, in the native full screen player. Invisible over its square until the full screen shows
   it (never the browser's loading or error face in the tile for an instant), seen there only when played in
   place (no full screen). */
.media-item__player:not(:fullscreen, .media-item__player--inline) {
    opacity: 0;
}

.media-item__player {
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    border-radius: inherit;
    background: #000;
    object-fit: contain;
}

/* ✕: a 44px target over the top-right corner, drawn as a small round ink mark; while it is sent too (it
   cancels the upload). */
.media-item__remove {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 3;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    width: var(--touch-min);
    height: var(--touch-min);
    padding: 6px;
    border: 0;
    background: none;
    color: var(--color-surface-card);
    cursor: pointer;
}

.media-item__remove-icon {
    width: 28px;
    height: 28px;
    padding: 7px;
    border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-ink) 72%, transparent);
    stroke-width: 3;
}

.media-item__remove:focus-visible {
    outline-offset: calc(-1 * var(--focus-ring-width));
}

/* « Couverture » under a photo or a video once it is « Envoyé » (story 2.18): a small text button, a 44px target across
   the square; on the one chosen, the mark « ✓ Couverture » in ink on a soft ground (said by its text, never by its
   colour alone), the same button no longer active. A narrow column breaks the word rather than overflow. */
.media-item__cover {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    width: 100%;
    min-height: var(--touch-min);
    margin-top: var(--space-1);
    padding: 0 var(--space-1);
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    text-decoration: underline;
    text-underline-offset: 0.2em;
    hyphens: auto;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.media-item[data-state="sent"] .media-item__cover {
    display: flex;
}

.media-item[data-cover="true"] .media-item__cover {
    background: var(--color-primary-soft);
    color: var(--color-ink);
    font-weight: 800;
    text-decoration: none;
    cursor: default;
}

/* A photo is a row of the list of « Médias » (story 2.23, deliverable 2, the mockup « 6 · Description d'une photo »): its
   square on the left, as wide as a square of the gallery (a third of it, its gaps aside), then « Photo n », « Couverture »,
   its description and « Modifier la description », once it is « Envoyé ». The videos and the audios keep their squares,
   three by row. */
.media-item[data-kind="photo"] {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: calc((100% - 2 * var(--space-2)) / 3) minmax(0, 1fr);
    align-items: start;
    gap: var(--space-3);
}

.media-item__meta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
    min-width: 0;
}

.media-item__title {
    margin: 0;
    font: var(--font-label);
}

.media-item[data-kind="photo"] .media-item__cover {
    width: auto;
    min-width: var(--touch-min);
    max-width: 100%;
    margin-top: 0;
    justify-content: flex-start;
}

.media-item__description {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
    white-space: pre-line;
}

.media-item[data-description="none"] .media-item__description {
    font-style: italic;
}

.media-item__describe {
    display: none;
    align-items: center;
    min-width: var(--touch-min);
    max-width: 100%;
    min-height: var(--touch-min);
    padding: 0;
    border: 0;
    background: none;
    color: var(--color-ink);
    font: var(--font-label);
    text-align: left;
    text-decoration: underline;
    text-underline-offset: 0.2em;
    overflow-wrap: anywhere;
    cursor: pointer;
}

/* Said once it is « Envoyé » (the photo of a description refused too). */
.media-item[data-state="sent"] .media-item__describe {
    display: inline-flex;
}

.media-item:not([data-state="sent"]) :is(.media-item__title, .media-item__description) {
    display: none;
}

/* The sheet of a description: its thumbnail and what it is for, the field, then « Effacer » on the left, « Annuler » and
   « Enregistrer » on the right (wrapped under on a narrow screen). */
.description-sheet__photo {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.description-sheet__thumb {
    flex-shrink: 0;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: var(--radius-sm);
    background: var(--color-surface-sunken);
    object-fit: cover;
}

.description-sheet__thumb:not([src]),
.description-sheet__thumb[src=""] {
    visibility: hidden;
}

.description-sheet__hint {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.description-sheet__field {
    margin-top: var(--space-3);
}

.description-sheet__error {
    margin: var(--space-2) 0 0;
}

.description-sheet__error[hidden] {
    display: none;
}

.description-sheet__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
}

.description-sheet__clear {
    margin-right: auto;
}

@media (forced-colors: active) {
    .media-item__frame,
    .media-item__kind,
    .media-item__retry,
    .media-item__cover {
        border: 2px solid ButtonText;
    }

    .media-item__mark,
    .media-item__wave > span {
        forced-color-adjust: none;
        background: ButtonText;
        color: ButtonFace;
    }
}

/* The cast of « Nouveau dossier » (story 2.4, dossier/_cast.html.twig, the mockups « Héros B » and « Carte
   complète »): the same card as « Médias », under the title. Its heading is the count of who would see the
   dossier, in red while nobody would. As long as nobody is chosen, two dashed buttons side by side
   (« + Héros », « + Témoin »), then « + Qui peut le voir » full width, and the sentence that tells the roles
   apart; once someone is chosen, one section by role instead — heroes, witnesses, audience — separated by a
   rule, each with its 48px faces and a round dashed button that adds one. Which of the two is drawn is
   decided here, by the presence of a face in the card. */
/* The count, on the right of the name of the card, as « Médias » counts its media; red and bolder while
   nobody would see the dossier (never by the colour alone: the sentence says it). */
.cast-count {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    font-weight: 700;
    overflow-wrap: anywhere;
}

.cast-count--nobody {
    color: var(--color-danger);
    font-weight: 800;
}

.cast-body {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
}

.cast-body > * {
    min-width: 0;
}

/* Full width, whatever the card shows: the audience (its dashed button and its section) and the sentence. */
.cast-body > .sheet-host:has(> .cast-add--wide),
.cast-hint {
    grid-column: 1 / -1;
}

/* Someone chosen: the sections take the whole width, one under the other, the dashed buttons go, and so does
   the sentence that introduced them. */
.cast-card:has(.cast-face) .cast-body {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
}

.cast-card:has(.cast-face) .cast-add,
.cast-card:has(.cast-face) .cast-hint,
.cast-card:not(:has(.cast-face)) .cast-role,
/* The audience says nobody was added only while it holds no face of its own. */
.cast-role--audience:has(.cast-face) .cast-role__empty {
    display: none;
}

/* A line between the sections, as in the mockup. */
.cast-card:has(.cast-face) .cast-body > .sheet-host + .sheet-host {
    padding-top: var(--space-3);
    border-top: 2px solid var(--color-line);
}

/* The dashed buttons, of the same make as « + Photo »: a large icon over the label. */
.cast-add {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    width: 100%;
    min-width: 0;
    min-height: 88px;
    padding: var(--space-1);
    border: 2px dashed var(--color-line-field);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-ink);
    font: var(--font-button);
    font-size: 0.875rem;
    text-align: center;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.cast-add--wide {
    flex-direction: row;
    gap: var(--space-2);
    min-height: var(--touch-min);
}

.cast-add__icon {
    width: 1.5rem;
    height: 1.5rem;
}

.cast-hint {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

/* One section by role: its name, then its faces. */
.cast-role {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.cast-role__title {
    margin: 0;
    font: var(--font-caption);
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-ink-muted);
}

/* « les témoins compris », under the name of the audience section. */
.cast-role__note {
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
}

.cast-role__empty {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.cast-faces {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.cast-face {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    width: 3.875rem;
    min-width: 0;
}

.cast-face__avatar {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: var(--radius-full);
    color: var(--color-on-ink);
    font: var(--font-title-card);
    font-weight: 900;
}

/* A hero carries a ✓ (decorative: it is under « Héros »). */
.cast-face__mark {
    position: absolute;
    right: -0.125rem;
    bottom: -0.125rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    border: 2px solid var(--color-primary);
    border-radius: var(--radius-full);
    background: var(--color-surface-card);
    color: var(--color-primary);
    font: var(--font-badge);
    line-height: 1;
}

.cast-face__name {
    max-width: 100%;
    font: var(--font-caption);
    font-weight: 800;
    text-align: center;
    overflow-wrap: anywhere;
}

/* The round dashed button that adds one, at the end of the faces. */
.cast-faces__add {
    display: flex;
    align-items: center;
    min-height: 3rem;
}

.cast-face-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch-min);
    height: var(--touch-min);
    padding: 0;
    border: 2px dashed var(--color-line-field);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--color-ink);
    font: var(--font-title-card);
    font-weight: 900;
    line-height: 1;
    cursor: pointer;
}

/* The sheet of the audience: the sentence that says what it is for, then the two tabs (the switch of the
   story), then « Amis » or « Mes groupes ». */
.cast-sheet__hint {
    margin: 0 0 var(--space-3);
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.cast-tabs {
    margin: 0 0 var(--space-3);
    padding: 0;
    border: 0;
}

/* The same pill as the switch of the story: a sunken track holding two halves, the chosen one a raised light
   surface, told without colour too by its bolder label. */
.cast-tabs__switch {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-1);
    padding: var(--space-1);
    border-radius: var(--radius-full);
    background: var(--color-surface-sunken);
}

.cast-tabs__choice {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    min-height: var(--touch-min);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--color-ink-muted);
    font: var(--font-button);
    font-weight: 700;
    overflow-wrap: anywhere;
    cursor: pointer;
}

/* Hidden but still focusable and announced: its focus ring is drawn around its whole half. */
.cast-tabs__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}

.cast-tabs__choice:has(.cast-tabs__radio:checked) {
    background: var(--color-surface-card);
    color: var(--color-ink);
    font-weight: 900;
}

.cast-tabs__choice:has(.cast-tabs__radio:focus-visible) {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: calc(-1 * var(--focus-ring-width));
}

.cast-tabs__label {
    min-width: 0;
}

@media (forced-colors: active) {
    .cast-tabs__choice:has(.cast-tabs__radio:checked) {
        background: Highlight;
        color: HighlightText;
        forced-color-adjust: none;
    }
}

/* One line per group: an icon, its name, how many people it holds, « Ajouter ». */
.cast-groups {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.cast-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-2);
    min-height: var(--touch-min);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    background: var(--color-surface-sunken);
}

.cast-group__icon {
    flex: 0 0 auto;
    width: 1.5rem;
    height: 1.5rem;
}

.cast-group__name {
    flex: 1 1 6rem;
    min-width: 0;
    font: var(--font-body);
    font-weight: 800;
    overflow-wrap: anywhere;
}

.cast-group__count {
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.cast-group__add {
    flex: 0 0 auto;
}

/* Why « Ajouter » does nothing for this group: on its own line, under it. */
.cast-group__none {
    flex: 1 0 100%;
    margin: 0;
}

/* What the last group added, and who it could not. */
.cast-sheet__said {
    margin: var(--space-3) 0 0;
    font: var(--font-caption);
    font-weight: 700;
}

.cast-sheet__said:empty {
    display: none;
}

/* The sheet of a role: a search, then the circle of the author four by row, ✓ on the ones chosen. The search
   is a field like any other (text-field__input): the same surface, the same rounding, the same 16px that
   keeps Safari from zooming. Its box is spaced from the faces under it. */
.cast-sheet__search {
    margin: 0 0 var(--space-3);
}

.cast-search {
    min-height: var(--touch-min);
}

.cast-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.cast-grid__cell[hidden] {
    display: none;
}

.cast-person {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    width: 100%;
    min-width: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--color-ink);
    cursor: pointer;
}

.cast-person__avatar {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: min(3.75rem, 100%);
    aspect-ratio: 1 / 1;
    border-radius: var(--radius-full);
    color: var(--color-on-ink);
    font: var(--font-headline);
}

.cast-person__mark {
    position: absolute;
    right: -0.125rem;
    bottom: -0.125rem;
    display: none;
    align-items: center;
    justify-content: center;
    width: 1.375rem;
    height: 1.375rem;
    border: 2px solid var(--color-primary);
    border-radius: var(--radius-full);
    background: var(--color-surface-card);
    color: var(--color-primary);
    font: var(--font-badge);
    line-height: 1;
}

.cast-person[aria-pressed="true"] .cast-person__mark {
    display: inline-flex;
}

.cast-person__name {
    max-width: 100%;
    font: var(--font-caption);
    font-weight: 800;
    text-align: center;
    overflow-wrap: anywhere;
}

.cast-sheet__none {
    margin: 0;
    padding: var(--space-4) 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    font-weight: 700;
    text-align: center;
}

.cast-sheet__none[hidden] {
    display: none;
}

/* The card, which holds the whole cast: there is no summary block under it any more, its heading carries the
   count of who would see the dossier. */
.cast-block {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

@media (forced-colors: active) {
    .cast-face__mark,
    .cast-person__mark {
        forced-color-adjust: none;
        background: ButtonFace;
        color: ButtonText;
        border-color: ButtonText;
    }
}

/* The action bar of « Nouveau dossier » (a .me-save bar): « Enregistrer » then « Soumettre » (with its round « i »,
   story 4.3), side by side while they fit, one above the other otherwise (320px at 200% text). What is missing to
   submit (story 2.9) is the window of the round, drawn above the bar, right-aligned with its buttons: the bar keeps
   the height of its buttons. Without JavaScript, it is listed under them, in the bar; a bar grown past half of the
   screen flows at the end of the form instead (new-dossier). */
.dossier-actions__buttons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.dossier-actions__buttons > .btn,
.dossier-actions__buttons > .info-round-host {
    flex: 1 1 8rem;
    max-width: 100%;
}

/* The window of the round, aligned on the right edge of the buttons (the gutter of the bar, its content centred), never
   taller than the room left above the bar (it scrolls then). */
.dossier-actions {
    --info-round-panel-right: calc(max(0px, (100% - var(--content-max)) / 2) + var(--me-save-gutter));
    --info-round-panel-max-height: calc(100dvh - var(--me-tab-bar) - var(--me-save-h) - var(--space-4));
}

/* What is missing to submit (story 2.9): without JavaScript, a card with the danger rule of a serious message
   under the buttons; with it, the window of the round « i » (components.css, .info-round__panel), ink ground. One line
   per thing missing, each a link to its field (a target of 44px at least), never told by the colour alone. Empty, it
   takes no room. */
.submit-blockers:empty {
    display: none;
}

.submit-blockers {
    margin-top: var(--space-2);
    padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4);
    border-left: 6px solid var(--color-danger);
    border-radius: var(--radius-md);
    background: var(--color-surface-card);
    color: var(--color-ink);
}

/* « Il manque encore » (story 2.20): every line, under its title. */
.submit-blockers__title {
    margin: 0;
    font: var(--font-body);
    font-weight: 600;
}

.submit-blockers__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.submit-blockers__line {
    display: flex;
    align-items: center;
    min-height: var(--touch-min);
    font: var(--font-body);
    overflow-wrap: anywhere;
}

.submit-blockers__link {
    color: var(--color-ink);
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

.submit-blockers__item + .submit-blockers__item {
    border-top: 1px solid var(--color-line);
}

/* In the window of the round: the ink ground, its text and its rules light. */
html[data-js] .info-round__panel .submit-blockers__link {
    color: inherit;
}

html[data-js] .info-round__panel .submit-blockers__item + .submit-blockers__item {
    border-top-color: color-mix(in srgb, var(--color-on-ink) 20%, transparent);
}

@media (forced-colors: active) {
    .submit-blockers {
        border: 2px solid CanvasText;
        border-left-width: 6px;
    }
}

/* The fiche of a dossier (dossier/sheet.html.twig, stories 2.11 and 3.1): its blocks in their closed order — its
   state, its people, « Visible par », the audio of its story, its story in its whole, its photos and its videos —, one
   under the other. */
.dossier-sheet {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-width: 0;
}

.dossier-story,
.proof-grid {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

.dossier-story__title,
.proof-grid__title {
    margin: 0;
    font: var(--font-title-section);
}

/* « Le récit » (story 3.1, deliverable 3): its paragraphs spaced, the first letter of the first one a tomato drop cap
   (::first-letter, the word whole in the DOM for a screen reader). */
.dossier-story__text,
.dossier-story__original-text {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    font: var(--font-body-story);
}

.dossier-story__paragraph {
    margin: 0;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.dossier-story__text > .dossier-story__paragraph:first-child::first-letter {
    font: var(--font-dropcap);
    color: var(--color-primary);
}

/* The date and where the story comes from. */
.dossier-story__mention {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

/* « Les preuves » (story 3.2, deliverable 1, the proof-grid of key-fiche-dossier): the title and the counter, then
   square cases of 2 columns, 4 from 64rem, their images covering them; a video carries the play pictogram and its
   length on a caption pastille; the caption of a contributor under its case. */
.proof-grid__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0 var(--space-2);
}

.proof-grid__counter {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.proof-grid__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 64rem) {
    .proof-grid__list {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.proof-grid__cell,
.proof-grid__more {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* « Tout voir »: folded, 5 proofs then the button under 64rem, 7 then the other one above; unfolded (or without
   JavaScript), every proof and no button. */
.proof-grid__more {
    display: none;
}

@media not all and (min-width: 64rem) {
    .proof-grid__list--folded .proof-grid__cell--beyond-narrow {
        display: none;
    }

    .proof-grid__list--folded .proof-grid__more--narrow {
        display: flex;
    }
}

@media (min-width: 64rem) {
    .proof-grid__list--folded .proof-grid__cell--beyond-wide {
        display: none;
    }

    .proof-grid__list--folded .proof-grid__more--wide {
        display: flex;
    }
}

.proof {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 1;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--color-surface-sunken);
    color: var(--color-ink);
}

.proof:focus-visible,
.proof-grid__cell:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: var(--focus-ring-offset);
}

.proof__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The play pictogram and the length of a video, on the caption ground (never inverted back: it is no image). */
.proof__play {
    position: absolute;
    left: var(--space-2);
    bottom: var(--space-2);
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2) var(--space-1) var(--space-1);
    border-radius: var(--radius-full);
    background: var(--color-caption-ground);
    color: var(--color-on-ink);
    font: var(--font-label);
}

.proof__play-icon {
    display: grid;
    place-items: center;
    width: 1.625rem;
    height: 1.625rem;
    border-radius: var(--radius-full);
    background: var(--color-on-ink);
    color: var(--color-ink);
}

.proof__play-icon .icon {
    width: 0.875rem;
    height: 0.875rem;
    fill: currentColor;
}

/* A proof still prepared: its text in the middle of a neutral case. */
.proof--preparing {
    display: grid;
    place-items: center;
    padding: var(--space-2);
    text-align: center;
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

.proof__caption {
    margin: var(--space-1) 0 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

/* A case of an addition waiting for the heroes (story 4.3), to the author and the deciders: a plain neutral ground, the
   pictogram of its first medium in the middle, the tag « en attente » in the corner — never its content. */
.proof--pending {
    display: grid;
    place-items: center;
    text-decoration: none;
}

.proof__pending-icon {
    width: 1.75rem;
    height: 1.75rem;
    color: var(--color-ink-muted);
}

.proof__pending-tag {
    position: absolute;
    left: var(--space-2);
    bottom: var(--space-2);
    max-width: calc(100% - 2 * var(--space-2));
    padding: 0.1875rem var(--space-2);
    border-radius: var(--radius-full);
    background: var(--color-sun-soft);
    color: var(--color-on-sun-soft);
    font: var(--font-caption);
    font-weight: 900;
    overflow-wrap: anywhere;
}

/* « Tout voir »: a whole case (44px at least, whatever its width), its text in the middle. */
.proof-grid__all {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-height: var(--touch-min);
    padding: var(--space-2);
    border: 0;
    font: var(--font-label);
    cursor: pointer;
}

.proof-grid__count {
    font: var(--font-title-section);
}

.proof-grid__transcript {
    margin-top: var(--space-1);
}

/* « Les preuves » without any proof (story 4.3): its title, then « Ton ajout » alone. */
.proof-grid-empty {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
}

/* « Ton ajout » (story 4.3, dossier/sheet/_addition.html.twig, the mockup validated on 2026-10-30): under the grid, the
   three buttons of « Nouveau dossier » under « Ajouter un média »; the first medium chosen opens the card « Ton ajout »
   — its tray, its media squares (a photo too: no description nor cover here), the AI at work or failed, then
   « Envoyer aux héros », dimmed, with its round « i » whose window opens above it, in the flow of the fiche. Nothing of
   it without JavaScript: nothing could be sent. */
html:not([data-js]) .addition {
    display: none;
}

.addition {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
}

.addition__tray:not(:has(.media-tray > .media-item)) {
    padding: 0;
    border-radius: 0;
    background: none;
}

.addition__tray:not(:has(.media-tray > .media-item)) .addition__title {
    color: var(--color-ink-muted);
    font: var(--font-label);
}

.addition__tray:has(.media-tray > .media-item) .addition__title-empty,
.addition__tray:not(:has(.media-tray > .media-item)) .addition__title-mine,
.addition:not(:has(.media-tray > .media-item)) > :is(.addition__ai, .addition__bar, .addition__confirm) {
    display: none;
}

/* An addition sent (« Envoyer aux héros »): its card, its state in a pill, its media in small squares, nothing to add
   to it nor to send. */
.addition__status {
    display: inline-block;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-full);
    background: var(--color-surface-sunken);
    color: var(--color-ink);
    font: var(--font-badge);
}

.addition__sent .addition__title {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.addition__sent-media {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.addition__sent-item {
    display: grid;
    place-items: center;
    width: 4rem;
    height: 4rem;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--color-surface-sunken);
}

.addition__sent-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.addition__sent-icon {
    width: 1.5rem;
    height: 1.5rem;
    color: var(--color-ink-muted);
}

.addition__ready {
    margin: 0;
    padding: var(--space-2) 0;
    font: var(--font-body);
}

.addition__title-mine {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

/* « pas encore envoyé »: the pill of badge-draft, never a state badge of the dossier (its audience is shown none). */
.addition__draft {
    display: inline-block;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-full);
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    font: var(--font-badge);
}

.addition .media-item[data-kind="photo"] {
    display: block;
    grid-column: auto;
}

.addition__ai {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
}

.addition__ai-text {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

.addition__ai--failed .addition__ai-text,
.addition__ai-alert {
    color: var(--color-danger);
    font-weight: 700;
}

.addition__ai-alert {
    flex-basis: 100%;
    margin: 0;
    font: var(--font-caption);
}

.addition__ai-alert:empty {
    display: none;
}

/* The bar of « Envoyer aux héros » in the flow of the fiche: its window above it, never taller than the screen. */
.addition__bar {
    --info-round-panel-right: 0px;
    --info-round-panel-max-height: calc(100dvh - var(--me-tab-bar) - var(--space-8));
    position: relative;
}

/* What the browser alone knows: a medium on its way, one that failed (media-tray marks its tray). The server's
   « Ajoute un média. » goes once a medium not refused is in the tray, « Plus rien ne manque. » while one is not
   sent. */
.addition__blocker:is([data-blocker="uploading"], [data-blocker="upload_failed"]),
.addition:has(.media-tray > .media-item:not([data-state="refused"])) .addition__blocker[data-blocker="media"],
.addition:has([data-uploads-pending]) .addition__ready {
    display: none;
}

.addition:has([data-uploads-sending]) .addition__blocker[data-blocker="uploading"],
.addition:has([data-uploads-pending]:not([data-uploads-sending])) .addition__blocker[data-blocker="upload_failed"] {
    display: block;
}

/* A sending is a wait: the rim of the round turns, as for the AI. */
.addition:has([data-uploads-sending]) .info-round__mark::before {
    border-top-color: var(--color-primary);
}

@media (prefers-reduced-motion: no-preference) {
    .addition:has([data-uploads-sending]) .info-round__mark::before {
        animation: info-round-spin 1s linear infinite;
    }
}

/* Its lines, without rules between them (some hidden); « Abandonner l'ajout » under them, a rule above. */
html[data-js] .addition__blockers .submit-blockers__item + .submit-blockers__item {
    border-top: 0;
}

.addition__discard {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: var(--touch-min);
    margin-top: var(--space-1);
    padding: 0;
    border: 0;
    border-top: 1px solid var(--color-line);
    background: none;
    color: inherit;
    font: var(--font-body);
    font-weight: 600;
    text-align: left;
    text-decoration: underline;
    text-underline-offset: 0.15em;
    overflow-wrap: anywhere;
    cursor: pointer;
}

html[data-js] .info-round__panel .addition__discard {
    border-top-color: color-mix(in srgb, var(--color-on-ink) 20%, transparent);
}

@media (forced-colors: active) {
    .proof,
    .proof__play {
        border: 1px solid CanvasText;
    }
}

/* The viewer of the proofs (story 3.2, deliverable 2, media-viewer of key-fiche-dossier): the whole screen on the viewer
   ground, white text; the counter and ✕ above, the proof in the middle shown whole (contain), the caption of a
   contributor under it, ‹ › under it all — square outlined icon buttons of 44px, visible on every width. */
.media-viewer {
    width: 100%;
    max-width: none;
    height: 100dvh;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: var(--color-viewer-ground);
    color: var(--color-on-ink);
    overflow: hidden;
}

.media-viewer[open] {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
}

.media-viewer::backdrop {
    background: var(--color-viewer-ground);
}

/* The page behind the viewer does not scroll (iOS included). */
html:has(dialog.media-viewer[open]) {
    overflow: hidden;
}

.media-viewer__bar,
.media-viewer__nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    min-width: 0;
    padding: var(--space-2) var(--space-3);
}

.media-viewer__bar {
    padding-top: max(var(--space-2), env(safe-area-inset-top));
}

.media-viewer__nav {
    padding-bottom: max(var(--space-2), env(safe-area-inset-bottom));
}

.media-viewer__counter {
    margin: 0;
    min-width: 0;
    font: var(--font-label);
    font-variant-numeric: tabular-nums;
}

.media-viewer .btn--icon {
    flex: none;
    background: transparent;
    border-color: var(--color-on-ink);
    color: var(--color-on-ink);
}

/* ‹ or › at a bound: as any unavailable button (components.css), on the dark ground — still there and readable,
 * never dimmed: no outline, a sunken ground and a muted ink. */
.media-viewer .btn--icon[aria-disabled="true"] {
    background: color-mix(in srgb, var(--color-on-ink) 12%, transparent);
    border-color: transparent;
    color: color-mix(in srgb, var(--color-on-ink) 65%, var(--color-viewer-ground));
}

@media (forced-colors: active) {
    .media-viewer .btn--icon[aria-disabled="true"] {
        border-color: GrayText;
        color: GrayText;
    }
}

/* The dialog takes the focus of a press on its stage (the arrows keep working): no ring around the whole viewer. */
.media-viewer[tabindex="-1"]:focus {
    outline: none;
    box-shadow: none;
}

/* The swipe is the viewer's own: no scroll under the finger; the pinch to zoom stays. */
.media-viewer__stage {
    display: grid;
    min-height: 0;
    min-width: 0;
    touch-action: pinch-zoom;
    -webkit-user-select: none;
    user-select: none;
}

.media-viewer__slide {
    display: grid;
    grid-template-rows: minmax(0, 1fr) auto;
    justify-items: center;
    gap: var(--space-2);
    min-height: 0;
    min-width: 0;
    margin: 0;
}

.media-viewer__slide[hidden] {
    display: none;
}

.media-viewer__media {
    display: block;
    width: 100%;
    height: 100%;
    min-height: 0;
    object-fit: contain;
    -webkit-user-drag: none;
}

.media-viewer__caption {
    padding: 0 var(--space-3);
    -webkit-user-select: text;
    user-select: text;
    font: var(--font-caption);
    text-align: center;
    overflow-wrap: anywhere;
}

/* A proof shown fades in; never under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
    .media-viewer__slide:not([hidden]) {
        animation: media-viewer-in 160ms ease-out;
    }
}

@keyframes media-viewer-in {
    from {
        opacity: 0;
    }
}

@media (forced-colors: active) {
    .media-viewer {
        border: 1px solid CanvasText;
    }
}

/* The title of the fiche (story 3.1): display, wrapped, never truncated. */
.page-title.dossier-title {
    font: var(--font-display);
    overflow-wrap: anywhere;
}

/* The place of the state of the fiche (story 3.1), under its title: the alert, « Contesté », the badge and the block
   « Héros »; not drawn when it says nothing. A grouping only: its blocks keep the gap of the fiche between them. */
.dossier-sheet__state {
    display: contents;
}

/* « Retour » above the title of the fiche (story 2.12): a square outlined icon button. */
.dossier-back {
    display: flex;
    margin-bottom: var(--space-3);
}

/* The block « Héros » of the fiche (story 2.14; its card and title with the sections of the home screen): one line per
   hero — its avatar (48px), its first name in a bold caption (as cast-face__name), « Envoyer un rappel » or « ✓ Rappel
   envoyé » under it, its badge on the right —, the lines separated by a rule. At 320px and 200% text the badge goes
   under the name. */
.hero-block__lines {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

.hero-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    padding-block: var(--space-3);
}

.hero-line + .hero-line {
    border-top: 1px solid var(--color-line);
}

.hero-line:focus {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: var(--focus-ring-offset);
    box-shadow: none;
}

.avatar.hero-line__avatar {
    width: 3rem;
    height: 3rem;
}

.hero-line__body {
    display: flex;
    flex: 1 1 8rem;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    min-width: 0;
}

.hero-line__name {
    font: var(--font-caption);
    font-weight: 800;
    overflow-wrap: anywhere;
}

.hero-line__form {
    max-width: 100%;
}

.hero-line__reminded {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    font-weight: 700;
}

.hero-line__badge {
    flex: 0 0 auto;
}

/* The state of a dossier submitted, on its fiche (story 2.11; its screen of state, stories 2.9 and 2.10): its badge
   and what happens now. */
.dossier-state {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
    min-width: 0;
}

.dossier-state__status {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
}

.dossier-state__text {
    margin: 0;
    font: var(--font-body);
    overflow-wrap: anywhere;
}

/* A bar that would cover more than half of the screen (new-dossier: its buttons one above the other, at 320px
   and 200% text) closes the form in its flow instead of staying fixed. */
.me-save.dossier-actions--flow {
    --info-round-panel-right: 0px;
    /* The window opens above the bar in the flow of the page (info-round brings it into view): never taller than the
       screen. */
    --info-round-panel-max-height: calc(100dvh - var(--me-tab-bar) - var(--space-8));
    position: relative;
    padding-inline: 0;
    border-top: 0;
    background: none;
}

.me-save.dossier-actions--flow::after {
    content: none;
}

/* No room to keep for a bar that no longer floats: the room of the tab bar (.app-shell) is enough, with a margin
   for its raised capture button. */
.dossier-screen:has(.dossier-actions--flow) {
    padding-bottom: var(--space-4);
}

/* Dossiers (dossier/index.html.twig): a line with « Mes brouillons » and « Trier par » at its right (story 3.8), the
   number of the dossiers a search finds, then the list; « Voir plus » centred under it. On a narrow screen or with a
   large text, « Trier par » goes under « Mes brouillons », still at the right. */
.dossier-index__count {
    margin: 0 0 var(--space-3);
    color: var(--color-ink-muted);
    font: var(--font-caption);
}
.dossier-index__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}
.dossier-index__drafts {
    margin: 0;
}
.dossier-index__list {
    display: block;
}
.dossier-index__more {
    display: flex;
    justify-content: center;
    margin-top: var(--space-4);
}

/* The list and the fiche side by side (story 3.8, deliverable 3; key-fiche-dossier.html, « Desktop »): under 1024px,
   the list alone, as before — the pane of the fiche is not displayed. From 1024px, main spans what the side navigation
   leaves: at the left the list (--list-pane-width, never more than 45% of main: 200% text), its head (the title, the
   search) above its own scroll, its margins narrower with a large text; at the right the fiche of the dossier chosen, its own scroll, within --content-max.
   The row of the dossier open is current (aria-current on its link): on the soft primary, an inner line of ink. */
.dossier-index__pane {
    display: block;
}
.dossier-index__sheet {
    display: none;
}
.dossier-pane__frame {
    display: block;
}
.dossier-pane__content {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    max-width: var(--content-max);
}

@media (min-width: 64rem) {
    .app-main.app-main--panes {
        display: grid;
        grid-template-columns: minmax(0, min(var(--list-pane-width), 45%)) minmax(0, 1fr);
        grid-template-rows: auto auto minmax(0, 1fr);
        gap: 0;
        max-width: none;
        height: 100vh;
        height: 100dvh;
        margin: 0;
        padding: 0;
        /* The side margins of the list, smaller with a large text. */
        --pane-gutter: min(var(--space-4), 1.5vw);
    }
    .app-main--panes > .system-banner-region {
        grid-column: 1 / -1;
        grid-row: 1;
    }
    .app-main--panes > .page-head {
        /* Its suggestions open over the list. */
        z-index: 2;
        grid-column: 1;
        grid-row: 2;
        padding: var(--space-5) var(--pane-gutter);
    }
    .app-main--panes > .dossier-index__pane {
        grid-column: 1;
        grid-row: 3;
        min-height: 0;
        overflow-y: auto;
        padding: 0 var(--pane-gutter) calc(var(--space-8) + var(--toast-room, 0px));
    }
    .app-main--panes > .dossier-index__sheet {
        display: block;
        grid-column: 2;
        grid-row: 2 / 4;
        min-width: 0;
        min-height: 0;
        overflow-y: auto;
        padding: var(--space-5) min(var(--space-8), 5%) calc(var(--space-8) + var(--toast-room, 0px));
    }
    /* Narrow with a large text: « Date | Note » on two lines rather than out of the list. */
    .app-main--panes .dossier-sort__options {
        flex-wrap: wrap;
        max-width: 100%;
    }
    .dossier-row:has(.list-row__link[aria-current="true"]) {
        background: var(--color-primary-soft);
        box-shadow: inset 0 0 0 2px var(--color-ink);
    }
    .dossier-row:has(.list-row__link[aria-current="true"]) .list-row__meta {
        color: var(--color-on-primary-soft);
    }
}

@media (min-width: 64rem) and (forced-colors: active) {
    .dossier-row:has(.list-row__link[aria-current="true"]) {
        outline: 2px solid CanvasText;
        outline-offset: -2px;
    }
}

/* The search of the tab Dossiers (story 3.8, deliverable 2): folded, the title and the magnifier at its right (44px,
   a card circle); open, the bar in place of the title — the h1 visually hidden —: a box with the 2px line of the
   fields (3px of ink with the focus inside) holding the pills, the field and the ✕ at its end, all wrapping; its
   suggestions under it, over the list, by group under their title, the letters that match on the soft sun. */
.dossier-search {
    position: relative;
}
.dossier-search__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}
.dossier-search__title {
    min-width: 0;
    overflow-wrap: anywhere;
}
.dossier-search--open .dossier-search__title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.dossier-search__open,
.dossier-search__close {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--touch-min);
    height: var(--touch-min);
    border-radius: var(--radius-full);
    color: var(--color-ink);
}
.dossier-search__open {
    border: 2px solid var(--color-line-strong);
    background: var(--color-surface-card);
}
.dossier-search__open[hidden],
.dossier-search__form[hidden],
.dossier-search__chips[hidden],
.dossier-search__options[hidden] {
    display: none;
}
.dossier-search__close {
    margin-left: auto;
    background: var(--color-surface-sunken);
}
.dossier-search__box {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-height: 3rem;
    padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3);
    border: 2px solid var(--color-line-field);
    border-radius: var(--radius-sm);
    background: var(--color-surface-card);
}
.dossier-search__box:focus-within {
    border: 3px solid var(--color-ink);
    padding: calc(var(--space-1) - 1px) calc(var(--space-1) - 1px) calc(var(--space-1) - 1px) calc(var(--space-3) - 1px);
}
.dossier-search__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    max-width: 100%;
    margin: 0;
    padding: 0;
    list-style: none;
}
.dossier-search__chip {
    display: inline-flex;
    max-width: 100%;
}
.dossier-search__field {
    flex: 1 1 8rem;
    min-width: 0;
    min-height: var(--touch-min);
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--color-ink);
    /* 16px: no automatic zoom in Safari. */
    font: var(--font-input);
}
.dossier-search__field::placeholder {
    color: var(--color-ink-muted);
    opacity: 1;
}
/* The box draws the focus of its field. */
.dossier-search__field:focus,
.dossier-search__field:focus-visible {
    outline: 1px solid transparent;
    box-shadow: none;
}
.dossier-search__options {
    position: absolute;
    top: calc(100% + var(--space-2));
    right: 0;
    left: 0;
    z-index: 15;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    max-height: 60vh;
    overflow-y: auto;
    padding: var(--space-2);
    border: 2px solid var(--color-line-strong);
    border-radius: var(--radius-md);
    background: var(--color-surface-card);
}
.dossier-search__group {
    display: flex;
    flex-direction: column;
}
.dossier-search__group + .dossier-search__group {
    margin-top: var(--space-1);
    padding-top: var(--space-1);
    border-top: 1px solid var(--color-line);
}
.dossier-search__group-title {
    padding: var(--space-2) var(--space-3) var(--space-1);
    color: var(--color-ink-muted);
    font: var(--font-eyebrow);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.dossier-search__option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--touch-min);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--color-ink);
    font: var(--font-body);
    font-weight: 700;
    overflow-wrap: anywhere;
    cursor: pointer;
}
.dossier-search__option > span {
    min-width: 0;
}
.dossier-search__option[aria-selected="true"] {
    background: var(--color-primary-soft);
    color: var(--color-on-primary-soft);
    outline: 2px solid var(--color-ink);
    outline-offset: -2px;
}
.icon.dossier-search__option-icon {
    width: 1rem;
    height: 1rem;
}
.dossier-search__mark {
    padding: 0;
    border-bottom: 2px solid var(--color-sun-border);
    background: var(--color-sun-soft);
    color: inherit;
}
@media (forced-colors: active) {
    .dossier-search__option[aria-selected="true"] {
        outline-color: Highlight;
    }
    .dossier-search__mark {
        background: Mark;
        color: MarkText;
    }
}

/* « Trier par » Date | Note: a segmented choice of two links on a sunken pill, the current one raised on the card
   colour; each a 44px target. */
.dossier-sort {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-left: auto;
}
.dossier-sort__label {
    color: var(--color-ink-muted);
    font: var(--font-label);
}
.dossier-sort__options {
    display: inline-flex;
    padding: 0.1875rem;
    border-radius: var(--radius-full);
    background: var(--color-surface-sunken);
}
.dossier-sort__option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    padding: 0 var(--space-4);
    border-radius: var(--radius-full);
    color: var(--color-ink-muted);
    font: var(--font-label);
    font-weight: 800;
    text-decoration: none;
}
.dossier-sort__option[aria-current="true"] {
    background: var(--color-surface-card);
    color: var(--color-ink);
}
@media (forced-colors: active) {
    .dossier-sort__option[aria-current="true"] {
        outline: 2px solid CanvasText;
    }
}

/* A row of the tab Dossiers (dossier/_published_rows.html.twig): its heroes in bold, in ink; the bubble of its
   comments on the line of the text. */
.dossier-row__heroes {
    color: var(--color-ink);
    font-weight: 800;
}
.dossier-row__comments {
    white-space: nowrap;
}
.icon.dossier-row__bubble {
    display: inline-block;
    width: 0.875rem;
    height: 0.875rem;
    vertical-align: -0.125rem;
}

/* The screen of the additions of a dossier (story 4.3, dossier/additions.html.twig, the mockup validated on 2026-10-30,
   screen 8): under the h1, « sur « La chaussette » »; a card per addition — its previews (64px squares, a touch opens
   the viewer), what it holds and who sent it, then ✓ | ✕ side by side (the tomato ✓, the secondary ✕, joined, the radius
   of the buttons, 44px targets) —; a line decided or settled faded; then « Tout valider », full width. At 320px and 200%
   text, the choice goes under the text. */
.addition-review {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.addition-review__on {
    margin: 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
    overflow-wrap: anywhere;
}

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

.addition-review__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-lg);
    background: var(--color-surface-card);
}

.addition-review__row--done .addition-review__previews,
.addition-review__row--done .addition-review__name {
    opacity: 0.6;
}

.addition-review__previews {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.addition-review__preview {
    position: relative;
    display: grid;
    place-items: center;
    width: 4rem;
    height: 4rem;
    flex: none;
    border-radius: var(--radius-xs);
    overflow: hidden;
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
}

.addition-review__preview:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: var(--focus-ring-offset);
}

.addition-review__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.addition-review__icon {
    width: 1.5rem;
    height: 1.5rem;
}

.addition-review .addition-review__play {
    left: 0.1875rem;
    bottom: 0.1875rem;
    padding: 0.125rem 0.375rem 0.125rem 0.125rem;
    font-size: 0.6875rem;
}

.addition-review .addition-review__play .proof__play-icon {
    width: 1rem;
    height: 1rem;
}

.addition-review__text {
    flex: 1 1 8rem;
    min-width: 0;
    overflow-wrap: anywhere;
}

.addition-review__name {
    margin: 0;
    font: var(--font-title-card);
}

.addition-review__state {
    margin: 0.125rem 0 0;
    color: var(--color-ink-muted);
    font: var(--font-caption);
}

.addition-review__choice {
    display: flex;
    flex: none;
    margin-inline-start: auto;
    border: 2px solid var(--color-line-field);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

.addition-review__choice form {
    display: flex;
}

.addition-review__accept,
.addition-review__refuse {
    display: grid;
    place-items: center;
    min-width: 2.875rem;
    min-height: var(--touch-min);
    padding: 0 var(--space-2);
    border: 0;
    font: var(--font-button);
    font-weight: 900;
    cursor: pointer;
}

.addition-review__accept {
    background: var(--color-primary);
    color: var(--color-on-primary);
}

.addition-review__refuse {
    border-inline-start: 2px solid var(--color-line-field);
    background: var(--color-surface-card);
    color: var(--color-ink);
}

.addition-review__accept:focus-visible,
.addition-review__refuse:focus-visible {
    outline: var(--focus-ring-width) solid var(--color-focus-ring);
    outline-offset: calc(-1 * var(--focus-ring-width));
}

.addition-review__accept:active,
.addition-review__refuse:active {
    transform: translateY(1px);
}

/* An audio in the viewer (the screen of the additions): its controls across the stage. */
.media-viewer__audio {
    width: min(100%, 32rem);
}
