/*
 * Ce que la page vaut avant qu'un composant s'y pose : la remise à zéro, la typographie, la
 * charpente et le halo de focus.
 *
 * Les règles portent sur des éléments — `body`, `a`, `h1` — et sur la charpente `app-shell`.
 * Tout ce qui se répète d'un écran à l'autre vit dans `components.css`.
 */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* `hidden` cache, quoi que porte l'élément. Presque tous les composants déclarent leur `display`,
   et une déclaration d'auteur l'emporte sur le `display: none` que le navigateur attache à
   l'attribut : sans cette exception, un composant caché par le HTML resterait à l'écran. Elle est
   forcée parce que la feuille des composants est lue après celle-ci, et gagnerait à égalité. */
[hidden] {
    display: none !important;
}

/* La taille se pose sur la racine, et non sur `body` : c'est elle que `rem` mesure, donc la
   seule qui entraîne l'interface entière quand une palette la relève. */
html {
    font-size: var(--app-font-size);
    -webkit-text-size-adjust: 100%;
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    min-height: 100dvh;
    font-family: var(--app-font);
    font-size: var(--app-text);
    line-height: var(--app-leading);
    color: var(--app-ink);
    background: var(--app-bg);
    -webkit-font-smoothing: antialiased;
}

/* Une image ne déborde jamais de ce qui la porte, et ne s'étire pas non plus pour le remplir :
   posée dans une carte ou une pile, qui rangent leurs enfants en colonne, elle prendrait sinon
   toute la largeur offerte quelle que soit sa taille propre. */
img {
    max-width: 100%;
    width: fit-content;
    height: auto;
}

h1,
h2,
h3,
h4 {
    margin: 0;
    font-weight: var(--app-weight-bold);
    line-height: var(--app-leading-tight);
    letter-spacing: -.01em;
}

h1 {
    font-size: var(--app-text-2xl);
}

h2 {
    font-size: var(--app-text-xl);
}

h3 {
    font-size: var(--app-text-lg);
}

p {
    margin: 0;
}

a {
    color: var(--app-accent);
    text-decoration: var(--app-link-decoration);
}

a:hover {
    text-decoration: underline;
}

code,
kbd,
pre {
    font-family: var(--app-font-mono);
    /* En `em` et non en `rem` : le code suit la taille du texte qui l'entoure, qu'il soit dans
       un paragraphe ou dans un titre. */
    font-size: .875em;
}

code {
    padding: .1em .35em;
    border-radius: var(--app-radius-ui);
    background: var(--app-raised);
    color: var(--app-ink-soft);
}

hr {
    height: 1px;
    margin: var(--app-space-xl) 0;
    border: 0;
    background: var(--app-line);
}

/* Un seul halo de focus pour toute l'application, et seulement au clavier. */
:focus-visible {
    outline: var(--app-focus-width) solid var(--app-accent);
    outline-offset: var(--app-focus-offset);
}

/* ═══ Charpente ════════════════════════════════════════════════════════════════════════ */

/* Lien d'évitement : hors du cadre tant qu'aucun clavier ne l'atteint, il descend en place
   dès qu'il prend le focus. Il reste dans le flux de lecture d'un lecteur d'écran. */
.app-skip-link {
    position: fixed;
    top: calc(var(--app-space-3xl) * -2);
    left: var(--app-space-sm);
    z-index: var(--app-z-skip);
    padding: var(--app-space-md) var(--app-space-lg);
    border: 1px solid var(--app-line-control);
    border-radius: var(--app-radius-ui);
    background: var(--app-surface);
    color: var(--app-ink);
    box-shadow: var(--app-shadow);
    transition: top var(--app-speed) var(--app-ease);
}

.app-skip-link:focus {
    top: var(--app-space-sm);
    text-decoration: none;
}

.app-shell {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
}

/*
 * La charpente à menu latéral.
 *
 * Le menu prend le coin haut gauche et toute la hauteur ; l'en-tête commence à sa droite et ne
 * porte plus que le contexte de la page. C'est la disposition des interfaces dont le menu est
 * la navigation principale, et elle a l'avantage de n'avoir aucun décalage à calculer : les
 * deux barres partent du haut de la fenêtre.
 *
 * Sur un écran étroit il n'y a plus de colonne : le menu se replie derrière son bouton, en tête
 * de page, et l'en-tête passe dessous.
 *
 * `minmax(0, 1fr)` et non `1fr` : sans le zéro, la colonne du contenu prendrait pour largeur
 * minimale celle de son contenu, et une table large pousserait le menu hors de l'écran.
 */
.app-shell[data-layout="sidebar"] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
        "sidebar"
        "header"
        "main"
        "footer";
}

.app-shell[data-layout="sidebar"] > .app-header {
    grid-area: header;
}

.app-shell[data-layout="sidebar"] > .app-sidebar {
    grid-area: sidebar;
}

.app-shell[data-layout="sidebar"] > .app-main {
    grid-area: main;
}

.app-shell[data-layout="sidebar"] > .app-footer {
    grid-area: footer;
}

@media (min-width: 60em) {
    .app-shell[data-layout="sidebar"] {
        grid-template-columns: var(--app-sidebar-width) minmax(0, 1fr);
        grid-template-areas:
            "sidebar header"
            "sidebar main"
            "sidebar footer";
    }

    /* Les deux barres tiennent en place, chacune dans sa colonne. */
    .app-shell[data-layout="sidebar"] > .app-header {
        position: sticky;
        top: 0;
        z-index: var(--app-z-sticky);
    }
}

/*
 * La charpente d'une page seule : une connexion, une page d'erreur, une étape isolée.
 *
 * Le contenu s'y pose au milieu de l'écran, dans une colonne étroite — il n'y a rien d'autre à
 * regarder, et une colonne pleine largeur pour un formulaire de deux champs se lit mal. L'en-tête
 * reste au-dessus : le choix de palette doit rester joignable avant même d'entrer.
 */
.app-shell[data-layout="centered"] > .app-main {
    display: flex;
    flex-direction: column;
    justify-content: center;
    max-width: var(--app-content-narrow);
}

/*
 * Le menu latéral.
 *
 * Il se replie derrière un bouton là où il n'y a pas de côté, et reste déplié partout ailleurs.
 * Le repli est posé par le comportement `sidebar` et non par cette feuille : sans script, le
 * menu reste visible et utilisable, ce qui est le pire cas acceptable.
 */
.app-sidebar {
    display: flex;
    flex-direction: column;
    background: var(--app-surface);
    border-bottom: 1px solid var(--app-line);
}

/* La marque et le bouton de repli, sur une ligne : c'est ce qui reste du menu une fois replié.
   Sa hauteur est celle de l'en-tête, pour que marque et contexte de page se posent sur la même
   ligne. Le filet, lui, n'est là que sur un écran étroit, où le menu passe au-dessus du
   contenu. */
.app-sidebar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--app-space-sm);
    min-height: var(--app-topbar-height);
    padding: var(--app-space-sm) var(--app-space-lg);
    border-bottom: 1px solid var(--app-line);
}

/* L'affichage n'est pas déclaré ici : un dialogue fermé est caché par le navigateur, et une
   règle d'auteur — fût-elle moins spécifique — l'emporterait sur la sienne. Ce sont les deux
   règles qui suivent, chacune dans son état, qui l'affichent. */
.app-sidebar-panel {
    flex-direction: column;
    gap: var(--app-space-md);
    min-height: 0;
    padding: var(--app-space-lg);
}

/*
 * Le panneau est un `<dialog>`. Hors tiroir il n'est pas ouvert, et c'est la règle ci-dessous
 * qui l'affiche : il reste donc une colonne ordinaire, que rien n'annonce comme une fenêtre.
 * En tiroir, `showModal()` lui donne la couche supérieure, le voile, le piège au clavier et la
 * fermeture par Échap — tout ce qu'un tiroir demande, et que le navigateur sait déjà faire.
 *
 * Sans script, aucun `data-drawer` n'est posé : le panneau s'affiche, dans le flux, à toute
 * largeur. C'est le pire cas acceptable.
 */
.app-sidebar:not([data-drawer]) > .app-sidebar-panel {
    display: flex;
    position: static;
    inset: auto;
    width: auto;
    max-width: none;
    max-height: none;
    margin: 0;
    border: 0;
    background: transparent;
    color: inherit;
}

/*
 * Le tiroir sort du côté de son bouton — le pouce y est déjà — et passe par-dessus le contenu
 * plutôt que de le pousser : un menu de quinze entrées repousserait la page de plusieurs
 * écrans, et on perdrait ce qu'on était en train de lire.
 */
.app-sidebar[data-drawer] > .app-sidebar-panel[open] {
    display: flex;
    position: fixed;
    inset: 0 0 0 auto;
    width: min(20rem, 85vw);
    max-width: none;
    height: auto;
    max-height: none;
    margin: 0;
    border: 0;
    border-left: 1px solid var(--app-line);
    background: var(--app-surface);
    color: inherit;
    box-shadow: var(--app-shadow-pop);
    overflow-y: auto;
}

/* Le bouton de fermeture n'existe que dans le tiroir : ailleurs le panneau ne se ferme pas.
   Le sélecteur passe par le parent, comme celui du bouton d'ouverture : le bouton porte aussi
   `app-btn`, qui déclare son affichage dans la feuille suivante et l'emporterait à égalité. */
.app-sidebar-panel > .app-sidebar-close {
    display: none;
}

.app-sidebar[data-drawer] > .app-sidebar-panel > .app-sidebar-close {
    display: inline-flex;
    align-self: flex-end;
}

.app-sidebar-panel::backdrop {
    background: var(--app-backdrop);
}

/* En tiroir, il ne reste du menu qu'une ligne : elle se colle en haut plutôt que de partir avec
   la page, sans quoi il faudrait remonter pour rouvrir. */
.app-shell > .app-sidebar[data-drawer] {
    position: sticky;
    top: 0;
    z-index: var(--app-z-sticky);
}

/* Ce qui doit rester au bas du menu plutôt que suivre les liens : un compte, une version, une
   porte de sortie. La marge automatique l'y pousse dès que le menu a de la hauteur à prendre. */
.app-sidebar-footer {
    margin-top: auto;
    padding-top: var(--app-space-md);
    border-top: 1px solid var(--app-line);
}

@media (min-width: 60em) {
    /* Le bouton ne sert que là où le menu se replie. Le sélecteur passe par le parent : le
       bouton porte aussi `app-btn`, qui déclare son affichage dans la feuille suivante. */
    .app-sidebar > .app-sidebar-head > .app-sidebar-toggle {
        display: none;
    }

    /* Toute la hauteur de l'écran, depuis le haut : les liens restent sous les yeux quand la
       page descend. `align-self` empêche la colonne de s'étirer sur toute la grille, sans quoi
       il n'y aurait rien à coller. Seule la charpente est concernée — un menu latéral montré
       en exemple ailleurs reste une boîte ordinaire. */
    /* Le filet du menu court du haut en bas sans rien qui le coupe : la colonne est ce qu'on
       lit d'abord, et la barre du haut n'est que celle de la colonne de droite. Rien ne passe
       donc sous la marque, qui appartient au menu et non à cette barre. */
    .app-shell > .app-sidebar {
        position: sticky;
        top: 0;
        align-self: start;
        height: 100dvh;
        border-bottom: 0;
        border-right: 1px solid var(--app-line);
    }

    .app-shell > .app-sidebar > .app-sidebar-head {
        border-bottom: 0;
    }

    .app-shell > .app-sidebar > .app-sidebar-panel {
        flex: 1;
        min-height: 0;
    }

    /* Ce sont les liens qui défilent, et non le menu entier : ce qu'on a collé en bas y reste,
       même quand la liste est plus haute que l'écran. */
    .app-shell > .app-sidebar > .app-sidebar-panel > .app-nav {
        flex: 1;
        min-height: 0;
        overflow-y: auto;
    }
}

.app-header {
    display: flex;
    align-items: center;
    gap: var(--app-space-lg);
    min-height: var(--app-topbar-height);
    padding: var(--app-space-sm) var(--app-space-xl);
    background: var(--app-surface);
    border-bottom: 1px solid var(--app-line);
}

/* L'en-tête qui reste sous les yeux quand la page défile. */
.app-header[data-sticky] {
    position: sticky;
    top: 0;
    z-index: var(--app-z-sticky);
}

.app-brand {
    font-weight: var(--app-weight-heavy);
    font-size: var(--app-text-lg);
    color: var(--app-ink);
}

.app-brand:hover {
    text-decoration: none;
}

/* Pousse ce qui suit contre le bord droit de l'en-tête. */
.app-header-spacer {
    flex: 1;
}

/* Le choix de palette : un champ ordinaire, dont l'étiquette n'est lue que par les lecteurs
   d'écran. Un menu déroulant plutôt qu'une bascule, parce qu'il y a plus de deux réponses et
   qu'aucune ne se devine depuis l'icône de la précédente. */
.app-theme-picker {
    max-width: 13rem;
}

.app-main {
    flex: 1;
    width: 100%;
    max-width: var(--app-content-width);
    margin: 0 auto;
    padding: var(--app-space-2xl) var(--app-space-xl);
}

.app-footer {
    padding: var(--app-space-xl);
    text-align: center;
    font-size: var(--app-text-sm);
    color: var(--app-ink-muted);
}

/* ═══ Messages éphémères ═══════════════════════════════════════════════════════════════ */

.app-flashes {
    display: flex;
    flex-direction: column;
    gap: var(--app-space-sm);
    margin-bottom: var(--app-space-xl);
}

/* ═══ Utilitaires ══════════════════════════════════════════════════════════════════════ */

/* Empile ses enfants ; l'écart se règle par `--app-stack-gap` sur l'instance. */
.app-stack {
    display: flex;
    flex-direction: column;
    gap: var(--app-stack-gap, var(--app-space-lg));
}

/* Aligne ses enfants sur une ligne, et les fait passer à la suivante quand la place manque. */
.app-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--app-row-gap, var(--app-space-sm));
}

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

/* Réservé aux lecteurs d'écran : lu à voix haute, invisible à l'œil. */
.app-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
