/*
 * La feuille du projet. Elle est vide au départ, et le reste : ce qui vaut pour tous les
 * écrans appartient à `base.css`, ce qui se répète appartient à `components.css`.
 *
 * Ce qui s'écrit ici : le décalage propre à un projet — une police, un rayon, un accent — en
 * réécrivant un alias de `tokens.css`, jamais en recopiant une couleur.
 *
 *     :root {
 *         --app-accent: #0e7490;
 *         --app-radius: 8px;
 *     }
 *
 * Un écran qui demande ses propres règles prend sa feuille, `styles/<zone>/<gabarit>.css`,
 * importée depuis `app.js`.
 *
 * Une palette, elle, prend son fichier dans `styles/themes/` : le paquet l'y lit, la sert et
 * la propose sans qu'aucune ligne soit à écrire ici.
 */

/*
 * La barre du haut reste en vue pendant le défilement. Le paquet le prévoit derrière
 * `data-sticky`, mais son gabarit pose la balise `header` en dur : l'attribut ne peut pas s'y
 * ajouter sans recopier tout le bloc. Les trois lignes ci-dessous disent la même chose, avec
 * le jeton du paquet plutôt qu'une valeur inventée.
 */
.app-header {
    position: sticky;
    top: 0;
    z-index: var(--app-z-sticky);
}

/*
 * La barre de debug se pose par-dessus la page, et non à sa suite.
 *
 * En développement, Symfony insère sa barre en fin de `body`, après une charpente qui occupe
 * déjà toute la hauteur de la fenêtre. Ce qu'on voit en bas de l'écran est bien collé là par
 * l'outil, mais la boîte qui le porte, elle, reste dans le flux : trente-six pixels de plus
 * que la fenêtre, donc une barre de défilement sur chaque page alors que rien ne dépasse.
 *
 * La sortir du flux rend la page à sa hauteur. Le dessin ne bouge pas d'un pixel : ce qui est
 * à l'intérieur était déjà fixé au bas de la fenêtre.
 *
 * Rien à retirer le jour venu : ce sélecteur ne rencontre aucune balise en production, où la
 * barre n'est pas rendue.
 */
body > .sf-toolbar {
    position: fixed;
    inset: auto 0 0 0;
}

/* ═══ La charpente ═════════════════════════════════════════════════════════════════════ */

/*
 * La fenêtre est le cadre. La charpente en prend exactement la hauteur, et c'est le contenu qui
 * défile à l'intérieur, jamais la page : le menu latéral et la barre du haut tiennent en place
 * d'eux-mêmes, et rien ne dépasse par le bas.
 *
 * Le contenu prend la rangée du milieu — `minmax(0, …)` et non `1fr` seul, sans quoi une page
 * longue imposerait sa hauteur à la rangée et la fenêtre serait de nouveau dépassée.
 *
 * Seulement là où le menu est une colonne : plus bas, il occupe une rangée de plus, la barre du
 * haut passe dessous, et une page qui défile normalement est ce qu'on attend d'un téléphone.
 */
@media (min-width: 60em) {
    .app-shell[data-layout="sidebar"] {
        height: 100dvh;
        grid-template-rows: auto minmax(0, 1fr) auto;
    }

    .app-shell[data-layout="sidebar"] > .app-main {
        overflow-y: auto;
    }

    /*
     * La page ne défile plus d'elle-même : la place que le socle réserve à sa barre de
     * défilement resterait vide le long du bord droit, et cette bande-là est plus large que la
     * gouttière qu'elle vient doubler.
     */
    html:has(.app-shell[data-layout="sidebar"]) {
        scrollbar-gutter: auto;
    }
}

/* ═══ La gouttière ═════════════════════════════════════════════════════════════════════ */

/*
 * Une seule mesure d'écart pour toute l'application : le retrait des bords de la page, l'espace
 * entre deux panneaux et la marge intérieure d'une carte valent la même chose.
 *
 * Les écarts du paquet sont taillés pour une page qui se lit en une colonne, où de larges marges
 * posent le texte au milieu de l'écran. Ici, une page porte plusieurs panneaux côte à côte, et
 * trois retraits différents empilés le long d'un même bord se voient — d'autant qu'ils prennent
 * la place de ce qu'ils encadrent.
 */
.app-main {
    padding: var(--app-space-lg);
}

.app-card {
    padding: var(--app-space-lg);
}

/*
 * La barre du haut se retire d'autant que la page : le titre tombe alors sur la marge où
 * commencent les cartes, au lieu de flotter un centimètre plus loin.
 *
 * **Trois colonnes, et les deux bords de largeur égale.** La recherche tombe ainsi au milieu de
 * l'écran, et non au milieu de ce qui reste : rangée à la suite du titre, un nom de projet long
 * l'aurait poussée vers le compte, et elle aurait changé de place d'une page à l'autre.
 */
.app-header {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    padding-inline: var(--app-space-lg);
}

/* L'espaceur du paquet pousse vers la droite ce qui le suit — ce dont une grille n'a pas
   besoin, et il y prendrait une colonne pour ne rien montrer. */
.app-header > .app-header-spacer {
    display: none;
}

/* Le compte se range contre le bord droit de sa colonne, le titre contre le bord gauche de la
   sienne : sans cela, chacun flotterait au milieu d'une moitié d'écran. */
.app-header > .account-menu {
    justify-self: end;
}

/*
 * Le contenu commence au bord et non au milieu. La largeur de lecture du paquet reste un
 * plafond, mais centrer ce qui n'y arrive pas décalerait la page de sa barre de titre, qui,
 * elle, part du bord. Seulement là où un menu latéral tient la gauche : une page seule — la
 * connexion — garde son contenu au centre de l'écran, qui est tout ce qu'elle porte.
 */
.app-shell[data-layout="sidebar"] > .app-main {
    margin-inline: 0;
}

/* ═══ La barre de titre ════════════════════════════════════════════════════════════════ */

/*
 * L'en-tête ne porte plus que le nom de ce qu'on regarde, et ce qu'on peut en faire. Un titre
 * de page y prend donc la taille d'un intitulé de barre, et non celle d'un titre de document :
 * la charpente est déjà pleine de repères — la marque, le menu, la page surlignée — et une
 * capitale de deux centimètres écraserait tout le reste.
 */
.page-title {
    font-size: var(--app-text-lg);
    /* Un nom de projet plus long que la barre s'arrête en points de suspension plutôt que de
       repousser les commandes hors de l'écran. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * ── La barre du haut sur un téléphone ─────────────────────────────────────────────────────────
 *
 * Trois colonnes, dont une au milieu qui prend sa taille naturelle : sur un écran de téléphone,
 * la recherche mange la largeur et le titre tombe à trois lettres suivies de points — « Bu… »
 * pour Budget, « Pro… » pour Projets. Le nom de la page est pourtant ce qui dit où l'on est.
 *
 * La recherche cède donc le pas : elle se réduit jusqu'à sa propre limite, et le titre garde de
 * quoi s'écrire. Aucun des deux ne disparaît — l'un et l'autre servent, et les empiler ferait
 * descendre la page d'une rangée entière à chaque ouverture.
 */
@media (max-width: 37.5rem) {
    .app-header {
        grid-template-columns: minmax(0, max-content) minmax(0, 1fr) auto;
        padding-inline: var(--app-space-sm);
        gap: var(--app-space-2xs);
    }

    /*
     * Le champ cède la largeur qu'il réclamait.
     *
     * **C'est bien lui qui étouffait le titre** : il vaut 18 rem au minimum, soit les trois
     * quarts d'un écran de téléphone, et la colonne du nom de page prenait ce qui restait — une
     * cinquantaine de pixels, de quoi écrire « Bud… ». Son propre intitulé, lui, se coupe sans
     * dommage : on sait ce qu'on tape dans une loupe, là où un nom de page coupé ne dit plus où
     * l'on est.
     */
    .app-header > .search-bar {
        min-width: 0;
        max-width: 55%;
    }
}

/*
 * ── Une seule marge, et non deux ──────────────────────────────────────────────────────────────
 *
 * Sur un écran de trois cent vingt pixels, la gouttière de la page et la marge intérieure d'un
 * cadre se suivaient le long du même bord : deux retraits pour un seul, et un quart de la
 * largeur parti avant qu'un mot ne s'écrive.
 *
 * La gouttière reste — un texte collé au bord se lit mal, et le pouce qui tient l'appareil le
 * recouvre — mais elle se resserre, et la marge du cadre avec elle. Ce que le cadre garde, c'est
 * son filet et son angle : c'est à cela qu'on voit où il commence et où il finit.
 */
@media (max-width: 37.5rem) {
    .app-main {
        padding-inline: var(--app-space-sm);
    }

    .app-card {
        padding: var(--app-space-md);
    }

    /*
     * Les deux bouts de la barre du haut se visent du pouce comme le reste : la marque ramène à
     * l'accueil, la pastille ouvre le compte, et ni l'un ni l'autre n'avait la hauteur d'une
     * cible.
     */
    :is(.app-brand, .account) {
        min-height: var(--app-touch);
    }

    /* La pastille est seule dans sa boîte quand l'adresse ne tient plus : sans largeur, la cible
       restait plus étroite que haute. */
    .account {
        justify-content: center;
        min-width: var(--app-touch);
    }
}

/*
 * Le compte, au bout de la barre : une pastille, une adresse, et rien qui ressemble à un bouton
 * tant que le curseur n'est pas dessus. Il ouvre le menu qui porte le profil et la déconnexion.
 */
.account {
    display: flex;
    align-items: center;
    gap: var(--app-space-sm);
    padding: var(--app-space-2xs);
    border-radius: var(--app-radius-ui);
    cursor: pointer;
}

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

/* L'adresse, et sous elle l'espace où l'on travaille. Deux lignes serrées : c'est une
   identité, pas un paragraphe. */
.account-identity {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: var(--app-leading-tight);
}

.account-email {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--app-text-sm);
    color: var(--app-ink-soft);
}

/* Le nom de l'espace se lit sous l'adresse, en plus discret : il dit où va ce qu'on écrit,
   il ne dispute pas la vedette à qui l'écrit. */
.account-workspace {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--app-text-xs);
    color: var(--app-ink-muted);
}

/* Ce qui annonce un groupe dans un menu déroulant. Le paquet coiffe un panneau entier
   (`app-menu-head`) mais ne nomme pas une tranche à l'intérieur. */
.menu-title {
    margin: 0;
    padding: var(--app-space-xs) var(--app-space-md);
    font-size: var(--app-text-xs);
    font-weight: var(--app-weight-semibold);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--app-ink-muted);
}

/*
 * Sur un écran étroit, il n'y a plus de milieu à tenir : les trois colonnes se referment en une
 * rangée, où la recherche prend ce que le titre et le compte lui laissent.
 */
@media (max-width: 48em) {
    .app-header {
        display: flex;
    }

    .app-header > .app-header-spacer {
        display: block;
    }
}

/* Sur un téléphone, la barre porte déjà un titre et le bouton du menu : l'identité s'efface,
   la pastille suffit à dire où l'on clique. */
@media (max-width: 40em) {
    .account-identity {
        display: none;
    }
}

/* ═══ Le menu latéral ══════════════════════════════════════════════════════════════════ */

/*
 * La colonne est la seule surface pleine de l'interface, et ce qui donne à l'application son
 * visage avant qu'on ait lu un mot.
 *
 * Aucune couleur n'est inventée ici. Une palette lui donne la sienne par `--app-rail`, et
 * l'encre qui s'y pose par `--app-rail-ink` ; sans palette, ce sont l'accent et son encre, ce
 * qui laisse la colonne colorée par défaut. Ce jeton propre existe pour une raison : la nuit,
 * l'accent s'éclaircit — il devient lui-même du texte sur un fond noir — et une colonne qui le
 * suivrait rendrait à l'écran le grand bloc lumineux qu'on vient d'éteindre.
 *
 * Tout ce qui se pose dessus part de cette encre : les fonds de survol en sont des voiles, et
 * la page en cours un voile plus dense. Une palette qui déplace les deux jetons emporte donc la
 * colonne entière avec elle.
 */
.app-sidebar {
    --rail-bg: var(--app-rail, var(--app-accent));
    --rail-ink: var(--app-rail-ink, var(--app-on-accent));

    /*
     * Le survol et la page en cours sont des voiles de l'encre : sur un fond profond, une encre
     * claire éclaircit d'autant, ce qui est exactement ce qu'on veut.
     *
     * Une palette peut les reprendre, et deux doivent le faire : celles dont la colonne est
     * pâle et l'encre sombre. Un voile d'encre y assombrit au lieu d'éclairer, et la page en
     * cours se signale par une tache grise au lieu d'une tuile plus vive.
     */
    --rail-veil: var(--app-rail-veil, color-mix(in srgb, var(--rail-ink) 14%, transparent));
    --rail-veil-strong: var(--app-rail-veil-strong, color-mix(in srgb, var(--rail-ink) 26%, transparent));
    --rail-ink-soft: color-mix(in srgb, var(--rail-ink) 78%, transparent);

    background: var(--rail-bg);
    color: var(--rail-ink);
    border-color: var(--rail-veil);
}

/* Les filets de la charpente sont dessinés pour un fond clair : sur l'accent, ils disparaissent
   ou tranchent. Ils deviennent des voiles, comme le reste. */
.app-sidebar .app-sidebar-head {
    border-bottom-color: var(--rail-veil);
}

/* En tiroir, le panneau reprend le fond du paquet : il faut le lui redire, sans quoi la couleur
   s'arrêterait au bord de l'écran étroit. */
.app-sidebar[data-drawer] > .app-sidebar-panel[open] {
    background: var(--rail-bg);
    border-left-color: var(--rail-veil);
}

/* Les deux boutons de la charpente — ouvrir le tiroir, le refermer — sont des boutons du
   paquet, faits pour un fond clair. Sur l'accent, ils s'effacent et ne gardent que leur voile. */
.app-sidebar :is(.app-sidebar-toggle, .app-sidebar-close) {
    background: transparent;
    border-color: var(--rail-veil);
    color: inherit;
}

.app-sidebar :is(.app-sidebar-toggle, .app-sidebar-close):hover {
    background: var(--rail-veil);
}

/*
 * La marque : le dessin, puis le nom. Le paquet ne pose qu'un mot ; l'image demande une ligne
 * qui les aligne, et c'est tout ce qui s'ajoute ici.
 */
.app-brand {
    display: flex;
    align-items: center;
    gap: var(--app-space-sm);
    color: inherit;
}

/* La pousse est dessinée dans la couleur d'accent : posée telle quelle sur un fond de cette
   même couleur, elle disparaîtrait. Elle garde donc son carré clair, comme un logo. */
.app-brand img {
    width: 1.875rem;
    height: 1.875rem;
    padding: var(--app-space-2xs);
    border-radius: var(--app-radius-ui);
    background: var(--app-surface);
}

/*
 * Une entrée du menu porte son dessin devant son nom. Le composant du paquet est fait pour du
 * texte seul sur un fond clair : ces règles lui donnent la ligne d'alignement qui manque et
 * l'encre du fond coloré, sans toucher à ce qu'il décide — le retrait, le rayon, la page en
 * cours.
 *
 * Elles ne valent que dans la colonne : c'est là qu'il y a un fond coloré, et les voiles qui
 * s'y posent n'existent nulle part ailleurs. Le même composant, employé sur fond clair —
 * le sous-menu des réglages —, garde donc celui du paquet, fait pour ce fond-là.
 */
.app-sidebar .app-nav-item {
    display: flex;
    align-items: center;
    gap: var(--app-space-sm);
    /* La hauteur d'une cible qu'on vise sans regarder, et la même une fois la colonne repliée :
       le dessin garde alors son carré, au lieu de flotter dans une ligne de texte. */
    min-height: 2.75rem;
    padding-inline: var(--app-space-sm);
    color: var(--rail-ink-soft);
}

.app-sidebar .app-nav-item:hover {
    background: var(--rail-veil);
    color: var(--rail-ink);
}

/* La page où l'on se trouve : un voile plus dense que celui du survol, et l'encre pleine. */
.app-sidebar .app-nav-item[aria-current="page"] {
    background: var(--rail-veil-strong);
    color: var(--rail-ink);
}

/* Le dessin ne dit rien que le nom ne dise déjà : il suit la teinte de son entrée, sans la
   devancer. Sa taille est celle d'une icône de navigation — le double du texte, à peu près,
   parce que c'est tout ce qui reste quand la colonne est repliée. */
.rail-icon {
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 1.5rem;
    color: inherit;
}

/* Le repli, seul au pied de la colonne, du côté du bord qu'il déplace. */
.rail-footer {
    display: flex;
    justify-content: flex-end;
    margin-top: auto;
}

/* La commande de repli : la même discrétion que celles de la barre de saisie — sans fond, elle
   ne s'allume qu'au survol. */
.rail-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: none;
    border-radius: var(--app-radius-ui);
    background: none;
    color: var(--rail-ink-soft);
    font-size: 1.25rem;
    cursor: pointer;
    transition: background 150ms var(--app-ease), color 150ms var(--app-ease);
}

.rail-toggle:hover {
    background: var(--rail-veil);
    color: var(--rail-ink);
}

/*
 * Le menu replié : les dessins restent, les mots s'effacent, et la page gagne onze rem de
 * largeur. L'infobulle de chaque entrée rend le nom à qui la survole.
 *
 * Seulement là où le menu est une colonne : plus bas, il se replie déjà derrière son bouton, et
 * un tiroir large de quatre rem n'aurait aucun sens. C'est la césure du paquet, reprise telle
 * quelle.
 *
 * L'état est porté par le bouton lui-même, seule balise que l'application pose dans cette
 * charpente ; `:has()` va le lire d'où il se trouve.
 */
@media (min-width: 60em) {
    .app-shell:has(.rail-toggle[data-rail="narrow"]) {
        --app-sidebar-width: 3.5rem;
    }

    /* Les marges intérieures suivent : celles d'une colonne de quinze rem laisseraient au
       dessin moins de place qu'au vide autour de lui. */
    .app-sidebar:has(.rail-toggle[data-rail="narrow"]) :is(.app-sidebar-head, .app-sidebar-panel) {
        padding-inline: var(--app-space-xs);
    }

    .app-sidebar:has(.rail-toggle[data-rail="narrow"]) :is(.app-sidebar-head, .app-nav-item, .rail-footer) {
        justify-content: center;
    }

    .app-sidebar:has(.rail-toggle[data-rail="narrow"]) .app-nav-item {
        padding-inline: 0;
    }

    .app-sidebar:has(.rail-toggle[data-rail="narrow"]) :is(.rail-brand-name, .rail-label) {
        display: none;
    }
}

/* ═══ Les intitulés de cadre ═══════════════════════════════════════════════════════════ */

/*
 * Ce qui nomme un panneau : un dessin, puis le nom en petites capitales, posés sur un filet qui
 * les sépare de ce qu'ils annoncent.
 *
 * Le même d'un cadre à l'autre — le fil, l'assistant, les réglages, le profil — pour qu'ils se
 * lisent comme les faces d'une même page. L'intitulé reste en retrait : ce qui compte est le
 * contenu, et un titre en gras de deux centimètres le disputerait à ce qu'il présente.
 */
.panel-title {
    display: flex;
    align-items: center;
    gap: var(--app-space-xs);
    margin: 0;
    padding-bottom: var(--app-space-sm);
    border-bottom: 1px solid var(--app-line);
    font-size: var(--app-text-sm);
    font-weight: var(--app-weight-semibold);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--app-ink-muted);
}

/* Le dessin est la seule couleur de la ligne, et le seul repère qui distingue un cadre d'un
   autre au coup d'œil. Sa taille se relève : le texte est celui d'un intitulé, et une icône à
   sa mesure disparaîtrait. */
.panel-title-icon {
    display: inline-flex;
    flex: 0 0 auto;
    font-size: var(--app-text-lg);
    color: var(--app-accent);
}

/* ═══ Le choix d'un type, en dessins ═══════════════════════════════════════════════════ */

/*
 * Quelques types, tous visibles à la fois, un seul retenu : le type d'une activité, celui d'une
 * liste. Une liste déroulante pour choisir entre deux ou quatre mots pèse plus que ce qu'elle
 * fait choisir ; le dessin se reconnaît sans être lu, et se vise d'un geste.
 *
 * **Une seule pièce, découpée en cases.** C'est le groupe qui porte le contour, l'arrondi et
 * l'ombre ; les cases n'ont à l'intérieur qu'un trait de séparation. Un contour par case en
 * ferait des boutons posés côte à côte, et autant d'ombres que de types sur trois centimètres.
 *
 * L'ombre est celle d'une commande posée sur la page, et non celle d'un panneau : elle donne au
 * groupe le relief qui le fait viser, sans le détacher du champ qu'il commande.
 */
.type-picker {
    display: inline-flex;
    /* Le groupe prend la largeur de ses dessins, et non celle de la colonne qui le porte : un
       champ empilé étire ce qu'il contient, et deux icônes étalées sur toute une fenêtre ne se
       liraient plus comme un choix. */
    align-self: flex-start;
    flex: 0 0 auto;
    /* Collé au champ qu'il commande, et non posé à la distance d'un champ voisin : ces dessins
       règlent ce que la ligne du dessus attend, ils n'en sont pas le suivant. */
    margin-top: var(--app-space-2xs);
    border: 1px solid var(--app-line-control);
    border-radius: var(--app-radius-ui);
    background: var(--app-surface);
    box-shadow: var(--app-shadow-sm);
}

/*
 * Chaque type est une étiquette qui porte un bouton radio invisible : le clic passe par elle, la
 * valeur part avec le formulaire sans script, et un lecteur d'écran annonce le nom que le dessin
 * remplace à l'œil.
 *
 * **Une case carrée**, pour que le dessin ait la même marge de tous les côtés.
 *
 * **Les dessins sont en encre pleine, pas en gris d'appoint.** Ils portent seuls le sens de ce
 * choix — aucun mot ne les double —, et une icône estompée est une icône qu'on ne lit pas.
 */
.type-picker-option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 2rem;
    height: 2rem;
    color: var(--app-ink);
    /* Le dessin fait seize pixels, comme partout ailleurs : sa taille suit celle du texte de
       l'élément qui le porte, réglée ici et non dans le comportement qui le trace. */
    font-size: 1rem;
    cursor: pointer;
    transition: background-color var(--app-speed) var(--app-ease),
                color var(--app-speed) var(--app-ease),
                border-color var(--app-speed) var(--app-ease);
}

/*
 * **Le dessin cesse d'être un mot pour devenir une boîte.** Posé dans un flux de texte, il repose
 * sur la ligne de base et garde sous lui la place des jambages : deux pixels de vide qui le
 * remontent d'autant, et un dessin qui n'est jamais au milieu de sa case.
 */
.type-picker-option > [data-controller="icon"] {
    display: flex;
}

/*
 * **Un trait entre les cases, aucun aux deux bouts** : c'est le contour du groupe qui ferme la
 * rangée, et un trait plus pâle que lui qui la découpe.
 *
 * La case qui porte ce trait est plus large d'autant : sinon il prendrait sa place sur le carré,
 * et le dessin se retrouverait un demi-pixel à côté du milieu.
 */
.type-picker-option + .type-picker-option {
    width: calc(2rem + 1px);
    border-left: 1px solid var(--app-line);
}

/* Les cases des extrémités suivent l'arrondi du groupe, d'un pixel moins creusé — celui que le
   contour occupe. Sans quoi leur fond déborderait des coins. */
.type-picker-option:first-child {
    border-start-start-radius: calc(var(--app-radius-ui) - 1px);
    border-end-start-radius: calc(var(--app-radius-ui) - 1px);
}

.type-picker-option:last-child {
    border-start-end-radius: calc(var(--app-radius-ui) - 1px);
    border-end-end-radius: calc(var(--app-radius-ui) - 1px);
}

.type-picker-option:hover {
    background: var(--app-hover);
}

/*
 * **Celle qui est choisie se teinte, elle ne s'inverse pas.** Un fond plein d'accent avec un
 * dessin blanc dessus fait disparaître le dessin — or c'est lui qu'on lit, et c'est la seule
 * chose qui distingue les cases entre elles. Le fond reste donc pâle et l'encre passe à l'accent :
 * le contraste se joue entre les cases, jamais à l'intérieur de celle qui compte.
 *
 * Le trait qui la sépare de ses voisines s'efface de part et d'autre, sans quitter la mise en
 * page : la case choisie se lit comme une plage pleine, et rien ne se décale.
 */
.type-picker-option:has(input:checked) {
    background: var(--app-accent-soft);
    color: var(--app-accent);
}

.type-picker-option:has(input:checked),
.type-picker-option:has(input:checked) + .type-picker-option {
    border-left-color: transparent;
}

/* Le trait du dessin choisi s'épaissit d'un quart : l'encre d'accent est plus claire que celle
   des autres, et sans cela le dessin qui compte serait le plus mince de la rangée. */
.type-picker-option:has(input:checked) svg {
    stroke-width: 2.25;
}

/* Le contour de mise au point suit le radio caché, qui n'a pas de surface à lui : c'est
   l'étiquette qui le montre. */
.type-picker-option:has(input:focus-visible) {
    z-index: 1;
    outline: var(--app-focus-width) solid var(--app-accent);
    outline-offset: var(--app-focus-offset);
}

/* ═══ Les pages d'un seul formulaire ═══════════════════════════════════════════════════ */

/*
 * Créer un projet, le renommer : une page, un champ. Le paquet a une charpente pour ça — la
 * colonne étroite et centrée — mais un menu latéral l'emporte sur elle, et c'est bien ainsi :
 * la navigation ne disparaît pas parce qu'on remplit un champ. La carte garde donc sa largeur
 * de lecture par elle-même, avec la mesure du paquet plutôt qu'une valeur inventée.
 */
.form-page {
    max-width: var(--app-content-narrow);
}

/* ═══ Le flou des palettes de verre ════════════════════════════════════════════════════ */

/*
 * La seule règle qu'une palette ne peut pas porter elle-même.
 *
 * `themes/verre.css` et `themes/aurore.css` rendent leurs surfaces translucides — cela, une
 * couleur sait le dire. Mais ce qui fait le verre est le flou de ce qu'on voit au travers, et
 * `backdrop-filter` n'est pas une couleur : aucun jeton ne peut le transporter. La règle vit
 * donc ici, sous le nom des deux palettes, et ne rencontre aucune balise tant qu'une autre est
 * choisie.
 *
 * Elle ne nomme que les surfaces posées sur le fond de page. Un panneau flouté par-dessus un
 * autre panneau flouté ne floute rien de plus, et coûte deux fois : c'est pourquoi les cartes
 * intérieures et les champs n'y sont pas.
 *
 * Si ces palettes sont retirées, ces lignes partent avec elles.
 */
:is([data-theme="verre"], [data-theme="aurore"])
:is(.app-card, .app-header, .app-sidebar) {
    backdrop-filter: blur(18px) saturate(150%);
}

/*
 * Ce qui flotte au-dessus de la page ne peut pas se contenter du verre des cartes.
 *
 * Une carte a la page sous elle : un fond calme, connu d'avance. Un menu, une liste de
 * propositions ou une boîte de dialogue s'ouvre sur ce qui se trouve là — un bouton plein, un
 * paragraphe entier — et le flou n'y peut rien : il efface le détail fin, pas une grande forme
 * unie. Un bouton bleu flouté reste un rectangle bleu, et son libellé se lit au travers. C'est
 * la densité qui couvre, pas le flou : les deux palettes disent laquelle, chacune dans sa
 * couleur.
 *
 * La règle vaut pour tout ce qui s'ouvre au-dessus de la page, d'où que vienne le panneau : ceux
 * du paquet, et ceux d'ici. Un panneau flottant qui reste sur `--app-surface` laisse lire ce
 * qu'il recouvre — c'est le seul endroit où il se déclare.
 */
:is([data-theme="verre"], [data-theme="aurore"])
:is(.app-menu-panel, .app-modal, .settings-dialog, .conversations-drawer, .search-panel, .address-panel,
    .suggest-panel, .field-picker, .budget-spend) {
    background: var(--app-glass-pop);
    backdrop-filter: blur(18px) saturate(150%);
}


/* ═══ Le calendrier des champs de date ═════════════════════════════════════════════════ */

/*
 * Un champ de date porte son propre calendrier, et deux écrans en posent : la fiche d'un projet,
 * où la date s'ouvre au clic, et celle d'une activité, où elle se tape dans un formulaire
 * ordinaire. La boîte qui les entoure diffère donc, et appartient à chacun ; le bouton et le
 * calendrier, eux, sont les mêmes, et vivent ici.
 */
.field-calendar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    color: var(--app-ink-muted);
    cursor: pointer;
}

.field-calendar:hover,
.field-calendar:focus-visible {
    color: var(--app-accent);
}

/*
 * Le calendrier flotte au-dessus de la page, posé par le navigateur lui-même : `popover` lui
 * donne sa couche, sa fermeture à la touche d'échappement et au clic à côté, sans une ligne de
 * script. Il n'est ancré à rien — les fiches sont étroites, et un calendrier centré n'y cache
 * pas ce qu'on est en train de lire.
 */
.field-picker {
    padding: var(--app-space-sm);
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius-panel, var(--app-radius-ui));
    background: var(--app-surface);
    color: var(--app-ink);
    box-shadow: var(--app-shadow-pop);
}

/* Le calendrier suit les couleurs du thème plutôt que les siennes : ses variables sont les
   nôtres, et les neuf palettes le traversent sans qu'une règle bouge ici. */
.field-picker :is(calendar-date, calendar-month) {
    --color-accent: var(--app-accent);
    --color-text-on-accent: var(--app-surface);
}

/*
 * ── `hidden` reprend le dessus ───────────────────────────────────────────────────────────────
 *
 * L'attribut `hidden` ne masque que par le `display: none` du navigateur, qu'une classe de
 * composant écrase sans le savoir : `.app-alert` pose `display: flex`, donc un bandeau rendu vide
 * et marqué `hidden` occupait sa place — une bande colorée sans texte, et une page qui se met à
 * défiler.
 *
 * Écrit ici plutôt que dans chaque composant : la même surprise attend toute classe qui pose un
 * `display`, et on ne la voit qu'en tombant dessus.
 */
[hidden] {
    display: none !important;
}

/*
 * ── La zone de touche d'un dessin qui suit le texte ───────────────────────────────────────────
 *
 * Un crayon de correction, une case à cocher, une croix qui détache : leur taille est celle de la
 * ligne qu'ils accompagnent — c'est ce qui les pose au milieu de leur intitulé, et c'est pour ça
 * qu'ils mesurent moins de vingt pixels.
 *
 * Grandir les décalerait. Ils gardent donc leur dessin et reçoivent une surface invisible qui
 * vaut la mesure du doigt, centrée sur eux : ce qui se voit ne bouge pas, ce qui se vise devient
 * atteignable.
 *
 * La règle leur est commune parce que le problème l'est. Recopiée dans chaque feuille, elle
 * aurait fini par valoir autant de mesures différentes.
 */
:is(.edit-pen, .home-check, .table-check, .element-check, .party-remove) {
    position: relative;
}

:is(.edit-pen, .home-check, .table-check, .element-check, .party-remove)::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--app-touch);
    height: var(--app-touch);
    transform: translate(-50%, -50%);
}

/*
 * ── Ce qui se replie et ce qui se tape, sur un écran qu'on tient d'une main ───────────────────
 *
 * Un intitulé de panneau qui ouvre et referme ce qu'il annonce est une commande, même dessiné en
 * petites capitales. Un champ posé au fil d'une ligne en est une aussi. Ni l'un ni l'autre n'a
 * l'air d'un bouton, et c'est pour cela qu'ils étaient les derniers à rester sous la mesure.
 */
@media (max-width: 37.5rem) {
    summary.panel-title,
    :is(.element-edit, .element-add) :is(input, textarea) {
        min-height: var(--app-touch);
    }
}

/*
 * ── Le tiroir ─────────────────────────────────────────────────────────────────────────────────
 *
 * Une fiche ouverte par-dessus la liste qu'on regardait, contre le bord droit.
 *
 * **Contre le bord et non au milieu**, parce qu'on n'a pas quitté la liste : elle reste visible
 * sur la gauche, et c'est ce qui distingue le tiroir d'une fenêtre modale — on consulte une ligne
 * sans perdre de vue celles d'à côté.
 *
 * Sur un écran qu'on tient d'une main, il n'y a pas de « à côté » : le tiroir prend toute la
 * place, et redevient la page qu'il montre.
 */
.app-drawer {
    width: min(52rem, 100vw);
    max-width: none;
    height: 100dvh;
    max-height: none;
    margin: 0 0 0 auto;
    padding: 0;
    border-radius: 0;
}

/*
 * ⚠️ LA MISE EN COLONNE NE VAUT QU'OUVERT. Le navigateur masque une fenêtre fermée par un
 * `display: none` que la moindre classe écrase sans le savoir : posée sans condition, la
 * disposition rendait le tiroir présent dans la page en étant fermé, et il prenait la moitié de
 * l'écran à côté d'une liste réduite d'autant.
 */
.app-drawer[open] {
    display: flex;
    flex-direction: column;
}

/* La barre du tiroir : refermer à gauche, agrandir à droite. Elle ne défile pas avec la fiche —
   ce qui referme doit rester sous la main quand on a descendu trois écrans. */
.drawer-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--app-space-sm);
    padding: var(--app-space-sm) var(--app-space-lg);
    border-bottom: 1px solid var(--app-line);
}

/* La fiche, qui défile seule sous la barre. */
.app-drawer > turbo-frame {
    display: block;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--app-space-lg);
}

/* Ce qui ramène à la liste n'a rien à faire dans un tiroir ouvert PAR-DESSUS cette liste : elle
   est là, derrière, et se retrouve en refermant. */
.app-drawer .sheet-back {
    display: none;
}

/*
 * ── Pendant que la fiche arrive ───────────────────────────────────────────────────────────────
 *
 * Le tiroir s'ouvre avant que la fiche soit là — c'est ce qui répond au clic tout de suite,
 * plutôt que de laisser croire qu'il ne s'est rien passé. Mais un panneau vide qui s'ouvre se lit
 * comme une charge sans rien dedans : il faut dire que ça vient.
 *
 * **La marque dure jusqu'à ce que la fiche soit peinte**, et non jusqu'à ce que la réponse
 * arrive. Celle de Turbo tombe dès la réponse reçue, et il reste alors une bonne seconde avant
 * qu'il peigne : on voyait un panneau vide, sans rien qui dise que ça venait.
 *
 * La fiche précédente, elle, s'efface : rouvrir sur une autre charge la montrerait le temps du
 * chargement, et l'on croirait avoir cliqué à côté.
 */
.app-drawer[data-loading] > turbo-frame > * {
    display: none;
}

.app-drawer[data-loading] > turbo-frame::after {
    content: "";
    display: block;
    width: 1.5rem;
    height: 1.5rem;
    margin: var(--app-space-3xl) auto;
    border: 2px solid var(--app-ink-muted);
    border-top-color: transparent;
    border-radius: var(--app-radius-pill);
    animation: app-spin .7s linear infinite;
}

/* Ce qui tourne sans fin gêne qui a demandé moins de mouvement : la marque reste, l'animation
   s'arrête — elle disait « ça travaille », le cercle le dit aussi à l'arrêt. */
@media (prefers-reduced-motion: reduce) {
    .app-drawer[data-loading] > turbo-frame::after {
        animation: none;
    }
}

@media (max-width: 37.5rem) {
    .app-drawer {
        width: 100vw;
    }

    .app-drawer > turbo-frame {
        padding: var(--app-space-sm);
    }
}
