/* ===========================================================================
   Thème « Festif » — module Festivités (Festifun)
   - Back-office (body.is-festivites)  : festif clair, ludique.
   - Vitrine publique (body.festif-public) : landing page audacieuse, fond encre
     + accents néon, cartes claires pour la lisibilité.
   Scopé aux pages festivités. Spécificité élevée plutôt que !important.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   1) BACK-OFFICE — thème festif clair
   --------------------------------------------------------------------------- */
body.is-festivites {
    /* Voile des modales : même jeton que le reste du site (`--modal-scrim` de
       styles.css), teinte violette du module. ⚠️ On redéfinit la COULEUR, pas
       la règle — un portail qui écrirait son propre fond de voile serait le
       clone que le jeton vient justement de supprimer. */
    --modal-scrim: rgba(18, 8, 32, 0.82);
    --fest-pink: #ff4d8d;
    --fest-orange: #ff8a3d;
    --fest-yellow: #ffd23f;
    --fest-ink: #2a1640;
    --fest-grad: linear-gradient(120deg, var(--fest-pink), var(--fest-orange) 55%, var(--fest-yellow));
    /* Statuts du back-office : « payé / en retard / à surveiller / météo ».
       Nommés ici parce qu'ils se rejouent en nuit festive (section 1c) avec des
       teintes remontées — écrits en dur, chaque écran aurait fallu être repris
       une règle à la fois, et le prochain naîtrait de nouveau en couleur fixe.
       ⚠️ Ce ne sont PAS `--success`/`--error` du socle : ceux-là sont des FONDS
       pastel (`#d9f0e5`, `#ffd9d9`), ceux-ci des couleurs de texte. */
    --fest-ok: #0f9d6b;
    --fest-bad: #dc2626;
    --fest-warn: #b45309;
    --fest-info: #0284c7;
    --fest-info-strong: #0369a1;
    --fest-alt: #1d4ed8;
    /* Surfaces OPAQUES : une modale, une pastille cliquable ou une tuile ne
       peuvent pas être translucides comme `--bg-elevated` (on lirait au travers
       ce qu'elles recouvrent). D'où deux jetons à part, repris en nuit. */
    --fest-panel: #ffffff;
    --fest-panel-soft: #f4f4f8;
    /* Voile discret des séparateurs, survols et cases de calendrier : ce sont
       des dizaines de `rgba(0,0,0,.0X)` dont SEULES les composantes changent la
       nuit (l'opacité, elle, est réglée règle par règle et reste telle quelle).
       ⚠️ Toujours écrit avec son repli `rgba(var(--fest-veil, 0, 0, 0), …)` :
       la vitrine publique lit les mêmes règles sans définir ce jeton, et une
       custom property absente rendrait la déclaration invalide — bordure
       disparue, sans un mot. */
    --fest-veil: 0, 0, 0;
    /* ⚠️ `--surface-2` n'est défini NULLE PART dans le dépôt : ses 25 lecteurs
       (livre de recettes, éditeur de menus…) tombaient tous sur leur repli en
       dur — un gris clair, donc des pavés blancs sur l'encre. Défini ici et
       pas dans `styles.css` : le dojo et les compétitions le lisent aussi, et
       leur rendu actuel n'a pas à changer pour un mode nuit de Festifun. */
    --surface-2: var(--fest-panel-soft);
    background:
        radial-gradient(900px 500px at 8% -10%, rgba(255,77,141,0.16), transparent 60%),
        radial-gradient(800px 480px at 100% 0%, rgba(56,189,248,0.16), transparent 55%);
}
body.is-festivites .site-header {
    background: var(--fest-grad);
    border: 0;
    box-shadow: 0 6px 24px rgba(255,90,140,0.28);
}
body.is-festivites .site-header .brand-title,
body.is-festivites .site-header .brand small { color: #fff; }
/* Les nav-link utilisent ::before pour le texte (système d'animation) :
   on passe le blanc dans les custom properties plutôt que sur color,
   pour éviter la double couche de texte visible. */
body.is-festivites .site-header .main-nav .nav-link {
    color: transparent;
    --nav-link-base-color: #fff;
    --nav-link-hover-color: rgba(255,255,255,0.82);
    /* Fond de survol : verre clair, jamais le lavis d'accent du site — la barre
       festive vit sur un dégradé rose où un accent bleu ne se voit pas. */
    --nav-link-hover-bg: rgba(255,255,255,0.16);
}
/* Les items des panels dropdown ont un fond clair : restaurer les couleurs sombres par défaut. */
body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link {
    --nav-link-base-color: color-mix(in srgb, var(--accent-strong) 86%, var(--text) 14%);
    --nav-link-hover-color: var(--accent);
    --nav-link-hover-bg: color-mix(in srgb, var(--accent) 12%, transparent);
}
body.is-festivites .site-header .brand-mark {
    background: rgba(255,255,255,0.22);
    border-radius: 14px; width: 42px; height: 42px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.4rem; animation: fest-pop 3.5s ease-in-out infinite;
}
body.is-festivites .site-header .nav-link.is-active { background: rgba(255,255,255,0.22); border-radius: 999px; }
/* Glider du nav-indicator : style vitré assorti aux pills du thème festif */
body.is-festivites .site-header .main-nav .calendar-member-filter-indicator {
    background: rgba(255,255,255,0.22);
    border-color: rgba(255,255,255,0.45);
}
/* Texte de l'item actif lisible sur le pill blanc : utilise la couleur ink (texte blanc déjà OK sur gradient) */
body.is-festivites .site-header .main-nav .nav-link.is-active::before,
body.is-festivites .site-header .main-nav .nav-link.is-button-indicator-pending::before {
    color: #fff;
}
/* Item actif DANS un panneau déroulant (fond clair) : pastille festive pleine.
   Sans ça, le fond blanc translucide (.is-active ci-dessus, pensé pour la barre
   sur dégradé) + texte blanc rend l'item invisible (blanc sur blanc). */
body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link.is-active {
    background: var(--fest-grad, #ff4d8d);
    border-color: transparent;
    border-radius: 10px;
}

/* ── Panneaux déroulants de la navigation (bureau) ─────────────────────────
   ⚠️ **Le panneau doit être OPAQUE, et il ne peut pas l'être par héritage.**
   Le socle le peint avec `--bg-elevated-strong`, jeton qui vaut ≥ 95 %
   d'opacité dans les dix thèmes du dojo — mais la nuit festive le redéfinit en
   verre (`rgba(255,255,255,0.11)`), parce que ses cartes sont posées SUR la
   page et doivent la laisser transparaître. Un panneau flottant, lui, n'a rien
   sous lui : à 11 %, on lisait les tuiles du tableau de bord au travers des
   libellés du menu. D'où `--fest-panel`, le jeton déclaré pour exactement ça
   (« surfaces opaques : une modale, une pastille, une tuile »), jamais un jeton
   de carte.

   ⚠️ **Aligné sur le bord GAUCHE de sa section**, là où le socle l'accroche à
   droite (`right: 0`). Ce n'est pas un goût : le socle CENTRE sa navigation,
   donc un panneau qui s'ouvre vers la gauche y reste sous son parent. Ici la
   barre est alignée à gauche depuis que la navigation a sa propre rangée — le
   panneau partait donc à gauche de son propre libellé, souvent sous la section
   précédente. Seule la DERNIÈRE section garde l'ancrage à droite (voir plus
   bas) : c'est la seule qui puisse atteindre le bord de l'écran.

   ⚠️ Sous 961 px, le sous-menu n'est plus un panneau flottant mais un
   accordéon dans le menu déroulé (section 1b) : ces règles ne valent donc que
   là où il flotte, sinon elles repeindraient l'encart vitré du menu mobile. */
@media (min-width: 961px) {
    body.is-festivites .site-header .main-nav .nav-submenu-panel {
        left: 0;
        right: auto;
        transform-origin: top left;
        background: var(--fest-panel, #ffffff);
        border-color: color-mix(in srgb, var(--fest-ink, #2a1640) 14%, transparent);
        box-shadow: 0 18px 42px rgba(42, 22, 64, 0.22);
        padding: 0.3rem;
    }

    /* Un peu plus d'air que le socle : la section ouverte reste rattachée à sa
       pastille (0.35rem), mais les entrées se lisent en liste, pas en pavé. */
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link {
        padding: 0.5rem 0.7rem;
        border-radius: 10px;
    }

    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link:hover {
        background: color-mix(in srgb, var(--fest-pink, #ff4d8d) 12%, transparent);
    }

    /* ⚠️ La DERNIÈRE section garde l'ancrage à droite du socle : c'est elle,
       et elle seule, qui peut sortir de l'écran. Mesuré : à 961 px (la première
       largeur où la barre est encore déployée), le panneau de « Réglages »
       finissait 21 px au-delà du bord droit — donc une barre de défilement
       horizontale sur toute la page. Bornée à une largeur d'écran, la règle
       serait fausse au premier renommage de section (des libellés plus longs
       repoussent le dernier onglet vers la droite) ; l'ancrage à droite du
       dernier onglet, lui, reste vrai quels que soient les libellés — et c'est
       le sens d'ouverture attendu d'un menu de bout de barre.
       `:last-of-type` suit le rôle : sans les droits de direction, « Réglages »
       et le menu « Catalogue » n'existent pas, et c'est « Approvisionnement »
       qui devient le dernier `<details>` — donc le bon. */
    body.is-festivites .site-header .main-nav .nav-submenu:last-of-type .nav-submenu-panel {
        left: auto;
        right: 0;
        transform-origin: top right;
    }
}

/* La nuit, l'ombre portée ne détache plus rien sur l'encre : c'est le liseré
   qui fait le bord, comme pour les cartes (section 1c). */
html[data-theme="dark"] body.is-festivites .site-header .main-nav .nav-submenu-panel {
    border-color: rgba(255, 255, 255, 0.16);
    box-shadow: 0 22px 48px rgba(4, 2, 14, 0.62);
}

/* ── Menu du compte (panneau du socle) ─────────────────────────────────────
   ⚠️ **Même piège que les panneaux de navigation ci-dessus, et il fallait le
   corriger ici AUSSI** : le socle peint `.profile-menu-panel` avec
   `--bg-elevated-strong`, jeton opaque dans les dix thèmes du dojo mais que la
   nuit festive redéfinit en verre (11 %). Le menu flotte au-dessus de la page,
   il n'a donc rien sous lui pour porter cette transparence : on lisait le texte
   de la page **à travers** ses entrées (« Compétitions » superposé au sous-titre
   des recettes). Le jeton `--fest-panel` est celui déclaré pour les surfaces
   opaques ; il vaut `#ffffff` en clair, donc la règle ne change rien de jour.

   ⚠️ Le socle passe par `color-mix(… 94%, transparent)`, un voile assumé sur
   une base déjà opaque : recopié sur une base translucide il empile deux
   transparences. On peint donc franc. */
body.is-festivites .profile-menu-panel {
    background: var(--fest-panel, #ffffff);
}

html[data-theme="dark"] body.is-festivites .profile-menu-panel {
    border-color: rgba(255, 255, 255, 0.16);
    box-shadow: 0 22px 48px rgba(4, 2, 14, 0.62);
}
/* Hamburger sur le dégradé : barres blanches, encadré vitré assorti aux pills. */
body.is-festivites .nav-toggle {
    color: #fff;
    border-color: rgba(255,255,255,0.45);
    background: rgba(255,255,255,0.15);
}
body.is-festivites .nav-toggle:hover { background: rgba(255,255,255,0.28); border-color: rgba(255,255,255,0.7); }
body.is-festivites .page-title {
    background: var(--fest-grad);
    -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Cette carte n'a pas le rayon générique de l'admin (24px). Il est déclaré dans
   `--handle-corner`, la variable que la poignée partagée lit pour ses coins
   gauches : le rayon de la carte et celui de la poignée ne peuvent donc plus
   diverger — avant, la seconde valeur était recopiée à la main juste en dessous. */
body.is-festivites .content-card { --handle-corner: 20px; border-radius: var(--handle-corner); box-shadow: 0 12px 30px rgba(42,22,64,0.08); }
body.is-festivites .button.primary {
    background: var(--fest-grad); border: 0; border-radius: 999px; color: #fff; font-weight: 700;
    box-shadow: 0 8px 18px rgba(255,90,140,0.30); transition: transform .15s ease, box-shadow .15s ease;
}
body.is-festivites .button.primary:hover { transform: translateY(-2px) scale(1.02); }
body.is-festivites .button.secondary { border-radius: 999px; border: 2px solid var(--fest-orange); font-weight: 600; }

/* ---------------------------------------------------------------------------
   1b) MENU MOBILE (back-office festif)
   Le nav s'ouvre sous l'en-tête, par-dessus le dégradé rose→orange. Objectifs :
   - lisibilité : pastilles vitrées assez contrastées + libellés blancs ombrés ;
   - item courant clairement repérable (pastille blanche pleine, texte rose) ;
   - sous-menus en accordéon (un seul ouvert) présentés en UNE colonne avec un
     rail d'imbrication, pour que la hiérarchie reste claire même à plusieurs.
   Sélecteurs préfixés body.is-festivites .site-header .main-nav pour l'emporter
   sur les règles mobiles globales (styles.css) et le <style> inline de base.html.

   ⚠️ **960 px, jamais 768 :** c'est le socle (styles.css) qui décide quand la
   navigation se replie derrière le hamburger, et il tranche à 960 — `menu.js`
   lui-même lit le `display` calculé du bouton plutôt qu'une largeur écrite en
   dur. Avec 768 ici, la tranche 769–960 px (un iPad en portrait) recevait la
   MISE EN COLONNE du socle sans l'habillage festif : pastilles jaune pâle
   illisibles sur le dégradé, empilées à droite sur 330 px de haut. Un seul
   seuil pour les deux feuilles, ou le trou revient.
   --------------------------------------------------------------------------- */
@media (max-width: 960px) {
    /* Le socle colle le nom à la pastille (`.brand { gap: 0 }`) parce qu'il
       MASQUE la pastille à cette largeur ; Festifun la garde jusqu'au téléphone
       (c'est sa signature), il lui faut donc son écart — sans quoi le confetti
       et le « F » de Festifun se touchent. */
    body.is-festivites .site-header .brand {
        gap: 0.6rem;
    }

    /* Conteneur : séparateur clair sous la marque + marge basse sûre (iPhone) */
    body.is-festivites .site-header .main-nav {
        gap: 0.5rem;
        padding: 0.7rem 0 0.6rem;
        border-top: 1px solid rgba(255, 255, 255, 0.28);
    }
    @supports (-webkit-touch-callout: none) {
        body.is-festivites .site-header .main-nav.is-open {
            padding-bottom: max(0.6rem, env(safe-area-inset-bottom));
        }
    }

    /* Pastilles de 1er niveau : verre translucide clair, lisible sur le dégradé */
    body.is-festivites .site-header .main-nav .nav-link,
    body.is-festivites .site-header .main-nav .nav-submenu-toggle {
        background: rgba(255, 255, 255, 0.16);
        border: 1px solid rgba(255, 255, 255, 0.32);
        border-radius: 14px;
        min-height: 50px;
        padding: 0.72rem 0.95rem;
        font-size: 1rem;
        font-weight: 600;
        -webkit-backdrop-filter: blur(6px);
        backdrop-filter: blur(6px);
        transition: background 160ms ease, border-color 160ms ease, transform 120ms ease;
    }
    /* De la place à droite du toggle pour le chevron (positionné en absolu) */
    body.is-festivites .site-header .main-nav .nav-submenu-toggle {
        padding-right: 2.4rem;
    }
    /* Le libellé est dessiné par ::before — ombre douce pour le détacher du fond */
    body.is-festivites .site-header .main-nav .nav-link::before {
        text-shadow: 0 1px 2px rgba(80, 18, 55, 0.30);
    }
    body.is-festivites .site-header .main-nav .nav-link:hover,
    body.is-festivites .site-header .main-nav .nav-submenu-toggle:hover {
        background: rgba(255, 255, 255, 0.24);
        border-color: rgba(255, 255, 255, 0.44);
    }
    body.is-festivites .site-header .main-nav .nav-link:active,
    body.is-festivites .site-header .main-nav .nav-submenu-toggle:active {
        transform: scale(0.985);
    }
    /* Focus clavier : anneau blanc net (au lieu du liseré bleu/or hérité) */
    body.is-festivites .site-header .main-nav .nav-link:focus-visible,
    body.is-festivites .site-header .main-nav .nav-submenu-toggle:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.92);
        outline-offset: 2px;
        border-color: rgba(255, 255, 255, 0.6);
    }

    /* Parent d'un sous-menu ouvert : mis en évidence (repère quand plusieurs) */
    body.is-festivites .site-header .main-nav .nav-submenu[open] .nav-submenu-toggle {
        background: rgba(255, 255, 255, 0.28);
        border-color: rgba(255, 255, 255, 0.5);
    }

    /* Item actif (page courante) : pastille blanche pleine, libellé rose vif.
       Le second sélecteur garde le blanc même quand la section active est ouverte
       (sinon la règle [open] ci-dessus l'emporterait → texte rose peu lisible). */
    body.is-festivites .site-header .main-nav .nav-link.is-active,
    body.is-festivites .site-header .main-nav .nav-submenu-toggle.is-active,
    body.is-festivites .site-header .main-nav .nav-submenu[open] .nav-submenu-toggle.is-active {
        background: #fff;
        border-color: #fff;
        box-shadow: 0 6px 16px rgba(120, 20, 60, 0.20);
    }
    body.is-festivites .site-header .main-nav .nav-link.is-active::before,
    body.is-festivites .site-header .main-nav .nav-submenu-toggle.is-active::before {
        color: var(--fest-pink, #ff4d8d);
        text-shadow: none;
    }

    /* Chevron : blanc et un peu plus lisible ; rose quand la section est active */
    body.is-festivites .site-header .main-nav .nav-submenu-caret {
        color: #fff;
        font-size: 0.9em;
    }
    body.is-festivites .site-header .main-nav .nav-submenu-toggle.is-active .nav-submenu-caret {
        color: var(--fest-pink, #ff4d8d);
    }

    /* Panneau de sous-menu : UNE colonne, encart vitré avec rail de gauche pour
       matérialiser l'imbrication (annule la grille 2 colonnes globale) */
    body.is-festivites .site-header .main-nav .nav-submenu-panel {
        grid-template-columns: 1fr;
        gap: 0.28rem;
        margin: 0.35rem 0 0.1rem 0.55rem;
        padding: 0.4rem 0.4rem 0.4rem 0.55rem;
        border-left: 2px solid rgba(255, 255, 255, 0.42);
        border-radius: 0 12px 12px 0;
        background: rgba(255, 255, 255, 0.12);
        -webkit-backdrop-filter: blur(4px);
        backdrop-filter: blur(4px);
    }
    /* Items du sous-menu : texte blanc sur verre, cohérent avec les parents */
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link {
        --nav-link-base-color: #fff;
        --nav-link-hover-color: #fff;
        background: transparent;
        border-color: transparent;
        border-radius: 10px;
        min-height: 46px;
        padding: 0.6rem 0.8rem;
        font-size: 0.95rem;
        font-weight: 500;
    }
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link::before {
        text-shadow: 0 1px 2px rgba(80, 18, 55, 0.28);
    }
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link:hover,
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link:active {
        background: rgba(255, 255, 255, 0.2);
    }
    /* Item de sous-menu actif : pastille blanche pleine + libellé rose */
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link.is-active {
        background: #fff;
        border-color: #fff;
    }
    body.is-festivites .site-header .main-nav .nav-submenu-panel .nav-link.is-active::before {
        color: var(--fest-pink, #ff4d8d);
        text-shadow: none;
    }

    /* Apparition douce du panneau (progressif : sans effet si non pris en charge) */
    body.is-festivites .site-header .main-nav .nav-submenu[open] .nav-submenu-panel {
        animation: fest-submenu-in 180ms ease-out;
    }
}
@keyframes fest-submenu-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    body.is-festivites .site-header .main-nav .nav-submenu[open] .nav-submenu-panel {
        animation: none;
    }
}

/* ---------------------------------------------------------------------------
   1c) BACK-OFFICE — NUIT FESTIVE (mode sombre)

   ⚠️ LA PALETTE EST POSÉE SUR `html`, PAS SUR `body`. Les jetons dérivés
   (`--surface: var(--bg-elevated)`, `--line-strong`, `--ring`, `--text-soft`,
   `--danger-strong`… une vingtaine) sont déclarés dans `:root` de `styles.css`
   et se calculent SUR CET ÉLÉMENT-LÀ : une valeur posée sur `body` ne les
   atteindrait pas, et il faudrait recopier les vingt formules ici — c'est
   exactement ce qu'a dû faire la vitrine publique (section 2), qui n'a pas le
   choix puisqu'elle repeint une page sous un thème de dojo quelconque. Ici on
   surcharge la SOURCE (`--bg`, `--bg-elevated`, `--text`, `--muted`, `--line`,
   `--accent`) et tout le reste suit, y compris ce qui sera ajouté demain.
   D'où `:has(body.is-festivites)` : le seul moyen de viser `html` en fonction
   du portail affiché.

   ⚠️ La nuit de Festifun n'est PAS le sombre du dojo : encre violette + néon
   rose/cyan, la palette déjà dessinée pour la vitrine. Un gris-bleu neutre sous
   un en-tête rose bonbon donnerait deux marques dans la même page.

   ⚠️ L'accent passe au rose en nuit alors qu'il reste le bleu du dojo en clair
   (le back-office festif n'a jamais surchargé `--accent`) : sur l'encre, le
   bleu #2f72dc s'éteint et se bat avec le dégradé de l'en-tête. Écart assumé,
   limité au mode nuit — repeindre l'accent du thème clair serait un autre
   changement, non demandé.
   --------------------------------------------------------------------------- */
html[data-theme="dark"]:has(body.is-festivites) {
    /* Sources du système de jetons — tout le reste en découle. */
    --bg: #0c0820;
    --bg-elevated: rgba(255, 255, 255, 0.062);
    --bg-elevated-strong: rgba(255, 255, 255, 0.11);
    --text: #ece9fb;
    --on-elevated: #ece9fb;
    --on-accent: #1a0a16;
    --muted: #b7afdc;
    --line: rgba(255, 255, 255, 0.15);
    --accent: #ff69a9;
    --accent-strong: #ffa3c8;
    --accent-soft: rgba(255, 46, 136, 0.20);
    --card-highlight: linear-gradient(140deg, rgba(255, 46, 136, 0.34), rgba(168, 85, 247, 0.28), rgba(34, 211, 238, 0.18));
    --shadow-soft: 0 30px 90px rgba(4, 2, 14, 0.66);
    --logo-glow-color: rgba(255, 105, 169, 0.5);
    --calendar-weekend-bg: rgba(168, 85, 247, 0.16);
    --calendar-weekend-border: rgba(196, 152, 255, 0.38);
}

/* ⚠️ Les jetons `--fest-*`, eux, se surchargent sur `body` et NON sur `html` :
   ils sont déclarés par `body.is-festivites` (section 1), qui l'emporterait
   sur toute valeur héritée de `html` — l'en-tête garderait son dégradé de jour.
   Deux familles, deux ancrages : ce qui nourrit `:root` en haut, ce que le
   thème festif déclare lui-même ici. */
html[data-theme="dark"] body.is-festivites {
    /* Marque, version néon (le rose bonbon du jour s'éteint sur l'encre). */
    --fest-pink: #ff2e88;
    --fest-orange: #ff9f4d;
    --fest-yellow: #ffe14d;
    --fest-purple: #a855f7;
    --fest-cyan: #22d3ee;
    /* ⚠️ `--fest-ink` est une couleur de TEXTE (jamais un fond) : elle s'inverse. */
    --fest-ink: #ece9fb;
    --fest-grad: linear-gradient(120deg, #ff2e88, #a855f7 52%, #22d3ee);

    /* Statuts du back-office : mêmes rôles, teintes remontées pour l'encre. */
    --fest-ok: #5fd39a;
    --fest-bad: #ff8f80;
    --fest-warn: #fbbf24;
    --fest-info: #67e8f9;
    --fest-info-strong: #a5f3fc;
    --fest-alt: #93c5fd;
    --fest-panel: #181233;
    --fest-panel-soft: #15102e;
    --fest-veil: 255, 255, 255;
    --surface-2: var(--fest-panel-soft);

    /* Fond de page : mêmes halos que le jour, posés sur l'encre (la règle du
       thème clair n'a pas de couleur de base — elle laisse voir `html`). */
    background:
        radial-gradient(900px 520px at 8% -10%, rgba(255, 46, 136, 0.22), transparent 60%),
        radial-gradient(820px 500px at 100% 0%, rgba(34, 211, 238, 0.16), transparent 55%),
        radial-gradient(760px 640px at 50% 118%, rgba(168, 85, 247, 0.20), transparent 60%),
        var(--bg);
}

/* En-tête : le dégradé reste la signature de Festifun, mais en néon et posé sur
   une base encre — le rose pastel du jour éclaire trop une pièce sombre. */
html[data-theme="dark"] body.is-festivites .site-header {
    box-shadow: 0 6px 28px rgba(4, 2, 14, 0.6);
}

/* Cartes : l'ombre portée claire disparaît sur fond sombre, c'est le liseré qui
   détache la carte (même parti que la vitrine). */
html[data-theme="dark"] body.is-festivites .content-card {
    border: 1px solid var(--line);
    box-shadow: 0 14px 34px rgba(4, 2, 14, 0.45);
}

/* Le titre de page est peint par le dégradé (background-clip: text) : la version
   néon suffit, rien à changer ici — noté pour qu'on ne le cherche pas. */

/* Dans le back-office, la rangée est posée sur le dégradé rose de l'en-tête :
   sa peau blanche par défaut convient, seul le segment sélectionné se repeint
   (le défaut du composant est `--accent`, qui vaut le bleu du dojo en clair). */
body.is-festivites .site-header .theme-switch {
    --theme-switch-selected: var(--fest-pink, #ff4d8d);
}

/* Dans la PWA, la rangée est posée sur l'en-tête « verre » du terrain — clair
   le jour, encre la nuit — et non sur le dégradé rose du back-office : elle
   emprunte donc la palette locale de l'app (`--ter-*`). Sans ça, les libellés
   blancs disparaissaient sur le verre rose pâle du mode clair. */
.ter-header .theme-switch {
    margin-left: auto;
    --theme-switch-track: var(--ter-switch-track);
    --theme-switch-border: var(--ter-line);
    --theme-switch-ink: var(--ter-seg-ink);
    --theme-switch-ink-hover: var(--ter-title);
    --theme-switch-glider: var(--ter-switch-glider);
    --theme-switch-selected: var(--ter-title);
    --theme-switch-ring: var(--ter-accent);
}

/* ---------------------------------------------------------------------------
   2) VITRINE PUBLIQUE — landing page audacieuse (fond encre + néon)
   --------------------------------------------------------------------------- */
/* ⚠️ JETONS DE THÈME DE LA VITRINE — lire avant d'ajouter un formulaire ici.
   La vitrine a un rendu FIXE (page encre, cartes claires) écrit en couleurs en
   dur, mais `<html data-theme>` porte le thème choisi par le DOJO (clair, sombre,
   « paper-ink-dark »…) et les composants PARTAGÉS, eux, lisent les jetons :
   `.form-control` prend `--bg-elevated`/`--text`, les cases et boutons radio
   `--line`/`--accent`, `.ui-toggle` de même, la boîte du contrat `--surface`.
   Sous un thème de dojo sombre, le formulaire de réservation sortait donc en
   champs noirs à texte pâle DANS une carte blanche, et le contrat en texte foncé
   sur fond foncé — illisible (thème du dojo ybgaspesie : « paper-ink-dark »).
   Les jetons sont donc posés DEUX fois : valeurs sombres sur la page, valeurs
   claires sur les cartes (plus bas). La vitrine cesse ainsi de dépendre du thème
   du dojo, et le prochain composant partagé s'accorde sans qu'on ait à le
   rhabiller une règle à la fois — c'est déjà ce qu'avait dû faire le calendrier
   de réservation (`catalog_calendar.css`, flèches déduites de `currentColor`). */
body.festif-public {
    /* Voile des modales : même jeton que le reste du site (`--modal-scrim` de
       styles.css), teinte violette du module. ⚠️ On redéfinit la COULEUR, pas
       la règle — un portail qui écrirait son propre fond de voile serait le
       clone que le jeton vient justement de supprimer. */
    --modal-scrim: rgba(18, 8, 32, 0.82);
    --neon-pink: #ff2e88;
    --neon-cyan: #22d3ee;
    --neon-yellow: #ffe14d;
    --neon-purple: #a855f7;
    --ink: #0c0820;
    --ink-2: #160f30;
    --grad-hot: linear-gradient(110deg, var(--neon-pink), var(--neon-purple) 50%, var(--neon-cyan));
    --grad-warm: linear-gradient(110deg, var(--neon-pink), var(--neon-yellow));
    /* Jetons « page sombre » : ce qui vit à même le fond encre. */
    --bg: var(--ink);
    --bg-elevated: rgba(255, 255, 255, 0.07);
    --bg-elevated-strong: rgba(255, 255, 255, 0.12);
    --surface: rgba(255, 255, 255, 0.07);
    --text: #ece9fb;
    --on-elevated: #ece9fb;
    --muted: #c2bce0;
    --text-muted: #c2bce0;
    --line: rgba(255, 255, 255, 0.18);
    --line-strong: rgba(255, 255, 255, 0.34);
    --border: rgba(255, 255, 255, 0.18);
    --border-color: rgba(255, 255, 255, 0.18);
    --accent: var(--neon-pink);
    --accent-strong: #ff69a9;
    --accent-soft: rgba(255, 46, 136, 0.22);
    --ring: rgba(255, 46, 136, 0.4);
    --field-required: #ff8f80;
    --field-error: #ff8f80;
    --field-error-surface: rgba(255, 143, 128, 0.16);
    --field-error-ring: rgba(255, 143, 128, 0.28);
    --danger: #ff8f80;
    color: #ece9fb;
    background:
        radial-gradient(1000px 600px at 12% -8%, rgba(255,46,136,0.22), transparent 60%),
        radial-gradient(900px 600px at 100% 0%, rgba(34,211,238,0.18), transparent 55%),
        radial-gradient(800px 700px at 50% 120%, rgba(168,85,247,0.20), transparent 60%),
        var(--ink);
}
body.festif-public .background-orb { display: none; }

/* En-tête : barre vitrée sombre, collante */
body.festif-public .site-header {
    position: sticky; top: 0; z-index: 50;
    background: rgba(12,8,32,0.72);
    -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
    border-bottom: 1px solid rgba(255,255,255,0.08);
}
/* Bandeau dev/local : décale l'en-tête collant sous la bande (comme le site principal).
   `--dojo-banners-height` est mesurée par js/sticky_offsets.js. */
body.festif-public.has-dev-banner .site-header { top: var(--dojo-banners-height, calc(1.32rem + 2px)); }
body.festif-public .site-header .brand-title { color: #fff; font-weight: 800; }
body.festif-public .site-header .brand small { color: var(--neon-cyan); }
body.festif-public .site-header .brand-mark {
    background: var(--grad-hot); color: #fff;
    border-radius: 14px; width: 42px; height: 42px;
    display: inline-flex; align-items: center; justify-content: center; font-size: 1.4rem;
    box-shadow: 0 0 22px rgba(255,46,136,0.55); animation: fest-pop 3.5s ease-in-out infinite;
}
/* Bouton d'appel du haut de page. L'allure est un choix fermé de l'admin
   (`festivites_public.HEADER_CTA_STYLES`) : chaque variante ne fait que changer
   la couleur du thème utilisée, jamais le dessin du bouton.
   ⚠️ La lueur suit la couleur choisie (`color-mix`) : elle était figée sur le
   jaune d'origine, donc un Festifun qui repeignait son thème gardait un halo
   jaune sous un bouton rose. La déclaration `rgba()` qui précède reste le repli
   des navigateurs sans `color-mix`. */
body.festif-public .header-cta {
    --cta-color: var(--neon-yellow);
    --cta-ink: #0c0820;
    margin-left: auto;
    padding: 0.55rem 1.1rem; border-radius: 999px; font-weight: 700; text-decoration: none;
    color: var(--cta-ink); background: var(--cta-color);
    box-shadow: 0 0 20px rgba(255,225,77,0.45);
    box-shadow: 0 0 20px color-mix(in srgb, var(--cta-color) 45%, transparent);
    transition: transform .15s ease;
}
body.festif-public .header-cta:hover { transform: translateY(-2px) scale(1.03); }
body.festif-public .header-cta--primaire { --cta-color: var(--neon-pink); --cta-ink: #fff; }
body.festif-public .header-cta--secondaire { --cta-color: var(--neon-cyan); --cta-ink: #05202b; }
/* Contour : la bordure remplace le fond, d'où le retrait de son épaisseur sur le
   remplissage — sans quoi ce bouton serait plus haut que les autres. */
body.festif-public .header-cta--contour {
    --cta-color: transparent; --cta-ink: #fff;
    border: 2px solid rgba(255,255,255,0.45);
    padding: calc(0.55rem - 2px) calc(1.1rem - 2px);
    box-shadow: none;
}
body.festif-public .header-cta--contour:hover { border-color: var(--neon-cyan); }

/* Boutons */
body.festif-public .button.primary {
    background: var(--grad-hot); border: 0; border-radius: 999px; color: #fff; font-weight: 800;
    box-shadow: 0 0 26px rgba(255,46,136,0.45); transition: transform .15s ease, box-shadow .15s ease, filter .15s ease;
}
body.festif-public .button.primary:hover { transform: translateY(-2px) scale(1.03); filter: saturate(1.1); }
body.festif-public .button.secondary {
    border-radius: 999px; border: 2px solid rgba(255,255,255,0.35); color: #fff;
    background: rgba(255,255,255,0.06); font-weight: 700;
}
body.festif-public .button.secondary:hover { border-color: var(--neon-cyan); color: #fff; transform: translateY(-2px); }

/* Cartes claires (catalogue, formulaires) lisibles sur fond sombre */
body.festif-public .content-card {
    background: #fbfaff; color: #1a1330; border: 0; border-radius: 22px;
    box-shadow: 0 20px 50px rgba(0,0,0,0.35);
    transition: transform .18s ease, box-shadow .18s ease;
}
body.festif-public .content-card:hover { transform: translateY(-4px); }
body.festif-public .content-card h2,
body.festif-public .content-card h3,
body.festif-public .content-card p,
body.festif-public .content-card label,
body.festif-public .content-card .muted { color: #1a1330; }
body.festif-public .form-label { color: #2a1f45; }

/* Jetons « carte claire » : la carte est une ÎLE claire sur la page sombre, donc
   tout ce qu'elle contient repart des valeurs claires (voir l'avertissement en
   tête de section). Ce sont les mêmes noms de jetons que le socle : rien n'est à
   rhabiller composant par composant, ni ici ni dans la boutique du dojo qui
   partage ces gabarits (`_option_fields.html`, `_booking_date_field.html`). */
body.festif-public .content-card,
body.festif-public .fest-request__form {
    --bg: #fbfaff;
    --bg-elevated: #ffffff;
    --bg-elevated-strong: #ffffff;
    --surface: #ffffff;
    --surface-soft: #f4f1ff;
    --text: #1a1330;
    --on-elevated: #1a1330;
    --muted: #6b6390;
    --text-muted: #6b6390;
    --text-soft: #57507d;
    --line: rgba(26, 19, 48, 0.16);
    --line-strong: rgba(26, 19, 48, 0.34);
    --border: rgba(26, 19, 48, 0.16);
    --border-color: rgba(26, 19, 48, 0.16);
    /* Accent violet plutôt que le rose néon de la page : le rose est calibré pour
       briller sur l'encre, il perd son contraste sur du blanc — or c'est lui qui
       colore la coche d'une case et la pastille d'un interrupteur. */
    --accent: #7a2ff2;
    --accent-strong: #5b18c4;
    --accent-soft: #ede4ff;
    --ring: rgba(122, 47, 242, 0.28);
    --field-required: #b42318;
    --field-error: #b42318;
    --field-error-surface: #fdf3f2;
    --field-error-ring: rgba(180, 35, 24, 0.2);
    --danger: #b42318;
    --danger-strong: #8f1c13;
}
/* Bouton secondaire DANS une carte claire : celui de la page est blanc sur fond
   translucide (calibré pour l'encre) — sur du blanc, « Ajouter au panier »
   devenait invisible. */
body.festif-public .content-card .button.secondary,
body.festif-public .fest-request__form .button.secondary {
    border-color: rgba(26, 19, 48, 0.22);
    color: #2a1f45;
    background: rgba(26, 19, 48, 0.045);
}
body.festif-public .content-card .button.secondary:hover,
body.festif-public .fest-request__form .button.secondary:hover {
    border-color: var(--neon-purple);
    color: #1a1330;
    background: rgba(168, 85, 247, 0.1);
}

/* --- HERO : le SOCLE, teinté ---------------------------------------------
   ⚠️ Festifun n'a plus de bandeau à lui. `.fest-hero` et `.fest-page-hero`
   étaient un second moteur de hero, avec ses couleurs en dur et rien de ce que
   le socle savait déjà faire (carrousel de diapos, cadrage vertical, filigrane,
   voile de lisibilité) — une correction posée sur l'un n'atteignait pas l'autre,
   et deux bandeaux qui divergent s'affichent tous les deux sans qu'un test
   tombe. Les deux formes sont entrées dans le registre `app/public_hero.py`
   sous les styles « fete » et « bandeau », rendues par `.site-hero--*` de
   styles.css. Il ne reste ici que la TEINTE : trois jetons, aucune règle.

   ⚠️ `--brand-*` est remappé SUR LE HERO, jamais sur le `<body>` : ces deux
   jetons servent des composants partagés de tout le site, et les repeindre
   globalement changerait des écrans que ce changement ne vise pas.
   ⚠️ `--text-soft` n'est PAS à définir : il est dérivé de `--text` sur `:root`
   (styles.css), donc déjà juste sur le fond encre du module. */
body.festif-public .site-hero {
    --site-hero-eyebrow-ink: var(--neon-cyan);
    --site-hero-pop: var(--grad-warm);
    --brand-cyan: var(--neon-cyan);
    --brand-orange: var(--neon-yellow);
}

/* --- MARQUEE --- */
body.festif-public .fest-marquee {
    overflow: hidden; white-space: nowrap; border-top: 1px solid rgba(255,255,255,.1);
    border-bottom: 1px solid rgba(255,255,255,.1); padding: .9rem 0; margin: 1.5rem 0 3rem;
    background: rgba(255,255,255,0.03);
}
body.festif-public .fest-marquee__track { display: inline-block; animation: fest-marquee 22s linear infinite; }
body.festif-public .fest-marquee span {
    font-size: 1.25rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
    margin: 0 1.2rem; color: #fff;
}
body.festif-public .fest-marquee span .dot { color: var(--neon-pink); }

/* --- BENTO --- */
/* Empêche le scroll horizontal au niveau de la page (clip au bord du viewport),
   tout en laissant les images bento déborder/sortir dans les marges. */
body.festif-public main { overflow-x: clip; }
body.festif-public .fest-bento {
    display: grid; gap: 1.4rem; grid-template-columns: repeat(2, 1fr);
    max-width: 760px; margin: 0 auto;
}
/* Nombre impair de cartes : la dernière (seule sur sa rangée) est centrée,
   à la largeur d'une colonne. */
body.festif-public .fest-bento .bento-card:last-child:nth-child(odd) {
    grid-column: 1 / -1; justify-self: center; width: calc(50% - 0.7rem);
}
/* Le lien-carte n'est qu'un cadre de positionnement : le panneau coloré (texte)
   est au-dessus, l'image décorative est un calque positionné DERRIÈRE lui. */
body.festif-public .bento-card { position: relative; display: block; text-decoration: none; }
body.festif-public .bento-card__panel {
    position: relative; z-index: 1;
    border-radius: 24px; padding: 1.6rem; aspect-ratio: 1 / 1; color: #0c0820;
    display: flex; flex-direction: column; justify-content: center; gap: .4rem;
    transition: transform .18s ease, box-shadow .18s ease;
}
body.festif-public .bento-card:hover .bento-card__panel { transform: translateY(-5px) rotate(-0.5deg); box-shadow: 0 24px 50px rgba(0,0,0,.4); }
/* Image derrière le panneau (z-index 0) : `contain` pour ne JAMAIS rogner l'image.
   Elle dépasse en grande partie sur le côté extérieur (≈ ¾ visible) ; seul un quart
   passe derrière la carte. La drop-shadow épouse la forme (utile pour un logo PNG). */
body.festif-public .bento-card__img {
    position: absolute; top: 0; height: 100%; width: auto; max-width: none;
    object-fit: contain;
    z-index: 0; filter: drop-shadow(0 12px 22px rgba(0,0,0,.45));
    transform: translate(0, 0) rotate(0) scale(1); transform-origin: center bottom;
    /* Courbe avec léger dépassement → petit rebond en fin de course (entrée/sortie). */
    transition: transform .5s cubic-bezier(.34, 1.56, .64, 1), filter .4s ease;
    will-change: transform;
}
/* Colonne de gauche (cartes impaires d'une grille 2 colonnes) : dépasse à gauche. */
body.festif-public .bento-card:nth-child(odd) .bento-card__img { left: -30%; right: auto; }
/* Colonne de droite (cartes paires) : dépasse à droite. */
body.festif-public .bento-card:nth-child(even) .bento-card__img { right: -30%; left: auto; }

/* Survol : l'image sort de derrière la carte, passe au premier plan et s'avance
   (translation vers l'extérieur + léger redressement + agrandissement). */
body.festif-public .bento-card:hover .bento-card__img { filter: drop-shadow(0 24px 42px rgba(0,0,0,.55)); }
/* Survol : l'image sort un peu plus par le côté (en restant DERRIÈRE la carte),
   avec un léger rebond en fin de course (cf. la courbe `transition` ci-dessus). */
body.festif-public .bento-card:nth-child(odd):hover .bento-card__img { transform: translate(-32%, -8%) rotate(-6deg) scale(1.06); }
body.festif-public .bento-card:nth-child(even):hover .bento-card__img { transform: translate(32%, -8%) rotate(6deg) scale(1.06); }
@media (prefers-reduced-motion: reduce) {
    body.festif-public .bento-card__img { transition: none; }
}
body.festif-public .bento-card h3 { margin: 0; font-size: 1.9rem; font-weight: 900; line-height: 1.1; letter-spacing: -.01em; color: inherit; }
body.festif-public .bento-card p { margin: .15rem 0 0; font-size: 1.12rem; line-height: 1.35; opacity: .92; color: inherit; }
body.festif-public .bento-card.c-pink .bento-card__panel { background: var(--neon-pink); color: #fff; }
body.festif-public .bento-card.c-cyan .bento-card__panel { background: var(--neon-cyan); }
body.festif-public .bento-card.c-yellow .bento-card__panel { background: var(--neon-yellow); }
body.festif-public .bento-card.c-purple .bento-card__panel { background: var(--neon-purple); color: #fff; }

/* --- SECTIONS génériques sombres --- */
body.festif-public .fest-section { max-width: 1100px; margin: 0 auto; padding: 3.5rem 1.2rem; }
/* L'ancrage des liens internes (#catalogue, #etapes) s'arrête sous le menu grâce
   au `scroll-padding-top` de <html> (styles.css), qui suit la hauteur réelle de
   l'en-tête et des bandeaux. Le 5.5rem codé ici s'y ajoutait : l'ancre tombait
   une hauteur d'en-tête trop bas. */
body.festif-public .fest-section > h2 {
    font-size: clamp(1.8rem, 5vw, 3rem); font-weight: 800; color: #fff; text-align: center; margin: 0 0 .5rem;
}
body.festif-public .fest-section > p.section-sub { text-align: center; color: #c2bce0; max-width: 60ch; margin: 0 auto 2.2rem; }
/* Bloc « diaporama » (socle partagé, `templates/_slideshow.html`) : la bande est
   la même partout, mais son titre et sa légende suivent le portail.
   ⚠️ Sans ce remappage, ils sortaient à l'encre du site d'une école — sombre —
   sur le fond encre de la vitrine : la légende était **illisible** et le titre
   aligné à gauche au milieu d'une page dont tous les titres de section sont
   centrés (mesuré au navigateur, invisible à la lecture du gabarit).
   ⚠️ Les valeurs sont celles des sections juste au-dessus : c'est le même
   vocabulaire, pas une seconde palette. */
body.festif-public .site-slideshow {
    --slideshow-ink: #fff;
    --slideshow-caption-ink: #c2bce0;
    --slideshow-align: center;
}
body.festif-public .site-slideshow__title { font-weight: 800; }

/* --- ÉTAPES --- */
body.festif-public .fest-steps { display: grid; gap: 1rem; grid-template-columns: repeat(3, 1fr); }
body.festif-public .step {
    background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
    border-radius: 20px; padding: 1.6rem; text-align: center;
}
body.festif-public .step .num {
    display: inline-flex; width: 46px; height: 46px; align-items: center; justify-content: center;
    border-radius: 50%; font-weight: 800; font-size: 1.2rem; color: #0c0820;
    background: var(--grad-warm); margin-bottom: .8rem;
}
body.festif-public .step h3 { color: #fff; margin: 0 0 .4rem; }
body.festif-public .step p { color: #c2bce0; margin: 0; font-size: .92rem; }

/* --- VITRINE CATALOGUE (cartes claires) --- */
body.festif-public .fest-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1.1rem;
}
body.festif-public .cat-block { margin-bottom: 2.6rem; }
body.festif-public .cat-block > h3 {
    color: #fff; font-size: 1.5rem; font-weight: 800; display: inline-block; margin: 0 0 1rem;
    position: relative; padding-bottom: .25rem;
}
body.festif-public .cat-block > h3::after {
    content: ""; position: absolute; left: 0; bottom: -2px; width: 100%; height: 4px;
    border-radius: 999px; background: var(--grad-hot);
}
body.festif-public .prod-card { padding: 0; overflow: hidden; }
body.festif-public .prod-card .media { aspect-ratio: 1/1; overflow: hidden; background: #efeaff; }
body.festif-public .prod-card .media img { width: 100%; height: 100%; object-fit: cover; transition: transform .25s ease; }
body.festif-public .prod-card:hover .media img { transform: scale(1.06); }
body.festif-public .prod-card .body { padding: .65rem .9rem .75rem; }
body.festif-public .prod-card .ptype { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--neon-purple); font-weight: 700; margin: 0; }
body.festif-public .prod-card h4 { margin: .15rem 0 .25rem; color: #1a1330; }
body.festif-public .prod-card .price { font-weight: 800; color: #1a1330; margin: .25rem 0 0; }
body.festif-public .prod-card .price small { color: #6b6390; font-weight: 600; }

/* Pastille « à partir de » de la vitrine (app/festivites_starting_price.py).

   ⚠️ **Opaque, et c'est ce qui la rend posable partout** : elle s'affiche sur
   une carte CLAIRE (`.prod-card .price`, encre #1a1330) comme sur le fond
   SOMBRE de la fiche d'article et de l'écran de réservation. Un remplissage
   translucide prendrait la couleur de ce qu'il y a dessous — lisible ici,
   invisible là, sans qu'une ligne échoue. Les deux `color-mix` finissent sur du
   blanc et sur de l'encre, donc sur des couleurs pleines.

   ⚠️ **Elle ANNONCE le prix, elle ne lui dispute pas la place** : plus petite
   que le montant, en capitales, alignée sur sa ligne. Le gros chiffre reste ce
   que l'œil lit en premier.

   ⚠️ `white-space: nowrap` : « à partir » d'un côté et « de » de l'autre ferait
   deux pastilles à l'œil. */
body.festif-public .fest-price-from {
    display: inline-block;
    vertical-align: middle;
    margin-right: .3rem;
    padding: .12rem .45rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--neon-purple) 16%, #fff);
    color: color-mix(in srgb, var(--neon-purple) 72%, #1a1330);
    font-size: .62rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .06em;
    line-height: 1.45;
    white-space: nowrap;
}

/* ⚠️ Dans la CARTE du catalogue, la pastille prend sa PROPRE ligne. Mesuré au
   navigateur : la colonne fait environ 230 px et, posée devant le montant, la
   pastille repoussait l'unité à la ligne suivante — « 190.00 $ / » puis
   « événement », avec une barre oblique orpheline en fin de ligne. Sur la fiche
   d'article et l'écran de réservation la ligne est large : la pastille y reste
   en ligne, et c'est ce qui se lit le mieux. */
body.festif-public .prod-card .price .fest-price-from {
    display: block;
    width: max-content;
    max-width: 100%;
    margin: 0 0 .18rem;
}

/* --- FAQ --- */
body.festif-public .fest-faq { max-width: 760px; margin: 0 auto; }
body.festif-public .fest-faq details {
    background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
    border-radius: 16px; padding: 1rem 1.2rem; margin-bottom: .7rem;
}
body.festif-public .fest-faq summary {
    cursor: pointer; font-weight: 700; color: #fff; list-style: none; display: flex; justify-content: space-between; gap: 1rem;
}
body.festif-public .fest-faq summary::after { content: "+"; color: var(--neon-cyan); font-size: 1.4rem; line-height: 1; }
body.festif-public .fest-faq details[open] summary::after { content: "–"; }
body.festif-public .fest-faq p { color: #c2bce0; margin: .8rem 0 0; }

/* --- GROUPE D'OPTIONS REPLIABLE (formulaire de réservation, >4 choix) --- */
.fest-option-collapse { border: 1px solid rgba(var(--fest-veil, 0, 0, 0), .14); border-radius: 12px; overflow: hidden; }
.fest-option-collapse > .fest-option-summary {
    cursor: pointer; list-style: none; display: flex; align-items: center; gap: .4rem;
    margin: 0; padding: .6rem .85rem;
    background: rgba(var(--fest-veil, 0, 0, 0), .045);
    border-bottom: 1px solid transparent;
    font-weight: 600;
    transition: background .15s ease;
}
.fest-option-collapse > .fest-option-summary:hover { background: rgba(var(--fest-veil, 0, 0, 0), .075); }
.fest-option-collapse > .fest-option-summary::-webkit-details-marker { display: none; }
.fest-option-collapse > .fest-option-summary::after {
    content: "+"; margin-left: auto; font-size: 1.3rem; line-height: 1; opacity: .7;
}
.fest-option-collapse[open] > .fest-option-summary {
    background: rgba(var(--fest-veil, 0, 0, 0), .06);
    border-bottom-color: rgba(var(--fest-veil, 0, 0, 0), .12);
}
.fest-option-collapse[open] > .fest-option-summary::after { content: "–"; }
/* Corps déplié : marge intérieure pour décaler les options sous le résumé. */
.fest-option-collapse__body { padding: .85rem 1.25rem 1rem; }

/* --- RANGÉE D'OPTION (socle partagé `templates/_option_fields.html`) ---
   Ces règles reprennent à l'identique les styles qui étaient en ligne dans
   `reserver.html` avant la mise en commun du balisage avec la boutique du dojo.
   Le préfixe `fest-` reste propre à Festivités : seul le gabarit est partagé. */
.fest-option {
    display: flex; align-items: center; gap: .5rem; padding: .35rem 0; cursor: pointer;
}
.fest-option--soldout { opacity: .55; cursor: not-allowed; }
/* « Choix unique » quantifiable : le radio et son libellé forment le label cliquable,
   le champ de quantité reste **à côté** (voir `_option_fields.html`). Sans `min-width:
   0`, un nom long refuse de rétrécir et pousse la boîte de quantité hors de la carte. */
.fest-option__pick {
    display: flex; align-items: center; gap: .5rem; flex: 1 1 auto; min-width: 0; cursor: pointer;
}
.fest-option__pick > span { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.fest-option__qty { width: 5rem; flex: 0 0 auto; }
.fest-option__price { font-weight: 600; }
.fest-option__soldout { color: var(--danger, #c0392b); }
.fest-option-group { border: 0; padding: 0; margin: 0 0 1rem; }
.fest-option-group__desc { margin: .1rem 0 .5rem; }
.fest-option-summary__count { font-weight: 400; }

/* --- BANDEAU CTA --- */
body.festif-public .fest-cta-band {
    max-width: 1100px; margin: 1rem auto 3rem; border-radius: 28px; padding: clamp(2rem,6vw,3.5rem);
    text-align: center; background: var(--grad-hot); color: #fff;
    box-shadow: 0 24px 60px rgba(255,46,136,0.35);
}
body.festif-public .fest-cta-band h2 { font-size: clamp(1.8rem, 5vw, 3rem); font-weight: 800; margin: 0 0 1rem; color: #fff; }

/* --- Pages secondaires (détail, réserver, confirmation) sur fond sombre --- */
body.festif-public .page-title { color: #fff; }
body.festif-public > main > .section .page-subtitle,
body.festif-public .breadcrumb-link { color: #c2bce0; }
body.festif-public .breadcrumb-link:hover { color: #fff; }
/* Texte des cartes claires : on rétablit des couleurs sombres lisibles. */
body.festif-public .content-card .page-title { background: var(--grad-warm); -webkit-background-clip: text; background-clip: text; color: transparent; }
body.festif-public .content-card .muted { color: #6b6390; }

/* --- Pied de page --- */
body.festif-public .site-footer { border-top: 1px solid rgba(255,255,255,0.1); color: #9a93c0; background: transparent; }
body.festif-public .footer-admin-link { color: #6f689a; font-size: 0.78rem; text-decoration: none; opacity: 0.7; }
body.festif-public .footer-admin-link:hover { opacity: 1; text-decoration: underline; }

/* --- Responsive --- */
@media (max-width: 860px) {
    body.festif-public .fest-bento { grid-template-columns: repeat(2, 1fr); }
    body.festif-public .fest-steps { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
    body.festif-public .fest-bento { grid-template-columns: 1fr; }
    /* En une colonne, l'orpheline reprend la largeur pleine comme les autres. */
    body.festif-public .fest-bento .bento-card:last-child:nth-child(odd) { grid-column: auto; width: auto; }
}

/* ---------------------------------------------------------------------------
   3) Drop-zone & grille d'images (back-office)
   --------------------------------------------------------------------------- */
/* Gestion des cartes bento (liste triable, personnalisation back-office).
   ⚠️ La poignée partagée (.drag-handle, styles.css) se colle au bord de la carte
   par une marge négative égale au padding de celle-ci : tout conteneur qui change
   ce padding DOIT redéclarer --handle-inset-y/-x (et --handle-corner s'il change
   l'arrondi), sinon la poignée déborde de sa carte — ici elle sortait à gauche et
   dépassait en hauteur au point de chevaucher les lignes voisines. */
.is-festivites .bento-item {
    --handle-inset-y: .5rem;
    --handle-inset-x: .6rem;
    /* ⚠️ Le repli partagé passe tout `.sortable-item` en colonne sous 960px : ici
       il centrait le contenu et renvoyait la corbeille sous le champ, alors que la
       carte bento a sa propre disposition mobile (grille, sous 640px). On garde
       donc la ligne — la spécificité (0,2,0) suffit, pas de media query. */
    flex-direction: row;
    align-items: center;
    /* ⚠️ L'axe remis en rangée, la forme de la poignée doit l'être aussi : le
       repli empilé de `.sortable-item` (sous 960px) en fait un bandeau pleine
       largeur. Gardé tel quel, il déborderait à droite sous le champ et
       s'arrêterait avant le bas de la carte. Voir `.drag-handle`. */
    --handle-bleed-right: 0px;
    --handle-bleed-bottom: var(--handle-inset-y);
    padding: var(--handle-inset-y) var(--handle-inset-x);
}
.is-festivites .bento-item__main { flex: 1 1 auto; align-items: center; gap: .65rem; min-width: 0; }
.is-festivites .bento-item__fields { display: flex; flex-direction: column; gap: .3rem; flex: 1 1 auto; min-width: 0; margin: 0; }
.is-festivites .bento-item__fields .form-control { width: 100%; padding: .42rem .65rem; font-size: .9rem; }
.is-festivites .bento-item__fields .autosave-status { font-size: .72rem; margin: 0; line-height: 1; }
.is-festivites .bento-item__fields .autosave-status:empty { display: none; }
.is-festivites .bento-item__actions { display: flex; align-items: center; align-self: center; margin-top: 0; }
.is-festivites .bento-item__actions form { margin: 0; display: flex; align-items: center; }
.is-festivites .bento-item__actions .btn-icon { width: 34px; height: 34px; min-width: 34px; min-height: 34px; box-shadow: none; }
.is-festivites .bento-item__actions .btn-icon:hover { box-shadow: none; }

/* Variante compacte : listes à un seul champ (ex. mots du bandeau défilant). */
.is-festivites .fest-list--compact { gap: .35rem; }
.is-festivites .fest-list--compact .bento-item {
    --handle-inset-y: .12rem;
    --handle-inset-x: .3rem;
    --handle-corner: 10px;
    padding: var(--handle-inset-y) var(--handle-inset-x);
    border-radius: var(--handle-corner);
    align-items: center;
}
.is-festivites .fest-list--compact .bento-item__main { gap: .45rem; align-items: center; }
.is-festivites .fest-list--compact .bento-item__fields .form-control { padding: .28rem .55rem; font-size: .85rem; }
.is-festivites .fest-list--compact .bento-item__actions .btn-icon { width: 28px; height: 28px; min-width: 28px; min-height: 28px; border-radius: 8px; }
.is-festivites .fest-list--compact .bento-item__actions .btn-icon i { font-size: .9rem; }
/* ⚠️ Pas de font-size ici : le composant partagé masque son texte source (« :: »)
   par font-size:0 et dessine les points en box-shadow — le rétablir affichait les
   deux-points par-dessus la grille de points. La ligne étant basse, on réduit la
   poignée plutôt que sa police. */
.is-festivites .fest-list--compact .drag-handle { min-width: 2rem; min-height: 1.4rem; }

/* Vignette = zone d'upload (clic + glisser-déposer) ; × en coin pour retirer. */
.is-festivites .bento-item__thumb {
    position: relative; flex: 0 0 auto;
    width: 58px; height: 58px; border-radius: 12px;
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--accent-soft, #eef) 45%, transparent);
    border: 1.5px dashed var(--line); color: var(--muted);
    cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .12s ease;
}
.is-festivites .bento-item__thumb:hover { border-color: var(--accent); }
.is-festivites .bento-item__thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.is-festivites .bento-item__thumb.has-image { border-style: solid; }
.is-festivites .bento-item__thumb.is-drop-target { border-color: var(--accent); background: color-mix(in srgb, var(--accent-soft, #eef) 75%, transparent); transform: scale(1.03); }
.is-festivites .bento-item__thumb.is-uploading { opacity: .6; }
.is-festivites .bento-item__thumb-ph { font-size: 1.4rem; }
.is-festivites .bento-item__thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: 12px; display: block; }
.is-festivites .bento-item__thumb-x {
    position: absolute; top: -8px; right: -8px;
    width: 22px; height: 22px; padding: 0; border: 2px solid var(--bg-elevated, #fff); border-radius: 50%;
    background: var(--danger, #c0392b); color: #fff; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center; font-size: .72rem; line-height: 1;
    box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.is-festivites .bento-item__thumb-x:hover { filter: brightness(1.08); }
.is-festivites .bento-item__thumb-x[hidden] { display: none; }
.is-festivites .bento-item__thumb-zoom {
    position: absolute; bottom: -8px; right: -8px;
    width: 22px; height: 22px; padding: 0; border: 2px solid var(--bg-elevated, #fff); border-radius: 50%;
    background: var(--accent, #2a1640); color: #fff; cursor: zoom-in;
    display: inline-flex; align-items: center; justify-content: center; font-size: .72rem; line-height: 1;
    box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.is-festivites .bento-item__thumb-zoom:hover { filter: brightness(1.08); }
.is-festivites .bento-item__thumb-zoom[hidden] { display: none; }
@media (max-width: 640px) {
    /* Carte bento compacte : poignée + vignette + corbeille sur une rangée
       (corbeille poussée à droite), titre/description pleine largeur dessous.
       `display:contents` fait remonter les enfants de __main dans la grille
       de la carte pour les aligner avec les actions. */
    .is-festivites .bento-item {
        display: grid;
        grid-template-columns: auto auto 1fr;
        grid-template-areas:
            "handle thumb actions"
            "fields fields fields";
        column-gap: .6rem;
        row-gap: .55rem;
        align-items: center;
    }
    .is-festivites .bento-item__main { display: contents; }
    .is-festivites .bento-item .drag-handle { grid-area: handle; }
    .is-festivites .bento-item__thumb { grid-area: thumb; }
    .is-festivites .bento-item__fields { grid-area: fields; }
    .is-festivites .bento-item__actions { grid-area: actions; justify-self: end; }
}

.fest-dropzone {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .4rem;
    padding: 1.6rem; text-align: center; color: var(--text, #2a1640);
    border: 2.5px dashed rgba(255,138,61,0.55); border-radius: 16px; background: rgba(255,210,63,0.08);
    cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .15s ease;
}
.fest-dropzone:hover, .fest-dropzone:focus-visible { border-color: #ff4d8d; background: rgba(255,77,141,0.08); outline: none; }
.fest-dropzone.is-drop-target { border-color: #ff4d8d; background: rgba(255,77,141,0.14); transform: scale(1.01); }
.fest-dropzone__hint { margin: 0; font-size: .9rem; opacity: .8; }
.fest-img-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: .75rem; }
.fest-img-tile {
    position: relative; margin: 0; border-radius: 14px; overflow: hidden; aspect-ratio: 4/3;
    box-shadow: 0 6px 16px rgba(42,22,64,0.12); cursor: grab; background: var(--fest-panel-soft, #f4f4f8);
}
.fest-img-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fest-img-tile.is-primary { outline: 3px solid #ff4d8d; outline-offset: -3px; }
.fest-img-tile__actions { position: absolute; top: 6px; right: 6px; display: flex; gap: 4px; }
.fest-img-tile__star, .fest-img-tile__delete, .fest-img-tile__zoom, .fest-img-tile__reuse {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; border: 0; border-radius: 50%; cursor: pointer; font-size: .85rem; line-height: 1;
    background: rgba(255,255,255,0.92); box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
.fest-img-tile__star { color: #ff8a3d; }
.fest-img-tile__delete { color: #d3344f; }
.fest-img-tile__zoom { color: #2a1640; }
.fest-img-tile__reuse { color: #7c3aed; }
.fest-img-tile.is-primary .fest-img-tile__star { color: #ff4d8d; }

/* ---------------------------------------------------------------------------
   3bis) Calendrier (dates réservables) — DÉPLACÉ dans le composant partagé
   `static/css/catalog_calendar.css` (préfixe neutre `.resa-`), commun à la
   boutique du dojo et à Festifun. L'accent rose du module est repointé là-bas
   via `body.is-festivites` / `body.festif-public`. Ne pas le recopier ici.
   --------------------------------------------------------------------------- */
.fest-link-btn {
    border: 0; background: none; padding: 0; cursor: pointer;
    color: var(--fest-pink, #ff4d8d); font: inherit; font-size: .8rem;
    text-decoration: underline; margin-left: auto;
}
.fest-link-btn:hover { opacity: .75; }

/* Aperçu des options d'un groupe (édition d'article) */
.fest-opt-preview {
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin: -.15rem 0 .15rem;
}
.fest-opt-chip {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    background: rgba(var(--fest-veil, 0, 0, 0), .04);
    border: 1px solid rgba(var(--fest-veil, 0, 0, 0), .1);
    border-radius: 999px;
    padding: .2rem .6rem;
    font-size: .8rem;
    line-height: 1.4;
}
.fest-opt-chip__price { color: var(--fest-pink, #ff4d8d); font-weight: 600; }
.fest-opt-chip__price--muted { color: var(--muted, #6b7280); font-weight: 500; }
.fest-opt-chip i { font-size: .8rem; opacity: .7; }
.fest-opt-chip--default { background: rgba(255, 77, 141, .1); border-color: rgba(255, 77, 141, .3); }
.fest-opt-edit {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: .8rem;
    color: var(--fest-pink, #ff4d8d);
    text-decoration: none;
    white-space: nowrap;
}
.fest-opt-edit:hover { text-decoration: underline; }
/* Rangée d'attache d'un groupe à un article (socle `templates/_option_attach.html`) :
   reprend le style qui était en ligne avant la mise en commun avec la boutique. */
.fest-opt-attach-row {
    display: block;
    padding: .75rem 0; border-top: 1px solid var(--line, rgba(0, 0, 0, .08));
}
/* ⚠️ Règle explicite : le `display` ci-dessus (feuille de l'auteur) bat le
   `[hidden] { display: none }` du navigateur — sans elle, le filtre de la barre
   « Options de cet article » masquerait les rangées… en apparence seulement. */
.fest-opt-attach-row[hidden] { display: none; }

/* En-tête de la rangée : le groupe, son « choix obligatoire », le lien d'édition. */
.fest-opt-attach-head {
    display: flex; gap: 1.25rem; align-items: center; flex-wrap: wrap;
}
.fest-opt-attach-head__pick { flex: 1 1 240px; }

/* Réglages POUR CET ARTICLE, option par option.
   ⚠️ Repliés tant que le groupe n'est pas coché — en CSS pur (`:has`) et non en
   JavaScript : la liste se recharge par fragment, un script devrait se réarmer à
   chaque fois. Le bloc reste DANS le formulaire (les champs continuent d'être
   postés) : `display:none` ne retire pas un champ de l'envoi, contrairement à
   `disabled`. */
.fest-opt-attach-opts { display: none; margin: .6rem 0 .1rem; }
.fest-opt-attach-row:has(input[name^="group_"]:checked) .fest-opt-attach-opts { display: block; }
/* Repli sans `:has` (navigateur ancien) : tout reste visible plutôt que tout
   masqué — un réglage qu'on ne trouve pas est pire qu'un écran chargé. */
@supports not selector(:has(*)) {
    .fest-opt-attach-opts { display: block; }
}

.fest-opt-attach-opts__head,
.fest-opt-attach-opt {
    display: grid;
    grid-template-columns: minmax(140px, 2fr) minmax(70px, .8fr) 108px 64px minmax(8rem, 1.2fr);
    gap: .75rem; align-items: center;
}
/* Choix unique : la colonne « Imposée » n'est pas rendue (le radio impose déjà de
   choisir) — la grille perd donc sa 4e piste, sinon les en-têtes se décalent des
   cellules qu'ils nomment. */
.fest-opt-attach-opts.is-single .fest-opt-attach-opts__head,
.fest-opt-attach-opts.is-single .fest-opt-attach-opt {
    grid-template-columns: minmax(140px, 2fr) minmax(70px, .8fr) 108px minmax(8rem, 1.2fr);
}
/* Titre de section : « Offerts sur cet article » / « Reste de la bibliothèque ». */
.fest-opt-attach-title {
    margin: 1rem 0 .1rem; font-size: .72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .05em; color: var(--muted, #6b7280);
}
.fest-opt-attach-title:first-child { margin-top: 0; }
.fest-opt-attach-opts__head {
    font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
    color: var(--muted, #6b7280); padding: 0 .5rem .35rem;
    border-bottom: 1px solid var(--line, rgba(0, 0, 0, .08));
}
.fest-opt-attach-opt {
    padding: .3rem .5rem;
    border-bottom: 1px solid var(--line, rgba(0, 0, 0, .05));
}
.fest-opt-attach-opt:last-child { border-bottom: 0; }
.fest-opt-attach-opt__name { font-size: .9rem; }
.fest-opt-attach-opt__name i { font-size: .8rem; color: var(--fest-pink, #ff4d8d); }
.fest-opt-attach-opt__price { font-size: .85rem; color: var(--muted, #6b7280); white-space: nowrap; }
.fest-opt-attach-opt__qty { padding: .25rem .4rem; text-align: center; }
.fest-opt-attach-opt__na { text-align: center; }
.fest-opt-attach-empty { font-size: .82rem; margin: .4rem 0 0; }
/* Hiérarchie : « Afficher seulement si… » d'un groupe, et colonne « Si… » par option. */
.fest-opt-attach-requires { margin: .5rem 0 0; display: grid; gap: .25rem; }
.fest-opt-attach-opt__if { min-width: 0; }

/* Conteneur de la liste d'options (jumeau de `.shop-option-rows` : le gabarit est
   partagé, une marge posée d'un seul côté se voit sur un écran et pas sur l'autre). */
.fest-option-rows { margin-bottom: .5rem; }

/* ── Bibliothèque d'options : une option = UNE rangée de tableau ──────────────
   Chaque option répétait ses cinq libellés de champ, ses cinq interrupteurs sur
   une deuxième ligne et un bouton « Suppr. » sur une troisième : trois lignes
   hautes par option, et autant de répétitions des mêmes titres. L'en-tête de
   colonnes est désormais rendu une fois, la rangée tient sur une ligne.
   ⚠️ Le `<form>` intérieur est en `display: contents` : ses champs participent à
   la grille de la rangée, sinon il formerait une boîte de plus et les colonnes ne
   s'aligneraient pas d'une option à l'autre. */
.fest-option-head,
.fest-option-row {
    display: grid;
    grid-template-columns:
        28px                    /* poignée (rail) */
        minmax(150px, 2.4fr)    /* nom */
        minmax(76px, 1fr)       /* prix */
        72px 72px               /* qté min / max */
        minmax(76px, 1fr)       /* stock */
        32px;                   /* supprimer */
    gap: .35rem .6rem;
    align-items: center;
}
/* ⚠️ Les cinq interrupteurs ne tiennent PAS sur la ligne des cinq champs (mesuré :
   ils s'empilaient sur quatre lignes, rendant la rangée plus haute qu'avant). Ils
   prennent donc la 2e ligne de la MÊME grille, alignés sous les champs qu'ils
   complètent — une rangée de deux lignes, pas trois, et un seul jeu d'en-têtes. */
/* Poignée en RAIL : elle longe les deux lignes de l'option (champs +
   interrupteurs) et prend toute la largeur de sa colonne. Un petit badge centré
   entre deux lignes ne disait pas ce qu'il déplaçait ; une bande sur la hauteur
   de l'option se lit comme la prise de TOUTE l'option, et se vise sans viser.
   Survoler la poignée teinte la rangée entière : c'est elle qui va bouger. */
.fest-option-row > .drag-handle {
    grid-column: 1; grid-row: 1 / span 2;
    justify-self: stretch; align-self: stretch;
    min-width: 0; margin: 0;
    border-radius: 10px;
}
.fest-option-row:has(> .drag-handle:hover),
.fest-option-row.is-dragging {
    background-color: color-mix(in srgb, var(--accent-soft, rgba(0, 0, 0, .05)) 45%, transparent);
}
.fest-option-row.drop-target {
    box-shadow: inset 0 2px 0 var(--accent, currentColor);
}
/* ⚠️ Colonne EXPLICITE pour la corbeille : `grid-row` seul sort l'élément du flux
   automatique, il se posait alors au premier emplacement libre — c'est-à-dire en
   2e colonne, devant le nom, en décalant tous les champs d'une colonne. */
.fest-option-row__delete > button { grid-column: -2; grid-row: 1 / span 2; }
.fest-option-head {
    font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
    color: var(--muted, #6b7280); padding: 0 0 .35rem;
    border-bottom: 1px solid var(--line, rgba(0, 0, 0, .12));
}
.fest-option-row {
    padding: .4rem 0;
    transition: background-color var(--transition-fast, .15s);
    border-top: 1px solid var(--line, rgba(0, 0, 0, .06));
}
.fest-option-row:first-child { border-top: 0; }
.fest-option-row__form { display: contents; }
.fest-option-row__delete { display: contents; }

.fest-optf { margin: 0; min-width: 0; position: relative; }
.fest-optf .form-control { padding: .3rem .5rem; }
/* Les libellés par champ vivent toujours dans le DOM (accessibilité, et seul
   repère quand la disposition en tableau ne tient plus) : masqués tant que
   l'en-tête de colonnes les remplace. */
.fest-optf__label { display: none; }
.fest-optf__hint {
    position: absolute; right: .5rem; top: 50%; transform: translateY(-50%);
    font-size: .7rem; pointer-events: none;
}
.fest-optf-toggles {
    grid-column: 2 / -2;
    display: flex; gap: .35rem 1rem; align-items: center; flex-wrap: wrap;
    padding-top: .1rem;
}
/* Le statut d'autosave prend toute la largeur SOUS la rangée : dans une cellule,
   il élargirait sa colonne au premier message et décalerait toutes les autres. */
.fest-optf-status {
    grid-column: 1 / -1;
    min-height: 0; margin: 0; font-size: .8rem;
}
.fest-optf-status:empty { display: none; }

/* Sous 1100 px la ligne unique ne tient plus : on repasse en carte, et les
   libellés de champ reviennent (l'en-tête de colonnes, lui, disparaît).
   ⚠️ Chaque cellule est placée EXPLICITEMENT : un `repeat(auto-fit, …)` donne un
   nombre de colonnes qui dépend de la largeur, donc un placement automatique
   imprévisible — mesuré, le nom prenait toute la ligne, le prix se retrouvait
   rogné en bout de rangée et la corbeille flottait seule sous le bloc.
   Les champs sont des `div.form-group` frères dans le formulaire, dans l'ordre
   nom, prix, qté min, qté max, stock, réglages : c'est ce que compte
   `nth-of-type`. */
@media (max-width: 1100px) {
    .fest-option-head { display: none; }
    .fest-option-row {
        grid-template-columns: 28px repeat(4, minmax(0, 1fr)) 32px;
        align-items: end;
        gap: .5rem .6rem;
        padding: .75rem 0;
    }
    .fest-optf__label { display: block; font-size: .75rem; margin-bottom: .15rem; }
    .fest-option-row > .drag-handle { grid-column: 1; grid-row: 1 / span 3; align-self: stretch; }
    .fest-option-row__delete > button { grid-column: 6; grid-row: 1; }
    .fest-option-row__form > .fest-optf:nth-of-type(1) { grid-column: 2 / span 4; grid-row: 1; }
    .fest-option-row__form > .fest-optf:nth-of-type(2) { grid-column: 2; grid-row: 2; }
    .fest-option-row__form > .fest-optf:nth-of-type(3) { grid-column: 3; grid-row: 2; }
    .fest-option-row__form > .fest-optf:nth-of-type(4) { grid-column: 4; grid-row: 2; }
    .fest-option-row__form > .fest-optf:nth-of-type(5) { grid-column: 5; grid-row: 2; }
    .fest-optf-toggles { grid-column: 2 / -1; grid-row: 3; }
    .fest-optf-status { grid-row: 4; }
}

/* Sous 700 px, quatre champs de front deviennent illisibles : deux par ligne. */
@media (max-width: 700px) {
    .fest-option-row { grid-template-columns: 28px repeat(2, minmax(0, 1fr)) 32px; }
    .fest-option-row > .drag-handle { grid-row: 1 / span 4; }
    .fest-option-row__delete > button { grid-column: 4; }
    .fest-option-row__form > .fest-optf:nth-of-type(1) { grid-column: 2 / span 2; }
    .fest-option-row__form > .fest-optf:nth-of-type(2) { grid-column: 2; grid-row: 2; }
    .fest-option-row__form > .fest-optf:nth-of-type(3) { grid-column: 3; grid-row: 2; }
    .fest-option-row__form > .fest-optf:nth-of-type(4) { grid-column: 2; grid-row: 3; }
    .fest-option-row__form > .fest-optf:nth-of-type(5) { grid-column: 3; grid-row: 3; }
    /* Les interrupteurs s'empilaient un par ligne (une règle mobile leur donne
       toute la largeur en `flex`) : 250 px pour cinq réglages. Une grille à deux
       colonnes les remet sur trois lignes. */
    .fest-optf-toggles {
        grid-row: 4;
        display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: .3rem .75rem;
    }
    .fest-optf-status { grid-row: 5; }
}


/* Sous 640 px la grille à 4 colonnes déborde : les réglages passent sous le nom,
   avec leur libellé (l'en-tête de colonnes, lui, disparaît). */
@media (max-width: 640px) {
    .fest-opt-attach-opts__head { display: none; }
    .fest-opt-attach-opt { grid-template-columns: 1fr auto auto; }
    .fest-opt-attach-opt__name { grid-column: 1 / -1; font-weight: 600; }
    .fest-opt-attach-opt__if { grid-column: 1 / -1; }
    .fest-opt-attach-opts.is-single .fest-opt-attach-opt { grid-template-columns: 1fr auto; }
}

/* Bibliothèque d'options : accordéon repliable par groupe */
.fest-opt-toolbar {
    display: flex;
    gap: .75rem;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.fest-opt-toolbar__search { flex: 1 1 240px; }
.fest-acc {
    border: 1px solid var(--line, rgba(0, 0, 0, .12));
    border-radius: 12px;
    margin-bottom: .75rem;
    background: var(--surface, #fff);
    overflow: hidden;
}
.fest-acc--inactive { opacity: .6; }
.fest-acc__summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .85rem 1rem;
    user-select: none;
}
.fest-acc__summary::-webkit-details-marker { display: none; }
.fest-acc__summary:hover { background: rgba(var(--fest-veil, 0, 0, 0), .02); }
.fest-acc__chevron { transition: transform .2s ease; flex: 0 0 auto; opacity: .6; }
.fest-acc[open] > .fest-acc__summary .fest-acc__chevron { transform: rotate(90deg); }
.fest-acc__title { font-weight: 600; }
.fest-acc__meta {
    display: flex;
    gap: .4rem;
    flex-wrap: wrap;
    align-items: center;
    margin-left: auto;
}
.fest-acc__badge {
    font-size: .75rem;
    padding: .15rem .55rem;
    border-radius: 999px;
    background: rgba(var(--fest-veil, 0, 0, 0), .05);
    white-space: nowrap;
}
.fest-acc__badge--warn { background: rgba(245, 158, 11, .15); color: var(--fest-warn, #b45309); }
.fest-acc__badge--price { background: rgba(255, 77, 141, .12); color: var(--fest-pink, #ff4d8d); font-weight: 600; }
.fest-acc__body { padding: .25rem 1rem 1rem; border-top: 1px solid var(--line, rgba(0, 0, 0, .08)); }

/* Actions rapides par ligne dans la liste des réservations */
.fest-row-actions {
    display: flex;
    gap: .35rem;
    flex-wrap: wrap;
    align-items: center;
}
.fest-row-actions form { margin: 0; display: inline; }

/* ---------------------------------------------------------------------------
   4) Calendrier admin — vue mensuelle des réservations
   --------------------------------------------------------------------------- */
.fest-admin-cal-nav {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    margin-bottom: 1rem;
}
.fest-admin-cal-nav__title {
    font-size: 1.1rem;
    font-weight: 700;
    text-transform: capitalize;
    min-width: 13rem;
    text-align: center;
}
.fest-admin-cal-nav__btn {
    min-width: 2.4rem;
    min-height: 2.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    line-height: 1;
    padding: 0;
}

.fest-admin-cal__header {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    background: rgba(var(--fest-veil, 0, 0, 0), .03);
    border-bottom: 1px solid rgba(var(--fest-veil, 0, 0, 0), .08);
}
.fest-admin-cal__dow {
    padding: .6rem .25rem;
    text-align: center;
    font-size: .7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    opacity: .55;
}
.fest-admin-cal__grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    grid-auto-rows: minmax(110px, auto);
    border-top: 1px solid rgba(var(--fest-veil, 0, 0, 0), .07);
    border-left: 1px solid rgba(var(--fest-veil, 0, 0, 0), .07);
}
.fest-admin-cal__cell {
    border-right: 1px solid rgba(var(--fest-veil, 0, 0, 0), .07);
    border-bottom: 1px solid rgba(var(--fest-veil, 0, 0, 0), .07);
    padding: .35rem .4rem .45rem;
    display: flex;
    flex-direction: column;
    gap: .18rem;
    min-height: 110px;
}
.fest-admin-cal__cell--vide {
    background: rgba(var(--fest-veil, 0, 0, 0), .025);
}
/* Jours des mois voisins affichés dans la même grille : numéro + contenu pâles */
.fest-admin-cal__cell--outside {
    background: rgba(var(--fest-veil, 0, 0, 0), .025);
}
.fest-admin-cal__cell--outside .fest-admin-cal__num { opacity: .3; }
.fest-admin-cal__cell--outside .fest-admin-festival,
.fest-admin-cal__cell--outside .fest-admin-event { opacity: .4; }
/* Dates passées : grisées, en retrait visuel */
.fest-admin-cal__cell--past {
    background: rgba(var(--fest-veil, 0, 0, 0), .04);
}
.fest-admin-cal__cell--past .fest-admin-cal__num { opacity: .45; }
.fest-admin-cal__cell--past .fest-admin-event,
.fest-admin-cal__cell--past .fest-admin-festival { opacity: .55; }
/* Titre de mois au-dessus de chaque grille */
.fest-admin-cal__month-title {
    font-size: 1rem;
    font-weight: 700;
    padding: .7rem 1rem .55rem;
    color: var(--text, #2a1a2e);
    border-bottom: 1px solid rgba(var(--fest-veil, 0, 0, 0), .07);
}
.fest-admin-cal__num {
    font-size: .78rem;
    font-weight: 600;
    color: var(--muted, #888);
    width: 1.65rem;
    height: 1.65rem;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 50%;
    margin-bottom: .05rem;
}
.fest-admin-cal__cell--today .fest-admin-cal__num {
    background: var(--fest-pink, #ff4d8d);
    color: #fff;
}
.fest-admin-events {
    display: flex;
    flex-direction: column;
    gap: .14rem;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
}
.fest-admin-event {
    display: flex;
    align-items: center;
    gap: .28rem;
    border-radius: 4px;
    padding: .16rem .38rem .16rem .28rem;
    border-left: 3px solid transparent;
    text-decoration: none;
    color: inherit;
    font-size: .72rem;
    line-height: 1.3;
    overflow: hidden;
    white-space: nowrap;
    transition: filter .12s ease;
}
.fest-admin-event:hover { filter: brightness(.9); text-decoration: none; }
.fest-admin-event__dot {
    flex-shrink: 0;
    width: 6px;
    height: 6px;
    border-radius: 50%;
}
.fest-admin-event__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 500;
}
.fest-admin-event--more {
    font-size: .67rem;
    color: var(--muted, #888);
    padding: .1rem .28rem;
    background: none;
    border-left-color: transparent;
    cursor: default;
    font-style: italic;
}
.fest-admin-event--more:hover { filter: none; }

/* Festivals (bandeaux pleins, en tête de cellule) */
.fest-admin-festivals {
    display: flex;
    flex-direction: column;
    gap: .14rem;
    min-width: 0;
}
.fest-admin-festival {
    display: flex;
    flex-direction: column;
    gap: .05rem;
    border-radius: 5px;
    padding: .14rem .4rem;
    background: var(--fest-accent, #e11d48);
    color: #fff;
    text-decoration: none;
    font-size: .72rem;
    line-height: 1.25;
    overflow: hidden;
    transition: filter .12s ease;
}
.fest-admin-festival:hover { filter: brightness(.92); text-decoration: none; color: #fff; }
/* Festival multi-jours : prolonge la barre vers les jours suivants/précédents */
.fest-admin-festival--span { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.fest-admin-festival--cont { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.fest-admin-festival__name {
    display: flex;
    align-items: center;
    gap: .3rem;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.fest-admin-festival__name i { flex-shrink: 0; font-size: .9em; opacity: .9; }
.fest-admin-festival__time {
    font-size: .66rem;
    font-weight: 600;
    opacity: .9;
    white-space: nowrap;
}

@media (max-width: 700px) {
    .fest-admin-cal__grid { grid-auto-rows: minmax(64px, auto); }
    .fest-admin-cal__cell { padding: .2rem .22rem .25rem; min-height: 64px; gap: .1rem; }
    .fest-admin-cal__num { font-size: .68rem; width: 1.4rem; height: 1.4rem; }
    .fest-admin-cal__dow { font-size: .6rem; padding: .45rem .1rem; }
    .fest-admin-event { font-size: .62rem; padding: .1rem .2rem .1rem .18rem; border-left-width: 2px; }
    .fest-admin-event__dot { width: 5px; height: 5px; }
    .fest-admin-event--more { font-size: .6rem; }
    .fest-admin-festival { font-size: .6rem; padding: .1rem .22rem; border-radius: 4px; }
    .fest-admin-festival__time { font-size: .56rem; }
    .fest-admin-cal-nav__title { font-size: .95rem; min-width: 10rem; }
}

/* Liste compacte (cartes-cadeaux, codes promo) */
.fest-compact-list {
    display: flex;
    flex-direction: column;
    gap: .3rem;
}
.fest-compact-item {
    border: 1px solid rgba(var(--fest-veil, 0, 0, 0), .09);
    border-radius: 10px;
    background: var(--surface, #fff);
    overflow: hidden;
}
.fest-compact-item__summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: .6rem .85rem;
    user-select: none;
}
.fest-compact-item__summary::-webkit-details-marker { display: none; }
.fest-compact-item__summary:hover { background: rgba(var(--fest-veil, 0, 0, 0), .025); }
.fest-compact-item[open] > .fest-compact-item__summary {
    border-bottom: 1px solid rgba(var(--fest-veil, 0, 0, 0), .08);
    background: rgba(var(--fest-veil, 0, 0, 0), .02);
}
.fest-compact-item__chevron {
    flex-shrink: 0;
    width: .85rem;
    height: .85rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    opacity: .4;
    position: relative;
}
.fest-compact-item__chevron::before {
    content: '';
    display: block;
    width: 0;
    height: 0;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    border-left: 6px solid currentColor;
    transition: transform .18s ease;
}
.fest-compact-item[open] > .fest-compact-item__summary .fest-compact-item__chevron::before {
    transform: rotate(90deg);
}
.fest-compact-item__code {
    font-family: monospace;
    font-size: .82rem;
    font-weight: 600;
    background: rgba(var(--fest-veil, 0, 0, 0), .06);
    border-radius: 5px;
    padding: .12rem .42rem;
    flex-shrink: 0;
    letter-spacing: .02em;
}
.fest-compact-item__label {
    flex: 1 1 auto;
    font-size: .88rem;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.fest-compact-item__meta {
    font-size: .78rem;
    opacity: .6;
    flex-shrink: 0;
    white-space: nowrap;
}
.fest-compact-item__meta--strong {
    font-weight: 700;
    opacity: .85;
    color: var(--fest-pink, #ff4d8d);
}
.fest-compact-item__badge {
    font-size: .7rem;
    font-weight: 600;
    padding: .18rem .48rem;
    border-radius: 999px;
    flex-shrink: 0;
    white-space: nowrap;
}
.fest-compact-item__badge--on {
    background: rgba(3,127,76,0.13);
    color: var(--fest-ok, #037f4c);
}
.fest-compact-item__badge--off {
    background: rgba(var(--fest-veil, 0, 0, 0), .07);
    color: var(--muted, #888);
}
.fest-compact-item__actions {
    display: flex;
    align-items: center;
    gap: .2rem;
    flex-shrink: 0;
    margin-left: .25rem;
}
.fest-compact-item__actions form { margin: 0; }
.fest-compact-item__body {
    padding: .85rem 1rem 1rem;
}

@media (max-width: 640px) {
    .fest-compact-item__meta { display: none; }
    .fest-compact-item__summary { gap: .4rem; padding: .55rem .65rem; }
    .fest-compact-item__code { font-size: .75rem; }
    .fest-compact-item__label { font-size: .83rem; }
}

/* Panneau d'abonnement WebCal */
.fest-webcal-panel { overflow: hidden; }
.fest-webcal-panel__summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: .9rem 1.1rem;
    font-weight: 600;
    user-select: none;
}
.fest-webcal-panel__summary::-webkit-details-marker { display: none; }
.fest-webcal-panel__summary i { font-size: 1.15rem; color: var(--fest-pink, #ff4d8d); }
.fest-webcal-panel__hint {
    margin-left: auto;
    font-size: .78rem;
    font-weight: 400;
    opacity: .6;
}
.fest-webcal-panel[open] .fest-webcal-panel__summary {
    border-bottom: 1px solid rgba(var(--fest-veil, 0, 0, 0), .08);
}
.fest-webcal-panel__body {
    padding: 1.1rem 1.25rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: .85rem;
}
.fest-webcal-panel__desc { margin: 0; font-size: .92rem; line-height: 1.5; }
.fest-webcal-panel__actions {
    display: flex;
    align-items: center;
    gap: .85rem;
    flex-wrap: wrap;
}
.fest-webcal-btn {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
}
.fest-webcal-panel__or {
    font-size: .85rem;
    opacity: .65;
    margin: 0;
}
/* Boutons à gauche, code QR à droite — même disposition que le panneau du dojo.
   Le QR passe sous les boutons quand la colonne devient trop étroite. */
.fest-webcal-panel__share {
    display: flex;
    gap: 1.25rem;
    align-items: center;
    flex-wrap: wrap;
}
.fest-webcal-panel__share-copy {
    flex: 1 1 18rem;
    display: flex;
    flex-direction: column;
    gap: .6rem;
}
.fest-webcal-panel__qr-wrap {
    margin: 0;
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .4rem;
    max-width: 12.5rem;
}
.fest-webcal-panel__qr {
    width: 12.5rem;
    height: auto;
    border-radius: 10px;
    /* Un code QR se lit par contraste : fond blanc imposé, y compris en thème
       sombre où l'image PNG noire sur blanc resterait sinon cernée de gris. */
    background: #fff;
    padding: .5rem;
    border: 1px solid rgba(var(--fest-veil, 0, 0, 0), .09);
}
.fest-webcal-panel__qr-wrap figcaption {
    font-size: .76rem;
    opacity: .65;
    text-align: center;
    line-height: 1.35;
}
.fest-webcal-url-row {
    display: flex;
    gap: .5rem;
    align-items: center;
}
.fest-webcal-url-input {
    flex: 1 1 auto;
    font-family: monospace;
    font-size: .8rem;
    background: rgba(var(--fest-veil, 0, 0, 0), .03);
    cursor: text;
}
.fest-webcal-copy-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    white-space: nowrap;
}
.fest-webcal-panel__instructions {
    border: 1px solid rgba(var(--fest-veil, 0, 0, 0), .09);
    border-radius: 10px;
    overflow: hidden;
}
.fest-webcal-panel__instructions details {
    border-bottom: 1px solid rgba(var(--fest-veil, 0, 0, 0), .07);
}
.fest-webcal-panel__instructions details:last-child { border-bottom: 0; }
.fest-webcal-panel__instructions summary {
    cursor: pointer;
    padding: .6rem .85rem;
    font-size: .85rem;
    font-weight: 600;
    list-style: none;
    background: rgba(var(--fest-veil, 0, 0, 0), .02);
}
.fest-webcal-panel__instructions summary::-webkit-details-marker { display: none; }
.fest-webcal-panel__instructions summary:hover { background: rgba(var(--fest-veil, 0, 0, 0), .04); }
.fest-webcal-panel__instructions ol {
    margin: .5rem 0 .75rem;
    padding-left: 1.8rem;
    font-size: .85rem;
    line-height: 1.6;
}
.fest-webcal-panel__instructions code {
    background: rgba(var(--fest-veil, 0, 0, 0), .07);
    border-radius: 4px;
    padding: .1rem .35rem;
    font-size: .8rem;
}
.fest-webcal-panel__warning {
    margin: 0;
    font-size: .8rem;
    opacity: .65;
    display: flex;
    align-items: flex-start;
    gap: .4rem;
}
.fest-webcal-panel__warning i { flex-shrink: 0; margin-top: .1rem; }

@media (max-width: 560px) {
    .fest-webcal-url-row { flex-direction: column; align-items: stretch; }
    .fest-webcal-panel__actions { flex-direction: column; align-items: flex-start; }
    .fest-webcal-panel__hint { display: none; }
}

/* ---------------------------------------------------------------------------
   5) Animations
   --------------------------------------------------------------------------- */
@keyframes fest-pop { 0%,100% { transform: translateY(0) rotate(-4deg); } 50% { transform: translateY(-3px) rotate(4deg); } }
@keyframes fest-float { 0%,100% { transform: translateY(0) rotate(-6deg); } 50% { transform: translateY(-16px) rotate(6deg); } }
@keyframes fest-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
    body.is-festivites .site-header .brand-mark,
    body.festif-public .site-header .brand-mark,
    body.festif-public .fest-marquee__track { animation: none; }
    body.festif-public .content-card, body.festif-public .bento-card, body.festif-public .button.primary { transition: none; }
}

/* ─────────────────────────────────────────────
   Messages flash lisibles sur le thème festif
   (le fond est sombre et la couleur de texte par défaut est claire :
   on force un fond clair + texte foncé à fort contraste).
   ───────────────────────────────────────────── */
body.festif-public .flash,
body.is-festivites .flash {
    font-weight: 600;
    border: 1px solid rgba(var(--fest-veil, 0, 0, 0), 0.12);
}
body.festif-public .flash-success,
body.is-festivites .flash-success {
    background: #d8f7e4;
    color: #0d4a2b;
    border-color: rgba(13, 74, 43, 0.25);
}
body.festif-public .flash-warning,
body.is-festivites .flash-warning {
    background: #fff2c2;
    color: #6b4e00;
    border-color: rgba(107, 78, 0, 0.3);
}
body.festif-public .flash-error,
body.is-festivites .flash-error {
    background: #ffd9dd;
    color: #8a0f1e;
    border-color: rgba(138, 15, 30, 0.3);
}

/* ===========================================================================
   5) Optimisation mobile & iOS (vitrine + back-office)
   Le socle global (styles.css) gère déjà .table-wrap, les cibles 44px et les
   safe-area. On complète ici ce qui est propre aux Festivités.
   =========================================================================== */

/* ── En-tête du back-office sur UNE rangée au téléphone ───────────────────
   Sous ~436 px, la barre d'outils (« Voir le site » + commutateur d'apparence +
   profil, 242 px à elle seule) ne tenait plus à côté du hamburger et de la
   marque : elle basculait sur une rangée à elle, vide aux deux tiers. Un
   en-tête COLLANT de 120 px sur un écran de 844, pour trois boutons.

   ⚠️ On rogne les deux postes les plus chers, pas le contenu : la pastille
   confetti (43 px — c'est déjà ce que fait le socle pour le dojo dès 960 px, et
   le nom « Festifun » reste écrit à côté) et les marges internes du commutateur,
   dont les quatre segments ne portent plus que leurs glyphes sous 1100 px.
   Personne ne perd d'action ni de cible tactile : les 44 px des boutons sont
   intacts, seul l'espace autour des icônes se resserre.

   ⚠️ Sous ~340 px (iPhone SE de 1re génération), la barre repasse sur deux
   rangées — c'est le comportement d'avant, pas une régression : la seule façon
   de l'éviter serait de retirer une commande. */
@media (max-width: 480px) {
    body.is-festivites .site-header .brand .brand-mark {
        display: none;
    }

    /* ⚠️ Sans ceci, masquer la pastille ne changeait RIEN : sous 480 px le socle
       passe la barre en `flex` et donne au nom `flex: 1 1 55%` avec un
       `min-width` de 8.5 rem — exprès, pour que la barre d'outils CHARGÉE du
       dojo (thème + aperçu + écoles + profil + « Gérer ») retombe d'elle-même
       sur sa rangée. Celle de Festifun pèse trois fois moins (207 px) et tient
       à côté du nom ; c'est le nom qui, en réclamant 55 % de la barre, la
       faisait retomber quand même. On rend donc le nom à sa largeur propre —
       la barre d'outils garde son `margin-left: auto`, donc reste à droite. */
    body.is-festivites .site-header .brand {
        flex: 0 1 auto;
        min-width: 0;
    }

    body.is-festivites .site-header .toolbar {
        gap: 0.4rem;
    }

    /* ⚠️ Le JETON, jamais `padding` en direct : la pastille se positionne en
       `calc()` sur ce rembourrage (voir `.theme-switch` dans `styles.css`).
       Resserrer la rangée par `padding` la laissait calée sur 3 px. */
    body.is-festivites .site-header .theme-switch {
        --theme-switch-pad: 2px;
    }

    body.is-festivites .site-header .theme-switch__opt {
        padding: 0.36rem;
    }

    /* La marge droite du lien servait à écarter le commutateur ; c'est le `gap`
       de la barre d'outils qui s'en charge maintenant. */
    body.is-festivites .site-header .fest-view-site {
        margin-right: 0;
    }
}

/* Encoche iOS en paysage : le back-office a la même barre collante que la
   vitrine (dont la règle est plus bas), et la même raison de l'écarter du bord
   arrondi. */
@supports (padding: max(0px)) {
    body.is-festivites .site-header .topbar {
        padding-left: max(0px, env(safe-area-inset-left));
        padding-right: max(0px, env(safe-area-inset-right));
    }
}

/* ── Deux rangées d'actions dans un en-tête de page ───────────────────────
   Les actions principales sur une ligne, les secondaires (import/export) sur la
   suivante, le tout aligné à droite. Écrit en `style=""` sur la page Catalogue,
   donc INDÉPLIABLE sur téléphone : un attribut en ligne bat toute media query,
   et le socle ne pouvait pas appliquer sa règle d'empilement — les quatre
   boutons restaient serrés à droite, deux par deux, sur des largeurs
   différentes. Même raison que `.fest-form-grid` plus bas. */
.fest-header-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.5rem;
}

.fest-header-actions__row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
}

@media (max-width: 768px) {
    /* Même seuil que le socle, qui passe déjà `.page-header` en colonne et
       étire ses actions sur toute la largeur (`.page-header-actions`). */
    .fest-header-actions {
        align-items: stretch;
        width: 100%;
    }

    /* ⚠️ Le bouton « Import JSON » est un `<label>` DANS un `<form>` (il ouvre
       un sélecteur de fichier) : c'est le formulaire qui est l'élément flexible
       de la rangée, le label doit donc s'étirer dedans — sans la seconde règle,
       cette action seule restait à sa largeur de texte. */
    .fest-header-actions__row > .button,
    .fest-header-actions__row > form {
        flex: 1 1 auto;
        justify-content: center;
    }

    .fest-header-actions__row > form > .button {
        display: flex;
        width: 100%;
        justify-content: center;
    }
}
/* Grille de formulaire 2 colonnes, repliée sur 1 colonne en téléphone.
   Remplace les `display:grid;grid-template-columns:1fr 1fr` posés en ligne
   (qui, eux, ne pouvaient pas se replier). */
.fest-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
@media (max-width: 560px) {
    .fest-form-grid { grid-template-columns: 1fr; }
}

/* iOS Safari zoome au focus si la police d'un champ est < 16px. Les pages
   festivités utilisent .form-control (0.95rem) : on force 16px sous 640px. */
@media (max-width: 640px) {
    body.is-festivites :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea, .form-control),
    body.festif-public :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea, .form-control) {
        font-size: 16px;
    }
}

/* Encoche iOS : on étend les marges de sécurité à la barre collante et au pied
   de la vitrine sombre (mode paysage avec notch, bord arrondi). */
@supports (padding: max(0px)) {
    body.festif-public .site-header .topbar {
        padding-left: max(0px, env(safe-area-inset-left));
        padding-right: max(0px, env(safe-area-inset-right));
    }
    body.festif-public .site-footer {
        padding-bottom: max(0px, env(safe-area-inset-bottom));
    }
}

/* Vitrine — en-tête en flex explicite : marque à gauche, puis le groupe de
   liens (catalogue, puis profil) poussé à droite. On évite ainsi l'auto-
   placement de la grille `.topbar` globale qui renvoyait la marque à droite. */
body.festif-public .site-header .topbar {
    display: flex;
    align-items: center;
    gap: .75rem;
}
body.festif-public .site-header .brand { margin-right: auto; }
body.festif-public .topbar-nav {
    display: flex;
    align-items: center;
    gap: .75rem;
}
/* Le CTA n'a plus besoin de se pousser lui-même : le groupe est déjà à droite. */
body.festif-public .topbar-nav .header-cta { margin-left: 0; }
/* Lien « Mon compte » réduit à l'icône : la rendre plus grande et bien centrée. */
body.festif-public .header-link { display: inline-flex; align-items: center; line-height: 1; }
body.festif-public .header-link i { font-size: 1.75rem; }

@media (max-width: 640px) {
    /* Vitrine — en-tête : laisser les liens passer sous la marque si l'espace
       manque, plutôt que de déborder ou de rogner le CTA. */
    body.festif-public .site-header .topbar { flex-wrap: wrap; row-gap: .45rem; }
    body.festif-public .topbar-nav { gap: .5rem; flex-wrap: wrap; }
    body.festif-public .header-link { font-size: .85rem; }
    body.festif-public .header-cta { padding: .5rem .9rem; font-size: .85rem; }

    /* Cartes « offres » (bento) : les images décoratives débordent sur les côtés
       et ne sont visibles qu'en sliver une fois rognées sur téléphone. On les
       masque et on laisse le panneau se compacter (au lieu d'un grand carré). */
    body.festif-public .bento-card__img { display: none; }
    body.festif-public .bento-card__panel {
        aspect-ratio: auto; min-height: 7rem; overflow: hidden;
    }
    /* Sans image, chaque carte devient un aplat plat et monotone : on redonne du
       caractère avec un emoji festif décoratif en filigrane (cycle de 4, calé sur
       le cycle de couleurs, comme dans le hero). */
    body.festif-public .bento-card__panel::after {
        position: absolute; right: .5rem; bottom: .2rem;
        font-size: 3.4rem; line-height: 1; opacity: .26; pointer-events: none;
    }
    body.festif-public .bento-card:nth-child(4n+1) .bento-card__panel::after { content: "🎈"; }
    body.festif-public .bento-card:nth-child(4n+2) .bento-card__panel::after { content: "🎉"; }
    body.festif-public .bento-card:nth-child(4n+3) .bento-card__panel::after { content: "🎪"; }
    body.festif-public .bento-card:nth-child(4n)   .bento-card__panel::after { content: "🎂"; }

    /* ⚠️ Les pastilles du bandeau festif ont suivi le hero dans le socle : leur
       repli sur écran étroit vit avec elles, dans styles.css. */
    body.festif-public .hero-actions { flex-direction: column; }
    body.festif-public .hero-actions .button { width: 100%; }

    /* Étapes : les numéros restent lisibles, padding réduit. */
    body.festif-public .step { padding: 1.2rem; }

    /* Tables denses du back-office : compacter pour limiter le défilement
       horizontal (elles restent dans .table-wrap si ça déborde). */
    body.is-festivites .data-table { font-size: .85rem; }
    body.is-festivites .data-table :is(th, td) { padding: .5rem .55rem; }

    /* Actions par ligne : boutons confortables qui passent à la ligne. */
    body.is-festivites .fest-row-actions .button,
    body.is-festivites .data-table td .button { min-height: 38px; }
}

/* Cibles tactiles agrandies sur écrans sans souris (vignettes d'images).
   Le calendrier/les chips de dates ont les leurs dans catalog_calendar.css. */
@media (pointer: coarse) {
    .fest-img-tile__star,
    .fest-img-tile__delete { width: 32px; height: 32px; }
}

/* ---------------------------------------------------------------------------
   Rapports Festifun (rendement `festival_rapport.html` + réconciliation
   d'inventaire `festival_reconciliation.html`). Lisibilité mobile/iOS :
   tuiles KPI fluides, tables défilantes, actions empilées, cibles 44px, print.
   Portée `.fest-report` (posée sur le <section> racine des deux pages).
   --------------------------------------------------------------------------- */
.fest-report-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 1rem;
    margin-bottom: 1.25rem;
}
/* Les tables du rapport défilent horizontalement dans .table-wrap (patron admin
   global : `inline-size: max-content` sur la table). On garde les EN-TÊTES sur
   une seule ligne pour éviter le repli illisible (« VENTE / S ») et déclencher
   un vrai scroll horizontal ; les cellules de noms, elles, se replient toujours.
   La classe `.data-table` sur la table nous fait passer devant la règle globale
   `.is-admin .content-card .table-wrap th` (même nb de classes + festivites.css
   chargé après → priorité), sans recourir à !important. */
.fest-report .content-card .table-wrap table.data-table th { white-space: nowrap; }

/* Listes de modificateurs (gabarit partagé `_modifiers_table.html`) : le rapport d'un
   festival ET le bilan global les affichent, l'habillage n'est donc pas dans une
   balise <style> de page. Portée volontairement NON préfixée par `.fest-report` :
   le bilan global est une page `.wperf`.
   `auto-fit` + `minmax` : côte à côte sur la page large du bilan global, empilées
   dans la demi-colonne du rapport d'un festival — c'est la largeur du CONTENEUR qui
   décide, pas celle de l'écran. */
.fest-mod-lists {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
    gap: 1rem 1.5rem;
    margin-top: .6rem;
}
.fest-mod-list { min-width: 0; }
.fest-mod-title {
    margin: 0;
    font-size: 1rem;
    display: flex;
    align-items: center;
    gap: .35rem;
}
.fest-mod-title i { color: var(--muted, #6b7280); }
.fest-mod-count { font-weight: 400; font-size: .85rem; }

/* Groupe Square du modificateur, en suffixe discret du nom. Assez présent pour
   situer l'option (« Extra fromage » — de quelle liste ?), assez effacé pour qu'on
   lise d'abord le nom. Le séparateur est en `::before` : pas de « · » orphelin dans
   le texte copié-collé ni dans un tri de colonne. */
.fest-mod-group {
    color: var(--muted, #6b7280);
    font-size: .82em;
    white-space: nowrap; /* le groupe passe à la ligne entier, jamais coupé en deux */
}
/* ⚠️ Espaces **insécables** dans le `content` : des espaces ordinaires en début et
   fin d'une boîte inline sont supprimés au rendu (white-space collapsing) — vérifié
   à l'écran, le nom et le groupe se retrouvaient collés (« Extra fromageGarnitures »). */
.fest-mod-group::before { content: "\00a0·\00a0"; }

/* Liste ENTIÈRE dans une hauteur bornée : une vingtaine de lignes visibles, le reste
   au défilement. ⚠️ `max-height` et non `height` — la barre n'apparaît que s'il y a
   plus à voir, une carte de 6 modificateurs ne se retrouve pas dans un cadre à moitié
   vide. Et `--mod-rows` est un repère, pas une promesse au pixel : un nom long passe
   sur deux lignes. Ce qui est garanti, c'est qu'aucune ligne n'est retirée. */
.fest-mod-scroll {
    --mod-rows: 20;
    --mod-row-h: 1.75rem;   /* hauteur mesurée d'une ligne à nom court */
    --mod-chrome-h: 5.25rem; /* en-tête + ligne « Total », tous deux collés */
    max-height: calc(var(--mod-rows) * var(--mod-row-h) + var(--mod-chrome-h));
    overflow-y: auto;
}
/* En-tête et ligne « Total » collés pendant le défilement : sans eux on fait défiler
   une colonne de chiffres sans savoir laquelle, et le total sortirait de l'écran dès
   la 21ᵉ ligne. ⚠️ Fond **opaque obligatoire** : `--bg-elevated` vaut
   `rgba(255,255,255,.94)` (et `rgba(7,14,30,.9)` en sombre), donc posé seul il laisse
   lire les lignes qui défilent dessous — vérifié à l'écran. On l'empile sur `--bg`,
   ce qui garde la teinte du thème tout en fermant la transparence. ⚠️ Bordures en
   `box-shadow` : sur une table `border-collapse:collapse`, la bordure d'une cellule
   collée reste au fil du tableau au lieu de suivre le défilement. */
.fest-mod-scroll thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: linear-gradient(var(--bg-elevated), var(--bg-elevated)), var(--bg, #fff);
    box-shadow: inset 0 -1px 0 var(--line, rgba(0, 0, 0, .1));
}
.fest-mod-scroll tfoot td {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: linear-gradient(var(--bg-elevated), var(--bg-elevated)), var(--bg, #fff);
    box-shadow: inset 0 2px 0 var(--line, rgba(0, 0, 0, .1));
}

/* Sélecteur de colonnes « Les deux · Quantités · Ventes » (`fest_metric_toggle.js`).
   ⚠️ La règle `[hidden]` est explicite : l'attribut ne suffit pas à masquer une
   cellule de tableau si une feuille de style lui impose un `display`. */
.fest-metric-filter { display: inline-flex; gap: .25rem; flex-wrap: wrap; }
.data-table [data-metric][hidden] { display: none; }

/* En-tête de ligne (comparatif journalier : « Ventes », « Transactions »…) :
   non tronqué, l'icône Phosphor alignée sur le libellé. */
.fest-daily-table th[scope="row"] { white-space: nowrap; }
.fest-daily-table th[scope="row"] i { margin-right: .3rem; color: var(--fest-pink, #ff4d8d); }

/* Par mode de paiement : icône + libellé, et barre de part (%) alignée à droite. */
.fest-payment-table .fest-pay-mode { display: inline-flex; align-items: center; gap: .45rem; }
.fest-payment-table .fest-pay-icon { font-size: 1.15rem; color: var(--fest-pink, #ff4d8d); flex: 0 0 auto; }
.fest-payment-table .fest-pay-share { display: flex; align-items: center; gap: .5rem; min-width: 130px; }
.fest-payment-table .fest-pay-bar {
    position: relative;
    flex: 1 1 auto;
    height: .5rem;
    min-width: 48px;
    border-radius: 999px;
    background: var(--line, rgba(0,0,0,.08));
    overflow: hidden;
}
.fest-payment-table .fest-pay-bar > span {
    position: absolute;
    inset: 0 auto 0 0;
    height: 100%;
    border-radius: 999px;
    background: var(--fest-grad, var(--fest-pink, #ff4d8d));
}
.fest-payment-table .fest-pay-pct {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: var(--muted, #6b7280);
    min-width: 3.4rem;
    text-align: right;
}

/* Cibles tactiles iOS HIG (44px) sur les boutons d'action des rapports. */
@media (pointer: coarse) {
    .fest-report .page-header-actions .button,
    .fest-report .fest-report-actions .button,
    .fest-report .fest-report-actions button {
        min-height: 44px;
    }
}

@media (max-width: 640px) {
    /* KPIs : au moins deux tuiles par ligne sur téléphone (évite des tuiles
       étirées), une seule si l'écran est très étroit. */
    .fest-report-kpis { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .75rem; }
    .fest-report-kpis .content-card { padding: .9rem .75rem; }

    /* Barres d'actions empilées pleine largeur (rafraîchir/export/réconcilier ;
       appliquer/annuler). La 1re action « Rafraîchir » est un <form> → l'étirer
       aussi pour qu'elle occupe toute la ligne comme les autres. */
    .fest-report-actions { width: 100%; }
    .fest-report-actions > * { flex: 1 1 100%; }
    .fest-report-actions form { display: flex; }
    .fest-report-actions form .button,
    .fest-report-actions .button { width: 100%; }
}

/* Impression : masquer les actions, aplatir les cartes pour une sortie papier
   nette (le bloc @media print plus bas masque déjà .site-header /
   .page-header-actions ; on complète ici pour le rapport). */
@media print {
    .fest-report-actions { display: none; }
    .fest-report .content-card { box-shadow: none; border: 1px solid #ccc; break-inside: avoid; }
    /* Sur papier il n'y a pas de barre de défilement : une hauteur bornée couperait
       purement et simplement les modificateurs au-delà de la 20ᵉ ligne. */
    .fest-mod-scroll { max-height: none; overflow: visible; }
}

/* ---------------------------------------------------------------------------
   Météo & rendement (festival_meteo_rendement.html) — page analytique qui
   croise l'archive météo et les rapports de ventes. Portée `.wperf` sur la
   <section> racine ; réutilise le socle `.fest-report` (KPIs fluides, tables
   défilantes, cibles 44px, print).
   --------------------------------------------------------------------------- */

/* Rouge/vert LISIBLES pour les écarts (+/- %). ⚠️ Les tokens --danger/--success
   du thème sont des teintes de FOND pastel (#ffd9d9 / #d9f0e5) : illisibles en
   texte, et leurs variantes `-strong` virent au gris désaturé. On définit donc
   ici de vrais tons sémantiques, déclinés clair/sombre comme --field-error. */
.wperf {
    --wperf-neg: #c0392b;
    --wperf-pos: #1a7a4c;
}
html[data-theme="dark"] .wperf,
html[data-theme="aurora"] .wperf,
html[data-theme="sunset-dark"] .wperf,
html[data-theme="paper-ink-dark"] .wperf,
html[data-theme="vampire"] .wperf,
html[data-theme="bamboo-dark"] .wperf,
html[data-theme="iris-dark"] .wperf {
    --wperf-neg: #ff8f80;
    --wperf-pos: #5fd39a;
}
.wperf .delta-pos { color: var(--wperf-pos); font-weight: 700; }
.wperf .delta-neg { color: var(--wperf-neg); font-weight: 700; }
.wperf .delta-none { color: var(--muted, #6b7280); }

/* Cellules numériques : alignées à droite, chiffres tabulaires et JAMAIS
   coupées (fini « 40.2 / mm » ou « 2295.59 / $ » sur deux lignes). */
.wperf .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Tables lisibles : en-têtes sur une ligne, lignes zébrées et surlignage au
   survol. La classe `.data-table` passe devant la règle admin globale sans
   !important (même spécificité + festivites.css chargé après). */
.wperf .data-table { width: 100%; border-collapse: collapse; }
.wperf .data-table :is(th, td) { padding: .5rem .6rem; }
.wperf .data-table thead th {
    text-align: left;
    white-space: nowrap;
    border-bottom: 1px solid var(--line, rgba(0,0,0,.12));
    font-size: .82rem;
    letter-spacing: .01em;
    color: var(--muted, #6b7280);
}
.wperf .data-table tbody tr { border-bottom: 1px solid color-mix(in srgb, var(--line, rgba(0,0,0,.1)) 60%, transparent); }
.wperf .data-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--line, rgba(0,0,0,.06)) 45%, transparent); }
.wperf .data-table tbody tr:hover { background: color-mix(in srgb, var(--accent-soft, #d8e8ff) 35%, transparent); }

/* Nom de festival + date : la date passe sous le nom, en muted, non collée. */
.wperf .wperf-day-date { display: block; font-size: .82em; color: var(--muted, #6b7280); }

/* Année d'une journée : discrète (le jour reste la donnée lue), mais présente —
   ce tableau enchaîne plusieurs saisons, et « ven. 18 juil. » seul ne dit pas
   laquelle. */
.wperf .wperf-year { font-size: .85em; color: var(--muted, #6b7280); }

/* « Par température » : icône de tranche + barre comparative sous les ventes
   moyennes (largeur = % du meilleur rendement). La barre est décorative
   (aria-hidden au gabarit) : le chiffre reste la donnée, et le tri lit
   data-sort-value sur la cellule. */
.wperf .wperf-bucket { white-space: nowrap; }
.wperf .wperf-bucket .ph { color: var(--muted, #6b7280); margin-right: .15rem; vertical-align: -.1em; }
.wperf .wperf-bar {
    display: block;
    margin-top: .3rem;
    height: 5px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--line, rgba(0,0,0,.12)) 55%, transparent);
    overflow: hidden;
}
.wperf .wperf-bar__fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--accent, #1f62c4);
    min-width: 3px;
}

/* Note de bas de section (seuils, avertissements d'échantillon). */
.wperf .wperf-note { margin: .85rem 0 0; font-size: .88em; }

/* « Ce qui bouge avec les ventes » : un facteur par bloc, lisible à toute
   largeur. Grille en 2 colonnes — flèche colorée (sens) à gauche, contenu à
   droite sur DEUX lignes : le facteur en gras, puis « effet · force · r » qui se
   replient proprement sous le facteur (jamais de coefficient qui flotte au loin
   comme avec l'ancien `margin-left:auto`). Remplace la formule touffue
   « (hausse des ventes quand ça monte) ». */
.wperf .wperf-corr { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .75rem; }
.wperf .wperf-corr__row { display: grid; grid-template-columns: 1.6rem 1fr; column-gap: .5rem; row-gap: .1rem; align-items: start; }
.wperf .wperf-corr__dir { grid-column: 1; grid-row: 1 / span 2; text-align: center; font-size: 1.2rem; line-height: 1.25; }
.wperf .wperf-corr__label { grid-column: 2; grid-row: 1; font-weight: 700; }
.wperf .wperf-corr__meta { grid-column: 2; grid-row: 2; display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .5rem; }
.wperf .wperf-corr__effect { color: var(--text); }
.wperf .wperf-corr__strength { color: var(--muted, #6b7280); font-size: .9em; }
.wperf .wperf-corr__r { color: var(--muted, #6b7280); font-size: .8em; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Séparateur « · » entre effet et force, mais PAS devant le coefficient r : sur
   mobile r se replie souvent seul, et un « · » orphelin en début de ligne est
   laid. r n'a donc qu'un espace (gap flex) devant lui — repli net « r -0.38 ». */
.wperf .wperf-corr__meta > span + span:not(.wperf-corr__r)::before { content: "·"; margin-right: .5rem; color: var(--muted, #6b7280); }

/* Net réel mis en avant (desktop comme mobile). */
.wperf .wperf-net { font-weight: 700; }

/* « Net réel par festival » sur téléphone : chaque ligne devient une CARTE lisible
   plutôt qu'un tableau à 7 colonnes qui déborde en scroll horizontal. En-têtes
   masqués (mais accessibles), libellé injecté devant chaque valeur via data-label,
   nom du festival en en-tête de carte. */
@media (max-width: 640px) {
    .wperf .wperf-table--stack thead {
        position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
        overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
    }
    /* Battre le patron admin `inline-size: max-content` (spécificité (0,4,1)) qui,
       sinon, étire la table au-delà de l'écran et pousse les valeurs hors-champ à
       droite. On force la table à remplir sa carte. */
    .wperf .content-card .table-wrap table.data-table.wperf-table--stack {
        display: block; inline-size: 100%; min-inline-size: 0; max-inline-size: 100%;
    }
    .wperf .wperf-table--stack tbody { display: block; width: 100%; }
    .wperf .wperf-table--stack tbody tr,
    .wperf .wperf-table--stack tbody tr:nth-child(even) {
        display: block;
        border: 1px solid var(--line, rgba(0,0,0,.14));
        border-radius: 12px;
        padding: .3rem .8rem .55rem;
        margin-bottom: .7rem;
        background: color-mix(in srgb, var(--line, rgba(0,0,0,.05)) 28%, transparent);
    }
    .wperf .wperf-table--stack tbody td {
        display: flex; justify-content: space-between; align-items: baseline;
        gap: 1rem; padding: .3rem 0; border: 0; text-align: right; white-space: nowrap;
    }
    .wperf .wperf-table--stack tbody td::before {
        content: attr(data-label);
        color: var(--muted, #6b7280); font-size: .85em; font-weight: 400;
        font-variant-numeric: normal; text-align: left; white-space: normal;
    }
    /* En-tête de carte : nom + date, pleine largeur, pas de libellé. */
    .wperf .wperf-table--stack tbody td.wperf-fest-name {
        display: block; text-align: left; white-space: normal;
        font-weight: 700; font-size: 1.05em;
        padding: .1rem 0 .45rem; margin-bottom: .35rem;
        border-bottom: 1px solid color-mix(in srgb, var(--line, rgba(0,0,0,.12)) 70%, transparent);
    }
    .wperf .wperf-table--stack tbody td.wperf-fest-name::before { content: none; }
    .wperf .wperf-table--stack tbody td.wperf-net { font-size: 1.08em; }
}

/* ---------------------------------------------------------------------------
   Comparatif côte à côte (comparatif.html) — N festivals EN COLONNES, les
   indicateurs en lignes. Portée `.fcmp`, posée avec `.wperf` : deltas, barres et
   tables viennent du socle analytique, on n'ajoute ici que ce qui est propre à
   une lecture horizontale.

   ⚠️ Ce tableau est fait pour DÉBORDER : à six festivals il est plus large que
   l'écran, et c'est voulu — on fait défiler les colonnes, jamais la première
   (le nom de l'indicateur), sinon on lit une rangée de chiffres sans savoir
   lesquels. D'où la colonne figée + l'en-tête collé.
   --------------------------------------------------------------------------- */

/* Accent de la page. ⚠️ `--accent` est le BLEU du portail dojo : le socle `.wperf` s'en
   sert pour ses barres et ses survols, et cette page-ci n'existe que dans Festifun, dont
   tout le reste (titre, en-tête, icônes de famille) est rose/orange — les pastilles, les
   cases retenues et les barres de proportion y arrivaient en bleu franc, plus voyantes
   que les chiffres qu'elles accompagnent. Un jeton local, pas une réécriture du socle :
   les deux autres écrans analytiques ne bougent pas.
   Les tons se dérivent de `--text` plutôt que d'être figés : le thème sombre les suit. */
.fcmp {
    --fcmp-accent: var(--fest-pink, #ff4d8d);
    --fcmp-accent-2: var(--fest-orange, #ff8a3d);
    --fcmp-wash: color-mix(in srgb, var(--fcmp-accent) 12%, transparent);
    --fcmp-wash-strong: color-mix(in srgb, var(--fcmp-accent) 18%, transparent);
    --fcmp-ink: color-mix(in srgb, var(--fcmp-accent) 55%, var(--text, #2a1640));
}

/* Barre de proportion : un REPÈRE sous le chiffre, pas la donnée — en bleu plein elle
   attirait l'œil avant lui. Dégradé de marque, aplati et adouci. */
.fcmp .wperf-bar { height: 4px; }
.fcmp .wperf-bar__fill {
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--fcmp-accent) 62%, transparent),
        color-mix(in srgb, var(--fcmp-accent-2) 62%, transparent));
}

/* Défilement HORIZONTAL par défaut : c'est lui qui permet à la première colonne de
   rester figée. ⚠️ Pas de hauteur bornée ici — quatre tableaux à ascenseur interne sur
   une même page, c'est quatre fois se demander où l'on est. */
.fcmp .fcmp-scroll { overflow-x: auto; }
/* Le tableau des indicateurs, lui, est long (25 lignes) : il prend son propre
   ascenseur vertical pour que les NOMS DES FESTIVALS restent collés en haut pendant
   qu'on descend les lignes. ⚠️ Ce ne peut pas être le défilement de la page : l'en-tête
   du site est déjà `position:sticky` avec un `z-index` supérieur, une ligne collée à
   `top:0` passerait dessous — et sa hauteur varie (mode compact, bandeau dev). */
.fcmp .fcmp-scroll--tall { max-height: 78vh; overflow-y: auto; }
.fcmp .fcmp-table { border-collapse: separate; border-spacing: 0; }
.fcmp .fcmp-table :is(th, td) { padding: .5rem .7rem; }

/* Première colonne figée + en-tête collé. ⚠️ Fond **opaque obligatoire** :
   `--bg-elevated` est semi-transparent (rgba), posé seul il laisserait lire les
   chiffres qui défilent dessous — même piège que `.fest-mod-scroll`. On l'empile
   donc sur `--bg`. ⚠️ Bordures en `box-shadow` : sur une cellule collée, une
   `border` reste au fil du tableau au lieu de suivre le défilement. */
/* ⚠️ Ordre d'empilement à respecter, sinon une ligne qui glisse **sous** l'en-tête
   le repeint par-dessus : un `position:sticky` sans `z-index` explicite se dessine
   après ceux à z-index plus bas (ordre du DOM). Vécu ici — le titre de famille
   (colspan pleine largeur, fond opaque) effaçait les noms de festivals de l'en-tête
   collé, qui n'étaient qu'à z-index 2.
     6 = coin haut-gauche de l'en-tête   5 = en-tête des colonnes
     3 = première colonne des lignes     2 = titre de famille   0 = cellules */
.fcmp .fcmp-table thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    vertical-align: bottom;
    background: linear-gradient(var(--bg-elevated), var(--bg-elevated)), var(--bg, #fff);
    box-shadow: inset 0 -1px 0 var(--line, rgba(0, 0, 0, .12));
}
.fcmp .fcmp-table .fcmp-head {
    position: sticky;
    left: 0;
    z-index: 3;
    text-align: left;
    white-space: normal;
    min-width: 12rem;
    background: linear-gradient(var(--bg-elevated), var(--bg-elevated)), var(--bg, #fff);
    box-shadow: inset -1px 0 0 var(--line, rgba(0, 0, 0, .12));
}
.fcmp .fcmp-table thead .fcmp-head { z-index: 6; }
.fcmp .fcmp-table tbody tr { border-bottom: 0; }
.fcmp .fcmp-table tbody td,
.fcmp .fcmp-table tfoot td {
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--line, rgba(0,0,0,.1)) 60%, transparent);
}
/* Le zébrage du socle traverserait la colonne figée (fond opaque) : on le retire
   ici et le survol suffit à suivre une ligne d'un bout à l'autre. */
.fcmp .fcmp-table tbody tr:nth-child(even) { background: none; }
.fcmp .fcmp-table tbody tr:hover :is(td, th) {
    background: var(--fcmp-wash);
}

/* En-tête d'une colonne : nom du festival, puis ses repères (date, lieu, jours).
   ⚠️ Le nom **passe à la ligne** (le socle `.wperf` impose `nowrap` aux en-têtes) :
   sinon « Festivités de Bonaventure » impose à lui seul la largeur de sa colonne, et
   huit festivals aux noms longs donnent un tableau qu'on ne fait plus que défiler.
   ⚠️ `white-space: normal` **seul ne change rien** : en `table-layout:auto` la colonne
   prend sa largeur de contenu maximal et le tableau déborde plutôt que de replier quoi
   que ce soit (mesuré : 1 785 px avec ou sans). C'est `max-width` qui déclenche le
   repli — 1 785 → 1 724 px à huit colonnes. On ne va pas jusqu'à `table-layout:fixed`
   (qui tiendrait dans l'écran) : il donne la même largeur à toutes les colonnes, donc
   tronquerait « 12 907,00 $ » dès qu'on en compare une douzaine. */
/* ⚠️ Le `nowrap` à battre n'est PAS celui du socle `.wperf` mais celui de
   `.fest-report .content-card .table-wrap table.data-table th` — spécificité (0,5,1),
   au-dessus de `.fcmp .fcmp-table thead th.fcmp-col` (0,3,1). La règle ci-dessous
   n'appliquait donc que son `max-width`, et « Festival Western de Nouvelle » débordait
   de sa colonne pour se peindre PAR-DESSUS le nom du festival suivant (en-tête sans
   `overflow`), sur les quatre tableaux de la page. D'où la chaîne complète : elle passe
   à (0,5,3). Mesuré : nom rendu sur 243 px dans une colonne de 219 px. */
.fest-report .content-card .table-wrap table.fcmp-table thead th.fcmp-col,
.fcmp .fcmp-table thead th.fcmp-col { white-space: normal; max-width: 12rem; }
.fcmp .fcmp-col { min-width: 8.5rem; }
.fcmp .fcmp-col__meta {
    display: block;
    margin-top: .2rem;
    font-weight: 400;
    font-size: .78em;
    line-height: 1.35;
    color: var(--muted, #6b7280);
    white-space: normal;
}
.fcmp .fcmp-tag {
    display: inline-block;
    margin-top: .15rem;
    padding: 0 .35rem;
    border-radius: 999px;
    font-size: .92em;
    background: color-mix(in srgb, var(--line, rgba(0,0,0,.12)) 70%, transparent);
}
.fcmp .fcmp-tag.is-warn { background: color-mix(in srgb, var(--wperf-neg) 22%, transparent); }

/* Colonne « Ensemble » : c'est un cumul, pas un festival — un liseré la sépare
   pour qu'on ne la lise pas comme une colonne de plus. */
.fcmp .fcmp-col--sum {
    box-shadow: inset 2px 0 0 var(--line, rgba(0, 0, 0, .14)),
                inset 0 -1px 0 color-mix(in srgb, var(--line, rgba(0,0,0,.1)) 60%, transparent);
    background: color-mix(in srgb, var(--line, rgba(0,0,0,.05)) 35%, transparent);
}
.fcmp .fcmp-table thead th.fcmp-col--sum {
    box-shadow: inset 2px 0 0 var(--line, rgba(0, 0, 0, .14)),
                inset 0 -1px 0 var(--line, rgba(0, 0, 0, .12));
}

/* Titre de famille d'indicateurs : une ligne pleine largeur dans le tableau.
   ⚠️ C'est le LIBELLÉ qui est collé à gauche, pas seulement la cellule : une cellule
   en `colspan` pleine largeur reste « à sa place » quand on défile vers la droite, et
   son texte, lui, part hors de l'écran — on lisait alors des familles d'indicateurs
   sans savoir laquelle. */
.fcmp .fcmp-group th {
    /* Volontairement NON positionnée : une cellule collée sans `z-index` se dessine
       après l'en-tête (ordre du DOM) et l'efface quand la ligne glisse dessous. */
    text-align: left;
    font-size: .95em;
    padding-top: 1rem;
    background: linear-gradient(var(--bg-elevated), var(--bg-elevated)), var(--bg, #fff);
}
.fcmp .fcmp-group__label { position: sticky; left: .7rem; display: inline-block; }
.fcmp .fcmp-group th .ph { color: var(--fest-pink, #ff4d8d); margin-right: .35rem; }

/* Gagnante / perdante d'une ligne. ⚠️ La couleur ne porte PAS l'information seule
   (daltonisme, impression noir et blanc) : le fond est doublé d'un liseré et le
   chiffre passe en gras — et rien n'est marqué quand la grandeur n'a pas de sens
   de lecture, ce que le gabarit décide. */
.fcmp .fcmp-table td.is-best,
.fcmp .fcmp-table td.is-worst { font-weight: 700; }
.fcmp .fcmp-table td.is-best {
    background: color-mix(in srgb, var(--wperf-pos) 16%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wperf-pos) 45%, transparent);
    color: var(--wperf-pos);
}
.fcmp .fcmp-table td.is-worst {
    background: color-mix(in srgb, var(--wperf-neg) 14%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wperf-neg) 40%, transparent);
    color: var(--wperf-neg);
}
.fcmp .fcmp-legend { display: inline-flex; align-items: center; gap: .3rem; margin-right: .9rem; }
.fcmp .fcmp-swatch { width: .85rem; height: .85rem; border-radius: 3px; display: inline-block; }
.fcmp .fcmp-swatch.is-best { background: color-mix(in srgb, var(--wperf-pos) 45%, transparent); }
.fcmp .fcmp-swatch.is-worst { background: color-mix(in srgb, var(--wperf-neg) 40%, transparent); }

/* Mesure secondaire empilée sous la principale (ventes sous la quantité, part
   sous le montant) : présente, mais qui ne dispute pas la lecture à la première. */
.fcmp .fcmp-share {
    display: block;
    font-size: .82em;
    font-weight: 400;
    color: var(--muted, #6b7280);
}
/* Unité ou groupe accolé au libellé d'une ligne (« / jour », « Garnitures »). */
.fcmp .fcmp-unit { display: block; font-size: .8em; font-weight: 400; color: var(--muted, #6b7280); }
.fcmp .fcmp-hint { color: var(--muted, #6b7280); font-size: .9em; margin-left: .2rem; }

/* Rythme vertical de la page. ⚠️ Il vivait en `style="margin-bottom:1.25rem"` recopié
   sur chacune des huit sections : une carte ajoutée sans la copie se collait à la
   suivante. Même chose pour le `margin-top:0` du titre de carte. */
.fcmp .content-card { margin-bottom: 1.25rem; }
/* Titre de carte au corps `h2` de la page (≈2,4 rem ici), c'était le plus gros texte de
   l'écran — plus gros que le titre de la page une fois défilé, et sans commune mesure
   avec les chiffres qu'il annonce. Une page d'analyse se lit par ses tableaux ; les
   titres ne sont que des repères. */
.fcmp .content-card > h2 {
    margin: 0 0 .35rem;
    font-size: 1.3rem;
    line-height: 1.25;
}
.fcmp .content-card > h2 .ph { color: var(--fcmp-accent); }
/* Chapeau d'un tableau (ce qu'il montre, comment le lire) : collé au tableau, détaché
   du titre, et d'un cran sous le corps de texte — c'est un mode d'emploi, pas la donnée. */
.fcmp .fcmp-lede { margin: .15rem 0 .85rem; font-size: .92rem; }

/* Notes de contexte (provenance des chiffres, vues partielles, avertissements).
   ⚠️ Elles étaient des `content-card` pleines : trois d'affilée repoussaient le
   tableau — la donnée de la page — sous la ligne de flottaison. Une note se lit en
   un coup d'œil et se saute ; elle n'a pas à peser autant qu'une section. */
.fcmp .fcmp-note {
    display: flex;
    align-items: flex-start;
    gap: .55rem;
    margin: 0 0 .7rem;
    padding: .6rem .85rem;
    border-radius: 12px;
    border-left: 3px solid color-mix(in srgb, var(--fcmp-accent) 55%, transparent);
    background: color-mix(in srgb, var(--bg-elevated, #fff) 70%, transparent);
    font-size: .93rem;
    line-height: 1.45;
}
/* Une note dépliante est un `<details>` : le `flex` de la note irait sur le bloc
   entier (résumé + contenu côte à côte), c'est le RÉSUMÉ qui le porte. */
.fcmp details.fcmp-note { display: block; }
.fcmp .fcmp-note > .ph { flex: 0 0 auto; margin-top: .15rem; color: var(--fcmp-ink); }
.fcmp .fcmp-note--warn {
    border-left-color: var(--warning, #d97706);
    background: color-mix(in srgb, var(--warning, #d97706) 10%, transparent);
}
.fcmp .fcmp-note--warn > .ph { color: var(--warning, #d97706); }
.fcmp .fcmp-note--fold > summary {
    display: flex; align-items: flex-start; gap: .55rem;
    cursor: pointer; list-style: none;
}
.fcmp .fcmp-note--fold > summary::-webkit-details-marker { display: none; }
.fcmp .fcmp-note--fold > summary > .ph { flex: 0 0 auto; margin-top: .15rem; color: var(--fcmp-ink); }
.fcmp .fcmp-note__list { margin: .5rem 0 0 1.6rem; }

/* Écran vide : l'icône est un `<p>`, donc soumise au `margin-bottom: 2rem` du socle
   `.empty-state p` — sans quoi elle flotte loin de son titre. */
.fcmp .fcmp-empty__icon {
    margin: 0 0 .4rem;
    font-size: 2.8rem;
    line-height: 1;
    color: color-mix(in srgb, var(--fest-pink, #ff4d8d) 55%, transparent);
}

/* Sélecteur de colonnes (les festivals à comparer). */
.fcmp .fcmp-picker__head {
    display: flex; flex-wrap: wrap; gap: .75rem 1rem;
    align-items: flex-start; justify-content: space-between;
}
.fcmp .fcmp-picker__intro { min-width: 14rem; }
/* Ce panneau est un OUTIL, pas une section de données : au corps `h2` de la page il
   pesait plus lourd que les titres des tableaux qu'il commande. */
.fcmp .fcmp-picker__title {
    display: flex; align-items: center; gap: .55rem;
    margin: 0; font-size: 1.25rem; line-height: 1.2;
}
.fcmp .fcmp-picker__icon {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto;
    width: 2.1rem; height: 2.1rem;
    border-radius: 12px;
    font-size: 1.1rem;
    background: color-mix(in srgb, var(--fest-pink, #ff4d8d) 15%, transparent);
    color: var(--fest-pink, #ff4d8d);
}
.fcmp .fcmp-picker__hint { margin: .4rem 0 0; font-size: .9em; }
.fcmp .fcmp-picker__actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; }
/* La bascule est posée dans une puce pour tenir tête au bouton d'à côté : nue, elle
   flottait contre lui. ⚠️ Aucune propriété du composant `.ui-toggle` n'est reprise
   ici (ni `display`, ni `gap`) — cette portée est plus spécifique et l'écraserait. */
.fcmp .fcmp-mode {
    padding: .3rem .7rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--bg-elevated, #fff) 75%, transparent);
    border: 1px solid var(--line, rgba(0, 0, 0, .12));
    font-size: .92rem;
}
.fcmp .fcmp-picker__list { margin-top: .9rem; }
/* Dépliant du choix des festivals : le marqueur natif du navigateur n'a ni la
   couleur ni la taille du reste de l'écran — chevron Phosphor qui pivote à
   l'ouverture. ⚠️ `list-style` (Firefox) ET `::-webkit-details-marker` (Safari) : ni
   l'un ni l'autre ne suffit seul. */
.fcmp .fcmp-picker__summary {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .38rem .8rem .38rem .6rem;
    border-radius: 999px;
    border: 1px solid var(--line, rgba(0, 0, 0, .12));
    background: color-mix(in srgb, var(--bg-elevated, #fff) 65%, transparent);
    cursor: pointer; font-weight: 600; font-size: .92rem;
    list-style: none;
    transition: background .16s ease, border-color .16s ease;
}
.fcmp .fcmp-picker__summary::-webkit-details-marker { display: none; }
.fcmp .fcmp-picker__summary:hover {
    background: var(--fcmp-wash);
    border-color: color-mix(in srgb, var(--fcmp-accent) 40%, var(--line, rgba(0, 0, 0, .12)));
}
.fcmp .fcmp-picker__caret { transition: transform .16s ease; }
.fcmp .fcmp-picker__list[open] .fcmp-picker__caret { transform: rotate(90deg); }
.fcmp .fcmp-picker__count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.4rem; height: 1.4rem; padding: 0 .35rem;
    border-radius: 999px;
    background: var(--fcmp-wash-strong);
    color: var(--fcmp-ink);
    font-size: .72rem; font-weight: 700;
}
.fcmp .fcmp-picker__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: .35rem .9rem;
    margin-top: .6rem;
    max-height: 22rem;
    overflow-y: auto;
}
.fcmp .fcmp-picker__year {
    grid-column: 1 / -1;
    margin: .6rem 0 .1rem;
    font-weight: 700;
    color: var(--muted, #6b7280);
}
.fcmp .fcmp-picker__item {
    /* ⚠️ La case cochée est peinte par le socle avec `--accent`, derrière une chaîne de
       six `:not()` — spécificité (0,8,1) qu'aucune surcharge lisible ne bat. On corrige
       donc le JETON, sur la portée la plus étroite possible : ce label ne contient
       qu'une case et son libellé, rien d'autre ici ne lit `--accent`. */
    --accent: var(--fcmp-accent);
    display: flex; align-items: flex-start; gap: .5rem;
    padding: .35rem .45rem; border-radius: 10px; cursor: pointer;
    border: 1px solid transparent;
    transition: background .14s ease, border-color .14s ease;
    /* Cible tactile confortable : ce sélecteur se coche souvent au téléphone. */
    min-height: 2.5rem;
}
.fcmp .fcmp-picker__item:hover { background: var(--fcmp-wash); }
/* Ce qui est retenu se voit sans relire les cases une à une — dix-neuf festivals,
   dont deux cochés quelque part au milieu. ⚠️ `:has` n'est qu'un CONFORT : sur un
   navigateur qui l'ignore la case reste cochée et la sélection reste juste. */
.fcmp .fcmp-picker__item:has(input:checked) {
    background: var(--fcmp-wash-strong);
    border-color: color-mix(in srgb, var(--fcmp-accent) 35%, transparent);
    font-weight: 600;
}
.fcmp .fcmp-picker__item small { display: block; font-size: .8em; }
.fcmp .fcmp-picker__item.is-empty span { color: var(--muted, #6b7280); }

@media (max-width: 640px) {
    /* Sur téléphone la première colonne mange l'écran : on la rétrécit plutôt que
       de renoncer au figement (sans elle, les chiffres n'ont plus de nom). */
    .fcmp .fcmp-table .fcmp-head { min-width: 8.5rem; font-size: .9em; }
    .fcmp .fcmp-col { min-width: 6.5rem; }
    .fcmp .fcmp-scroll { max-height: none; }
}

@media print {
    /* Sur papier, rien ne défile : le figement n'a plus de sens et couperait le
       tableau à la largeur de la page. */
    .fcmp .fcmp-scroll { max-height: none; overflow: visible; }
    .fcmp .fcmp-table thead th,
    .fcmp .fcmp-table .fcmp-head,
    .fcmp .fcmp-group__label { position: static; }
}

/* ===========================================================================
   OPÉRATIONS TERRAIN (food truck) — festivals, factures, notes, tâches,
   inventaire et scanner de factures. Préfixe .fest-* (back-office).
   =========================================================================== */

.fest-section-title { display:flex; align-items:center; gap:.5rem; margin:0 0 .9rem; font-size:1.15rem; }
.fest-section-title .ph { color:var(--fest-orange, #ff8a3d); }

/* Panneau de création repliable */
.fest-create__summary { display:flex; align-items:center; gap:.5rem; cursor:pointer; font-weight:600; list-style:none; }
.fest-create__summary::-webkit-details-marker { display:none; }
.fest-create__summary .ph { color:var(--fest-pink, #ff4d8d); font-size:1.25rem; }
.fest-create[open] .fest-create__summary { margin-bottom:.25rem; }
.fest-create--sub { border-top:1px dashed var(--line, rgba(0,0,0,.12)); padding-top:.75rem; }

/* Grilles de formulaire */
.fest-form-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:.85rem 1rem; }
.fest-form-grid--single { grid-template-columns:1fr; }
/* Champ dont la VALEUR est longue par nature — un identifiant de modèle IA
   (« google/gemini-2.5-flash-preview-05-20 ») se lit « google/gemi… » dans une
   colonne de 180 px, donc on ne sait plus lequel est choisi.
   ⚠️ Sous la media query : `span 2` sur une grille qui n'a qu'UNE colonne crée une
   colonne implicite et fait déborder la ligne. Le seuil est celui à partir duquel
   deux colonnes tiennent (2 × 180 px + gouttière). */
@media (min-width: 480px) {
    .fest-form-grid > .form-group--wide { grid-column: span 2; }
}
.fest-grid-2 { display:grid; grid-template-columns:1fr 1fr; gap:.75rem; }
@media (max-width: 480px) {
    /* Colonnes trop étroites pour les libellés longs (« Quantité (unités
       finales) », « Seuil bas (minimum) »…) sur un très petit écran. */
    .fest-grid-2 { grid-template-columns:1fr; }
}

/* Filtres en « pilules » */
.fest-chips { display:flex; flex-wrap:wrap; gap:.5rem; align-items:center; margin:1rem 0 1.25rem; }
.fest-chip { display:inline-flex; align-items:center; padding:.4rem .9rem; border-radius:999px;
    border:1px solid var(--line, rgba(0,0,0,.15)); background:var(--surface, #fff); color:var(--text, #2a1640);
    text-decoration:none; font-size:.9rem; font-weight:600; transition:transform .12s, background .12s; }
.fest-chip:hover { transform:translateY(-1px); }
.fest-chip.is-active { background:var(--fest-grad, #ff4d8d); color:#fff; border-color:transparent; }
.fest-chip-select .form-control { border-radius:999px; min-width:12rem; }
/* Sous-filtre (ex. l'année, sous les filtres de statut) : même composant, en retrait
   visuel — il précise la rangée du dessus, il ne rivalise pas avec elle. */
.fest-chips--sub { margin:-.75rem 0 1.25rem; gap:.4rem; }
.fest-chips__label { display:inline-flex; align-items:center; gap:.3rem; margin-right:.15rem;
    font-size:.82rem; font-weight:600; color:var(--muted, #6b7280); }
.fest-chip--sm { padding:.28rem .7rem; font-size:.82rem; }

/* Barre de filtres d'une liste : plusieurs commandes (recherche, listes
   déroulantes, bouton) sur UNE rangée qui se replie.

   ⚠️ Ce n'est pas `.fest-chip-select`, qui pose UNE liste déroulante au bout
   d'une rangée de pilules : là, le <select> vit dans son propre <form>, qui est
   l'élément flex et se dimensionne sur son contenu. Ici les commandes SONT la
   rangée — et `.form-control` arrive avec `width:100%` (il est fait pour une
   colonne de formulaire), ce qui pousse chaque commande sur sa propre ligne :
   quatre lignes pleine largeur au lieu d'une barre.

   ⚠️ `.custom-select` (l'enveloppe posée par custom_select.js autour du
   déclencheur) porte elle aussi `width:100%` : la neutraliser sur le seul
   `.form-control` ne suffirait pas, c'est l'enveloppe qui est l'élément flex. */
.fest-filterbar { align-items:center; }
.fest-filterbar .form-control { width:auto; max-width:100%; border-radius:999px; }
.fest-filterbar .custom-select { width:auto; }
.fest-filterbar select.form-control,
.fest-filterbar .custom-select__trigger { min-width:11rem; }
/* La recherche prend la place qui reste : c'est la commande qu'on remplit, les
   autres n'ont besoin que de leur libellé le plus long. */
.fest-filterbar input[type="search"].form-control { flex:1 1 16rem; }
@media (max-width: 640px) {
    /* Sur un téléphone, une commande par ligne se lit mieux qu'une rangée
       hachée — et le champ de recherche y garde toute la largeur. */
    .fest-filterbar .form-control,
    .fest-filterbar input[type="search"].form-control,
    .fest-filterbar .custom-select { width:100%; flex:1 1 100%; }
}

/* Ligne d'aide du tri des articles (Catalogue) : la phrase à gauche, le bouton
   « Désactivés à la fin » à droite, l'un sous l'autre dès que la largeur manque.
   ⚠️ En CSS et non en `style=""` : un attribut en ligne bat toute media query,
   donc le bouton ne pourrait jamais passer sous la phrase sur téléphone. */
.fest-sort-hint {
    display:flex;
    flex-wrap:wrap;
    align-items:center;
    justify-content:space-between;
    gap:.5rem;
    margin:.15rem 0 .75rem;
}
.fest-sort-hint p { margin:0; font-size:.85rem; }
/* Le <form> n'est qu'un porteur : sans ça, sa marge par défaut décale le bouton
   de la ligne de texte qu'il accompagne. */
.fest-sort-hint form { margin:0; }

/* Badges de statut */
.fest-status { font-weight:700; }
.fest-status--planifie { background:rgba(99,102,241,.14); color:#4f46e5; }
.fest-status--confirme { background:rgba(16,185,129,.16); color:var(--fest-ok, #0f9d6b); }
.fest-status--en_cours { background:rgba(255,138,61,.18); color:#d2691e; }
.fest-status--termine { background:rgba(var(--fest-veil, 0, 0, 0), .08); color:var(--muted, #6b7280); }
.fest-status--non_retenu { background:rgba(100,116,139,.16); color:#475569; }
.fest-status--annule  { background:rgba(220,38,38,.14); color:var(--fest-bad, #dc2626); }
.fest-pay--due  { background:rgba(220,38,38,.14); color:var(--fest-bad, #dc2626); font-weight:700; }
.fest-pay--paid { background:rgba(16,185,129,.16); color:var(--fest-ok, #0f9d6b); font-weight:700; }

/* Cellule « oui / non » d'un tableau (macro `bool_cell` de _fest_ops.html).
   ⚠️ La taille est en `em` et pas en `rem` : la cellule vit dans un tableau qui
   se rétrécit sur téléphone, l'icône doit suivre le texte qu'elle remplace.
   ⚠️ `line-height:1` sinon le glyphe rehausse la hauteur de la rangée, ce qui
   décale toutes les lignes du tableau d'un cran par rapport à leurs voisines. */
.fest-bool { display:inline-block; line-height:1; vertical-align:-.1em; }
.fest-bool--yes { color:var(--fest-ok, #0f9d6b); font-size:1.25em; }
.fest-bool--no { color:var(--muted, #6b7280); }

/* Cartes festival */
.fest-card-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(min(260px, 100%), 1fr)); gap:1rem; }

/* Section « En cours » : festivals en cours mis en avant au-dessus de la liste */
.fest-encours { margin-bottom:1.5rem; }
.fest-encours__title {
    display:flex; align-items:center; gap:.5rem;
    margin:0 0 .75rem; font-size:.92rem; font-weight:800;
    text-transform:uppercase; letter-spacing:.05em;
    color:var(--fest-ok, #0f9d6b);
}
.fest-encours__pulse {
    width:.7rem; height:.7rem; border-radius:50%; flex-shrink:0;
    background:#16a34a;
    box-shadow:0 0 0 0 rgba(22,163,74,.5);
    animation:fest-encours-pulse 1.8s ease-out infinite;
}
@keyframes fest-encours-pulse {
    0% { box-shadow:0 0 0 0 rgba(22,163,74,.5); }
    70% { box-shadow:0 0 0 .55rem rgba(22,163,74,0); }
    100% { box-shadow:0 0 0 0 rgba(22,163,74,0); }
}
/* Léger accent vert sur les cartes en cours */
.fest-encours .fest-festival-card { box-shadow:0 0 0 1.5px rgba(22,163,74,.35); }
@media (prefers-reduced-motion: reduce) {
    .fest-encours__pulse { animation:none; }
}
.fest-festival-card { display:flex; flex-direction:column; gap:.35rem; transition:transform .14s, box-shadow .14s; }
.fest-festival-card:hover { transform:translateY(-3px); box-shadow:0 16px 36px rgba(42,22,64,.14); }
.fest-festival-card__head { display:flex; justify-content:space-between; align-items:flex-start; gap:.5rem; }
.fest-festival-card__title { font-size:1.1rem; font-weight:700; text-decoration:none; color:var(--text, #2a1640); }
.fest-festival-card__title:hover { color:var(--fest-pink, #ff4d8d); }
.fest-festival-card__meta { display:flex; align-items:center; gap:.4rem; margin:.1rem 0; color:var(--muted, #6b7280); font-size:.9rem; }
.fest-festival-card__actions { display:flex; gap:.5rem; align-items:center; margin-top:auto; padding-top:.6rem; }

/* Bilan */
.fest-balance { display:flex; gap:1.25rem; flex-wrap:wrap; padding-top:.5rem; }
.fest-balance > div { display:flex; flex-direction:column; }
.fest-balance__label { font-size:.72rem; text-transform:uppercase; letter-spacing:.04em; color:var(--muted, #6b7280); }
.fest-balance__val { font-weight:700; }
/* Bandeau bilan (fiche festival) : 3 segments icône + libellé + valeur */
.fest-balance-banner {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
    padding: 0;
    margin-bottom: 1.5rem;
    overflow: hidden;
    border-top: 4px solid var(--fest-accent, var(--brand, #e11d48));
}
.fest-balance__item {
    position: relative;
    display: flex;
    align-items: center;
    gap: .85rem;
    padding: 1.15rem 1.4rem;
    min-width: 0;
}
/* Séparateur fin entre les segments */
.fest-balance__item + .fest-balance__item::before {
    content: "";
    position: absolute;
    left: 0; top: 20%; bottom: 20%;
    width: 1px;
    background: var(--line, rgba(0,0,0,.09));
}
.fest-balance__icon {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 2.6rem; height: 2.6rem;
    border-radius: 13px;
    font-size: 1.3rem;
    background: color-mix(in srgb, var(--fest-accent, #e11d48) 13%, transparent);
    color: var(--fest-accent, #e11d48);
}
.fest-balance__item--dep .fest-balance__icon {
    background: color-mix(in srgb, var(--fest-bad, #dc2626) 12%, transparent); color: var(--fest-bad, #dc2626);
}
.fest-balance__item--rec .fest-balance__icon {
    background: color-mix(in srgb, var(--fest-ok, #0f9d6b) 13%, transparent); color: var(--fest-ok, #0f9d6b);
}
.fest-balance__item--net .fest-balance__icon {
    background: color-mix(in srgb, var(--fest-accent, #e11d48) 14%, transparent);
    color: var(--fest-accent, #e11d48);
}
.fest-balance__body { display: flex; flex-direction: column; gap: .08rem; min-width: 0; }
.fest-balance-banner .fest-balance__label { font-weight: 600; letter-spacing: .06em; }
.fest-balance__value {
    font-size: 1.5rem;
    font-weight: 800;
    line-height: 1.12;
    letter-spacing: -.01em;
    white-space: nowrap;
}
.fest-balance__value--empty { color: var(--muted, #9ca3af); font-weight: 600; }
.fest-balance__hint { font-size: .68rem; color: var(--muted, #6b7280); line-height: 1.3; margin-top: .12rem; }
.fest-net--pos { color:var(--fest-ok, #0f9d6b); }
.fest-net--neg { color:var(--fest-bad, #dc2626); }

@media (max-width: 620px) {
    .fest-balance-banner { grid-template-columns: 1fr; }
    .fest-balance__item + .fest-balance__item::before {
        left: 1.4rem; right: 1.4rem; top: 0; bottom: auto;
        width: auto; height: 1px;
    }
}

/* Accès au rapport de rendement : carte-lien mise en évidence */
.fest-report-link {
    display: flex;
    align-items: center;
    gap: .9rem;
    padding: .85rem 1.1rem;
    margin-bottom: 1.25rem;
    text-decoration: none;
    color: inherit;
    border-left: 4px solid var(--fest-accent, var(--brand, #e11d48));
    transition: transform .14s ease, box-shadow .14s ease;
}
.fest-report-link:hover {
    transform: translateY(-1px);
    box-shadow: 0 16px 34px rgba(42,22,64,.13);
    text-decoration: none;
    color: inherit;
}
.fest-report-link__icon {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 2.6rem; height: 2.6rem;
    border-radius: 13px;
    font-size: 1.35rem;
    background: color-mix(in srgb, var(--fest-accent, #e11d48) 14%, transparent);
    color: var(--fest-accent, #e11d48);
}
.fest-report-link__text { display: flex; flex-direction: column; gap: .04rem; min-width: 0; flex: 1; }
.fest-report-link__title { font-weight: 700; font-size: 1.02rem; letter-spacing: -.01em; }
.fest-report-link__sub { font-size: .8rem; color: var(--muted, #6b7280); }
.fest-report-link__arrow {
    flex-shrink: 0;
    font-size: 1.15rem;
    color: var(--muted, #9ca3af);
    transition: transform .14s ease, color .14s ease;
}
.fest-report-link:hover .fest-report-link__arrow {
    transform: translateX(3px);
    color: var(--fest-accent, #e11d48);
}

/* Météo (festivites_weather) — badge prévision pour les festivals à venir */
.fest-weather { display:inline-flex; align-items:center; gap:.55rem; flex-wrap:wrap;
    padding:.3rem .6rem; border-radius:999px; font-size:.9rem; line-height:1;
    background:linear-gradient(135deg, rgba(56,189,248,.14), rgba(99,102,241,.12));
    border:1px solid rgba(56,189,248,.28); color:var(--text, #2a1640); width:max-content; max-width:100%; }
.fest-weather--compact { gap:.4rem; padding:.22rem .5rem; font-size:.82rem; margin:.15rem 0 .1rem; }
/* Badge cliquable vers un site météo : se comporte comme un bouton-lien discret. */
a.fest-weather--link { text-decoration:none; cursor:pointer; transition:border-color .15s, box-shadow .15s, background .15s; }
a.fest-weather--link:hover { border-color:rgba(56,189,248,.6); box-shadow:0 2px 8px rgba(56,189,248,.22); }
a.fest-weather--link:focus-visible { outline:2px solid var(--fest-info, #0284c7); outline-offset:2px; }
.fest-weather__ext { font-size:.78rem; color:var(--fest-info-strong, #0369a1); opacity:.7; margin-left:.05rem; }
a.fest-weather--link:hover .fest-weather__ext { opacity:1; }
.fest-weather__icon { font-size:1.15rem; color:var(--fest-info, #0284c7); }
.fest-weather--compact .fest-weather__icon { font-size:1rem; }
.fest-weather__label { font-weight:600; }
.fest-weather__rain { display:inline-flex; align-items:center; gap:.2rem; color:var(--fest-info-strong, #0369a1); font-weight:600; }
.fest-weather__rain .ph { font-size:.9rem; }
.fest-weather__temp { font-weight:700; color:var(--muted, #6b7280); }
.fest-weather-banner { display:flex; align-items:center; gap:.85rem; flex-wrap:wrap; margin-bottom:1rem; padding:.75rem 1rem; position:relative; }
/* Le popover horaire flotte via z-index:1500, MAIS les .content-card créent un
   contexte d'empilement (backdrop-filter) qui le piégeait sous la carte suivante
   (la ligne rouge le coupait). On élève la bannière entière pendant l'ouverture
   (survol / focus clavier / .is-open au tap) pour que le popover passe réellement
   au-dessus de tout le contenu qui suit. */
.fest-weather-banner:hover,
.fest-weather-banner:focus-within,
.fest-weather-banner:has(.fest-weather-hover.is-open) { z-index:1200; }
.fest-weather-banner__head { display:inline-flex; align-items:center; gap:.4rem; font-weight:700; color:var(--text, #2a1640); }
.fest-weather-banner__head .ph { font-size:1.2rem; color:var(--fest-info, #0284c7); }
/* Météo indisponible : avertissement discret, à la même place que le badge.
   Ton neutre/gris (pas d'alarme) + bulle (i) expliquant la cause. */
.fest-weather--unavailable { background:rgba(148,163,184,.12); border-color:rgba(148,163,184,.32);
    color:var(--muted, #6b7280); }
.fest-weather--unavailable .fest-weather__icon { color:var(--muted, #94a3b8); }
.fest-weather--unavailable .fest-weather__label { font-weight:600; color:var(--muted, #6b7280); }
.fest-weather--unavailable .help-tooltip__trigger { color:var(--muted, #94a3b8); }

/* Capsule météo « jour même » : dépliage horaire au survol (desktop) ou au tap
   sur l'horloge (mobile). Le panneau flotte AU-DESSUS du contenu (position
   absolute + z-index élevé) : il ne pousse jamais la mise en page, il la recouvre. */
.fest-weather-hover { position:relative; display:inline-block; }
.fest-weather-hover__badge { display:inline-flex; align-items:center; gap:.3rem; }
/* Icône-horloge : cible de tap distincte du badge (qui, lui, est un lien).
   Ouvre la prévision horaire au clic/tap/clavier ; sur desktop le survol suffit. */
.fest-weather-hover__toggle { display:inline-flex; align-items:center; justify-content:center;
    width:1.7rem; height:1.7rem; padding:0; border:1px solid rgba(56,189,248,.28);
    border-radius:999px; background:rgba(56,189,248,.10); color:var(--fest-info-strong, #0369a1); cursor:pointer;
    font-size:.95rem; line-height:1; flex:0 0 auto;
    transition:background .2s ease, color .2s ease, transform .25s ease; }
/* L'horloge ne sert qu'aux appareils SANS survol (mobile/tactile) : sur desktop,
   le survol de la capsule ouvre déjà le popover → on masque l'horloge. */
@media (hover: hover) and (pointer: fine) {
    .fest-weather-hover__toggle { display:none; }
}
.fest-weather-hover__toggle:hover { background:rgba(56,189,248,.20); }
.fest-weather-hover__toggle:focus-visible { outline:2px solid var(--fest-info, #0284c7); outline-offset:2px; }
.fest-weather-hover:hover .fest-weather-hover__toggle,
.fest-weather-hover.is-open .fest-weather-hover__toggle {
    background:rgba(56,189,248,.24); color:var(--fest-info, #0284c7); transform:scale(1.06); }
/* Popover flottant : masqué (opacité 0 + léger décalage), révélé fluidement.
   Ouvert par le survol (desktop) OU la classe .is-open posée par le JS (tap/clavier). */
.fest-weather-hourly-wrap { position:absolute; top:calc(100% + .45rem); left:0; z-index:1500;
    min-width:100%; max-width:min(92vw, 640px);
    background: linear-gradient(var(--surface, #fff), var(--surface, #fff)), var(--bg, #fff); border:1px solid rgba(56,189,248,.28); border-radius:14px;
    box-shadow:0 16px 40px rgba(2,132,199,.22), 0 2px 8px rgba(0,0,0,.08);
    opacity:0; transform:translateY(-6px) scale(.98); transform-origin:top left; pointer-events:none;
    transition:opacity .2s ease, transform .26s cubic-bezier(.4,0,.2,1); }
/* Pont invisible : couvre l'espace badge↔popover pour que le survol ne « lâche »
   pas dans le vide entre les deux (desktop). */
.fest-weather-hourly-wrap::before { content:""; position:absolute; left:0; right:0; top:-.5rem; height:.5rem; }
.fest-weather-hover:hover .fest-weather-hourly-wrap,
.fest-weather-hover.is-open .fest-weather-hourly-wrap { opacity:1; transform:translateY(0) scale(1); pointer-events:auto; }
.fest-weather-hourly { overflow:hidden; border-radius:14px; }
.fest-weather-hourly__strip { display:flex; gap:.35rem; overflow-x:auto; padding:.6rem; scroll-behavior:smooth; }
.fest-weather-hourly__strip::-webkit-scrollbar { height:6px; }
.fest-weather-hourly__strip::-webkit-scrollbar-thumb { background:rgba(56,189,248,.35); border-radius:999px; }
.fest-hour { flex:0 0 auto; display:flex; flex-direction:column; align-items:center; gap:.15rem;
    min-width:3.4rem; padding:.4rem .35rem; border-radius:10px;
    background:rgba(56,189,248,.07); border:1px solid rgba(56,189,248,.16); }
.fest-hour.is-now { background:linear-gradient(135deg, rgba(56,189,248,.22), rgba(99,102,241,.16));
    border-color:rgba(56,189,248,.55); box-shadow:0 0 0 1px rgba(56,189,248,.25) inset; }
.fest-hour__h { font-size:.7rem; color:var(--muted, #6b7280); white-space:nowrap; }
.fest-hour.is-now .fest-hour__h { color:var(--fest-info-strong, #0369a1); font-weight:700; }
.fest-hour__icon { font-size:1.15rem; color:var(--fest-info, #0284c7); }
.fest-hour__rain { display:inline-flex; align-items:center; gap:.1rem; font-size:.7rem; color:var(--fest-info-strong, #0369a1); font-weight:600; }
.fest-hour__rain .ph { font-size:.72rem; }
.fest-hour__temp { font-size:.82rem; font-weight:700; color:var(--text, #2a1640); }
@media (prefers-reduced-motion: reduce) {
    .fest-weather-hourly-wrap { transition:opacity .12s linear; transform:none; }
    .fest-weather-hover:hover .fest-weather-hourly-wrap,
    .fest-weather-hover.is-open .fest-weather-hourly-wrap { transform:none; }
    .fest-weather-hover__toggle { transition:background .12s linear, color .12s linear; }
    .fest-weather-hover:hover .fest-weather-hover__toggle,
    .fest-weather-hover.is-open .fest-weather-hover__toggle { transform:none; }
}

/* Mises en page deux colonnes */
.fest-two-col { display:grid; grid-template-columns:1fr 1fr; gap:1.25rem; }
@media (max-width:860px) { .fest-two-col { grid-template-columns:1fr; } }

/* Ajout en ligne (tâches / notes / mouvements) */
.fest-inline-add { display:flex; gap:.5rem; flex-wrap:wrap; align-items:center; margin-top:.75rem; }
.fest-inline-add .form-control { flex:1 1 8rem; }
.fest-inline-add--note { flex-direction:column; align-items:stretch; }
/* En colonne, le flex-basis 8rem s'applique à la hauteur : on le neutralise pour
   que le titre garde sa hauteur naturelle et que la note suive ses rows. */
.fest-inline-add--note .form-control { flex:0 0 auto; }

/* Tâches */
.fest-task-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; }
.fest-task { display:flex; flex-direction:column; gap:.3rem; padding:.5rem 0; border-bottom:1px solid var(--line, rgba(0,0,0,.07)); }
.fest-task__main { display:flex; align-items:center; gap:.45rem; }
.fest-task__check { margin:0; display:flex; }
.fest-task__box { background:none; border:none; cursor:pointer; font-size:1.35rem; line-height:1; color:var(--muted, #9ca3af); padding:0; display:flex; align-items:center; }
.fest-task__box--on { color:var(--fest-ok, #0f9d6b); }
/* Titre cliquable (ouvre l'éditeur) ; bouton remis à plat, aligné à gauche. */
.fest-task__label { flex:1; min-width:0; text-align:left; background:none; border:none; padding:.15rem 0; margin:0; font:inherit; color:inherit; cursor:pointer; word-break:break-word; white-space:normal; }
button.fest-task__label:hover { color:var(--fest-ink, #2a1640); }
.fest-task--done .fest-task__label { text-decoration:line-through; color:var(--muted, #9ca3af); }
.fest-task__meta { display:inline-flex; align-items:center; gap:.4rem; flex-wrap:wrap; }
.fest-task__due { font-size:.8rem; color:var(--muted, #6b7280); display:inline-flex; gap:.2rem; align-items:center; white-space:nowrap; }
.fest-task__pri { font-size:.7rem; font-weight:600; padding:.05rem .45rem; border-radius:1rem; white-space:nowrap; }
.fest-task__pri--1 { background:color-mix(in srgb, var(--fest-bad, #b42318) 16%, transparent); color:var(--fest-bad, #b42318); }
.fest-task__pri--5 { background:color-mix(in srgb, var(--fest-warn, #b54708) 18%, transparent); color:var(--fest-warn, #b54708); }
.fest-task__pri--9 { background:color-mix(in srgb, var(--fest-alt, #3b5b8c) 16%, transparent); color:var(--fest-alt, #3b5b8c); }
.fest-task__ic { color:var(--muted, #6b7280); font-size:.95rem; display:inline-flex; text-decoration:none; }
.fest-task__ic:hover { color:var(--fest-ink, #2a1640); }
.fest-task__editpanel { padding:.35rem 0 .25rem 1.85rem; }
.fest-task__editform { display:flex; flex-direction:column; gap:.55rem; }

/* Enrichissement web (Tavily) — bouton fiche festival + modale de choix des champs. */
.fest-enrich { display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; }
.fest-enrich__status { font-size:.82rem; color:var(--muted, #6b7280); }
.fest-enrich-modal { position:fixed; inset:0; z-index:60; display:flex; align-items:center; justify-content:center; background:var(--modal-scrim, rgba(18,8,32,.82)); -webkit-backdrop-filter: var(--modal-scrim-blur, blur(4px)); backdrop-filter: var(--modal-scrim-blur, blur(4px)); padding:1rem; }
.fest-enrich-modal__panel { background:var(--fest-panel, #fff); border-radius:1rem; max-width:520px; width:100%; padding:1.2rem 1.3rem; box-shadow:0 12px 40px rgba(0,0,0,.25); max-height:88vh; overflow-y:auto; }
.fest-enrich-rows { display:flex; flex-direction:column; gap:.5rem; margin:.6rem 0; }
.fest-enrich-row { display:grid; grid-template-columns:6.5rem 1fr auto; gap:.15rem .6rem; padding:.5rem .6rem; border:1px solid var(--line, rgba(0,0,0,.08)); border-radius:.6rem; align-items:center; }
.fest-enrich-row__lbl { grid-row:1 / span 2; font-weight:700; align-self:center; }
.fest-enrich-row__cur { grid-column:2; font-size:.82rem; color:var(--muted, #6b7280); }
.fest-enrich-row__new { grid-column:2; font-size:.9rem; color:var(--fest-ink, #2a1640); font-weight:600; }
.fest-enrich-row__apply { grid-column:3; grid-row:1 / span 2; align-self:center; white-space:nowrap; }
.fest-enrich-row__apply.is-done { color:var(--fest-ok, #0f9d6b); border-color:var(--fest-ok, #0f9d6b); background:transparent; }
.fest-enrich-actions { display:flex; gap:.5rem; justify-content:flex-end; flex-wrap:wrap; margin-top:1rem; }
/* Modale d'enrichissement à deux temps : choix puis confirmation cochable. */
.fest-enrich-choices { display:flex; flex-direction:column; gap:.5rem; margin:.4rem 0; }
.fest-enrich-choice { display:flex; align-items:flex-start; gap:.6rem; padding:.6rem .7rem; border:1px solid var(--line, rgba(0,0,0,.08)); border-radius:.6rem; cursor:pointer; }
.fest-enrich-choice:hover { background:rgba(var(--fest-veil, 0, 0, 0), .03); }
.fest-enrich-choice input { margin-top:.2rem; }
.fest-enrich-choice__txt { display:flex; flex-direction:column; gap:.1rem; }
.fest-enrich-choice__txt .muted { font-size:.82rem; }
.fest-enrich-section { margin:.8rem 0 .2rem; }
.fest-enrich-section h4 { margin:0 0 .4rem; font-size:.82rem; text-transform:uppercase; letter-spacing:.03em; color:var(--muted, #6b7280); }
.fest-enrich-item { display:flex; align-items:flex-start; gap:.6rem; padding:.5rem .6rem; border:1px solid var(--line, rgba(0,0,0,.08)); border-radius:.6rem; margin-bottom:.4rem; cursor:pointer; }
.fest-enrich-item:hover { background:rgba(var(--fest-veil, 0, 0, 0), .03); }
.fest-enrich-item input { margin-top:.25rem; }
.fest-enrich-item__body { display:flex; flex-direction:column; gap:.12rem; min-width:0; }
.fest-enrich-item__lbl { font-weight:700; }
.fest-enrich-item__cur { font-size:.8rem; color:var(--muted, #6b7280); }
.fest-enrich-item__new { font-size:.9rem; color:var(--fest-ink, #2a1640); font-weight:600; }
.fest-enrich-item__corps { font-size:.85rem; color:var(--fest-ink, #2a1640); white-space:normal; }
.fest-enrich-sub { margin:.25rem 0 0; padding-left:1.1rem; font-size:.83rem; color:var(--fest-ink, #2a1640); }
.fest-enrich-sub li { margin:.05rem 0; }
.fest-enrich-tag { display:inline-block; font-size:.7rem; font-weight:700; padding:.05rem .4rem; border-radius:.5rem; background:rgba(var(--fest-veil, 0, 0, 0), .06); color:var(--muted, #6b7280); vertical-align:middle; }
.fest-enrich-tag.is-ok { background:rgba(15,157,107,.12); color:var(--fest-ok, #0f9d6b); }
.fest-icon-btn { background:none; border:none; cursor:pointer; color:var(--muted, #9ca3af); font-size:1.1rem; padding:.2rem; border-radius:8px; display:flex; }
.fest-icon-btn:hover { color:var(--danger, #dc2626); background:rgba(var(--fest-veil, 0, 0, 0), .05); }

/* Notes */
.fest-note-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:.75rem; }
.fest-note { background:var(--fest-yellow, #fff7d6); border-radius:14px; padding:.75rem .85rem; box-shadow:0 4px 12px rgba(42,22,64,.07); }
.fest-note--pinned { outline:2px solid var(--fest-orange, #ff8a3d); }
.fest-note__head { display:flex; justify-content:space-between; align-items:center; gap:.3rem; }
.fest-note__head .fest-note__edit { flex:1; min-width:0; }
.fest-note__tools { display:flex; gap:.1rem; flex:0 0 auto; }
.fest-note__tools .fest-icon-btn:hover { color:var(--fest-ink, #2a1640); }
/* Épingle active : la police Phosphor « fill » n'est pas chargée en back-office, on
   signale donc l'épinglage par la couleur orange + une pastille (au lieu de l'icône pleine). */
.fest-note__tools .fest-icon-btn.is-pinned { color:var(--fest-orange, #ff8a3d); background:rgba(255,138,61,.16); }
.fest-note__tools .fest-icon-btn.is-pinned:hover { color:var(--fest-orange, #ff8a3d); background:rgba(255,138,61,.28); }
/* Champs d'édition en place : se fondent dans la carte (pas de bordure ni fond). */
.fest-note__title-input { width:100%; border:none; outline:none; background:transparent; padding:0; margin:0; font-family:inherit; font-size:.98rem; font-weight:700; line-height:1.3; color:var(--fest-ink, #2a1640); resize:none; overflow:hidden; overflow-wrap:anywhere; word-break:break-word; field-sizing:content; min-height:1.3em; }
.fest-note__title-input::placeholder { color:var(--muted, #6b7280); font-weight:500; }
.fest-note__title-input:read-only { cursor:default; }
/* La hauteur du champ épouse le texte (field-sizing: content) : pas de zone vide ni
   de scroll interne ; repli sur rows="1" si le navigateur ne gère pas field-sizing. */
.fest-note__body-input { width:100%; border:none; outline:none; resize:vertical; background:transparent; margin:.35rem 0; padding:0; font:inherit; font-size:.92rem; color:var(--fest-ink, #2a1640); overflow-wrap:anywhere; word-break:break-word; field-sizing:content; min-height:1.3em; }
.fest-note__body-input::placeholder { color:var(--muted, #6b7280); }
.fest-note__body-input:read-only { cursor:default; resize:none; }
/* Sources d'une note : favicons cliquables plutôt que de longues URL. */
.fest-note__sources { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; margin:.45rem 0 .15rem; }
.fest-note__sources-lbl { font-size:.72rem; font-weight:600; color:var(--muted, #6b7280); margin-right:.1rem; }
.fest-note__src { width:1.7rem; height:1.7rem; border-radius:.5rem; flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; background:rgba(42,22,64,.06); border:1px solid rgba(42,22,64,.12); color:var(--muted, #6b7280); text-decoration:none; font-size:1rem; }
.fest-note__src:hover { background:rgba(255,138,61,.16); border-color:var(--fest-orange, #ff8a3d); }
.fest-note__src-ico { width:1rem; height:1rem; border-radius:.2rem; display:block; }
.fest-note__date { font-size:.72rem; margin:0; }

/* ── Modèles de listes de tâches (page de gestion) ───────────────────────── */
.fest-tpl-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(min(100%, 640px), 1fr)); gap:1rem; }
.fest-tpl { display:flex; flex-direction:column; gap:.6rem; }
.fest-tpl--auto { border-left:4px solid var(--fest-orange, #ff8a3d); }
.fest-tpl__head { display:flex; align-items:center; gap:.4rem; }
.fest-tpl__name-form { flex:1; min-width:0; display:flex; align-items:center; gap:.4rem; }
.fest-tpl__name-form .ph { color:var(--fest-orange, #ff8a3d); flex:0 0 auto; }
.fest-tpl__name { font-weight:700; }
.fest-tpl__auto { margin:0; }
.fest-tpl__items { display:flex; flex-direction:column; }
.fest-tpl-item { display:flex; align-items:center; gap:.25rem; padding:.1rem 0; border-bottom:1px solid rgba(42,22,64,.06); }
.fest-tpl-item:last-child { border-bottom:0; }
.fest-tpl-item__edit { flex:1; min-width:0; display:flex; align-items:center; gap:.4rem; }
/* Le libellé se lit comme du texte ; encadré seulement au survol / à l'édition */
.fest-tpl-item__label { flex:1 1 auto; min-width:0; font-weight:500; border-color:transparent; background:transparent; box-shadow:none; padding:.35rem .4rem; }
.fest-tpl-item__label:hover { background:rgba(42,22,64,.04); }
.fest-tpl-item__label:focus { background:var(--fest-panel, #fff); border-color:var(--fest-orange, #ff8a3d); box-shadow:0 0 0 3px rgba(255,138,61,.15); }
/* Échéance + priorité : masquées par défaut, révélées au survol/focus ou si renseignées */
.fest-tpl-item__offset { display:none; align-items:center; gap:.2rem; font-size:.78rem; color:var(--muted, #6b7280); flex:0 0 auto; }
.fest-tpl-item__offset input { width:2.8rem; text-align:center; padding-left:.25rem; padding-right:.25rem; }
.fest-tpl-item__prio { display:none; flex:0 0 auto; width:auto; min-width:5.5rem; font-size:.82rem; }
.fest-tpl-item:hover .fest-tpl-item__offset,
.fest-tpl-item:focus-within .fest-tpl-item__offset,
.fest-tpl-item--meta .fest-tpl-item__offset { display:inline-flex; }
.fest-tpl-item:hover .fest-tpl-item__prio,
.fest-tpl-item:focus-within .fest-tpl-item__prio,
.fest-tpl-item--meta .fest-tpl-item__prio { display:inline-block; }
/* Corbeille discrète : apparaît au survol / focus de la ligne */
.fest-tpl-item > form:last-child { flex:0 0 auto; }
.fest-tpl-item .fest-icon-btn { opacity:0; transition:opacity .15s ease; }
.fest-tpl-item:hover .fest-icon-btn,
.fest-tpl-item:focus-within .fest-icon-btn { opacity:1; }
.fest-tpl__additem { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; margin-top:.35rem; padding-top:.55rem; border-top:1px dashed rgba(42,22,64,.14); }
.fest-tpl__additem .form-control[name="label"] { flex:1 1 100%; }
.fest-tpl__additem .fest-tpl-item__offset,
.fest-tpl__additem .fest-tpl-item__prio { display:inline-flex; }
.fest-tpl__additem .fest-tpl-item__prio { flex:1 1 auto; min-width:5rem; }
.fest-tpl__additem .button.primary { flex:0 0 auto; margin-left:auto; }
.fest-apply-template { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; margin-top:.6rem; }
.fest-apply-template__lbl { display:inline-flex; align-items:center; gap:.3rem; font-weight:600; font-size:.88rem; color:var(--fest-ink, #2a1640); }
.fest-apply-template select { width:auto; min-width:9rem; }

/* ── Onglet Logistique (équipe · hébergement · transport · installation) ──── */
/* Cartes en rangées adaptatives : 2 par ligne, 3 sur grand écran, 1 au téléphone.
   Chaque carte déclare la largeur que son contenu réclame (`--full` = toute la
   ligne, `--large` = deux colonnes quand il y en a trois) et l'ordre du gabarit
   suit ces largeurs : la grille se pave sans trou aux trois paliers. */
.fest-logi-grid { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:1rem; align-items:start; }
.fest-logi--full { grid-column:1 / -1; }
@media (min-width:1400px) {
    .fest-logi-grid { grid-template-columns:repeat(3, minmax(0, 1fr)); }
    .fest-logi--large { grid-column:span 2; }
}
@media (max-width:899px) { .fest-logi-grid { grid-template-columns:1fr; } }
/* Champs d'une carte large : `auto-fill` (et non `auto-fit`) ne fusionne pas les
   colonnes vides — deux champs de date gardent leur taille au lieu de s'étirer
   sur toute la carte. */
.fest-logi-grid .fest-form-grid { grid-template-columns:repeat(auto-fill, minmax(min(100%, 170px), 1fr)); }
/* Une carte pleine ligne mérite des champs plus larges : à 170 px, un menu comme
   « Montant fixe (saisi ci-contre) » se coupait au milieu d'une carte de 1 300 px. */
.fest-logi--full .fest-form-grid { grid-template-columns:repeat(auto-fill, minmax(min(100%, 240px), 1fr)); }
/* Adresse du coucher : toute la ligne au téléphone, deux colonnes ensuite — elle
   rejoint alors le coût et sa source sur une seule ligne. */
.fest-lodging__lieu { grid-column:1 / -1; }
@media (min-width:900px) { .fest-lodging__lieu { grid-column:span 2; } }
.fest-logi__h { display:flex; align-items:center; gap:.45rem; font-size:1.02rem; margin:0 0 .75rem; }
/* Statut d'autosauvegarde logé au titre : discret, et sans hauteur tant qu'il est vide. */
.fest-logi__h .autosave-status { margin:0; font-size:.78rem; font-weight:500; }
.fest-logi__h .autosave-status:empty { display:none; }
.fest-logi__h .ph { color:var(--fest-orange, #ff8a3d); }
/* Équipe */
.fest-staff-list { display:flex; flex-direction:column; gap:.4rem; }
.fest-staff { display:flex; align-items:center; gap:.4rem; }
.fest-staff__edit { flex:1; min-width:0; display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; }
.fest-staff__nom { flex:1 1 7rem; min-width:6rem; font-weight:600; }
.fest-staff__tel { flex:1 1 7rem; min-width:6rem; }
.fest-staff__shift { flex:1 1 8rem; min-width:6rem; }
/* La colonne d'outils garde sa largeur même quand le téléphone (ou le courriel)
   manque : sinon les jours travaillés d'une ligne se décalent de ceux de la
   ligne voisine. Deux boutons de 34 px pour un membre, trois pour un contact. */
.fest-staff__tools { display:flex; gap:.1rem; flex:0 0 auto; min-width:4.4rem; justify-content:flex-end; }
[data-contact-row] .fest-staff__tools { min-width:6.6rem; }
.fest-staff-add { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; margin-top:.6rem; padding-top:.6rem; border-top:1px dashed rgba(42,22,64,.14); }
.fest-staff-add .form-control { flex:1 1 7rem; min-width:6rem; }
/* Contacts d'un festival : même patron que l'équipe, avec rôle + courriel. */
.fest-contact__role { flex:1 1 7rem; min-width:6rem; }
.fest-contact__mail { flex:1 1 9rem; min-width:7rem; }
/* Planification pourboire d'un membre : jours travaillés (puces cochables) + toggle. */
/* Jours + pourboire rejoignent la ligne du membre dès qu'il y a la place (carte
   pleine largeur) ; sous cette base, ils repassent à la ligne d'eux-mêmes. */
.fest-staff__sched { flex:1 1 24rem; display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; }
/* Dès qu'il y a la place, le bloc reste d'un seul tenant : sinon les jours
   passaient sous la ligne pendant que le toggle (margin-left:auto) restait en
   haut à droite — deux morceaux du même réglage sur deux lignes. Ce sont les
   puces de jours qui s'enroulent à l'intérieur. Au téléphone, au contraire, le
   toggle doit pouvoir passer sous les jours, sinon les puces sont écrasées. */
@media (min-width:900px) { .fest-staff__sched { flex-wrap:nowrap; } }
.fest-staff__days { display:flex; gap:.25rem; flex-wrap:wrap; }
/* `white-space:nowrap` : une date ne se casse jamais en « jeu. / 6 / août ». */
.fest-daychip { display:inline-flex; align-items:center; cursor:pointer; padding:.15rem .5rem; border-radius:999px; border:1px solid var(--line, rgba(42,22,64,.2)); font-size:.8rem; line-height:1.2; color:var(--muted, #5b4b6e); background:var(--fest-panel, #fff); user-select:none; white-space:nowrap; }
.fest-daychip input { position:absolute; opacity:0; width:0; height:0; }
.fest-daychip.is-on,
.fest-daychip:has(input:checked) { background:var(--fest-accent,#e11d48); border-color:var(--fest-accent,#e11d48); color:#fff; }
.fest-daychip:has(input:focus-visible) { outline:2px solid var(--fest-accent,#e11d48); outline-offset:1px; }
/* Le toggle garde sa largeur (« Pourboire » passait sous son icône quand la place
   manquait) : ce sont les puces de jours qui cèdent le terrain. */
.fest-staff__tips { margin-left:auto; flex:0 0 auto; white-space:nowrap; }
/* Coût de l'essence : prix au litre + consommation + coût déduit, avec suggestions. */
.fest-fuel { margin-bottom:.85rem; }
.fest-fuel__grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:.85rem 1rem; }
.fest-fuel__field { margin:0; }
.fest-suggest { display:flex; flex-wrap:wrap; gap:.3rem; margin-top:.35rem; }
.fest-suggest__chip { display:inline-flex; align-items:center; cursor:pointer; padding:.2rem .55rem; border-radius:999px;
    border:1px solid var(--line, rgba(42,22,64,.2)); background:var(--fest-panel, #fff); color:var(--muted, #5b4b6e); font-size:.78rem; line-height:1.2; font-family:inherit; user-select:none; }
.fest-suggest__chip:hover { background:var(--fest-accent,#e11d48); border-color:var(--fest-accent,#e11d48); color:#fff; }
.fest-suggest__chip:focus-visible { outline:2px solid var(--fest-accent,#e11d48); outline-offset:1px; }
/* Relevé du prix réel : une ACTION (elle part chercher un prix), pas une valeur
   figée comme ses voisines — d'où le trait plein et l'icône. Le bord suffit à
   l'en distinguer sans en faire un bouton principal : ça reste une suggestion. */
.fest-suggest__chip--live { gap:.28rem; border-style:solid; border-color:var(--fest-accent,#e11d48); color:var(--fest-accent,#e11d48); }
.fest-suggest__chip--live:disabled { opacity:.6; cursor:progress; }
.fest-fuel__calc { font-size:.82rem; margin:.6rem 0 0; min-height:1.1em; }
/* Provenance du prix relevé + mention de source. Vide tant qu'on n'a rien
   relevé : aucune hauteur réservée, la rangée ne doit pas sauter au chargement. */
.fest-fuel__source { font-size:.76rem; margin:.35rem 0 0; }
.fest-fuel__source:empty { display:none; }
.fest-fuel__source a { color:inherit; text-decoration:underline; }
/* Heure de départ déduite (Installation du food truck) : bandeau discret mis en
   évidence, masqué tant que le calcul n'a rien à afficher. */
.fest-departure { grid-column:1 / -1; margin:.1rem 0 0; padding:.55rem .7rem; border-radius:.6rem;
    font-size:.88rem; line-height:1.35; color:var(--fest-ink, #2a1640);
    background:rgba(255,138,61,.1); border:1px solid rgba(255,138,61,.25); }
.fest-departure:empty { display:none; }
/* Transport : un bloc par trajet (base→festival, coucher→festival). */
.fest-transport-legs { display:flex; flex-direction:column; gap:.5rem; margin-bottom:.75rem; }
.fest-transport-leg { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; }
.fest-transport-leg__from { display:inline-flex; align-items:center; gap:.4rem; font-weight:600; font-size:.9rem; color:var(--fest-ink, #2a1640); min-width:9.5rem; }
.fest-transport-dot { width:.7rem; height:.7rem; border-radius:50%; flex:0 0 auto; }
.fest-transport__badge { display:inline-flex; align-items:center; gap:.35rem; font-weight:700; font-size:1.05rem;
    padding:.25rem .65rem; border-radius:999px; color:var(--fest-bad, #b91c1c);
    background:linear-gradient(135deg, rgba(244,63,94,.14), rgba(244,63,94,.08)); border:1px solid rgba(244,63,94,.28); }
.fest-transport__badge--alt { color:var(--fest-alt, #1d4ed8); background:linear-gradient(135deg, rgba(37,99,235,.16), rgba(37,99,235,.09)); border-color:rgba(37,99,235,.34); }
.fest-transport__dist { display:inline-flex; align-items:center; gap:.3rem; font-weight:600; color:var(--muted, #6b7280); }
.fest-map { height:320px; width:100%; border-radius:12px; overflow:hidden; border:1px solid var(--line, rgba(42,22,64,.14)); background:var(--fest-panel-soft, #eef2f7); }
.fest-map--3d { height:380px; }
.fest-map .maplibregl-map { height:100%; width:100%; font:inherit; }
.fest-map .maplibregl-ctrl-attrib { font-size:.68rem; }

/* GPS : « Démarrer la navigation » → ouvre l'appli native du téléphone. */
.fest-nav { display:flex; flex-direction:column; gap:.5rem; margin-bottom:.85rem; }
.fest-nav__go { align-self:flex-start; font-weight:700; }
.fest-nav__go .ph { font-size:1.1em; }
.fest-nav__apps { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; }
.fest-nav__hint { font-size:.82rem; }
.fest-nav__app { display:inline-flex; align-items:center; gap:.3rem; padding:.28rem .6rem; border-radius:999px;
    font-size:.85rem; font-weight:600; text-decoration:none; color:var(--fest-ink, #2a1640);
    background:rgba(42,22,64,.05); border:1px solid rgba(42,22,64,.14); }
.fest-nav__app:hover { background:rgba(42,22,64,.09); }

/* Statistiques */
.fest-stat-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:1rem; }
.fest-stat__value { font-size:1.8rem; font-weight:800; margin:.2rem 0 0; }

/* Dépenses */
.fest-expense-list { display:flex; flex-direction:column; }
.fest-expense { border-bottom:1px solid var(--line, rgba(0,0,0,.08)); }
.fest-expense__summary { display:flex; align-items:center; gap:.75rem; padding:.7rem .25rem; cursor:pointer; list-style:none; }
.fest-expense__summary::-webkit-details-marker { display:none; }
.fest-expense__thumb { width:42px; height:42px; border-radius:10px; overflow:hidden; flex-shrink:0; display:flex; align-items:center; justify-content:center;
    background:rgba(var(--fest-veil, 0, 0, 0), .05); color:var(--muted, #9ca3af); font-size:1.3rem; }
.fest-expense__thumb img { width:100%; height:100%; object-fit:cover; }
.fest-expense__main { flex:1; min-width:0; display:flex; flex-direction:column; }
.fest-expense__vendor { font-weight:600; }
.fest-expense__sub { font-size:.82rem; }
.fest-expense__amount { font-weight:700; white-space:nowrap; }
.fest-expense__body { padding:.5rem .25rem 1rem; }
.fest-expense__foot { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap; margin-top:.5rem; }
.fest-expense__link { font-size:.85rem; }
.fest-scan-preview { font-size:.85rem; color:var(--fest-ok, #0f9d6b); }
.fest-link-btn { background:none; border:none; padding:0; color:var(--fest-pink, #ff4d8d); cursor:pointer; font:inherit; text-decoration:underline; }

/* Lignes itémisées d'une facture (articles lus au scan, rapprochés à l'inventaire). */
.fest-expense__lines { margin-top:.6rem; padding-top:.5rem; border-top:1px dashed rgba(var(--fest-veil, 0, 0, 0), .12); }
.fest-expense__lines-title { margin:0 0 .35rem; font-size:.78rem; display:flex; align-items:center; gap:.35rem; }
.fest-expense__lines-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.25rem; }
.fest-expense__lines-list li { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; font-size:.85rem; }
.fest-expense__line-name { font-weight:600; }

/* Inventaire (assez large pour les sous-grilles à 2 colonnes du formulaire
   d'édition — « Quantité (unités finales) »/« Unité finale », etc. — sans
   que leurs libellés ne s'enroulent sur plusieurs lignes). */
.fest-inv-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap:1rem; }
.fest-inv { display:flex; flex-direction:column; gap:.25rem; }
.fest-inv--low { outline:2px solid rgba(220,38,38,.35); }
.fest-inv__head { display:flex; justify-content:space-between; align-items:flex-start; gap:.5rem; }
/* Sélection multiple : la case ouvre l'en-tête, le nom la suit et pousse le
   badge de droite (« space-between » aurait sinon renvoyé le nom à droite,
   la case restant seule à gauche). */
.fest-inv__head .bulk-select-checkbox { flex:0 0 auto; margin-top:.15rem; }
.fest-inv__head .bulk-select-checkbox + .fest-inv__name { margin-right:auto; }
.fest-inv__name { margin:0; font-size:1.05rem; }
.fest-inv__qty { display:flex; align-items:baseline; gap:.35rem; }
.fest-inv__qty-val { font-size:1.9rem; font-weight:800; }
.fest-inv__qty-unit { color:var(--muted, #6b7280); }
.fest-inv__controls { display:flex; align-items:center; gap:.4rem; margin-top:.5rem; flex-wrap:wrap; }
.fest-inv__step-form { display:flex; gap:.3rem; align-items:center; }
.fest-step-btn { width:40px; height:40px; border-radius:12px; border:1px solid var(--line, rgba(0,0,0,.15)); background:var(--surface, #fff);
    cursor:pointer; font-size:1.2rem; display:flex; align-items:center; justify-content:center; }
.fest-step-btn:hover { background:var(--fest-grad, #ff4d8d); color:#fff; border-color:transparent; }
.fest-inv__edit { margin-top:.6rem; }
.fest-inv__edit summary { list-style:none; }

/* Alerte stock bas */
.fest-alert { display:flex; align-items:center; gap:.6rem; padding:.8rem 1rem; border-radius:14px; margin-bottom:1.25rem;
    background:rgba(255,138,61,.14); color:var(--fest-warn, #b45309); font-weight:600; }
.fest-alert .ph { font-size:1.3rem; }

/* Valeur totale du stock (carte résumé inventaire) */
.fest-inv-summary { display:flex; align-items:center; gap:.6rem; padding:.7rem 1rem; margin-bottom:1.25rem; font-size:1rem; }
.fest-inv-summary .ph { font-size:1.35rem; color:var(--fest-pink, #ff4d8d); }
.fest-inv-summary strong { font-weight:800; }
/* Achat / réapprovisionnement chiffré */
.fest-inv__restock summary { color:var(--fest-pink, #ff4d8d); font-weight:600; }

/* Bouton de suppression compact (icône seule) : plus de « grosse pastille ». */
.fest-icon-btn { flex:0 0 auto; width:34px; height:34px; padding:0; border-radius:10px;
    display:inline-flex; align-items:center; justify-content:center; font-size:1rem; line-height:1; }

/* Création manuelle d'une réservation (back-office) */
.fest-fieldset { border:1px solid var(--line, rgba(0,0,0,.1)); border-radius:12px; padding:1rem 1.1rem; margin:0; }
.fest-fieldset legend { padding:0 .4rem; font-weight:700; font-size:.95rem; }
.fest-fieldset .req { color:var(--fest-bad, #dc2626); }
.fest-resa-line { border:1px solid var(--line, rgba(0,0,0,.08)); border-radius:10px; padding:.7rem; margin-bottom:.7rem; }
.fest-resa-line__head { display:grid; grid-template-columns:2fr 0.7fr auto; gap:.6rem; align-items:end; }
.fest-resa-line .form-group { margin:0; }
.fest-resa-line__del { align-self:end; height:fit-content; }
@media (max-width: 640px) {
    .fest-resa-line__head { grid-template-columns:1fr auto; }
    .fest-resa-line__item { grid-column:1 / -1; }
}
/* Config de ligne (options, champs, créneaux) chargée en AJAX */
.fest-line-cfg:not(:empty) { margin-top:.7rem; padding-top:.6rem; border-top:1px dashed var(--line, rgba(0,0,0,.12)); display:flex; flex-direction:column; gap:.7rem; }
.fest-line-cfg__group { border:1px solid var(--line, rgba(0,0,0,.1)); border-radius:8px; padding:.5rem .7rem; margin:0; }
.fest-line-cfg__group legend { font-weight:600; font-size:.88rem; padding:0 .3rem; }
.fest-line-cfg__group .req, .fest-fieldset .req { color:var(--fest-bad, #dc2626); }
.fest-opt-collapse > summary { font-weight:600; font-size:.88rem; cursor:pointer; list-style:revert; }
.fest-opt-collapse[open] > summary { margin-bottom:.3rem; }
.fest-opt-collapse__body { display:flex; flex-direction:column; }
.fest-opt-row { display:flex; align-items:center; gap:.45rem; padding:.2rem 0; }
.fest-opt-row.is-out { opacity:.55; }
/* « Choix unique » quantifiable : le radio et son libellé forment le label cliquable,
   le champ de quantité reste **à côté** (voir `_reservation_line_config.html`). */
.fest-opt-pick { display:flex; align-items:center; gap:.45rem; flex:1 1 auto; min-width:0; cursor:pointer; }
.fest-opt-qty { width:4.5rem; flex:0 0 auto; }
.fest-line-cfg__creneaux { display:flex; flex-wrap:wrap; gap:.4rem; }
.fest-cre-chip { display:inline-flex; align-items:center; gap:.35rem; border:1px solid var(--line, rgba(0,0,0,.18)); border-radius:999px; padding:.25rem .6rem; cursor:pointer; font-size:.88rem; }
.fest-cre-chip.is-out { opacity:.5; cursor:not-allowed; }
.fest-cre-chip input { margin:0; }
.fest-cre-open { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; margin-top:.5rem; font-size:.88rem; }
.fest-cre-time { width:auto; padding:.2rem .4rem; }
.fest-cre-open__msg { flex-basis:100%; }

/* Fournisseurs */
.fest-supplier--inactive { opacity:.6; }
.fest-supplier__contact { list-style:none; margin:.35rem 0; padding:0; display:flex; flex-direction:column; gap:.15rem; font-size:.88rem; }
.fest-supplier__contact li { display:flex; align-items:center; gap:.4rem; }
.fest-supplier__contact a { color:inherit; }

/* Préparation de commande (réappro par fournisseur) */
.fest-reorder-flag { color:var(--fest-warn, #b45309); font-weight:700; }
.fest-order { margin-bottom:1.25rem; }
.fest-order__head { display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:.5rem; margin-bottom:.6rem; }
.fest-order__supplier { margin:0; font-size:1.2rem; display:flex; align-items:center; gap:.4rem; }
.fest-order__meta { display:flex; flex-wrap:wrap; gap:.6rem; align-items:baseline; font-size:.88rem; }
.fest-order__subtotal { font-weight:600; }
.fest-order__table { display:flex; flex-direction:column; }
.fest-order__row { display:grid; grid-template-columns:minmax(8rem,2fr) repeat(5, minmax(4rem,1fr)); gap:.5rem; align-items:center; padding:.35rem 0; border-bottom:1px solid var(--line, rgba(0,0,0,.06)); }
.fest-order__row--head { font-weight:700; font-size:.82rem; color:var(--muted, #6b7280); border-bottom-width:2px; }
.fest-order__row .num { text-align:right; }
.fest-order__name { font-weight:600; }
.fest-order__qty, .fest-order__cost { width:100%; max-width:6rem; margin-left:auto; text-align:right; padding:.25rem .4rem; }
.fest-order__qty-stack { display:flex; flex-direction:column; gap:.25rem; align-items:flex-end; }
.fest-order__unit { width:100%; max-width:8rem; font-size:.78rem; padding:.15rem .3rem; }
.fest-order__foot { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:1rem; margin-top:.8rem; }
.fest-order__foot-fields { display:flex; flex-wrap:wrap; align-items:center; gap:.8rem; }
.fest-order__actions { margin-top:.5rem; }
@media (max-width: 640px) {
    .fest-order__row { grid-template-columns:1fr 1fr; }
    .fest-order__row--head { display:none; }
    .fest-order__name { grid-column:1 / -1; }
}
/* Impression : feuille de commande seule. Masquer .site-header (englobe la nav)
   suffit ; aucune de ces cibles n'a de règle `display` concurrente → pas besoin
   de !important. */
@media print {
    .site-header, .fest-noprint, .page-header-actions { display:none; }
    .fest-order__qty, .fest-order__cost { border:none; box-shadow:none; }
    .content-card { box-shadow:none; border:1px solid #ccc; }
}

/* Mouvements */
.fest-movement-list { list-style:none; margin:0; padding:0; }
.fest-movement { display:flex; align-items:center; gap:.6rem; padding:.4rem 0; border-bottom:1px solid var(--line, rgba(0,0,0,.06)); font-size:.9rem; }
.fest-movement__dir { display:inline-flex; align-items:center; gap:.2rem; font-weight:700; min-width:3.5rem; }
.fest-movement__dir--in { color:var(--fest-ok, #0f9d6b); }
.fest-movement__dir--out { color:var(--fest-bad, #dc2626); }
.fest-movement__item { flex:1; }
.fest-movement__reason { font-size:.82rem; }
.fest-movement__cost { font-size:.82rem; font-weight:600; }
.fest-movement__date { font-size:.8rem; }

/* Tableau de bord : tuiles */
.fest-dash-tile { display:flex; flex-direction:column; gap:.15rem; text-decoration:none; color:var(--text, #2a1640); position:relative; transition:transform .14s, box-shadow .14s; }
a.fest-dash-tile:hover { transform:translateY(-3px); box-shadow:0 16px 36px rgba(42,22,64,.14); }
.fest-dash-tile__icon { font-size:1.5rem; color:var(--fest-pink, #ff4d8d); }
.fest-dash-tile__value { font-size:2rem; font-weight:800; }
.fest-dash-tile--warn .fest-dash-tile__value { color:var(--fest-bad, #dc2626); }
.fest-dash-tile--warn .fest-dash-tile__icon { color:var(--fest-bad, #dc2626); }
/* Vignette « en cours » : même vert que la section mise en avant (.fest-encours) */
.fest-dash-tile--live .fest-dash-tile__value { color:var(--fest-ok, #0f9d6b); }
.fest-dash-tile--live .fest-dash-tile__icon { color:var(--fest-ok, #16a34a); }
.fest-upcoming { list-style:none; margin:0; padding:0; }
.fest-upcoming__item { display:flex; align-items:center; gap:.6rem; padding:.45rem 0; border-bottom:1px solid var(--line, rgba(0,0,0,.06)); }
.fest-upcoming__dot { width:10px; height:10px; border-radius:50%; flex-shrink:0; }
.fest-upcoming__item a { font-weight:600; text-decoration:none; color:var(--text, #2a1640); }
.fest-upcoming__item a:hover { color:var(--fest-pink, #ff4d8d); }

/* Scanner plein écran : styles déplacés dans document_scanner.css
   (composant partagé plateforme, chargé par base.html festivités). */

/* ─────────────────────────────────────────────
   Synchronisation des listes vers iOS Rappels (CalDAV)
   ───────────────────────────────────────────── */
.fest-section-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap; }
.fest-section-head .fest-section-title { margin: 0; }

.fest-list-toolbar { display: flex; align-items: center; gap: .5rem; justify-content: flex-end; margin: 0 0 .5rem; }
.fest-list-rename { display: inline-flex; align-items: center; gap: .35rem; margin-right: auto; color: var(--muted, #6b7280); }
.fest-list-rename .form-control { max-width: 16rem; font-weight: 600; }
.fest-list-rename .autosave-status { font-size: .75rem; }

/* ── Widget d'import Square (recettes, inventaire) ───────────────────────── */
.fest-sqimport__row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.fest-sqimport__row .form-control { flex: 0 1 16rem; min-width: 0; }
/* ⚠️ Le composant de liste déroulante enveloppe le <select> dans un
   `.custom-select` **large de 100 %** (juste dans un formulaire en colonne,
   faux dans une rangée) : la largeur voulue ci-dessus est portée par le champ,
   pas par cette enveloppe — le sélecteur de compte prenait donc toute la ligne
   et renvoyait les deux boutons en dessous. La rangée porte la contrainte,
   l'enveloppe suit. */
.fest-sqimport__row .custom-select { flex: 0 1 16rem; width: auto; min-width: 0; }
.fest-sqimport__cats {
    display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; margin-top: 0.75rem;
    padding: 0.6rem 0.75rem; border: 1px solid var(--border, #e5e7eb); border-radius: 0.5rem;
}
.fest-sqcat { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; }

/* ═══════════════════════════════════════════════════════════════════════════
   SITE PUBLIC FESTIFUN — pages d'univers, formulaire de demande, pied de page
   ═══════════════════════════════════════════════════════════════════════════
   Ajouté avec le socle `app/festivites_pages.py` : le site public n'avait qu'une
   vitrine d'une seule page. Tout est préfixé `body.festif-public` comme le reste
   du bloc public, pour ne jamais déborder sur le back-office ni sur le dojo.  */

/* Accessibilité : lien d'évitement + libellés lus par les lecteurs d'écran.
   Aucun des deux n'existait — le pied de page a un champ courriel sans libellé
   visible, qui serait annoncé « champ de saisie » et rien d'autre. */
body.festif-public .skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    padding: .7rem 1.1rem; background: var(--neon-yellow); color: #0c0820;
    font-weight: 700; border-radius: 0 0 .6rem 0;
}
body.festif-public .skip-link:focus { left: 0; }
body.festif-public .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── Navigation des pages d'univers ─────────────────────────────────────── */
/* ⚠️ Préfixe `festpub-` et non `fest-nav` : `.fest-nav` est DÉJÀ pris par le bloc
   GPS du module terrain (plus haut dans ce fichier), en `flex-direction: column`.
   La collision empilait les liens du site public les uns sous les autres.
   ⚠️ `flex-direction: row` est posé explicitement : ne pas le faire, c'est
   dépendre de l'absence d'une autre règle — c'est exactement ce qui a cassé. */
body.festif-public .festpub-nav {
    display: flex; flex-direction: row; align-items: center;
    gap: .35rem; flex-wrap: wrap; justify-content: flex-end;
}
body.festif-public .festpub-nav__link {
    padding: .4rem .7rem; border-radius: .5rem; color: #d8d2f0;
    font-size: .92rem; font-weight: 600; white-space: nowrap;
}
body.festif-public .festpub-nav__link:hover { color: #fff; background: rgba(255,255,255,.08); }
body.festif-public .festpub-nav__link.is-current { color: var(--neon-cyan); }
/* Bouton d'appel + panier + compte : un groupe, pour que les dispositions les
   déplacent ensemble sans casser leur alignement entre eux. */
body.festif-public .festpub-actions {
    display: flex; align-items: center; gap: .6rem; flex-shrink: 0;
}

/* Pas de menu « burger » : il reposait sur une case à cocher masquée, que la
   règle globale des cases du site (styles.css, spécificité 0,6,1) réaffichait en
   `display: grid` — un carré gris apparaissait dans la barre. Les liens passent
   simplement à la ligne quand la place manque. */
@media (max-width: 860px) {
    body.festif-public .festpub-nav { justify-content: center; }
    body.festif-public .festpub-nav__link { padding: .35rem .55rem; font-size: .86rem; }
}

/* ── Héro d'une page d'univers ──────────────────────────────────────────── */
/* ⚠️ `.fest-page-hero` n'existe plus : le bandeau des pages d'univers est le
   style « bandeau » du socle (`app/public_hero.py`), et son image de fond passe
   désormais par le voile de lisibilité du socle — qui sait en plus enchaîner
   plusieurs photos. Voir la section HERO plus haut. */

/* ── Bande « Obtenir ma soumission » ────────────────────────────────────── */
body.festif-public .fest-quote-band {
    background: var(--grad-hot); padding: 3rem 1.2rem;
}
body.festif-public .fest-quote-band__panel {
    max-width: 720px; margin: 0 auto; text-align: center;
    background: #14102b; border-radius: 1rem; padding: 2.2rem 1.6rem;
    box-shadow: 0 18px 40px rgba(0,0,0,.35);
}
body.festif-public .fest-quote-band__eyebrow {
    text-transform: uppercase; letter-spacing: .22em; font-size: .78rem;
    font-weight: 700; color: var(--neon-yellow); margin: 0 0 .4rem;
}
body.festif-public .fest-quote-band__panel h2 { color: #fff; margin: 0 0 .6rem; font-size: 1.7rem; }
body.festif-public .fest-quote-band__panel p { color: #c9c2e8; margin: 0 0 1.3rem; }

/* ── Informations utiles (blocs Q/R sur deux colonnes) ──────────────────── */
body.festif-public .fest-infos__grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    gap: 1.6rem 2.4rem;
}
body.festif-public .fest-infos__item h3 { color: #fff; font-size: 1.15rem; margin: 0 0 .5rem; }
body.festif-public .fest-infos__item p { color: #c2bce0; margin: 0; line-height: 1.6; white-space: pre-line; }

/* ── Galerie & témoignages ──────────────────────────────────────────────── */
body.festif-public .fest-gallery__grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: .8rem;
}
body.festif-public .fest-gallery__grid figure { margin: 0; }
body.festif-public .fest-gallery__grid img {
    width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: .7rem; display: block;
}
body.festif-public .fest-gallery__grid figcaption {
    color: #a8a1cc; font-size: .85rem; margin-top: .4rem; text-align: center;
}
body.festif-public .fest-quotes__grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 1rem;
}
body.festif-public .fest-quotes blockquote { margin: 0; padding: 1.3rem; }
body.festif-public .fest-quotes blockquote p { font-style: italic; margin: 0 0 .7rem; }
body.festif-public .fest-quotes cite { font-style: normal; font-weight: 700; color: #4b4270; font-size: .9rem; }

/* ── Formulaire de demande ──────────────────────────────────────────────── */
body.festif-public .fest-request {
    display: grid; grid-template-columns: minmax(240px, 1fr) minmax(320px, 1.3fr);
    gap: 2.5rem; align-items: start;
}
@media (max-width: 780px) { body.festif-public .fest-request { grid-template-columns: 1fr; gap: 1.4rem; } }
body.festif-public .fest-request__copy h2 { color: #fff; font-size: 1.9rem; margin: 0 0 .7rem; }
body.festif-public .fest-request__copy p { color: #c2bce0; line-height: 1.65; }
body.festif-public .fest-request__phone a { color: var(--neon-cyan); font-weight: 700; }
body.festif-public .fest-request__form {
    background: #fff; border-radius: 1rem; padding: 1.6rem; display: flex;
    flex-direction: column; gap: .9rem;
}
body.festif-public .fest-request__form .form-row { margin: 0; }
body.festif-public .fest-request__form .form-label { color: #2a1f45; font-weight: 600; }
body.festif-public .form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: .9rem; }
@media (max-width: 520px) { body.festif-public .form-grid-2 { grid-template-columns: 1fr; } }
body.festif-public .fest-request__consent {
    display: flex; gap: .6rem; align-items: flex-start; color: #4b4270; font-size: .9rem;
}
body.festif-public .form-error--global {
    background: #fee2e2; color: #991b1b; padding: .7rem .9rem; border-radius: .5rem; margin: 0;
}
body.festif-public .fest-section--form { max-width: 1100px; }
body.festif-public .fest-section--narrow { max-width: 760px; }

/* ── Confirmations & politiques ─────────────────────────────────────────── */
body.festif-public .fest-confirm { text-align: center; padding: 2.4rem 1.6rem; }
body.festif-public .fest-confirm__icon { font-size: 3rem; margin-bottom: .6rem; }
body.festif-public .fest-confirm h1 { color: #1a1330; margin: 0 0 .7rem; }
body.festif-public .fest-confirm__actions {
    display: flex; gap: .7rem; justify-content: center; flex-wrap: wrap; margin-top: 1.4rem;
}
body.festif-public .fest-policy h1 { color: #fff; margin: 0 0 .3rem; }
body.festif-public .fest-policy__date { color: #a8a1cc; margin: 0 0 1.2rem; }
body.festif-public .fest-policy__contact { color: #c2bce0; margin-top: 1rem; text-align: center; }
body.festif-public .fest-policy__contact a { color: var(--neon-cyan); }
body.festif-public .prod-card__desc { color: #57507d; font-size: .88rem; margin: .1rem 0 .5rem; }
body.festif-public .prod-card__placeholder {
    width: 100%; height: 100%; display: flex; align-items: center;
    justify-content: center; font-size: 2.5rem;
}

/* ── Pied de page ───────────────────────────────────────────────────────── */
body.festif-public .fest-footer {
    background: #0a0718; border-top: 1px solid rgba(255,255,255,.08); margin-top: 3rem;
}
body.festif-public .fest-footer__grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 2rem; padding: 3rem 1.2rem 2rem;
}
body.festif-public .fest-footer__name { color: #fff; font-weight: 800; font-size: 1.15rem; margin: 0 0 .4rem; }
body.festif-public .fest-footer__tagline { color: #a8a1cc; font-size: .9rem; line-height: 1.55; margin: 0 0 .6rem; }
body.festif-public .fest-footer__area { color: #c2bce0; font-size: .88rem; display: flex; gap: .4rem; align-items: center; }
body.festif-public .fest-footer__col h3 {
    color: #fff; font-size: .82rem; text-transform: uppercase; letter-spacing: .12em;
    margin: 0 0 .8rem;
}
body.festif-public .fest-footer__subhead { margin-top: 1.2rem; }
body.festif-public .fest-footer__list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .45rem; }
body.festif-public .fest-footer__list li, body.festif-public .fest-footer__list a { color: #a8a1cc; font-size: .9rem; }
body.festif-public .fest-footer__list a:hover { color: var(--neon-cyan); }
body.festif-public .fest-footer__address { line-height: 1.5; }
body.festif-public .fest-footer__socials { display: flex; gap: .6rem; margin-top: .9rem; }
body.festif-public .fest-footer__socials a {
    width: 2.2rem; height: 2.2rem; border-radius: 50%; display: inline-flex;
    align-items: center; justify-content: center; font-size: 1.1rem;
    color: #fff; background: rgba(255,255,255,.1);
}
body.festif-public .fest-footer__socials a:hover { background: var(--neon-pink); }
body.festif-public .fest-news-form { display: flex; flex-direction: column; gap: .5rem; }
body.festif-public .fest-news-form input[type="email"] {
    padding: .6rem .8rem; border-radius: .5rem; border: 1px solid rgba(255,255,255,.16);
    background: rgba(255,255,255,.06); color: #fff;
}
body.festif-public .fest-news-form input::placeholder { color: #7d76a3; }
body.festif-public .fest-footer__legal {
    display: flex; justify-content: space-between; align-items: center; gap: 1rem;
    flex-wrap: wrap; padding: 1rem 1.2rem 1.6rem; border-top: 1px solid rgba(255,255,255,.07);
}
body.festif-public .fest-footer__legal p { color: #6f6796; font-size: .85rem; margin: 0; }

/* Pastille de compte dans la navigation du back-office (« Demandes 3 »).
   N'existait pas : sans elle, une demande arrivée pendant la journée ne se
   signale nulle part et attend qu'on pense à ouvrir l'écran. */
.nav-badge {
    display: inline-block; min-width: 1.25rem; padding: 0 .35rem;
    margin-left: .35rem; border-radius: 999px; text-align: center;
    background: var(--fest-orange, #ff8a3d); color: #fff;
    font-size: .72rem; font-weight: 800; line-height: 1.25rem;
}

/* Lien « Voir le site » de la barre du back-office (ouvre le site public dans un
   nouvel onglet). Le back-office n'avait aucun chemin vers le site qu'il pilote :
   il fallait retaper l'adresse à la main. */
.fest-view-site {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .4rem .75rem; margin-right: .5rem; border-radius: .5rem;
    border: 1px solid var(--border, #e5e7eb);
    font-size: .88rem; font-weight: 600; white-space: nowrap;
}
.fest-view-site:hover { border-color: var(--fest-orange, #ff8a3d); color: var(--fest-orange, #ff8a3d); }
/* Sur téléphone, la barre est déjà serrée : l'icône seule suffit. */
@media (max-width: 720px) {
    .fest-view-site span { display: none; }
    .fest-view-site { padding: .4rem .5rem; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   EN-TÊTE PUBLIC : LES QUATRE DISPOSITIONS DE LOGO
   ═══════════════════════════════════════════════════════════════════════════
   Liste fermée pilotée par `festivites_public.HEADER_LAYOUTS` : l'admin choisit
   une mise en page déjà écrite ici, il ne rédige jamais de CSS. Les hauteurs
   viennent de deux variables posées en `style=""` sur le <header>
   (--fest-logo-h pour la barre, --fest-hero-logo-h pour le bandeau) : un logo
   lisible dans une barre est minuscule en plein écran, et l'inverse déborde. */

body.festif-public .fest-logo {
    height: var(--fest-logo-h, 3.6rem);
    width: auto; max-width: 100%; object-fit: contain; display: block;
}

/* ── Compact : logo (ou pastille) + nom côte à côte — le repli sans logo ─── */
body.festif-public .fest-header--compact .fest-logo { height: 2.4rem; }

/* ── Grand logo à gauche, menu à droite ─────────────────────────────────── */
body.festif-public .fest-header--gauche .topbar { align-items: center; }

/* ── Logo centré, menu sur une ligne dessous ────────────────────────────── */
/* ⚠️ **Grille et non colonne flex.** En `flex-direction: column`, la règle
   existante `body.festif-public .site-header .brand { margin-right: auto }`
   pousse le logo À GAUCHE au lieu de le centrer — c'est exactement ce qui
   arrivait. La grille place chaque élément par zone, donc cette marge n'a plus
   prise. Elle permet aussi de garder le bouton à DROITE de la rangée du logo,
   comme le site remplacé, pendant que les liens se centrent en dessous. */
body.festif-public .fest-header--centre .topbar {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    grid-template-areas:
        "spacer brand actions"
        "nav    nav   nav";
    align-items: center;
    gap: .4rem .75rem;
    padding-top: .9rem; padding-bottom: .5rem;
}
body.festif-public .fest-header--centre .brand {
    grid-area: brand; justify-content: center; margin-right: 0;
}
body.festif-public .fest-header--centre .festpub-actions {
    grid-area: actions; justify-self: end;
}
body.festif-public .fest-header--centre .festpub-nav {
    grid-area: nav; justify-content: center;
    /* Le menu déborderait en une ligne sur un écran étroit : il défile plutôt
       que de pousser le logo hors du cadre. */
    max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
body.festif-public .fest-header--centre .festpub-nav::-webkit-scrollbar { display: none; }

/* Sur téléphone, la colonne de gauche vide gaspille la largeur : le logo prend
   toute la rangée et les actions passent dessous, centrées. */
@media (max-width: 700px) {
    body.festif-public .fest-header--centre .topbar {
        grid-template-columns: 1fr;
        grid-template-areas: "brand" "actions" "nav";
    }
    body.festif-public .fest-header--centre .festpub-actions { justify-self: center; }
}

/* ── Logo géant dans le bandeau : en-tête discret, logo dans le contenu ─── */
body.festif-public .fest-header--hero .fest-logo { height: 2.2rem; }
body.festif-public .fest-hero-logo {
    display: flex; justify-content: center; padding: 2.4rem 1.2rem .4rem;
}
body.festif-public .fest-hero-logo img {
    height: var(--fest-hero-logo-h, 8rem);
    width: auto; max-width: min(90%, 720px); object-fit: contain;
    /* L'ombre portée évite qu'un logo sombre disparaisse sur le fond sombre du
       thème festif, sans imposer de pastille blanche derrière. */
    filter: drop-shadow(0 6px 18px rgba(0, 0, 0, .45));
}
/* Le héro suit immédiatement : sous le logo, sa grande marge du haut ferait un
   trou. ⚠️ **Les deux formes de DOM**, et c'est ce qui manquait : le héro est
   tantôt le frère suivant lui-même (le bandeau de la page d'accueil), tantôt
   niché dans ce frère (pages d'univers). Écrite avec le seul `+ * .site-hero` —
   un descendant du frère —, la règle ne mordrait pas sur l'accueil, qui
   garderait ses 6rem de padding sous un logo « géant ».
   ⚠️ Les noms ont suivi le passage au socle (`.fest-hero`/`.fest-page-hero` →
   `.site-hero`) : laissés tels quels, ces quatre sélecteurs ne désigneraient
   plus rien et le trou reviendrait — sans qu'aucun test le signale. */
body.festif-public .fest-hero-logo + .site-hero,
body.festif-public .fest-hero-logo + * > .site-hero:first-child,
body.festif-public .fest-hero-logo + * .site-hero { padding-top: 1.6rem; }

/* ── Couronne festive du logo géant ───────────────────────────────────────
   Le logo « hero » est la toute première chose qu'on voit du site, et il était
   posé SEUL au milieu d'une grande zone sombre : la fête ne commençait qu'au
   bandeau, deux écrans plus bas. On lui donne la grammaire d'ornement qui existe
   déjà juste en dessous — lueur douce derrière, pastilles flottantes en
   satellite — plutôt qu'un second vocabulaire pour dire la même chose.

   ⚠️ **DEUX repères, et c'est voulu.** La lueur épouse l'IMAGE (`__stage`, une
   boîte en ligne dimensionnée par elle) : posée sur la bande, elle éclairerait
   toute la largeur de l'écran au lieu d'entourer le logo. Les pastilles, elles,
   se posent sur la BANDE pleine largeur, exactement comme celles du bandeau qui
   suit — c'est ce qui leur donne le même écartement, et c'est la seule façon de
   l'obtenir : la boîte du logo porte une marge transparente d'un fichier à
   l'autre (mesuré ici : 447 px pour un disque de 330), donc un pourcentage
   calculé dessus ne veut pas dire la même distance selon le logo téléversé.

   ⚠️ **Le RANG fait la position et le décalage d'animation** (même règle que
   `.site-hero__emoji`) : la liste vit dans `festivites_public.HERO_LOGO_EMOJIS`,
   et une pastille ajoutée là sans sa règle ici se poserait en haut à gauche, SUR
   le logo. Un test l'exige.

   ⚠️ **La keyframe est celle du bandeau** (`site-hero-float`, styles.css) : en
   écrire une seconde, identique, ferait deux flottements à tenir d'accord — et
   le jour où l'un serait adouci, le premier écran montrerait deux rythmes. */
/* Repère des pastilles : la bande, comme le bandeau qui suit. */
body.festif-public .fest-hero-logo { position: relative; }

body.festif-public .fest-hero-logo__stage {
    position: relative;
    display: inline-flex;
}

/* Lueur derrière le logo. ⚠️ Elle ne remplace pas l'ombre portée de l'image :
   celle-ci détache un logo sombre du fond, celle-là l'entoure de couleur. */
body.festif-public .fest-hero-logo__glow {
    position: absolute;
    inset: -8%;
    z-index: 0;
    border-radius: 50%;
    pointer-events: none;
    background:
        radial-gradient(circle at 32% 30%, color-mix(in srgb, var(--neon-pink) 42%, transparent), transparent 60%),
        radial-gradient(circle at 72% 34%, color-mix(in srgb, var(--neon-cyan) 34%, transparent), transparent 58%),
        radial-gradient(circle at 50% 78%, color-mix(in srgb, var(--neon-purple) 40%, transparent), transparent 62%);
    filter: blur(34px);
    opacity: .75;
    animation: fest-hero-logo-glow 9s ease-in-out infinite;
}

/* L'image passe DEVANT la lueur : sans ce contexte d'empilement, un logo à fond
   transparent se ferait voiler par le halo au lieu d'être posé dessus. */
body.festif-public .fest-hero-logo__stage img { position: relative; z-index: 1; }

body.festif-public .fest-hero-logo__emoji {
    position: absolute;
    z-index: 2;
    line-height: 1;
    /* La MÊME que celles du bandeau : les deux couronnes se voient d'un seul
       coup d'œil sur le premier écran, et deux tailles y feraient deux
       ornements qui se ressemblent sans se répondre. */
    font-size: clamp(1.8rem, 4vw, 3rem);
    opacity: .92;
    pointer-events: none;
    user-select: none;
    filter: drop-shadow(0 4px 12px rgba(0, 0, 0, .5));
    animation: site-hero-float 6s ease-in-out infinite;
}

/* ⚠️ **Les pourcentages sont ceux du bandeau** (`.site-hero__emoji--1..4`), aux
   mêmes bords et dans le même ordre — pas une valeur choisie à l'œil : les deux
   couronnes se lisent ensemble, et deux écartements différents feraient deux
   constellations qui ne se répondent pas. */
body.festif-public .fest-hero-logo__emoji--1 { top: 16%; left: 6%; animation-delay: 0s; }
body.festif-public .fest-hero-logo__emoji--2 { top: 10%; right: 8%; animation-delay: 1.1s; }
body.festif-public .fest-hero-logo__emoji--3 { bottom: 14%; left: 14%; animation-delay: 2.2s; }
body.festif-public .fest-hero-logo__emoji--4 { bottom: 18%; right: 12%; animation-delay: 3.3s; }

@keyframes fest-hero-logo-glow {
    0%, 100% { opacity: .6; transform: scale(0.96); }
    50% { opacity: .9; transform: scale(1.05); }
}

/* ⚠️ Une @keyframes n'est pas coupée par le bloc « mouvement réduit » générique
   de styles.css (qui ne neutralise que les transitions) : chaque animation s'y
   nomme. Celles-ci vivent dans le module, leur coupure aussi — et le sélecteur
   porte `body.festif-public[…]` pour gagner par SPÉCIFICITÉ et non par l'ordre
   des feuilles. */
body.festif-public[data-motion-level="reduced"] .fest-hero-logo__emoji,
body.festif-public[data-motion-level="reduced"] .fest-hero-logo__glow {
    animation: none;
}

@media (prefers-reduced-motion: reduce) {
    body.festif-public .fest-hero-logo__emoji,
    body.festif-public .fest-hero-logo__glow { animation: none; }
}

@media (max-width: 720px) {
    /* Sur téléphone, un logo « géant » mangerait l'écran avant le premier mot. */
    body.festif-public .fest-logo { height: min(var(--fest-logo-h, 3.6rem), 3rem); }
    body.festif-public .fest-hero-logo img { height: min(var(--fest-hero-logo-h, 8rem), 7rem); }
    body.festif-public .fest-hero-logo { padding-top: 1.4rem; }
    /* Le logo rétrécit mais la bande garde la largeur de l'écran : les
       pastilles se rapprochent des bords plutôt que du logo — même geste que
       les `.site-hero__emoji` sous 640 px. */
    body.festif-public .fest-hero-logo__emoji--1 { top: 8%; left: 3%; }
    body.festif-public .fest-hero-logo__emoji--2 { top: 4%; right: 3%; }
    body.festif-public .fest-hero-logo__emoji--3 { bottom: 10%; left: 6%; }
    body.festif-public .fest-hero-logo__emoji--4 { bottom: 14%; right: 6%; }
}

/* ── Écran « Site public » : aperçu du logo et sélecteur de disposition ──── */
.fest-logo-admin { display: flex; gap: 1.2rem; flex-wrap: wrap; align-items: flex-start; }
.fest-logo-admin__preview {
    flex: 0 0 auto; min-width: 190px; min-height: 110px; padding: .9rem;
    display: flex; align-items: center; justify-content: center;
    border: 1px dashed var(--border, #e5e7eb); border-radius: .6rem;
    /* Damier : un logo à fond transparent se juge mal sur du blanc uni. */
    background-image:
        linear-gradient(45deg, rgba(0,0,0,.06) 25%, transparent 25%, transparent 75%, rgba(0,0,0,.06) 75%),
        linear-gradient(45deg, rgba(0,0,0,.06) 25%, transparent 25%, transparent 75%, rgba(0,0,0,.06) 75%);
    background-size: 16px 16px; background-position: 0 0, 8px 8px;
}
.fest-logo-admin__preview img { max-width: 180px; max-height: 100px; object-fit: contain; }
.fest-logo-admin__actions { flex: 1 1 260px; min-width: 240px; }

.fest-layout-picker {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: .7rem; margin-top: .5rem;
}
.fest-layout-opt {
    display: flex; flex-direction: column; gap: .35rem; cursor: pointer;
    padding: .8rem; border: 2px solid var(--border, #e5e7eb); border-radius: .7rem;
}
.fest-layout-opt:hover { border-color: var(--fest-orange, #ff8a3d); }
.fest-layout-opt.is-selected,
.fest-layout-opt:has(input:checked) {
    border-color: var(--fest-orange, #ff8a3d);
    box-shadow: 0 0 0 3px rgba(255, 138, 61, .18);
}
.fest-layout-opt input { position: absolute; opacity: 0; pointer-events: none; }
.fest-layout-opt__label { font-weight: 700; font-size: .92rem; }
.fest-layout-opt__hint { font-size: .82rem; color: var(--text-muted, #6b7280); line-height: 1.45; }

/* « Où afficher le grand logo » ne concerne que la disposition qui en pose un :
   masqué autrement, parce qu'un réglage sans effet visible se lit comme un
   bogue. ⚠️ `:has()` sur le picker, donc juste sans JavaScript — et non une
   classe posée au rendu, qui ne suivrait pas le clic. */
/* Le picker de forme de bandeau vit dans le socle (styles.css) mais s'affiche
   d'abord ici : on lui donne l'orange du back-office, celui du sélecteur de
   disposition posé juste au-dessus. Deux anneaux de couleurs différentes pour
   deux choix voisins se lisent comme une incohérence. */
body.is-festivites .hero-style-picker {
    --hero-style-accent: var(--fest-orange, #ff8a3d);
    --hero-style-accent-soft: rgba(255, 138, 61, .18);
}

.fest-hero-logo-scope { display: none; }
.fest-layout-picker:has(input[value="hero"]:checked) ~ .fest-hero-logo-scope,
.fest-layout-picker:has(input[value="hero"]:checked) + * + .fest-hero-logo-scope {
    display: block;
}

/* ── Aperçu du bouton du haut de page ───────────────────────────────────── */
/* ⚠️ Le vrai bouton (`.header-cta`) vit sous `body.festif-public` : il ne peut
   pas être réutilisé tel quel dans le back-office, dont le thème est différent.
   L'aperçu recrée donc la barre sombre du site en miniature, avec les couleurs
   du thème passées en variables par le gabarit — c'est ce qui permet de choisir
   une couleur sans enregistrer, aller voir, revenir (même parti pris que
   `.fest-layout-opt`). */
.fest-cta-preview {
    margin-top: .9rem; border-radius: .7rem; overflow: hidden;
    border: 1px solid var(--border, #e5e7eb);
    background: radial-gradient(120% 180% at 10% -40%, rgba(255,46,136,.28), transparent 60%), #0c0820;
}
.fest-cta-preview__bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; padding: .7rem .9rem;
}
.fest-cta-preview__brand {
    width: 4.5rem; height: .7rem; border-radius: 999px;
    background: rgba(255,255,255,.28);
}
.fest-cta-preview__btn {
    --cta-color: var(--cta-accent, #ffe14d); --cta-ink: #0c0820;
    padding: .45rem 1rem; border-radius: 999px; font-weight: 700; font-size: .88rem;
    color: var(--cta-ink); background: var(--cta-color);
    box-shadow: 0 0 16px color-mix(in srgb, var(--cta-color) 45%, transparent);
    max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fest-cta-preview__btn--primaire { --cta-color: var(--cta-primary, #ff2e88); --cta-ink: #fff; }
.fest-cta-preview__btn--secondaire { --cta-color: var(--cta-secondary, #22d3ee); --cta-ink: #05202b; }
.fest-cta-preview__btn--contour {
    --cta-color: transparent; --cta-ink: #fff;
    border: 2px solid rgba(255,255,255,.45); padding: calc(.45rem - 2px) calc(1rem - 2px);
    box-shadow: none;
}
/* Bouton éteint : l'aperçu le dit au lieu de disparaître — un cadre vide se
   lirait comme un bogue d'affichage. */
.fest-cta-preview.is-off { opacity: .45; }
.fest-cta-preview.is-off .fest-cta-preview__btn { text-decoration: line-through; }

/* ── Blocs d'une page d'univers : liste ordonnable ──────────────────────── */
/* La liste remplace la grille de cartes : on ne peut pas glisser-déposer dans
   une grille sans que l'ordre visuel mente sur l'ordre réel de la page. */
.fest-sections-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .9rem; }
/* ⚠️ `align-items: stretch` : `.sortable-item` est prévu pour une ligne (poignée
   + contenu côte à côte) et cale ses enfants en `flex-start` ; en colonne, cela
   rétrécit le formulaire à la largeur de son contenu — il n'occupait que 720px
   d'une carte de 1050 et aucune colonne supplémentaire ne pouvait apparaître. */
.fest-sections-list .sortable-item { cursor: default; align-items: stretch; }
/* ⚠️ Poignée en RAIL : elle vit dans l'en-tête (la carte est en colonne) mais
   longe la carte sur TOUTE sa hauteur. La règle vit dans `styles.css`, avec les
   deux autres écrans qui ont exactement cette structure (éditeurs par blocs du
   dojo) — trois cartes, une seule décision. Elle y a été un quart de cercle
   (bande arrêtée à la ligne de titre, un seul coin arrondi) : le rayon était
   alors recopié ici, il vient désormais de `--handle-corner`, celui-là même qui
   peint la carte. */
/* ⚠️ `justify-content` remis à `flex-start` : `.fest-inv__head` sépare son nom
   et son compteur aux deux bouts (patron des cartes d'inventaire). Ici la ligne
   occupe toute la largeur de la carte, ce qui renvoyait le titre du bloc à
   l'autre bout, loin de sa poignée. */
.fest-sections-list .sortable-item-main { display: flex; align-items: center; justify-content: flex-start; gap: .6rem; }
/* Seule la poignée attrape le glissement : sans ça, sélectionner du texte dans
   un champ du bloc déclencherait un déplacement. */
.fest-sections-list .drag-handle { cursor: grab; user-select: none; }
.fest-sections-list .drag-handle:active { cursor: grabbing; }
.fest-sections-list .sortable-item.dragging { opacity: .55; }
.fest-sections-list .sortable-item.drop-target { outline: 2px dashed var(--fest-orange, #ff8a3d); outline-offset: 3px; }

/* ⚠️ **Plus de décalage à recopier ici.** La description s'alignait sur le titre
   du bloc par un `padding-left` qui refaisait le calcul de la bande
   (`calc(2.3rem + .6rem - var(--handle-inset-x))`) — donc la largeur de la
   poignée écrite une deuxième fois, dans une deuxième feuille. Depuis que la
   poignée fait la pleine hauteur de la carte, c'est la CARTE qui réserve la
   bande en padding : tout ce qu'elle contient s'aligne dessus, sans qu'une
   ligne le redemande. */
.fest-sections-list .fest-section-hint {
    margin: .1rem 0 .6rem;
    font-size: .88em;
}
/* Champs d'un bloc : en colonnes. La carte occupe toute la largeur de la page,
   et les champs y étaient empilés en une seule colonne étroite — les deux tiers
   de la carte restaient vides et la page devenait un long ruban à faire défiler.
   Même patron que les cartes de logistique (`.fest-logi--full`) : des colonnes
   d'au moins 260px qui retombent à une seule sur téléphone (`min(100%, …)`). */
.fest-sections-list .fest-form-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
    align-items: start;
}
/* Un champ long ne se met pas en colonne : il prend la ligne entière (sinon le
   texte de la page se rédige dans un couloir de 260px). L'interrupteur et son
   statut d'enregistrement passent aussi la ligne, pour rester sous les champs. */
.fest-sections-list .fest-form-grid > .fest-section-field--wide,
.fest-sections-list .fest-form-grid > .ui-toggle,
.fest-sections-list .fest-form-grid > .autosave-status { grid-column: 1 / -1; }

/* ── Panier public ──────────────────────────────────────────────────────── */
/* ⚠️ `display: contents` et non un conteneur ordinaire : la barre d'actions est
   un `flex` avec `gap`, et une enveloppe vide y créerait un espace fantôme entre
   le bouton d'appel et l'icône de compte. Vide, cet élément ne génère aucune
   boîte ; plein, son lien devient directement l'élément flex. Le conteneur, lui,
   doit exister en permanence : c'est la zone que les gestes du panier
   rafraîchissent (`#fest-cart-slot`). */
body.festif-public .festpub-cart-slot { display: contents; }
body.festif-public .festpub-cart { position: relative; }
body.festif-public .festpub-cart__count {
    position: absolute; top: -.35rem; right: -.5rem;
    min-width: 1.15rem; padding: 0 .28rem; border-radius: 999px;
    background: var(--neon-pink); color: #fff;
    font-size: .68rem; font-weight: 800; line-height: 1.15rem; text-align: center;
}
body.festif-public .fest-cart__title { color: #fff; margin: 0 0 1.2rem; font-size: 2rem; }
/* Avis de revalidation : ce que le panier a dû corriger tout seul (date partie,
   article retiré du catalogue). Rendu DANS la zone vivante, donc toujours
   présent après un geste — voir `_panier_live.html`. */
body.festif-public .fest-cart__notice {
    padding: .9rem 1.1rem; margin: 0 0 1rem;
    border-left: 4px solid var(--neon-yellow, #ffd54a);
}
body.festif-public .fest-cart__notice-title {
    margin: 0 0 .35rem; font-weight: 800; color: #1a1330;
    display: flex; align-items: center; gap: .4rem;
}
body.festif-public .fest-cart__notice ul { margin: 0; padding-left: 1.1rem; color: #57507d; font-size: .92rem; }
body.festif-public .fest-cart { list-style: none; padding: 0; margin: 0 0 1.2rem; display: flex; flex-direction: column; gap: .8rem; }
body.festif-public .fest-cart__line { padding: 1.1rem 1.2rem; }
body.festif-public .fest-cart__head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
body.festif-public .fest-cart__head h3 { margin: 0; color: #1a1330; font-size: 1.1rem; }
body.festif-public .fest-cart__total { margin: 0; font-weight: 800; color: #1a1330; white-space: nowrap; }
body.festif-public .fest-cart__dates { margin: .4rem 0 0; color: #57507d; font-size: .9rem; display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
body.festif-public .fest-cart__slots { color: #6b6390; }
body.festif-public .fest-cart__options { margin: .5rem 0 0; padding-left: 1.1rem; color: #57507d; font-size: .88rem; }
body.festif-public .fest-cart__actions {
    display: flex; justify-content: space-between; align-items: flex-end;
    gap: 1rem; flex-wrap: wrap; margin-top: .9rem;
    padding-top: .8rem; border-top: 1px solid rgba(var(--fest-veil, 0, 0, 0), .08);
}
body.festif-public .fest-cart__qty { display: flex; align-items: flex-end; gap: .5rem; }
body.festif-public .fest-cart__qty label { color: #2a1f45; font-size: .85rem; font-weight: 600; }
body.festif-public .fest-cart__qty input {
    width: 4.5rem; padding: .4rem .5rem; border-radius: .4rem; border: 1px solid #cfc7e6;
}
body.festif-public .fest-cart__remove {
    background: none; border: 0; cursor: pointer; color: var(--fest-bad, #b91c1c);
    font-weight: 600; font-size: .9rem; padding: .35rem .1rem;
}
body.festif-public .fest-cart__remove:hover { text-decoration: underline; }
body.festif-public .fest-cart__summary { padding: 1.2rem; }
body.festif-public .fest-cart__row { display: flex; justify-content: space-between; padding: .35rem 0; color: #2a1f45; }
body.festif-public .fest-cart__row--total {
    border-top: 1px solid rgba(var(--fest-veil, 0, 0, 0), .12); margin-top: .4rem; padding-top: .7rem; font-size: 1.15rem;
}
body.festif-public .fest-cart__cta { display: flex; gap: .7rem; flex-wrap: wrap; margin-top: 1rem; }
body.festif-public .fest-cart__recap ul { list-style: none; padding: 0; margin: .6rem 0; }
body.festif-public .fest-cart__recap li { display: flex; justify-content: space-between; gap: 1rem; padding: .3rem 0; color: #2a1f45; }
body.festif-public .fest-cart__form { margin-top: 1rem; display: flex; flex-direction: column; gap: .8rem; }
body.festif-public .fest-cart__note { font-size: .85rem; margin: .2rem 0 0; }
body.festif-public .fest-cart__hint { color: #a8a1cc; font-size: .88rem; margin: .6rem 0 0; }

/* ── Aperçus visuels des dispositions d'en-tête ─────────────────────────── */
/* Maquette miniature du site, pas un croquis en caractères : le logo téléversé
   y apparaît vraiment, sur le fond sombre du site public. Choisir une mise en
   page sans la voir, c'est enregistrer, aller voir, revenir. */
.fest-lp {
    display: block; border-radius: .5rem; overflow: hidden;
    background: #0f0b24; border: 1px solid rgba(255,255,255,.09);
    aspect-ratio: 16 / 10;
}
.fest-lp__bar {
    display: flex; align-items: center; gap: .3rem;
    padding: .38rem .45rem; background: #16112f;
    border-bottom: 1px solid rgba(255,255,255,.07);
}
.fest-lp__logo { display: flex; align-items: center; flex: 0 0 auto; }
.fest-lp__logo img { height: 14px; width: auto; max-width: 62px; object-fit: contain; display: block; }
.fest-lp__mark {
    display: block; width: 15px; height: 15px; border-radius: 4px;
    background: linear-gradient(135deg, #ff2e88, #a855f7 60%, #22d3ee);
}
.fest-lp__mark--big { width: 46px; height: 46px; border-radius: 12px; }
.fest-lp__links { display: flex; gap: .22rem; margin-left: auto; }
.fest-lp__links i { display: block; width: 15px; height: 4px; border-radius: 2px; background: rgba(255,255,255,.32); }
.fest-lp__links i:last-child { width: 22px; background: #ffe14d; }
.fest-lp__body { display: block; padding: .5rem .55rem; }
.fest-lp__title { display: block; height: 7px; width: 58%; margin: 0 auto .28rem; border-radius: 3px; background: rgba(255,255,255,.55); }
.fest-lp__text { display: block; height: 4px; width: 76%; margin: 0 auto .45rem; border-radius: 2px; background: rgba(255,255,255,.22); }
.fest-lp__grid { display: flex; gap: .25rem; justify-content: center; }
.fest-lp__grid i { display: block; width: 26%; height: 20px; border-radius: 4px; background: rgba(255,255,255,.13); }

/* Centré : logo au milieu, liens dessous. */
.fest-lp--centre .fest-lp__bar { flex-direction: column; gap: .25rem; }
.fest-lp--centre .fest-lp__links { margin-left: 0; }
.fest-lp--centre .fest-lp__logo img { height: 19px; max-width: 84px; }
.fest-lp--centre .fest-lp__mark { width: 19px; height: 19px; }

/* Grand logo à gauche. */
.fest-lp--gauche .fest-lp__logo img { height: 22px; max-width: 78px; }
.fest-lp--gauche .fest-lp__mark { width: 22px; height: 22px; }

/* Logo géant dans le bandeau : barre discrète, logo dans le corps. */
.fest-lp--hero .fest-lp__logo img { height: 11px; max-width: 46px; }
.fest-lp--hero .fest-lp__mark { width: 11px; height: 11px; }
.fest-lp__herologo { display: flex; justify-content: center; margin: .15rem 0 .4rem; }
.fest-lp__herologo img { height: 42px; width: auto; max-width: 70%; object-fit: contain; }

/* Sélection : pastille radio visible (l'input réel est hors flux). */
.fest-layout-opt__label { display: flex; align-items: center; gap: .45rem; }
.fest-layout-opt__radio {
    width: 15px; height: 15px; flex: 0 0 auto; border-radius: 50%;
    border: 2px solid var(--border, #cbd5e1); background: transparent;
}
.fest-layout-opt:has(input:checked) .fest-layout-opt__radio {
    border-color: var(--fest-orange, #ff8a3d);
    box-shadow: inset 0 0 0 3px var(--fest-orange, #ff8a3d);
}

/* ── Sélecteur d'image partagé : la teinte, rien d'autre ────────────────────
   ⚠️ La peau du composant vit dans `styles.css` (la seule feuille que les sept
   bases chargent). Elle vivait ICI, ce qui la rendait invisible au portail du
   dojo, au portail système et aux compétitions : le composant se disait
   « neutre » et n'était utilisable que sur Festifun. Ne reste ici que le
   remappage de l'accent — patron `--resa-accent` du calendrier de réservation :
   une teinte de module ne justifie pas une seconde copie des règles. */
body.is-festivites .image-drop,
body.festif-public .image-drop {
    --image-drop-accent: var(--fest-orange, #ff8a3d);
}

/* ── Invitations imprimables (site public) ──────────────────────────────── */
body.festif-public .fest-invit { display: flex; flex-direction: column; gap: 1rem; padding: 1.5rem; }
body.festif-public .fest-invit__themes { border: 0; padding: 0; margin: 0; }
/* ⚠️ 132px et non 140 : la page de confirmation d'une réservation rend le même
   formulaire dans une carte de 620 px (grille utile 572 px). À 140, les quatre
   habillages passaient à 3 + 1 — le quatrième seul sur sa rangée se lit comme
   une option à part. `auto-fit` ne crée jamais plus de colonnes qu'il n'y a
   d'habillages, donc la page dédiée, plus large, ne change pas. */
body.festif-public .fest-invit__theme-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
    gap: .7rem; margin-top: .5rem;
}
body.festif-public .fest-invit__theme {
    display: flex; flex-direction: column; gap: .3rem; cursor: pointer;
    padding: .6rem; border-radius: .6rem; border: 2px solid transparent;
    background: rgba(var(--fest-veil, 0, 0, 0), .03);
}
body.festif-public .fest-invit__theme input { position: absolute; opacity: 0; pointer-events: none; }
/* Loupe d'aperçu. ⚠️ Elle est POSÉE SUR la vignette, pas rangée sous le libellé :
   sous la carte, elle repoussait le nom et l'astuce d'une ligne sur chacun des
   huit habillages — de la place perdue pour un geste qu'on fait une fois.
   ⚠️ C'est le conteneur qui porte `position: relative`, jamais la carte : le
   `<label>` change de bordure quand il est coché, et la loupe suivrait ce
   décalage d'un pixel. */
body.festif-public .fest-invit__theme-slot { position: relative; }
body.festif-public .fest-invit__zoom {
    position: absolute; top: .95rem; right: .95rem;
    display: inline-flex; align-items: center; justify-content: center;
    /* ⚠️ 28 px de zone de clic pour une icône de 15 : c'est la borne du doigt
       (patron des segments de `.registration-progress`). Réduite à l'icône,
       elle se rate une fois sur deux et on coche l'habillage à la place. */
    width: 28px; height: 28px; border-radius: 999px; font-size: .95rem;
    color: #2a1f45; background: rgba(255,255,255,.92);
    box-shadow: 0 1px 4px rgba(20,10,40,.22);
    text-decoration: none; line-height: 1;
}
body.festif-public .fest-invit__zoom:hover,
body.festif-public .fest-invit__zoom:focus-visible {
    background: #fff; color: var(--neon-pink); transform: scale(1.08);
}
body.festif-public .fest-invit__theme:has(input:checked) {
    border-color: var(--neon-pink); background: rgba(255,46,136,.07);
}
/* Aperçu du carton : les couleurs viennent de variables posées par le gabarit,
   depuis le registre serveur — jamais d'une valeur saisie. */
body.festif-public .fest-invit__card {
    display: flex; flex-direction: column; position: relative; overflow: hidden;
    aspect-ratio: 4 / 5; border-radius: .4rem;
    background: var(--t-bg); border: 1px solid var(--t-accent);
}
body.festif-public .fest-invit__card-band {
    display: block; position: relative; padding: .3rem; text-align: center;
    background: var(--t-band); color: var(--t-band-ink);
    font-size: .5rem; font-weight: 800; letter-spacing: .04em;
}
/* Feston : les mêmes demi-disques que le carton imprimé (`_draw_band`) —
   l'aperçu doit montrer ce qu'on reçoit, pas un rectangle net. */
body.festif-public .fest-invit__card-band::after {
    content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 5px;
    background: radial-gradient(circle 4.4px at 4.5px 0,
                var(--t-band) 96%, transparent 98%) 0 0 / 9px 5px repeat-x;
}
body.festif-public .fest-invit__card-emoji { display: block; text-align: center; font-size: 1.6rem; margin: .5rem 0 .1rem; }
body.festif-public .fest-invit__card-pill {
    display: block; height: 7px; width: 34%; margin: .2rem auto .3rem;
    border-radius: 99px; background: var(--t-accent); opacity: .38;
}
body.festif-public .fest-invit__card-line {
    display: block; height: 3px; width: 62%; margin: .25rem auto;
    border-radius: 2px; background: var(--t-ink); opacity: .35;
}
body.festif-public .fest-invit__card-line.short { width: 40%; }
/* Bandeau RSVP : `margin-top: auto` le colle au bas de la colonne flex, comme
   il est collé au bas du carton. */
body.festif-public .fest-invit__card-strip {
    display: block; height: 9px; margin: auto .3rem .3rem;
    border-radius: 4px; background: var(--t-band); opacity: .3;
}
/* Formats de feuille : mêmes tuiles que l'habillage, mais l'aperçu montre la
   FEUILLE (proportions du papier lettre, un rectangle par carton). Grille plus
   étroite — un aperçu de feuille se lit petit, quatre par rangée tiennent aussi
   dans la carte de 620 px de la page de confirmation. */
body.festif-public .fest-invit__format-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
    gap: .7rem; margin-top: .5rem;
}
/* ⚠️ Hauteur fixe et largeur déduite de l'orientation (`aspect-ratio` posé par
   le gabarit) : à largeur fixe, une feuille paysage était deux fois moins haute
   qu'une portrait et les libellés des quatre tuiles ne s'alignaient plus. */
body.festif-public .fest-invit__sheet {
    display: grid; grid-auto-rows: 1fr; gap: 3px; padding: 4px;
    height: 78px; width: auto; max-width: 100%;
    margin: 0 auto .15rem; border-radius: .25rem;
    background: #fff; box-shadow: 0 0 0 1px rgba(26,19,48,.18);
}
/* Un carton : liseré + bandeau de titre, assez pour reconnaître l'invitation
   sans redessiner le carton au complet (l'habillage s'en charge juste au-dessus). */
body.festif-public .fest-invit__sheet > span {
    border-radius: 2px; border: 1px solid rgba(26,19,48,.16);
    background: linear-gradient(var(--neon-pink) 0 0) top / 100% 22% no-repeat,
                rgba(26,19,48,.07);
}
body.festif-public .fest-invit__format:has(input:checked) .fest-invit__sheet {
    box-shadow: 0 0 0 1px var(--neon-pink);
}
body.festif-public .fest-invit__theme-name { font-weight: 700; font-size: .88rem; color: #1a1330; }
body.festif-public .fest-invit__theme-hint { font-size: .78rem; color: #57507d; line-height: 1.35; }
body.festif-public .fest-invit .form-label { color: #2a1f45; }
body.festif-public .fest-invit legend.form-label { color: #2a1f45; font-weight: 600; }
body.festif-public .fest-invit__note { font-size: .85rem; margin: .2rem 0 0; color: #57507d; }

/* ─────────────────────────────────────────────
   Bandeau roulant « pannes Square en cours » (back-office + PWA terrain)
   ─────────────────────────────────────────────
   Festifun encaisse par Square : une panne de Square est une panne de caisse,
   en plein service. Voir `app/festivites_square_status.py`.

   ⚠️ Portée `body.is-festivites` : la règle sert le back-office ET la PWA
   terrain (`terrain-body` porte aussi `is-festivites`), qui chargent toutes
   deux cette feuille. Ne pas la restreindre à `.is-admin`.

   ⚠️ NON collant, volontairement : il se lit à l'arrivée sur la page (un
   back-office navigue sans arrêt) et il est masquable. Collé, il mangerait une
   bande permanente de hauteur sur le téléphone du camion — là où l'écran est
   le plus rare et l'info la plus utile.

   ⚠️ L'animation réutilise `@keyframes fest-marquee` (déjà globale, employée
   par le bandeau festif de la vitrine) : pas de seconde animation jumelle. */
/* Deux variables par ton, pas une : `--statusbar-surface` est un fond (les
   tokens `--warning`/`--error` du thème SONT des surfaces pâles en clair,
   sombres en foncé) et `--statusbar-ink` en est la version contrastée, tirée
   vers `--text`. C'est `--statusbar-ink` qui porte le signal — dilué dans le
   fond, un avis de panne se confondait avec une bannière d'information. */
body.is-festivites .fest-statusbar {
    --statusbar-surface: var(--warning);
    --statusbar-ink: color-mix(in srgb, var(--warning) 30%, var(--text) 70%);
    background: var(--statusbar-surface);
    border-block: 1px solid var(--statusbar-ink);
    color: var(--text);
}
body.is-festivites .fest-statusbar[data-tone="error"] {
    --statusbar-surface: var(--error);
    --statusbar-ink: color-mix(in srgb, var(--error) 30%, var(--text) 70%);
}
body.is-festivites .fest-statusbar[data-tone="info"] {
    --statusbar-surface: var(--accent-soft);
    --statusbar-ink: color-mix(in srgb, var(--accent-soft) 30%, var(--text) 70%);
}

body.is-festivites .fest-statusbar__inner {
    display: flex;
    align-items: center;
    gap: .7rem;
    /* Aligné sur la largeur de contenu du site, comme `.container`. */
    max-width: 1200px;
    margin: 0 auto;
    padding: .45rem 1rem;
}

/* Pastille pleine : c'est elle qui donne au bandeau sa lecture immédiate
   « avis », avant même qu'on lise le texte. Le fond prend l'encre du ton et le
   texte reprend la couleur de surface — donc contrasté dans les deux thèmes,
   sans couleur écrite en dur. */
body.is-festivites .fest-statusbar__badge {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    flex: 0 0 auto;
    padding: .18rem .5rem;
    border-radius: 99px;
    background: var(--statusbar-ink);
    color: var(--statusbar-surface);
    font-weight: 800;
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .05em;
}
body.is-festivites .fest-statusbar__badge i { font-size: 1.05rem; }

/* La fenêtre masque ce qui dépasse ; la piste, elle, défile dedans. */
body.is-festivites .fest-statusbar__viewport {
    flex: 1 1 auto;
    overflow: hidden;
    /* `min-width: 0` : sans lui, un enfant en `white-space: nowrap` impose sa
       largeur intrinsèque à l'élément flex et déborde au lieu d'être rogné. */
    min-width: 0;
}
body.is-festivites .fest-statusbar__track {
    display: inline-block;
    white-space: nowrap;
}
/* Le défilement n'est armé que quand le contenu déborde (classe posée par le
   script) : un message court reste immobile et lisible. */
body.is-festivites .fest-statusbar.is-rolling .fest-statusbar__track {
    animation: fest-marquee 30s linear infinite;
}
/* Lecture au survol / au clavier : on arrête la piste plutôt que de courir
   après un lien qui bouge. */
body.is-festivites .fest-statusbar:hover .fest-statusbar__track,
body.is-festivites .fest-statusbar:focus-within .fest-statusbar__track {
    animation-play-state: paused;
}

body.is-festivites .fest-statusbar__item {
    display: inline-flex;
    align-items: baseline;
    gap: .45rem;
    margin-right: 2.4rem;
    font-size: .85rem;
}
body.is-festivites .fest-statusbar__item--muted { opacity: .75; font-style: italic; }
body.is-festivites .fest-statusbar__title { font-weight: 700; }
body.is-festivites .fest-statusbar__status {
    font-weight: 600;
    padding: .05rem .4rem;
    border-radius: 99px;
    background: color-mix(in srgb, var(--text) 12%, transparent);
    font-size: .76rem;
}
body.is-festivites .fest-statusbar__parts { opacity: .85; }
body.is-festivites .fest-statusbar__link {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    font-weight: 600;
}

body.is-festivites .fest-statusbar__close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    cursor: pointer;
    opacity: .7;
}
body.is-festivites .fest-statusbar__close:hover,
body.is-festivites .fest-statusbar__close:focus-visible {
    opacity: 1;
    background: color-mix(in srgb, var(--text) 12%, transparent);
}

@media (max-width: 768px) {
    body.is-festivites .fest-statusbar__inner { padding: .4rem .7rem; gap: .5rem; }
    body.is-festivites .fest-statusbar__badge span { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    /* Le texte ne bouge plus ; il reste consultable en faisant défiler la
       fenêtre horizontalement. */
    body.is-festivites .fest-statusbar.is-rolling .fest-statusbar__track { animation: none; }
    body.is-festivites .fest-statusbar__viewport { overflow-x: auto; }
}

/* Un avis de panne n'a rien à faire sur un menu, une affiche ou un rapport. */
@media print {
    body.is-festivites .fest-statusbar { display: none; }
}

/* Hiérarchie des options (`static/js/option_dependencies.js`) : un enfant se retire
   tant qu'aucun parent n'est choisi. ⚠️ `[hidden]` ne bat pas un `display: flex`
   d'auteur : la spécificité est relevée ici plutôt qu'un !important. */
.fest-option-groups [data-option-group][hidden],
.fest-option-groups .fest-option[hidden],
.fest-line-cfg__block [data-option-group][hidden],
.fest-line-cfg__block .fest-opt-row[hidden] { display: none; }
.fest-option-group[data-requires],
.fest-line-cfg__group[data-requires] { margin-left: 1rem; padding-left: .75rem; border-left: 2px solid var(--border, currentColor); }

/* Tri des groupes ATTACHÉS à un article (socle `_option_attach.html`) : la poignée
   est un badge libre posé en tête de la rangée, seul élément `draggable`. */
.fest-opt-attach-head > .drag-handle { cursor: grab; user-select: none; flex: 0 0 auto; }
.fest-opt-attach-head > .drag-handle:active { cursor: grabbing; }
.fest-opt-attach-hint { margin: 0 0 .25rem; font-size: .85rem; }
.fest-opt-attach-sortable [data-opt-sort-row].is-dragging { opacity: .5; }

/* Replier les options d'un groupe COCHÉ (socle `_option_attach.html`) : pour
   réordonner, il faut voir les en-têtes seuls. Le bouton n'apparaît que sur un
   groupe coché (non coché, ses options sont déjà repliées par la règle `:has`).
   ⚠️ La règle de repli doit battre celle qui déplie (`:has(...:checked)`) :
   d'où le même `:has` répété, plus `.is-collapsed`. */
.fest-opt-attach-collapse {
    margin-left: auto; /* en bout de rangée, à droite */
    display: none; align-items: center; gap: .3rem; flex: 0 0 auto;
    padding: .3rem .55rem; border: 1px solid var(--line, rgba(0, 0, 0, .12));
    border-radius: 999px; background: transparent; color: inherit;
    font: inherit; font-size: .8rem; cursor: pointer;
}
.fest-opt-attach-collapse:hover { background: var(--accent-soft, rgba(0, 0, 0, .05)); }
.fest-opt-attach-collapse .ph-caret-down { transition: transform .15s ease; }
.fest-opt-attach-row.is-collapsed .fest-opt-attach-collapse .ph-caret-down { transform: rotate(-90deg); }
.fest-opt-attach-collapse__count { color: var(--muted, #6b7280); white-space: nowrap; }
.fest-opt-attach-row:has(input[name^="group_"]:checked) .fest-opt-attach-collapse { display: inline-flex; }
.fest-opt-attach-row.is-collapsed:has(input[name^="group_"]:checked) .fest-opt-attach-opts { display: none; }
@supports not selector(:has(*)) {
    .fest-opt-attach-collapse { display: inline-flex; }
    .fest-opt-attach-row.is-collapsed .fest-opt-attach-opts { display: none; }
}
.fest-opt-attach-bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: .75rem; flex-wrap: wrap; margin: 0 0 .25rem;
}
.fest-opt-attach-bar .fest-opt-attach-title { margin: 0; }
.fest-opt-attach-collapse-all { margin-left: auto; }
@media (prefers-reduced-motion: reduce) {
    .fest-opt-attach-collapse .ph-caret-down { transition: none; }
}

/* Texte du bouton d'un type d'article : suggestions d'un clic sous le champ
   (templates/festivites_admin/_booking_label_field.html). Puces plus petites que
   les `.fest-chip` de la vitrine : elles accompagnent un champ, elles ne sont pas
   une navigation. */
.fest-booking-label__presets { display:flex; flex-wrap:wrap; gap:.35rem; align-items:center; }
.fest-booking-label__intro { font-size:.85em; margin-right:.15rem; }
.fest-booking-label__chip { padding:.2rem .65rem; font-size:.82rem; cursor:pointer; }

/* ── Modifier une réservation (festivites_admin/reservation_edit.html) ──
   Deux colonnes : les articles à gauche, l'aperçu et l'enregistrement à droite,
   collés pendant qu'on défile — c'est là qu'on revient après chaque retouche.
   « Retirer cet article » grise la ligne au lieu de la cacher : on doit voir ce
   qu'on s'apprête à retirer, et pouvoir se raviser. */
.fest-resa-edit { max-width: 1180px; }
.fest-resa-edit__layout {
    display: grid; grid-template-columns: minmax(0, 1fr) 320px;
    gap: 1.25rem; align-items: start;
}
@media (max-width: 900px) {
    .fest-resa-edit__layout { grid-template-columns: minmax(0, 1fr); }
}
.fest-resa-edit__section-title { font-size: 1.05rem; margin: 0 0 .75rem; }
.fest-resa-edit__line { margin-bottom: 1rem; transition: border-color .15s, box-shadow .15s; }
.fest-resa-edit__line--new { border-style: dashed; }
.fest-resa-edit__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: .75rem; flex-wrap: wrap; margin-bottom: .9rem;
}
.fest-resa-edit__name { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; min-width: 0; }
.fest-resa-edit__title { font-size: 1.15rem; line-height: 1.25; margin: 0; }
.fest-resa-edit__mode {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .15rem .55rem; border-radius: 999px; font-size: .78rem; font-weight: 600;
    background: color-mix(in srgb, var(--fest-pink) 12%, transparent);
    color: color-mix(in srgb, var(--fest-pink) 75%, var(--text));
}
.fest-resa-edit__mode--new {
    background: color-mix(in srgb, var(--fest-orange) 18%, transparent);
    color: color-mix(in srgb, var(--fest-orange) 55%, var(--text));
}
.fest-resa-edit__removed-flag {
    display: none; padding: .15rem .55rem; border-radius: 999px; font-size: .78rem; font-weight: 700;
    background: color-mix(in srgb, var(--fest-bad, #dc2626) 14%, transparent);
    color: var(--fest-bad, #dc2626);
}
.fest-resa-edit__remove { margin: 0; }
.fest-resa-edit__line:has([data-edit-remove]:checked) {
    border-color: color-mix(in srgb, var(--fest-bad, #dc2626) 45%, transparent);
}
.fest-resa-edit__line:has([data-edit-remove]:checked) .fest-resa-edit__removed-flag { display: inline-flex; }
.fest-resa-edit__line:has([data-edit-remove]:checked) .fest-resa-edit__title { text-decoration: line-through; opacity: .6; }
.fest-resa-edit__line:has([data-edit-remove]:checked) .fest-resa-edit__body {
    opacity: .4; pointer-events: none;
}
.fest-resa-edit__grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: .75rem 1rem;
}
.fest-resa-edit__grid .form-group { margin: 0; }
.fest-resa-edit__wide { grid-column: 1 / -1; }
.fest-resa-edit__qty .form-control { max-width: 120px; }
.fest-resa-edit__slots:not(:empty) {
    margin-top: .9rem; padding: .75rem .9rem; border-radius: 10px;
    background: color-mix(in srgb, var(--text) 4%, transparent);
}
.fest-resa-edit__slots .fest-line-cfg__creneaux { margin-top: .35rem; }
.fest-resa-edit__fields {
    margin-top: .9rem; border: 1px solid var(--line, rgba(0,0,0,.1)); border-radius: 10px;
}
.fest-resa-edit__fields > summary {
    display: flex; align-items: center; flex-wrap: wrap; gap: .45rem;
    padding: .6rem .85rem; cursor: pointer; font-weight: 600; list-style: none;
}
.fest-resa-edit__fields > summary::-webkit-details-marker { display: none; }
.fest-resa-edit__fields > summary::after {
    content: "▾"; margin-left: auto; order: 2; transition: transform .15s; opacity: .6;
}
.fest-resa-edit__fields[open] > summary::after { transform: rotate(180deg); }
/* La note passe sous le titre : le chevron reste sur la ligne qu'on clique. */
.fest-resa-edit__fields > summary small { order: 3; flex-basis: 100%; font-weight: 400; font-size: .8rem; }
.fest-resa-edit__fields[open] > .fest-resa-edit__fields-body { padding: 0 .85rem .85rem; }
/* Les groupes d'options sur deux colonnes quand la place le permet : la liste
   déroulait sur des milliers de pixels en une seule colonne. */
@media (min-width: 1100px) {
    .fest-resa-edit__fields-body .fest-line-cfg__block,
    .fest-resa-edit__fields-body > .fest-line-cfg:not(:empty) {
        display: block; column-count: 2; column-gap: .9rem;
    }
    .fest-resa-edit__fields-body .fest-line-cfg__block > *,
    .fest-resa-edit__fields-body .fest-line-cfg > * {
        break-inside: avoid; margin-bottom: .7rem;
    }
}
.fest-resa-edit__fields-body .fest-opt-row small { white-space: nowrap; }
/* De l'air entre les options : serrées à .2rem, une liste de 18 ballons se lit
   comme un bloc et le clic tombe sur la voisine. */
.fest-resa-edit__fields-body .fest-opt-row { padding: .45rem 0; }
.fest-resa-edit__fields-body .fest-opt-row + .fest-opt-row {
    border-top: 1px solid var(--line, rgba(0,0,0,.06));
}
.fest-resa-edit__fields-body .fest-line-cfg__group { padding: .65rem .9rem; }
.fest-resa-edit__add {
    display: flex; align-items: center; justify-content: center; gap: .45rem;
    width: 100%; padding: .85rem; margin: 0 0 1rem;
    border: 2px dashed color-mix(in srgb, var(--fest-pink) 45%, transparent);
    border-radius: 14px; background: transparent; color: inherit;
    font: inherit; font-weight: 600; cursor: pointer;
    transition: background .15s, border-color .15s;
}
.fest-resa-edit__add:hover, .fest-resa-edit__add:focus-visible {
    background: color-mix(in srgb, var(--fest-pink) 8%, transparent);
    border-color: var(--fest-pink);
}
.fest-resa-edit__aside { position: sticky; top: calc(var(--fest-header-h, 110px) + 1rem); }
@media (max-width: 900px) { .fest-resa-edit__aside { position: static; } }
.fest-resa-edit__summary { display: flex; flex-direction: column; gap: .75rem; }
.fest-resa-edit__summary .fest-resa-edit__section-title { margin: 0; }
.fest-resa-edit__facts { margin: 0; display: grid; gap: .4rem; }
.fest-resa-edit__facts > div {
    display: flex; justify-content: space-between; gap: .75rem;
    padding-bottom: .4rem; border-bottom: 1px solid var(--line, rgba(0,0,0,.08));
}
.fest-resa-edit__facts dt { color: var(--text-soft, inherit); font-size: .88rem; }
.fest-resa-edit__facts dd { margin: 0; text-align: right; }
.fest-resa-edit__hint { margin: 0; font-size: .85rem; }
.fest-resa-edit__preview-btn, .fest-resa-edit__save .button, .fest-resa-edit__cancel {
    width: 100%; justify-content: center;
}
.fest-resa-edit__preview:empty { display: none; }
.fest-resa-edit__save { display: flex; flex-direction: column; gap: .75rem; }
.fest-resa-edit__save[hidden] { display: none; }
.fest-resa-edit__save .ui-toggle { margin: 0; }
