/*
-- LAYOUT
-- CONTENT
-- FORMS
-- COMPONENTS
-- TOPBAR
-- SIDEBAR
-- SPECIFIC CONTENT
-- UTILITIES
*/

/*
 * Pont Bootstrap ↔ design system : les variables ci-dessous consomment les
 * tokens --sp-* de tokens.css, dérivés de designsystem/spicto/.
 */
:root {
    /* Fond de page : bg.default (#FAF9F5) sous 992px, bg.cream (#F5F3EB) au-dessus
       (cf. le bloc @media après ce :root). */
    --bs-body-bg: var(--sp-color-bg-default);
    /* Échelle typo DS en px, la racine rem reste à 14px (--font-size-root) :
       les espacements rem ne bougent pas. */
    --bs-body-font-size: var(--sp-font-size-body);
    --bs-body-font-weight: var(--sp-font-weight-body);
    --bs-body-line-height: var(--sp-font-leading-normal);
    --bs-heading-color: var(--sp-color-text-default);
    --font-size-xxs: 5px;   /* hors échelle DS, conservé (phase 5) */
    --font-size-xs: 10px;   /* hors échelle DS, conservé (phase 5) */
    --font-size-s: var(--sp-font-size-12);
    --font-size-lg: var(--sp-font-size-16);

    --contrast-font-family: var(--sp-font-family-label);
    --bs-body-font-family: var(--sp-font-family-base);
    --heading-font-family: var(--sp-font-family-base);
    --font-size-root: 14px;
    --text-muted: var(--sp-color-text-muted);
    --text-muted-rgb: var(--sp-color-grey-600-rgb);

    --bs-primary: var(--sp-color-action-primary);
    --bs-primary-rgb: var(--sp-color-ink-800-rgb);
    --bs-secondary: var(--sp-color-grey-300);
    --bs-success: var(--sp-color-feedback-success);
    --bs-success-rgb: var(--sp-color-green-700-rgb);
    --bs-danger: var(--sp-color-feedback-danger);
    --bs-danger-rgb: var(--sp-color-red-600-rgb);
    --bs-light-bg-subtle: var(--sp-color-bg-grey);

    --bs-link-color: var(--sp-color-text-default);
    --bs-link-color-rgb: var(--sp-color-ink-800-rgb);
    --bs-link-hover-color: var(--sp-color-text-brown);
    --bs-link-hover-color-rgb: var(--sp-color-brown-700-rgb);

    --list-spacer-left: 2.5rem;
    --bullet-size: 1.75rem;

    --danger: var(--sp-color-feedback-danger);
    --danger-light: color-mix(in srgb, var(--sp-color-feedback-danger) 14%, transparent);
    --warning: var(--sp-color-feedback-warning);
    --warning-light: color-mix(in srgb, var(--sp-color-feedback-warning) 12%, var(--sp-color-cream-0));
    --success: var(--sp-color-feedback-success);
    --success-light: color-mix(in srgb, var(--sp-color-feedback-success) 10%, var(--sp-color-cream-0));
    /* Le bleu d'aplat ne porte jamais de texte : ces deux-là en sont la version écrite. */
    --info: var(--sp-color-feedback-info-text);
    --info-light: color-mix(in srgb, var(--sp-color-feedback-info) 10%, var(--sp-color-cream-0));
    --light: var(--sp-color-bg-grey);
    --dark-grey: var(--sp-color-text-default);

    --bs-border-radius-sm: var(--sp-radius-xs);
    --bs-border-radius: var(--sp-radius-sm);
    /* Rayon des surfaces de saisie : 8px par défaut, 12px dans l'espace connecté
       (fiche input). Défini une fois, consommé par toutes ces surfaces. */
    --field-radius: var(--sp-radius-sm);
    --bs-border-color: var(--sp-color-border-default);

    /* Calendar (fiche calendar) : la sélection se lit à l'orange, la plage en pêche.
       primary = accent.selected (cercle début/fin), non action.primary qui est l'ink.
       secondary reste le filet chaud des bordures ; range porte la bande de plage. */
    --flatpickr-primary: var(--sp-color-accent-selected);
    --flatpickr-secondary: var(--sp-color-border-cream);
    --flatpickr-range: var(--sp-color-accent-soft);
    --flatpickr-on-primary: var(--sp-color-text-inverse);
    --flatpickr-on-range: var(--sp-color-text-default);
}

/* Le fond de page change de crème avec la largeur (DESIGN, « Le fond de page change de crème
   avec la largeur » ; DESIGNAPP, « Le fond de page de l'app suit la règle du tronc »).
   Sous 992px la page EST la feuille : bg.default, le crème le plus clair, ne teinte pas ce
   qu'on lit. À partir de 992px — le seuil où col-lg-* passe à deux colonnes, donc où du vide
   apparaît autour des colonnes — la page devient un SOL : bg.cream rend au blanc des cartes
   son statut de surface (1,05:1 → 1,11:1).
   Conséquence assumée : bg.default cesse d'être « le fond de page » tout court, c'est la
   surface claire posée dessus dès qu'on est en deux colonnes. Les blocs qui le portent
   (récapitulatif d'étape repliée, aperçu sous la carte) ne bougent pas — c'est la page qui
   bouge sous eux. */
@media (min-width: 992px) {
    :root {
        --bs-body-bg: var(--sp-color-bg-cream);
    }
}

/*
 * Fond de page crème (bg.default) mais surfaces blanches (bg.surface) : Bootstrap
 * fait dériver le fond des champs, cartes, modales… de --bs-body-bg ; on rétablit
 * ici le blanc plein voulu par le DS pour chaque surface.
 */
.form-control,
.form-control:focus,
.form-select,
.img-thumbnail {
    background-color: var(--sp-color-bg-surface);
}

.form-floating > .form-control-plaintext ~ label::after,
.form-floating > .form-control:focus ~ label::after,
.form-floating > .form-control:not(:placeholder-shown) ~ label::after,
.form-floating > .form-select ~ label::after {
    background-color: var(--sp-color-bg-surface);
}

.form-check-input {
    --bs-form-check-bg: var(--sp-color-bg-surface);
}

/* Base de la carte : gouttière space.6, rayon lg, fond blanc — les valeurs de la fiche card.
   ELLE DOIT RESTER AVANT LES VARIANTS (.card-section, .card-nested, .card-dark…). Ces variants
   redéclarent les mêmes variables --bs-card-* à spécificité égale : posée APRÈS eux, cette base
   les écrasait silencieusement — l'ombre de .card-section et le rayon de .card-nested étaient
   inertes, règles valides et jamais appliquées. Ne pas redéclarer .card plus bas dans ce
   fichier : c'est ainsi que le défaut est né. */
.card {
    --bs-card-spacer-y: var(--sp-space-6);
    --bs-card-spacer-x: var(--sp-space-6);
    --bs-card-bg: var(--sp-color-bg-surface);
    --bs-card-border-width: 0;
    --bs-card-border-radius: var(--sp-radius-lg);
    --bs-card-box-shadow: var(--sp-shadow-none);
    box-shadow: var(--bs-card-box-shadow);
    /* Filet chaud, pas le noir translucide de Bootstrap : « pas de gris froid » vaut aussi pour
       les bordures (DESIGN). Il ne s'affiche que là où la carte porte .border — l'ombre OU le
       filet, jamais les deux (fiche card). */
    --bs-card-border-color: var(--sp-color-border-default);
}

/* Crème, pas blanc (fiche dialog) : une modale porte des champs, et un champ blanc sur du
   blanc n'a plus que son filet pour exister. C'est aussi une page entière sur mobile. */
.modal {
    --bs-modal-bg: var(--sp-color-bg-default);
}

.offcanvas,
.offcanvas-lg,
.offcanvas-md,
.offcanvas-sm,
.offcanvas-xl,
.offcanvas-xxl {
    --bs-offcanvas-bg: var(--sp-color-bg-surface);
}

.dropdown-menu {
    --bs-dropdown-bg: var(--sp-color-bg-surface);
}

.accordion {
    --bs-accordion-bg: var(--sp-color-bg-surface);
}

.list-group {
    --bs-list-group-bg: var(--sp-color-bg-surface);
}

.popover {
    --bs-popover-bg: var(--sp-color-bg-surface);
}

.pagination {
    --bs-pagination-bg: var(--sp-color-bg-surface);
}

/* LAYOUT */

.site-banner a {
    color: inherit;
    text-decoration: underline;
}

.container, .container-fluid, .container-lg,
.container-md, .container-sm, .container-xl,
.container-xxl {
    --bs-gutter-x: 2.5rem;
}

/* container custom */

@media (min-width: 1400px) {
    .container.container-custom {
        max-width: 1215px;
    }
}

/* CONTENT */

/* Typo */

html{
    font-size: var(--font-size-root);
}

body {
    font-family: var(--bs-body-font-family), sans-serif;
    font-size: var(--bs-body-font-size);
}

h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5 {
    font-family: var(--heading-font-family), sans-serif;
    font-weight: var(--sp-font-weight-title);
    letter-spacing: var(--sp-font-tracking-tight);
}

h1, .h1{
    font-size: var(--sp-font-size-h1);
}

/* Registre dense des espaces pro : cran h2 de l'échelle DS pour le titre de page.
   Ce repli de layout ne vaut QUE pour les h1 qui n'ont pas déjà leur règle de titre : un
   `.page-title` porte la sienne (plus bas), et ce sélecteur, plus spécifique, la couvrait —
   l'écran des paramètres affichait donc 28px là où le DS demande h2.dense (24px). */
.photographer-layout h1:not(.page-title),
.club-layout h1:not(.page-title){
    font-size: var(--sp-font-size-h2);
    margin-bottom: 24px;
}

.h1-xl{
    font-size: var(--sp-font-size-h1);
}

h2, .h2{
    font-size: var(--sp-font-size-h2);
}

h3, .h3{
    font-size: var(--sp-font-size-h3);
}

.photographer-layout h3,
.club-layout h3,
.photographer-layout .h3,
.club-layout .h3{
    margin-bottom: 16px;
}

#photographer-dashboard h3,
#club-dashboard h3,
#photographer-dashboard .h3,
#club-dashboard .h3{
    font-size: var(--sp-font-size-h3);
    margin-bottom: 16px;
}

h4, .h4{
    font-size: var(--sp-font-size-16);
}

.lead{
    font-size: 1.25rem;
}

.lead-xl{
    font-size: 24px;
    font-weight: 400;
}

.subtitle{
    font-size: 20px;
    font-weight: 400;
    line-height: 21px;
}

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

.text-muted a{
    --bs-link-color-rgb: var(--text-muted-rgb);
}

.fw-light {
    font-weight: 200 !important;
}

sup{
    font-size: .4em;
    top: -1em;
}

/* `family.label` (Anuphan) est un choix de FONTE, rien de plus : il n'emporte ni capitales ni
   interlettrage ouvert (DESIGN.md, Typography). Un style nommé « label » accumule sinon ce
   qu'on associe machinalement à l'idée d'étiquette — uppercase, un demi-pixel de tracking —
   puis se pose sur des phrases entières. La casse suit la règle générale, et le seul
   interlettrage du système est tracking.tight, sur les titres et les boutons. */
.label-text{
    font-size: 14px;
    font-family: var(--contrast-font-family), sans-serif;
    font-weight: 400;
}

.label-text-xl{
    font-family: var(--contrast-font-family), sans-serif;
    color: var(--bs-heading-color);
    font-weight: 500;
    font-size: 16px;
    line-height: 18px;
}

.xl-text{
    font-family: var(--contrast-font-family), sans-serif;
    font-size: 2rem;
}

.xxl-text{
    font-family: var(--contrast-font-family), sans-serif;
    font-size: 40px;
}

@media screen and (min-width: 992px) {

    h1.h1-xl,
    .h1.h1-xl{
        font-size: 64px;
        font-weight: 600;
    }

    h1, .h1{
        font-size: 40px;
        margin-bottom: 24px;
    }

    .subtitle{
        font-size: 18px;
    }

    h2, .h2{
        font-size: 24px;
    }

    h4, .h4{
        font-size: 1.2rem;
    }

    .lead{
        font-size: 1.5rem;
    }

    .lead-xl{
        font-size: 32px;
    }

}

a:hover {
    text-decoration: none;
}

hr {
    color: var(--sp-color-border-default);
}

/* TABLES */

th{
    font-weight: 400;
    color: var(--bs-secondary-color)!important;
}

.table>:not(caption)>*>* {
    padding: 1rem 1.25rem;
}

table tbody > tr:last-child > td {
    border-bottom: none;
}

/* FORMS */

/* Form Control */

.form-control,.form-select {
    padding: 8px 12px;
}

.input-group-text {
    background-color: var(--light);
    border-color: var(--sp-color-border-cream);
    border-right: none;
}

.input-group-before {
    border-right: none;
    border-color: var(--sp-color-border-cream);
    color: var(--text-muted);
    font-weight: 600;
}

.input-group-before + .form-control,
.input-group-before + .form-select {
    border-left: none;
}

.input-group-text + .form-control{
    border-left: none;
}

/* Champ plat : filet chaud au repos, bordure orange + anneau de focus (DS input/select) */
.form-control:focus, .form-select:focus {
    border-color: var(--sp-color-border-focus);
    box-shadow: var(--sp-shadow-ring);
}

.input-group .form-select:focus{
    border-color: var(--sp-color-border-cream);
}

.form-control,
.form-select,
.address-suggestions{
    --bs-border-color: var(--sp-color-border-cream);
    --bs-body-color: var(--sp-color-text-default);
    border: 1px solid var(--bs-border-color);
    color: var(--bs-body-color);
    border-radius: var(--sp-radius-sm);
}

.focus .ts-control {
    box-shadow: none!important;
}

::-webkit-input-placeholder { color: var(--sp-color-text-muted) } /* Chrome, Safari, Edge */
:-ms-input-placeholder { color: var(--sp-color-text-muted) }      /* Internet Explorer 10-11 */
::placeholder { color: var(--sp-color-text-muted) }

.form-floating > label {
    color: var(--sp-color-text-muted);
}

.form-floating>.form-control-plaintext~label:after, .form-floating>.form-control:focus~label:after,
.form-floating>.form-control:not(:placeholder-shown)~label:after, .form-floating>.form-select~label:after {
    content: normal;
}

@media screen and (max-width: 991px) {
    .form-select-sm-light{
        border: none!important;
        background-color: transparent!important;
        font-size: 0.9rem !important;
    }
}

/* Le champ autorempli reprend la surface d'un champ — bg.surface, le blanc plein —, pas le fond
   de la page : c'est la même restauration que .form-control plus haut. Il lisait --bs-body-bg,
   qui n'est plus une seule teinte depuis que la page passe au crème à 992px ; un champ autorempli
   y serait devenu crème sur desktop, et il était déjà crème au lieu de blanc sur mobile. */
input:-internal-autofill-selected {
    background-color: var(--sp-color-bg-surface) !important;
}

input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 1000px var(--sp-color-bg-surface) inset !important;
    box-shadow: 0 0 0 1000px var(--sp-color-bg-surface) inset !important;
    -webkit-text-fill-color: var(--sp-color-text-default) !important;
    caret-color: var(--sp-color-text-default);
}

.input-group .form-select{
    background-color: var(--light);
    padding: 7px 34px 7px 12px;
}

.address-suggestions{
    margin-top: -1px;
    z-index: 99;
}

.address-suggestions > .suggestion-item{
    background-color: var(--sp-color-bg-surface);
}

.address-suggestions > .suggestion-item:first-child{
    border-top-left-radius: var(--bs-border-radius);
    border-top-right-radius: var(--bs-border-radius);
}

.address-suggestions > .suggestion-item:last-child{
    border-bottom-left-radius: var(--bs-border-radius);
    border-bottom-right-radius: var(--bs-border-radius);
}

/* Prevent iOS zoom on input focus (requires font-size >= 16px) */
@media (max-width: 767px) {
    .form-control,
    .form-select,
    .ts-control input {
        font-size: 16px !important;
    }
    .form-select.form-select-sm-light {
        font-size: 0.9rem !important;
    }
}

/* Datepicker - Flatpickr Override */

.flatpickr-calendar {
    -webkit-box-shadow: 1px 0 0 var(--flatpickr-secondary), -1px 0 0 var(--flatpickr-secondary), 0 1px 0 var(--flatpickr-secondary), 0 -1px 0 var(--flatpickr-secondary), 0 3px 13px rgba(0,0,0,0.08)!important;
    box-shadow: 1px 0 0 var(--flatpickr-secondary), -1px 0 0 var(--flatpickr-secondary), 0 1px 0 var(--flatpickr-secondary), 0 -1px 0 var(--flatpickr-secondary), 0 3px 13px rgba(0,0,0,0.08)!important;
}

.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) {
    -webkit-box-shadow: -2px 0 0 var(--flatpickr-secondary), 5px 0 0 var(--flatpickr-secondary)!important;
    box-shadow: -2px 0 0 var(--flatpickr-secondary), 5px 0 0 var(--flatpickr-secondary)!important;
}

.flatpickr-calendar.hasTime .flatpickr-time {
    border-top: 1px solid var(--flatpickr-secondary)!important;
}

.flatpickr-calendar.arrowTop:before {
    border-bottom-color: var(--flatpickr-secondary)!important;
}

.flatpickr-calendar.arrowBottom:before {
    border-top-color: var(--flatpickr-secondary)!important;
}

.dayContainer + .dayContainer {
    -webkit-box-shadow: -1px 0 0 var(--flatpickr-secondary)!important;
    box-shadow: -1px 0 0 var(--flatpickr-secondary)!important;
}

/* Jours entre début et fin : bande pêche, texte ink (fiche calendar) */
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange {
    background: var(--flatpickr-range)!important;
    border-color: var(--flatpickr-range)!important;
    color: var(--flatpickr-on-range)!important;
}

/* Survol : teinte pêche légère (accent.softer), pas la couleur de plage pleine */
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
    background: var(--sp-color-accent-softer)!important;
    border-color: var(--sp-color-accent-softer)!important;
    color: var(--flatpickr-on-range)!important;
}

.flatpickr-day {
    border: none!important;
}

.flatpickr-day.today {
    background-color: var(--sp-color-border-default)!important;
}

/* Calendar posé dans le panneau de filtres (fiche calendar) : rendu inline, plein
   largeur, cases de 44×44px, résumé « début → fin » au-dessus de la grille. */
.calendar {
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-4);
}

.calendar-summary {
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    margin: 0;
    padding-bottom: var(--sp-space-4);
    border-bottom: 1px solid var(--sp-color-border-default);
    font-size: var(--sp-font-size-body);
    color: var(--sp-color-text-default);
}

.calendar-summary-arrow {
    display: inline-flex;
    color: var(--sp-color-text-brown);
}

/* flatpickr inline : cases carrées de 44×44px, cercles pleins qui restent RONDS (fiche
   calendar). Toute la clé est la largeur du CONTENEUR : flatpickr garde chaque jour à 1/7
   (14.28%), donc à 7×44px la case fait 44px et le cercle est rond ; laissé pleine largeur,
   il s'étale en pilule et la grille s'enroule à plus de 7 par ligne. On fige donc le
   calendrier à sept cases, centré dans le panneau. */
.calendar .flatpickr-calendar.inline {
    box-shadow: none!important;
    margin: 0 auto;
    width: calc(7 * var(--sp-control-icon-button));
    max-width: 100%;
}

/* Grille et en-têtes de jours partagent cette largeur : les 14.28% de flatpickr y valent
   44px des deux côtés, et les colonnes restent alignées. */
.calendar .flatpickr-rContainer,
.calendar .flatpickr-days,
.calendar .dayContainer,
.calendar .flatpickr-weekdays,
.calendar .flatpickr-weekdaycontainer {
    width: 100%;
    min-width: 0;
    max-width: none;
}

.calendar .flatpickr-day {
    max-width: none;
    height: var(--sp-control-icon-button);
    line-height: var(--sp-control-icon-button);
    border-radius: var(--sp-radius-full)!important;
}

/* Le calendrier tient en entier : pas de scroll vertical quand il rentre (fiche calendar).
   Le corps du panneau Dates ne défile donc pas, contrairement à la liste des sports. */
.calendar.filter-panel-body {
    overflow: visible;
}

/* Plage : début et fin sont des cercles pleins et RONDS, jamais rognés (fiche calendar).
   Ces règles (0,3,0) l'emportent sur le thème flatpickr custom plus haut (0,2,0), dont la
   demi-bande :after coupait le cercle de début. On la neutralise et on relie les deux
   cercles par une bande pêche continue, tracée derrière eux. */
.calendar .flatpickr-day.selected,
.calendar .flatpickr-day.startRange,
.calendar .flatpickr-day.endRange {
    position: relative;
    z-index: 1;
    background: var(--sp-color-accent-selected)!important;
    border-color: var(--sp-color-accent-selected)!important;
    color: var(--sp-color-text-inverse)!important;
    border-radius: var(--sp-radius-full)!important;
    box-shadow: none!important;
}

/* Les cases sont inline-block : un blanc d'environ 4px les sépare. La bande pêche comble
   cette gouttière par un box-shadow de 5px de chaque côté — assez pour rejoindre la case
   voisine (et se glisser sous le bord du cercle de plage), jamais assez pour atteindre le
   chiffre, centré à 22px du bord. Un décalage plus grand recouvrait les chiffres de pêche. */
.calendar .flatpickr-day.inRange {
    background: var(--sp-color-accent-soft)!important;
    color: var(--sp-color-text-default)!important;
    border-radius: 0!important;
    box-shadow: -5px 0 0 var(--sp-color-accent-soft), 5px 0 0 var(--sp-color-accent-soft)!important;
}

/* Au bord d'une semaine, ce box-shadow de 5px déborderait hors de la ligne en un moignon
   pêche. On rogne la grille à sa largeur exacte : le débord de bord disparaît, la bande
   s'arrête net au bord. Plus robuste qu'un nth-child, que les jours du mois précédent
   décalent. */
.calendar .flatpickr-days,
.calendar .dayContainer {
    overflow: hidden;
}

/* Le thème custom traçait la bande par des :after asymétriques (cause du cercle rogné) :
   on les neutralise, la bande vient désormais du box-shadow ci-dessus. */
.calendar .flatpickr-day.startRange::after,
.calendar .flatpickr-day.endRange::after,
.calendar .flatpickr-day.selected::after {
    content: none!important;
}

/* Le mois en Casse Titre, ink (fiche : « Juillet 2026 ») */
.calendar .flatpickr-current-month {
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* En-têtes de jours estompés (Dim…Sam) */
.calendar span.flatpickr-weekday {
    color: var(--sp-color-text-muted);
    font-weight: var(--sp-font-weight-body);
}

.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
    background: var(--flatpickr-primary)!important;
    -webkit-box-shadow: none!important;
    box-shadow: none!important;
    color: var(--flatpickr-on-primary)!important;
    border-color: var(--flatpickr-primary)!important;
}

.flatpickr-day.selected.startRange,
.flatpickr-day.startRange.startRange,
.flatpickr-day.endRange.startRange {
    border-radius: 50px!important;
}

.flatpickr-day.selected.endRange,
.flatpickr-day.startRange.endRange,
.flatpickr-day.endRange.endRange {
    border-radius: 50px!important;
}

.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
    box-shadow: none!important;
}

.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)):before,
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)):before,
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)):before {
    content: "";
    position: absolute;
    top: 0;
    left: -5px;
    width: 100%;
    height: 100%;
    background: var(--flatpickr-range);
    z-index: -1;
    border-radius: 0 50px 50px 0;
}

.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)):after,
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)):after,
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)):after {
    content: normal!important;
}

.flatpickr-day.selected.startRange.endRange,
.flatpickr-day.startRange.startRange.endRange,
.flatpickr-day.endRange.startRange.endRange {
    border-radius: 50px!important;
}
.flatpickr-day.inRange {
    position: relative;
    color: var(--flatpickr-on-range)!important;
    border-radius: 0!important;
    -webkit-box-shadow: -5px 0 0 var(--flatpickr-range), 5px 0 0 var(--flatpickr-range)!important;
    box-shadow: -5px 0 0 var(--flatpickr-range), 5px 0 0 var(--flatpickr-range)!important;
}

/* La demi-case pêche qui relie le cercle de début à la bande (et fin, à gauche) */
.flatpickr-day.startRange:after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    width: 50%;
    height: 100%;
    background: var(--flatpickr-range);
    z-index: -1;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.flatpickr-day.endRange:after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 50%;
    height: 100%;
    background: var(--flatpickr-range);
    z-index: -1;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

.flatpickr-day.week.selected {
    border-radius: 0!important;
    -webkit-box-shadow: -5px 0 0 var(--flatpickr-primary), 5px 0 0 var(--flatpickr-primary)!important;
    box-shadow: -5px 0 0 var(--flatpickr-primary), 5px 0 0 var(--flatpickr-primary)!important;
}

.flatpickr-weekwrapper .flatpickr-weeks {
    -webkit-box-shadow: 1px 0 0 var(--flatpickr-secondary)!important;
    box-shadow: 1px 0 0 var(--flatpickr-secondary)!important;
}

/* Form Check */

.required-d-none label.required{
    display: none;
}

.form-check {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin-bottom: 0.35rem;
}

/* Le libellé d'un ENGAGEMENT (CGU, politique de confidentialité) descend à size.meta : c'est une
   phrase juridique qu'on lit une fois avant de valider, pas l'option qu'on est venu choisir — à la
   taille du corps, deux de ces phrases pèsent plus lourd que le geste qu'elles autorisent (fiche
   checkbox). Ce qui descend est la TAILLE, pas le registre : le libellé et son lien gardent leur
   couleur, un engagement estompé étant une case cochée sans savoir à quoi l'on s'engage.

   La case ne suit pas : la règle du type ci-dessous la fixe à 18px, hors de l'`em` de Bootstrap,
   et sa cible ne dépend pas de la longueur de ce qu'elle fait accepter.

   La règle suit le RÔLE, pas le balisage : un engagement se rend tantôt en case-et-libellé,
   tantôt en ligne d'option (libellé à gauche, contrôle à droite). C'est la même chose qu'on fait
   accepter, donc un seul jeu de valeurs — sans quoi le produit fait accepter ses conditions de
   deux façons selon l'écran. */
.form-check-consent .form-check-label,
.option-row-consent .option-row-name{
    font-size: var(--sp-font-size-meta);
}

/* Grammaire DS des contrôles : filet chaud au repos, coché = orange, anneau de focus */
.form-check-input {
    border: var(--sp-border-width-strong) solid var(--sp-color-border-cream);
    margin: 0;
}

/* Case à cocher : 18px, rayon radius.xs (fiche checkbox). Bootstrap la dimensionne en `em`
   et arrondit en `.25em` : la case suivrait donc la taille du texte à côté d'elle, et son
   rayon avec. Le sélecteur vise le type — le radio partage cette classe et garde son
   cercle de 20px. */
.form-check-input[type="checkbox"] {
    width: 18px;
    height: 18px;
    border-radius: var(--sp-radius-xs);
}

.form-check-input:checked {
    background-color: var(--sp-color-accent-selected);
    border-color: var(--sp-color-accent-selected);
}

.form-check-input:focus {
    box-shadow: var(--sp-shadow-ring);
    border-color: var(--sp-color-border-cream);
}

.form-check-input:checked:focus {
    border-color: var(--sp-color-accent-selected);
}


/* Form Switch */

.form-switch {
    padding-left: 3.5em;
}

/* Switch DS : piste chaude au repos, orange activé, bouton blanc dans tous les états.
   Un switch est un input[type=checkbox] : la règle de la case à cocher ci-dessus l'atteint
   donc aussi. Sa piste reprend ici sa forme de pill (fiche switch), que le rayon de la case
   lui avait ôtée. */
.form-switch .form-check-input {
    width: 2.5em;
    margin-left: -3.5em;
    height: 1.5em;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-border-cream);
    border-color: var(--sp-color-border-cream);
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e");
}

/* Switch SANS libellé visible : son titre de section le nomme (fiche switch). Bootstrap réserve
   3,5em de padding pour un texte qui n'est plus dans le flux — la piste flotterait alors à gauche
   d'une boîte vide, décollée du bord qu'elle doit longer. */
.form-switch-bare {
    padding-left: 0;
}

.form-switch-bare .form-check-input {
    margin-left: 0;
}

.form-switch .form-check-input:checked {
    background-color: var(--sp-color-accent-selected);
    border-color: var(--sp-color-accent-selected);
}

/* Bootstrap repeint le bouton en bleu (#86b7fe) au focus, et sa règle est plus spécifique
   que celle du repos : le bouton est blanc dans tous les états (fiche switch), et le DS ne
   connaît pas ce bleu. Le focus se dit par son anneau, qui est orange. */
.form-switch .form-check-input:focus {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e");
}

/* Astérisque de champ requis en orange (DS input : required.mark) */
/* Marque « champ requis » écrite dans le libellé, pour les champs où le ::after de
   `label.required` ci-dessous ne survit pas (form-floating le remet à `content: normal`).
   Même orange, même écart : c'est la même marque, posée autrement. */
.field-required {
    margin-left: 0.25em;
    color: var(--sp-color-accent-selected);
}

label.required:after {
    content: "*";
    margin-left: 0.25em;
    color: var(--sp-color-accent-selected);
}

/* Panneau de localisation (DESIGNAPP) : recherche avec loupe, puis Slider de rayon. */
.filter-location-search {
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-2);
    margin-bottom: var(--sp-space-6);
}

.filter-location-label {
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* La loupe se pose dans le champ ; le dropdown de suggestions se rend dans ce parent,
   d'où position: relative. */
.filter-location-field {
    position: relative;
    display: block;
}

.filter-location-icon {
    position: absolute;
    top: 50%;
    left: var(--sp-space-3);
    transform: translateY(-50%);
    color: var(--sp-color-text-muted);
    pointer-events: none;
}

/* Un champ qui cherche est toujours un pill (fiche input, variant search) : la forme dit
   qu'on y tape pour trouver, pas pour renseigner un formulaire. Vaut pour tout champ de
   recherche — type=search (faq, blog) et le champ de localisation (type=text). */
.form-control[type="search"] {
    border-radius: var(--sp-radius-full);
}

.filter-location-field .form-control {
    border-radius: var(--sp-radius-full);
    padding-left: calc(var(--sp-space-3) + 18px + var(--sp-space-2));
}

/* Slider (fiche slider) : libellé + valeur au-dessus, piste bicolore, poignée blanche à
   liseré orange, bornes en dessous. La portion parcourue (orange) et le reste (pêche) se
   tracent d'un dégradé calé sur --slider-progress, que le contrôleur met à jour. */
.slider {
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-2);
}

.slider-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
}

.slider-label {
    color: var(--sp-color-text-default);
}

.slider-value {
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

.slider-input {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 8px;
    border-radius: var(--sp-radius-full);
    background: linear-gradient(
        to right,
        var(--sp-color-accent-selected) 0%,
        var(--sp-color-accent-selected) var(--slider-progress, 0%),
        var(--sp-color-accent-soft) var(--slider-progress, 0%),
        var(--sp-color-accent-soft) 100%
    );
    cursor: pointer;
}

/* La poignée : blanc plein, sans bordure, détachée de la piste par une ombre portée
   (fiche slider). Répétée pour chaque moteur — les pseudo-éléments de curseur ne se
   groupent pas dans un même sélecteur. */
.slider-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 22px;
    height: 22px;
    border-radius: var(--sp-radius-full);
    background: var(--sp-color-bg-surface);
    border: 0;
    box-shadow: var(--sp-shadow-knob);
    cursor: pointer;
}

.slider-input::-moz-range-thumb {
    width: 22px;
    height: 22px;
    border-radius: var(--sp-radius-full);
    background: var(--sp-color-bg-surface);
    border: 0;
    box-shadow: var(--sp-shadow-knob);
    cursor: pointer;
}

/* Firefox trace sa propre piste : on la neutralise, le dégradé du background suffit */
.slider-input::-moz-range-track {
    height: 8px;
    border-radius: var(--sp-radius-full);
    background: transparent;
}

.slider-input:focus-visible {
    outline: none;
}

.slider-input:focus-visible::-webkit-slider-thumb {
    box-shadow: var(--sp-shadow-ring);
}

.slider-input:focus-visible::-moz-range-thumb {
    box-shadow: var(--sp-shadow-ring);
}

.slider-bounds {
    display: flex;
    justify-content: space-between;
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Search-form */

.search-bar-desktop-wrapper,
.search-bar-mobile-wrapper{
    display: flex;
    flex-grow: 1;
}

/* SearchBar DS : traitement liquid glass, un seul par vue
   (designsystem/spicto/app/components/search-bar.md) */
.search-bar{
    background: var(--sp-glass-search-tint);
    backdrop-filter: blur(var(--sp-glass-blur));
    -webkit-backdrop-filter: blur(var(--sp-glass-blur));
    border: var(--sp-glass-search-border);
    box-shadow: var(--sp-glass-search-shadow);
    border-radius: var(--sp-radius-full);
    min-height: 44px;
}

.search-bar:focus-within{
    box-shadow: var(--sp-shadow-ring), var(--sp-glass-search-shadow);
}

/* Sous 992px, la barre porte le verre — donc un `backdrop-filter`, donc un CONTEXTE
   D'EMPILEMENT. Son panneau de résultats a beau être en z-index 1000, il est enfermé dedans :
   tout ce qui suit dans la page (onglets, chips, cartes du rail) lui passait devant, et le
   panneau se lisait comme transparent. C'est la barre qu'il faut élever, pas lui — un élément
   ne sort pas du contexte de son parent.

   Le défaut ne se voyait pas avant : le panneau était purement et simplement masqué sous
   992px, puisque la recherche mobile se faisait ailleurs, dans un panneau plein écran.

   Sur desktop le verre tombe (fiche search-bar), il n'y a plus de contexte d'empilement, et
   la règle n'a plus lieu d'être. */
@media (max-width: 991.98px) {
    .search-bar{
        z-index: 20;
    }
}

/* Desktop : la barre repose sur un fond de page plein, pas sur une photo — le verre n'a
   rien pour le porter. On prend la surface pleine des filtres (bg.surface + shadow.sm) et
   on plafonne la largeur à 500px (fiche search-bar). */
@media (min-width: 992px) {
    .search-bar{
        background: var(--sp-color-bg-surface);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        border: none;
        box-shadow: var(--sp-shadow-sm);
        max-width: 500px;
    }

    .search-bar:focus-within{
        box-shadow: var(--sp-shadow-ring);
    }
}

.search-bar .form-control,
.search-bar .input-group-text{
    border-radius: var(--sp-radius-full);
    background-color: transparent;
    border: none;
}

.search-bar .input-group-text{
    color: var(--sp-color-text-caption);
    padding-left: 1rem;
    padding-right: 0;
    display: flex;
    align-items: center;
}

.search-bar .form-control{
    padding-left: 0.75rem;
    padding-top: 12px;
    padding-bottom: 12px;
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-default);
}

/* Le placeholder suit la règle de l'Input : text.muted est le seul ton qui tienne l'AA sur les
   deux fonds que cette barre connaît — le verre sur photo, et la surface pleine du desktop
   (fiche search-bar). L'icône, elle, reste en caption : c'est un pictogramme, pas du texte. */
.search-bar .form-control::placeholder{
    color: var(--sp-color-text-muted);
}

.search-bar .form-control:focus {
    box-shadow: none;
    background-color: transparent;
    border: none;
}

.search-bar .ts-control,
.search-bar .ts-wrapper.focus .ts-control{
    background: transparent;
    border: none;
    box-shadow: none;
}

.search-bar .ts-control{
    padding: 12px 16px!important;
}

.search-bar .ts-control input {
    font-size: 14px;
    line-height: 15px !important;
}

/* Panneau de résultats de la SearchBar (fiche search-bar) : une surface flottante NUE —
   bg.surface, radius.lg, shadow.pop, et pas de filet (l'ombre ou le filet, jamais les deux).
   Ni titre ni pied d'actions : ce n'est pas un Dropdown, qui règle quelque chose et se valide ;
   celui-ci ne fait que montrer ce qu'on a écrit. */
.search-bar .ts-dropdown{
    background: var(--sp-color-bg-surface);
    border: none;
    border-radius: var(--sp-radius-lg);
    box-shadow: var(--sp-shadow-pop);
    margin-top: var(--sp-space-2);
    overflow: hidden;
}

/* Les lignes sont des ListItem, en UNE colonne : la grille de tuiles à deux ou trois colonnes
   faisait de la liste un catalogue et perdait l'ordre de pertinence, que le regard suit de haut
   en bas. La gouttière est horizontale seulement — une ligne de liste court d'un bord à l'autre
   de sa surface, c'est son filet qui la sépare de la suivante, pas du vide. */
.search-bar .ts-dropdown .ts-dropdown-content{
    padding: 0 var(--sp-space-4);
    /* Le plafond suit l'écran, pas un nombre de pixels : les 200px de TomSelect étaient
       calibrés sur des lignes compactes et ne montraient plus que deux ListItem, quand la
       requête en rend cinq (fiche search-bar). 60vh les laisse tous voir sur desktop et borne
       le panneau à ce que le mobile peut céder. */
    max-height: 60vh;
}

.search-bar .ts-dropdown .option{
    max-width: none;
    padding: 0;
    background: transparent;
}

/* Le filet de la dernière ligne ne sépare plus rien : chaque option étant un conteneur à part,
   `.list-item:last-child` est vrai partout et le filet tombait sous la dernière (fiche
   list-item : « la dernière n'en porte pas, elle ne sépare plus rien »). */
.search-bar .ts-dropdown .option:last-child .list-item{
    border-bottom: 0;
}

/* Survol et parcours au clavier : le crème le plus clair du système, celui qui marque la ligne
   sans la découper. Il se pose sur la ligne entière, gouttière comprise. */
.search-bar .ts-dropdown .option.active,
.search-bar .ts-dropdown .option:hover{
    background: var(--sp-color-bg-default);
    margin: 0 calc(-1 * var(--sp-space-4));
    padding: 0 var(--sp-space-4);
}

.search-bar .ts-dropdown .no-results,
.search-bar .ts-dropdown .option.no-more-results{
    max-width: none;
    padding: var(--sp-space-4) 0;
    color: var(--sp-color-text-muted);
    font-size: var(--sp-font-size-meta);
}

/*legend {*/
/*    font-family: var(--heading-font-family), sans-serif;*/
/*    font-size: 1.1rem !important;*/
/*    font-weight: 600;*/
/*    padding: 0;*/
/*    margin-bottom: 1rem;*/
/*}*/

/*fieldset {*/
/*    padding-top: .5rem;*/
/*    padding-bottom: .5rem;*/
/*}*/

/*fieldset:not(:last-child) {*/
/*    border-bottom: 1px solid var(--light);*/
/*    margin-bottom: .5rem;*/
/*}*/

/* Search suggestions (évènements, lieux, photographes, sports) */

/* Les raccourcis qui prolongent les résultats : ils ne sont pas des lignes de liste — ils ne
   désignent pas un évènement mais une VUE filtrée. D'où la pilule, et le filet chaud qui les
   sépare des résultats au-dessus. Tokens du DS de bout en bout : le bloc portait des rem hors
   échelle, un gris Bootstrap et `--light`.

   La gouttière horizontale est celle de la liste d'options (`.ts-dropdown-content`) : depuis que
   le panneau se lit groupé par type, les groupes doivent s'aligner sur les lignes au-dessus
   d'eux, sinon deux marges gauches se répondent dans la même surface. */
.search-suggestions {
    border-top: 1px solid var(--sp-color-border-default);
    padding: var(--sp-space-4);
}

.search-suggestions-section:not(:last-child) {
    margin-bottom: var(--sp-space-4);
}

/* Un en-tête de groupe, pas une étiquette en capitales : `family.label` est un choix de fonte
   et n'emporte ni majuscules ni interlettrage (DESIGN.md, « Typography »). */
.search-suggestions-title {
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-brown);
    margin-bottom: var(--sp-space-2);
}

.search-suggestions-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
}

.search-suggestions-item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-2);
    padding: var(--sp-space-2) var(--sp-space-3);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-default);
    text-decoration: none;
    font-size: var(--sp-font-size-label);
    transition: background-color var(--sp-duration-fast) var(--sp-easing-standard);
}

/* Au survol, la surface se creuse d'un cran — elle ne prend ni l'ink ni l'orange : ces
   raccourcis mènent quelque part, ils ne sont ni une action ni un état réglé. */
.search-suggestions-item:hover {
    background-color: var(--sp-color-bg-sunken);
    color: var(--sp-color-text-default);
    text-decoration: none;
}

/* En-tête du groupe « Évènements » : il coiffe la liste d'options du champ, qui n'est pas dans
   `.search-suggestions` et ne reçoit donc pas sa gouttière. Il la reprend ici pour s'aligner
   sur les lignes qu'il annonce. */
.search-suggestions-title--options {
    padding: var(--sp-space-4) var(--sp-space-4) 0;
    margin-bottom: 0;
}

/* Évènement et photographe désignent une ENTITÉ, pas une vue filtrée : ils prennent une ligne,
   en une colonne, comme partout où l'app liste des résultats (fiche list-item). */
.search-suggestions-list--rows {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 0;
}

.search-suggestions-row {
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    padding: var(--sp-space-2);
    margin: 0 calc(-1 * var(--sp-space-2));
    border-radius: var(--sp-radius-md);
    color: var(--sp-color-text-default);
    text-decoration: none;
    transition: background-color var(--sp-duration-fast) var(--sp-easing-standard);
}

.search-suggestions-row:hover {
    background-color: var(--sp-color-bg-default);
    color: var(--sp-color-text-default);
    text-decoration: none;
}

/* Les rôles de texte sont ceux du ListItem ; ils s'empilent, la vignette reste centrée. */
.search-suggestions-row-body {
    display: flex;
    flex-direction: column;
}


/* Event title Tom Select suggestions */
.event-suggestion-option {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem .5rem;
}


/* Liste d'options d'un formulaire (DESIGNAPP, « La même anatomie sert les options d'un
   formulaire ») : libellé à gauche, Radio aligné à droite, filet entre les options.

   Ce n'est plus une carte par option : une carte par choix pose chacun sur sa propre surface,
   et l'œil compare alors des surfaces au lieu de comparer des offres. C'est le groupe entier
   qui a une carte, pas ses membres. */
.option-list{
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;   /* un fieldset ne rétrécit pas sans ça */
}

.option-row{
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-space-4);
    padding: var(--sp-space-4) 0;
    cursor: pointer;
}

/* Le filet coiffe la ligne au lieu de la souligner : la dernière n'en porte donc aucun, sans
   avoir à la désigner. Même construction que la liste de filtres. */
.option-row + .option-row{
    border-top: 1px solid var(--sp-color-border-default);
}

.option-row:first-of-type{ padding-top: 0; }
.option-row:last-of-type{ padding-bottom: 0; }

.option-row-label{
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-1);
    min-width: 0;
}

/* Trois lignes au plus : le nom, la valeur en gras, ce que l'option fait (DESIGNAPP).

   Le nom et la valeur sont à `size.body` : elles se lisent d'un bloc — l'une nomme, l'autre
   chiffre —, et c'est la GRAISSE qui les départage, pas l'échelle. Les deux lisaient
   `font.size.16`, une primitive qu'un composant n'a pas le droit de lire, et à cette taille le
   nom d'une option pesait autant que le sous-titre d'un bloc dans une liste qu'on parcourt
   justement pour comparer. */
.option-row-name{
    font-size: var(--sp-font-size-body);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-default);
}

.option-row-value{
    font-size: var(--sp-font-size-body);
    color: var(--sp-color-text-default);
}

.option-row-value strong{
    font-weight: var(--sp-font-weight-bold);
}

.option-row-help{
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-muted);
}

/* Le contrôle garde sa cible tactile et ne se déforme pas quand le libellé s'allonge */
.option-row .form-check-input{
    flex-shrink: 0;
    margin: 0;
}

/* Radio variant `segmented` (fiche radio) : le même choix exclusif, rendu en boutons alignés,
   quand les options sont courtes et comparables — une gamme de prix.

   Au repos : aplat crème, sans filet, libellé ink. Deux aplats plutôt qu'un plein et un vide :
   les options non choisies ressembleraient sinon à des trous. Le libellé ne s'estompe pas — un
   prix qu'on n'a pas choisi est exactement aussi cliquable que celui qu'on a choisi.

   Le libellé est centré SUR LES DEUX AXES. La hauteur est fixe (48px) alors que la mise en page
   d'un .btn repose sur ses marges internes : sans ce centrage, le texte pend au bord haut du
   cadre, et rien dans le style ne le signale (fiche radio). */
.btn-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 48px;
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-bold);
    --bs-btn-bg: var(--sp-color-bg-cream);
    --bs-btn-border-color: var(--sp-color-bg-cream);
    --bs-btn-color: var(--sp-color-text-default);
    --bs-btn-hover-bg: var(--sp-color-bg-sunken);
    --bs-btn-hover-border-color: var(--sp-color-bg-sunken);
    --bs-btn-hover-color: var(--sp-color-text-default);
    --bs-btn-border-radius: var(--sp-radius-lg);
}

/* Sélectionné : aplat ink. Sans cercle à cocher, seul un remplissage dit « c'est celui-là »
   d'un bout à l'autre de la rangée — et l'ink, pas l'orange, parce qu'on est dans le parcours
   de dépôt (DESIGNAPP, « Dans le dépôt, la sélection est ink »). */
.btn-check:checked + .btn-toggle {
    --bs-btn-bg: var(--sp-color-action-primary);
    --bs-btn-border-color: var(--sp-color-action-primary);
    --bs-btn-color: var(--sp-color-text-inverse);
    --bs-btn-active-bg: var(--sp-color-action-primary);
    --bs-btn-active-border-color: var(--sp-color-action-primary);
    --bs-btn-active-color: var(--sp-color-text-inverse);
    --bs-btn-hover-bg: var(--sp-color-action-primary);
    --bs-btn-hover-border-color: var(--sp-color-action-primary);
    --bs-btn-hover-color: var(--sp-color-text-inverse);
}

/* Sélection ink dans le parcours de dépôt, et là seulement (DESIGNAPP). Cinq écrans de
   décisions constelleraient la carte d'accents orange, et le seul orange qui compte — celui
   qui dépose — ne se distinguerait plus. La portée est la carte : un panneau de filtres ouvert
   depuis cet écran garde l'orange, il n'est pas dans le parcours. */
.upload-flow .form-check-input:checked {
    background-color: var(--sp-color-action-primary);
    border-color: var(--sp-color-action-primary);
}

.upload-flow .form-check-input:checked:focus {
    border-color: var(--sp-color-action-primary);
}


/* Photo-upload form*/

/* Dropzone (designsystem/spicto/app/components/dropzone.md).

   Blanche, pas grise : les vignettes qui s'y afficheront ensuite ne doivent pas être posées
   sur une couleur. Le POINTILLÉ est ce qui la désigne — c'est le seul signe que l'interface
   n'a pas à expliquer —, en filet chaud et non dans le gris froid de Bootstrap.

   Rayon 2xl (24px) : c'est un bloc posé DANS une carte, et il prend donc le rayon au-dessus
   d'elle (DESIGN, « Un bloc posé dans une carte prend le rayon au-dessus d'elle »). */
.custom-dropzone-wrapper{
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px dashed var(--sp-color-border-cream);
    /* Un cran SOUS la carte imbriquée qui la porte, laquelle est à `xl` (fiche dropzone) : elle
       était à égalité avec elle, et deux surfaces concentriques à rayons identiques ne se lisent
       plus comme emboîtées mais comme mal alignées. Sa grande taille n'y change rien — ce n'est
       pas la surface qui choisit son coin, c'est son rang. */
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-surface);
    padding: var(--sp-space-12) var(--sp-space-8);
    min-height: 280px;
    cursor: pointer;
    transition: border-color var(--sp-duration-base) var(--sp-easing-standard),
                background-color var(--sp-duration-base) var(--sp-easing-standard);
    text-align: center;
    position: relative;
}

.custom-dropzone-wrapper:hover,
.custom-dropzone-wrapper:focus-within{
    border-color: var(--sp-color-border-focus);
    background-color: var(--sp-color-bg-default);
}

/* La contrainte principale de la zone : petite pour ne pas disputer la phrase d'appel, mais en
   graisse de titre — c'est un chiffre qui fait échouer un dépôt quand on le manque (fiche
   dropzone). Elle garde text.default : ce n'est pas de la méta au sens du registre, et la taille
   n'emporte pas la mise en retrait (tronc, « size.meta est une taille, pas un registre »). */
.dropzone-limit{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* La mention légale qui ferme la carte : la seule ligne du bloc qui accompagne vraiment sans
   rien demander, d'où le muted (fiche dropzone). Elle ne descend pas à size.12 pour autant —
   une phrase qui engage l'acheteur prendrait l'air d'un pied de page qu'on ne lit pas. */
.dropzone-legal{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
}

/* L'amorce descend à la même taille que la contrainte ci-dessus, mais pas pour la même raison :
   elle redit le geste que le pointillé annonce déjà. C'est la COULEUR qui départage les deux
   petites lignes — celle qu'il faut lire garde l'ink, celle qui accompagne prend le muted. */
.dropzone-prompt{
    font-size: var(--sp-font-size-meta);
}

/* Les règles SOUS la zone, en deux colonnes de faits — sans puces : ce ne sont pas des étapes
   à suivre (fiche dropzone). Sur mobile la seconde colonne passe dessous. */
/* Les règles sont des FAITS, courts et nombreux : à size.meta, les six tiennent en deux colonnes
   sans que la liste devienne le sujet de la carte (fiche dropzone). Elles gardent text.default —
   ce sont elles qui disent ce que le contrôle refuse, la taille ne les met pas en retrait. */
.dropzone-rules{
    display: grid;
    grid-template-columns: 1fr;
    column-gap: var(--sp-space-8);
    row-gap: var(--sp-space-2);
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: var(--sp-font-size-meta);
}

@media (min-width: 768px) {
    .dropzone-rules{
        grid-template-columns: 1fr 1fr;
    }
}

.custom-dropzone-wrapper .dropzone-content{
    pointer-events: none;
}

.custom-dropzone-input{
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* Photographer & Club Form*/

/* Espaces pro : même grammaire de champ que le reste de l'app (DS input) ;
   l'ancien traitement gris plein sans bordure est abandonné. */
.photographer-layout .input-group-text,
.club-layout .input-group-text {
    background-color: var(--sp-color-bg-surface);
    border-color: var(--sp-color-border-cream);
}

.photographer-layout .form-floating>.form-control,
.photographer-layout .form-floating>.form-control-plaintext,
.photographer-layout .form-floating>.form-select,
.photographer-layout .form-select,
.photographer-layout .form-control{
    height: calc(52px + calc(var(--bs-border-width) * 2));
    min-height: calc(52px + calc(var(--bs-border-width) * 2));
}

.club-layout .form-floating>.form-control,
.club-layout .form-floating>.form-control-plaintext,
.club-layout .form-floating>.form-select,
.club-layout .form-select,
.club-layout .form-control{
    height: calc(52px + calc(var(--bs-border-width) * 2));
    min-height: calc(52px + calc(var(--bs-border-width) * 2));
}

/* LE COIN DU CHAMP SUIT LA ZONE : radius.sm (8px) dehors, radius.md (12px) dans l'espace
   connecté — la MÊME densité que le bouton, et pour la même raison (fiche input). Les deux
   doivent bouger ensemble : un formulaire dont les champs gardent le coin d'accueil sous des
   boutons resserrés se lit comme deux jeux de contrôles empruntés à deux écrans.

   `:where()` n'apporte AUCUNE spécificité : cette règle pèse donc autant que `.form-control`
   seul. C'est délibéré, et c'est ce qui protège les champs non concernés — tous ceux qui
   portent déjà une autre forme sont posés par des règles plus spécifiques (0,2,0), qui gagnent
   quoi qu'il arrive et sans dépendre de l'ordre du fichier :
   - .form-control[type="search"]        le champ de recherche, en pilule ;
   - .search-bar .form-control           la barre de recherche globale, en pilule ;
   - .filter-location-field .form-control le filtre de lieu, en pilule.
   Elle gagne en revanche sur le défaut de Bootstrap, qui pèse pareil mais est chargé avant.

   Ne sont pas visés du tout : .form-check-input (case, radio), .form-switch, les ChoicePill et
   le Slider — ils ne portent pas ces classes. La densité vaut pour la SURFACE OÙ L'ON SAISIT,
   pas pour tout ce qui vit dans un formulaire. */
:where(.club-layout, .photographer-layout){
    --field-radius: var(--sp-radius-md);
}

/* Les surfaces de saisie consomment cette valeur — y compris celles qui TIENNENT LIEU de champ
   sans en porter la classe : la zone qui affiche le fichier retenu, le conteneur qui dessine le
   champ à la place du select qu'il enveloppe. Écrite sur la seule classe des champs, la règle
   les oubliait, et elles restaient au coin d'accueil au milieu de vrais champs resserrés — 4px
   d'écart sur deux bords voisins, que l'œil relève aussitôt (fiche input, « ce qui tient lieu de
   champ suit la règle du champ »).

   Le préfixe est `--field-` et non `--sp-` : ce n'est pas un token du DS mais un véhicule
   d'implémentation, et les deux ne doivent pas se confondre dans la feuille. */
.form-control,
.form-select,
.cover-picker-name{
    border-radius: var(--field-radius);
}

/* PANNEAU DE SUGGESTIONS d'un champ (adresse, ville). C'est le panneau de la SearchBar, pas un
   Dropdown : il ne règle rien, il montre ce qu'on a écrit — donc ni titre ni pied d'actions
   (fiche input, « un champ qui suggère ouvre le panneau de la recherche »).

   Surface flottante NUE : `bg.surface`, `radius.lg`, `shadow.pop` — l'ombre des surfaces qui
   s'ouvrent —, et SANS filet : l'ombre ou le filet, jamais les deux (fiche card). Il pend à
   `space.2` du champ, comme toute surface qui s'ouvre, au lieu de lui être collé.

   Ces règles vivent ici et non dans le contrôleur Stimulus, qui les posait en `style.cssText`
   avec des couleurs en dur (#dee2e6, #f8f9fa) : un panneau stylé en JavaScript échappe aux
   tokens, donc au thème, et personne ne le trouve en cherchant dans la feuille. */
/* L'ancre du panneau : c'est le conteneur du champ qui le porte. */
.places-anchor{
    position: relative;
}

.places-suggestions{
    position: absolute;
    top: calc(100% + var(--sp-space-2));
    left: 0;
    right: 0;
    z-index: 1050;
    display: none;
    max-height: 60vh;
    overflow-y: auto;
    background-color: var(--sp-color-bg-surface);
    border-radius: var(--sp-radius-lg);
    box-shadow: var(--sp-shadow-pop);
}

.places-suggestions.is-open{
    display: block;
}

.places-suggestion{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    padding: var(--sp-space-3) var(--sp-space-4);
    cursor: pointer;
    color: var(--sp-color-text-default);
}

/* Le glyphe d'une installation référencée ne doit pas se laisser écraser par un nom long. */
.places-suggestion-icon{
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--sp-color-text-brown);
}

/* En-tête de GROUPE, pas titre de panneau : il dit de quelle nature sont les lignes qui suivent
   — une installation référencée ou une adresse (fiche search-bar). Même traitement que les
   suggestions de la recherche globale, et il n'apparaît que si les deux groupes coexistent. */
.places-suggestions-title{
    padding: var(--sp-space-3) var(--sp-space-4) var(--sp-space-1);
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-brown);
}

/* Survol et parcours au clavier sont le MÊME état : la ligne visée. Les distinguer ferait deux
   surlignages pour une seule notion, et le clavier hériterait du plus discret. */
.places-suggestion:hover,
.places-suggestion.is-active{
    background-color: var(--sp-color-bg-cream);
}

.places-suggestion:first-child{
    border-top-left-radius: var(--sp-radius-lg);
    border-top-right-radius: var(--sp-radius-lg);
}

.places-suggestion:last-child{
    border-bottom-left-radius: var(--sp-radius-lg);
    border-bottom-right-radius: var(--sp-radius-lg);
}

/* EN-TÊTE DE GROUPE : il mène la liste. Le navigateur le rend en gris, donc plus pâle que les
   options qu'il coiffe — hiérarchie exactement inversée, alors que c'est lui qu'on balaie pour
   se repérer sur cent options. Il reprend le ton du texte et se distingue par la GRAISSE, pas
   par la couleur : deux gris disent deux niveaux d'importance, une graisse dit deux natures —
   un titre et ses entrées (fiche select). */
.form-select optgroup{
    color: var(--sp-color-text-default);
    font-weight: var(--sp-font-weight-title);
}

/* Les options sont les ENFANTS du groupe : elles héritent de sa graisse, et sans ce retour
   explicite la liste devient uniformément grasse — la distinction qu'on vient de créer
   disparaîtrait (fiche select, « les options héritent de leur optgroup »). */
.form-select optgroup option{
    font-weight: var(--sp-font-weight-body);
}

/* Un textarea grandit sur plusieurs lignes : il ne prend pas la hauteur fixe des champs d'une
   ligne, mais garde le même filet, le même rayon et le même fond blanc (attribut `rows` pour la
   hauteur de départ). */
.photographer-layout textarea.form-control,
.club-layout textarea.form-control{
    height: auto;
    min-height: calc(52px + calc(var(--bs-border-width) * 2));
    padding-top: var(--sp-space-3);
    padding-bottom: var(--sp-space-3);
}

/* Sélecteur de couverture : un champ qui porte le nom du fichier (mêmes filet, rayon, fond que les
   inputs texte) et le bouton ink « Choisir un fichier » à sa droite. Le sélecteur natif est masqué
   dans le bouton. */
.cover-picker {
    display: flex;
    align-items: stretch;
    gap: var(--sp-space-3);
}

.cover-picker-name {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    min-width: 0;
    min-height: calc(52px + calc(var(--bs-border-width) * 2));
    padding: 0 var(--sp-space-4);
    background-color: var(--sp-color-bg-surface);
    border: var(--bs-border-width) solid var(--sp-color-border-cream);
    color: var(--sp-color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cover-picker-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
}

/* `text.muted`, pas `bg.grey` : un token de SURFACE employé comme couleur de texte donnait un
   placeholder invisible (un gris de fond sur du blanc, ~1:1) sans qu'aucune règle n'ait l'air
   violée — les deux se ressemblent dans le code, jamais à l'écran (fiche input). */
.photographer-layout .form-control::placeholder,
.club-layout .form-control::placeholder{
    color: var(--sp-color-text-muted);
    opacity: 1;
}

/* DANS un label flottant, le placeholder doit rester TRANSPARENT : c'est le libellé flottant qui
   tient sa place au repos. La règle de placeholder visible ci-dessus le rendait lisible, et libellé
   flottant + placeholder se superposaient sur un champ vide (fiche input). Le placeholder reparaît
   au focus, quand le libellé remonte. */
.photographer-layout .form-floating > .form-control::placeholder,
.club-layout .form-floating > .form-control::placeholder{
    color: transparent;
}

/* Champ DATE (`.is-datefield`) : exception au repos ci-dessus. Le sélecteur (flatpickr) affiche
   toujours un gabarit « jj/mm/aaaa », le libellé ne peut donc pas lui servir de placeholder. On
   flotte le libellé en permanence et on garde le gabarit lisible dessous. Les valeurs de flottement
   sont celles de Bootstrap ; le champ réserve la place en haut pour le libellé remonté. */
.form-floating.is-datefield > label {
    opacity: .65;
    transform: scale(.85) translateY(-.5rem) translateX(.15rem);
}

.form-floating.is-datefield > .form-control {
    padding-top: 1.625rem;
    padding-bottom: .625rem;
}

.photographer-layout .form-floating.is-datefield > .form-control::placeholder,
.club-layout .form-floating.is-datefield > .form-control::placeholder {
    color: var(--sp-color-text-muted);
}

/* LE FOND DU CHAMP NE CHANGE PAS AU FOCUS (fiche input). Ces mêmes sélecteurs teintaient le
   champ actif en bg.grey dans l'espace connecté — donc dans tous les formulaires du produit.
   Deux torts : le doublon (la bordure orange et l'anneau disent déjà où l'on est, et il n'y a
   qu'un champ focalisé par écran) et surtout l'inversion de sens — un neutre clair sous un
   contrôle dit le REPOS dans ce système (bg.control), si bien que le champ prenait l'aspect de
   ce qu'on ne touche pas au moment où l'on y écrit. La règle est supprimée, pas neutralisée :
   .form-control garde son bg.surface posé plus haut, focus compris.

   Elle emportait aussi la SearchBar au passage : .search-bar .form-control:focus pose un fond
   transparent (le verre porte le sien) à spécificité égale mais PLUS HAUT dans le fichier — une
   barre de recherche rencontrée dans l'espace connecté repassait donc au gris. */

.photographer-layout .form-control[type=file],
.club-layout .form-control[type=file]{
    line-height: 34px;
}

.photographer-layout .form-control.custom-upload-file-btn,
.club-layout .form-control.custom-upload-file-btn {
    height: auto;
    min-height: auto;
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
    color: var(--sp-color-bg-surface);
    --bs-border-radius-sm: 5px;
}

/* Vich Image Preview - limit size for responsive */
.photographer-layout .vich-image img,
.club-layout .vich-image img {
    max-width: 300px;
    max-height: 200px;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: var(--bs-border-radius);
}

@media (max-width: 575px) {
    .photographer-layout .vich-image img,
    .club-layout .vich-image img {
        max-width: 100%;
        max-height: 150px;
    }
}

/* COMPONENTS */

/* Accordion */

.accordion {
    --bs-accordion-active-bg: transparent;
    --bs-accordion-active-color: var(--bs-primary);
    --bs-accordion-btn-focus-box-shadow: none;
}

.accordion-button {
    font-size: 16px;
    font-weight: 600;
}

/* Alert */

.alert{
    border: none;
}

.alert-success{
    background-color: var(--success-light);
    color: var(--success);
}

.alert-success.border{
    border-color: rgba(54, 168, 102, 0.22)!important;
}

.alert-info{
    background-color: var(--info-light);
    color: var(--info);
}

.alert-light{
    background-color: var(--light);
    color: var(--dark-grey);
}

.alert-warning{
    background-color: var(--warning-light);
    color: var(--warning);
}

.alert-danger{
    background-color: var(--danger-light);
    color: var(--danger);
}

/* Badges */

/* Badge : le libellé s'écrit comme il se lit — Inter, méta 13px, graisse de corps,
   sans capitales ni interlettrage (fiche badge) */
.badge {
    --bs-badge-font-size: var(--sp-font-size-meta);
    /* Padding vertical serré : le badge reste compact (fiche badge) */
    --bs-badge-padding-y: var(--sp-space-1);
    --bs-badge-padding-x: var(--sp-space-2);
    --bs-badge-font-weight: var(--sp-font-weight-body);
    --bs-badge-border-radius: var(--sp-radius-sm);
    font-family: var(--sp-font-family-base), sans-serif;
    line-height: 1;
}

.badge-circle{
    height: 28px;
    width: 28px;
    border-radius: 5rem;
    font-size: .8rem;
    padding: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.badge-success{
    background-color: var(--success-light);
    color: var(--success);
}


.badge-info{
    background-color: var(--info-light);
    color: var(--info);
}

/* Ton neutral du DS : fond crème, texte ink */
/* Ton « neutral » de la fiche badge : creux crème (bg.sunken, #EFE9DD), texte ink. Pas
   bg.cream (#F5F3EB), trop proche du fond de page pour que l'étiquette se détache. */
.badge-light{
    background-color: var(--sp-color-bg-sunken);
    color: var(--sp-color-text-default);
}

/* Ton live du DS : orange, évènement en direct / du jour */
.badge-live{
    background-color: var(--sp-color-accent-selected);
    color: var(--sp-color-text-inverse);
}

/* Ton verre sombre du DS : compteurs et méta posés sur photo */
.badge-glass{
    display: inline-flex;
    align-items: center;
    gap: 5px;
    /* Le voile PAR-DESSUS la teinte : le dégradé est une image de fond, la teinte la couleur
       qu'il recouvre — les deux se cumulent au lieu de se remplacer. */
    background-color: var(--sp-glass-overlay-bg);
    background-image: var(--sp-glass-overlay-tint);
    color: var(--sp-color-text-inverse);
    border: 1px solid var(--sp-glass-overlay-border);
    box-shadow: var(--sp-glass-overlay-shadow);
    /* La saturation accompagne le flou : sans elle, un flou de 20px délave la photo dessous et
       le verre vire au gris — c'est ce qui distingue un vrai verre d'un rectangle translucide. */
    backdrop-filter: blur(var(--sp-glass-blur)) saturate(var(--sp-glass-saturate));
    -webkit-backdrop-filter: blur(var(--sp-glass-blur)) saturate(var(--sp-glass-saturate));
}

/* Ton dark du DS : méta sur fond clair */
.badge-dark{
    background-color: var(--sp-color-bg-dark);
    color: var(--sp-color-text-inverse);
}

/* Ton count du DS : pastille ronde blanche, chiffre ink (panier, cloche) */
.badge-count{
    --bs-badge-border-radius: var(--sp-radius-full);
    min-width: 18px;
    height: 18px;
    --bs-badge-padding-y: 0;
    --bs-badge-padding-x: 5px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: var(--sp-color-bg-surface);
    color: var(--sp-color-text-default);
    font-size: 11px;
    font-weight: var(--sp-font-weight-bold);
}

/* SportBadge : glyphe de sport sur carré arrondi teinté
   (designsystem/spicto/app/components/sport-badge.md) */
.sport-badge{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 28%;
    background-color: var(--sp-color-accent-soft);
    color: var(--sp-color-accent-selected);
}

.sport-badge svg{
    display: block;
}

.sport-badge-round{
    border-radius: var(--sp-radius-full);
}

.sport-badge-cream{
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-brown);
}

.sport-badge-ink{
    background-color: var(--sp-color-bg-dark);
    color: var(--sp-color-text-inverse);
}

.sport-badge-green{
    background-color: color-mix(in srgb, var(--sp-color-feedback-success) 12%, var(--sp-color-cream-0));
    color: var(--sp-color-feedback-success);
}

.sport-badge-blue{
    background-color: color-mix(in srgb, var(--sp-color-feedback-info) 12%, var(--sp-color-cream-0));
    color: var(--sp-color-feedback-info);
}

.sport-badge-sm{ width: 18px; height: 18px; }
.sport-badge-sm svg{ width: 10px; height: 10px; }
.sport-badge-md{ width: 40px; height: 40px; }
.sport-badge-md svg{ width: 18px; height: 18px; }
.sport-badge-lg{ width: 64px; height: 64px; }
.sport-badge-lg svg{ width: 24px; height: 24px; }
.sport-badge-fill{ width: 100%; height: 100%; }
.sport-badge-fill svg{ width: 33%; height: 33%; }

/* En-tête de section (DESIGNAPP) : titre h3 + icône à gauche, « Voir tout » ink à droite */
.sec-header{
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--sp-space-3);
}

.sec-header-title{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    font-size: var(--sp-font-size-h3);
    font-weight: var(--sp-font-weight-title);
}

/* Dans un en-tête, l'espacement appartient au `.sec-header`, jamais au titre : posée sur un
   enfant de flex, une marge de titre rallongerait la ligne d'en-tête d'autant. Deux classes
   parce que `.photographer-layout h3` (0,1,1) impose encore sa marge de 16px aux h3 des espaces
   pro. Hors en-tête (checkout), le titre garde ses utilitaires de marge. */
.sec-header .sec-header-title{
    margin: 0;
}

/* Le titre d'un bloc DANS une carte, et non d'une section : size.h4 (fiche dropzone). Il ne
   coiffe pas une part de l'écran mais un contenu à l'intérieur d'une carte — à size.h3 il
   pèserait autant que le titre de l'étape qui l'englobe. Ce n'est pas le mésusage que le tronc
   nomme trois fois : on ne rétrécit pas ici pour gagner de la place, mais parce que le bloc ne
   réclame pas le rang qu'il prenait. */
.sec-header-title-sub{
    font-size: var(--sp-font-size-h4);
}

/* Décompte : le pendant du lien « Voir tout », quand la section montre tout */
/* Le décompte d'une section, et l'identification d'une carte de parcours (« Grind Séries ·
   Dépôt 4 ») : même rendu, deux rôles. Les deux accompagnent sans porter (DESIGNAPP). */
.sec-header-count,
.sec-header-context{
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-muted);
}

/* La phrase qui CADRE une section, sous l'en-tête et avant le premier champ : elle instruit,
   elle conditionne ou elle rassure. Ce n'est pas de la méta — le décompte accompagne le titre,
   celle-ci cadre ce qui vient après —, donc jamais muted : estomper reviendrait à parier qu'on
   la lira quand même. C'est la TAILLE qui la range sous le titre, pas la couleur (DESIGNAPP, et
   tronc « size.meta est une taille, pas un registre »). La mention fine d'un champ garde son
   muted, elle. */
.sec-note{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-default);
}

/* CHAMPS FACULTATIFS : un champ ne se dessine pas tant qu'on ne l'a pas demandé (DESIGNAPP).
   Les propositions en tête, les champs ouverts dessous — la liste du haut rétrécit pendant que
   celle du bas grandit, donc le point d'ajout ne bouge pas d'un ajout à l'autre. */
.field-offers{
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-space-1);
}

/* La proposition. Gouttière horizontale nulle : sans surface, il n'y a rien dont se retirer, et
   le glyphe s'aligne ainsi sur le bord du texte qui la surmonte plutôt que de flotter à droite. */
.field-offer{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-2);
    padding-left: 0;
    padding-right: 0;
}

/* `hidden` doit gagner sur le `display` que ces deux règles posent : à spécificité égale, c'est
   la dernière déclarée qui l'emporterait, et la proposition cachée resterait visible. */
.field-offer[hidden],
.field-item[hidden]{
    display: none;
}

/* La gouttière est portée par les items, pas par un conteneur : la liste des champs ouverts est
   vide au premier passage, et un margin sur le conteneur y laisserait un blanc sans contenu. */
.field-item{
    margin-top: var(--sp-space-3);
}

.field-row{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
}

/* Le champ prend la largeur ; `min-width: 0` l'autorise à se réduire — sans lui, la largeur
   minimale de l'input pousserait la croix hors de la carte sur une laize étroite. */
.field-row > .form-floating{
    flex: 1 1 auto;
    min-width: 0;
}

/* Croix de retrait : IconButton variant ghost — icône seule, sans fond, pour une action de
   liste. La cible 44px vient de .btn-circle ; la couleur est celle du ghost (text.default). */
.field-row-remove{
    color: var(--sp-color-text-default);
}

/* En-tête d'une carte de parcours : le seul en-tête suivi d'un filet. Il sépare l'identité de
   la carte, qui ne change pas d'une étape à l'autre, du contenu de l'étape, qui change à chaque
   écran (DESIGNAPP). Un en-tête de section ordinaire n'en porte pas. */
.sec-header--flow{
    padding-bottom: var(--sp-space-4);
    border-bottom: 1px solid var(--sp-color-border-default);
    margin-bottom: var(--sp-space-4);
}

.sec-header-link{
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
    text-decoration: none;
}

/* Rail Live (DESIGNAPP, carrousel) : les cartes mises en avant défilent
   horizontalement, la suivante déborde du bord ; pastilles indicatrices.
   `.pro-rail` est la même piste : elle partage la mécanique et ne garde en propre que les
   largeurs de tuile (voir « Spicto Pro : le rail des avantages » plus bas). */
.event-rail,
.pro-rail{
    display: flex;
    /* Hauteurs égales : les tuiles côte à côte s'étirent sur la plus haute (fiche
       event-card-highlighted). C'est le défaut flex, rendu explicite. */
    align-items: stretch;
    gap: var(--sp-space-3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    padding-bottom: var(--sp-space-1);
}

.event-rail::-webkit-scrollbar,
.pro-rail::-webkit-scrollbar{
    display: none;
}

/* Mobile : une carte presque pleine largeur, 10% de la suivante coupée net.
   Elle est atténuée : elle s'annonce sans se donner (fiche event-rail). */
@media (max-width: 991.98px) {
    .event-rail > *,
    .pro-rail > *{
        flex: 0 0 calc((100% - var(--sp-space-3)) / 1.1);
    }

    .event-rail > *:not(.is-current){
        opacity: .4;
        transition: opacity var(--sp-duration-base) var(--sp-easing-standard);
    }

    /* Sous le seuil (1 carte) : pas de piste, la carte prend toute la largeur */
    .event-rail-section[data-items="1"] .event-rail > *{
        flex-basis: 100%;
        opacity: 1;
    }

    /* Plafond mobile : 3 cartes (fiche event-rail). Le serveur sert le plafond desktop —
       il ignore le viewport — et les deux dernières sont retenues ici. Le rail reste un
       extrait : au-delà, c'est « Voir tout ». */
    .event-rail > *:nth-child(n+4){
        display: none;
    }
}

/* Desktop : trois cartes, puis 50% de la quatrième, à pleine opacité */
@media (min-width: 992px) {
    .event-rail > *{
        flex: 0 0 calc((100% - 3 * var(--sp-space-3)) / 3.5);
    }

    /* Sous le seuil (≤ 3 cartes), aucune surcharge : les cartes gardent la largeur
       qu'elles auraient eue dans la piste et s'alignent à gauche, le reste de la ligne
       demeure vide. Une carte n'a pas de raison de grossir parce qu'elle est seule
       (fiche event-rail) — les étaler donnait une tuile de 1215px de large à une carte
       seule. Sur mobile la règle ne s'applique pas : la carte y occupe déjà presque
       toute la largeur. */
}

@media (prefers-reduced-motion: reduce) {
    .event-rail > *:not(.is-current){
        transition: none;
    }
}

/* EventCardHighlighted DS : plein cadre 3:2, scrim bas-vers-haut, texte blanc
   (designsystem/spicto/app/components/event-card-highlighted.md) */
.event-highlighted{
    position: relative;
    /* Colonne flex, haut et bas répartis : le badge/compteur et le bloc de texte occupent
       deux zones séparées de la tuile — ils ne se chevauchent jamais, quel que soit le
       contenu (fiche event-card-highlighted, règle « aucun chevauchement »). */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    /* 3:2 TENU au redimensionnement : le cadre ne s'étire pas pour faire de la place, c'est
       le contenu qui s'y adapte (titre borné à deux lignes). Une tuile qui grandit avec son
       texte casse la rangée — ses voisines s'alignent sur la plus haute et cessent d'être au
       format. Le plancher de 248px est la seule dérogation, et seulement vers le bas (fiche
       event-card-highlighted). */
    aspect-ratio: 3 / 2;
    min-height: 248px;
    /* Le plancher ne joue que vers le BAS (fiche event-card-highlighted, « la seule
       dérogation au ratio, et elle ne joue que vers le bas »). Sans cette borne il joue aussi
       vers la droite : dans une colonne plus étroite que 372px, `aspect-ratio` recalcule la
       LARGEUR depuis les 248px du plancher, la tuile déborde de son conteneur et la page part
       en défilement horizontal. En piste (`event-rail`) le débord ne se voyait pas — les
       cartes y ont une base flex explicite. Il apparaît partout où la tuile est un bloc
       ordinaire : la grille de gestion, celle de « Récent ». */
    max-width: 100%;
    border-radius: var(--sp-radius-xl);
    overflow: hidden;
    scroll-snap-align: start;
    background-color: var(--sp-color-bg-dark);
    text-decoration: none;
    transition: box-shadow var(--sp-duration-fast) var(--sp-easing-standard);
}

/* L'ombre seule : la tuile ne se déplace pas. Elle porte une photographie plein cadre, et
   déplacer la surface déplace l'image — sur une grille, le moindre passage de souris ferait
   sautiller la vignette qu'on est en train de lire (tronc DESIGN.md, Motion). */
.event-highlighted:hover{
    box-shadow: var(--sp-shadow-md);
}

.event-highlighted:focus-visible{
    outline: none;
    box-shadow: var(--sp-shadow-ring);
}

@media (prefers-reduced-motion: reduce) {
    .event-highlighted{
        transition: none;
    }
}

.event-highlighted-cover{
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: brightness(.62) saturate(.9);
    z-index: 0;
}

/* Le haut (badge de date + compteur) est un enfant en flux, ancré en haut de la colonne :
   il ne recouvre plus le scrim, la répartition space-between les sépare. */
.event-highlighted-top{
    position: relative;
    z-index: 1;
    padding: var(--sp-space-3);
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--sp-space-2);
}

/* Le badge d'échéance et le compteur partagent une ligne, ils doivent donc partager une
   hauteur. Le ton verre porte un liseré d'1px, les tons pleins n'en ont pas : sans cette
   bordure transparente, le `danger` est 2px plus court que le compteur d'en face — et l'écart
   se voit d'une carte à l'autre, puisque dans son autre ton le même badge s'aligne au pixel.
   `:not(.badge-glass)` pour ne pas effacer le liseré du verre, qui lui est structurel. */
.event-highlighted-top .badge:not(.badge-glass){
    border: 1px solid transparent;
    /* Et le même décollement que le verre d'en face : c'est la photo dessous qui l'exige, pas
       la matière du badge (cf. `glass.overlay.lift`). */
    box-shadow: var(--sp-glass-overlay-lift);
}

/* Variant short : la même tuile, en dense. Quatre écarts, pas un de plus
   (fiche event-card-highlighted, variant `short`). */

/* 1. aucune hauteur imposée : ni 4:3, ni plancher 248px, le texte donne la hauteur */
.event-highlighted-short{
    aspect-ratio: auto;
    min-height: auto;
}

/* 2. le titre est clampé à 1 seule ligne (au lieu de 2) : la densité prime */
.event-highlighted-short .event-highlighted-title{
    -webkit-line-clamp: 1;
    line-clamp: 1;
}

/* La ligne d'en-tête (sport + badge) respire au-dessus du bloc titre/méta : un espace
   l'en sépare, elle ne lui est pas collée (fiche event-card-highlighted, variant short). */
.event-highlighted-short .event-highlighted-sport-line{
    margin-bottom: var(--sp-space-2);
}

/* 3. le badge de date quitte le haut (désormais vide) pour la ligne du sport, en vis-à-vis */
.event-highlighted-short .event-highlighted-top{
    display: none;
}

/* Le scrim est un enfant en flux, ancré en bas de la colonne (fiche event-card-highlighted).
   Son dégradé couvre son propre bloc de texte ; jamais superposé au haut. */
.event-highlighted-scrim{
    position: relative;
    z-index: 1;
    padding: var(--sp-space-4);
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: linear-gradient(to top, var(--sp-color-overlay-scrim), transparent);
    color: var(--sp-color-text-inverse);
}

/* Variant header (fiche event-card-highlighted, variant `header`) : coiffe la page
   évènement. Comme short — hauteur libre, scrim relatif —, mais pleine largeur, une hauteur
   minimale pour que la photo respire, et les actions de page dans les coins hauts. Ce n'est
   pas un lien : c'est un <section>, il ne défile pas plus qu'une autre section (plus de
   sticky). */
.event-highlighted-header{
    /* flex colonne, texte poussé en bas : les actions (absolues) restent en haut, le scrim
       descend au ras du bord bas, son dégradé couvre alors toute la moitié basse sans laisser
       de photo nue en dessous. */
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    aspect-ratio: auto;
    min-height: 248px;
}

.event-highlighted-header .event-highlighted-scrim{
    position: relative;
    /* le dégradé démarre plus haut pour couvrir généreusement, et laisse la place aux
       actions au-dessus du texte */
    padding-top: var(--sp-space-16);
}

/* Exception assumée au space.4 des cartes photo (fiche card, fiche event-card-highlighted) :
   le header qui coiffe VRAIMENT la page publique de l'évènement — et lui seul, via --page —
   ouvre sa gouttière de scrim à space.6 (24px). Sur son propre écran plein cadre il porte son
   titre en display (h1/40px), qu'un scrim à 16px lirait à l'étroit. Les reprises du même chrome
   (aside de dépôt, aperçu de création, en --labelled) gardent le space.4. Le haut garde sa
   réserve (space.16) pour dégager les actions de coin — seuls les trois autres côtés à 24px. */
.event-highlighted-header--page .event-highlighted-scrim{
    padding: var(--sp-space-16) var(--sp-space-6) var(--sp-space-6);
}

/* Actions de page dans les coins hauts, sur le scrim : retour à gauche, menu/partage à
   droite. IconButton clairs sur la photo sombre. */
.event-highlighted-header .event-highlighted-actions{
    position: absolute;
    top: var(--sp-space-4);
    left: var(--sp-space-4);
    right: var(--sp-space-4);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-2);
}

/* Deux retours, deux formes (fiche event-card-highlighted) : le DISQUE revient d'un cran, le
   LIBELLÉ à côté quitte le parcours pour un endroit nommé. Ils se distinguent par la forme, pas
   par la couleur — l'un est un disque sans mot, l'autre un mot sans disque. */
.event-back{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-3);
    min-width: 0;
}

/* Une MÉTA, pas un titre (fiche event-card-highlighted) : le libellé partage la surface avec le
   nom de l'évènement, qui est le sujet de la carte, et un chemin de retour ne pèse pas autant que
   ce sur quoi on travaille. Il gardait `font.size.16` — une PRIMITIVE, que le DS interdit à un
   composant de lire — en graisse de titre ; il passe à size.meta / weight.body. Le blanc plein ne
   suit pas la descente : c'est la taille qui le range, pas la couleur, et il doit rester lisible
   sur la photo. */
.event-back-label{
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
    text-decoration: none;
    /* Le libellé cède avant la carte : sur une colonne étroite il se tronque plutôt que de
       pousser le badge de date hors du cadre. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.event-back-label:hover{
    color: var(--sp-color-text-inverse);
    text-decoration: underline;
}

/* Scrim HAUT, et seulement quand une action de coin porte un libellé (fiche
   event-card-highlighted). Un disque verre se suffit à lui-même ; une phrase posée sur la photo
   n'a rien sous elle, et sa lisibilité dépendrait du cliché. Symétrique de celui du bas, borné
   à la hauteur de la ligne d'actions : assombrir plus mangerait la photo par les deux bouts. */
.event-highlighted-header--labelled::before{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: calc(var(--sp-control-icon-button) + var(--sp-space-8));
    z-index: 1;
    background: linear-gradient(to bottom, var(--sp-color-overlay-scrim), transparent);
    pointer-events: none;
}

.event-highlighted-header .event-highlighted-actions-group{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
}

/* Ligne du sport : en short, le sport et le badge de date s'y font face (space-between) ;
   en version pleine, le sport y est seul, donc calé à gauche (fiche event-card-highlighted). */
.event-highlighted-sport-line{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-2);
}

.event-highlighted-sport{
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--sp-font-size-12);
    font-weight: var(--sp-font-weight-title);
}

.event-highlighted-title{
    /* Blanc explicite : le scrim le donne par héritage au span du composant, mais un <h1>
       (variant header) a sa couleur de base et casserait l'héritage. Tout le texte du
       scrim est blanc (fiche event-card-highlighted). */
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-h3);
    font-weight: var(--sp-font-weight-title);
    letter-spacing: var(--sp-font-tracking-tight);
    margin: 0;
    /* 2 lignes maximum, tronqué proprement par une ellipsis (fiche event-card-highlighted) :
       borne le bloc de texte et le tient à distance des superpositions du haut. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

/* Header : la page de l'évènement montre son titre en entier — pas de troncature sur
   sa propre page (le clamp 2 lignes ne vaut que pour les tuiles en liste/rail). */
.event-highlighted-header .event-highlighted-title{
    display: block;
    -webkit-line-clamp: none;
    line-clamp: none;
    overflow: visible;
}

/* Display de page (h1, 40px) réservé au titre qui EST le hero : le <h1> de la vraie page
   évènement, unique titre de document de l'écran. Le même chrome header est réutilisé en aside
   de dépôt et en aperçu de création, mais avec un <p> — pas un second h1 —, et ce titre-là
   reste à l'échelle carte (h3, règle de base). Le poids `weight.title` (Semibold 600) et Inter
   sont déjà hérités de la base : seule la taille change ici.

   Et seulement sur DESKTOP (mobile-first, fiche event-card-highlighted) : sur mobile la card
   prend toute la largeur et 40px sur la photo écraserait le scrim et casserait sur plusieurs
   lignes. Sous 992px, aucune règle ici — le titre retombe sur l'échelle de base (h3, 20px). */
@media (min-width: 992px) {
    .event-highlighted-header h1.event-highlighted-title{
        font-size: var(--sp-font-size-h1);
    }
}

.event-highlighted-meta{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
}

/* Header : « aujourd'hui » remplace la date et passe à l'orange de marque (fiche
   event-card-highlighted, variant header). */
/* Variant `manage` : l'état temporel n'est plus un badge de coin, il s'écrit dans la méta entre
   le lieu et la date, dans la couleur de son état (fiche event-card-highlighted). Un badge
   signale une exception ; sur « Mes évènements » toutes les tuiles en portent un, et ce qui est
   partout ne signale plus rien.

   Seul endroit du système où plusieurs couleurs se posent sur un scrim : la méta n'y dit plus
   une date mais un STATUT, qu'on veut balayer sans lire. Les trois tons y tiennent l'AA — il n'y
   a pas de quatrième ton parce que `feedback.warning` n'a pas de variante lisible sur ce sol
   (cf. tronc) ; l'échéance parle donc le rouge de l'expiration, dont elle est le prélude. */
.event-highlighted-when{
    font-weight: var(--sp-font-weight-title);
}

.event-highlighted-when--now{
    color: var(--sp-color-accent-selected);
}

/* Le vert clair, pas le vert-sarcelle de feedback.success : celui-ci disparaît sur un scrim.
   Le rôle est emprunté — il ne dit pas une réussite, il dit ce qui est encore devant. */
.event-highlighted-when--future{
    color: var(--sp-color-feedback-success-fill);
}

/* La variante claire du rouge, pas `feedback.danger` : sur un scrim, red.600 tombe à 2,59:1 et
   passe pour du brun (cf. tronc, délégation selon la surface). */
.event-highlighted-when--danger{
    color: var(--sp-color-feedback-danger-on-dark);
}

/* La plage se dit avec la flèche du système, comme le résumé « début → fin » du Calendar
   (fiche calendar) : le tiret est écarté des contenus, et deux dates accolées se liraient
   comme deux évènements. L'inline-flex n'est là que pour aligner le glyphe sur la ligne de
   texte, sans nudge en dur. */
.event-highlighted-range{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-1);
}

/* --- EventRail : commandes et repères (fiche event-rail) -------------------
   Les sélecteurs passent par .event-rail-section : ils l'emportent sur .btn-circle
   sans dépendre de l'ordre du fichier. Chaque commande ne sert qu'un support :
   les pastilles le doigt, les chevrons la souris — jamais les deux ensemble. */

.event-rail-viewport{
    position: relative;
}

/* Pastilles : mobile seulement */
.event-rail-section .rail-dots{
    display: flex;
    gap: 6px;
    justify-content: center;
    padding-top: var(--sp-space-3);
}

.rail-dot{
    width: var(--sp-carousel-dot);
    height: var(--sp-carousel-dot);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-border-cream);
}

.rail-dot.active{
    background-color: var(--sp-color-accent-selected);
}

/* Chevrons : desktop seulement */
.event-rail-section .rail-control{
    display: none;
}

@media (min-width: 992px) {
    .event-rail-section .rail-dots{
        display: none;
    }

    .event-rail-section .rail-control:not(.d-none){
        display: inline-flex;
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        z-index: 3;
        box-shadow: var(--sp-shadow-md);
    }

    /* Une commande ne se dérobe pas sous le doigt : pas de press ici */
    .event-rail-section .rail-control:not(:disabled):active{
        transform: translateY(-50%);
    }

    /* La piste court désormais jusqu'aux bords de la Card : les chevrons se calent dans
       la gouttière rendue à la piste, et gardent la place qu'ils occupaient (12px du bord
       de la Card) — sans ce report, ils sortiraient de la Card. */
    .event-rail-section .rail-control-prev{
        left: var(--sp-space-3);
    }

    .event-rail-section .rail-control-next{
        right: var(--sp-space-3);
    }
}

/* Pastilles indicatrices du rail : active orange, inactives filet chaud */
/* Avatar DS (designsystem/spicto/app/components/avatar.md) : rond, photo cover,
   repli en initiales crème/brun, jamais d'anneau orange décoratif */
.avatar{
    display: inline-flex;
    flex-shrink: 0;
    border-radius: var(--sp-radius-full);
    overflow: hidden;
}

/* xs : la seule taille qui se pose DANS une ligne de texte (fiche avatar) */
.avatar-xs{ width: var(--sp-avatar-xs); height: var(--sp-avatar-xs); }
.avatar-sm{ width: var(--sp-avatar-sm); height: var(--sp-avatar-sm); }
.avatar-md{ width: var(--sp-avatar-md); height: var(--sp-avatar-md); }
/* lg et xl : la personne est le sujet de la surface (Card variant `photo`). Le même rôle à
   deux dispositions — lg quand le portrait est AU-DESSUS du nom et partage sa hauteur, xl
   quand il est À CÔTÉ et que la hauteur se libère (fiche avatar). */
.avatar-lg{ width: var(--sp-avatar-lg); height: var(--sp-avatar-lg); }
.avatar-xl{ width: var(--sp-avatar-xl); height: var(--sp-avatar-xl); }

/* Ce qui garantit le cercle en rangée souple est déjà là : `flex-shrink: 0` sur `.avatar`
   l'empêche d'être comprimé, et sa hauteur EXPLICITE l'empêche d'être étiré (l'étirement
   transverse d'un flex ne s'applique qu'à une taille `auto`). Rien à ajouter — surtout pas un
   `align-self` global, qui toucherait tous les avatars du produit pour un cas de carte. */

/* La photo remplit le cercle sans être ni étirée ni coupée au bord : cover, centrée. */
.avatar-img{
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

.avatar-fallback{
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-brown);
    font-size: var(--sp-font-size-12);
    font-weight: var(--sp-font-weight-title);
    letter-spacing: 0;
}

/* Portrait par défaut (fiche avatar, variant `default`) : le glyphe de personne sur l'un des
   quatre tons décoratifs fermés du DS. Ce sont exactement les tons de SportBadge moins le pêche —
   l'orange reste l'accent d'action, et le pêche est déjà ce que porte un évènement sans photo. */
.avatar-default{
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Le glyphe se dimensionne en proportion du cercle : les cinq tailles d'avatar le traversent. */
.avatar-default svg{
    width: 55%;
    height: 55%;
}

.avatar-default--cream{
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-brown);
}

.avatar-default--ink{
    background-color: var(--sp-color-bg-dark);
    color: var(--sp-color-text-inverse);
}

.avatar-default--green{
    background-color: color-mix(in srgb, var(--sp-color-feedback-success) 12%, var(--sp-color-cream-0));
    color: var(--sp-color-feedback-success);
}

.avatar-default--blue{
    background-color: color-mix(in srgb, var(--sp-color-feedback-info) 12%, var(--sp-color-cream-0));
    color: var(--sp-color-feedback-info);
}

/* ListItem DS (designsystem/spicto/app/components/list-item.md) : rôles de texte
   de la ligne de liste canonique ; la pastille de sport chevauche le coin
   haut-droit de la vignette */
.list-item-title{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

.list-item-detail{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-default);
}

.list-item-accent{
    color: var(--sp-color-accent-selected);
}

/* Un rail posé DANS une carte : la carte lui rend sa gouttière horizontale, pour que la tuile
   qui déborde soit coupée par le bord de la carte et non arrêtée 24px avant — c'est la coupe au
   bord qui dit que ça continue. La gouttière n'est pas supprimée, elle change de porteur (fiche
   card, et event-rail « Posé sur une Card »). */
.event-rail-section--in-card{
    margin-left: calc(-1 * var(--sp-space-6));
    margin-right: calc(-1 * var(--sp-space-6));
}

.event-rail-section--in-card .event-rail{
    padding-left: var(--sp-space-6);
    padding-right: var(--sp-space-6);
    scroll-padding-left: var(--sp-space-6);
}

/* --- Spicto Pro : le rail des avantages (fiche event-rail) ---------------------------------
   La piste et sa largeur mobile sont celles de `.event-rail`, déclarées avec elle. Ne restent
   ici que les deux écarts : le calage des tuiles, que l'EventRail porte sur sa carte, et deux
   avantages et demi sur desktop au lieu de trois et demi. Pas de plafond de cartes non plus :
   les avantages sont cinq et se comptent tous — en retenir un au-delà de trois ferait
   disparaître celui qu'on vient de nommer. Les commandes (chevrons, pastilles) restent celles
   du rail, d'où le `.event-rail-section` qui les porte. */
.pro-rail > *{
    scroll-snap-align: start;
}

/* Le rail des avantages vit toujours dans une carte : la gouttière rendue par `--in-card` est
   écrite pour `.event-rail`, la piste Pro la reprend ici. */
.event-rail-section--in-card .pro-rail{
    padding-left: var(--sp-space-6);
    padding-right: var(--sp-space-6);
    scroll-padding-left: var(--sp-space-6);
}

@media (min-width: 992px) {
    .pro-rail > *{
        flex: 0 0 calc((100% - 2 * var(--sp-space-3)) / 2.5);
    }
}

/* Les trois montants d'un assujetti sur une seule ligne, séparés comme de la méta : HT et TVA
   accompagnent, seul le TTC est le montant (DESIGN, « Nombres et méta »). */
.vat-amounts{
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-space-2);
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Le montant qu'on paie sort du registre secondaire : HT et TVA accompagnent, lui porte
   (DESIGN, « size.meta est une taille, pas un registre » — le muted ne se déduit pas de la
   taille, et une somme à débiter ne s'estompe pas). */
.vat-amounts-total{
    font-size: var(--sp-font-size-body);
    color: var(--sp-color-text-default);
}

/* Une tuile qui porte l'action de l'écran : l'action se pose À CÔTÉ du lien, superposée à la
   tuile — la tuile EST un lien, et un bouton dans un lien n'est ni valide ni atteignable
   (fiche event-card-highlighted). Elle occupe la place du compteur, jamais celle du texte. */
.event-highlighted-wrap{
    position: relative;
    height: 100%;
}

.event-highlighted-action{
    position: absolute;
    top: var(--sp-space-3);
    right: var(--sp-space-3);
    z-index: 2;
}

/* Variant `manage` (fiche event-card-highlighted) : sur un écran de gestion, les actions
   descendent en barre au pied. Le coin haut droit tient UNE action ; ici il y en a jusqu'à
   trois, et trois boutons empilés dans un coin recouvriraient la photographie qui sert
   justement à reconnaître sa propre sortie parmi trente.

   Elle se range dans la GOUTTIÈRE DE CARTE (space.4) sur ses trois côtés, et l'occupe
   entièrement : le premier bouton commence exactement sous le titre, le dernier finit sous la
   fin de la méta. Sport, titre, méta et actions forment une seule colonne — un retrait propre,
   fût-il de 4px, se lirait comme un défaut de montage (fiche event-card-highlighted). */
.event-highlighted-action--bar{
    top: auto;
    left: var(--sp-space-4);
    right: var(--sp-space-4);
    bottom: var(--sp-space-4);
    display: flex;
    align-items: center;
    /* Plus large que la gouttière interne d'un bouton : deux commandes, pas un contrôle
       segmenté (fiche). */
    gap: var(--sp-space-3);
}

/* « Aucun chevauchement » (fiche) : c'est le TEXTE qui remonte pour laisser la barre, jamais
   l'action qui descend sur lui. La réserve vaut la hauteur d'un bouton `sm` (36px : gouttière
   verticale, filet et hauteur de ligne — aucun token ne la porte) plus la gouttière de carte
   au-dessus et en dessous. */
.event-highlighted-manage .event-highlighted-scrim{
    padding-bottom: calc(var(--sp-space-4) * 2 + 36px);
}

/* Le bouton qui déborde (« Modifier », « 923 photos publiées ») prend la place disponible ;
   celui qui dépose garde sa largeur — c'est lui qu'on vise, il ne doit pas se déplacer d'une
   tuile à l'autre selon la longueur du voisin. */
.event-highlighted-action--bar .btn{
    min-width: 0;
}

.event-highlighted-action--bar .btn-grow{
    flex: 1 1 auto;
}

/* Deux chemins vers la même fin : un filet portant le mot « ou ». Sans lui, les deux cartes se
   lisent comme deux étapes à suivre l'une après l'autre (DESIGNAPP). */
.path-separator{
    display: flex;
    align-items: center;
    gap: var(--sp-space-4);
    color: var(--sp-color-text-brown);
}

.path-separator::before,
.path-separator::after{
    content: "";
    flex: 1;
    border-top: 1px solid var(--sp-color-border-default);
}

/* Champ qui cherche : toujours un pill, loupe de tête (fiche input, variant `search`).
   Distinct de la SearchBar globale, qui ajoute au pill son traitement liquid glass. */
.input-search{
    position: relative;
    display: flex;
    align-items: center;
}

.input-search .form-control{
    padding-left: var(--sp-space-10);
    padding-right: var(--sp-space-10);
    border-radius: var(--sp-radius-full);
    border-color: var(--sp-color-border-cream);
    min-height: 44px;
}

.input-search-icon,
.input-search-clear{
    position: absolute;
    display: inline-flex;
    align-items: center;
    color: var(--sp-color-text-muted);
}

.input-search-icon{
    left: var(--sp-space-4);
    pointer-events: none;
}

.input-search-clear{
    right: var(--sp-space-4);
    color: var(--sp-color-text-default);
}

/* Le champ « dossard » de la frise d'un évènement : un champ qui cherche (fiche input, variant
   `search`), posé dans le panneau de sa chip. Il prend toute la largeur du panneau. */
.event-bib-search{
    width: 100%;
}

.event-bib-search .input-search{
    width: 100%;
}

/* Ligne d'évènement dont le dépôt n'est pas encore ouvert : montrée inerte plutôt que cachée
   — la cacher pousse à créer un doublon (DESIGNAPP, « Choisir un évènement »). L'estompe dit
   l'indisponibilité, la mention en dit la DATE : la couleur seule ne porte jamais l'info. */
.list-item--disabled{
    opacity: .55;
}

/* L'action de ligne fournie par l'écran s'aligne en haut à droite, comme l'IconButton du
   composant (fiche list-item). */
.list-item-action-btn{
    align-self: flex-start;
    flex-shrink: 0;
}

.list-item-note{
    align-self: flex-start;
    flex-shrink: 0;
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Variant `agenda` : le sport écrit en toutes lettres au-dessus du titre, précédé de son
   glyphe. Rôle de méta (13px/600), pas une taille inventée — dans une frise, chaque ligne est
   isolée et le sport est ce qui distingue deux évènements (fiche list-item). */
.list-item-sport{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

.list-item-meta{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

.list-item-thumb{
    position: relative;
    border-radius: var(--sp-radius-md);
}

.list-item-thumb .ratio,
.list-item-thumb .thumbnail-wrapper{
    border-radius: var(--sp-radius-md);
    overflow: hidden;
}

.list-item-thumb > .sport-badge:not(.sport-badge-fill){
    position: absolute;
    top: -6px;
    right: -6px;
    z-index: 2;
}

/* Sections sur desktop : le contenu (hors en-tête) est posé sur une Card ; sur mobile
   il court à même le fond (DESIGNAPP, « Sections sur desktop ») */
@media (min-width: 992px) {
    .section-surface{
        background-color: var(--sp-color-bg-surface);
        border-radius: var(--sp-radius-lg);
        box-shadow: var(--sp-shadow-sm);
        padding: var(--sp-space-6);   /* la gouttière de la Card (fiche card) */
    }

    /* Une Card qui porte un rail ne borne pas sa piste (fiche event-rail) : la gouttière
       passe à la piste elle-même. L'item qui déborde est alors coupé par le bord de la
       Card, et non arrêté 24px avant — c'est la coupe au bord qui dit que ça continue.
       La gouttière défile avec la piste : après navigation, c'est l'item de gauche qui
       est coupé au bord, sans qu'aucune valeur ait à changer.

       Les deux classes portent le même élément : ce sélecteur l'emporte par sa
       spécificité (0,2,0) et non par sa place dans le fichier — l'ordre seul avait déjà
       rendu muettes les commandes du rail. */
    .section-surface.event-rail-section{
        padding-left: 0;
        padding-right: 0;
    }

    .event-rail-section .event-rail{
        padding-left: var(--sp-space-6);
        padding-right: var(--sp-space-6);
        /* sans quoi le calage ramène la première carte au bord et mange la gouttière */
        scroll-padding-left: var(--sp-space-6);
    }
}

/* Grille de ListItem : 1 colonne sur mobile, 3 à parts égales sur desktop,
   gouttière space.3 dans les deux sens (fiche list-item) */
.list-item-grid{
    --bs-gutter-x: var(--sp-space-3);
    --bs-gutter-y: var(--sp-space-3);
}

/* ListItem : la ligne de liste canonique (fiche list-item). Vignette 72px, bloc de
   texte centré et aéré, action de fin de ligne alignée en haut. Le séparateur court sur
   la largeur du bloc de texte ; sur le dernier item, sur toute la largeur. */
.list-item{
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-space-4);
    padding-top: var(--sp-space-4);
    padding-bottom: var(--sp-space-4);
}

.list-item .list-item-thumb{
    width: 72px;
    height: 72px;
    flex-shrink: 0;
}

.list-item-body{
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 5px;   /* interligne confortable entre les rôles (fiche list-item) */
}

.list-item:not(:last-child)::after{
    content: '';
    position: absolute;
    bottom: 0;
    left: calc(72px + var(--sp-space-4));
    right: 0;
    border-bottom: 1px solid var(--sp-color-border-default);
}

.list-item:last-child{
    border-bottom: 1px solid var(--sp-color-border-default);
}

/* Grille (desktop) : le filet sépare les rangées, la dernière n'en porte donc pas —
   il ne séparerait plus rien (fiche list-item).

   Le filet passe sous l'item de la rangée suivante plutôt qu'au-dessus de celui de la
   rangée précédente : la dernière rangée n'est pas désignable en CSS — un ad-slot
   pleine ligne décale les rangées, et le défilement infini la déplace à chaque page —
   là où la première rangée, elle, est toujours les trois premiers items. On retire donc
   le filet de tous, et on le rend à tout ce qui n'est pas la première rangée. */
@media (min-width: 992px) {
    /* Les deux filets de la liste verticale sont neutralisés : lequel s'affiche dépend
       de la place de l'item dans sa colonne, or la modale d'abonnement y traîne comme
       frère — un même item porte donc le ::after ou le border-bottom selon qu'on lui
       passe showSubscription. En grille, le filet appartient à la rangée, pas à ça. */
    .list-item-grid > .col .list-item{
        border-bottom: 0;
    }

    .list-item-grid > .col .list-item::after{
        content: none;
    }

    /* Rendu à toute rangée sauf la première, au-dessus de l'item, aligné après la
       vignette comme dans la liste verticale. */
    .list-item-grid > .col:not(:nth-child(-n+3)) .list-item::after{
        content: '';
        position: absolute;
        top: 0;
        bottom: auto;
        left: calc(72px + var(--sp-space-4));
        right: 0;
        border-top: 1px solid var(--sp-color-border-default);
        border-bottom: 0;
    }
}

.list-item .list-item-action{
    position: relative;
    z-index: 2;
    align-self: flex-start;
    color: var(--sp-color-text-default);
}

/* AdSlot DS (designsystem/spicto/app/components/ad-slot.md) : encart publicitaire
   identifié, surface pêche pâle, jamais l'apparence d'une carte de contenu */
.ad-slot{
    background-color: var(--sp-color-accent-softer);
    border-radius: var(--sp-radius-lg);
    padding: var(--sp-space-4);
    text-align: center;
}

.ad-slot-label{
    display: block;
    color: var(--sp-color-action-marketing);
    font-size: var(--sp-font-size-12);
    font-weight: var(--sp-font-weight-title);
    letter-spacing: .08em;
    text-transform: uppercase;
    margin-bottom: var(--sp-space-2);
}

/* PhotoGallery DS (designsystem/spicto/app/components/photo-gallery.md) :
   tuiles carrées, coins radius.md, gap 16px issu de l'échelle */
.photo-gallery{
    --bs-gutter-x: var(--sp-space-4);
    --bs-gutter-y: var(--sp-space-4);
}

.photo-gallery .ratio-1x1{
    border-radius: var(--sp-radius-md);
    overflow: hidden;
}

.photo-gallery a{
    display: block;
    border-radius: var(--sp-radius-md);
}

.photo-gallery a:focus-visible{
    outline: none;
    box-shadow: var(--sp-shadow-ring);
}

/* Tuile « +N » : photo réelle sous voile sombre, nombre restant en blanc */
.photo-gallery-more-overlay{
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--sp-color-overlay-scrim);
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-h3);
    font-weight: var(--sp-font-weight-title);
    border-radius: var(--sp-radius-md);
}

/* Timeline DS (designsystem/spicto/app/components/timeline.md) : frise verticale des journées
   d'un évènement. Rail continu chaud, un nœud par journée (pastille + en-tête + grille), nœud
   courant en orange, nœud « + » final pour les photos à venir. */
/* Carte de section d'une page évènement, mais sur DESKTOP seulement (mobile-first, DESIGNAPP) :
   la galerie en frise, et les deux blocs de l'écran « sans photos » (Photos, Photographes). Sur
   mobile, aucune règle : le contenu reste sur le fond crème, pleine largeur — la photo est la
   vedette, une carte la rognerait. À partir de 992px, mêmes valeurs qu'une carte de section
   (fiche card : bg.surface, radius.lg, space.6, shadow.sm) : si ces tokens bougent dans .card,
   les répercuter ici. Une seule classe pour ne pas fabriquer un second langage de carte. */
@media (min-width: 992px) {
    .event-section-card{
        background-color: var(--sp-color-bg-surface);
        border-radius: var(--sp-radius-lg);
        box-shadow: var(--sp-shadow-sm);
        padding: var(--sp-space-6);
    }
}

/* Zone AU REPOS — à ne pas confondre avec une zone EN ATTENTE (DESIGNAPP, zone
   « Téléchargements » du dépôt). Le pointillé du produit dit qu'un contenu VIENDRA là : une
   journée de frise qui recevra ses photos, un album qu'on va créer. Ici rien n'est en route et
   rien n'est promis — la zone garde seulement la place de ce qui viendra SI l'on dépose. D'où le
   filet continu, aucun aplat (le fond de la page traverse), et la hauteur qui réserve
   l'emplacement des cartes de transfert.

   Le texte est en text.brown et non en text.muted : ce gris-là est calculé pour le blanc, et la
   zone est posée sur le crème de la page (DESIGNAPP, « Bordures et séparateurs : c'est le SOL »).
   Même raison pour le filet, chaud lui aussi. */
.idle-panel{
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 160px;
    padding: var(--sp-space-6);
    border: var(--sp-border-width-default) solid var(--sp-color-border-cream);
    border-radius: var(--sp-radius-lg);
    text-align: center;
}

.idle-panel-text{
    color: var(--sp-color-text-brown);
}

/* État « sans photos » d'un évènement (DESIGNAPP, « Un évènement sans photos »). Encart d'attente
   en pointillé chaud (border.cream), texte muted centré : le pointillé dit l'attente, une surface
   pleine dirait un contenu — même langage que la journée `awaiting` de la frise. */
.event-empty-panel{
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 220px;
    padding: var(--sp-space-6);
    border: 1px dashed var(--sp-color-border-cream);
    border-radius: var(--sp-radius-lg);
    text-align: center;
}

.event-empty-panel-text{
    color: var(--sp-color-text-muted);
}

/* Carte d'un photographe inscrit : filet chaud, radius, pas d'ombre (surface posée sur la
   colonne). La carte de l'utilisateur courant se réchauffe en crème et porte le badge « Moi ». */
.event-photographer-card{
    position: relative;
    padding: var(--sp-space-3);
    border: 1px solid var(--sp-color-border-default);
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-surface);
}

.event-photographer-card--me{
    background-color: var(--sp-color-bg-cream);
}

/* Badge « Moi » : pastille ink au coin haut droit, au-dessus du lien étiré de la carte
   (stretched-link de ProfileListItem, z-index 1) — sinon il passerait dessous et deviendrait
   cliquable comme le reste de la carte. */
.event-photographer-me{
    position: absolute;
    top: var(--sp-space-3);
    right: var(--sp-space-3);
    z-index: 2;
    padding: 2px var(--sp-space-2);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-bg-dark);
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
}

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

.timeline-item{
    position: relative;
    display: grid;
    grid-template-columns: 16px 1fr;
    gap: var(--sp-space-4);
    padding-bottom: var(--sp-space-8);
}

.timeline-item:last-child{
    padding-bottom: 0;
}

/* Le rail : un filet chaud continu, tracé depuis la pastille jusqu'au nœud suivant. Il passe
   derrière les pastilles (z-index) et ne descend pas sous le dernier nœud. */
.timeline-marker{
    position: relative;
    display: flex;
    justify-content: center;
}

.timeline-item:not(:last-child) .timeline-marker::before{
    content: "";
    position: absolute;
    left: 50%;
    /* Part du centre de la pastille — donc du milieu de la ligne de la date — et rejoint
       exactement celui de la pastille suivante. Plus haut, le filet dépasserait au-dessus. */
    top: var(--sp-space-2);
    top: calc(1lh / 2);
    height: calc(100% + var(--sp-space-8));
    width: 1px;
    transform: translateX(-50%);
    background-color: var(--sp-color-border-default);
}

.timeline-dot{
    position: relative;
    z-index: 1;
    /* 7×7 et rien ne la déforme : rapport 1:1, et elle ne s'écrase pas dans la colonne flex
       du marqueur (fiche timeline). */
    width: 7px;
    height: 7px;
    aspect-ratio: 1;
    flex: 0 0 auto;
    /* Centrée sur la ligne de sa date : le repère est la hauteur de ligne réelle du libellé
       (1lh), pas une marge devinée — si la typo change, la pastille reste alignée (fiche
       timeline). La première déclaration n'est qu'un repli. */
    margin-top: var(--sp-space-2);
    margin-top: calc((1lh - 7px) / 2);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-text-caption);
}

/* Variant `agenda` : l'en-tête n'est plus une ligne de texte mais une LIGNE DE CONTRÔLES — un
   badge de date, et parfois l'action du jour. Sa hauteur est donc celle du bouton, imposée à
   tous les nœuds : sans quoi le nœud qui porte l'action serait plus haut que les autres et
   ses voisins auraient chacun un centre différent. Un repère unique, d'où découlent la
   pastille et le rail (fiche timeline). */
.timeline--agenda{
    --timeline-agenda-header: 36px;
}

.timeline--agenda .timeline-header{
    min-height: var(--timeline-agenda-header);
}

.timeline--agenda .timeline-dot{
    margin-top: calc((var(--timeline-agenda-header) - 7px) / 2);
}

.timeline--agenda .timeline-item:not(:last-child) .timeline-marker::before{
    top: calc(var(--timeline-agenda-header) / 2);
}

/* Un nœud ne porte qu'un évènement : le filet de ListItem n'y sépare rien, et c'est le rail
   qui jalonne (fiche list-item, variant `agenda`). */
.timeline--agenda .list-item{
    border-bottom: 0;
}

/* Nœud courant : pastille orange de marque, seule couleur d'accent de la frise */
.timeline-item--current .timeline-dot{
    background-color: var(--sp-color-accent-selected);
}

/* Nœud de fin : glyphe NU de 12×12 — ni cercle, ni liseré, ni fond. Ce n'est pas une pastille
   de journée, il ne doit pas s'y ressembler (fiche timeline). Son centre tombe au même endroit
   que celui des pastilles, pour que le rail le rejoigne exactement. */
.timeline-end-glyph{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 12px;
    height: 12px;
    flex: 0 0 auto;
    color: var(--sp-color-border-cream);
    margin-top: var(--sp-space-1);
    margin-top: calc((1lh - 12px) / 2);
}

/* En-tête de nœud : la date à gauche, à droite le compteur (variant default) ou l'action que
   la date appelle (variant agenda). Alignement centré, parce que ce qui se fait face n'est pas
   du texte mais des objets — un badge, un bouton (fiche timeline). */
.timeline-header{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-2);
}

.timeline-label{
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* Le compteur est un Badge ton neutral : taille, couleur, rayon et padding viennent de lui
   (fiche timeline). Ne restent ici que sa tenue dans la ligne d'en-tête. */
.timeline-count{
    flex-shrink: 0;
    white-space: nowrap;
}

.timeline-count-total{
    color: var(--sp-color-text-muted);
    font-weight: var(--sp-font-weight-body);
}

.timeline-meta{
    margin-top: 2px;
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.timeline-gallery{
    margin-top: var(--sp-space-3);
}

.timeline-end-text{
    color: var(--sp-color-text-muted);
}

/* Journée en attente : le pointillé dit l'attente ; une surface pleine dirait un contenu
   (fiche timeline, état `awaiting`). */
.timeline-awaiting{
    margin-top: var(--sp-space-3);
    padding: var(--sp-space-6) var(--sp-space-4);
    border: 1px dashed var(--sp-color-border-cream);
    border-radius: var(--sp-radius-lg);
    text-align: center;
    color: var(--sp-color-text-muted);
}

.badge-warning{
    background-color: var(--warning-light);
    color: var(--warning);
}

/* Ton danger du DS : aplat rouge, texte blanc (alerte forte). Le rouge est celui des surfaces
   sombres (`feedback.danger.on-dark`, #F5494A) et non `feedback.danger` : ce badge se pose sur
   une PHOTOGRAPHIE, où le rouge foncé passe pour du brun et cesse d'alerter (fiche badge). La
   dérogation AA qui va avec est assumée et tracée dans la fiche. */
.badge-danger{
    background-color: var(--sp-color-feedback-danger-on-dark);
    color: var(--sp-color-text-inverse);
}


/* Traitement chip du DS : fond blanc stable, ombre douce, libellé ink */
.badge-filter{
    --bs-badge-padding-y: 10px;
    --bs-badge-padding-x: 12px;
    --bs-badge-border-radius: var(--sp-radius-md);
    color: var(--sp-color-text-default);
    background-color: var(--sp-color-bg-surface);
    box-shadow: var(--sp-shadow-sm);
    border: none;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
}


.mobile-filter-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 8px 12px 8px 8px;
    border: 0.75px solid var(--sp-color-text-default);
    border-radius: 5px;
    font-size: 15px;
    font-weight: 500;
    background: var(--sp-color-bg-surface);
    color: var(--bs-body-color);
    text-decoration: none;
}

.mobile-filter-pill:hover {
    background: var(--sp-color-bg-grey);
    color: var(--bs-body-color);
}

.btn-sport-filter {
    gap: 4px;
    padding: 8px 12px;
    border-radius: 5px;
    font-size: 13px;
    height: 32px;
    display: inline-flex;
    align-items: center;
}

/* Panneau de filtres : Sheet sur mobile, Dropdown sur desktop (fiches sheet.md et
   dropdown.md). Même markup des deux côtés — même contenu, même fonction, autre surface.

   Base = mobile : le panneau monte du bas, sur un voile. */
.filter-anchor {
    position: relative;
}

.filter-veil {
    position: fixed;
    inset: 0;
    background-color: var(--sp-color-overlay-veil);
    z-index: 1045;
    animation: filter-veil-in var(--sp-duration-base) var(--sp-easing-standard);
}

/* Le panneau se déploie depuis le bord bas : le geste dit d'où il vient et par où il
   repartira (fiche sheet, « Mouvement »). Le voile se fond en même temps. */
@keyframes filter-veil-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes filter-sheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

.filter-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1046;
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-4);
    /* jamais tout l'écran : la bande qui dépasse dit qu'on peut refermer (fiche sheet) */
    max-height: 85vh;
    padding: var(--sp-space-6);
    background-color: var(--sp-color-bg-surface);
    /* le panneau sort du bord bas : rien à arrondir de ce côté */
    border-radius: var(--sp-radius-lg) var(--sp-radius-lg) 0 0;
    box-shadow: var(--sp-shadow-pop);
    animation: filter-sheet-up var(--sp-duration-base) var(--sp-easing-standard);
}

/* L'information est dans la position finale, pas dans le trajet (fiche sheet) */
@media (prefers-reduced-motion: reduce) {
    .filter-panel,
    .filter-veil {
        animation: none;
    }
}

/* La poignée annonce le glissement ; elle ne porte jamais l'unique moyen de fermer */
.filter-panel-grabber {
    align-self: center;
    width: 36px;
    height: 4px;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-border-cream);
}

.filter-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-3);
}

.filter-panel-title {
    margin: 0;
    font-size: var(--sp-font-size-h3);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

.filter-panel-close {
    color: var(--sp-color-text-default);
}

.filter-panel-form {
    display: flex;
    flex-direction: column;
    min-height: 0;   /* sans quoi le corps refuse de défiler */
    gap: var(--sp-space-4);
}

/* Le corps défile, le pied non : une validation qu'il faut aller chercher en bas de trente
   lignes ne se trouve pas (fiches dropdown et sheet) */
.filter-panel-body {
    overflow-y: auto;
    min-height: 0;
}

.filter-panel-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-3);
    padding-top: var(--sp-space-4);
    border-top: 1px solid var(--sp-color-border-default);
}

.filter-panel-foot .btn {
    flex: 1;
}

@media (min-width: 992px) {
    /* Desktop : le panneau pend sous la barre d'onglets et ne bloque rien — pas de voile,
       la page reste lisible et cliquable (fiche dropdown).

       Il se mesure sur la barre, non sur sa chip : c'est ce qui aligne son bord droit sur
       celui du contenu. Ancré à la chip, il aurait fallu deviner la distance qui l'en
       sépare — le CSS ne la connaît pas, et une largeur fixe aurait débordé du conteneur
       sur les écrans étroits, jusqu'à pousser la page. */
    .tab-bar-block {
        position: relative;
    }

    .filter-anchor {
        position: static;
    }

    /* Page évènement : la barre de chips n'est pas une .tab-bar-block. Elle sert elle-même
       de contexte au dropdown (sans quoi l'absolu se cale sur le viewport — coin bas droit).
       Ses chips sont à gauche : on cale donc le panneau à gauche, non à droite comme la home. */
    .photo-filter-bar {
        position: relative;
    }

    .photo-filter-bar .filter-panel {
        left: 0;
        right: auto;
    }

    .filter-panel {
        position: absolute;
        top: calc(100% + var(--sp-space-2));
        left: auto;
        right: 0;
        bottom: auto;
        /* Largeur de son contenu, avec le plancher du variant « filters » : réglé sur son
           seul contenu il n'occupait que 39% de la place et serrait ses colonnes ; poussé
           à 100%, il était trop large (fiche dropdown, « Largeur »).
           Le plancher ne vaut que pour un panneau de filtres — c'est .filter-panel qui le
           porte, pas une règle de dropdown. */
        width: max-content;
        min-width: 600px;
        /* borné par la barre, donc par le conteneur : jamais de débord, jamais de scroll */
        max-width: 100%;
        max-height: none;
        border-radius: var(--sp-radius-lg);
        /* le Dropdown ne monte de nulle part : il est déjà chez lui, sous sa barre */
        animation: none;
        transform: none;
    }

    .filter-panel-foot .btn {
        flex: 0 0 auto;
    }

    /* Déclencheur isolé dans une carte : le panneau s'aligne sur SON bord gauche, et sort donc
       visiblement de la chip qui l'a ouvert. L'ancre redevient le repère (position: relative),
       au lieu de la barre pleine largeur des filtres de la home — là-bas c'est l'alignement à
       droite qui garde le panneau dans le conteneur (fiche dropdown). */
    .filter-anchor--start {
        position: relative;
    }

    .filter-anchor--start .filter-panel {
        left: 0;
        right: auto;
        /* Le plancher de 600px vaut pour une liste d'options parcourue du regard, pas pour un
           calendrier, qui a sa propre largeur (fiche dropdown, « Largeur »). */
        min-width: 0;
        max-width: min(600px, calc(100vw - var(--sp-space-8)));
    }

    /* Pendant de --start pour une chip posée à DROITE de sa ligne (ex. « Trier par » de la page
       évènement, calé à droite sur desktop) : le panneau s'aligne sur SON bord droit et s'ouvre
       vers la gauche, sous sa chip. C'est l'autre façon de tenir l'invariant de la fiche dropdown
       — ne jamais franchir un bord du conteneur : une chip à droite dont le panneau s'alignerait
       à gauche déborderait par la droite et ferait défiler la page de travers. La largeur est
       bornée au viewport (pas en % de l'ancre, qui ne fait que la taille de la chip). */
    .filter-anchor--end {
        position: relative;
    }

    .filter-anchor--end .filter-panel {
        right: 0;
        left: auto;
        min-width: 0;
        max-width: min(600px, calc(100vw - var(--sp-space-8)));
    }
}

/* Liste de filtres (DESIGNAPP, « Liste de filtres ») : glyphe, libellé, contrôle à droite.
   Une colonne sur mobile, deux sur desktop ; l'en-tête garde la ligne entière. */
.filter-list {
    display: grid;
    grid-template-columns: 1fr;
    /* Gouttière franche entre les colonnes (DESIGNAPP, « Liste de filtres ») : une colonne
       finit par un contrôle, la suivante commence par un glyphe — ils se confondent s'ils
       se frôlent. Deux listes autonomes, pas quatre colonnes. */
    column-gap: var(--sp-space-8);
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;   /* un fieldset ne rétrécit pas sans ça */
}

@media (min-width: 992px) {
    .filter-list {
        grid-template-columns: 1fr 1fr;
    }

    /* Quelques options ne font pas deux listes : elles gardent leur colonne. Les deux
       classes portent le même élément — la spécificité tranche, pas l'ordre. */
    .filter-list.filter-list-single {
        grid-template-columns: 1fr;
    }

    .filter-list .filter-row-head {
        grid-column: 1 / -1;
    }
}

.filter-row {
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    min-height: var(--sp-control-icon-button);   /* cible tactile (fiche checkbox) */
    padding: var(--sp-space-2) 0;
}

/* Le filet coiffe la ligne au lieu de la souligner : la dernière n'en porte donc aucun,
   quel que soit le nombre de sports, et sans avoir à désigner « la dernière » — que la
   grille à deux colonnes rendrait indésignable.

   Il ne sépare que les options entre elles : l'en-tête coiffe le groupe, il n'en est pas
   membre et ne s'en détache pas d'un trait. D'où le :not() — une option qui suit
   l'en-tête n'ouvre pas de filet. */
.filter-row:not(.filter-row-head) + .filter-row {
    border-top: 1px solid var(--sp-color-border-default);
}

/* Sur deux colonnes, l'en-tête tient la ligne entière : la première rangée d'options est
   faite des 2e et 3e lignes. La 3e ne suit pas l'en-tête dans le DOM — la règle ci-dessus
   lui pose donc un filet — mais elle est bien sur la première rangée. On le lui retire.

   nth-of-type et non nth-child : le <legend> du fieldset est le premier enfant, invisible
   mais bien là, et il décalait tout le comptage d'un rang — le filet sautait une ligne
   plus haut que voulu. nth-of-type ne compte que les div.

   Et seulement à deux colonnes : sur une seule, la 3e ligne n'a rien d'une première
   rangée, elle a besoin de son filet comme les autres.

   Placé APRÈS la règle qu'il corrige : à spécificité égale, c'est l'ordre qui tranche. */
@media (min-width: 992px) {
    .filter-list:not(.filter-list-single) > .filter-row:nth-of-type(3) {
        border-top: 0;
    }
}

.filter-row-icon {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--sp-color-text-brown);
}

.filter-row-icon svg {
    display: block;
}

/* L'en-tête commande le groupe : son glyphe prend l'accent, son libellé le poids de titre */
.filter-row-icon-accent {
    color: var(--sp-color-accent-selected);
}

.filter-row-head .filter-row-label {
    font-weight: var(--sp-font-weight-title);
}

.filter-row-label {
    flex: 1;
    min-width: 0;
    margin: 0;
    cursor: pointer;
    color: var(--sp-color-text-default);
}

/* Le critère d'une option porte le poids de titre, son sens reste au poids de corps
   (DESIGNAPP, « Liste de filtres »). Le <strong> du navigateur vaudrait 700, que le DS
   réserve ailleurs : ici c'est font.weight.title. */
.filter-row-label strong {
    font-weight: var(--sp-font-weight-title);
}

/* Saisie libre à côté des options (DESIGNAPP, « Une saisie libre à côté des options ») :
   sous la liste, avec son libellé — ce n'est pas une option de plus, c'est l'autre façon
   de répondre. Le filet la sépare des options, comme elles se séparent entre elles. */
.filter-range {
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-2);
    padding-top: var(--sp-space-4);
    margin-top: var(--sp-space-2);
    border-top: 1px solid var(--sp-color-border-default);
}

.filter-range-label {
    color: var(--sp-color-text-default);
    font-weight: var(--sp-font-weight-title);
}

/* Le contrôle tient le bout de ligne : même colonne pour tous, quel que soit le libellé */
.filter-row-check,
.filter-row-switch {
    flex-shrink: 0;
    margin: 0;
}

/* .form-switch réserve une gouttière à gauche pour son libellé ; ici le libellé est
   ailleurs sur la ligne. Les deux classes portent le même élément : on l'emporte par la
   spécificité, non par l'ordre du fichier. */
.form-switch.filter-row-switch {
    padding-left: 0;
    min-height: 0;
}

.mobile-filter-pill svg {
    color: var(--sp-color-text-default);
    margin-left: 4px;
    flex-shrink: 0;
}

/* Breadcrumb */

.breadcrumb-nav .crumb {
    --bs-nav-link-padding-y: 8px;
    --bs-nav-link-padding-x: 8px;
    --bs-nav-link-font-weight: 600;
    color: var(--bs-heading-color);
    font-weight: 600;
    text-decoration: none;
    border-bottom: none;
    display: inline-block;
    padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x) 8px var(--bs-nav-link-padding-x);
}

.breadcrumb-nav .separator {
    color: var(--sp-color-text-muted);
    font-size: 18px;
    margin: 0 6px;
}

.breadcrumb-nav.nav-underline .nav-link,
.nav.nav-underline .crumb{
    padding: 18px var(--bs-nav-link-padding-x) 18px var(--bs-nav-link-padding-x);
}

@media (min-width: 992px) {

    .breadcrumb-nav.nav-underline .nav-link,
    .nav.nav-underline .crumb{
        padding: 16px var(--bs-nav-link-padding-x) 16px var(--bs-nav-link-padding-x);
    }

}

/* Buttons */

/* Boutons DS : rayon lg (16px) — plus arrondi que les chips, c'est ce qui désigne
   l'élément qu'on presse ; anneau de focus orange, press ~0,97, disabled à 40 % */
.btn {
    --bs-btn-padding-x: 12px;
    --bs-btn-padding-y: 8px;
    --bs-btn-font-size: var(--sp-font-size-body);
    --bs-btn-font-weight: var(--sp-font-weight-body);
    --bs-btn-border-radius: var(--sp-radius-lg);
    --bs-btn-disabled-opacity: 0.4;
    --bs-btn-focus-box-shadow: var(--sp-shadow-ring);
    letter-spacing: var(--sp-font-tracking-tight);
    transition: transform var(--sp-duration-fast) var(--sp-easing-standard),
                background-color var(--sp-duration-fast) var(--sp-easing-standard),
                border-color var(--sp-duration-fast) var(--sp-easing-standard),
                color var(--sp-duration-fast) var(--sp-easing-standard);
}

.btn:not(:disabled):active {
    transform: var(--sp-gesture-press);
}

.btn:focus-visible {
    box-shadow: var(--sp-shadow-ring);
}

@media (prefers-reduced-motion: reduce) {
    .btn {
        transition: none;
    }
    .btn:not(:disabled):active {
        transform: none;
    }
}

.btn-sm {
    --bs-btn-font-size: 12px;
    /* Coin resserré à radius.sm (8px) : à 32px de haut, le radius.lg de base arrondirait presque
       en pilule — le coin suit la taille (fiche button). */
    --bs-btn-border-radius: var(--sp-radius-sm);
}

/* Un Button réduit à son glyphe reste un Button (fiche button) : il garde la hauteur et le rayon
   du bouton nommé qu'il raccourcit, et devient carré. Ce n'est PAS .btn-circle, le disque 44px de
   l'IconButton, qui est un contrôle de chrome et dépasserait ses voisines dans la barre.

   Aucune taille propre n'est fixée ici : la hauteur vient de la rangée (align-self), et la largeur
   se REFAIT depuis les mêmes variables que celles qui donnent sa hauteur à un bouton libellé de
   même taille — gouttière verticale, filet, hauteur de ligne. `aspect-ratio` ne suffirait pas :
   dans une rangée flex, la largeur se résout sur le contenu (le glyphe, 16px) avant que la hauteur
   étirée ne soit connue, et le carré ne se ferait jamais. */
.btn-icon-only{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    /* Le raccourci ne se déforme pas au profit des libellés voisins. */
    flex: 0 0 auto;
    width: calc(var(--bs-btn-font-size) * var(--bs-btn-line-height)
                + 2 * var(--bs-btn-padding-y)
                + 2 * var(--bs-btn-border-width));
    padding-left: 0;
    padding-right: 0;
}

.btn.p-0{
    padding: 0!important;
}

.btn-primary{
    --bs-btn-color: var(--sp-color-bg-surface);
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-color: var(--sp-color-bg-surface);
    --bs-btn-hover-bg: rgba(var(--bs-primary-rgb), .7);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-disabled-bg: var(--bs-primary);
    --bs-btn-disabled-border-color: var(--bs-primary);
    --bs-btn-active-bg: var(--bs-primary);
    --bs-btn-active-border-color: var(--bs-primary);
}


.btn-outline-primary {
    --bs-btn-color: var(--bs-heading-color);
    --bs-btn-border-color: var(--bs-secondary);
    --bs-btn-hover-color: var(--sp-color-bg-surface);
    --bs-btn-hover-bg: var(--bs-primary);
    --bs-btn-hover-border-color: var(--bs-primary);
    --bs-btn-active-color: var(--sp-color-bg-surface);
    --bs-btn-active-bg: var(--bs-primary);
    --bs-btn-active-border-color: var(--bs-primary);
    --bs-btn-disabled-color: var(--bs-secondary);
    --bs-btn-disabled-border-color: var(--bs-secondary);
}

.btn-outline-primary {
    --bs-btn-font-weight: 600;
}

/* .btn-outline-secondary a été retiré : contour gris froid et texte gris, il ne correspondait
   à aucun variant de la fiche button, et le DS écarte le gris froid des filets. Les actions de
   faible emphase qu'il portait sont passées en .btn-neutral (crème) ; la navigation « voir
   tout » qu'il portait aussi est redevenue un lien (.sec-header-link). */

.btn-light {
    --bs-btn-color: var(--bs-primary);
    --bs-btn-bg: var(--light);
    --bs-btn-border-color: var(--sp-color-border-default);
    --bs-btn-hover-color: var(--bs-primary);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: var(--bs-primary);
    --bs-btn-focus-shadow-rgb: 211, 212, 213;
    --bs-btn-active-color: var(--bs-primary);
    --bs-btn-active-bg: transparent;
    --bs-btn-active-border-color: var(--bs-primary);
    --bs-btn-disabled-color: var(--bs-primary);
    --bs-btn-disabled-bg: var(--sp-color-bg-grey);
    --bs-btn-disabled-border-color: var(--sp-color-bg-grey);
    font-variation-settings: 'wght' 400;
    transition: font-variation-settings 0.2s;
    position: relative;
}

.btn-light:active,
.btn-check+.btn-light:active,
.btn-check:checked+.btn-light {
    font-variation-settings: 'wght' 600;
}

.btn-check+.btn:hover {
    color: var(--bs-btn-hover-color);
    background-color: var(--bs-btn-hover-bg);
    border-color: var(--bs-btn-hover-border-color);
}

/* Chip DS (designsystem/spicto/app/components/chip.md) : les toggles de filtre
   (btn-check + label) prennent le traitement chip. Fond blanc et ombre douce
   stables entre états ; à la sélection, libellé et icône passent à l'orange. */
.btn-check + .btn-light {
    --bs-btn-bg: var(--sp-color-bg-surface);
    --bs-btn-color: var(--sp-color-text-default);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-bg: var(--sp-color-bg-surface);
    --bs-btn-hover-color: var(--sp-color-text-default);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-bg: var(--sp-color-bg-surface);
    --bs-btn-active-color: var(--sp-color-accent-selected);
    --bs-btn-active-border-color: transparent;
    --bs-btn-border-radius: var(--sp-radius-md);
    box-shadow: var(--sp-shadow-sm);
}

.btn-check:checked + .btn-light {
    color: var(--sp-color-accent-selected);
    background-color: var(--sp-color-bg-surface);
    border-color: transparent;
}

.btn-check:focus-visible + .btn-light {
    box-shadow: var(--sp-shadow-ring), var(--sp-shadow-sm);
}

/* Chips d'accès aux filtres (rangée sous les onglets, mockup home) :
   même traitement chip, libellé orange quand la catégorie est active */
.chip-filter {
    height: 36px;   /* fiche chip */
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
    --bs-btn-bg: var(--sp-color-bg-surface);
    --bs-btn-color: var(--sp-color-text-default);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-bg: var(--sp-color-bg-surface);
    --bs-btn-hover-color: var(--sp-color-text-default);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-bg: var(--sp-color-bg-surface);
    --bs-btn-active-color: var(--sp-color-accent-selected);
    --bs-btn-active-border-color: transparent;
    --bs-btn-border-radius: var(--sp-radius-md);
    box-shadow: var(--sp-shadow-sm);
}

.chip-filter.active {
    color: var(--sp-color-accent-selected);
}

/* Sortie de la rangée de chips (DESIGNAPP, « Effacer les filtres »). Pas de fond ni d'ombre :
   la surface est ce qui fait une chip, et une sortie n'est pas un filtre de plus. Elle se cale
   sur la hauteur des chips pour tenir la ligne, et garde la typo d'un lien de section.

   L'orange est une EXCEPTION tracée au DESIGNAPP : seul élément sans surface d'une rangée qui
   n'est faite que de surfaces, en ink il serait le plus discret de la barre au moment où il est
   le plus utile. text.link, donc — c'est un lien, et le tronc donne l'orange aux liens. Ne pas
   étendre : le « Voir tout » d'un en-tête de section reste ink.

   Typo des chips, pas celle d'un lien de section : size.label (14px) et weight.body (500). Elle
   prolonge la rangée, elle ne la surplombe pas — en 600 elle pèserait plus que les filtres
   qu'elle efface, et ajouterait une troisième graisse à la barre. */
.filter-clear {
    display: inline-flex;
    align-items: center;
    height: 36px;
    padding: 0 var(--sp-space-2);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-link);
    text-decoration: none;
}

.filter-clear:hover {
    color: var(--sp-color-text-link);
    text-decoration: underline;
}

/* Le défilement horizontal impose overflow-x, qui rogne aussi le débord vertical :
   sans réserve, l'ombre des chips est coupée net. shadow.sm déborde de 4px sous la
   chip et de 2px au-dessus ; la réserve est symétrique pour ne pas décaler les chips
   quand la barre les centre (DESIGNAPP, « Défilement horizontal »). */
/* Les chips défilent au doigt sur mobile (DESIGNAPP, « Défilement horizontal »). Sur
   desktop elles tiennent la ligne : l'overflow y est rendu visible, sans quoi il rognerait
   le Dropdown ancré sous l'une d'elles — overflow-x rogne aussi le débord vertical, et
   c'est ce qui rendait le panneau invisible. */
.filter-chips {
    scrollbar-width: none;
    padding: var(--sp-space-1) 0;
    overflow-x: auto;
}

@media (min-width: 992px) {
    .filter-chips {
        overflow: visible;
    }
}

.filter-chips::-webkit-scrollbar {
    display: none;
}

/* Btn-lg */

.btn-group-lg>.btn, .btn-lg {
    --bs-btn-padding-y: .85rem;
    --bs-btn-padding-x: 1.25rem;
    --bs-btn-font-size: 1rem;
    /* radius.lg comme la taille md : la fiche button ne resserre le coin QUE sur `sm`. Ce bloc
       portait radius.md, ce qui faisait mentir la fiche sur toutes les grandes tailles. */
    --bs-btn-border-radius: var(--sp-radius-lg);
}

/*
 * Variantes DS (cf. designsystem/spicto/app/components/button.md) :
 * - .btn-marketing = « primary » du DS (orange) : l'intention la plus forte d'un
 *   parcours (inscription, achat), UN SEUL par écran.
 * - .btn-primary Bootstrap (ink) = « secondary » du DS : action standard.
 * - .btn-neutral = « neutral » du DS (crème) : action de faible emphase.
 */
/* Exception de rayon (fiche button) : les deux boutons d'acquisition d'une photo — acheter
   et télécharger — passent en pill. Ce sont les gestes qui font l'app ; ce rayon les distingue
   de tous les autres boutons, et d'eux seuls. */
.btn-pill {
    --bs-btn-border-radius: var(--sp-radius-full);
}

/* LE COIN DU BOUTON SUIT LA ZONE : radius.lg (16px) dehors, radius.md (12px) dans l'espace
   connecté. Pendant exact de la carte de section qui perd son ombre en passant la connexion —
   dehors l'interface accueille, dedans elle outille, et des boutons qui se multiplient en tête
   de section et en pied de formulaire paraissent plus gros qu'ils ne pèsent au rayon d'accueil
   (fiche button). Comme pour la carte, la frontière est la ZONE, portée par le gabarit, jamais
   la session : l'écran de connexion n'a pas de classe d'espace pro, ses boutons restent à 16px.

   Les trois exclusions sont écrites, jamais confiées à l'ordre de déclaration :
   - .btn-sm      garde radius.sm (8px) : le coin suit la TAILLE avant de suivre la zone ;
   - .btn-pill    garde son pill : acheter / télécharger le portent dans les deux zones ;
   - .btn-gesture les deux gestes fondateurs du tableau de bord (voir ci-dessous).
   .btn-circle n'a pas besoin d'exclusion : il pose son rayon en propriété directe, pas par la
   variable, et reste donc un disque.

   Rayon ET gouttière horizontale changent ENSEMBLE : c'est une seule décision à deux effets.
   Un coin resserré sur une gouttière d'accueil donne un bouton trapu ; une gouttière élargie
   sur un coin ouvert le fait paraître mou. Le vertical ne bouge pas — c'est la hauteur du
   bouton, elle appartient à la taille, pas à la zone. D'où un SEUL bloc pour les deux : les
   séparer, c'est se donner l'occasion de n'en déplacer qu'un. */
.club-layout .btn:not(.btn-sm):not(.btn-pill):not(.btn-gesture),
.photographer-layout .btn:not(.btn-sm):not(.btn-pill):not(.btn-gesture){
    --bs-btn-border-radius: var(--sp-radius-md);
    --bs-btn-padding-x: var(--sp-space-4);
    --bs-btn-padding-y: var(--sp-space-2);
}

/* « Créer un évènement » et « Déposer des photos » sur les TABLEAUX DE BORD : les deux gestes
   qui font vivre la plateforme gardent le coin d'accueil (16px), qui les rattache à ce dont ils
   sont la porte et les détache des commandes autour. La classe ne se pose QUE sur un tableau de
   bord (fiche button) : les mêmes gestes ailleurs dans l'espace connecté sont des boutons de
   travail à 12px. Elle ne s'étend à aucun troisième bouton — ce n'est pas « le CTA de l'écran »
   qui la reçoit, ce sont ces deux gestes-là. La valeur est réaffirmée plutôt que laissée au
   repli sur .btn : l'exclusion ci-dessus suffirait, mais elle rendrait l'intention illisible. */
.btn-gesture{
    --bs-btn-border-radius: var(--sp-radius-lg);
}

/* Variant « outline » de la fiche button : la même intention que primary, en retenue. Il garde
   l'orange pour dire qu'on reste dans la même famille d'action, mais rend le plein au CTA —
   il se définit par rapport au bouton qu'il suit. */
.btn-outline {
    --bs-btn-color: var(--sp-color-action-marketing);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--sp-color-action-marketing);
    --bs-btn-hover-color: var(--sp-color-action-primary-text);
    --bs-btn-hover-bg: var(--sp-color-action-marketing);
    --bs-btn-hover-border-color: var(--sp-color-action-marketing);
    --bs-btn-active-color: var(--sp-color-action-primary-text);
    --bs-btn-active-bg: var(--sp-color-action-marketing);
    --bs-btn-active-border-color: var(--sp-color-action-marketing);
    --bs-btn-disabled-color: var(--sp-color-action-marketing);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--sp-color-action-marketing);
}

/* Variant « tertiary » de la fiche button : lien / action fantôme, sans fond, texte ink. Sans
   surface, il ne pèse sur rien.

   LE SOULIGNEMENT APPARTIENT AU RENVOI, PAS AU VARIANT (fiche button). Un tertiary qui MÈNE
   AILLEURS — la sortie d'un parcours (« Annuler le dépôt ») — n'a que ce trait pour se donner
   comme cliquable. Un tertiary qui AGIT SUR PLACE et porte un glyphe — la proposition qui ouvre
   un champ facultatif — ne se souligne pas : le glyphe le désigne déjà comme un contrôle, et le
   trait lui promettrait une destination qu'il n'a pas. La frontière est celle de <a> et de
   <button>, d'où le sélecteur ci-dessous plutôt qu'un modificateur de plus. */
.btn-tertiary {
    --bs-btn-color: var(--sp-color-text-default);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--sp-color-text-default);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--sp-color-text-default);
    --bs-btn-active-bg: transparent;
    --bs-btn-active-border-color: transparent;
}

/* Le renvoi, lui, se souligne : 0,1,1 l'emporte sur le `text-decoration: none` de .btn (0,1,0). */
a.btn-tertiary {
    text-decoration: underline;
}

/* Stepper (fiche stepper) : compteur puis noms d'étapes séparés de flèches. Il informe, il
   n'agit pas — d'où sa place au pied de la carte, sous les actions, et son absence de surface. */
.stepper{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-space-3);
}

.stepper-count{
    flex-shrink: 0;
}

.stepper-list{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
}

.stepper-step--done{ color: var(--sp-color-text-default); }

/* Une étape faite y ramène : revenir sur un choix ne doit pas coûter plus cher que l'avoir
   fait (fiche stepper). Souligné, parce que dans une frise de mots c'est la seule chose qui
   distingue ce qui se clique de ce qui se lit. */
.stepper-link{
    color: inherit;
    text-decoration: underline;
}

.stepper-link:hover{ color: var(--sp-color-text-default); }
/* L'orange marque l'étape courante : c'est un état, pas une action (fiche stepper) */
.stepper-step--current{ color: var(--sp-color-accent-selected); }
.stepper-step--upcoming{ color: var(--sp-color-text-muted); }

.stepper-arrow{
    display: inline-flex;
    color: var(--sp-color-text-muted);
}

/* Étape franchie, repliée : nom, ce qui y a été décidé, et « Modifier ».

   bg.default, le crème du fond de page — le seul endroit du système où il revient DANS une
   carte, et c'est le sens qu'on lui donne : une étape close retombe au niveau de la page. Le
   bg.sunken, plus soutenu, dirait « regarde ici » à ce qui est justement derrière soi
   (DESIGNAPP, « Une étape faite se replie en récapitulatif »). */
/* Le récapitulatif est une COLONNE : sa tête (identité + « Modifier ») et, quand l'étape a
   décidé plusieurs choses de natures différentes, ses qualificatifs sous un filet (DESIGNAPP).
   C'est `.step-recap-head` qui porte désormais la rangée — le parcours de dépôt l'emploie aussi,
   pour que les deux récapitulatifs gardent la même anatomie. */
.step-recap{
    display: flex;
    flex-direction: column;
    padding: var(--sp-space-4);
    margin-bottom: var(--sp-space-4);
    background-color: var(--sp-color-bg-default);
    /* EN DESSOUS de la carte qui le porte, comme tout ce qui s'emboîte (DESIGN, « Ce qui est
       posé dans une carte a le coin plus fermé qu'elle »). Ce n'est pas son rayon qui
       l'empêche de se lire comme une étape encore ouverte, c'est sa surface : bg.default, le
       crème du fond de page — une étape close retombe au niveau de la page. */
    border-radius: var(--sp-radius-xl);
}

/* La tête : l'identité de l'étape à gauche, « Modifier » à droite. */
/* Le « Modifier » s'aligne en HAUT, jamais au centre (DESIGNAPP) : sa hauteur ne dépend pas de ce
   qu'il y a à côté, alors que la méta tient sur une ligne ou sur trois selon l'étape. Centré, il
   descendrait à mesure que le récapitulatif s'allonge, et une colonne de récapitulatifs verrait
   ses boutons flotter chacun à une hauteur différente. */
.step-recap-head{
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-space-3);
}

/* Le nom de l'étape et ce qu'il résume respirent : collés, la méta se lit comme la deuxième ligne
   du titre et le bloc perd le seul indice qui dit lequel des deux porte. L'écart se pose ici, en
   gouttière de flex, et non en marge de titre : les deux `<p>` portent `mb-0`, qu'une utilitaire
   Bootstrap défend en `!important`. */
.step-recap-lines{
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-2);
}

/* Le filet sépare l'IDENTITÉ de ses qualificatifs. Il prend la gouttière du récapitulatif sur
   toute sa largeur : c'est un partage du bloc, pas un ornement sous une ligne. */
.step-recap-rule{
    margin: var(--sp-space-3) 0;
    border: 0;
    border-top: var(--sp-border-width-default) solid var(--sp-color-border-cream);
    opacity: 1;
}

/* Les qualificatifs sont de la MÉTA : size.meta (13px), text.muted, libellé et valeur d'un SEUL
   ton (DESIGNAPP). Les partager en deux nuances reviendrait à hiérarchiser à l'intérieur de ce
   qui est déjà secondaire, et à faire croire qu'une moitié de la ligne prime alors qu'on la lit
   d'un bloc. C'est le contraste avec la ligne d'identité, en text.default, qui dit lequel des
   deux blocs porte.

   text.muted vaut #767676 — et non le #757575 d'une maquette : à une unité par canal, l'écart
   est imperceptible, et lui donner un token fonderait le « troisième gris improvisé » que le
   tronc désigne comme un bug. */
.step-recap-detail,
.step-recap-detail-label{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Le bouton y passe au blanc, sinon il se noie : son crème et celui de la surface sont trop
   proches pour se distinguer. Même règle que dans une carte crème (fiche card). */
.step-recap .btn-neutral{
    --bs-btn-bg: var(--sp-color-bg-surface);
    --bs-btn-border-color: var(--sp-color-bg-surface);
    --bs-btn-hover-bg: var(--sp-color-bg-surface);
    --bs-btn-hover-border-color: var(--sp-color-bg-surface);
    --bs-btn-active-bg: var(--sp-color-bg-surface);
    --bs-btn-active-border-color: var(--sp-color-bg-surface);
}

/* Le nom de l'étape est un titre de bloc DANS une carte : size.h4, et non la primitive 16 qu'il
   lisait — un composant ne lit jamais une primitive (DESIGN). Même valeur, le rôle en plus. */
.step-recap-title{
    font-size: var(--sp-font-size-h4);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* Ce que l'étape a décidé : à `size.body`, un cran au-dessus des qualificatifs qui vivent sous le
   filet (`size.meta`). C'est la TAILLE qui range ces deux secondaires entre eux, la couleur ne
   sert qu'une fois — à les distinguer du nom de l'étape, seul en text.default (DESIGNAPP). */
.step-recap-meta{
    font-size: var(--sp-font-size-body);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-muted);
}

/* Variant « dark » de la fiche card : il isole le chiffre qui décide de la visite. UNE seule
   par vue — multiplié, il perd exactement ce qu'on lui demande : trancher. */
.card-dark {
    background-color: var(--sp-color-bg-dark);
    color: var(--sp-color-text-inverse);
    border-color: transparent;
}

/* Un lien ne suit PAS la couleur du texte courant : il tire la sienne de --bs-link-color, qui
   vaut l'ink — soit exactement bg.dark. Sans cette règle il disparaît dans son fond (1:1) sans
   qu'aucune autre n'ait l'air violée. Le soulignement reste : privé de couleur propre, il n'a
   plus que lui pour se distinguer de la phrase (fiche card, variant dark). Les boutons, eux,
   portent leurs propres --bs-btn-* et ne sont pas concernés. */
/* Même piège que le lien ci-dessous, un cran plus haut : un titre ne tire pas sa couleur du
   texte qui l'entoure mais de --bs-heading-color (l'ink). Entré dans la carte — l'en-tête de
   section est dedans, désormais —, il y resterait ink sur ink (DESIGNAPP, « L'en-tête entre
   dans la carte »). */
.card-dark :is(h1, h2, h3, h4, h5, h6){
    color: var(--sp-color-text-inverse);
}

.card-dark a:not(.btn) {
    color: var(--sp-color-text-inverse);
    text-decoration: underline;
}

/* Carte de SECTION : blanc, radius.lg, gouttière space.6, SANS filet — le même rendu partout,
   À UNE VARIABLE PRÈS, l'ombre. L'ombre ou le filet, jamais les deux (fiche card). C'est le
   pendant de `.section-surface`, qui applique le même rendu à un bloc qui n'est pas une .card
   Bootstrap ; les deux disent la même surface. */
.card-section{
    box-shadow: var(--sp-shadow-sm);
}

/* HORS espace connecté, la carte porte l'ombre : elle est minoritaire au milieu de
   photographies et de fond crème, et c'est l'ombre qui la pose comme une surface. DANS
   l'espace connecté, l'écran EST fait de cartes : une ombre sous chacune ne distingue plus
   rien et ajoute un halo par bloc — elles sont donc plates, exprès (fiche card).

   La frontière est la ZONE, jamais la session : c'est le GABARIT qui la porte, si bien qu'une
   page publique garde son ombre quand un utilisateur connecté la consulte. Prendre la session
   pour critère rendrait le même écran de deux façons selon le visiteur, et masquerait le
   défaut en développement, où l'on est presque toujours connecté. */
.club-layout .card-section,
.photographer-layout .card-section{
    box-shadow: var(--sp-shadow-none);
}

/* TABLEAU DE BORD, et lui seul : les cartes du fond ouvrent leur coin à 2xl (24px). C'est la
   suite de la règle ci-dessus — privée d'ombre, la carte n'a plus que son coin pour se donner
   comme surface. Ailleurs dans l'espace connecté, `lg` suffit : les cartes y sont peu
   nombreuses et empilées en une colonne. Le tableau de bord est le seul à les juxtaposer en
   mosaïque sur trois colonnes, où, plates et serrées, elles retomberaient en cases de tableau.

   Le sélecteur porte sur .card, PAS sur .card-section : la cagnotte est une `dark` et le profil
   une `photo`, ni l'une ni l'autre ne porte la classe de section, et toutes deux sont pourtant
   des cartes du fond. Les laisser à 16px les distinguerait par leur DÉCOUPE, alors que ces
   variants se distinguent par leur FOND et par lui seul (fiche card, « un même niveau de
   surface, un même rayon »).

   La règle d'emboîtement est LEVÉE ici : une carte de premier niveau est TOUJOURS à 24px,
   qu'elle héberge une tuile ou non — pas de 36px sur cet écran. La mosaïque prime, et
   l'emboîtement se dit sans le coin, par le filet de la tuile posée sur du blanc.

   Seul :not(.card-nested) demeure, et il est écrit plutôt que confié à l'ordre de déclaration
   (la fragilité qui avait rendu .card-section inerte) : les tuiles ne sont pas de premier
   niveau et gardent leurs 20px. */
.dashboard-view .card:not(.card-nested){
    --bs-card-border-radius: var(--sp-radius-2xl);
}

/* La carte `photo` ne porte pas sa gouttière sur son card-body : c'est le SCRIM qui la porte
   (fiche card), et il est à space.4 partout ailleurs. Sur le tableau de bord, où toute carte
   du fond est à 24px, il suit — sinon le contenu de la carte de profil commencerait 8px plus
   près du bord que celui de ses voisines, et la mosaïque perdrait son alignement. C'est le
   PORTEUR qui est propre au composant, pas la valeur. */
.dashboard-view .card-photo-scrim{
    padding: var(--sp-space-6);
}

/* Bulle de message adressée à l'utilisateur (fiche message). Elle prend la largeur de son
   texte, jamais celle de l'écran : pleine largeur, elle se lirait comme un bandeau système —
   quelque chose que la plateforme impose —, alors qu'ici l'interface s'adresse à quelqu'un.
   Même ombre que les chips de filtre : c'est la surface flottante de l'app. */
.message{
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-space-4);
    width: fit-content;
    max-width: 100%;
    padding: var(--sp-space-4);
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-surface);
    color: var(--sp-color-text-default);
    box-shadow: var(--sp-shadow-sm);
}

/* L'emoji précède le titre et reste décoratif : il donne un ton, jamais une information
   (fiche message, ICONS.md). L'espace le sépare du mot sans dépendre d'une espace typographique
   dans le gabarit. */
.message-title{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

.message-title > span{
    margin-right: var(--sp-space-2);
}

/* La croix s'aligne sur la LIGNE DU TITRE, pas sur le centre de la bulle : elle ferme le
   message, elle ne l'accompagne pas (fiche message). Sa cible tactile fait 44px, soit deux
   fois la hauteur de cette ligne : on la remonte donc de la moitié de l'écart, pour que son
   glyphe retombe au centre du titre sans que le bouton perde sa cible. L'écart se calcule
   depuis la typo du titre — si elle change, l'alignement suit. */
.message-dismiss{
    flex-shrink: 0;
    margin-top: calc(
        (var(--sp-font-size-16) * var(--sp-font-leading-normal) - var(--sp-control-icon-button)) / 2
    );
}

/* Une section qui EST un chiffre le pose sur la ligne de son titre, aligné à droite : le titre
   nomme, le chiffre répond. En display sous le titre, il ouvrirait une deuxième zone de lecture
   pour dire ce que la section annonce déjà (DESIGNAPP, « Le chiffre d'une section »). */
.section-header-figure{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-3);
}

/* Inter, pas Anuphan : deux fontes qui se font face sur une même ligne se lisent comme un
   accident de rendu, pas comme une hiérarchie — c'est la graisse qui distingue (tronc). */
.section-figure{
    font-family: var(--sp-font-family-base), sans-serif;
    font-size: var(--sp-font-size-figure);
    font-weight: var(--sp-font-weight-bold);
    white-space: nowrap;
}

/* L'en-tête entré dans la carte garde l'écart qu'il avait sur le fond (space.3, comme
   .sec-header) : entrer dans la surface ne change pas la respiration d'un titre. */
.card-section > h3,
.card-dark > h3{
    margin-bottom: var(--sp-space-3);
}

/* Carte IMBRIQUÉE dans une carte : filet et pas d'ombre — il n'y a plus de fond crème sous
   elle pour porter une ombre, et deux ombres empilées ne font que salir la carte hôte.
   Rayon xl (20px) pendant que son hôte monte à 3xl (36px) : l'emboîtement de deux CARTES
   ÉCARTE les deux coins au lieu de les rapprocher — à rayons voisins, deux surfaces
   concentriques se confondent en un défaut d'alignement (fiche card, tronc « Shape »). */
.card-nested{
    --bs-card-border-radius: var(--sp-radius-xl);
    /* La gouttière s'ajoute à celle de l'hôte : space.6 dedans comme dehors ferait 48px de
       vide le long du bord partagé (fiche card). */
    --bs-card-spacer-x: var(--sp-space-3);
    --bs-card-spacer-y: var(--sp-space-3);
    /* SEUL FILET GRIS DU SYSTÈME (tronc, « Color » ; fiche card). Il n'y a plus de crème sous
       cette carte à prolonger : border.default y tombe à 1,21:1 sur du blanc, un trait qu'on
       devine sans le voir. border.on-white remonte à 1,53:1. Ce n'est pas border.cream (1,58:1,
       même poids) parce que celui-là est le filet d'un CONTRÔLE, et une carte n'en est pas un. */
    border: 1px solid var(--sp-color-border-on-white);
    box-shadow: var(--sp-shadow-none);
}

/* Sur l'étape de VALIDATION, tous les blocs de la carte prennent le crème le plus clair, sans
   filet (DESIGNAPP) : cette étape ne demande plus rien, elle donne à relire avant l'orange, et
   tout y est au même rang — c'est la surface du récapitulatif d'étape, étendue à ses voisins. Le
   filet tombe parce que la teinte délimite déjà ; un filet par-dessus ferait double emploi, comme
   l'ombre et le filet ne cohabitent jamais (fiche card). Les étapes de SAISIE, elles, gardent
   leurs groupes d'options à filet sur le blanc : là on décide, ici on vérifie.

   Deux conséquences suivent le changement de sol, et elles sont ci-dessous : le filet interne
   redevient chaud, et le texte secondaire quitte le gris, qui est calculé pour le blanc. */
.card-nested-cream{
    background-color: var(--sp-color-bg-default);
    border-color: transparent;
}

.card-nested-cream .card-rule{
    border-top-color: var(--sp-color-border-cream);
}

/* Le texte secondaire d'un panneau : muted sur du blanc… */
.panel-note{
    color: var(--sp-color-text-muted);
}

/* …et brun dès que le sol devient crème — c'est le SOL qui décide, le gris étant calculé pour le
   blanc (DESIGNAPP, « Bordures et séparateurs »). */
.card-nested-cream .panel-note{
    color: var(--sp-color-text-brown);
}

/* Filet INTERNE d'une carte blanche : entre deux champs, entre une consigne et la case qui la
   suit, entre ce qui bouge et ce qui est décidé. Dans une carte qui empile des blocs sans surface
   propre, seule la ligne dit où l'un finit et où l'autre commence — un simple écart vertical se
   confond avec celui qui sépare un libellé de son champ. Il respire autant des deux côtés : il
   appartient à l'intervalle, pas au bloc qui le précède.

   Il court sur du BLANC, donc il est gris (border.on-white), comme le contour de la carte qui
   le porte : c'est le SOL qui décide de la teinte d'un filet, pas sa nature (DESIGNAPP). En
   crème il resterait chaud — cf. .step-recap-rule, qui vit dans un récapitulatif crème. */
.card-rule{
    margin: var(--sp-space-4) 0;
    border: 0;
    border-top: var(--sp-border-width-default) solid var(--sp-color-border-on-white);
    opacity: 1;   /* Bootstrap pose .5 sur hr : le filet est déjà un ton clair, l'atténuer l'efface */
}

/* Le filet qui sépare le contenu d'une carte de son PIED (le Stepper, sous les actions). Même
   teinte et même raison que .card-rule — blanc dessous —, mais sa gouttière est dissociée :
   il prend plus d'air au-dessus, où il quitte le formulaire, que dessous, où le pied le suit. */
.card-foot-rule{
    margin: var(--sp-space-6) 0 var(--sp-space-4);
    border: 0;
    border-top: var(--sp-border-width-default) solid var(--sp-color-border-on-white);
    opacity: 1;
}

/* Bloc de tête d'un Select, DÉCORATIF au même titre que le chevron (fiche select) : le glyphe du
   sport parent puis son nom, que le champ fermé perdrait sinon — il ne montre jamais l'optgroup.
   Il ne porte rien que la liste ne dise déjà. Le select natif reste le seul contrôle — d'où
   `pointer-events: none`, qui laisse passer le clic jusqu'à lui. */
.select-lead-icon{
    display: none;
    align-items: center;
    gap: var(--sp-space-2);
    color: var(--sp-color-text-default);
    /* Le parent ne se replie jamais : il occupe une colonne, et un retour à la ligne ferait
       grandir le champ au lieu de pousser le texte du select. */
    white-space: nowrap;
    /* Le select natif reste le seul contrôle : le clic traverse le bloc jusqu'à lui. */
    pointer-events: none;
}

.select-lead-glyph{
    display: inline-flex;
    align-items: center;
}

/* SANS valeur choisie, rien à loger : le select se rend seul, exactement comme les autres champs
   de la carte, et le placeholder démarre là où démarrent les leurs.

   AVEC une valeur, le bloc entre EN FLUX et c'est la mise en page qui lui réserve la place : le
   champ devient une rangée [bloc de tête][select]. L'habillage — fond, filet, rayon, hauteur,
   anneau de focus — passe alors au conteneur, et le select se rend transparent à l'intérieur.

   C'est volontairement du CSS et non une mesure JavaScript. Une gouttière calculée depuis la
   largeur du bloc doit tenir compte de son décalage, se recalculer au chargement de la fonte,
   au redimensionnement et à chaque changement de valeur — trois occasions de coller le texte
   contre le « › », et c'est exactement ce qui est arrivé. En flux, la place ne se calcule pas :
   elle est occupée. */
.select-lead.has-lead{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    height: calc(52px + calc(var(--bs-border-width) * 2));
    padding-left: var(--sp-space-3);
    background-color: var(--sp-color-bg-surface);
    border: var(--bs-border-width) solid var(--sp-color-border-cream);
    border-radius: var(--field-radius);
}

.select-lead.has-lead .select-lead-icon{
    display: inline-flex;
}

/* Le champ garde son chevron et son comportement natifs ; il rend seulement son habillage au
   conteneur, qui le porte désormais pour eux deux. */
.select-lead.has-lead .form-select{
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    min-height: 0;
    padding-left: 0;
    background-color: transparent;
    border: 0;
    box-shadow: none;
}

/* L'anneau de focus suit le contrôle jusqu'à son nouveau porteur : c'est le conteneur qui se
   dessine, mais c'est bien le select natif qui a le focus. */
.select-lead.has-lead:focus-within{
    border-color: var(--sp-color-border-focus);
    box-shadow: var(--sp-shadow-ring);
}

/* Carte imbriquée qui porte des CHAMPS : gouttière dissociée, 16px en vertical et 24px en
   horizontal. Le space.3 ci-dessus a été calibré pour une TUILE, dont la gouttière mange le
   contenu ; un champ occupe toute la largeur qu'on lui laisse et n'a pas besoin de place mais
   d'un retrait qui le décolle du filet — à 12px, un contrôle de 40px paraît collé à son cadre.
   Les 48px le long du bord partagé sont assumés ici : rien ne se lit dans cette marge. Le
   vertical reste à 16px parce qu'il sert l'EMPILEMENT, pas le retrait (fiche card).

   Classe déclarée, jamais déduite d'un `form` ancêtre : le dépôt de photos est un écran de
   saisie SANS <form> (upload piloté en JS, les champs ne se sérialisent pas), et il doit
   respirer comme les autres étapes de son parcours. */
.card-nested-fields{
    --bs-card-spacer-y: var(--sp-space-4);
    --bs-card-spacer-x: var(--sp-space-6);
}

/* La carte dont le contenu principal est un CADRE VISIBLE — la zone de dépôt et son pointillé —
   reprend une gouttière UNIFORME (fiche card). La dissociation des axes est écrite pour un
   empilement de champs, où le vertical se rabat parce qu'il y a de la hauteur à économiser ; ici
   il n'y a rien à empiler, et la gouttière n'est plus un retrait avant des champs mais la marge
   autour d'un rectangle. Inégale, elle ne se lit pas comme de la densité mais comme un défaut de
   centrage. */
.card-nested-framed{
    --bs-card-spacer-y: var(--sp-space-6);
    --bs-card-spacer-x: var(--sp-space-6);
}

/* La carte qui HÉBERGE une carte imbriquée : c'est l'unique exception à « un même niveau de
   surface, un même rayon » (fiche card). Le sélecteur suit la RÈGLE plutôt qu'une classe à
   poser à la main : une carte devient hôte parce qu'elle contient une carte imbriquée, et
   c'est exactement ce que :has() sait dire. Descendant et non enfant direct — le formulaire
   rendu par form_start s'intercale souvent entre les deux. */
.card:has(.card-nested){
    --bs-card-border-radius: var(--sp-radius-3xl);
}

/* Quand la carte mène quelque part, c'est ELLE le lien : cible sur toute la surface, et
   aucun renvoi posé sous son contenu. Elle ne prend ni la couleur ni le soulignement d'un
   lien — c'est sa surface qui signale, pas sa typographie (fiche card). */
a.card-nested{
    color: inherit;
    text-decoration: none;
    transition: box-shadow var(--sp-duration-fast) var(--sp-easing-standard);
}

/* Élévation seule, comme toute carte (tronc DESIGN.md, Motion). */
a.card-nested:hover{
    box-shadow: var(--sp-shadow-md);
}

a.card-nested:focus-visible{
    outline: none;
    box-shadow: var(--sp-shadow-ring);
}

@media (prefers-reduced-motion: reduce) {
    a.card-nested{
        transition: none;
    }
}

/* Variant « sunken » de la fiche card : crème creusé, pour l'encart d'une autre nature que
   les blocs qui l'entourent. Le bouton y passe au blanc — le crème du bouton `neutral` est à
   un cran de ce fond et s'y noierait : deux crèmes voisins font une tache, pas un bouton. */
.card-sunken{
    background-color: var(--sp-color-bg-sunken);
}

.card-sunken .btn-neutral{
    --bs-btn-bg: var(--sp-color-bg-surface);
    --bs-btn-border-color: var(--sp-color-bg-surface);
    --bs-btn-hover-bg: var(--sp-color-bg-surface);
    --bs-btn-hover-border-color: var(--sp-color-bg-surface);
    --bs-btn-active-bg: var(--sp-color-bg-surface);
    --bs-btn-active-border-color: var(--sp-color-bg-surface);
}

/* Variant « photo » de la fiche card : le fond est une photographie, le texte repose sur le
   scrim de l'identité — bas-vers-haut, overlay.scrim vers transparent. C'est une surface
   d'identité : elle porte du texte en haut (avatar, nom, lieu) comme en bas (les chiffres),
   donc le scrim couvre TOUTE la surface et non une bande basse. Les actions restent en haut,
   en verre. Son fond n'est jamais vide (fiche card). */
.card-photo{
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 248px;
    overflow: hidden;
    border-color: transparent;
    color: var(--sp-color-text-inverse);
}

.card-photo-media{
    position: absolute;
    inset: 0;
    z-index: 0;
}

.card-photo-media img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Le scrim occupe toute la carte : il porte l'identité en tête et les chiffres au pied, la
   rampe traversant la surface entière. Ce que ça coûte est tracé dans la fiche : en haut de
   rampe le scrim ne porte plus rien, et c'est l'IMAGE qui tient la lisibilité — admissible
   tant que ce fond est l'image de marque, sombre par construction et la même pour tous. Le
   jour où l'utilisateur pourra fournir sa couverture, la règle se rouvre (fiche card). */
.card-photo-scrim{
    position: relative;
    z-index: 1;
    display: flex;
    flex: 1;
    flex-direction: column;
    padding: var(--sp-space-4);
    background: linear-gradient(
        to top,
        var(--sp-color-overlay-scrim) 0,
        transparent 100%
    );
}

/* Le bloc d'identité doit pouvoir rétrécir sous la largeur de son contenu, sinon un long
   pseudo déborde du scrim au lieu de se tronquer. */
.card-photo-identity{
    min-width: 0;
}

/* Les chiffres sont à l'autre bout de la surface : ils disent ce qu'on a fait, pas qui l'on
   est (fiche card, variant photo). */
.card-photo-stats{
    margin-top: auto;
}

/* Blanc explicite sur le titre : le scrim donne sa couleur par héritage au texte courant,
   mais un <hN> tire la sienne de --bs-heading-color (l'ink) et casse l'héritage — le nom
   restait sombre sur la photographie. Même famille de piège que le lien sur card-dark
   (fiche card, variant photo : tout le texte de la carte est en text.inverse). */
.card-photo :is(h1, h2, h3, h4, h5, h6){
    color: var(--sp-color-text-inverse);
}

/* Le portrait en tête : avatar.lg quand la carte est en colonne (fiche avatar). La taille
   vient du token, jamais d'une règle d'écran.

   Cette règle existe pour le balisage du tableau de bord, qui pose un <img class="avatar">
   brut au lieu du composant Avatar et n'a donc pas de classe de taille. La disposition
   `banner` en est EXCLUE : son portrait passe le sien en `xl` par le composant, et sans ce
   `:not()` cette règle — plus spécifique — le ramènerait à 64px. */
.card-photo:not(.card-photo--banner) .avatar{
    width: var(--sp-avatar-lg);
    height: var(--sp-avatar-lg);
}

/* Le lieu, sous le nom : de la méta, en casse phrase — le DS n'écrit pas en majuscules. */
.card-photo-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-inverse);
}

.card-photo-actions{
    position: absolute;
    top: var(--sp-space-4);
    right: var(--sp-space-4);
    z-index: 2;
}

/* Deux actions ne tiennent pas dans le coin haut droit : empilées sur le visage, elles font
   un bloc de commandes et la carte cesse d'être un portrait. Elles descendent au pied, où le
   scrim est le plus opaque — donc l'endroit le plus sûr pour un libellé —, et s'y séparent
   par nature : à gauche celle qui MODIFIE la carte, à droite celle qui en SORT (fiche card).
   Sans glyphe : il venait après le mot pour ancrer un bouton à droite ; sur celui de gauche,
   il pointerait vers le vide. */
.card-photo-footer{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-3);
    margin-top: auto;
}

/* Disposition « banner » du variant photo (fiche card) : l'identité se couche. La bascule
   tient à la LARGEUR DE LA COLONNE, pas à l'écran — cette carte occupe la colonne large des
   paramètres, là où le tableau de bord la range dans une colonne étroite parmi trois. Sous ce
   seuil elle redevient « column », et c'est le défaut : rien à défaire ici. */
@media (min-width: 992px){
    .card-photo--banner .card-photo-identity{
        display: flex;
        align-items: center;
        gap: var(--sp-space-4);
    }

    /* La colonne de texte doit pouvoir rétrécir sous son contenu, sinon un long pseudo pousse
       le bloc hors du scrim au lieu de se tronquer. */
    .card-photo--banner .card-photo-identity-text{
        min-width: 0;
    }

    /* Sans pied, l'identité se centre (fiche card). La carte a un plancher de 248px ; à deux
       actions il est tenu aux deux bouts, à une seule le pied reste vide et l'identité calée
       en tête laisse sous elle une bande de photographie nue. C'est le cas de l'organisateur,
       qui n'a pas de profil public d'où sortir. */
    .card-photo--banner .card-photo-scrim:not(:has(.card-photo-footer)){
        justify-content: center;
    }

    /* Les chiffres rejoignent l'identité : le pied revient aux actions, et un chiffre isolé
       sous deux boutons ne se lirait plus comme un trait de portrait (fiche card). */
    .card-photo--banner .card-photo-stats{
        margin-top: var(--sp-space-2);
    }

    /* Couchée, la carte change d'échelle : le nom devient ce qu'il est sur cette surface — le
       SUJET, pas un titre —, d'où font.size.identity (48px), la seule taille au-dessus du hero
       (DESIGN.md). Il monte avec le portrait, jamais sans lui : à avatar.lg sous un nom de
       48px, le visage serait plus petit que le mot qui le nomme (fiche avatar).

       Ce sélecteur est plus lourd qu'il n'en a l'air, et pas par confort : `.photographer-layout
       h1:not(.page-title)`, plus haut, dimensionne TOUS les h1 des espaces pro — y compris ceux
       qu'un composant possède — et il gagnait à égalité. C'est exactement ce que la fiche card
       interdit (« aucune règle de page ne redéfinit ce qui est propre au composant ») ; tant
       qu'elle vit, un titre de composant doit la dépasser au lieu de dépendre de l'ordre du
       fichier. */
    .card-photo--banner .card-photo-identity-text h1.card-photo-name{
        font-size: var(--sp-font-size-identity);
        line-height: var(--sp-font-leading-tight);
    }
}

/* Sur le scrim, le muted du libellé tomberait sous l'AA : valeur ET libellé passent en
   text.inverse, et c'est la taille qui hiérarchise — comme la méta d'EventCardHighlighted.
   Le bloc s'y resserre aussi : sur cette carte, un chiffre est un trait de portrait, pas le
   chiffre de l'écran — valeur en size.body, libellé en size.meta (DESIGNAPP, « Bloc de
   statistique »). */
.card-photo .stat-block-value,
.card-photo .stat-block-label{
    color: var(--sp-color-text-inverse);
}

.card-photo .stat-block-value{
    font-size: var(--sp-font-size-body);
}

.card-photo .stat-block-label{
    font-size: var(--sp-font-size-meta);
}

/* Variant « free » de la fiche button : le téléchargement OFFERT. Le vert y dit la gratuité,
   l'orange dirait l'achat — et les deux ne se disputent jamais la même zone (une photo est
   vendue ou offerte). Hors de ce rôle, le vert reste feedback.success. */
.btn-free {
    --bs-btn-color: var(--sp-color-action-primary-text);
    --bs-btn-bg: var(--sp-color-action-free);
    --bs-btn-border-color: var(--sp-color-action-free);
    --bs-btn-hover-color: var(--sp-color-action-primary-text);
    --bs-btn-hover-bg: var(--sp-color-action-free);
    --bs-btn-hover-border-color: var(--sp-color-action-free);
    --bs-btn-active-color: var(--sp-color-action-primary-text);
    --bs-btn-active-bg: var(--sp-color-action-free);
    --bs-btn-active-border-color: var(--sp-color-action-free);
    --bs-btn-disabled-color: var(--sp-color-action-primary-text);
    --bs-btn-disabled-bg: var(--sp-color-action-free);
    --bs-btn-disabled-border-color: var(--sp-color-action-free);
}

.btn-marketing {
    --bs-btn-color: var(--sp-color-action-primary-text);
    --bs-btn-bg: var(--sp-color-action-marketing);
    --bs-btn-border-color: var(--sp-color-action-marketing);
    --bs-btn-hover-color: var(--sp-color-action-primary-text);
    --bs-btn-hover-bg: var(--sp-color-orange-400); /* primitive dédiée au survol de l'accent */
    --bs-btn-hover-border-color: var(--sp-color-orange-400);
    --bs-btn-active-color: var(--sp-color-action-primary-text);
    --bs-btn-active-bg: var(--sp-color-action-marketing);
    --bs-btn-active-border-color: var(--sp-color-action-marketing);
    --bs-btn-disabled-color: var(--sp-color-action-primary-text);
    --bs-btn-disabled-bg: var(--sp-color-action-marketing);
    --bs-btn-disabled-border-color: var(--sp-color-action-marketing);
}

/* Destructif : rouge DS (feedback.danger, réservé aux actions destructives) */
.btn-danger {
    --bs-btn-bg: var(--sp-color-feedback-danger);
    --bs-btn-border-color: var(--sp-color-feedback-danger);
    --bs-btn-hover-bg: color-mix(in srgb, var(--sp-color-feedback-danger) 85%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--sp-color-feedback-danger) 85%, black);
    --bs-btn-active-bg: var(--sp-color-feedback-danger);
    --bs-btn-active-border-color: var(--sp-color-feedback-danger);
    --bs-btn-disabled-bg: var(--sp-color-feedback-danger);
    --bs-btn-disabled-border-color: var(--sp-color-feedback-danger);
}

.btn-outline-danger {
    --bs-btn-color: var(--sp-color-feedback-danger);
    --bs-btn-border-color: var(--sp-color-feedback-danger);
    --bs-btn-hover-bg: var(--sp-color-feedback-danger);
    --bs-btn-hover-border-color: var(--sp-color-feedback-danger);
    --bs-btn-active-bg: var(--sp-color-feedback-danger);
    --bs-btn-active-border-color: var(--sp-color-feedback-danger);
}

.btn-neutral {
    --bs-btn-color: var(--sp-color-text-default);
    --bs-btn-bg: var(--sp-color-bg-cream);
    --bs-btn-border-color: var(--sp-color-bg-cream);
    --bs-btn-hover-color: var(--sp-color-text-default);
    --bs-btn-hover-bg: var(--sp-color-bg-sunken);
    --bs-btn-hover-border-color: var(--sp-color-bg-sunken);
    --bs-btn-active-color: var(--sp-color-text-default);
    --bs-btn-active-bg: var(--sp-color-bg-sunken);
    --bs-btn-active-border-color: var(--sp-color-bg-sunken);
    --bs-btn-disabled-color: var(--sp-color-text-default);
    --bs-btn-disabled-bg: var(--sp-color-bg-cream);
    --bs-btn-disabled-border-color: var(--sp-color-bg-cream);
}

/* IconButton variant glass (fiche icon-button) : sur fond sombre ou photo, reprend le
   fond du badge glass — verre sombre translucide, flou, liseré clair atténué (les mêmes
   glass.overlay.* que les compteurs sur photo). Icône blanche : le crème trancherait sur
   l'image, l'ink y disparaîtrait. */
.btn-glass {
    --bs-btn-color: var(--sp-color-text-inverse);
    --bs-btn-hover-color: var(--sp-color-text-inverse);
    --bs-btn-active-color: var(--sp-color-text-inverse);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: transparent;
    background: var(--sp-glass-button-bg);
    backdrop-filter: blur(var(--sp-glass-blur));
    -webkit-backdrop-filter: blur(var(--sp-glass-blur));
    border: 1px solid var(--sp-glass-button-border);
}

/* Plusieurs actions glass voisines se groupent dans UNE pilule de verre (fiche icon-button) :
   c'est le conteneur qui porte le verre, les items ne portent rien, aucune séparation entre eux —
   le groupe se lit comme un seul objet. */
.glass-group {
    display: inline-flex;
    align-items: center;
    border-radius: var(--sp-radius-full);
    background: var(--sp-glass-button-bg);
    backdrop-filter: blur(var(--sp-glass-blur));
    -webkit-backdrop-filter: blur(var(--sp-glass-blur));
    border: 1px solid var(--sp-glass-button-border);
    color: var(--sp-color-text-inverse);
}

/* « Les boutons ne portent RIEN : ni fond, ni bordure » (fiche icon-button). Ce n'est pas
   qu'une consigne de balisage : un <button> arrive avec le fond et le filet du navigateur, et
   sans ce remise à zéro il se dessine en carré clair au milieu du verre — le groupe cesse de
   se lire comme un seul objet. Un <a> ne le montrait pas, d'où le défaut invisible tant que
   les deux actions étaient des liens. */
.glass-group-item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Un disque reste un disque : il ne rétrécit ni ne s'étire dans la rangée. */
    flex: 0 0 auto;
    width: var(--sp-control-icon-button);
    height: var(--sp-control-icon-button);
    padding: 0;
    background: none;
    border: 0;
    border-radius: var(--sp-radius-full);
    color: var(--sp-color-text-inverse);
}

.glass-group-item:hover{
    color: var(--sp-color-text-inverse);
}

.glass-group-item:focus-visible{
    outline: none;
    box-shadow: var(--sp-shadow-ring);
}

/* Action de page NOMMÉE, en pilule glass — desktop, header de la page évènement (fiche
   event-card-highlighted, « les actions de page se nomment dans le header »). Le verre vient de
   .btn-glass, le rayon de .rounded-pill ; ici on ne règle que l'anatomie icône + mot : ils se
   suivent sur une ligne, sans retour, à l'échelle d'un label. La pilule porte son propre fond
   sombre — le mot repose sur lui, pas sur la photo —, donc pas de scrim haut à ajouter. */
.event-hero-action{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-2);
    padding: var(--sp-space-2) var(--sp-space-4);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
    white-space: nowrap;
}

/* « Organisé par » accompagne le nom sans le porter : blanc atténué sur le verre. */
.event-hero-action-muted{
    opacity: .7;
}

/* Menu déroulant de l'organisateur : surface claire d'app (héritée de .dropdown-menu), largeur
   plafonnée pour que la description reste lisible sans s'étaler. */
.event-organizer-menu{
    max-width: 320px;
}

/* Aperçu de la page publique : la carte EventCardHighlighted au repos, sans photo (le sombre est
   hérité de .event-highlighted). Décorative, elle ne répond pas au survol comme une vraie tuile
   cliquable — elle annulait le déplacement, qui n'existe plus nulle part ; il ne restait que
   l'ombre à neutraliser. */
.event-highlighted-preview {
    cursor: default;
}

.event-highlighted-preview:hover {
    box-shadow: none;
}

/* Titre de la colonne d'accompagnement : size.h3.dense (15px), la graisse de titre venant déjà de
   la règle des h1..h5. La colonne portait deux tailles pour un même rang (16px ici, 20px aux
   astuces) — à 15px elle parle d'une voix et ne dispute plus au formulaire l'entrée de l'écran
   (DESIGNAPP). Une classe, pas `.h4` : celle-ci lit une primitive, et le rang ne se déduit pas
   d'un nom d'utilitaire Bootstrap. */
.aside-title{
    font-size: var(--sp-font-size-h3-dense);
}

/* Le titre des astuces, lui, NE descend PAS avec la colonne : l'encart voyage d'un écran à
   l'autre (création, dépôt), et un composant qui change de taille selon la colonne qui l'accueille
   a deux rendus pour une même chose. Il garde donc le `size.h3` de `.sec-header-title`, sans
   règle propre — la constance du composant l'emporte sur l'uniformité de la colonne (fiche tips,
   DESIGNAPP). C'est la même raison qui laisse la carte « Validation » à `size.h3` dans cette
   colonne, par un autre chemin : elle, c'est une section de formulaire déplacée à droite. */

/* APERÇU DE LA PAGE PUBLIQUE, sous la carte : ce que la page dira de l'évènement (DESIGNAPP).

   IL NE CHANGE JAMAIS DE TEINTE : c'est la PAGE qui bouge sous lui (cf. --bs-body-bg plus haut).
   Sur mobile sa surface coïncide avec le fond de page, et il s'y lit comme du contenu qui coule
   dans le fil du formulaire ; sur desktop la page recule d'un cran et le même bloc devient, sans
   s'être redéclaré, la surface claire d'une colonne parallèle (DESIGNAPP). */
.preview-page{
    background-color: var(--sp-color-bg-default);
    margin-bottom: var(--sp-space-4);
}

/* Seules la gouttière et le coin sont conditionnels, et ils suivent la SURFACE, pas la largeur :
   confondu avec la page, le bloc n'a pas de bord dont écarter son contenu — il va pleine largeur.
   Détaché d'elle, il en a un. Même point de rupture que le fond de page, forcément : c'est la
   même décision vue de l'autre côté. */
@media (min-width: 992px){
    .preview-page{
        border-radius: var(--sp-radius-lg);
        padding: var(--sp-space-6);
    }
}

/* Une section masquée ne laisse pas sa gouttière derrière elle. */
.preview-page [hidden]{
    display: none;
}

.preview-organizer{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
}

.preview-organizer-logo{
    flex: 0 0 auto;
    border-radius: var(--sp-radius-full);
    object-fit: cover;
}

.preview-organizer-text{
    display: flex;
    flex-direction: column;
}

/* « Organisé par » accompagne, le nom porte : le contraste des deux suffit à le dire. Sur crème,
   le secondaire est brun, jamais le gris calculé pour le blanc (tronc, Color). */
.preview-organizer-label{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-brown);
}

.preview-organizer-name{
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* Filet sur CRÈME, donc chaud : c'est le sol qui décide (DESIGNAPP, « Bordures et séparateurs »).
   Il ne prend pas border.on-white, qui est le filet du blanc. */
.preview-rule{
    margin: var(--sp-space-4) 0;
    border: 0;
    border-top: var(--sp-border-width-default) solid var(--sp-color-border-cream);
    opacity: 1;
}

.preview-section + .preview-section{
    margin-top: var(--sp-space-5);
}

/* DEUX CLASSES, ET C'EST DÉLIBÉRÉ. Ces titres sont des `h3`, et `.photographer-layout h3` /
   `.club-layout h3` (0,1,1) leur impose encore sa marge de 16px dans les espaces pro : une
   classe seule (0,1,0) ne suffit pas à la ramener à space-3. Ne pas « simplifier » ce sélecteur
   en une seule classe. */
.preview-page .preview-section-title{
    font-size: var(--sp-font-size-h3-dense);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
    margin-bottom: var(--sp-space-3);
}

.preview-pills{
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
}

/* Étiquette de lien : PAS un Chip (blanc, ombré, et qui filtre — fiche chip). Celle-ci ne fait
   rien : elle représente un lien de la page publique. Creux crème sur le panneau crème, aucune
   ombre, aucun état de survol — rien qui invite à la presser (DESIGNAPP). */
.preview-pill{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-2);
    padding: var(--sp-space-2) var(--sp-space-3);
    border-radius: var(--sp-radius-md);
    background-color: var(--sp-color-bg-sunken);
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-default);
}

/* La description se coupe comme sur la page publique. Le nombre de lignes est ce qui décide, pas
   un nombre de caractères : c'est la hauteur du bloc qu'on borne, et le contrôleur mesure ensuite
   si « Voir plus » a lieu d'être.

   C'est le seul bloc de PROSE CONTINUE de la colonne : d'où size.label (14px) et weight.prose
   (400), un cran sous l'UI qui l'entoure sur les deux axes — elle cesse de se lire comme la
   valeur d'un champ recopiée à côté du formulaire (DESIGNAPP). La couleur, elle, ne suit pas :
   text.default, l'ink chaud. On allège le POIDS, jamais le contraste — le text.muted que la
   petite taille appelle par réflexe est exactement ce que le tronc interdit d'en déduire. */
.preview-description{
    display: -webkit-box;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-bottom: var(--sp-space-3);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-prose);
    color: var(--sp-color-text-default);
    white-space: pre-line;   /* la description est saisie en lignes : elles comptent */
}

/* « Voir plus » : la représentation INERTE de l'affordance de la page publique, pas un bouton.
   Aucun curseur de main, aucun état de survol — il n'y a rien à presser dans un aperçu. */
.preview-more{
    display: block;
    text-align: center;
    padding: var(--sp-space-3);
    border-radius: var(--sp-radius-md);
    background-color: var(--sp-color-bg-sunken);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* L'astérisque d'un champ requis : orange (accent.selected), rien d'autre (fiche input). */
.required-mark {
    color: var(--sp-color-accent-selected);
    margin-left: var(--sp-space-1);
}

/* Astuces du jour : navigation compacte du carrousel, dans l'en-tête de section. */
.tips-nav {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-1);
}

.tips-nav-btn {
    color: var(--sp-color-text-default);
}

.tips-nav-count {
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-muted);
    min-width: 2.5em;
    text-align: center;
}

/* Carte des astuces : aucun fond, aucune ombre, mais un filet chaud — elle se fond dans la page
   (crème), en retrait des cartes blanches du formulaire, tout en restant délimitée. */
/* Gouttière dissociée : 16px en vertical, 24px en horizontal (fiche tips). L'horizontal sert le
   retrait du texte contre le filet, le vertical sert l'empilement — à 24px en haut et en bas, une
   carte qui empile quatre blocs courts (en-tête, visuel, conseil, pastilles) prend une hauteur
   qu'aucun d'eux ne réclame. Ce sont les variables de la Card qu'on déplace, pas un `padding`
   posé par-dessus : le `card-body` lit les siennes. */
.tips-card {
    --bs-card-spacer-y: var(--sp-space-4);
    --bs-card-spacer-x: var(--sp-space-6);
    background-color: transparent;
    box-shadow: none;
    border: var(--bs-border-width) solid var(--sp-color-border-cream);
}

/* L'emplacement du visuel : la SEULE surface pleine de la carte (fiche tips). Le sous-titre et le
   texte se lisent à même la carte — un aplat qui courrait sous les trois créerait un second niveau
   de surface là où il n'y a qu'un niveau de lecture. Coin fermé d'un cran sous la carte qui le
   porte (tronc, « ce qui est posé dans une carte a le coin plus fermé qu'elle »). Aucune gouttière
   propre : le visuel, le sous-titre et le texte partagent le bord gauche de la carte. */
.tip-media {
    height: var(--sp-space-16);
    border-radius: var(--sp-radius-md);
    background-color: var(--sp-color-bg-sunken);
    margin-bottom: var(--sp-space-4);
}

/* Le conseil lui-même, ramassé en une phrase : c'est ce qu'on retient si l'on ne lit rien
   d'autre. Titre d'un bloc DANS une carte, donc size.h4 et graisse de titre — pas le bold, qui
   est celui d'un titre de carte. */
.tip-title {
    font-size: var(--sp-font-size-h4);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
    margin-bottom: var(--sp-space-2);
}

/* Ce qui l'explique : on le LIT, donc ni l'ink d'un titre ni le muted d'une méta (tronc,
   « text.advice »). Le muted y tomberait à 3,7:1 sur ce creux, sous l'AA pour du corps. */
.tip-text {
    font-size: var(--sp-font-size-body);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-advice);
}

.tips-dots {
    display: flex;
    justify-content: center;
    gap: var(--sp-space-2);
    margin-top: var(--sp-space-4);
}

.tips-dot {
    width: var(--sp-space-2);
    height: var(--sp-space-2);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-border-cream);
}

.tips-dot.is-active {
    background-color: var(--sp-color-text-muted);
}

/* Btn-square btn-circle */

/* IconButton DS : cible tactile control.icon-button (44px), disque radius.full */
.btn-square,
.btn-circle{
    display: inline-flex;
    justify-content: center;
    align-items: center;
    height: var(--sp-control-icon-button);
    width: var(--sp-control-icon-button);
    /* Un bouton-icône garde son carré/disque — jamais déformé, contenu centré H et V (fiche
       icon-button). La taille est donc fermée des DEUX côtés : `width`/`height` disent la taille,
       `min-*` empêchent qu'un parent la reprenne. `flex: 0 0 auto` ne couvre que la rangée flex ;
       une grille, une cellule ou un conteneur en `stretch` compriment un bouton qui n'a pas de
       plancher, et le disque devient une ellipse — c'est ce qu'on voit sur les chevrons d'un
       carrousel, seuls boutons-icônes que le produit pose au milieu d'un contenu qui bouge. */
    min-width: var(--sp-control-icon-button);
    min-height: var(--sp-control-icon-button);
    flex: 0 0 auto;
    aspect-ratio: 1;
    /* La gouttière s'écrit en PROPRIÉTÉ, pas par la variable Bootstrap : dans l'espace connecté,
       la règle de densité du bouton pose `--bs-btn-padding-x: 16px` sur tout `.btn` non exclu, et
       elle atteignait aussi les disques — 32px de gouttière dans un cercle de 44 ne laissent que
       12px à un glyphe qui en fait 20. Un disque n'a de toute façon aucune gouttière à porter :
       sa taille est fixe et son contenu est centré par le flex. */
    padding: 0;
}

.btn-circle{
    border-radius: var(--sp-radius-full);
}

/* Icône de navigation : icon.size.nav (ICONS.md) */
.icon-nav{
    font-size: 20px;
    line-height: 1;
}

/* Barre haute front : hauteur mobile layout.topbar.h */
.topbar-front{
    min-height: var(--sp-layout-topbar-h);
    align-items: center;
    /* La barre haute — et la recherche qu'elle porte — flotte au-dessus du contenu
       (fiche search-bar). Sans z-index, elle vient avant le contenu dans le DOM et se
       peignait donc DESSOUS : le dropdown de suggestions passait sous les onglets et les
       cartes. Au-dessus du contenu (z-index max ~20), sous l'offcanvas (1045). */
    position: relative;
    z-index: 1000;
}

/* custom-upload-file-btn */

.btn.custom-upload-file-btn,
.btn.custom-upload-file-btn:focus{
    color: var(--sp-color-bg-surface);
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
    height: auto;
    min-height: auto;
    display: inline-block;
    --bs-border-radius-sm: 5px;
}

.btn.custom-upload-file-btn:hover{
    background-color: var(--bs-btn-hover-bg);
    border-color: var(--bs-btn-hover-border-color);
}

/* Profile picture upload */

.profile-picture-upload__preview {
    width: 96px;
    height: 96px;
    min-width: 96px;
    border-radius: 50%;
    background-color: var(--sp-color-border-default);
    background-size: cover;
    background-position: center;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: var(--sp-color-text-muted);
}

.profile-picture-upload__preview.has-image > svg {
    display: none;
}

.profile-picture-upload__btn {
    display: flex;
    align-items: center;
    width: 260px;
    height: 48px;
    gap: 8px;
    background-color: var(--sp-color-text-default);
    color: var(--sp-color-bg-surface);
    border: none;
    border-radius: 4px;
    padding: 12px 24px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 0.2s;
}

.profile-picture-upload__btn:hover {
    background-color: var(--sp-color-text-brown);
}

.profile-picture-upload__btn-icon {
    font-size: 1.3rem;
    font-weight: 300;
    line-height: 1;
}

/* Btn-xs */

@media (max-width: 991px) {

    .btn-xs {
        --bs-btn-padding-x: .4rem;
        font-size: .9rem !important;
    }

    .btn-xs svg {
        display: none;
    }

}

/* Over-btn */

.over-form-check {
    width: auto;
    height: auto;
    top: 1rem;
    right: 1rem;
    left: auto;
    z-index: 2;
    padding-left: 0;
    margin-bottom: 0;
}

.over-btn {
    --bs-btn-bg: rgba(255, 255, 255, 0.6);
    --bs-btn-hover-bg: rgba(255, 255, 255, 0.6);
    border: none;
    z-index: 3;
    border-radius: var(--bs-border-radius-sm);
    right: .75rem;
    top: .75rem;
}

.btn-check:checked + .over-btn{
    background-color: var(--bs-btn-bg);
}

.over-btn-check {
    z-index: 4;
    position: absolute;
    width: 100%;
    height: 100%;
    border: none;
    margin: 0;
    padding: 0;
    opacity: 0;
}

.over-btn-check:hover {
    cursor: pointer;
}

.modal .btn-close,
.offcanvas .btn-close {
    --bs-btn-close-opacity: 1;
    color: var(--bs-btn-close-color);
    background: var(--light) var(--bs-btn-close-bg) center / 1em auto no-repeat;
    height: 35px !important;
    width: 35px !important;
    background-size: 12px;
}

.modal .btn-close:hover,
.offcanvas .btn-close:hover {
    background: var(--sp-color-border-default) var(--bs-btn-close-bg) center / 1em auto no-repeat;
    background-size: 12px;
    opacity: 1;
}

/* Card */

/* La base .card vit EN HAUT de ce fichier, avant ses variants — voir le commentaire qui l'y
   accompagne. Elle était dupliquée ici, après eux, et les neutralisait. */

/* Gouttières plus serrées, pour les surfaces denses */
.card-spacer-md{
    --bs-card-spacer-y: var(--sp-space-3);
    --bs-card-spacer-x: var(--sp-space-3);
}

.card-spacer-lg{
    --bs-card-spacer-y: var(--sp-space-4);
    --bs-card-spacer-x: var(--sp-space-4);
}

.card-title, .card-title a{
    font-weight: 600;
    font-size: 16px;
    line-height: 18px;
    margin-bottom: 0;
    text-decoration: none;
    color: var(--bs-heading-color);
    font-family: var(--heading-font-family), sans-serif;
}

.card-header{
    --bs-card-cap-padding-y: var(--bs-card-spacer-y);
    --bs-card-cap-padding-x: var(--bs-card-spacer-x);
    --bs-card-cap-bg: transparent;
}

/* Onboarding (club et photographe) : wrapper et carte de bienvenue */
.onboarding-container{
    max-width: 760px;
}

.onboarding-card{
    max-width: 480px;
}

/* Pastille d'upload sur l'avatar de profil */
.avatar-upload-badge{
    width: 32px;
    height: 32px;
}

/* Grille de tarification du wizard de dépôt */
/* Ce que le photographe encaissera, DANS sa phrase (DESIGNAPP) : le montant garde la taille de la
   ligne qui le porte. Il était en `size.figure`, et ce rang ne se justifiait plus une fois les
   pastilles de prix posées juste au-dessus, où la décision se prend — cette phrase la confirme.
   Un nombre de 24px au milieu d'une ligne de 15 rompt la ligne et fait crier la confirmation plus
   fort que le contrôle qui la produit. */
.earnings-figure{
    font-size: var(--sp-font-size-body);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-default);
    white-space: nowrap;
}

/* Simulation du barème (DESIGNAPP, « La remise activée montre le barème appliqué ») : des lignes
   posées sur une SURFACE, pas un tableau à filets. Quatre paliers ne sont pas des données à
   comparer colonne par colonne, ce sont quatre offres qu'on parcourt. */
.tier-head{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-3);
    margin-bottom: var(--sp-space-3);
    color: var(--sp-color-text-muted);
}

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

/* Le gris neutre, pas le crème : ces lignes sont dans une carte BLANCHE, où le crème vire au
   beige sale (tronc DESIGN, le raisonnement de sol de bg.control). Coin plus fermé que la carte
   imbriquée qui les porte (xl → md), comme tout ce qui s'emboîte. */
.tier-row{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-3);
    padding: var(--sp-space-4);
    border-radius: var(--sp-radius-md);
    background-color: var(--sp-color-bg-grey);
}

.tier-row-label{
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
}

/* Le seuil et la tranche disent la MÊME borne — l'un pour décider, l'autre pour vérifier : même
   taille et même encre, seule la graisse les départage. Les échelonner ferait croire à une
   hiérarchie qui n'existe pas. */
.tier-row-threshold{
    font-weight: var(--sp-font-weight-title);
}

.tier-row-range{
    font-weight: var(--sp-font-weight-body);
}

.tier-row-figures{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
}

/* Le prix ferme la ligne : le seul chiffre que l'acheteur verra, donc la seule graisse forte.
   Sans surface propre — l'encadrer dans la ligne qui le porte déjà poserait un champ là où rien
   ne se saisit. */
.tier-row-price{
    font-weight: var(--sp-font-weight-bold);
    white-space: nowrap;
}

/* La remise, en pilule violette : la forme COMPACTE du bandeau de remise du panier (.cart-deal),
   même objet et même teinte, vue du côté où on la règle (DESIGNAPP). Ce n'est pas un `Badge` :
   le jeu de tons du composant est fermé et n'a pas de violet. Le taux est en graisse de titre,
   le mot suit — on lit le chiffre, on reconnaît le reste. */
.deal-pill{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-1);
    padding: var(--sp-space-1) var(--sp-space-3);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-deal);
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
    white-space: nowrap;
}

.deal-pill strong{
    font-weight: var(--sp-font-weight-title);
}

/* Les sorties du parcours de dépôt : ink souligné, jamais l'orange (DESIGNAPP). L'accent est
   réservé au geste qui dépose ; le soulignement fait ici tout le travail que la couleur ne fait
   plus, sans lui un mot ink au pied d'une carte ne se distingue pas du texte qui le précède. */
.flow-link{
    color: var(--sp-color-text-default);
    font-weight: var(--sp-font-weight-title);
    text-decoration: underline;
}

.flow-link:hover,
.flow-link:focus-visible{
    color: var(--sp-color-text-default);
}

/* Progress variant `transfer` (fiche progress) : même barre que la jauge du DS — 8px, rayon
   full, piste et remplissage arrondis — mais son remplissage porte l'ISSUE et non l'orange.
   C'est le seul variant qui quitte l'accent : il ne mesure pas un reste, il rapporte un état,
   et trois transferts empilés dont un a échoué doivent se distinguer d'un coup d'œil. */
.upload-progress{
    height: 8px;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-bg-grey);
}

.upload-progress .progress-bar{
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-feedback-info);
    transition: width var(--sp-duration-base) var(--sp-easing-standard);
}

.upload-progress .progress-bar.is-done{ background-color: var(--sp-color-feedback-success-fill); }
.upload-progress .progress-bar.is-failed{ background-color: var(--sp-color-feedback-danger); }

/* Bande d'aperçus de la sélection (fiche dropzone) : cinq vignettes au plus, la sixième
   portant le reste en nombre. Elles se serrent sans se déformer si la carte rétrécit. */
.upload-preview-strip{
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
}

.upload-preview{
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    flex: 0 0 auto;
    border-radius: var(--sp-radius-md);
    overflow: hidden;
    background-color: var(--sp-color-bg-grey);
}

.upload-preview img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Le reste en nombre : un aplat ink, parce qu'il ne montre rien — il compte. */
.upload-preview--more{
    background-color: var(--sp-color-bg-dark);
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

/* Carte d'un transfert : blanche, dans la colonne gauche, sous l'évènement (DESIGNAPP) */
.upload-item{
    background-color: var(--sp-color-bg-surface);
    border-radius: var(--sp-radius-lg);
    padding: var(--sp-space-4);
    margin-bottom: var(--sp-space-3);
}

.upload-item-title{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* En tête de carte : le rang et la cible à gauche, la croix d'abandon à droite. */
.upload-item-head{
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-space-2);
    margin-bottom: var(--sp-space-3);
}

/* Croix d'abandon : IconButton variant ghost (fiche icon-button) — icône seule, sans fond, pour
   une action de liste (« retirer »). La cible 44px vient de .btn-circle ; la couleur est celle du
   ghost (text.default), l'icône la reprend via currentColor. */
.upload-item-abandon{
    color: var(--sp-color-text-default);
}

/* Le pourcentage et le mot d'état encadrent la barre : la couleur ne porte jamais seule
   (fiche progress). Le chiffre à gauche, ce qu'il vaut à droite. */
.upload-item-state{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-3);
    margin-top: var(--sp-space-2);
}

.upload-item-pct{
    font-size: var(--sp-font-size-20);
    font-weight: var(--sp-font-weight-bold);
    color: var(--sp-color-text-default);
}

.upload-item-status{
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

.upload-item-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.price-tier-input{
    max-width: 120px;
}

.img-cover-100{
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: var(--sp-radius-sm);
}

/* Pagination : l'actif reprend l'ink d'action (partout, plus d'override inline) */
.pagination{
    --bs-pagination-active-bg: var(--sp-color-action-primary);
    --bs-pagination-active-border-color: var(--sp-color-action-primary);
}

/* Profil photographe public : avatar héro et vignettes d'activité 80px */
.profile-avatar-xl{
    width: 80px;
    height: 80px;
}

.activity-thumb{
    width: 80px;
    height: 80px;
    overflow: hidden;
    border-radius: var(--sp-radius-md);
}

.activity-thumb img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Vignettes du panier / des récapitulatifs de commande */
.cart-thumb{
    width: 48px;
    height: 48px;
    overflow: hidden;
    border-radius: var(--sp-radius-xs);
    flex-shrink: 0;
}

.cart-thumb img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cart-thumb-lg{
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: var(--sp-radius-sm);
}

.cart-thumb-xl{
    width: 120px;
    height: 120px;
    object-fit: cover;
    border-radius: var(--sp-radius-sm);
}

/* Panier vide : cercle au trait ink (border.strong) */
.cart-empty-circle{
    width: 80px;
    height: 80px;
    border: 2px solid var(--sp-color-border-strong);
}

/* ------------------------------------------------------------------
   Panneau panier (DESIGNAPP, « Panier »)
   ------------------------------------------------------------------ */

/* L'en-tête ne porte que le logotype (mobile) et la croix, et AUCUN filet : c'est le titre
   qui suit qui sépare le chrome du contenu (DESIGNAPP). */
.cart-header{
    border-bottom: 0;
    padding-bottom: 0;
}

.cart-header-brand{
    display: inline-flex;
    align-items: center;
}

.cart-body{
    padding: var(--sp-space-4);
}

/* Le titre du panneau et le total en vis-à-vis : le chiffre d'une section se lit sur la ligne
   de son titre, en font.size.figure et family.base (DESIGNAPP). */
.cart-title-row{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-4);
    margin-bottom: var(--sp-space-4);
}

/* Titre de PAGE : il nomme l'écran entier et se pose sur le fond, en font.size.h2 (DESIGNAPP,
   « Le titre d'une page reste sur le fond »). UNE seule règle pour tous les écrans qui le
   portent : c'est une règle du DS, pas un style d'écran — dupliquée par écran, elle dérive. */
.page-title,
.cart-title,
.photo-side-title{
    font-size: var(--sp-font-size-h2);
    font-weight: var(--sp-font-weight-title);
    letter-spacing: var(--sp-font-tracking-tight);
    margin: 0;
}

/* Là où DEUX titres se font face sur une même ligne — le nom de l'écran et celui de la section
   qu'on y règle —, ils descendent à h2.dense (24px) : à 28px ils se disputent l'entrée de la
   page (DESIGNAPP, « Paramètres du compte »). Seule la taille change, pas la hiérarchie. */
.page-title--dense{
    font-size: var(--sp-font-size-h2-dense);
}

.cart-title-count{
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-muted);
}

.cart-title-total{
    font-family: var(--sp-font-family-base), sans-serif;
    font-size: var(--sp-font-size-figure);
    font-weight: var(--sp-font-weight-bold);
    white-space: nowrap;
}

/* Groupe d'un photographe : carte crème posée sur la surface BLANCHE du panneau. Une carte
   blanche ne s'y détacherait pas, et une ombre n'y a rien à détacher : c'est le crème qui
   fait la surface (DESIGNAPP). */
.cart-group{
    padding: var(--sp-space-4);
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-cream);
}

.cart-group + .cart-group{
    margin-top: var(--sp-space-4);
}

.cart-group-event{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    margin-bottom: var(--sp-space-4);
}

/* Le bloc de texte doit pouvoir rétrécir sous la largeur de son contenu, sans quoi un nom
   d'évènement long pousse le badge hors de la carte. */
.cart-group-event-text{
    min-width: 0;
}

.cart-group-event-title{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

/* Sur crème, la méta est brune : le gris muted est calculé pour le blanc (DESIGNAPP). */
.cart-group-event-meta,
.cart-group-author{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-brown);
}

/* L'attribution est une phrase, pas un bloc : l'avatar y prend `xs`, la seule taille qui se
   pose DANS une ligne de texte (fiche avatar). Elle respire plus bas que les autres lignes —
   elle referme l'identité du groupe avant que la remise et les photos ne commencent. */
.cart-group-author{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    margin-bottom: var(--sp-space-6);
}

.cart-group-author-name{
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
    text-decoration: none;
}

/* Bandeau de remise : le seul violet du produit (tronc, « Le violet de la remise »). Aplat
   accent.deal sous du texte blanc, en pilule pleine largeur. Il dit un avantage ACQUIS :
   il ne se clique pas, et le violet ne se pose jamais sur un bouton. */
.cart-deal{
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-space-2);
    margin-bottom: var(--sp-space-4);
    padding: var(--sp-space-2) var(--sp-space-4);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-deal);
    color: var(--sp-color-text-inverse);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
}

/* Lignes de photos : pas de filet entre elles — les vignettes découpent déjà la liste, et un
   filet de plus dans une carte crème la ferait passer pour un tableau (DESIGNAPP). */
.cart-lines{
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-4);
}

.cart-line{
    display: flex;
    align-items: stretch;
    gap: var(--sp-space-3);
}

/* L'infobulle de licence suit le mot sur sa ligne : posée en frère flex, elle était rejetée
   à l'autre bout de la colonne dès que « Utilisation personnelle » passait à la ligne. */
.cart-line-info{
    margin-left: var(--sp-space-1);
    color: var(--sp-color-text-brown);
    vertical-align: -2px;
}

/* Vignette de liste : ratio 1:1 et radius.md (DESIGN, « Images ») */
.cart-line-thumb{
    width: 76px;
    flex-shrink: 0;
    align-self: flex-start;
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: var(--sp-radius-md);
}

.cart-line-thumb img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cart-line-specs{
    flex: 1 1 auto;
    min-width: 0;
}

.cart-line-label{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-brown);
    line-height: var(--sp-font-leading-snug);
}

.cart-line-value{
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-default);
    line-height: var(--sp-font-leading-snug);
}

/* Le prix en tête de ligne, le retrait calé au pied : la colonne s'étire sur la hauteur de la
   ligne pour que la corbeille tombe toujours au même endroit, quelle que soit la longueur du
   bloc de specs. */
.cart-line-aside{
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex-shrink: 0;
    text-align: right;
}

.cart-line-aside form{
    margin-top: auto;
}

.cart-line-price-was{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-brown);
    text-decoration: line-through;
}

.cart-line-price{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

/* IconButton variant ghost — icône seule, sans fond : la fermeture du panneau et le retrait
   d'une ligne. La cible 44px vient de .btn-circle, la couleur est celle du ghost. */
.cart-icon-ghost{
    color: var(--sp-color-text-default);
}

/* L'offre encore à saisir (paliers, autres photos) vit HORS de la carte du groupe : ce qui
   est décidé dedans, ce qui reste à décider dehors (DESIGNAPP). */
.cart-offer{
    margin-top: var(--sp-space-4);
    padding: 0 var(--sp-space-1);
}

/* L'unité d'un prix de palier : elle accompagne le montant sans le concurrencer (échelle méta,
   pas une taille en dur hors échelle). */
.cart-offer-unit{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-brown);
}

/* ------------------------------------------------------------------
   Résumé de la commande (DESIGNAPP, « Résumé de la commande »)
   Réemploi de la carte crème du panier : deux écrans qui montrent la même commande ne
   s'inventent pas deux surfaces. Seules les lignes changent — ici on COMPTE les photos.
   ------------------------------------------------------------------ */

.order-line{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-4);
}

.order-line-title{
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
}

/* La bande des vignettes : on reconnaît ses photos d'un coup d'œil, sans dérouler autant de
   lignes identiques qu'il y a de photos (DESIGNAPP). */
.order-thumbs{
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
    margin-top: var(--sp-space-2);
}

.order-thumb{
    width: 56px;
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: var(--sp-radius-md);
}

.order-thumb img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.order-group + .order-group{
    margin-top: var(--sp-space-4);
}

/* Le total de la commande : le mot porte son décompte en méta, le montant répond en
   font.size.figure sur la même ligne (DESIGNAPP, « le chiffre d'une section »). */
.order-total{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-4);
}

.order-total-label{
    display: flex;
    align-items: baseline;
    gap: var(--sp-space-2);
    font-size: var(--sp-font-size-h3);
    font-weight: var(--sp-font-weight-title);
}

.order-total-count{
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-brown);
}

.order-total-amount{
    font-family: var(--sp-font-family-base), sans-serif;
    font-size: var(--sp-font-size-figure);
    font-weight: var(--sp-font-weight-bold);
    white-space: nowrap;
}

/* Le second total, celui posé au-dessus du bouton qui paie : même construction, mais sur le
   fond de page et non dans la carte — sa méta reprend donc le gris du blanc. */
.order-total--foot{
    padding-top: var(--sp-space-4);
    border-top: 1px solid var(--sp-color-border-default);
}

.order-total--foot .order-total-count{
    color: var(--sp-color-text-muted);
}

/* ------------------------------------------------------------------
   Paramètres du compte (DESIGNAPP, « Paramètres du compte »)
   ------------------------------------------------------------------ */

/* Une LISTE DE LIENS, pas des onglets : chaque entrée mène à son adresse, qu'on doit pouvoir
   mettre en favori et rouvrir (DESIGNAPP). */
/* Les entrées respirent : ce sont quatre destinations distinctes, pas un bloc plein. L'écart
   compte double depuis que la surface courante est un crème très clair — c'est le blanc entre
   les lignes qui donne à la ligne teintée son contour. */
.settings-nav{
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-3);
}

.settings-nav-item{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    padding: var(--sp-space-3) var(--sp-space-4);
    border-radius: var(--sp-radius-md);
    color: var(--sp-color-text-default);
    text-decoration: none;
}

/* Le survol prend la MÊME surface que l'entrée courante, pas une plus foncée : le crème de la
   page est déjà le plus clair de la palette, et tout ce qui se pose au-dessus rendrait la ligne
   survolée plus présente que celle où l'on est. Le partage se lit ainsi — la teinte dit
   « désignée », le poids de titre dit « courante ». */
.settings-nav-item:hover{
    background-color: var(--sp-color-bg-default);
    color: var(--sp-color-text-default);
}

/* L'entrée courante prend le crème de la page et le poids de titre — pas l'orange : c'est un
   lieu où l'on est, pas une action ni un filtre réglé (DESIGNAPP). Le creux (bg.sunken) est
   juste sur un fond crème ; dans une carte BLANCHE il se détache assez pour devenir un objet,
   et l'entrée réclame alors le clic qu'on vient de faire. La teinte peut être aussi discrète
   parce qu'elle ne porte pas l'état seule : le poids le tient à l'œil, aria-current au lecteur
   d'écran. */
.settings-nav-item.is-current{
    background-color: var(--sp-color-bg-default);
    font-weight: var(--sp-font-weight-title);
}

/* Glyphe à largeur constante : c'est ce qui rend la colonne parcourable d'un regard. */
.settings-nav-glyph{
    flex: 0 0 auto;
    display: inline-flex;
    width: 24px;
    color: var(--sp-color-text-default);
}

/* ------------------------------------------------------------------
   Livraison des photos (DESIGNAPP, « Livraison des photos »)
   ------------------------------------------------------------------ */

/* Encart d'information : glyphe de tête, titre, texte. Surface crème SANS ombre — c'est un
   bloc de contenu, pas une surface interactive (fiche card). */
.notice{
    display: flex;
    align-items: flex-start;
    gap: var(--sp-space-3);
    padding: var(--sp-space-4);
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-cream);
}

.notice-glyph{
    flex: 0 0 auto;
    color: var(--sp-color-text-default);
}

.notice-title{
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
}

/* Sur crème, le secondaire est brun : le gris muted est calculé pour le blanc. */
.notice-text{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-brown);
}

.delivery-order{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
}

/* Le bloc de texte doit pouvoir rétrécir sous la largeur de son contenu, sinon une date longue
   pousse la pastille hors de la carte. */
.delivery-order-text{
    min-width: 0;
}

.delivery-order-title{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

/* Sur un fond sombre, le muted ne survit pas : la méta passe en text.inverse et se distingue
   par sa taille, jamais par une teinte plus pâle (DESIGNAPP, « Bloc de statistique »). */
.delivery-order-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-inverse);
}

/* La photo livrée : pleine largeur, radius.lg, jamais déformée. */
.delivery-photo{
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--sp-radius-lg);
}

/* Carte imbriquée (fiche card) : filet, radius.md, AUCUNE ombre et gouttière resserrée —
   posée sur une autre surface, elle n'a plus de fond crème sous elle pour porter une ombre. */
.cart-payment-card{
    border: 1px solid var(--sp-color-border-default);
    border-radius: var(--sp-radius-md);
    box-shadow: none;
    --bs-card-spacer-y: var(--sp-space-3);
    --bs-card-spacer-x: var(--sp-space-3);
}

/* Le détail accompagne, le Total porte. */
.cart-summary-line{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-4);
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.cart-total{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-space-4);
    margin-top: var(--sp-space-3);
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

/* Pied : le filet sépare le récapitulatif de l'action. Les moyens de paiement viennent APRÈS
   le bouton — ce sont une garantie, pas un choix à faire (DESIGNAPP). */
.cart-foot{
    padding: var(--sp-space-4);
    border-top: 1px solid var(--sp-color-border-default);
}

.cart-methods-label{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Compteur de photos : vert-sarcelle du DS (feedback.success sert aussi aux compteurs) */
.photo-count{
    color: var(--sp-color-feedback-success);
    font-weight: var(--sp-font-weight-title);
}

/* Texte sur photo : scrim DS bas-vers-haut (voile léger en haut pour la ligne de retour) */
.card-image-overlay {
    background: linear-gradient(to top, var(--sp-color-overlay-scrim) 0%, rgba(15, 15, 17, .35) 100%);
    border-radius: var(--sp-radius-lg);
}

.thumbnail-wrapper{
    overflow: hidden;
}

.thumbnail-wrapper img {
    transition: .2s ease-in-out;
    object-fit: cover;
    height: 100%;
    width: 100%;
}

.card-hover:hover .thumbnail-wrapper img {
    transform: scale(1.1);
}

@media (min-width: 576px) {
    .card-spacer-xl{
        --bs-card-spacer-y: 32px;
        --bs-card-spacer-x: 32px;
    }
}

@media (min-width: 992px) {
    .card-spacer-lg{
        --bs-card-spacer-y: 24px;
        --bs-card-spacer-x: 24px;
    }

    .card-spacer-xl{
        --bs-card-spacer-y: 50px;
        --bs-card-spacer-x: 50px;
    }
}


/* Carousel */

[data-controller="carousel"] .btn[data-action*="Left"],
[data-controller="carousel"] .btn[data-action*="Right"]{
    --bs-btn-bg: var(--sp-color-bg-surface);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-bg: var(--sp-color-bg-surface);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-bg: var(--sp-color-bg-surface);
    --bs-btn-active-border-color: transparent;
    border-radius: 0;
    font-size: 1.1rem;
    --bs-btn-padding-x: .5rem;
    --bs-btn-padding-y: .5rem;
}

[data-controller="carousel"] .btn[data-action*="Left"]{
    left: -10px;
}

[data-controller="carousel"] .btn[data-action*="Right"]{
    right: -10px;
}

/* Check-btn-pill */

.check-btn-pill .btn{
    display: inline-flex;
    align-items: center;
    height: 48px;
    gap: 8px;
    border-radius: 24px;
    padding: 8px 12px 8px 4px;
    border-width: 1px;
}

/* La croix garde sa place : elle se révèle en fondu, la rangée de chips ne saute pas */
.check-btn-pill .btn .check-pill-cross{
    opacity: 0;
    transform: scale(.8);
    transition: opacity var(--sp-duration-base) var(--sp-easing-standard),
                transform var(--sp-duration-base) var(--sp-easing-standard);
}

.check-btn-pill .btn-check:checked + .btn .check-pill-cross {
    opacity: 1;
    transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
    .check-btn-pill .btn .check-pill-cross{
        transition: none;
        transform: none;
    }
}

/* ------------------------------------------------------------------
   ChoicePill (fiche choice-pill.md)
   ------------------------------------------------------------------ */

/* Une case à cocher en forme de pilule, pas une chip : la chip filtre ce qu'on regarde et
   flotte (blanche, ombrée, radius.md) ; la pilule compose ce qu'on enregistre et reste plate
   au rang d'un champ (filet, radius.full). Le traitement chip des filtres vit plus haut, sur
   `.btn-check + .btn-light`, et n'est pas touché ici. */
.choice-pill-group{
    position: relative;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-space-3);
}

/* La case reste une vraie case : masquée à l'œil, pas au clavier ni au lecteur d'écran —
   d'où ni `display:none` ni `visibility:hidden`, qui la sortiraient de l'ordre de tabulation.
   Elle s'absolute dans le groupe (`position:relative` ci-dessus) pour que le focus n'envoie
   pas la page en haut. */
.choice-pill-input{
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.choice-pill{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-2);
    min-height: 40px;
    padding: var(--sp-space-2) var(--sp-space-4);
    border: var(--sp-border-width-strong) solid var(--sp-color-border-default);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-bg-surface);
    color: var(--sp-color-text-muted);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-body);
    cursor: pointer;
    transition: color var(--sp-duration-fast) var(--sp-easing-standard),
                border-color var(--sp-duration-fast) var(--sp-easing-standard);
}

/* Le glyphe de sport se pose nu (sport-badge.md) : toute la zone ne parle que de disciplines,
   il n'y a rien dont l'isoler. Il prend la couleur du libellé, donc l'état. */
.choice-pill-glyph{
    flex: 0 0 auto;
    display: inline-flex;
}

.choice-pill:hover{
    border-color: var(--sp-color-text-brown);
}

/* Retenu = ink, PAS orange. Exception à checkbox.md, actée dans DESIGNAPP (« Là où les états
   cochés s'accumulent ») : trente pilules d'un coup, dont trois retenues, feraient de l'accent
   un semis d'alertes au lieu d'un point de mire. Glyphe, libellé et filet basculent ensemble ;
   le fond ne bouge pas — un aplat sous un libellé se lirait comme un bouton. */
.choice-pill-input:checked + .choice-pill{
    color: var(--sp-color-text-default);
    border-color: var(--sp-color-border-strong);
}

.choice-pill-input:focus-visible + .choice-pill{
    box-shadow: var(--sp-shadow-ring);
}

/* La croix n'existe qu'à l'état retenu : elle dit qu'on peut reprendre son choix. Elle est
   dans le libellé, jamais un bouton à part — un contrôle imbriqué dans un autre n'est
   atteignable ni au clavier ni au lecteur d'écran. C'est le SEUL aplat de la pilule, et le
   seul élément qui s'inverse : la croix y passe en blanc sur l'ink du filet retenu. */
.choice-pill .choice-pill-cross{
    display: none;
}

.choice-pill-input:checked + .choice-pill .choice-pill-cross{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-border-strong);
    color: var(--sp-color-text-inverse);
}

/* Quota atteint : ce qu'on ne peut plus prendre le dit soi-même, plutôt qu'un message après coup. */
.choice-pill-input:disabled + .choice-pill{
    opacity: .45;
    cursor: not-allowed;
}

/* Dropdown */

.dropdown-menu {
    --bs-dropdown-padding-x: 1rem;
    --bs-dropdown-padding-y: 1rem;
    --bs-dropdown-item-padding-x: 1rem;
    --bs-dropdown-item-padding-y: 0.5rem;
    --bs-dropdown-border-color: var(--bs-border-color);
    --bs-dropdown-border-radius: var(--bs-border-radius);
    --bs-dropdown-link-active-bg: var(--bs-dropdown-link-hover-bg);
}

.dropdown-item {
    --bs-dropdown-link-color: var(--bs-secondary-color);
}

.dropdown-item strong{
    color: var(--bs-body-color);
}

.dropdown-item.active, .dropdown-item:active {
    --bs-dropdown-link-active-bg: transparent;
    --bs-dropdown-link-active-color: var(--bs-heading-color);
    font-weight: 800;
}

.dropdown-toggle.no-arrow:after {
    content: normal;
}

/* List */

ul.styled,
ol.styled {
    padding: .25rem 0 .25rem var(--list-spacer-left);
}

ul.styled > li,
ol.styled > li {
    position: relative;
    min-height: 2rem;
    padding-top: .25rem;
    list-style-type: none;
}

ul.styled > li:not(:last-child),
ol.styled > li:not(:last-child) {
    margin-bottom: 1rem;
}

ol.styled > li {
    counter-increment: step-counter;
}

ul.styled > li:before{
    content: '';
    position: absolute;
    background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="%23680928" d="M8.59 16.59L13.17 12L8.59 7.41L10 6l6 6l-6 6z"/></svg>');
    background-repeat: no-repeat;
    background-position: center;
    display: inline-block;
    background-color: rgb(255 182 71);
    border-radius: 50%;
    left: calc( 0px - var(--list-spacer-left) );
    height: var(--bullet-size);
    width: var(--bullet-size);
    font-size: 1rem;
    line-height: 1rem;
}

ol.styled > li:before {
    --ol-bullets-bg-color: rgb(255 182 71);
    --ol-bullets-color: rgb(104 9 40);
    position: absolute;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    content: counter(step-counter);
    font-size: 80%;
    background-color: var(--ol-bullets-bg-color);
    color: var(--ol-bullets-color);
    font-weight: 700;
    padding: 3px 8px;
    border-radius: .25rem;
    height: var(--bullet-size);
    width: var(--bullet-size);
    left: calc( 0px - var(--list-spacer-left) );
}

ul.styled > li:after,
ol.styled > li:after {
    content:'';
    position: absolute;
    display: flex;
    left: calc( -1px - var(--list-spacer-left) + var(--bullet-size) / 2 );
    bottom: 0;
    height: calc(100% - 2.5rem);
    border-left: 1px solid currentcolor;
    opacity: .5;
}

@media (max-width: 991px) {
    :root {
        --list-spacer-left: 1.5rem;
        --bullet-size: 1rem;
    }
}

/* Modal — la Dialog du DS (designsystem/spicto/app/components/dialog.md) */

.modal {
    /* En-tête et pied suivent le corps : la modale est UNE surface, pas trois bandes */
    --bs-modal-header-bg: var(--sp-color-bg-default);
    --bs-modal-header-border-width: 0;
    --bs-modal-footer-bg: var(--sp-color-bg-default);
    --bs-modal-footer-border-width: 0;
    /* Gouttière space.6 à toutes les tailles : la fiche n'en connaît qu'une, et l'en-tête
       partage forcément celle du corps, sinon le titre ne s'aligne pas sur ce qu'il coiffe. */
    --bs-modal-padding: var(--sp-space-6);
    --bs-modal-header-padding-x: var(--sp-space-6);
    --bs-modal-header-padding-y: var(--sp-space-6);
    --bs-modal-header-padding: var(--sp-space-6) var(--sp-space-6);
    --bs-modal-border-radius: var(--sp-radius-lg);
    /* L'arrondi INTERNE ne se dérive pas tout seul du précédent : Bootstrap le recalcule
       depuis sa propre --bs-border-radius-lg. Sans cette ligne, l'en-tête et le pied gardent
       des coins de 6px dans une surface arrondie à 16px — invisible tant que leur fond est
       le même que celui du corps, visible dès qu'il change. */
    --bs-modal-inner-border-radius: var(--sp-radius-lg);
    --bs-modal-border-width: 0;
}

/* L'ombre se pose sur .modal-content, pas par variable : Bootstrap DÉCLARE
   --bs-modal-box-shadow mais ne la lit nulle part. La renseigner ne fait donc rien. */
.modal-content {
    box-shadow: var(--sp-shadow-pop);
}

/* Le voile de la fiche, pas le noir 50% de Bootstrap : ce qu'on recouvre reste deviné, et
   il reste chaud comme le reste de la marque.

   Il se règle ICI et pas sur .modal : .modal-backdrop est un élément que Bootstrap ajoute
   en fin de <body>, frère de la modale et non son enfant — une variable posée sur .modal ne
   l'atteint jamais, et il redéfinit de toute façon la sienne. L'opacité passe à 1 parce que
   le voile porte déjà la sienne dans son rgba ; laissée à .5, elle la diviserait par deux. */
.modal-backdrop {
    --bs-backdrop-bg: var(--sp-color-overlay-veil);
    --bs-backdrop-opacity: 1;
}

.modal-header {
    background-color: var(--bs-modal-header-bg);
    /* La croix se cale sur la ligne du titre, pas au milieu de la hauteur (fiche dialog) */
    align-items: flex-start;
}

/* Le titre entre dans le rythme du corps : space.4 sous lui, pas la gouttière de la modale
   (fiche dialog). La gouttière borne la SURFACE — ce qui la sépare du dehors ; le titre, lui,
   coiffe ce qui le suit dedans, et à 24px il flotte au-dessus au lieu de l'annoncer.

   Le sélecteur porte sur `.dialog`, la classe du composant, et non sur `.modal` : les modales
   qui ne sont pas des Dialog du DS (visionneuse d'image, remerciement de téléchargement) gardent
   ce qu'elles avaient. Une règle de fiche vaut en revanche pour TOUTES les Dialog. */
.dialog .modal-header {
    padding-bottom: var(--sp-space-4);
}

/* Le titre d'une modale est un h3 : c'est le rang du DS pour un titre de surface. Le niveau
   HTML, lui, suit la page ; la classe ne le décide pas. */
.modal-title {
    font-size: var(--sp-font-size-h3);
    font-weight: var(--sp-font-weight-title);
}

.modal-footer {
    justify-content: space-between;
}

/* Largeur plafonnée (fiche dialog) : au-delà, la ligne de texte dépasse les 75 caractères et
   le champ unique se met à flotter au milieu d'un vide. La variable se pose sur .modal, jamais
   sur .modal-content : c'est .modal-dialog qui la lit, et elle ne remonterait pas jusqu'à lui. */
@media (min-width: 768px) {
    .modal.dialog {
        --bs-modal-width: 420px;
    }
}

/* Corps : des blocs séparés de space.4, sans marges du bas héritées de Bootstrap — la
   gouttière appartient au conteneur, pas à chaque paragraphe. */
.dialog-body,
.dialog-form {
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-4);
}

.dialog-body {
    padding-top: 0;   /* l'en-tête porte déjà la gouttière haute */
}

/* Rappel de contexte : la ligne canonique d'évènement, seule. Son filet de séparation ne
   sépare rien ici (il n'y a pas de liste), et sa gouttière verticale est celle d'une liste,
   pas d'un premier bloc de modale. */
.dialog-recap .list-item {
    padding-top: 0;
    padding-bottom: 0;
    border-bottom: 0;
}

.dialog-recap .list-item::after {
    content: none;
}

/* Croix : IconButton ghost. Pas de fond au repos, la surface n'apparaît qu'au survol.
   La taille reste celle de .btn-circle (control.icon-button, 44px) : la redéfinir plus
   petite pour « équilibrer » l'en-tête coûterait la cible tactile que la fiche impose.
   Les marges négatives ne la rognent pas, elles la recentrent sur la gouttière. */
.dialog-close {
    color: var(--sp-color-text-default);
    background-color: transparent;
    /* `auto` à gauche, et lui seul, pousse la croix au coin : .modal-header de Bootstrap ne
       porte AUCUN justify-content, il compte sur la marge auto de son propre .btn-close.
       Remplacé par l'IconButton du DS, le bouton se collait au titre. */
    margin: calc(-1 * var(--sp-space-2)) calc(-1 * var(--sp-space-2)) 0 auto;
}

.dialog-close:hover {
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-default);
}

/* Le LIEN À PARTAGER d'une modale `done` (fiche dialog). Ce n'est pas un input habillé : c'est
   une surface qui TIENT LIEU de champ, et qui en prend donc le fond, le filet et le rayon de
   zone — 12px, l'espace connecté (fiche input, « Ce qui tient lieu de champ suit la règle du
   champ »). Blanc sur le crème de la modale : c'est la raison même pour laquelle la Dialog n'est
   pas blanche, un champ blanc sur du blanc n'ayant plus que son filet pour exister. */
.share-link {
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    min-height: 44px;   /* hauteur de champ (fiche input) */
    padding-left: var(--sp-space-3);
    padding-right: var(--sp-space-2);
    border: var(--sp-border-width-default) solid var(--sp-color-border-cream);
    border-radius: var(--sp-radius-md);
    background-color: var(--sp-color-bg-surface);
}

/* L'anneau se pose sur la SURFACE, pas sur le champ nu qu'elle contient : c'est elle qui a le
   filet et le rayon, et un anneau dessiné à l'intérieur d'elle se lirait comme un second cadre.
   Le champ garde donc son focus — il ne le perd pas, il le délègue. */
.share-link:focus-within {
    border-color: var(--sp-color-border-focus);
    box-shadow: var(--sp-shadow-ring);
}

.share-link-glyph {
    flex-shrink: 0;
    color: var(--sp-color-text-muted);
}

/* Le lien se lit en entier ; s'il déborde, c'est lui qui se tronque, jamais le bouton de copie
   qui se serre. */
.share-link-input {
    flex: 1;
    min-width: 0;
    padding: 0;
    border: 0;
    background-color: transparent;
    color: var(--sp-color-text-default);
    font-size: var(--sp-font-size-body);
    text-overflow: ellipsis;
}

.share-link-input:focus {
    outline: none;   /* l'anneau est porté par .share-link:focus-within, il n'est pas supprimé */
}

/* IconButton ghost : pas de fond au repos, la surface n'apparaît qu'au survol. Sa largeur est un
   MINIMUM et non une taille fixe — le contrôleur de copie remplace le glyphe par « Copié ! »
   pendant deux secondes, et un disque figé couperait le mot. */
.share-link-copy {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--sp-control-icon-button);
    min-height: var(--sp-control-icon-button);
    padding: 0 var(--sp-space-3);
    border: 0;
    border-radius: var(--sp-radius-full);
    background-color: transparent;
    color: var(--sp-color-text-muted);
    font-size: var(--sp-font-size-meta);
    white-space: nowrap;
}

.share-link-copy:hover {
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-default);
}

.share-link-copy:focus-visible {
    outline: none;
    box-shadow: var(--sp-shadow-ring);
}

/* La MENTION de Spicto Pro, sous le filet de la carte du prix : elle nomme l'offre là où sa limite
   se rencontre, et reste INERTE tant que l'abonnement n'existe pas (DESIGNAPP). Elle ne porte donc
   ni surface, ni chevron, ni soulignement — rien qui promette une destination. La pastille se cale
   sur la ligne du texte plutôt que sur sa boîte, sinon elle en dépasse. */
.pro-mention {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-space-2);
}

/* Ce que l'offre changerait, à la suite de son nom : en `size.meta`, parce que la ligne porte deux
   rangs — le nom du produit, puis ce qu'il fait — et qu'ils ne peuvent pas peser pareil. La graisse
   ne descend pas pour autant (`weight.body`), et l'encre reste `text.default` : ce qui rétrécit est
   la taille, pas le registre — une promesse qu'on met en gris est une promesse qu'on ne lit pas. */
.pro-mention-claim {
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-body);
    color: var(--sp-color-text-default);
}

/* Le VERROU de marque « Spicto Pro » (BRAND) : logotype, sceau, rang « PRO ». Trois pièces d'un
   seul nom — elles se serrent d'un cran sous l'espacement des mots pour se lire comme un bloc, et
   la zone de protection du logo ne s'applique pas ici : posée dans une phrase, elle y ferait un
   trou au lieu de protéger quoi que ce soit. */
.pro-lockup {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-1);
}

/* Le logotype seul : le signe est déjà là, porté par le sceau. Hauteur de plancher (15px), la
   même que dans les en-têtes — au-dessous, le tracé unique du mot perd ses liaisons. */
.pro-lockup-logotype {
    display: block;
    height: 15px;
    width: auto;
}

/* Le sceau prend `accent.seal` : même valeur que l'orange d'état, autre rôle — c'est de
   l'identité, pas un élément coché (DESIGN). Il n'est pas le logo, donc il peut être orange. */
.pro-lockup-seal {
    display: inline-flex;
    color: var(--sp-color-accent-seal);
}

/* Le rang, en capitales : un morceau de nom d'offre, pas un libellé d'interface — la seule casse
   haute admise ici avec le marqueur « PUBLICITÉ » (DESIGN, « Casse »). Sa pastille est à CONTOUR
   et n'est donc pas un `Badge` : le jeu de tons du composant est fermé et tout en aplat. */
.pro-lockup-tier {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--sp-space-1);
    border: var(--sp-border-width-default) solid var(--sp-color-border-strong);
    border-radius: var(--sp-radius-xs);
    color: var(--sp-color-text-default);
    font-size: var(--sp-font-size-12);
    font-weight: var(--sp-font-weight-body);
    line-height: var(--sp-font-leading-normal);
    text-transform: uppercase;
}

/* L'EFFET d'une réponse, montré à sa place (fiche dialog, variant `ask`) : la ligne concernée
   telle qu'elle paraîtra, posée sur le crème creusé pour se distinguer de la modale sans devenir
   une carte. Rien ne s'y clique — c'est une démonstration, pas un contenu. */
.dialog-preview {
    padding: var(--sp-space-3);
    border-radius: var(--sp-radius-md);
    background-color: var(--sp-color-bg-sunken);
}

/* Mention légale : sous l'action, en mention fine (fiche dialog) */
.dialog-note {
    font-size: var(--sp-font-size-12);
    color: var(--sp-color-text-muted);
}

/* L'abonnement déjà pris : un fait acquis, pas une alerte — d'où feedback.success et
   aucune surface. Le variant qui conviendrait au Message n'existe pas (sa liste est
   fermée), et en inventer un pour une phrase serait ajouter au DS ce qu'il refuse. */
.dialog-confirm {
    color: var(--sp-color-feedback-success);
    font-weight: var(--sp-font-weight-title);
}

#thanksModal .thumbnail-wrapper img {
    max-height: 200px;
}

@media (min-width: 992px) {
    #thanksModal .thumbnail-wrapper img {
        min-height: 260px;
        max-height: 260px;
    }
}

/* Nav-pills */

/* Tabs DS : l'actif est une pill crème à libellé ink, l'inactif est estompé
   (cf. designsystem/spicto/app/components/tabs.md) */
.nav-pills {
    --bs-nav-pills-border-radius: var(--sp-radius-md);
    --bs-nav-pills-link-active-color: var(--sp-color-text-default);
    --bs-nav-pills-link-active-bg: var(--sp-color-bg-sunken);
    --bs-nav-pills-border-width: 0;
    --bs-nav-pills-border-color: transparent;
    --bs-nav-pills-link-active-border-color: transparent;
    --bs-nav-link-color: var(--sp-color-text-muted);
    --bs-nav-link-hover-color: var(--sp-color-text-default);
}

.nav-pills .nav-link{
    border: var(--bs-nav-pills-border-width) solid var(--bs-nav-pills-border-color);
}

.nav-pills .nav-link.active, .nav-pills .show>.nav-link,
.nav-pills .nav-link:hover,
.nav-pills .nav-link:hover:focus{
    color: var(--bs-nav-pills-link-active-color);
    border-color: var(--bs-nav-pills-link-active-border-color);
}

/* Nav-scroll */

.nav-scroll-wrapper {
    max-width: 100%;
}

.nav-scroll {
    display: flex;
    flex-wrap: nowrap;
    max-width: 100%;
    white-space: nowrap;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    overflow-y: hidden;
    scroll-behavior: smooth;
    overflow-x: auto;
    -ms-overflow-style: none;
    scrollbar-width: none;
}

.nav-scroll::-webkit-scrollbar {
    display: none;
}

@media (min-width: 992px) {
    .nav-scroll-wrapper {
        max-width: none;
    }
}


/* Nav-underline */

.nav-underline .nav-link {
    --bs-nav-link-color: var(--sp-color-text-muted);
    --bs-nav-link-hover-color: var(--sp-color-text-default);
    --bs-nav-link-padding-y: 8px;
    --bs-nav-link-padding-x: 8px;
    --bs-nav-link-font-weight: 600;
    padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x) 8px var(--bs-nav-link-padding-x);
}


.nav-underline .nav-link.active,
.nav-underline .show>.nav-link {
    font-weight: 600;
    border-width: 2px;
}

.nav-underline .nav-link:focus, .nav-underline .nav-link:hover {
    border-width: 2px;
}

.main-menu{
    --bs-dropdown-min-width: 700px;
    z-index: 2000;
}

.photographer-menu{
    --bs-dropdown-min-width: 260px;
}

@media (min-width: 992px) {

    .nav-underline .nav-link {
        padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x) 16px var(--bs-nav-link-padding-x);
    }

}

/* Mobile tab bar - full width */

@media (max-width: 991.98px) {

    /* Filet bas sur la largeur du composant, jamais bord à bord (fiche tabs) */
    .tab-bar-nav {
        width: 100%;
    }

}

/* La fiche tabs ne prescrit pas de typographie : les onglets héritent du corps.
   Ne rien imposer ici tant que le DS ne le dit pas. */

/* Onglet d'une fonctionnalité à venir : lisible mais clairement inerte */
.tab-bar-nav .nav-link.disabled {
    opacity: .4;
    pointer-events: none;
}

/* Filet bas des onglets sous le groupe (fiche tabs), espace tabs.separator.gap
   de part et d'autre du filet ; le bloc tab-bar et la recherche mobile respirent
   de layout.section.gap */
.tab-bar-nav {
    border-bottom: 1px solid var(--sp-color-border-default);
    padding-bottom: var(--sp-space-3);
    margin-bottom: var(--sp-space-3);
}

.tab-bar-block {
    margin-bottom: var(--sp-layout-section-gap);
}

.tab-bar-search {
    margin-bottom: var(--sp-layout-section-gap);
}

/* Desktop : onglets à gauche, chips de filtre à droite, sur une seule ligne
   (DESIGNAPP, « Barre d'onglets »). Le filet ancre alors la barre entière et non le
   seul groupe d'onglets — il passe donc du <ul> au bloc. Placé après .tab-bar-nav :
   même spécificité, c'est l'ordre qui tranche. */
@media (min-width: 992px) {
    .tab-bar-block {
        display: flex;
        align-items: center;
        gap: var(--sp-space-3);
        border-bottom: 1px solid var(--sp-color-border-default);
        padding-bottom: var(--sp-space-3);
    }

    .tab-bar-nav {
        border-bottom: 0;
        padding-bottom: 0;
        margin-bottom: 0;
    }

    /* Les chips se rangent à droite. Leur réserve verticale reste : c'est elle qui
       laisse vivre l'ombre, que l'overflow du défilement rognerait. */
    .tab-bar-block .filter-chips {
        margin-left: auto;
    }
}

/* Desktop tab bar - bottom border */

@media (min-width: 992px) {

    .border-bottom-desktop {
        border-bottom: 1px solid var(--bs-border-color);
        padding-bottom: 0;
    }

}

/* Mobile search bar trigger */

/* Off-canvas */

.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl {
    --bs-offcanvas-width: 100vw;
}

/* Menu mobile : la carte de l'utilisateur ouvre le panneau et mène à son espace ; les
   rubriques suivent en listes nues ; le pied tient le seul bouton orange du panneau. */
.mobile-menu .offcanvas-header{
    justify-content: space-between;
}

.mobile-menu-user{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    padding: var(--sp-space-3);
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-default);
    text-decoration: none;
}

.mobile-menu-user-name{
    font-weight: var(--sp-font-weight-title);
}

.mobile-menu-user-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.mobile-menu-section{
    margin-bottom: var(--sp-space-5);
}

.mobile-menu-section li + li{
    margin-top: var(--sp-space-2);
}

.mobile-menu-heading{
    font-weight: var(--sp-font-weight-title);
    margin-bottom: var(--sp-space-2);
}

.mobile-menu-section a{
    display: inline-flex;
    align-items: center;
    color: var(--sp-color-text-default);
    text-decoration: none;
}

.mobile-menu-foot{
    padding: var(--sp-space-4);
    border-top: 1px solid var(--sp-color-border-default);
    background-color: var(--sp-color-bg-surface);
}

@media (min-width: 992px) {
    .offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl {
        --bs-offcanvas-width: 65vw;
    }
}

@media (min-width: 1600px) {
    .offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl {
        --bs-offcanvas-width: 30vw;
    }
}

.offcanvas-header {
    border-bottom: 1px solid var(--light);
    gap: 1rem;
}

.offcanvas-header .btn-close {
    padding: 0;
}

.offcanvas-footer {
    display: flex;
    align-items: center;
    padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}

/* Placeholder */

.placeholder{
    background-color: var(--bs-secondary);
    opacity: .4;
    border-radius: var(--bs-border-radius);
}

.placeholder-lg{
    min-height: 3em;
}

/* Progress */

.progress, .progress-stacked {
    --bs-progress-height: 8px;
}

/* Sale-item */

.sale-item:not(:last-child) {
    border-bottom: 1px solid var(--bs-border-color);
}

/* --- CookieBadge --- */

/* Le consentement se demande en bas de fenêtre, sans voile ni blocage : il n'interrompt rien,
   il ouvre un choix. Surface blanche, filet fin, ombre douce — le chrome reste discret. */
.cookie-badge {
    position: fixed;
    left: var(--sp-space-4);
    right: var(--sp-space-4);
    bottom: var(--sp-space-4);
    /* sous l'offcanvas panier (1045) et les panneaux de filtres : il ne recouvre rien */
    z-index: 1030;
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-3);
    max-width: 420px;
    padding: var(--sp-space-4);
    background-color: var(--sp-color-bg-surface);
    border: 1px solid var(--sp-color-border-on-white);
    border-radius: var(--sp-radius-lg);
    box-shadow: var(--sp-shadow-pop);
}

.cookie-badge-text {
    margin: 0;
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.cookie-badge-text a {
    color: var(--sp-color-text-link);
}

.cookie-badge-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-space-2);
}

/* TOPBAR */

/* Photographer Topbar - Club Topbar */

.photographer-layout #top-bar,
.club-layout #top-bar{
    min-height: 56px;
    display: flex;
    align-items: center;
    margin-bottom: 24px;
}

@media (min-width: 992px) {
    .photographer-layout #top-bar,
    .club-layout #top-bar{
        border-bottom: 1px solid var(--sp-color-border-default);
        margin-bottom: 48px;
    }
}

/* Space Switcher (Dior-style toggle) */

.space-switcher {
    display: none;
    background-color: var(--sp-color-border-default);
    border-radius: 50px;
    padding: 3px;
    gap: 0;
}

@media (min-width: 992px) {
    .space-switcher {
        display: flex;
    }
}

.space-switcher-option {
    padding: 6px 20px;
    border-radius: 50px;
    font-size: 0.8125rem;
    font-weight: 500;
    text-decoration: none;
    color: var(--sp-color-text-muted);
    white-space: nowrap;
    transition: all 0.2s ease;
}

.space-switcher-option:hover {
    color: var(--sp-color-text-default);
}

.space-switcher-option.active {
    background-color: var(--sp-color-bg-surface);
    color: var(--sp-color-text-default);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* SPECIFIC CONTENT */

/* Event preview (form) */

.event-preview-card {
    --bs-heading-color: var(--sp-color-bg-surface);
    --bs-body-color: var(--sp-color-bg-surface);
    --bs-card-color: var(--sp-color-bg-surface);
    min-height: 240px;
    background-size: cover;
    background-position: center;
    transition: .3s ease-in-out;
}

.event-preview-card .card-image-overlay {
    color: var(--sp-color-bg-surface);
}

.event-preview-card .event-date {
    font-weight: 400;
    font-size: 1rem;
}

.event-preview-card h1 {
    color: var(--sp-color-bg-surface);
    margin-bottom: 1rem;
    font-size: 1.5rem;
}

.event-preview-card a,
.event-preview-card span {
    color: var(--sp-color-bg-surface);
}

@media (min-width: 992px) {
    .event-preview-card .event-date {
        font-size: 1.2rem;
        letter-spacing: 1px;
    }

    .event-preview-card h1 {
        font-size: 2rem;
    }
}

/* Event-thumbnail */

/* EventCard DS (designsystem/spicto/app/components/event-card.md) :
   rayon xl, panneau crème, ombre douce, badges superposés sur la couverture */
.event-thumbnail.card {
    --bs-card-spacer-x: 12px;
    --bs-card-spacer-y: 12px;
    --bs-card-border-radius: var(--sp-radius-xl);
    --bs-card-bg: var(--sp-color-bg-cream);
    border: none;
    overflow: hidden;
    box-shadow: var(--sp-shadow-sm);
}

.card-hover {
    transition: box-shadow var(--sp-duration-fast) var(--sp-easing-standard);
}

/* Élévation seule : une carte ne se déplace pas au survol (tronc DESIGN.md, Motion). */
.card-hover:hover {
    box-shadow: var(--sp-shadow-md);
}

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

/* Superpositions dans les coins de la couverture */
.event-overlay-tl {
    position: absolute;
    top: var(--sp-space-3);
    left: var(--sp-space-3);
    z-index: 2;
    max-width: calc(100% - 2 * var(--sp-space-3));
}

.event-overlay-bl {
    position: absolute;
    top: auto;
    bottom: var(--sp-space-3);
    left: var(--sp-space-3);
    z-index: 2;
}

.event-thumbnail-sport {
    font-size: var(--sp-font-size-12);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

@media (max-width: 575.98px) {
    .event-thumbnail .ratio-4x3 {
        --bs-aspect-ratio: 56.25%; /* 16:9 on mobile */
    }
}

.event-thumbnail .card-body {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.event-thumbnail .card-body > * {
    margin-bottom: 0;
}

.event-thumbnail .card-title a{
    font-family: var(--heading-font-family), sans-serif;
    color: var(--bs-heading-color);
    font-size: 14px;
    line-height: 18px;
    font-weight: 600;
    text-decoration: none;
}

.event-thumbnail .event-thumbnail-location{
    font-family: var(--contrast-font-family), sans-serif;
    font-size: 12px;
    line-height: 13px;
    font-weight: 400;
}

.event-thumbnail .event-thumbnail-photo-nb{
    font-family: var(--heading-font-family), sans-serif;
    font-size: 12px;
    line-height: 16px;
    font-weight: 400;
}

.event-thumbnail .event-thumbnail-update{
    font-family: var(--contrast-font-family), sans-serif;
    font-size: 12px;
    line-height: 16px;
    font-weight: 400;
}

.event-thumbnail .btn-square{
    --bs-btn-padding-x: 15px;
    --bs-btn-padding-y: 11px;
    height: auto;
    width: auto;
}


/* Filters */

#filter-btn {
    position: relative;
    left: 0;
    bottom: 0;
    transform: none;
    z-index: 0;
}

#filter-btn.btn-outline-primary {
    --bs-btn-bg: var(--sp-color-bg-surface);
}

/* La mention à recopier : une surface imbriquée dans sa carte, donc un filet et pas d'ombre,
   au cran de rayon de l'emboîtement (fiche card, « Une carte imbriquée »). Le texte est le
   sujet du bloc : on le donne à lire tel qu'il sera copié. */
.club-mention{
    padding: var(--sp-space-3);
    border: 1px solid var(--sp-color-border-default);
    border-radius: var(--sp-radius-md);
}

/* photographer filter card mobile */

@media (max-width: 991.98px) {
    #photographer-filter-card {
        background: transparent;
        border: none !important;
        border-radius: 0;
        padding: 0;
    }

    #photographer-filter-card > p {
        margin-left: calc(var(--bs-gutter-x) * -.5);
        margin-right: calc(var(--bs-gutter-x) * -.5);
        padding: 12px calc(var(--bs-gutter-x) * .5);
        border-top: 1px solid var(--bs-border-color);
        border-bottom: 1px solid var(--bs-border-color);
        margin-bottom: 0;
    }

    #photographer-filter-card > p:has(+ .photographer-filter-content:not(.d-none)) {
        border-bottom: none;
    }

    #photographer-filter-card .photographer-filter-content:not(.d-none) {
        margin-left: calc(var(--bs-gutter-x) * -.5);
        margin-right: calc(var(--bs-gutter-x) * -.5);
        padding: 12px calc(var(--bs-gutter-x) * .5);
        border-bottom: 1px solid var(--bs-border-color);
    }

    #photographer-filter-card [data-mobile-toggle-target="icon"] {
        cursor: pointer;
        font-size: 16px;
    }
}

/* Login */

.login-toggle .toggle-group {
    background: var(--sp-color-bg-grey);
    border-radius: 50px;
    display: flex;
    gap: 4px;
}

.login-toggle .toggle-btn {
    flex: 1;
    text-align: center;
    padding: 16px 10px;
    border-radius: 40px;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    display: inline-flex;
    font-weight: 500;
    cursor: pointer;
    transition: 0.25s;
    border: none;
    background: transparent;
    color: var(--sp-color-text-default);
}

.login-toggle .toggle-btn.active {
    background: var(--sp-color-action-primary);
    color: var(--sp-color-bg-surface);
}


/* UTILITIES */

/* border */

@media (min-width: 992px) {
    .border-bottom-lg-0 {
        border-bottom: 0!important;
    }
}


/* max-width */

.mw-50{
    max-width: 50%!important;
}

.mw-65 {
    max-width: 65% !important;
}

.mw-30 {
    max-width: 30% !important;
}

/* height */

@media (min-width: 992px) {
    .h-xl-100 {
        height: 100% !important
    }
    .vh-xl-100 {
        height: 100vh !important;
    }
}

/* width */

@media (min-width: 576px) {
    .w-sm-auto {
        width: auto !important;
    }
}

/* gap */

.gap-8px{
    gap: 4px;
}

.gap-10 {
    gap: 10rem!important
}

@media (min-width: 992px) {

    .gap-lg-10 {
        gap: 10rem!important
    }

}

/* p */

.p-4{
    padding: 24px!important;
}

/* g */

.g-3, .gy-3 {
    --bs-gutter-y: 8px;
}

.g-3, .gx-3 {
    --bs-gutter-x: 8px;
}

@media (min-width: 992px) {

    .g-3, .gy-3 {
        --bs-gutter-y: 16px;
    }

    .g-3, .gx-3 {
        --bs-gutter-x: 16px;
    }

}

/* margin-bottom */

.mb-16px{
    margin-bottom: 16px;
}

.mb-30px{
    margin-bottom: 30px;
}

.mb-40px{
    margin-bottom: 30px;
}

.mb-50px{
    margin-bottom: 50px;
}

@media (min-width: 992px) {
    .mb-lg-30px{
        margin-bottom: 30px;
    }
    .mb-lg-50px{
        margin-bottom: 50px;
    }
}

/* font-size */

.fs-xxs{
    font-size: var(--font-size-xxs);
}

.fs-xs{
    font-size: var(--font-size-xs);
    line-height: 15px;
}

.fs-s{
    font-size: var(--font-size-s);
}

.fs-lg{
    font-size: var(--font-size-lg);
}

/* bg-wallpaper */

.bg-mobile-wallpaper{
    background-image: var(--bg-wallpaper);
    background-size: cover;
    background-repeat: no-repeat;
    background-attachment: fixed;
}

.bg-triathlon{
    --bg-wallpaper: url("../images/login-wallpaper-DpbYD6r.jpg");
}

.bg-athletics{
    --bg-wallpaper: url("../images/athletics-wallpaper-6LUdrYL.jpg");
}

.bg-team{
    --bg-wallpaper: url("../images/team-wallpaper-pEWZuRs.jpg");
}

.bg-athletics-track{
    --bg-wallpaper: url("../images/athletics-track-wallpaper-FKv3zDs.jpg");
}

.bg-volley{
    --bg-wallpaper: url("../images/volley-wallpaper--tPDHf7.jpg");
}

@media (min-width: 576px) {
    .bg-wallpaper{
        background-image: var(--bg-wallpaper);
        background-size: cover;
        background-repeat: no-repeat;
        background-attachment: fixed;
    }
}

/* shadow */

.shadow{
    --bs-card-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
    --bs-box-shadow: var(--bs-card-box-shadow);
}

.inner-shadow{
    box-shadow: inset 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

/* rounded */

.rounded-xl-top{
    border-top-left-radius: var(--bs-border-radius-xl);
    border-top-right-radius: var(--bs-border-radius-xl);
}

.rounded-lg-top{
    border-top-left-radius: var(--bs-border-radius-lg);
    border-top-right-radius: var(--bs-border-radius-lg);
}

/* column-count */

@media (min-width: 992px) {

    .column-count-3 {
        column-count: 3;
        column-gap: 3rem;
    }
}

/* flexible-grid */

.flexible-grid-12 {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .5rem;
}

@media (min-width: 576px) {
    .flexible-grid-12 {
        grid-template-columns: repeat(6, 1fr);
    }
}

@media (min-width: 992px) {
    .flexible-grid-12 {
        grid-template-columns: repeat(10, 1fr);
    }
}

/* img-cover */

.img-cover {
    overflow: hidden;
    height: 100%;
    border-radius: var(--bs-border-radius-xl);
}

.img-top {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}

.img-cover img {
    width: 100%;
    height: 100%!important;
    max-width: none!important;
    object-fit: cover;
}

/* Ce qui autorise une troncature : un enfant de flex vaut `min-width: auto`, donc il refuse de
   descendre sous la largeur de son contenu — son texte ne se coupe jamais et il pousse ses voisins
   hors du cadre. Bootstrap ne génère pas cette classe alors que les gabarits l'emploient partout
   où un texte doit céder à côté d'un bouton-icône (fiche icon-button) : sans elle, tous les
   `line-clamp` et `text-truncate` posés dans une rangée flex sont inertes. */
.min-w-0{
    min-width: 0;
}

/* Line-clamp */

.line-clamp{
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.line-clamp-1{
    -webkit-line-clamp: 1;
}

.line-clamp-2{
    -webkit-line-clamp: 2;
}
/* fixed */

@media (min-width: 992px) {
    .position-xl-fixed {
        position: fixed !important;
    }
}

/* sticky */

@media (min-width: 992px) {
    .sticky-lg-top {
        top: 1.5rem;
    }
}

/* dark-overlay */

.dark-overlay{
    margin: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg,rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.1) 100%);
}

/* text-decoration */

@media (min-width: 992px) {
    .text-decoration-lg-none {
        text-decoration: none !important;
    }
}

/* Google Auth */

.gsi-material-button {
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    -webkit-appearance: none;
    background-color: WHITE;
    background-image: none;
    border: 1px solid #747775;
    -webkit-border-radius: var(--bs-border-radius);
    border-radius: var(--bs-border-radius);
    box-sizing: border-box;
    color: #1f1f1f;
    cursor: pointer;
    font-family: 'Roboto', arial, sans-serif;
    font-size: 16px;
    height: 46px;
    letter-spacing: 0.25px;
    outline: none;
    overflow: hidden;
    padding: 0 12px;
    position: relative;
    text-align: center;
    -webkit-transition: background-color .218s, border-color .218s, box-shadow .218s;
    transition: background-color .218s, border-color .218s, box-shadow .218s;
    vertical-align: middle;
    white-space: nowrap;
    width: 100%;
    min-width: min-content;
}

.gsi-material-button .gsi-material-button-icon {
    height: 20px;
    margin-right: 12px;
    min-width: 20px;
    width: 20px;
}

.gsi-material-button .gsi-material-button-content-wrapper {
    -webkit-align-items: center;
    align-items: center;
    display: flex;
    -webkit-flex-direction: row;
    flex-direction: row;
    -webkit-flex-wrap: nowrap;
    flex-wrap: nowrap;
    height: 100%;
    justify-content: space-between;
    position: relative;
    width: 100%;
}

.gsi-material-button .gsi-material-button-contents {
    -webkit-flex-grow: 1;
    flex-grow: 1;
    font-family: 'Roboto', arial, sans-serif;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: top;
}

.gsi-material-button .gsi-material-button-state {
    -webkit-transition: opacity .218s;
    transition: opacity .218s;
    bottom: 0;
    left: 0;
    opacity: 0;
    position: absolute;
    right: 0;
    top: 0;
}

.gsi-material-button:disabled {
    cursor: default;
    background-color: #ffffff61;
    border-color: #1f1f1f1f;
}

.gsi-material-button:disabled .gsi-material-button-contents {
    opacity: 38%;
}

.gsi-material-button:disabled .gsi-material-button-icon {
    opacity: 38%;
}

.gsi-material-button:not(:disabled):active .gsi-material-button-state,
.gsi-material-button:not(:disabled):focus .gsi-material-button-state {
    background-color: #303030;
    opacity: 12%;
}

.gsi-material-button:not(:disabled):hover {
    -webkit-box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
    box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
}

.gsi-material-button:not(:disabled):hover .gsi-material-button-state {
    background-color: #303030;
    opacity: 8%;
}

.filter-badge {
    position: absolute;
    top: -2px;
    right: -2px;
    width: 12px;
    height: 12px;
    background: var(--sp-color-feedback-danger);
    border-radius: 50%;
    box-shadow: 0 0 0 2px var(--sp-color-bg-surface); /* anneau blanc pour contraste */
    z-index: 2;
}
.profile-card {
    max-width: none;
}

.profile-photo {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
}

.ts-wrapper.form-select {
    background-image: none !important;
}

.copied {
    background-color: rgba(201, 201, 201, 0.9) !important;
    transition: background-color .3s;
}

.user-circle {
    position: relative;
    width: 48px;
    height: 48px;
    background: var(--sp-color-border-default);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
}


.chevron-toggle{display:inline-flex;transition:transform var(--sp-duration-fast) var(--sp-easing-standard)}
.chevron-toggle.rotate {
    transform: rotate(180deg);
}

/* Gallery highlight overlay */
.gallery-highlight-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    color: var(--sp-color-bg-surface);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    font-weight: 600;
    z-index: 10;
    pointer-events: none;
    transition: opacity 0.5s ease;
}

.gallery-highlight-fade {
    opacity: 0;
}

@media (max-width: 767.98px) {
    #photo-actions {
        width: 100%;
    }
}

/* Bloc de statistique (DESIGNAPP) : la valeur en grand, le libellé dessous en muted. Tous les
   blocs partagent la même taille de valeur, quelle que soit la longueur du nombre — c'est
   l'alignement qui les rend comparables. */
.stat-block-value{
    font-size: var(--sp-font-size-h2);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
    line-height: 1.1;
}

.stat-block-label{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Les glyphes à la place du nombre : au rang de la valeur et à sa hauteur, nus et en
   currentColor — la zone ne parle que de disciplines, le badge n'aurait rien à isoler
   (DESIGNAPP, « Bloc de statistique » ; fiche sport-badge). */
.stat-block-glyphs{
    display: flex;
    align-items: center;
    gap: var(--sp-space-1);
}

/* 1,15em, pas 1em : un glyphe carré posé à la taille exacte des chiffres paraît plus petit
   qu'eux — sa hauteur inclut ses marges, celle d'un chiffre non (DESIGNAPP). */
.stat-block-glyphs svg{
    width: 1.15em;
    height: 1.15em;
}

/* Tabs variant `filter` (fiche tabs) : partition d'une liste DANS une carte. Exclusif et
   exhaustif — il n'y a jamais « rien de sélectionné ». L'actif est un aplat ink parce que la
   surface l'impose : dans une carte blanche, les pills au repos occupent déjà le crème, et un
   actif crème ne se distinguerait plus de ses voisines. */
.tabs-filter{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-space-2);
}

.tabs-filter-item{
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    padding: var(--sp-space-2) var(--sp-space-4);
    border: 1px solid transparent;
    border-radius: var(--sp-radius-md);
    /* bg.control, pas le crème : ces pills vivent DANS une carte blanche, où le crème vire au
       beige sale contre le blanc qui l'entoure (tronc, « Pas de gris hors des échelles »).
       Scopé à ce variant : les onglets existants gardent leur pill crème. */
    background-color: var(--sp-color-bg-control);
    color: var(--sp-color-text-default);
    font-size: var(--sp-font-size-label);
    text-decoration: none;
    transition: background-color var(--sp-duration-fast) var(--sp-easing-standard);
}

.tabs-filter-item--active{
    background-color: var(--sp-color-action-primary);
    color: var(--sp-color-action-primary-text);
}

.tabs-filter-item:focus-visible{
    outline: none;
    box-shadow: var(--sp-shadow-ring);
}

/* Second axe (la période) : il se DESSINE au lieu de se remplir. Ses pills restent blanches,
   et c'est le filet qui porte l'état — ink et trait fort quand l'option est retenue, clair et
   libellé muted quand elle ne l'est pas. Deux aplats sur deux lignes donneraient deux ancres
   qui se disputent le regard (fiche tabs, variant filter). Scopé au second axe : le premier
   garde son aplat, et les onglets existants leur pill crème. */
.tabs-filter--second .tabs-filter-item{
    background-color: var(--sp-color-bg-surface);
    border-color: var(--sp-color-border-control);
    color: var(--sp-color-text-muted);
}

.tabs-filter--second .tabs-filter-item--active{
    background-color: var(--sp-color-bg-surface);
    border-width: var(--sp-border-width-strong);
    border-color: var(--sp-color-border-strong);
    color: var(--sp-color-text-default);
}

/* Le filet entre deux axes : celui de tout le produit (border.default, le même que le
   séparateur d'une ListItem), et une respiration ÉGALE des deux côtés (separator.gap). Le
   `hr` de Bootstrap arrive avec une opacité de .25 qui le rendait plus pâle que les autres
   filets — c'est elle qu'on annule, pas la couleur (fiche tabs). */
.tabs-filter-separator{
    margin: var(--sp-space-3) 0;
    border: 0;
    border-top: 1px solid var(--sp-color-border-default);
    opacity: 1;
}

/* Ligne de transaction (DESIGNAPP, « Pastille de transaction ») */
.transaction-row{
    display: flex;
    /* Aligné en haut, pas centré : une vente dépliée grandit vers le bas, et sa pastille
       comme son montant doivent rester sur la ligne de la date (DESIGNAPP). */
    align-items: flex-start;
    gap: var(--sp-space-3);
    padding: var(--sp-space-3) 0;
    border-top: 1px solid var(--sp-color-border-default);
}

.transaction-row:first-child{
    border-top: 0;
}

/* La teinte classe la NATURE du mouvement — jamais son signe. */
.transaction-row-tile{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: var(--sp-radius-md);
    color: var(--sp-color-text-default);
}

.transaction-row-tile--sale{ background-color: var(--sp-color-accent-soft); }
.transaction-row-tile--ad{ background-color: var(--sp-color-bg-sunken); }
.transaction-row-tile--payout{ background-color: var(--sp-color-bg-cream); }

/* Un virement en cours n'est pas encore un mouvement : son montant s'estompe jusqu'à ce que
   l'argent parte vraiment. La pastille garde sa teinte — la nature n'a pas changé. */
.transaction-row--pending .transaction-row-amount{
    color: var(--sp-color-text-muted);
}

.transaction-row-date{
    color: var(--sp-color-text-default);
    font-weight: var(--sp-font-weight-title);
}

.transaction-row-time,
.transaction-row-detail{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
    font-weight: var(--sp-font-weight-body);
}

.transaction-row-amount{
    flex-shrink: 0;
    white-space: nowrap;
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-default);
}

/* Le signe qualifie : une sortie se lit en rouge. */
.transaction-row-amount--out{
    color: var(--sp-color-feedback-danger);
}

/* Une vente déplie ses photos SUR PLACE, sous la ligne, sans quitter la page. L'affordance
   dit le nombre, et son libellé bascule avec l'état — un « + » qui reste « + » une fois
   ouvert ment sur ce qu'un second clic va faire (DESIGNAPP, « Une vente déplie ses photos »). */
.transaction-row-toggle{
    color: var(--sp-color-text-default);
    font-weight: var(--sp-font-weight-title);
    text-decoration: none;
}

.transaction-row-toggle .transaction-row-toggle-hide,
.transaction-row-toggle[aria-expanded="true"] .transaction-row-toggle-show{
    display: none;
}

.transaction-row-toggle[aria-expanded="true"] .transaction-row-toggle-hide{
    display: inline;
}

.transaction-row-photos{
    grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
    gap: var(--sp-space-2);
    margin-top: var(--sp-space-3);
}

/* Bootstrap pilote l'ouverture par `display` : la grille ne s'applique qu'une fois ouverte,
   sans quoi `display: grid` rouvrirait le bloc replié. */
.transaction-row-photos.show,
.transaction-row-photos.collapsing{
    display: grid;
}

.transaction-row-photos .thumbnail-wrapper{
    border-radius: var(--sp-radius-md);
    overflow: hidden;
}

/* Liste d'informations (DESIGNAPP, « Liste d'informations ») : une ligne par fait, séparées
   d'un filet ; visuel de tête de largeur constante, puis titre et méta. */
.info-list{
    margin: 0;
}

.info-row{
    display: flex;
    align-items: flex-start;
    gap: var(--sp-space-3);
    padding: var(--sp-space-4) 0;
    border-top: 1px solid var(--sp-color-border-default);
}

/* Le filet sépare, il n'encadre pas : pas de trait au-dessus de la première ligne. */
.info-row:first-child{
    border-top: 0;
    padding-top: 0;
}

/* Largeur constante : les lignes s'alignent quel que soit leur visuel — avatar, pastille
   de sport ou glyphe nu. */
.info-row-visual{
    flex: 0 0 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.info-row > .sport-badge{
    flex: 0 0 auto;
}

.info-row-glyph{
    color: var(--sp-color-text-default);
}

.info-row-title{
    color: var(--sp-color-text-default);
}

.info-row-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.info-row-sep{
    margin: 0 var(--sp-space-1);
}

/* Progress (fiche progress) : la barre figure ce qui RESTE, part de la gauche, et ne porte
   aucune étiquette — le compte est dit au-dessus, en toutes lettres. */
.progress-gauge{
    height: 8px;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-soft);
    overflow: hidden;
}

.progress-gauge-fill{
    display: block;
    height: 100%;
    width: var(--progress-value, 0%);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-selected);
}

/* La légende de la jauge : plus petite que le corps parce qu'elle accompagne un chiffre déjà
   écrit en grand sur la ligne du titre — la graisse compense ce que la taille lui retire. Elle
   reste une phrase, jamais un style de libellé (fiche progress). */
.progress-gauge-legend{
    font-family: var(--sp-font-family-base), sans-serif;
    font-size: var(--sp-font-size-meta);
    font-weight: var(--sp-font-weight-title);
}

/* Onglets d'information de la vue photo (fiche tabs) : le filet bas ancre le groupe,
   à la même distance de part et d'autre (separator.gap). */
.photo-tabs{
    border-bottom: 1px solid var(--sp-color-border-default);
    padding-bottom: var(--sp-space-3);
    margin-bottom: var(--sp-space-3);
}

/* Pastille d'annonce d'un onglet : elle annonce, elle ne sélectionne pas — seul orange
   admis dans une barre d'onglets, et il y dit un état (fiche tabs). */
.tab-dot{
    display: inline-block;
    width: 6px;
    height: 6px;
    flex: 0 0 auto;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-selected);
}

/* EMPLACEMENT de la photo (DESIGNAPP, « Vue photo ») : c'est LUI qui a la taille fixe, ratio 3:2,
   pas la carte. Sans ça, le ruban, l'achat et les onglets qui vivent dessous montent et descendent
   à chaque photo. La carte s'y centre, et ce qui reste libre autour d'elle est le fond de page —
   jamais une bande dans la carte. Les flèches lui sont absolues, donc hors flux. */
.photo-frame{
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 3 / 2;
    /* Conteneur de requête : c'est ce qui permet de borner la photo sur CET emplacement, en
       `cqw`/`cqh`. Une chaîne de `max-height: 100%` ne marcherait pas — un pourcentage de hauteur
       ne résout pas contre un parent en hauteur auto, et la carte reprenait toute la place. */
    container-type: size;
    /* Le glissement se joue ICI (DESIGNAPP, « Vue photo ») : c'est la seule boîte dont la taille
       ne dépend pas de la photo. Le nom porté par la CARTE faisait animer au navigateur son
       redimensionnement en même temps que le déplacement, et les deux mouvements se contrariaient. */
    view-transition-name: photo-frame;
}

/* Vue photo (DESIGNAPP, « Vue photo ») : la photo est la vedette, coins radius.2xl. La carte
   ÉPOUSE la photo — c'est elle qui s'ajuste, jamais la photo —, donc ses coins arrondissent
   l'image et non un vide. Elle est bornée par l'emplacement, en hauteur comme en largeur. */
.photo-stage{
    position: relative;
    display: inline-block;
    max-width: 100%;
    max-height: 100%;
    border-radius: var(--sp-radius-2xl);
    overflow: hidden;
}

/* Transition de défilement entre deux photos (DESIGNAPP, « Vue photo »). Elle n'existe QUE si la
   direction a été posée sur <html> — donc entre deux photos, jamais en quittant l'écran. Le signe
   porte le sens : on sort du côté d'où l'on vient, on entre du côté où l'on allait. Le décalage
   est une AMORCE (12%), pas une traversée : la photo est la vedette, elle ne part pas en voyage. */
html[data-photo-transition="next"]{ --photo-slide: 1; }
html[data-photo-transition="prev"]{ --photo-slide: -1; }

/* Glissement FRANC : la sortante quitte le cadre par un bord, l'entrante arrive par l'autre, sur
   toute la largeur de la carte. Un décalage de 12% ne disait aucun sens — il ne restait qu'un
   fondu (DESIGNAPP, « Vue photo »). Pas de fondu ici, donc : ce qui porte le sens, c'est le
   déplacement, et une photo qui s'efface en même temps qu'elle sort brouille la lecture.

   Le repli à 1 n'est pas décoratif : sans lui, un `--photo-slide` absent rendrait le `calc()`
   invalide et la photo ne bougerait plus, en silence. Mieux vaut glisser du mauvais côté que ne
   plus glisser du tout — l'erreur se voit, et se corrige. */
@keyframes photo-slide-out{
    to { transform: translateX(calc(var(--photo-slide, 1) * -100%)); }
}

@keyframes photo-slide-in{
    from { transform: translateX(calc(var(--photo-slide, 1) * 100%)); }
}

/* PAS D'ESPACE avant le pseudo-élément : les pseudo-éléments de transition sont attachés à
   l'élément racine LUI-MÊME. `html[...] ::view-transition-old()` est un combinateur descendant —
   il cherche une transition née d'un descendant de <html>, ce qui n'existe pas, et la règle ne
   s'applique jamais (sans erreur, la feuille reste valide). */
html[data-photo-transition]::view-transition-old(photo-frame){
    animation: photo-slide-out var(--sp-duration-base) var(--sp-easing-standard) both;
}

html[data-photo-transition]::view-transition-new(photo-frame){
    animation: photo-slide-in var(--sp-duration-base) var(--sp-easing-standard) both;
}

/* Le groupe DÉTOURE le mouvement : sans ce clip, une photo qui se déplace de toute sa largeur
   passerait par-dessus ce qui l'entoure — la colonne d'achat sur desktop, le ruban sur mobile —
   et la vedette voyagerait en travers de la page au lieu d'être remplacée.

   Sur desktop l'emplacement ne change pas de taille : le groupe n'a donc rien à morpher, et le
   glissement est le SEUL mouvement à l'écran. Sur mobile sa hauteur suit la photo ; on lui donne
   la durée du système pour que le redimensionnement finisse avec le glissement, jamais après. */
html[data-photo-transition]::view-transition-group(photo-frame){
    animation-duration: var(--sp-duration-base);
    animation-timing-function: var(--sp-easing-standard);
    overflow: hidden;
}

/* Les deux images gardent leurs proportions pendant que le groupe change de taille : sans ça,
   l'ancienne est étirée aux dimensions de la nouvelle et la photo se tord en glissant. */
html[data-photo-transition]::view-transition-old(photo-frame),
html[data-photo-transition]::view-transition-new(photo-frame){
    object-fit: contain;
    height: 100%;
    width: 100%;
}

/* Le reste de la page (achat, onglets, ruban) se contente du fondu, à la durée du système —
   la valeur par défaut du navigateur (250ms) sort de l'échelle du DS. */
html[data-photo-transition]::view-transition-old(root),
html[data-photo-transition]::view-transition-new(root){
    animation-duration: var(--sp-duration-base);
    animation-timing-function: var(--sp-easing-standard);
}

/* La photo garde SON format, entière : ni rognée, ni remplie. Pas d'`object-fit` ici — la carte
   fait exactement sa taille (tronc, « Images » : la seule image du produit qui ne soit pas en
   `cover`). Elle se borne à l'emplacement par ses deux dimensions, sans quoi un portrait le
   déborderait en hauteur. */
.photo-stage img{
    display: block;
    width: auto;
    height: auto;
    max-width: 100cqw;
    max-height: 100cqh;
}

/* MOBILE : l'emplacement ne réserve RIEN (DESIGNAPP, « Vue photo »). La carte prend toute la
   largeur, sa hauteur suit la photo, et ce qui vit dessous se déplace d'une photo à l'autre —
   c'est assumé. Réserver la hauteur du cas portrait laissait, sous une photo paysage, un vide de
   la hauteur d'une main et repoussait l'achat sous la ligne de flottaison.

   Ce bloc vit APRÈS les règles de base, et pas avec les autres surcharges mobiles plus haut :
   à spécificité égale, un média query ne prime pas — c'est l'ordre qui tranche. */
@media (max-width: 767.98px) {
    .photo-frame{
        aspect-ratio: auto;
        /* Plus de hauteur définie, donc plus de conteneur de requête en hauteur : les `cqh` ne
           résoudraient plus rien. La carte se borne à la largeur seule. */
        container-type: inline-size;
    }

    .photo-stage img{
        max-height: none;
    }
}

/* Actions de page et évènement posés sur la photo, sur un scrim DESCENDANT : l'inverse de
   celui des cartes, puisque le texte est ici en haut. */
.photo-stage-bar{
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    padding: var(--sp-space-4);
    text-align: left;
    color: var(--sp-color-text-inverse);
    background: linear-gradient(to bottom, var(--sp-color-overlay-scrim), transparent);
}

/* Indicateur de carousel au BAS de la photo (DESIGNAPP, « Vue photo ») : miroir de la barre du
   haut — scrim MONTANT, texte blanc. Le compteur à gauche, les points au centre absolu. */
.photo-stage-carousel{
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    padding: var(--sp-space-4);
    color: var(--sp-color-text-inverse);
    background: linear-gradient(to top, var(--sp-color-overlay-scrim), transparent);
}

/* Compteur de position : il ne porte QUE la mise en page ; la taille, la graisse, le rayon, le
   verre et le liseré viennent de `.badge.badge-glass` — le même objet que le compteur de photos
   d'une EventCardHighlighted (fiche badge). Le `gap` du badge sert à décoller un glyphe de son
   nombre ; ce compteur n'en a pas, et sans cette remise à zéro il écarterait « 2 » de « /14 ». */
.photo-stage-counter{
    gap: 0;
}

/* Le total accompagne, la position porte : un cran plus discret. La hiérarchie se fait à
   l'opacité, jamais à la graisse — un badge n'a qu'une graisse (fiche badge). */
.photo-stage-counter-total{
    opacity: .7;
}

/* Points centrés dans la barre, quelle que soit la largeur du compteur à gauche. */
.photo-stage-dots{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-2);
}

/* Même pastille que l'indicateur de carrousel de l'EventRail (`.rail-dot`, fiche event-rail) :
   `carousel.dot` (6px), inactif `border.cream`, actif `accent.selected` — un rond qui change de
   couleur, jamais une pilule qui s'allonge. On garde le pattern commun, pas un second style. */
.photo-stage-dot{
    width: var(--sp-carousel-dot);
    height: var(--sp-carousel-dot);
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-border-cream);
    transition: background-color var(--sp-duration-fast) var(--sp-easing-standard);
}

.photo-stage-dot.is-active{
    background-color: var(--sp-color-accent-selected);
}

/* Cadre desktop de la colonne droite (DESIGNAPP, « Vue photo » deux colonnes). */

/* Retour texte vers la galerie : lien ink (pas l'orange, réservé à l'achat), avec chevron. */
.photo-back-gallery{
    color: var(--sp-color-text-default);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
    text-decoration: none;
}

.photo-back-gallery:hover{
    color: var(--sp-color-text-default);
    text-decoration: underline;
}

/* .photo-side-title est un titre de page : sa règle vit avec .page-title, plus haut. */

/* Carte évènement : surface crème surélevée (bg.cream), SportBadge + nom (gras) + méta. */
.photo-event-card{
    padding: var(--sp-space-3) var(--sp-space-4);
    border-radius: var(--sp-radius-lg);
    background-color: var(--sp-color-bg-cream);
    color: var(--sp-color-text-default);
}

.photo-event-card-title{
    font-size: var(--sp-font-size-16);
    font-weight: var(--sp-font-weight-title);
}

.photo-event-card-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Barre d'acquisition, sous le filmstrip (DESIGNAPP, « Vue photo ») : un libellé de bouton ne se
   coupe jamais en deux lignes — un CTA sur deux lignes se lit comme deux actions empilées, et le
   pill l'accuse. Quand les deux CTA ne tiennent plus côte à côte, c'est la BARRE qui passe à la
   ligne, pas le texte : `wrap` + un libellé `nowrap` font que le second bouton descend au moment
   exact où il ne tient plus (fiche button, « une barre d'actions se replie »). */
.photo-actions{
    flex-wrap: wrap;
}

.photo-actions .btn{
    white-space: nowrap;
    /* La cible tactile ne peut plus venir du voisin : `align-items: stretch` n'égalise QUE les
       boutons d'une même ligne, et une fois la barre repliée le bouton seul sur la sienne retombe
       à sa hauteur de texte. Chacun tient donc ses 44px pour lui-même (fiche button). */
    min-height: var(--sp-control-icon-button);
}

/* Dernier recours seulement : sous ~300px, même seul sur sa ligne un libellé peut déborder. On
   l'ellipse alors plutôt que de laisser la page défiler en largeur ; au-dessus de cette laize la
   règle ci-dessus l'a déjà fait descendre, et rien n'est coupé. */
.photo-actions .text-truncate{
    min-width: 0;
}

/* Groupe d'actions sur la photo : les boutons glass voisins partagent UNE pilule de verre
   plutôt que de se répéter en disques isolés (fiche icon-button). Poussé à droite de la barre. */
.photo-stage-actions{
    display: flex;
    align-items: center;
    flex-shrink: 0;
    margin-left: auto;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-glass-button-bg);
    backdrop-filter: blur(var(--sp-glass-blur));
    -webkit-backdrop-filter: blur(var(--sp-glass-blur));
    border: 1px solid var(--sp-glass-button-border);
}

/* Chaque action garde une cible tactile pleine (control.icon-button) */
.photo-stage-action{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--sp-control-icon-button);
    height: var(--sp-control-icon-button);
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    border-radius: var(--sp-radius-full);
    color: var(--sp-color-text-inverse);
}

/* Aucune séparation entre les actions : le groupe se lit comme UN objet, pas comme des
   boutons accolés (fiche icon-button). C'est le conteneur qui porte le verre ; les boutons
   ne portent ni fond, ni bordure, ni filet. */

/* Menu (fiche menu) : surface sombre PLEINE — un libellé d'action doit rester lisible quelle
   que soit la photo dessous — floutée par-dessous, liseré clair atténué. */
.menu-overlay{
    --bs-dropdown-bg: transparent;
    --bs-dropdown-border-width: 0;
    --bs-dropdown-padding-y: var(--sp-space-2);
    --bs-dropdown-min-width: 15rem;
    background-color: var(--sp-color-bg-dark);
    backdrop-filter: blur(var(--sp-glass-blur));
    -webkit-backdrop-filter: blur(var(--sp-glass-blur));
    border: 1px solid var(--sp-glass-overlay-border);
    border-radius: var(--sp-radius-lg);
    box-shadow: var(--sp-shadow-pop);
}

/* Une ligne = une action : glyphe de tête de largeur constante, puis libellé */
.menu-overlay-item{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    width: 100%;
    padding: var(--sp-space-2) var(--sp-space-4);
    border: 0;
    background: none;
    text-align: left;
    text-decoration: none;
    color: var(--sp-color-text-inverse);
}

.menu-overlay-item:hover,
.menu-overlay-item:focus-visible{
    color: var(--sp-color-text-inverse);
    background-color: var(--sp-glass-overlay-bg);
}

.photo-stage-title{
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-title);
    color: var(--sp-color-text-inverse);
}

/* Le texte est ce qui cède quand la barre manque de place — les boutons-icônes, eux, restent
   côte à côte (fiche icon-button). Le titre porte son clamp dans le gabarit ; la date le prend
   ici, sans quoi elle passerait sur deux lignes et grandirait la barre au lieu de se couper. */
.photo-stage-date{
    font-size: var(--sp-font-size-meta);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Photo navigation arrows */
/* Les flèches sont des IconButton variant `glass` : le gabarit porte `btn btn-circle btn-glass`,
   et il ne reste ici que le placement. Elles avaient leur verre à elles — un `rgba(0,0,0,.4)` de
   40px, sans flou ni liseré —, ce que le DS interdit deux fois : « un seul verre pour tous les
   boutons » (fiche icon-button) et la cible tactile de 44px. Sur une photo dont on ne connaît pas
   la valeur, c'est aussi l'opacité 0,6 du verre commun qui garantit que le glyphe blanc reste
   lisible ; à 0,4 il dépendait du cliché. */
.photo-nav-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 10;
}

/* Une commande de carrousel ne se dérobe pas sous le doigt — et surtout, le `scale` de la
   pression remplacerait le `translateY` qui la centre : elle retomberait d'une demi-hauteur au
   moment du clic. Même exception que les chevrons du rail. */
.photo-nav-arrow:not(:disabled):active {
    transform: translateY(-50%);
}

.photo-nav-arrow--prev {
    left: 12px;
}

.photo-nav-arrow--next {
    right: 12px;
}

/* Filmstrip de la vue photo (DESIGNAPP, « Vue photo ») : ruban horizontal des photos voisines,
   la courante mise en avant à l'OPACITÉ seule. Le padding vertical laisse respirer l'anneau de
   FOCUS, le seul qui se dessine encore ici. Vignettes radius.md (fiche card / DESIGN images). */
.photo-filmstrip {
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    /* Ni scroll, ni overflow : le contrôleur ne laisse visibles QUE les vignettes voulues
       (4 mobile / 7 desktop, centrées sur la courante), les autres sont `hidden`. Rien ne clippe,
       donc l'anneau de focus s'affiche en entier ; le ruban tient la largeur du contenu et
       reste aligné avec le reste, jamais collé au bord (DESIGNAPP, « Vue photo »). */
    padding-block: var(--sp-space-2);
}

.photo-filmstrip-item {
    /* Les vignettes VISIBLES se partagent la largeur du contenu ; le carré suit leur largeur. */
    flex: 1 1 0;
    aspect-ratio: 1;
    border-radius: var(--sp-radius-md);
    overflow: hidden;
    opacity: .55;
    transition: opacity var(--sp-duration-fast) var(--sp-easing-standard);
}

.photo-filmstrip-item img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.photo-filmstrip-item:hover {
    opacity: 1;
}

.photo-filmstrip-item:focus-visible {
    opacity: 1;
    outline: 2px solid var(--sp-color-border-focus);
    outline-offset: 2px;
}

/* La courante : pleine opacité, et RIEN d'autre. Pas d'anneau — l'orange cerné dit « coché »
   (PhotoGallery, état selected) ou « focus », jamais « vous êtes ici » (DESIGNAPP, « Vue photo »).
   Ce sont les voisines qui reculent ; la courante ne fait que rester entière. */
.photo-filmstrip-item.is-current {
    opacity: 1;
}

/* Mobile : pas de flèches de navigation — on change de photo au swipe et par le filmstrip
   (DESIGNAPP, « Vue photo »). Les flèches restent sur desktop, où il n'y a pas de swipe. */
@media (max-width: 767.98px) {
    .photo-nav-arrow {
        display: none;
    }
}

/* Swipe hint animation */
.swipe-hint {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 20;
    pointer-events: none;
    animation: swipe-hint-fade 3s ease forwards;
}

.swipe-hint-hand {
    font-size: 2rem;
    animation: swipe-hint-move 1.5s ease-in-out 2;
}

@keyframes swipe-hint-move {
    0% { transform: translateX(30px); opacity: 0.8; }
    50% { transform: translateX(-30px); opacity: 1; }
    100% { transform: translateX(30px); opacity: 0.8; }
}

@keyframes swipe-hint-fade {
    0% { opacity: 1; }
    80% { opacity: 1; }
    100% { opacity: 0; }
}

/* Text truncation */
/* Le dépliement est instantané : une transition vers `max-height: none` ne s'interpole pas.
   Pour l'animer un jour, passer le contenu en grid (0fr → 1fr), ce qui demande un wrapper. */
.text-truncate-content {
    max-height: 4.5em;
    overflow: hidden;
}

.text-truncate-content.text-truncate-expanded {
    max-height: none;
}

/* Live icon */

.live-icon {
    flex-shrink: 0;
}

/* Character counter */

.char-counter {
    font-size: 0.85rem;
    margin-top: 4px;
    display: block;
    text-align: right;
}

/* Image preview */

.image-preview {
    max-height: 200px;
    object-fit: cover;
    border-radius: var(--bs-border-radius);
    margin-top: 0.5rem;
    width: 100%;
}

/* Organizer suggestions */

.organizer-suggestion {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    cursor: pointer;
}

.organizer-suggestion:hover {
    background-color: var(--sp-color-bg-grey);
}

.organizer-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}

.organizer-avatar-placeholder {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background-color: var(--sp-color-border-default);
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    color: var(--sp-color-text-muted);
}

/* Trix editor */

.photographer-layout trix-editor,
.club-layout trix-editor {
    min-height: 150px;
    background-color: var(--sp-color-bg-grey);
    border: 1px solid var(--sp-color-bg-grey);
    border-radius: var(--bs-border-radius);
    padding: 12px;
}

.photographer-layout trix-editor:focus,
.club-layout trix-editor:focus {
    background-color: var(--sp-color-bg-surface);
    border-color: var(--sp-color-border-focus);
    box-shadow: var(--sp-shadow-ring);
}

.photographer-layout trix-toolbar,
.club-layout trix-toolbar {
    background-color: var(--sp-color-bg-surface);
    border: 1px solid var(--sp-color-border-default);
    border-radius: var(--bs-border-radius);
    padding: 4px;
    margin-bottom: 8px;
}

trix-toolbar .trix-button-group {
    margin-bottom: 0;
    border: none;
}

trix-toolbar .trix-button {
    border: none;
    border-radius: 4px;
}

trix-toolbar .trix-button:hover {
    background-color: var(--sp-color-bg-grey);
}

trix-toolbar .trix-button.trix-active {
    background-color: var(--sp-color-border-default);
}

/* Organizer mention box */

.organizer-mention-box {
    max-height: 120px;
    overflow-y: auto;
}

/* --- Facture d'abonnement (page imprimable) ---------------------------------------------------
   Un document, pas un écran : la feuille se cadre à la largeur d'une page et se pose sur le fond
   crème comme n'importe quelle carte de section. À l'impression, seule la feuille reste — la
   barre d'outils et les messages flash n'ont rien à faire sur une facture —, et les cartes
   perdent ombre et fond pour ne pas dévorer l'encre. */
.invoice-sheet{
    max-width: 800px;
    padding-top: var(--sp-space-8);
    padding-bottom: var(--sp-space-8);
}

@media print {
    .invoice-toolbar,
    .invoice-page .position-fixed{
        display: none !important;
    }

    .invoice-page{
        background: var(--sp-color-bg-surface);
    }

    .invoice-sheet{
        max-width: none;
        padding: 0;
    }

    .invoice-sheet .card{
        box-shadow: none;
        border: 1px solid var(--sp-color-border-on-white);
        break-inside: avoid;
    }
}

/* La tuile cochée d'un dépôt prend l'anneau orange (fiche photo-gallery, state `selected`) : la
   sélection se lit sur l'objet pris, pas seulement sur sa case — blanche à filet clair, celle-ci
   se perd sur une photographie. */
.photo-pick:has(.photo-pick-input:checked) .ratio{
    outline: 3px solid var(--sp-color-accent-selected);
    outline-offset: 2px;
}

/* Étape « Album » du dépôt (DESIGNAPP, « Le parcours a cinq étapes nommées ») : une carte par
   journée, dépliée quand c'est elle qu'on choisit. Sélection ink, comme tout le parcours. */
.album-days{
    display: flex;
    flex-direction: column;
    gap: var(--sp-space-3);
}

.album-day{
    border: 1px solid var(--sp-color-border-on-white);
    border-radius: var(--sp-radius-xl);
    padding: var(--sp-space-3) var(--sp-space-4);
}

.album-day.is-open{
    border-color: var(--sp-color-action-primary);
}

.album-day-head{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-space-3);
}

.album-day-label{
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-space-2);
    min-width: 0;
    cursor: pointer;
}

.album-day-name{
    font-weight: var(--sp-font-weight-bold);
}

.album-day-meta{
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-muted);
}

.album-day-controls{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    flex-shrink: 0;
}

.album-day-panel{
    margin-top: var(--sp-space-3);
    padding-top: var(--sp-space-3);
    border-top: 1px solid var(--sp-color-border-on-white);
}

.album-day-hint{
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-muted);
    margin-bottom: var(--sp-space-3);
}

/* La ligne d'un album est une option (fiche radio, « La même anatomie sert les options »), à
   ceci près que son libellé est un élément à part : la ligne peut porter un « Supprimer », qui
   n'aurait rien à faire dans un <label>. */
.album-row .album-row-label{
    cursor: pointer;
}

.album-row-lock{
    display: inline-flex;
    vertical-align: -2px;
    margin-left: var(--sp-space-1);
}

/* Panneau Albums (fiche timeline) : une journée est un groupe qui se replie. Son en-tête
   tient la date en poids de titre et son compte en méta ; le commutateur à droite prend ou
   lâche tous ses albums. Une journée à venir se lit fermée et ses albums s'estompent. */
.album-group + .album-group{
    border-top: 1px solid var(--sp-color-border-default);
    margin-top: var(--sp-space-2);
    padding-top: var(--sp-space-2);
}

.album-group-head{
    display: flex;
    align-items: center;
    gap: var(--sp-space-3);
    min-height: var(--sp-control-icon-button);
}

.album-group-toggle{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    flex: 1;
    min-width: 0;
    padding: var(--sp-space-2) 0;
    border: 0;
    background: none;
    text-align: left;
    color: var(--sp-color-text-default);
}

/* Le set n'a qu'un chevron haut : replié il pointe vers le bas, déplié il se redresse. */
.album-group-chevron{
    display: inline-flex;
    flex-shrink: 0;
    color: var(--sp-color-text-brown);
    transform: rotate(180deg);
    transition: transform .2s ease;
}

.album-group-toggle[aria-expanded="true"] .album-group-chevron{
    transform: none;
}

.album-group-text{
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.album-group-title{
    font-weight: var(--sp-font-weight-title);
}

.album-group-meta,
.filter-row-meta{
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

.filter-row-meta{
    display: block;
}

/* Les albums d'une journée se rangent sous son en-tête, d'un cran ; un sous-album d'un
   cran de plus. */
.album-group-list .filter-list{
    padding-left: var(--sp-space-6);
}

.filter-row--child{
    padding-left: var(--sp-space-4);
}

.album-group--future .album-group-toggle,
.album-group--future .filter-row{
    opacity: .55;
}

.album-delete{
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-decoration: underline;
}

/* Un album qu'on VA créer : le pointillé dit l'attente (DESIGNAPP, « une zone AU REPOS n'est
   pas une zone EN ATTENTE »), et il est orange parce qu'il ouvre le seul geste de l'étape qui
   crée quelque chose. */
.album-create{
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-space-2);
    width: 100%;
    margin-top: var(--sp-space-4);
    padding: var(--sp-space-3);
    border: 1px dashed var(--sp-color-accent-selected);
    border-radius: var(--sp-radius-lg);
    background: transparent;
    color: var(--sp-color-accent-selected);
    font-size: var(--sp-font-size-label);
    font-weight: var(--sp-font-weight-bold);
}

.album-create:hover,
.album-create:focus-visible{
    background-color: var(--sp-color-bg-cream);
}

/* Un album protégé, dans la galerie : la demande de mot de passe prend la place de la frise. */
.album-unlock-title{
    display: flex;
    align-items: center;
    gap: var(--sp-space-2);
    font-weight: var(--sp-font-weight-bold);
}

.album-unlock-form{
    display: flex;
    gap: var(--sp-space-2);
    max-width: 420px;
}

/* Ce qui borne le dépôt, dans la colonne gauche : les photos déjà en ligne contre le plafond.
   Jauge Progress variant default (fiche progress) : piste pêche, remplissage orange. */
.quota-progress{
    height: 8px;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-soft);
}

.quota-progress .progress-bar{
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-selected);
}

.quota-figures{
    display: flex;
    justify-content: space-between;
    gap: var(--sp-space-3);
    margin-top: var(--sp-space-2);
    font-size: var(--sp-font-size-label);
    color: var(--sp-color-text-muted);
}

.quota-figures strong{
    color: var(--sp-color-text-default);
}

/* Étape « Dossards » : le badge qui recommande, avec son étoile. */
.bib-recommended{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-1);
}

.bib-fee{
    font-size: var(--sp-font-size-body);
}

/* Étape « Visibilité » de la création : une option réservée se voit, désactivée, avec sa mention. */
.visibility-row--locked .option-row-label{
    opacity: .55;
}

.visibility-pro{
    display: inline-flex;
    align-items: center;
    gap: var(--sp-space-1);
    margin-left: var(--sp-space-2);
    padding: 2px var(--sp-space-2);
    border: 1px solid var(--sp-color-border-on-white);
    border-radius: var(--sp-radius-full);
    font-size: var(--sp-font-size-meta);
    color: var(--sp-color-text-muted);
}

/* Vue photo : le temps restant en ligne, jauge Progress variant default (fiche progress) —
   piste pêche, remplissage orange, 8px, sous le titre de sa ligne d'information. */
.photo-lifetime{
    height: 8px;
    margin-top: var(--sp-space-2);
    max-width: 320px;
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-soft);
}

.photo-lifetime .progress-bar{
    border-radius: var(--sp-radius-full);
    background-color: var(--sp-color-accent-selected);
}
