/**
 * Styles partagés des items (éléments de base : icône, badge, avatar, alerte…), chargés sur
 * tout le site et dans l'éditeur, juste après le socle (base.css). Une section commentée par
 * item ; le bouton reste dans base.css. Alias sémantiques du design system uniquement, jamais un
 * palier de rampe (sauf couleurs d'état, voir design-system/components.md). Aucun sélecteur
 * `html`, `body` ni `:root` : ce fichier ne pose que les styles propres à un item, à trois
 * exceptions documentées sur place : le verrouillage du défilement du Dialog, `.js-only` et
 * l'icône du Theme toggle, qui dépendent tous de l'état de <html> (`:has(dialog:modal)`, `.js`, `.dark`).
 */

/* ---------------------------------------------------------------------------------------------
   Icône (render_icon(), inc/core/icons.php)
   Le socle du design system pose déjà `display: block` sur les SVG ; une icône placée dans un
   conteneur flex (bouton, badge, alerte) ne doit pas être écrasée quand la place manque.
   --------------------------------------------------------------------------------------------- */
.icon {
    flex-shrink: 0;
}

/* ---------------------------------------------------------------------------------------------
   Section (section_open() / section_close(), inc/core/section.php ; en-tête : parts/section-header.php)
   Une bande de page : rythme vertical 64 / 80 / 96 px, fond, conteneur interne. Le conteneur
   réutilise `.container` (base.css : largeur « content », gouttière) ; `--narrow` et `--wide`
   changent seulement sa largeur maximale (800 / 1440 px + gouttières).
   --------------------------------------------------------------------------------------------- */
.section {
    padding-block: var(--section-y);
}

.section--tint {
    background-color: var(--surface-subtle);
}

.section--brand {
    background-color: var(--surface-brand-soft);
}

/*
 * Fond « ink » : bande sombre à toutes les valeurs du thème. section_open() ajoute la classe
 * `dark` à la section : les alias sémantiques de `.dark` (dark.css) s'y appliquent, si bien que
 * titres, textes, liens, focus, boutons et cartes de la bande sont lisibles en clair comme en
 * sombre, sans règle par élément. Le design system lit `--surface-inverse`, qui devient clair en
 * `.dark` alors que ses textes restent clairs (illisible) : ici le fond est `--surface-subtle`
 * de la portée sombre (neutral-900, la valeur de `--surface-inverse` en clair).
 */
.section--ink {
    background-color: var(--surface-subtle);
    color: var(--text-body-color);
}

.section__inner--narrow {
    max-width: calc(var(--container-narrow) + 2 * var(--gutter));
}

.section__inner--wide {
    max-width: calc(var(--container-wide) + 2 * var(--gutter));
}

/* En-tête : eyebrow, titre, sous-titre (12 px entre chaque, 40 px avant le contenu). */
.section__header {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    max-width: 40rem; /* 640 px */
    margin-bottom: var(--space-10);
}

.section__eyebrow {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--text-caption);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-brand);
}

/* Rendu de h2 quelle que soit la balise choisie (title_tag) : le niveau de titre est un choix de structure. */
.section__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-h2);
    line-height: var(--text-h2--line-height);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--tracking-heading);
    color: var(--text-strong);
    text-wrap: balance;
}

.section__subtitle {
    font-size: var(--text-body-lg);
    line-height: var(--text-body-lg--line-height);
    color: var(--text-muted);
}

/* Le design system remet à zéro les marges des <p> : l'espacement du texte riche (wysiwyg) est posé ici. */
.section__subtitle > * + * {
    margin-top: var(--space-4);
}

.section--center .section__header,
.section__header--center {
    align-items: center;
    margin-inline: auto;
    text-align: center;
}

@media (min-width: 768px) {
    .section {
        padding-block: var(--section-y-md);
    }

    .section__inner--narrow {
        max-width: calc(var(--container-narrow) + 2 * var(--gutter-md));
    }

    .section__inner--wide {
        max-width: calc(var(--container-wide) + 2 * var(--gutter-md));
    }
}

@media (min-width: 1024px) {
    .section {
        padding-block: var(--section-y-lg);
    }
}

/* Après les media queries : la section compacte garde 48 px à toutes les largeurs. */
.section--compact {
    padding-block: var(--space-12);
}

/* ---------------------------------------------------------------------------------------------
   Grid
   Grille de contenu mobile-first : 1 colonne, 2 dès 640 px, puis 3 (défaut) ou 4 dès 1024 px
   (`--cols-2` reste à 2). Jamais de saut direct de 1 à 4 colonnes. Intervalle : 16 / 24 / 32 px.
   --------------------------------------------------------------------------------------------- */
.grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-6);
}

.grid--gap-sm {
    gap: var(--space-4);
}

.grid--gap-md {
    gap: var(--space-6);
}

.grid--gap-lg {
    gap: var(--space-8);
}

@media (min-width: 640px) {
    .grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    .grid,
    .grid--cols-3 {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .grid--cols-2 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .grid--cols-4 {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* ---------------------------------------------------------------------------------------------
   Card
   Surface par défaut du design system : fond `--surface-card`, bordure 1 px et ombre ensemble
   (en `.dark`, l'ombre disparaît et la bordure porte la structure), rayon 12 px, padding 24 px.
   Jamais de bordure colorée ni de dégradé. Une carte entièrement cliquable est un seul `<a class="card">`.
   --------------------------------------------------------------------------------------------- */
.card {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-6);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background-color: var(--surface-card);
    box-shadow: var(--shadow-sm);
    color: var(--text-body-color);
    transition:
        box-shadow var(--duration-base) var(--ease-default),
        transform var(--duration-base) var(--ease-default);
}

.card--flat {
    box-shadow: none;
}

.card--padding-sm {
    padding: var(--space-4);
}

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

/* Carte-lien : `a` et `a:hover` du socle (couleur de lien, soulignement) ne s'appliquent pas à la carte entière. */
a.card,
a.card:hover {
    color: var(--text-body-color);
    text-decoration: none;
}

/* Survol : `shadow` approfondit l'ombre, `lift` la soulève aussi de 2 px (jamais les deux, jamais un zoom). */
.card--hover-shadow:hover,
.card--hover-lift:hover {
    box-shadow: var(--shadow-md);
}

.card--hover-lift:hover {
    transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
    .card--hover-lift:hover {
        transform: none;
    }
}

.card__title {
    margin: 0;
    font-size: var(--text-h5);
    line-height: var(--text-h5--line-height);
    font-weight: var(--font-weight-semibold);
    color: var(--text-strong);
}

.card__body {
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: 1.6;
    color: var(--text-body-color);
}

/* Le design system remet à zéro les marges des <p> : l'espacement du texte riche est posé ici. */
.card__body > * + * {
    margin-top: var(--space-3);
}

/* Pied de carte (bouton, lien) collé en bas : les cartes d'une même grille s'alignent. */
.card__footer {
    margin-top: auto;
    padding-top: var(--space-1);
}

/* ---------------------------------------------------------------------------------------------
   ArticleCard (parts/article-card.php)
   La tuile d'un article : une `.card` entièrement cliquable (un seul `<a>`, voir Card), sans padding
   sur l'image (16:9, recadrée) puis 20 px autour du texte : catégorie (Badge) et date, titre (jamais
   coupé), extrait tronqué à deux lignes. Survol : celui de `.card--hover-lift` ; focus : l'anneau
   du socle sur la carte entière. Sans image : un bloc neutre et une icône, comme le composant.
   --------------------------------------------------------------------------------------------- */
.article-card {
    gap: 0;
    padding: 0;
    overflow: hidden;
}

.article-card__media {
    position: relative;
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 9;
    background-color: var(--action-secondary-bg);
    color: var(--text-muted);
}

/* Posée par-dessus le bloc : la hauteur vient du ratio, pas de l'image. */
.article-card__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Le corps prend la place restante : les tuiles d'une même rangée ont la même hauteur. */
.article-card__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
    padding: var(--space-5);
}

.article-card__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-sans);
    font-size: var(--text-caption);
    line-height: var(--text-caption--line-height);
    color: var(--text-muted);
}

/* Titre : le socle donne la taille d'un h3 à la balise, la carte lui rend celle d'un h5 (comme `.card__title`). */
.article-card__title {
    margin: 0;
    font-size: var(--text-h5);
    line-height: var(--text-h5--line-height);
    font-weight: var(--font-weight-semibold);
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

.article-card__excerpt {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: 1.6;
    color: var(--text-body-color);
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

/* ---------------------------------------------------------------------------------------------
   Breadcrumb (parts/breadcrumb.php)
   Fil d'Ariane : `<nav><ol>`, séparateur « / » dessiné par le CSS (il n'est pas lu : `content` avec
   texte alternatif vide), ancêtres en gris (`--text-muted`) avec soulignement au survol, page courante
   en `--text-strong`, sans lien. Cible de 44 px de haut (le padding vient de la hauteur minimale).
   --------------------------------------------------------------------------------------------- */
.breadcrumb__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
    color: var(--text-muted);
}

.breadcrumb__item {
    display: flex;
    align-items: center;
    min-width: 0;
}

/* Le padding du premier lien ne décale pas le texte du fil par rapport au contenu qui suit. */
.breadcrumb__item:first-child {
    margin-inline-start: calc(-1 * var(--space-1));
}

/* Séparateur en fin d'élément : à un retour à la ligne, la barre reste sur la ligne de l'ancêtre. */
.breadcrumb__item:not(:last-child)::after {
    content: "/";
    content: "/" / "";
    padding-inline: var(--space-2);
    color: var(--text-muted);
}

.breadcrumb__link,
.breadcrumb__text,
.breadcrumb__current {
    display: inline-flex;
    align-items: center;
    min-height: var(--target-min);
    padding-inline: var(--space-1);
}

.breadcrumb__link {
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    text-decoration: none;
}

/* `color` redéclaré : le `a:hover` du socle est plus spécifique que `.breadcrumb__link`. */
.breadcrumb__link:hover {
    color: var(--text-strong);
    text-decoration: underline;
    text-underline-offset: 4px;
}

.breadcrumb__current {
    font-weight: var(--font-weight-medium);
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------------------------
   Pagination (parts/pagination.php)
   Liens `<a>` (le composant est en `<button>` : ici chaque page a une adresse) de 44 px minimum ;
   la page courante est pleine (`--action-primary-*`, un `<span aria-current>` non cliquable),
   les autres se teintent au survol. `.pager` et non `.pagination` : c'est la classe du `<nav>` de
   WordPress (the_posts_pagination), avec ses propres styles éventuels de plugins.
   --------------------------------------------------------------------------------------------- */
.pager__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
}

.pager__link,
.pager__gap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--target-min);
    min-height: var(--target-min);
    padding-inline: var(--space-2);
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: 1;
}

.pager__link {
    border-radius: var(--radius-md);
    color: var(--text-body-color);
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-default);
}

/* `color` redéclaré : le `a:hover` du socle est plus spécifique que `.pager__link`. */
a.pager__link:hover {
    background-color: var(--action-ghost-bg-hover);
    color: var(--text-strong);
}

.pager__link--current {
    background-color: var(--action-primary-bg);
    color: var(--action-primary-fg);
    font-weight: var(--font-weight-medium);
}

/* En `.dark`, `--action-primary-bg` (primary-500) sous le texte neutral-950 ne fait que 3,45:1 : la page courante prend le ton plus clair (`-hover`, primary-400). */
.dark .pager__link--current {
    background-color: var(--action-primary-bg-hover);
}

.pager__gap {
    color: var(--text-muted);
}

/* ---------------------------------------------------------------------------------------------
   Lecture, article, navigation entre articles, recherche, 404 (single.php, parts/posts-index.php,
   searchform.php, 404.php)
   `.reading` : conteneur de lecture d'un texte long (`the_content()` n'a pas de conteneur), à poser
   dans un `.container` : largeur du conteneur « narrow » (800 px), centré, rythme de 24 px entre
   blocs, titres espacés, listes, citations et images rétablies (le socle remet les listes à zéro).
   Le formulaire de recherche réutilise l'item de champ (`.input`, parts/field.php) : seule sa disposition est ici.
   --------------------------------------------------------------------------------------------- */
.reading {
    max-width: var(--container-narrow);
    margin-inline: auto;
}

.reading > * + * {
    margin-top: var(--space-6);
}

.reading > h2 {
    margin-top: var(--space-12);
}

.reading > h3,
.reading > h4 {
    margin-top: var(--space-8);
}

/* Après les règles de rythme : la première ligne du conteneur touche le haut. */
.reading > :first-child {
    margin-top: 0;
}

/* Listes du texte seulement (blocs enfants directs et listes imbriquées) : pas le fil d'Ariane du même conteneur. */
.reading > ul,
.reading > ol,
.reading li ul,
.reading li ol {
    padding-inline-start: var(--space-6);
}

.reading > ul,
.reading li ul {
    list-style: disc;
}

.reading > ol,
.reading li ol {
    list-style: decimal;
}

.reading > ul > li + li,
.reading > ol > li + li,
.reading li ul > li + li,
.reading li ol > li + li {
    margin-top: var(--space-2);
}

.reading blockquote {
    margin-inline: 0;
    padding-inline-start: var(--space-5);
    border-inline-start: 2px solid var(--border-strong);
    color: var(--text-strong);
}

.reading figure {
    margin-inline: 0;
    margin-block-end: 0;
}

.reading img {
    height: auto;
    border-radius: var(--radius-md);
}

.reading figcaption {
    margin-top: var(--space-2);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
    color: var(--text-muted);
}

.reading pre {
    padding: var(--space-4);
    overflow-x: auto;
    border-radius: var(--radius-md);
    background-color: var(--surface-subtle);
}

/* Article : fil d'Ariane, en-tête, image mise en avant ; 32 px entre ces blocs et le texte. */
.article .container > * + * {
    margin-top: var(--space-8);
}

.article__breadcrumb {
    margin-bottom: var(--space-4);
}

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

.article__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    color: var(--text-muted);
}

/* Le socle donne 44 px à tout h1, non responsif : 28 px, 36 px dès 640 px, 44 px dès 1024 px. */
.article__title {
    font-size: var(--text-h3);
    line-height: var(--text-h3--line-height);
    overflow-wrap: anywhere;
}

@media (min-width: 640px) {
    .article__title {
        font-size: var(--text-h2);
        line-height: var(--text-h2--line-height);
    }
}

@media (min-width: 1024px) {
    .article__title {
        font-size: var(--text-h1);
        line-height: var(--text-h1--line-height);
    }
}

.article__figure {
    max-width: 60rem;
    margin-inline: auto;
}

.article__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--radius-lg);
}

/* Navigation entre articles : filet, puis deux liens (précédent à gauche, suivant à droite dès 640 px). */
.post-nav {
    display: grid;
    gap: var(--space-4);
    padding-top: var(--space-6);
    border-top: 1px solid var(--border-default);
}

@media (min-width: 640px) {
    .post-nav {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-8);
    }

    .post-nav__link--next {
        grid-column: 2;
        align-items: flex-end;
        text-align: end;
    }
}

/* wp_link_pages() (single.php) : annule la grille à 2 colonnes de `.post-nav` (pensée pour précédent/suivant),
   et donne aux liens générés par WordPress (`.post-page-numbers`) le même langage visuel que `.pager__link`. */
.post-nav--pages {
    grid-template-columns: 1fr;
}

.post-nav--pages .post-page-numbers {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--target-min);
    min-height: var(--target-min);
    padding-inline: var(--space-2);
    margin-inline-end: var(--space-1);
    border-radius: var(--radius-md);
    color: var(--text-body-color);
    text-decoration: none;
}

.post-nav--pages a.post-page-numbers:hover {
    background-color: var(--action-ghost-bg-hover);
    color: var(--text-strong);
}

.post-nav--pages .post-page-numbers.current {
    background-color: var(--action-primary-bg);
    color: var(--action-primary-fg);
    font-weight: var(--font-weight-medium);
}

.dark .post-nav--pages .post-page-numbers.current {
    background-color: var(--action-primary-bg-hover);
}

.post-nav__link {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-height: var(--target-min);
    color: var(--text-strong);
    text-decoration: none;
}

/* `color` redéclaré : le `a:hover` du socle est plus spécifique que `.post-nav__link`. */
.post-nav__link:hover {
    color: var(--text-link-hover);
}

.post-nav__label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    color: var(--text-muted);
}

.post-nav__title {
    font-weight: var(--font-weight-semibold);
    overflow-wrap: anywhere;
}

/* Liste d'articles (home.php, archive.php, search.php) : fil d'Ariane, pagination, message sans résultat. */
.posts-index__breadcrumb {
    margin-bottom: var(--space-4);
}

/* Une recherche longue ne fait pas déborder le titre. */
.posts-index .section__title {
    overflow-wrap: anywhere;
}

.posts-index__pagination {
    margin-top: var(--space-12);
}

.posts-index__empty {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    max-width: 40rem;
}

/* Formulaire de recherche : champ `.input` (parts/field.php), disposition propre. */
.search-form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    width: 100%;
    max-width: 28rem;
}

.search-form__input {
    flex: 1 1 12rem;
    min-width: 0;
}

/* Page 404 : recherche et retour à l'accueil centrés sous l'en-tête. */
.error-404__actions {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-6);
}

/* ---------------------------------------------------------------------------------------------
   Tons (Badge, Avatar, Alert)
   Un ton pose trois variables lues par l'item : --tone-bg (fond), --tone-fg (texte, icône et
   bordure) et, pour les états, --tone-line (trait de la bordure). Primary, neutral et accent
   passent par les alias sémantiques, qui basculent seuls en `.dark` (accent lit sa rampe, faute
   d'alias de texte).

   Couleurs d'état (success, warning, danger, info) : le design system n'a pas d'alias pour elles,
   ses composants lisent des paliers de rampe (écart assumé, design-system/components.md). Seuls
   ces items le font. Les paliers de la spec ne passent pas tous 4,5:1 : warning-700 sur
   warning-50 fait 4,4:1, et le texte secondaire `-600` de l'alerte 2,6 (warning), 3,4 (success)
   et 4,1:1 (info). Le texte est donc toujours `-700` (warning assombri de 10 %), jamais `-600`.
   Les rampes d'état n'ont aucune surcharge dans dark.css : en `.dark`, le fond devient la
   surface de carte teintée à 14 % du ton, et le texte le ton `-500` éclairci de 30 % vers le
   texte fort. Rapports calculés : voir components.md.
   --------------------------------------------------------------------------------------------- */
.badge--primary,
.avatar--primary {
    --tone-bg: var(--surface-brand-soft);
    --tone-fg: var(--text-brand);
}

.badge--neutral,
.avatar--neutral {
    --tone-bg: var(--action-secondary-bg);
    --tone-fg: var(--action-secondary-fg);
}

.badge--accent {
    --tone-bg: var(--surface-accent-soft);
    --tone-fg: var(--color-accent-700);
}

.badge--success,
.alert--success,
.gforms-message--success {
    --tone-50: var(--color-success-50);
    --tone-500: var(--color-success-500);
    --tone-700: var(--color-success-700);
}

.badge--warning,
.alert--warning {
    --tone-50: var(--color-warning-50);
    --tone-500: var(--color-warning-500);
    --tone-700: color-mix(in oklab, var(--color-warning-700) 90%, var(--text-strong));
}

.badge--danger,
.alert--danger,
.gforms-message--error {
    --tone-50: var(--color-danger-50);
    --tone-500: var(--color-danger-500);
    --tone-700: var(--color-danger-700);
}

.badge--info,
.alert--info {
    --tone-50: var(--color-info-50);
    --tone-500: var(--color-info-500);
    --tone-700: var(--color-info-700);
}

.badge--success,
.badge--warning,
.badge--danger,
.badge--info,
.alert,
.gforms-message {
    --tone-bg: var(--tone-50);
    --tone-fg: var(--tone-700);
    --tone-line: var(--tone-500);
}

.dark .badge--accent {
    --tone-fg: var(--color-accent-300);
}

.dark .badge--success,
.dark .badge--warning,
.dark .badge--danger,
.dark .badge--info,
.dark .alert,
.dark .gforms-message {
    --tone-bg: color-mix(in oklab, var(--tone-500) 14%, var(--surface-card));
    --tone-fg: color-mix(in oklab, var(--tone-500) 70%, var(--text-strong));
}

/* ---------------------------------------------------------------------------------------------
   Badge (parts/badge.php)
   Pastille de statut ou de catégorie : 12 px, poids medium, rayon plein. Le libellé porte le
   sens ; l'icône (12 px, trait 2) l'accompagne. Espacements du composant (2 / 10 px, hors
   grille de 4 px). Bordure d'un cinquième de la couleur du texte : la pastille reste visible sur
   un fond de même teinte. `--outline` : fond transparent, bordure pleine.
   --------------------------------------------------------------------------------------------- */
.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px 10px;
    border: 1px solid color-mix(in oklab, var(--tone-fg) 20%, transparent);
    border-radius: var(--radius-full);
    background-color: var(--tone-bg);
    color: var(--tone-fg);
    font-family: var(--font-sans);
    font-size: var(--text-caption);
    line-height: 18px;
    font-weight: var(--font-weight-medium);
}

.badge--outline {
    border-color: var(--tone-fg);
    background-color: transparent;
}

/* ---------------------------------------------------------------------------------------------
   Avatar (parts/avatar.php)
   Portrait rond, ou initiales sur fond de ton. Les tailles (24 / 40 / 56 px) et les couleurs
   sont sur les modificateurs : `.avatar` est aussi la classe des avatars de WordPress
   (get_avatar), qui ne doivent pas être redimensionnés.
   --------------------------------------------------------------------------------------------- */
.avatar {
    flex-shrink: 0;
    border-radius: var(--radius-full);
    object-fit: cover;
}

/* Initiales : centrées, avec fond, texte et bordure de ton. Ne concerne pas l'image. */
span.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid color-mix(in oklab, var(--tone-fg) 20%, transparent);
    background-color: var(--tone-bg);
    color: var(--tone-fg);
    font-family: var(--font-sans);
    font-weight: var(--font-weight-medium);
    line-height: 1;
    letter-spacing: 0.02em;
}

/* Image du thème (avec un modificateur de taille) : fond neutre sous une image transparente ou en chargement. */
img.avatar[class*="avatar--"] {
    background-color: var(--action-secondary-bg);
}

/* Corps des initiales : 12 px (le composant donne 9 px, illisible), 14 px, 20 px. */
.avatar--sm {
    width: 1.5rem;
    height: 1.5rem;
    font-size: var(--text-caption);
}

.avatar--md {
    width: 2.5rem;
    height: 2.5rem;
    font-size: var(--text-body-sm);
}

.avatar--lg {
    width: 3.5rem;
    height: 3.5rem;
    font-size: 1.25rem;
}

/* ---------------------------------------------------------------------------------------------
   Alert (parts/alert.php)
   Bannière de message : icône, titre (semibold) et détail, rayon 8 px, padding 16 px, bordure 1 px
   dans le ton. Le titre et le détail ont la même couleur (le composant grise le détail, à un
   contraste insuffisant) : le titre passe de medium à semibold pour garder la hiérarchie.
   `role="status"`, ou `role="alert"` avec l'argument `live`. Les messages de galaxyy-forms
   (`.gforms-message`) reprennent cet habillage et ses tons (voir la section Galaxyy Forms).
   --------------------------------------------------------------------------------------------- */
.alert,
.gforms-message {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 1px solid color-mix(in oklab, var(--tone-line) 20%, transparent);
    border-radius: var(--radius-md);
    background-color: var(--tone-bg);
    color: var(--tone-fg);
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
}

/* En `.dark`, le fond est déjà teinté : la bordure se renforce pour porter la structure. */
.dark .alert,
.dark .gforms-message {
    border-color: color-mix(in oklab, var(--tone-line) 35%, transparent);
}

/* L'icône de 20 px s'aligne sur la première ligne de texte (21 px). */
.alert__icon {
    margin-top: 1px;
}

.alert__content {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
    overflow-wrap: anywhere;
}

.alert__title,
.gforms-message--error > p:first-child {
    font-weight: var(--font-weight-semibold);
}

/* Le design system remet à zéro les marges des <p> : l'espacement du texte riche est posé ici. */
.alert__body > * + * {
    margin-top: var(--space-2);
}

/* Listes du message : le socle les remet à zéro (`ul { list-style: none }`), le texte riche a besoin de puces. */
.alert__body ul,
.alert__body ol,
.gforms-message ul {
    padding-left: var(--space-5);
}

.alert__body ul,
.gforms-message ul {
    list-style: disc;
}

.alert__body ol {
    list-style: decimal;
}

/* Lien du message : couleur du ton (le `a:hover` du socle est plus spécifique que `a`). */
.alert__body a,
.alert__body a:hover,
.gforms-message a,
.gforms-message a:hover {
    color: inherit;
}

.alert__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-2);
}

/* ---------------------------------------------------------------------------------------------
   Dialog (parts/dialog.php, assets/js/dialog.js)
   Boîte de dialogue sur `<dialog>` natif, ouverte par showModal() : focus piégé, Échap, retour du
   focus et fond inerte viennent du navigateur, et la boîte est dans la couche supérieure (aucun
   z-index). Le `<dialog>` est une boîte transparente et sans marge intérieure : `.dialog__panel`
   la remplit entièrement et porte la surface, si bien qu'un clic dont la cible est le `<dialog>`
   lui-même est un clic sur le fond. Variantes : `modal` (centré, largeurs sm 400 / md 512 /
   lg 720 px, surface de carte, rayon 16 px), `drawer` (pleine hauteur, ancré à droite ou à gauche,
   filets d'en-tête et de pied), `lightbox` (large, sans surface, fond très sombre). Le corps défile
   dans le panneau, jamais derrière. Un `<dialog>` fermé reste masqué par la feuille du navigateur :
   ne jamais poser de `display` sur `.dialog`.
   --------------------------------------------------------------------------------------------- */
.dialog {
    --dialog-width: 32rem; /* 512 px */
    --dialog-pad: var(--space-6);
    --dialog-margin: var(--space-4);
    --dialog-max-height: calc(100dvh - 2 * var(--dialog-margin));
    width: min(var(--dialog-width), calc(100% - 2 * var(--dialog-margin)));
    max-width: none;
    max-height: var(--dialog-max-height);
    margin: auto;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-body-color);
    overflow: visible;
    font-family: var(--font-sans);
}

.dialog--sm {
    --dialog-width: 25rem; /* 400 px */
}

.dialog--md {
    --dialog-width: 32rem; /* 512 px */
}

.dialog--lg {
    --dialog-width: 45rem; /* 720 px */
}

.dialog__panel {
    display: flex;
    flex-direction: column;
    max-height: var(--dialog-max-height);
    overflow: hidden;
    border-radius: var(--radius-xl);
    background-color: var(--surface-card);
    box-shadow: var(--shadow-xl);
}

/* En `.dark`, la bordure porte la structure là où l'ombre ne se voit plus. */
.dark .dialog--modal .dialog__panel,
.dark .dialog--drawer .dialog__panel {
    border: 1px solid var(--border-default);
}

.dialog__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--dialog-pad) var(--dialog-pad) 0;
}

.dialog__title {
    min-width: 0;
    margin: 0;
    font-size: var(--text-h5);
    line-height: var(--text-h5--line-height);
    font-weight: var(--font-weight-semibold);
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

/* Bouton de fermeture : cible de 44 px, dont l'excédent déborde dans la marge pour aligner l'icône sur le titre. */
.dialog__close {
    flex-shrink: 0;
    margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) auto;
}

/* Le corps défile ; son padding laisse la place aux anneaux de focus de son contenu. */
.dialog__body {
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--space-4) var(--dialog-pad) var(--dialog-pad);
    overflow-y: auto;
    overscroll-behavior: contain;
    font-size: var(--text-body-sm);
    line-height: 1.6;
    color: var(--text-body-color);
}

/* Le design system remet à zéro les marges des <p> : l'espacement du contenu est posé ici. */
.dialog__body > * + * {
    margin-top: var(--space-3);
}

/* Rangée d'actions alignée à droite, bouton principal en dernier ; le corps la précède de 24 px. */
.dialog__footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-3);
    padding: 0 var(--dialog-pad) var(--dialog-pad);
}

/* Drawer : pleine hauteur, ancré au bord, sans rayon ; filets sous l'en-tête et au-dessus du pied. */
.dialog--drawer {
    --dialog-pad: var(--space-5);
    --dialog-max-height: 100dvh;
    width: min(var(--dialog-width), 100%);
    height: 100dvh;
}

.dialog--right {
    margin: 0 0 0 auto;
}

.dialog--left {
    margin: 0 auto 0 0;
}

.dialog--drawer .dialog__panel {
    height: 100%;
    border-radius: 0;
}

.dialog--drawer .dialog__header {
    align-items: center;
    padding: var(--dialog-pad);
    border-bottom: 1px solid var(--border-default);
}

.dialog--drawer .dialog__body {
    padding: var(--dialog-pad);
}

.dialog--drawer .dialog__footer {
    justify-content: flex-start;
    padding: var(--dialog-pad);
    border-top: 1px solid var(--border-default);
}

/*
 * Lightbox : sans surface, texte clair sur un fond très sombre quel que soit le mode. Elle épouse
 * son contenu (une image, en général) jusqu'à 1152 px : le bouton de fermeture reste au coin du contenu.
 */
.dialog--lightbox {
    --dialog-pad: 0;
    --dialog-width: 72rem; /* 1152 px */
    width: fit-content;
    min-width: min(20rem, calc(100% - 2 * var(--dialog-margin)));
    max-width: min(var(--dialog-width), calc(100% - 2 * var(--dialog-margin)));
    color: var(--color-white);
}

.dialog--lightbox .dialog__panel {
    overflow: visible;
    background-color: transparent;
    box-shadow: none;
}

.dialog--lightbox .dialog__header {
    align-items: center;
    padding: 0 0 var(--space-2);
}

.dialog--lightbox .dialog__title,
.dialog--lightbox .dialog__body {
    color: var(--color-white);
}

.dialog--lightbox .dialog__close {
    margin: 0 0 0 auto;
}

.dialog--lightbox .dialog__close .btn,
.dialog--lightbox .dialog__close .btn:hover {
    color: var(--color-white);
}

.dialog--lightbox .dialog__close .btn:hover {
    background-color: color-mix(in oklab, var(--color-white) 16%, transparent);
}

.dialog--lightbox .dialog__body {
    display: grid;
    place-items: center;
    padding: 0;
}

.dialog--lightbox .dialog__body img {
    width: auto;
    height: auto;
    max-height: calc(100dvh - 10rem);
    margin-inline: auto;
}

/*
 * Fond : le voile du design system (`--surface-scrim`, plus dense en `.dark`). `::backdrop` n'hérite
 * des propriétés personnalisées que dans les navigateurs récents ; la valeur de repli est le voile
 * du mode sombre du design system. Le fond de la lightbox est plus dense (90 %), dans les deux modes.
 */
.dialog::backdrop {
    background: var(--surface-scrim, rgb(10 10 10 / 0.65));
}

.dialog--lightbox::backdrop {
    background: oklch(11% 0.004 250 / 0.9);
}

/*
 * Verrouillage du défilement de la page tant qu'une boîte modale est ouverte, sans JavaScript.
 * `scrollbar-gutter` garde la place de la barre : la page ne se décale pas. Exception documentée à
 * la règle « aucun sélecteur html » de l'en-tête de ce fichier.
 */
html:has(dialog:modal) {
    overflow: hidden;
    scrollbar-gutter: stable;
}

/*
 * Entrée : mouvement non essentiel, donc réservé à `no-preference` ; sans lui, la boîte apparaît
 * telle quelle. Keyframes du design system (tokens/motion.css) : un seul par élément (boîte et
 * fond sont deux éléments) ; scale pour le modal, slide-in pour le drawer, fade pour la lightbox.
 */
@media (prefers-reduced-motion: no-preference) {
    .dialog[open] {
        animation: gl-scale-in var(--duration-base) var(--ease-out);
    }

    .dialog--drawer.dialog--right[open] {
        animation-name: gl-slide-in-right;
    }

    .dialog--drawer.dialog--left[open] {
        animation-name: gl-slide-in-left;
    }

    .dialog--lightbox[open] {
        animation-name: gl-fade-in;
    }

    .dialog[open]::backdrop {
        animation: gl-fade-in var(--duration-base) var(--ease-out);
    }
}

/* ---------------------------------------------------------------------------------------------
   Contrôles qui exigent JavaScript (`js-only`)
   Un bouton d'ouverture de dialog ou de bascule de thème ne sert à rien sans JavaScript : il porte
   `.js-only` et n'est visible que si `html.js` a été posé par le script de <head> (inc/hooks/setup.php).
   Le masquage ne touche pas au `display` quand `html.js` est présent (un `.btn` reste en
   `inline-flex`) ; `!important` le rend indépendant de la spécificité du composant. Exception
   documentée à la règle « aucun sélecteur :root » de l'en-tête de ce fichier.
   --------------------------------------------------------------------------------------------- */
:root:not(.js) .js-only {
    display: none !important;
}

/* ---------------------------------------------------------------------------------------------
   Theme toggle (parts/theme-toggle.php, assets/js/theme-toggle.js)
   Bouton icône `.btn--ghost.btn--icon` (base.css) portant deux icônes : la lune en clair, le soleil
   en sombre, selon la classe `dark` de <html> seulement (jamais une portée `.dark` locale : le
   bouton bascule la page). L'icône montre donc la cible de l'action ; l'état passe par aria-pressed.
   --------------------------------------------------------------------------------------------- */
.theme-toggle__sun {
    display: none;
}

html.dark .theme-toggle__moon {
    display: none;
}

html.dark .theme-toggle__sun {
    display: block;
}

/* ---------------------------------------------------------------------------------------------
   Formulaires (parts/field.php, choice.php, switch.php, range.php ; assets/js/range.js)
   Habillage du balisage natif, sans traitement de formulaire. Tout est cadré par des classes
   (`.field`, `.input`, `.textarea`, `.select`, `.choice`, `.switch`, `.range`) : aucun sélecteur
   ne vise `input`, `select`, `textarea` ou `label` seuls, si bien que les champs de WordPress et
   des extensions gardent leur rendu. Seule exception, cadrée par `.gforms-form` : les formulaires
   de l'extension galaxyy-forms (sous-section Galaxyy Forms, en fin de section).

   Champ : 40 px de haut (`--field-height`, comme le design system), rayon 8 px, bordure
   `--field-border`. Case, radio, interrupteur : la zone cliquable (le libellé) fait 44 px de haut
   au moins (`--target-min`), le contrôle lui-même moins. Focus : l'anneau du design system (2 px,
   `--focus-ring`, décalé de 2 px, posé par le socle) et la bordure `--field-border-focus`. Erreur :
   bordure `--border-danger`, et message avec icône et texte en `--text-danger` (jamais la couleur
   seule). Le placeholder est en `--text-muted` : `--field-placeholder` ne passe pas 4,5:1.
   Le corps du texte est de 16 px sous 768 px (en dessous, iOS zoome la page au focus), 14 px au-delà.
   --------------------------------------------------------------------------------------------- */

/* Conteneur : libellé, contrôle, aide, erreur. Sert aussi de `<fieldset>` (groupes). */
.field,
.gforms-form > :where(p:not(.gforms-hp), fieldset) {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    font-family: var(--font-sans);
    overflow-wrap: anywhere; /* Un libellé, une aide ou une unité saisis sans espace ne font pas déborder la page (320 px). */
}

.field__label,
.gforms-form :where(:not(.gforms-hp) > label, legend) {
    padding: 0;
    color: var(--text-strong);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
    font-weight: var(--font-weight-medium);
}

/* Marque « requis » : l'astérisque est décoratif, le texte masqué du libellé porte le sens. */
.field__req,
.gforms-form :where(label:has(+ :required:not([type="checkbox"], [type="radio"])), :not(fieldset) > input:is([type="checkbox"], [type="radio"]):required + label, fieldset:has(:required) > legend)::after {
    margin-left: 2px;
    color: var(--text-danger);
}

.field__help,
.field__error,
.gforms-form :where(:not(.gforms-message) > p:not(.gforms-hp):not(:has(input, select, textarea, button, label))) {
    margin: 0;
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
}

.field__help,
.gforms-form :where(:not(.gforms-message) > p:not(.gforms-hp):not(:has(input, select, textarea, button, label))) {
    color: var(--text-muted);
}

.field__error {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    color: var(--text-danger);
}

/* L'icône de 16 px s'aligne sur la première ligne de texte (21 px). */
.field__error .icon {
    margin-top: 2px;
}

.field__choices {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

/* `.field` en `flex-direction: column` étire ses enfants (`align-items: stretch`) : sans ceci, la
   zone cliquable de la case ou de l'interrupteur seul (hors `.field__choices`) couvrirait toute la
   largeur du champ au lieu de son seul contenu. */
.field > .choice,
.field > .switch {
    align-self: flex-start;
}

/* Champ de saisie, zone de texte et liste : même peau. */
.input,
.textarea,
.select__control,
.gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea, select) {
    display: block;
    width: 100%;
    min-height: var(--field-height);
    margin: 0;
    padding: 0 var(--space-3);
    appearance: none;
    border: 1px solid var(--field-border);
    border-radius: var(--radius-default);
    background-color: var(--field-bg);
    color: var(--text-strong);
    font-family: var(--font-sans);
    font-size: var(--text-body);
    line-height: var(--text-body-sm--line-height);
    transition:
        border-color var(--duration-fast) var(--ease-default),
        box-shadow var(--duration-fast) var(--ease-default);
}

@media (min-width: 768px) {
    .input,
    .textarea,
    .select__control,
    .gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea, select) {
        font-size: var(--text-body-sm);
    }
}

.input::placeholder,
.textarea::placeholder,
.gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea)::placeholder {
    color: var(--text-muted);
    opacity: 1; /* Firefox éclaircit le placeholder par défaut. */
}

.input:focus,
.textarea:focus,
.select__control:focus,
.gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea, select):focus {
    border-color: var(--field-border-focus);
}

.input[readonly],
.textarea[readonly],
.gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea)[readonly] {
    background-color: var(--surface-subtle);
}

/* Désactivé : `-webkit-text-fill-color` et `opacity` neutralisent le rendu propre de Safari et d'iOS. */
.input:disabled,
.textarea:disabled,
.select__control:disabled,
.gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea, select):disabled {
    background-color: var(--field-disabled-bg);
    color: var(--field-disabled-fg);
    -webkit-text-fill-color: var(--field-disabled-fg);
    opacity: 1;
    cursor: not-allowed;
}

/* Après `:focus` : une erreur garde sa bordure de danger, focus compris. */
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select__control[aria-invalid="true"],
.gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea, select)[aria-invalid="true"] {
    border-color: var(--border-danger);
}

/* Contrôles natifs (calendrier, liste déroulante) : `.dark` local, sans le `color-scheme` posé sur <html>. */
.dark .input,
.dark .textarea,
.dark .select__control,
.dark .gforms-form :where(:not(.gforms-hp) > input:is(:not([type]), [type="text"], [type="email"], [type="tel"], [type="url"], [type="number"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"], [type="search"]), textarea, select) {
    color-scheme: dark;
}

.textarea,
.gforms-form :where(textarea) {
    min-height: 96px;
    padding: var(--space-2) var(--space-3);
    resize: vertical;
    line-height: 1.5;
}

/* Champ avec icône de tête (16 px, décorative) : le texte démarre après elle. */
.input-wrap,
.select {
    position: relative;
    display: block;
}

.input-wrap__icon,
.select__icon {
    position: absolute;
    top: 50%;
    color: var(--text-muted);
    pointer-events: none;
    transform: translateY(-50%);
}

.input-wrap__icon {
    left: var(--space-3);
}

.input--icon {
    padding-left: var(--space-10);
}

/* Liste : flèche (icône positionnée) à droite ; texte de l'option vide en gris ; options lisibles partout. */
.select__control,
.gforms-form :where(select) {
    padding-right: var(--space-10);
    cursor: pointer;
}

.select__control:not(:disabled):has(option[value=""]:checked),
.gforms-form :where(select:not([multiple], [size])):not(:disabled):has(option[value=""]:checked) {
    color: var(--text-muted);
}

.select__control option,
.gforms-form :where(select:not([multiple], [size])) option {
    background-color: var(--field-bg);
    color: var(--text-strong);
}

.select__icon {
    right: var(--space-3);
}

.select__control:disabled + .select__icon {
    color: var(--field-disabled-fg);
}

/* Case et radio (parts/choice.php) : contrôle natif, 16 px, coloré par `accent-color` (accessible,
   et son état indéterminé suit). La rangée entière est cliquable : 12 px de marge haute et basse. */
.choice,
.gforms-form :where(input:is([type="checkbox"], [type="radio"]) + label) {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--space-2);
    min-height: var(--target-min);
    padding-block: var(--space-3);
    color: var(--text-strong);
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
    cursor: pointer;
}

.choice__input,
.gforms-form :where(input:is([type="checkbox"], [type="radio"])) {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    margin: 2px 0 0;
    accent-color: var(--action-primary-bg);
    cursor: pointer;
}

.choice__desc {
    display: block;
    margin-top: var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-caption);
    line-height: var(--text-caption--line-height);
}

.choice:has(:disabled),
.choice__input:disabled,
.gforms-form :where(input:is([type="checkbox"], [type="radio"])):disabled,
.gforms-form :where(input:is([type="checkbox"], [type="radio"]):disabled + label) {
    cursor: not-allowed;
}

.choice:has(:disabled),
.choice:has(:disabled) .choice__desc,
.gforms-form :where(input:is([type="checkbox"], [type="radio"]):disabled + label) {
    color: var(--field-disabled-fg);
}

/* Interrupteur (parts/switch.php) : `<input type="checkbox" role="switch">` transparent posé sur la
   piste de 44 × 24 px ; la piste et le bouton sont décoratifs. Hors marche : `--border-strong`, comme
   le design system (neutral-300). Le focus se voit sur la piste. */
.switch {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--target-min);
    color: var(--text-strong);
    font-family: var(--font-sans);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
    cursor: pointer;
}

.switch__input {
    position: absolute;
    top: 50%;
    left: 0;
    width: 44px;
    height: 24px;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    transform: translateY(-50%);
}

.switch__track {
    position: relative;
    flex-shrink: 0;
    width: 44px;
    height: 24px;
    border-radius: var(--radius-full);
    background-color: var(--border-strong);
    transition: background-color var(--duration-fast) var(--ease-default);
}

.switch__knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: var(--radius-full);
    background-color: var(--color-white);
    box-shadow: var(--shadow-sm);
    transition: transform var(--duration-fast) var(--ease-default);
}

.switch__input:checked + .switch__track {
    background-color: var(--action-primary-bg);
}

.switch__input:checked + .switch__track .switch__knob {
    transform: translateX(20px);
}

.switch__input:focus-visible + .switch__track {
    outline: var(--ring-width) solid var(--focus-ring);
    outline-offset: var(--ring-offset);
}

.switch__input:disabled + .switch__track {
    opacity: 0.5;
}

.switch:has(.switch__input:disabled) {
    color: var(--field-disabled-fg);
    cursor: not-allowed;
}

.switch__input:disabled {
    cursor: not-allowed;
}

/* Le déplacement du bouton est un mouvement non essentiel : la couleur de la piste suffit. */
@media (prefers-reduced-motion: reduce) {
    .switch__knob {
        transition: none;
    }
}

/* Contraste forcé (Windows) : les fonds sont remplacés, la piste et le bouton passent en couleurs système. */
@media (forced-colors: active) {
    .switch__track {
        border: 1px solid ButtonText;
        background-color: Canvas;
    }

    .switch__knob {
        background-color: ButtonText;
        box-shadow: none;
    }

    .switch__input:checked + .switch__track {
        background-color: Highlight;
    }

    .switch__input:checked + .switch__track .switch__knob {
        background-color: HighlightText;
    }
}

/* Curseur (parts/range.php) : `<input type="range">` natif de 44 px de haut (zone de saisie), piste
   de 8 px et poignée de 20 px dessinées ici. Le focus est un anneau sur la poignée. La valeur est en
   `<output>`, à droite du libellé. */
.range__top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
}

.range__top > * {
    min-width: 0;
}

.range__value {
    color: var(--text-body-color);
    font-size: var(--text-body-sm);
    line-height: var(--text-body-sm--line-height);
    font-variant-numeric: tabular-nums;
}

.range__input {
    width: 100%;
    height: var(--target-min);
    margin: 0;
    padding: 0;
    appearance: none;
    background: transparent;
    cursor: pointer;
}

.range__input::-webkit-slider-runnable-track {
    height: 8px;
    border-radius: var(--radius-full);
    background-color: var(--border-strong);
}

.range__input::-moz-range-track {
    height: 8px;
    border-radius: var(--radius-full);
    background-color: var(--border-strong);
}

.range__input::-webkit-slider-thumb {
    box-sizing: border-box;
    width: 20px;
    height: 20px;
    margin-top: -6px; /* (8 - 20) / 2 : centre la poignée sur la piste. */
    appearance: none;
    border: 2px solid var(--surface-card);
    border-radius: var(--radius-full);
    background-color: var(--action-primary-bg);
    box-shadow: var(--shadow-sm);
}

.range__input::-moz-range-thumb {
    box-sizing: border-box;
    width: 20px;
    height: 20px;
    border: 2px solid var(--surface-card);
    border-radius: var(--radius-full);
    background-color: var(--action-primary-bg);
    box-shadow: var(--shadow-sm);
}

.range__input:focus-visible {
    outline: none;
}

.range__input:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 var(--ring-offset) var(--surface-page), 0 0 0 calc(var(--ring-offset) + var(--ring-width)) var(--focus-ring);
}

.range__input:focus-visible::-moz-range-thumb {
    box-shadow: 0 0 0 var(--ring-offset) var(--surface-page), 0 0 0 calc(var(--ring-offset) + var(--ring-width)) var(--focus-ring);
}

.range__input:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Contraste forcé (Windows) : la piste garde un contour et le focus reste visible en couleurs système. */
@media (forced-colors: active) {
    .range__input:focus-visible {
        outline: 2px solid Highlight;
        outline-offset: 2px;
    }

    .range__input::-webkit-slider-runnable-track {
        border: 1px solid ButtonText;
    }

    .range__input::-moz-range-track {
        border: 1px solid ButtonText;
    }
}

/* ---------------------------------------------------------------------------------------------
   Galaxyy Forms (extension galaxyy-forms : `.gforms-form`, `.gforms-message`)
   Le plugin ne livre aucun CSS et rend le HTML saisi en administration tel quel : DOM plat, sans
   classe (`p`, `label`, `input`, `select`, `textarea`, `fieldset`, `legend`, `button`), dans un
   `<form class="gforms-form">`. Les valeurs viennent des items ci-dessus : chaque élément natif est
   ajouté à la liste de sélecteurs de son item (Field, Choice, Alert ; Button dans base.css), en
   `.gforms-form :where(…)` pour garder la spécificité d'une classe (0,1,0) et l'ordre des états
   (`:focus`, `[readonly]`, `:disabled`, puis `[aria-invalid]`). Cette section ne pose que la mise en page
   propre à ce DOM plat.

   Exception à la règle « aucun sélecteur d'élément » de la section Formulaires : les éléments
   sont toujours cadrés par `.gforms-form`, jamais visés seuls ; les champs de WordPress et des
   autres extensions gardent leur rendu. Les champs techniques du plugin (`input[type="hidden"]`)
   et son piège anti-spam (`.gforms-hp`, positionné par un style en ligne) sont exclus de chaque
   sélecteur. Les états `:disabled` et les listes `[size]` suivent les items par anticipation : la
   liste blanche du plugin ne les admet pas aujourd'hui (seul `option` accepte `disabled`). Les
   curseurs (`range`) et nuanciers (`color`) gardent leur rendu natif, à la couleur d'action (pas
   l'item Range). Conventions d'écriture des formulaires : une rangée par `<p>` (libellé puis
   contrôle) ; case et radio avant leur libellé ; aide dans un `<p>` sans contrôle, placée après
   le champ et reliée par `aria-describedby` ; groupe de radios ou de cases dans un `<fieldset>`
   avec `<legend>` (voir modules.md et le README du plugin).
   --------------------------------------------------------------------------------------------- */
.gforms-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    max-width: var(--container-narrow);
}

/* Groupe (fieldset) et rangée d'une case seule : contrôle de 16 px, libellé à côté, une rangée par
   choix ; les autres enfants (aide) prennent toute la largeur. Pas d'espace entre les rangées : la
   zone cliquable du libellé (44 px) les sépare déjà, comme `.field__choices`. */
.gforms-form > :where(fieldset, p:not(.gforms-hp):has(> input:is([type="checkbox"], [type="radio"]))) {
    display: grid;
    grid-template-columns: 16px 1fr;
    gap: 0 var(--space-2);
    align-items: start;
}

.gforms-form > :where(fieldset, p:not(.gforms-hp)) > :where(:not(input:is([type="checkbox"], [type="radio"]), input:is([type="checkbox"], [type="radio"]) + label)) {
    grid-column: 1 / -1;
}

.gforms-form :where(legend) {
    margin-bottom: var(--space-1);
}

/* Erreur d'un groupe ou d'une case seule : la légende ou le libellé passe en danger (le récapitulatif
   porte le texte de l'erreur). */
.gforms-form :where(fieldset:has([aria-invalid="true"]) > legend, :not(fieldset) > input:is([type="checkbox"], [type="radio"])[aria-invalid="true"] + label) {
    color: var(--text-danger);
}

/* Libellé d'une case ou d'un radio : poids normal (la liste des libellés le passe en medium) et
   bloc de texte (la marque « requis » suit le texte, sans l'écart d'un élément flex). Le contrôle
   descend de la marge du libellé (12 px) pour s'aligner sur sa première ligne. */
.gforms-form :where(input:is([type="checkbox"], [type="radio"]) + label) {
    display: block;
    font-weight: var(--font-weight-regular);
}

.gforms-form :where(input:is([type="checkbox"], [type="radio"])) {
    margin-top: calc(var(--space-3) + 2px);
}

/* Marque « requis » générée : décorative (texte alternatif vide), l'attribut `required` porte le sens. */
.gforms-form :where(label:has(+ :required:not([type="checkbox"], [type="radio"])), :not(fieldset) > input:is([type="checkbox"], [type="radio"]):required + label, fieldset:has(:required) > legend)::after {
    content: "*";
    content: "*" / "";
}

/* Aide : un `<p>` sans contrôle (rendu comme `.field__help`), rapproché du champ ou du groupe qui
   le précède (8 px au lieu de 20 px). Ailleurs (introduction, mention avant le bouton), il garde
   l'espacement normal. */
.gforms-form > :where(:is(p:not(.gforms-hp):has(input, select, textarea), fieldset) + p:not(.gforms-hp):not(:has(input, select, textarea, button, label))) {
    margin-top: calc(var(--space-2) - var(--space-5));
}

/* Liste native (sans icône dans le HTML) : flèche de 10 × 5 px en deux dégradés, à 12 px du bord. */
.gforms-form :where(select:not([multiple], [size])) {
    background-image:
        linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
        linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
    background-position:
        right calc(var(--space-3) + 5px) center,
        right var(--space-3) center;
    background-size: 5px 5px;
    background-repeat: no-repeat;
}

.gforms-form :where(select:not([multiple], [size])):disabled {
    background-image:
        linear-gradient(45deg, transparent 50%, var(--field-disabled-fg) 50%),
        linear-gradient(135deg, var(--field-disabled-fg) 50%, transparent 50%);
}

/* Liste ouverte (`multiple`, `size`) : hauteur de son contenu, sans flèche. */
.gforms-form :where(select[multiple], select[size]) {
    height: auto;
    padding: var(--space-2) var(--space-3);
}

.gforms-form :where(input:is([type="range"], [type="color"])) {
    accent-color: var(--action-primary-bg);
}

/* Bouton : à sa largeur, pas à celle du formulaire (ou de sa rangée). */
.gforms-form :where(button) {
    align-self: flex-start;
}

/* Message (récapitulatif d'erreurs, succès) : habillage de l'Alert, sans icône dans le HTML. L'icône
   du ton (Lucide `alert-circle`, `check-circle` : tracés de inc/core/icons.php, trait 1,5) est un
   masque CSS peint en `currentColor`, décorative. */
.gforms-message {
    display: grid;
    grid-template-columns: 20px 1fr;
    gap: 0 var(--space-3);
    max-width: var(--container-narrow);
    overflow-wrap: anywhere;
}

.gforms-message > * {
    grid-column: 2;
    min-width: 0;
}

.gforms-message::before {
    content: "";
    grid-row: 1 / span 2;
    grid-column: 1;
    width: 20px;
    height: 20px;
    margin-top: 1px; /* L'icône de 20 px s'aligne sur la première ligne de texte (21 px), comme `.alert__icon`. */
    background-color: currentColor;
    -webkit-mask: var(--gforms-icon) center / contain no-repeat;
    mask: var(--gforms-icon) center / contain no-repeat;
}

.gforms-message--error {
    --gforms-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 8v4'/%3E%3Cpath d='M12 16h.01'/%3E%3C/svg%3E");
}

.gforms-message--success {
    --gforms-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21.801 10A10 10 0 1 1 17 3.335'/%3E%3Cpath d='m9 11 3 3L22 4'/%3E%3C/svg%3E");
}

.gforms-message ul {
    margin-top: var(--space-1);
}
