/* Surprizio — design tokens
   Palette: cream canvas, deep ink, mint green, coral ribbon accent, amber twine
   Type: Nunito Sans (display + body/UI)
*/
:root{
  --canvas:#FDFCFC;
  --paper:#FFFFFF;
  --ink:#1E2B22;
  --ink-soft:#6B7568;
  --forest:#2F9E6E;
  --forest-dark:#237A56;
  --mint-soft:#ECF4EF;
  --berry:#EF5B50;
  --berry-dark:#D8483E;
  --berry-soft:#FDEAE7;
  --gold:#F2A93B;
  --gold-soft:#FDF1DC;
  /* 🔴 COMPAGNON SOMBRE DE `--gold`, ET IL MANQUAIT. `--gold` (#F2A93B) est
     volontairement CLAIR — il ne sert qu'à des usages non textuels (barre de
     disponibilité, contour de focus, accents) et tombe à ~2:1 sur blanc. Un
     lien doré avait donc besoin de sa propre teinte : 5,47:1 sur le fond de la
     carte, au-dessus du seuil AA du texte normal. ⚠️ Un premier essai à
     #A3660B donnait 4,45 — sous le seuil d'un cheveu, et sur un libellé de
     13,6px qui ne relève pas du « grand texte ». Ne pas l'éclaircir sans
     repasser par le calcul. */
  --gold-dark:#8F5A08;
  /* Fond des boutons d'action principale : le vert profond de la charte, qui
     était DÉJÀ la couleur de base de .btn (jamais rendue, toutes les instances
     portant une variante). Le vert dit « valider / go », convient à Réserver,
     Ajouter, Confirmer, et laisse --gold entièrement aux statuts et au décor.
     Contraste du texte blanc : 5,26:1, au-dessus du seuil AA — --forest seul
     n'étant qu'à 3,37:1, ne pas l'utiliser comme fond de bouton. */
  --primary:var(--forest-dark);
  --primary-dark:#1C6144;
  /* Vert du BANDEAU des modales en plein écran, et de lui seul.
     🔴 NI `--primary` NI `--primary-dark` : le premier est exactement la
     couleur du bouton d'action qui se trouve au bas du MÊME écran — bandeau et
     bouton se confondaient. Le second n'est qu'à 1,40:1 du premier, et c'est
     déjà la couleur de SURVOL de ce bouton : deux sens pour une teinte.
     ⚠️ 1,78:1 d'écart avec `--primary`, blanc à 9,35:1 dessus. */
  --forest-deep:#17503A;
  /* Couleur propre au badge « Organisateur » (tag-moderator). Il partageait
     --gold avec tag-prete, ce qui rendait les deux indiscernables sur une
     tuile « Prête » gérée par un organisateur — cas fréquent. */
  --organizer:#3F6C82;
  /* Compagnon clair de --organizer, sur le modèle de --mint-soft / --berry-soft
     / --gold-soft : fond des pastilles décoratives dont l'icône est --organizer.
     Même teinte (H 0.55), luminosité alignée sur --mint-soft (L 0.94). */
  --organizer-soft:#EBF1F4;
  /* Violet et son compagnon clair — la SEULE teinte nouvelle de la palette,
     ajoutée pour porter la rotation des pastilles de rubrique de quatre à six
     valeurs (voir .cat-tile--n). Les cinq autres existaient déjà.
     ⚠️ Construits sur le même modèle que --organizer / --organizer-soft :
     luminosité du fond alignée sur les autres `-soft`, et un compagnon foncé
     dont le contraste sur ce fond (4,97:1) reste dans la famille des paires
     existantes (3,68 à 4,59). Le seuil applicable est celui du NON-TEXTE
     (3:1) : ces pastilles portent une icône, pas une phrase. */
  --violet:#7A55A0;
  --violet-soft:#F1ECF7;
  /* Compagnon foncé — le TEXTE de `.btn-violet` au repos, sur --violet-soft :
     6,61:1. ⚠️ Il a d'abord servi au SURVOL d'un bouton en aplat ; le bouton
     est passé à la forme pâle (voir `.btn-violet`), le token est resté et a
     changé de rôle. Ne pas le relire comme une couleur de fond. */
  --violet-dark:#654488;
  /* Fond des bandeaux d'information. ⚠️ Token DÉDIÉ et non `--gold-soft`, qui
     sert à neuf autres endroits — pastilles d'icône, tuiles de rubrique,
     étapes de l'accueil, dégradé du hero. Le repeindre pour éclaircir un
     bandeau aurait touché tout ça, loin de l'écran concerné.
     ⚠️ Plus pâle que --gold-soft : le texte y gagne (6,11 → 6,36:1), et
     l'identité du bandeau ne vient PAS de son fond mais de sa BORDURE — il
     n'est qu'à 1,09:1 de la page, et se lit parfaitement. */
  --notice-soft:#FEF6E8;
  /* Fonds du bloc d'information d'une tuile de liste, UN PAR STATUT.
     ⚠️ Dérivés à faible saturation des couleurs déjà en place — aucune teinte
     nouvelle : --gold pour « prête », --organizer pour « brouillon », un gris
     neutre pour « archivée ». Ils reprennent donc les mêmes repères que les
     bordures de section et les badges, ce qui fait qu'on lit le statut d'un
     coup d'œil sans avoir à croiser deux codes visuels différents.
     ⚠️ Volontairement PLUS PÂLES que les -soft ci-dessus : ceux-là servent de
     fond à des pastilles d'icône de 40px, celles-ci à un bloc de texte de la
     taille d'une carte. La même intensité y deviendrait envahissante. */
  --bg-prete:#FBF3E2;
  --bg-brouillon:#EBF2F5;
  --bg-archivee:#F1F0ED;
  --beige: #F8F6F3;
  /* Variante plus soutenue de --beige, pour le survol des surfaces déjà
     beiges (boutons secondaires) : --canvas y serait plus CLAIR que le fond
     au repos, et le survol paraîtrait inversé. */
  --beige-dark: #EFECE6;
  --line:#EFE1D2;
  --danger:#D8483E;
  --radius:18px;
  --radius-lg:28px;
  --shadow: 0 10px 28px rgba(30,43,34,0.07), 0 2px 6px rgba(30,43,34,0.05);
  --shadow-sm: 0 2px 8px rgba(30,43,34,0.06);
}

*{box-sizing:border-box;}
/* 🔴 `[hidden]` DOIT ÊTRE REPOSÉ ICI, ET AVEC `!important`.
   Le `display:none` de l'attribut `hidden` vient de la feuille par DÉFAUT du
   navigateur : n'importe quelle règle auteur posant un `display` sur le même
   élément l'emporte, sans `!important` de son côté et sans le moindre
   avertissement. L'élément reste alors visible alors que le JS le croit
   masqué — panne parfaitement silencieuse, et le script n'a aucun moyen de
   s'en apercevoir.
   Le défaut s'est produit DEUX FOIS : sur le bouton « Partager » de la
   bannière (les deux états s'affichaient ensemble, « Copié ! » dès le
   chargement) et sur la modale d'idée cadeau, qui montrait un `<img src="">`
   pour une idée sans image. Deux corrections ponctuelles existaient déjà
   ailleurs pour la même raison (`.cover-field [hidden]`,
   `.modal-overlay[hidden]`) — le motif est donc systémique, pas accidentel.
   ⚠️ Tout composant qui bascule un `hidden` en JS dépend de cette ligne. Ne
   pas la retirer en la prenant pour une redondance avec le navigateur. */
[hidden]{display:none !important;}
html,body{margin:0;padding:0;}
body{
  background:var(--canvas);
  color:var(--ink);
  font-family:'Nunito Sans', system-ui, sans-serif;
  font-size:16px;
  line-height:1.5;
  min-height:100vh;
  display:flex;
  flex-direction:column;
}
h1,h2,h3,h4{
  font-family:'Nunito Sans', system-ui, sans-serif;
  font-weight:800;
  color:var(--ink);
  line-height:1.15;
  margin:0 0 .4em;
  letter-spacing:-.01em;
}
/* 🔴 LES 2,2rem ÉTAIENT SERVIS TELS QUELS SUR UN TÉLÉPHONE, exactement le
   défaut déjà corrigé sur le titre de l'accueil. Sur les ~312px utiles d'un
   écran de 360, 35px donnent une quinzaine de caractères par ligne : « Secret
   Santa en ligne, gratuit et vraiment secret » y occupait trois lignes, et le
   contenu partait sous la ligne de flottaison avant d'avoir commencé.

   ⚠️ Touche les titres qui n'ont PAS de règle propre — catalogue, guides, FAQ,
   Secret Santa, tableau de bord. `.hero h1`, `.list-hero h1`, `.panel-head h1`
   et `.modal-body h1` gardent les leurs, déjà dimensionnées pour leur contexte.

   ⚠️ `clamp()` PLUTÔT QU'UNE MEDIA QUERY, et pour la raison documentée sur
   `.hero h1` : la règle reste DANS le sélecteur de base. Une surcharge en
   `@media` aurait dû être placée après cette ligne pour l'emporter — les media
   queries n'ajoutent aucune spécificité — et c'est le piège déjà rencontré sur
   `.list-hero`.

   ⚠️ 1,5rem au plancher : à 24px on tient ~25 caractères par ligne sur 312px,
   soit deux lignes pour un titre long au lieu de trois. Le maximum ne bouge
   pas — la borne haute est atteinte vers 830px de large. */
h1{font-size:clamp(1.5rem, 0.95rem + 2.4vw, 2.2rem);}
h2{font-size:1.5rem;}
h3{font-size:1.15rem;}
p{margin:0 0 1em;}
a{color:var(--berry);text-decoration:none;}
a:hover{text-decoration:none;}
.wrap{max-width:1120px;margin:0 auto;padding:0 24px;}

/* ---- Icônes SVG inline (voir templates/_icons.html.twig) ---- */
.icon{display:inline-block;vertical-align:-0.15em;flex-shrink:0;}
.text-forest{color:var(--forest);}
.text-berry{color:var(--berry);}
.text-danger{color:var(--danger);}
.text-mint-soft{color:var(--mint-soft);}

/* ---- Import de cadeaux (liste à cocher) ---- */
.import-gift-list{display:flex;flex-direction:column;gap:8px;margin:14px 0 20px;}
.import-gift-row{display:flex;align-items:flex-start;gap:12px;padding:12px 14px;border:1px solid var(--line);border-radius:12px;cursor:pointer;transition:border-color .15s,background .15s;}
.import-gift-row:hover{border-color:var(--forest);background:var(--mint-soft);}
.import-gift-row input[type="checkbox"]{margin-top:3px;flex-shrink:0;width:auto;}
.import-gift-info{display:flex;flex-direction:column;gap:2px;}

/* ---- Topbar ---- */
.topbar{background:var(--paper);border-bottom:1px solid var(--line);}
.topbar-inner{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;}
.brand{color:var(--ink);font-family:'Nunito Sans',sans-serif;font-size:1.3rem;font-weight:800;display:flex;align-items:center;gap:10px;}
.brand:hover{text-decoration:none;opacity:.9;}
.brand-badge{
  display:inline-flex;align-items:center;justify-content:center;
  width:52px;height:52px;border-radius:14px;
  flex-shrink:0;
}
.brand-mark{width:40px;height:40px;object-fit:contain;display:block;}
.nav{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
/* Groupe « Mes listes / Profil ». `display:contents` : le conteneur n'a
   aucune existence visuelle, ses liens restent des enfants directs de .nav.
   Il n'existe que pour pouvoir les faire basculer ENSEMBLE sur une seconde
   ligne en mobile (voir le @media 760px), sans les sortir du <nav>. */
.nav-links{display:contents;}
.nav a{color:var(--ink-soft);font-weight:600;font-size:.95rem;padding:8px 6px;}
.nav a:hover{color:var(--ink);text-decoration:none;}
/* Bouton secondaire de la nav. Mêmes surfaces que .btn-outline : sans fond
   déclaré il laissait apparaître le blanc de la topbar et détonnait avec les
   boutons secondaires du contenu. Classe distincte de .btn car les métriques
   diffèrent en contexte de nav. */
.nav-login{background:var(--beige);border:1px solid var(--line);border-radius:4px;padding:8px 18px !important;color:var(--ink) !important;}
.nav-login:hover{background:var(--beige-dark);border-color:var(--ink-soft);}
/* Point « quelque chose vous attend dans le profil » — aujourd'hui l'adresse
   e-mail non confirmée. Il REMPLACE un bandeau pleine largeur qui coûtait
   ~270px de hauteur en mobile sur « Mes listes » (voir base.html.twig). */
/* Le lien du profil est devenu l'AVATAR du compte. Pas pour gagner de la
   largeur — le gain est marginal — mais parce que c'est la seule entrée dont
   le libellé n'apprend rien, qu'elle ouvre déjà une modale, et qu'elle portait
   déjà la pastille. Son nom accessible passe par un `.sr-only`. */
.nav-profile{display:inline-flex;align-items:center;padding:4px 6px;position:relative;}
.nav-avatar{--avatar-size:32px;}
.nav-dot{
  /* Coin haut droit du disque, hors du flux : en ligne, même de 8px, il
     coûterait de la largeur sur une rangée déjà comptée au pixel.
     ⚠️ L'ANNEAU N'EST PAS DÉCORATIF. Le point reposait sur le blanc de la
     topbar, d'où son contraste de 3,34:1 — au-dessus du seuil NON-TEXTE (3:1),
     celui qui s'applique, un point n'étant pas une phrase. Posé sur une photo
     de profil quelconque, ce calcul ne vaudrait plus rien : l'anneau lui rend
     le fond pour lequel il a été choisi.
     ⚠️ `--berry` et non `--danger` : ce n'est pas une erreur, c'est un rappel.
     Ne pas l'éclaircir sans repasser par le calcul.
     ⚠️ La couleur ne porte JAMAIS l'information seule : le lien rend en plus
     un `.sr-only` qui dit ce qui attend. */
  position:absolute;top:2px;right:3px;
  width:8px;height:8px;border-radius:50%;
  background:var(--berry);box-shadow:0 0 0 2px var(--paper);
}
/* Le même point, répété au coin du bandeau « Confirmez votre adresse » du
   profil : c'est lui que l'avatar signalait. À cheval sur le bord, comme une
   pastille de notification. ⚠️ `.nav-dot.profile-verify-dot` (0,2,0) l'emporte
   sur la position de `.nav-dot` par la spécificité. L'anneau blanc reste juste :
   le bandeau est posé sur le blanc de la carte du profil. */
.profile-verify-notice{position:relative;}
.nav-dot.profile-verify-dot{top:-4px;right:-4px;width:10px;height:10px;}
/* ⚠️ `.nav-cta` (« Créer un compte » de l'en-tête) a été SUPPRIMÉE avec son
   bouton — voir le commentaire dans base.html.twig. Ne pas la recréer pour
   remettre un second appel à l'action dans le header : c'est exactement le
   doublon qu'on a retiré. Une action principale de nav qui se justifierait
   un jour reprendrait les couleurs de .btn-primary, avec ses propres
   métriques et un `!important` sur la couleur pour l'emporter sur `.nav a`,
   plus spécifique. */
/* ================================================================
   Menus déroulants de l'en-tête — UN SEUL composant, deux porteurs
   ================================================================
   « Découvrir » et le sélecteur de langue. Deux mécanismes dans un même
   en-tête auraient fini par diverger, et l'ancien sélecteur reposait sur un
   `hidden` basculé en JS : SANS JAVASCRIPT IL NE S'OUVRAIT PAS, donc changer
   de langue était impossible — panne muette, la pastille est là et ne fait
   rien.

   ⚠️ `<details>`/`<summary>` natif, comme l'accordéon de la FAQ et les listes
   archivées du tableau de bord. Le script du header n'AJOUTE que la fermeture
   au clic extérieur et à Échap. */
.nav-menu{position:relative;}
/* ⚠️ LES DEUX RÈGLES SONT NÉCESSAIRES pour retirer le marqueur natif : la
   première couvre les navigateurs conformes, la seconde WebKit, qui l'expose
   par un pseudo-élément propriétaire. Même paire que `.faq-item summary`. */
.nav-menu-toggle{
  display:inline-flex;align-items:center;gap:4px;cursor:pointer;list-style:none;
  color:var(--ink-soft);font-weight:600;font-size:.95rem;padding:8px 6px;
}
.nav-menu-toggle::-webkit-details-marker{display:none;}
.nav-menu-toggle:hover{color:var(--ink);}
.nav-menu-toggle svg{width:12px;height:12px;transition:transform .15s ease;}
/* ⚠️ `[open]` et non plus `[aria-expanded="true"]` : l'état est porté par
   l'élément lui-même, plus par un attribut qu'un script doit tenir à jour. */
.nav-menu[open] > .nav-menu-toggle svg{transform:rotate(180deg);}
/* Variante « pastille » du sélecteur de langue : un code de deux lettres n'est
   pas un libellé de navigation, il a besoin d'un contour pour se lire comme un
   contrôle. */
.nav-menu-toggle--pill{
  font-size:.78rem;font-weight:700;letter-spacing:.02em;
  border:1px solid var(--line);border-radius:999px;padding:4px 10px;
}
.nav-menu-toggle--pill:hover{background:var(--beige);}
/* ⚠️ Ancré à GAUCHE par défaut : « Découvrir » vit au milieu de la rangée, et
   un panneau plus large que son déclencheur doit s'étendre vers l'intérieur de
   l'écran. Même choix que `.cover-menu`. */
.nav-menu-panel{
  position:absolute;z-index:30;top:calc(100% + 6px);left:0;min-width:150px;
  background:var(--paper);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--shadow);padding:6px;
}
/* ⚠️ …SAUF pour le dernier de la rangée. Le sélecteur de langue vit tout à
   droite de la nav : ancré à gauche, son panneau déborderait hors de l'écran
   sur les petites largeurs. Une règle `right:auto;left:0` a existé deux fois
   dans le @media mobile, chaque fois rendue nécessaire par un
   `flex-direction:column` qui collait la pastille au bord GAUCHE — les deux
   headers gardant maintenant leur pastille à DROITE, c'est bien cet ancrage-ci
   qui vaut partout. Ne pas réintroduire la surcharge : elle recréerait le
   défaut en miroir. */
.nav-menu--end{margin-left:4px;}
.nav-menu--end .nav-menu-panel{left:auto;right:0;}
/* 🔴 `.nav` EN TÊTE DU SÉLECTEUR, ET C'EST INDISPENSABLE. Ces options sont des
   `<a>` situés dans la nav : `.nav a` (une classe + un type) l'emporte sur
   `.nav-menu-option` seule (une classe), et leur imposait sa couleur grise et
   son rembourrage de lien de barre. Le défaut existait déjà du temps de
   `.lang-option` et ne se voyait pas — un menu de deux langues reste lisible
   de travers. On gagne par la SPÉCIFICITÉ, jamais par la position : déplacer
   la règle plus bas marcherait aussi, et se casserait au premier
   réarrangement du fichier. Même piège que les surcharges de `.list-hero`. */
.nav .nav-menu-option{
  display:block;width:100%;text-align:left;padding:8px 10px;border-radius:8px;
  font-size:.9rem;color:var(--ink);background:none;border:none;font:inherit;
}
.nav .nav-menu-option:hover{background:var(--canvas);text-decoration:none;}
.nav-menu-option-current{font-weight:700;background:var(--canvas);}

.main{flex:1;padding:20px;width:100%;}
.footer{color:var(--ink-soft);font-size:.85rem;padding:0;border-top:1px solid var(--line);background:var(--paper);margin-top:40px;}
.footer-top{display:flex;flex-wrap:wrap;justify-content:space-between;gap:32px;padding-top:44px;padding-bottom:44px;}
.footer-col{flex:1 1 200px;}
.footer-brand{display:flex;align-items:center;gap:10px;font-family:'Nunito Sans';font-weight:800;font-size:1.15rem;color:var(--ink);margin-bottom:10px;}
.footer-tagline{max-width:280px;}
.footer-col h4{font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--ink);margin-bottom:14px;}
.footer-col a{display:block;color:var(--ink-soft);font-size:.92rem;margin-bottom:10px;}
.footer-col a:hover{color:var(--berry);text-decoration:none;}
.footer-bottom{border-top:1px solid var(--line);padding:18px 0;text-align:center;}

/* ---- Flash ---- */
.flash{padding:15px;border-radius:var(--radius);margin:20px 0 20px 0;font-weight:500;}
/* ⚠️ Message de SESSION (hors de <main>) : pas de marge basse. Le padding
   haut de `.main` fait déjà l'écart avec la suite ; les deux additionnés
   laissaient 40px, et 60px au-dessus d'un bandeau qui avait lui-même sa marge
   haute. Aucun saut à la disparition : le message part avec toute sa hauteur. */
#flashes .flash{margin-bottom:0;}
/* Bandeau d'état de l'écran de gestion d'un tirage, collé sous la bannière
   comme la `.notice-stack` d'une liste. (0,2,0) l'emporte sur `.flash`. */
.flash.santa-status{margin-top:0;}
.flash-success{background:var(--mint-soft);color:var(--forest-dark);border:1px solid #C6E9D5;}
.flash-error{background:var(--berry-soft);color:var(--danger);border:1px solid #F4C7C1;}
.flash-info{background:var(--notice-soft);color:#7A5313;border:1px solid #F3DCA8;}
/* 🔴 BANDEAUX PERMANENTS : PASTILLE À GAUCHE, TEXTE À DROITE.

   ⚠️ `main .flash` et non `.flash` : c'est la frontière STRUCTURELLE du projet
   — les messages de SESSION sont rendus hors de `<main>`, les bandeaux
   permanents dedans (voir base.html.twig, et FlashDismissTest qui tient déjà
   cet invariant). Une pastille de 44px sur « Liste mise à jour », message
   court doté d'un bouton de fermeture, serait disproportionnée.

   🔴 ET SURTOUT PAS `display:flex` SUR LE BANDEAU — c'est le piège de ce
   chantier, et il a été commis. En flex, CHAQUE élément enfant devient un item
   à part : un bandeau dont le texte contient un `<strong>` ou un `<a>` ne se
   range pas en deux colonnes mais en TROIS OU QUATRE, chaque fragment de phrase
   devenant sa propre colonne étroite. Seules les suites de TEXTE NU sont
   regroupées en items anonymes — un élément ne l'est jamais.

   ⚠️ Le symptôme trompe : sur un bandeau au texte sans balise, la mise en page
   paraît juste. Elle se disloque uniquement là où le texte porte du gras ou un
   lien, c'est-à-dire sur sept bandeaux du projet.

   ⚠️ D'où une ICÔNE HORS DU FLUX et un simple retrait à gauche : le bandeau
   redevient un bloc ordinaire, son texte une phrase continue, et le gras y
   reprend son rôle d'inline — ou de titre, quand on le passe en `block` (voir
   la règle suivante).

   ⚠️ `:has()` n'est pas un raffinement, c'est le GARDE-FOU : huit bandeaux du
   projet n'ont pas d'icône, et un retrait de 74px y creuserait une gouttière
   vide. Il porte AUSSI le positionnement absolu, si bien qu'un navigateur qui
   ignorerait `:has()` laisse tomber les deux règles ensemble et retombe sur
   l'icône au fil du texte — dégradé, jamais superposé.

   ⚠️ LA PASTILLE EST PLUS FONCÉE QUE LE BANDEAU, et teintée de SA PROPRE
   ENCRE : `currentColor` à 12%. Une seule déclaration sert les trois variantes
   — ambre sur `.flash-info`, vert sur `.flash-success`, rose sur
   `.flash-error` — là où un token par variante aurait demandé trois règles à
   garder d'accord. ⚠️ Elle a d'abord été un blanc translucide, donc plus CLAIRE
   que le bandeau ; c'est l'inverse qui se lit bien.

   ⚠️ 12% ET PAS PLUS : le seuil applicable à l'icône est celui du NON-TEXTE
   (3:1), et c'est la variante ERREUR qui borne — 3,19:1 à 12%, 3,09:1 dès 14%.
   Les deux autres sont larges (5,34 et 4,02). Ne pas monter sans repasser par
   le calcul sur les trois.

   ⚠️ L'écart pastille/bandeau n'est que de 1,19:1, et c'est SANS IMPORTANCE :
   comme pour le bandeau lui-même (1,09:1 avec la page), c'est la FORME — ici un
   disque — qui la détache, pas la luminosité. Voir `.btn-violet` pour l'erreur
   de méthode que ce raisonnement corrige.

   ⚠️ Le repli `rgba()` vient EN PREMIER, et l'ordre est la seule chose qui le
   fait fonctionner : un navigateur sans `color-mix()` ignore la seconde
   déclaration et garde la première. Les inverser ferait perdre la teinte
   partout, en silence. */
main .flash{padding:16px;}
main .flash:has(> svg.icon){position:relative;padding-left:74px;}
main .flash:has(> svg.icon) > svg.icon{
  position:absolute;left:16px;top:16px;
  width:44px;height:44px;padding:11px;border-radius:50%;
  background:rgba(0,0,0,.06);
  background:color-mix(in srgb, currentColor 12%, transparent);
}
/* ── PILE DE BANDEAUX (page d'une liste) ─────────────────────────────────────
   Plusieurs bandeaux d'information réunis dans UN SEUL cadre, séparés d'un
   filet — au lieu de cartes empilées avec chacune sa bordure et sa gouttière.
   Une cinquantaine de pixels gagnés, et ça se lit comme un seul bloc.

   🔴 CHAQUE BANDEAU GARDE SON FOND ET SA BORDURE, et la pile n'en porte AUCUN.
   C'est ce qui la rend sûre : elle ne présume pas de la variante qu'elle
   contient. Poser l'ambre de `.flash-info` sur le conteneur aurait marché
   aujourd'hui — les dix bandeaux de cette page sont des `info` — et se serait
   cassé au premier `.flash-error` qu'on y glisserait.

   ⚠️ Le filet est la bordure HAUTE du bandeau suivant, la basse du précédent
   étant retirée : deux bordures adjacentes donneraient un trait de 2px. */
.notice-stack{margin:0 0 20px;}
.notice-stack > .flash{margin:0;border-radius:0;}
.notice-stack > .flash:not(:last-child){border-bottom:0;}
.notice-stack > .flash:first-child{border-radius:var(--radius) var(--radius) 0 0;}
.notice-stack > .flash:last-child{border-radius:0 0 var(--radius) var(--radius);}
/* ⚠️ APRÈS les deux précédentes, et c'est l'ordre qui tranche : un enfant
   unique est à la fois premier et dernier, donc à spécificité égale c'est la
   dernière règle écrite qui l'emporte. Sans elle, un bandeau seul n'aurait que
   la moitié de ses coins arrondis. */
.notice-stack > .flash:only-child{border-radius:var(--radius);}

/* 🔴 LE TITRE D'UN BANDEAU EST MARQUÉ, PAS DEVINÉ.

   ⚠️ UNE PREMIÈRE VERSION LE DÉDUISAIT DE SA POSITION — `svg.icon + strong` —
   et c'était FAUX. Le combinateur `+` désigne le prochain ÉLÉMENT frère : il
   ignore complètement les nœuds de TEXTE. Un gras placé au milieu d'une phrase
   reste donc le premier élément après l'icône, et se voyait traité comme un
   titre. Le bandeau du participant se brisait ainsi en trois morceaux, le point
   final atterrissant seul sur sa ligne. Défaut signalé à l'usage.

   🔴 CSS NE PEUT PAS VOIR OÙ COMMENCE LE TEXTE. La distinction « titre
   d'ouverture » / « emphase au fil de la phrase » est ÉDITORIALE, pas
   structurelle : elle doit être écrite, pas inférée.

   ⚠️ D'où `class="notice-title"` dans la valeur de traduction elle-même. Le
   coût — six clés × trois langues — est réel, et c'est ce qui avait fait
   préférer la position ; mais la position ne marchait pas. La parité entre
   langues est tenue par `FlashDismissTest`, ce qui ferme le risque d'oubli.

   ⚠️ `moderator_notice_visible_owner_confidential` n'a délibérément PAS la
   classe : son gras ouvre bien la chaîne, mais ce n'est pas un titre — c'est la
   première moitié d'une phrase coupée par un tiret cadratin. Détaché, il
   laissait la seconde ligne commencer par « — mais pas le propriétaire ». */
main .flash strong.notice-title{display:block;}

/* Le bandeau du brouillon porte en plus son bouton, SOUS le texte — d'où un
   conteneur intermédiaire, seul cas où le markup diffère des vingt-neuf autres.
   ⚠️ Il ne sert plus à isoler un item flex (il n'y en a plus) mais à garder le
   bouton solidaire du texte si le bandeau gagne un jour un second bloc. */
/* Un bandeau permanent qui porte un BOUTON sous son texte. Deux occurrences :
   le brouillon (« Passer en Prête ») et l'invitation du participant
   (« Ajouter à mes listes »).

   🔴 LE TEXTE EST ENVELOPPÉ, L'ICÔNE NE L'EST JAMAIS. `main .flash:has(> svg.icon)`
   exige que l'icône reste un enfant DIRECT du bandeau : la glisser dans
   `.notice-body` la sortirait de la règle et elle redeviendrait un petit trait
   au fil du texte — sans erreur, sans trace, juste un bandeau qui ne ressemble
   plus aux autres. */
.notice-action{margin:14px 0 0;}
/* ---- Messages de SESSION uniquement ----
   ⚠️ `.flash` sert aussi à des bandeaux permanents (avertissement de
   confidentialité, encart de lecture seule de la discussion, états vides) : une
   vingtaine dans le projet. Ce modificateur n'est posé que sur les messages
   rendus par `app.flashes()` — voir base.html.twig. Ne pas le généraliser à
   `.flash`, ce serait donner un bouton de fermeture à des explications qui
   doivent rester à l'écran. */
.flash-dismissible{position:relative;padding-right:48px;}
.flash-close{
  position:absolute;top:10px;right:10px;
  display:flex;align-items:center;justify-content:center;
  width:26px;height:26px;border-radius:999px;border:none;
  background:transparent;color:inherit;opacity:.55;cursor:pointer;
  transition:opacity .15s ease,background .15s ease;
}
.flash-close:hover,.flash-close:focus-visible{opacity:1;background:rgba(30,43,34,.08);}
.flash-close svg{width:16px;height:16px;}
/* La disparition elle-même. `max-height` et `margin` sont animés en plus de
   l'opacité : sans eux, le message s'efface mais laisse son espace, et la page
   garde un trou de la taille du bandeau jusqu'à son retrait du DOM. */
.flash--leaving{
  opacity:0;transform:translateY(-4px);
  max-height:0;margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0;
  overflow:hidden;
  transition:opacity .3s ease,transform .3s ease,max-height .3s ease,margin .3s ease,padding .3s ease;
}
/* ⚠️ Un contenu qui s'efface tout seul est exactement ce que ce réglage
   système vise. On le retire d'un coup plutôt que de l'animer — le message
   disparaît toujours, sans mouvement. */
@media (prefers-reduced-motion: reduce){
  .flash--leaving{transition:none;}
}

/* ---- Cards / layout blocks ---- */
.card{
  background:var(--paper);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:28px;
  border:1px solid var(--line);
}
.card + .card{margin-top:20px;}
.narrow{max-width:580px;margin:0 auto;}
/* Pages légales : plus larges que `.narrow`, volontairement.

   ⚠️ `.narrow` (580px) est calibrée pour des FORMULAIRES — connexion,
   inscription, création de liste — où des champs de 850px seraient désagréables
   à remplir. Ne pas l'élargir globalement pour régler ce cas : les 24 autres
   écrans qui la portent y perdraient.

   ⚠️ 720px et non davantage : ces pages sont des paragraphes denses qu'on lit
   en continu, et cette largeur donne ~83 caractères par ligne. La fourchette de
   confort typographique va de 45 à 75 ; au-delà de ~90, l'œil perd la ligne en
   revenant à la suivante. 900px en donnerait ~105.

   ⚠️ Aucune largeur en dessous de 720px : `max-width` ne contraint rien tant
   que la fenêtre est plus étroite, la carte suit alors `.wrap` et ses marges. */
.legal-page{max-width:720px;}

.page-head{display:flex;justify-content:space-between;align-items:flex-end;flex-wrap:wrap;gap:16px;margin-bottom:16px;}
.page-head p{color:var(--ink-soft);margin:4px 0 0;}
/* Colonne de gauche de l'en-tête (titre, description, méta, badges). `flex:1`
   pour qu'elle occupe la place laissée par les actions ; `min-width:0` pour
   qu'un mot très long de la description puisse se couper au lieu de pousser
   les boutons hors de l'écran. */
.page-head-main{flex:1 1 auto;min-width:0;}
/* En-tête de la section « Cadeaux » : même patron que celui d'une page, à deux
   ajustements près.
   ⚠️ `align-items:center` et non `flex-end` : ici la colonne de gauche n'est
   qu'un titre, sans description ni métadonnées dessous. Aligner sur la ligne de
   base laisserait les boutons flotter au-dessus de lui.
   ⚠️ `margin-top:4px` reprend celui que portait le titre seul, pour que la
   section ne remonte pas contre le bandeau qui la précède. */
.gifts-head{align-items:center;margin-top:4px;margin-bottom:20px;}
.gifts-head h2{margin:0;}

/* Description d'une liste, sous la bannière et avant les métadonnées :
   volontairement PAS un .card (28px de padding et l'ombre d'un panneau
   interactif, disproportionnés pour un paragraphe). Bloc léger, juste assez
   délimité pour se détacher du fond. Marge basse resserrée : la ligne de
   métadonnées la suit immédiatement.
   ⚠️ Doit rester un enfant direct de .list-main, pas de .page-head : ce dernier
   est un conteneur flex partagé avec les boutons d'action, la carte y perdait
   leur largeur et se retrouvait plus étroite que tous les blocs voisins.
   Pas de white-space:pre-line : les sauts de ligne sont déjà convertis en <br>
   par le filtre |nl2br côté template, les cumuler les doublerait. */
.list-description{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:16px 18px;margin-bottom:12px;
  color:var(--ink-soft);
}

/* ---- Buttons ---- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:'Nunito Sans',sans-serif;font-weight:700;font-size:.95rem;
  padding:12px 22px;border-radius:4px;border:1px solid transparent;
  cursor:pointer;background:var(--primary);color:#fff;transition:.15s ease;
}
.btn:hover{background:var(--primary-dark);text-decoration:none;}
/* Action PRINCIPALE. Vert et non rouge : --berry était trop proche de
   --danger pour que l'œil distingue « action à encourager » de « action
   destructive ». Reprend les mêmes couleurs que la base .btn ci-dessus —
   la classe reste explicite dans les templates pour marquer l'intention. */
.btn-primary{background:var(--primary);}
.btn-primary:hover{background:var(--primary-dark);}
/* Conservée : --berry reste la couleur des liens, de tag-participant et des
   accents décoratifs ; son usage « bouton d'action principale » a seul migré
   vers .btn-primary. */
.btn-berry{background:var(--berry);}
.btn-berry:hover{background:var(--berry-dark);}
/* 🔴 CHANGEMENT D'ÉTAT D'UNE LISTE — et c'est son INTENTION, pas seulement sa
   couleur. Aujourd'hui un seul porteur : « Passer en Prête », dans le bandeau
   du brouillon.

   ⚠️ POURQUOI PAS `.btn-primary`. Le vert de la charte est réservé à l'action
   principale du parcours ordinaire — ajouter, réserver, valider. Ouvrir une
   liste aux réservations n'en fait pas partie : c'est un changement d'ÉTAT,
   irréversible dans ses effets (l'activité qui suit ferme le droit de
   supprimer un cadeau). Une teinte à part le dit sans un mot.

   🔴 PÂLE AU REPOS, APLAT AU SURVOL — même patron que `.btn-danger`, et cette
   forme est le résultat d'une DÉCISION RENVERSÉE.

   ⚠️ L'aplat violet avait été retenu contre la variante pâle, au motif que
   celle-ci ne se sépare du bandeau qui l'entoure que par 1,09:1. L'argument
   était FAUX, et il faut savoir pourquoi pour ne pas le refaire : le contraste
   WCAG ne mesure que la LUMINOSITÉ, et il ne dit rien de la séparation entre
   deux SURFACES. Le bandeau lui-même est à 1,09:1 de la page et se lit
   parfaitement — parce qu'il est reconnu à sa bordure et à sa forme, pas à
   l'écart de clarté avec son fond. Le bouton pâle porte une bordure, un
   arrondi et un padding : il se lit comme un bouton pour les mêmes raisons.

   ⚠️ CE QUE LE SEUIL COUVRE VRAIMENT ici, et qui reste vérifié : le TEXTE sur
   son fond. 6,61:1 au repos (--violet-dark sur --violet-soft), 5,78:1 au survol
   (blanc sur --violet) — au-dessus du seuil AA dans les deux états.

   ⚠️ Réutilise `--violet` et `--violet-soft`, posés pour les pastilles de
   rubrique. Aucune teinte nouvelle hormis le compagnon foncé. */
.btn-violet{background:var(--violet-soft);color:var(--violet-dark);}
.btn-violet:hover{background:var(--violet);color:#fff;}

.btn-outline{background:var(--beige);border-color:var(--line);color:var(--ink);}
.btn-outline:hover{background:var(--beige-dark);color:var(--ink);border-color:var(--ink-soft);}
/* Conservée bien qu'aucun template ne la référence plus : tous les boutons
   « Annuler »/« Retour » sont passés en .btn-outline, qui les fait lire comme
   le second choix offert plutôt que comme du texte. Même statut que
   .btn-berry — gardée pour un besoin de bouton vraiment discret. */
.btn-ghost{background:transparent;color:var(--ink-soft);border-color:transparent;}
.btn-ghost:hover{background:var(--beige);color:var(--ink);}
/* Action destructive, style DISCRET par défaut (contour) : ces boutons
   cohabitent avec des actions courantes, un aplat rouge y attire l'œil autant
   qu'une action à encourager. Le rouge plein n'apparaît qu'au survol, quand
   l'intention est déjà là. */
.btn-danger{background:transparent;border-color:var(--danger);color:var(--danger);}
.btn-danger:hover{background:var(--danger);border-color:var(--danger);color:#fff;}
/* 🔴 VERT EN CONTOUR — pour une action qu'on PROPOSE sans la réclamer.
   Aujourd'hui un seul porteur : « Installer l'extension », dans les quatre
   surfaces de promotion.

   ⚠️ POURQUOI PAS `.btn-primary`. Il le portait, et se retrouvait donc du même
   vert plein que « Nouvelle liste » ou « Ajouter un cadeau » — c'est-à-dire
   aussi appuyé que l'action que la personne est venue faire. Installer une
   extension est une proposition ; elle ne doit pas peser autant que le geste
   qu'on interrompt.

   ⚠️ POURQUOI PAS `.btn-outline` NON PLUS : celui-ci est beige et gris, réservé
   aux « Annuler » et « Retour ». Ce bouton n'est pas un second choix qu'on
   écarte, c'est une invitation qu'on tempère — d'où la couleur de la charte,
   mais en contour.

   ⚠️ Même patron que `.btn-danger` : discret au repos, plein au survol. Texte
   `--primary` sur blanc à 5,26:1, blanc sur `--primary` au survol à 5,26:1 ; la
   bordure ressort à 4,70:1 du fond menté qui l'entoure, bien au-delà du seuil
   non-texte (3:1). */
.btn-primary-outline{background:var(--paper);border-color:var(--primary);color:var(--primary);}
.btn-primary-outline:hover{background:var(--primary);border-color:var(--primary);color:#fff;}

/* Rouge PLEIN, réservé au moment de confirmation finale d'une action
   destructive — c'est-à-dire une vraie page de confirmation dédiée (voir
   reservation/cancel_confirm.html.twig). Ne PAS l'utiliser sur un bouton qui
   ouvre une simple boîte confirm() JS : voir CLAUDE.md. */
.btn-danger-confirm{background:var(--danger);color:#fff;}
.btn-danger-confirm:hover{background:#B93A31;color:#fff;}
/* Bouton DÉSACTIVÉ, générique à toutes les variantes. Sert au cas « le droit
   existait et vient d'être retiré » : le bouton reste visible pour que
   l'absence ne passe pas pour un bug, mais il est inerte et son title explique
   pourquoi (voir _gifts_section.html.twig et AccessResolver::giftEditLockedByActivity).
   Le `cursor:not-allowed` est ce qui distingue à la souris un bouton désactivé
   d'un bouton simplement pâle. Déclaré APRÈS toutes les variantes pour les
   neutraliser toutes — y compris leur :hover, sans quoi .btn-danger:hover
   repeindrait en rouge plein un bouton pourtant inactif. */
.btn:disabled,.btn[disabled],.btn:disabled:hover,.btn[disabled]:hover{
  background:var(--beige);border-color:var(--line);color:var(--ink-soft);
  opacity:.65;cursor:not-allowed;
}
.btn-sm{padding:8px 16px;font-size:.85rem;}
.btn-block{width:100%;}
button.link-btn{background:none;border:none;padding:0;color:var(--berry);cursor:pointer;font:inherit;text-decoration:underline;}

/* ---- Forms ---- */
label{display:block;font-weight:700;font-size:.9rem;margin-bottom:6px;color:var(--ink);}
.field{margin-bottom:18px;}
input[type=text],input[type=email],input[type=password],input[type=number],input[type=date],input[type=url],input[type=file],textarea,select{
  width:100%;padding:11px 13px;border-radius:12px;border:1px solid var(--line);
  background:#fff;font-family:inherit;font-size:.95rem;color:var(--ink);
}
textarea{min-height:100px;resize:vertical;}
input:focus,textarea:focus,select:focus{outline:2px solid var(--gold);outline-offset:1px;}
.field-hint{font-size:.8rem;color:var(--ink-soft);margin-top:4px;}
/* ---- Champ enrichi : en-tête, aide, icône dans le champ, compteur ----
   Utilisé par le formulaire de liste ; réutilisable tel quel ailleurs. */
.field-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.field-head label{margin:0;}
.field-required{color:var(--danger);font-weight:700;}
.field-help{margin:2px 0 8px;font-size:.82rem;color:var(--ink-soft);}
/* Icône DANS le champ. `position:relative` sur le conteneur, et un padding
   gauche sur l'input pour lui laisser la place — sans ce padding, le texte
   saisi passe SOUS l'icône. */
.field-input{position:relative;}
.field-input-icon{
  position:absolute;left:14px;top:50%;transform:translateY(-50%);
  display:flex;color:var(--forest-dark);pointer-events:none;
}
.field-input-icon svg{width:20px;height:20px;}
.field-input input{padding-left:44px;}
.field-counter{margin:6px 0 0;text-align:right;font-size:.78rem;color:var(--ink-soft);}
/* Un dépassement reste possible : `maxlength` ne tronque pas une valeur déjà
   en place. On le signale plutôt que de le taire — le serveur refusera. */
.field-counter--over{color:var(--danger);font-weight:700;}
/* Chevron à DROITE d'un champ, pour signaler une liste de suggestions
   (`<datalist>`). ⚠️ Purement visuel : le navigateur n'expose aucun moyen de
   styler l'indicateur natif, qui n'apparaît d'ailleurs pas partout. */
.field-input-chevron{
  position:absolute;right:14px;top:50%;transform:translateY(-50%);
  display:flex;color:var(--ink-soft);pointer-events:none;
}
.field-input-chevron svg{width:18px;height:18px;}
.field-input:has(.field-input-chevron) input{padding-right:42px;}

/* ---- Sélecteur de quantité en pas à pas ----
   Les flèches natives d'un `input[type=number]` sont minuscules et quasiment
   invisibles sur mobile. ⚠️ Le champ reste éditable au clavier : on ajoute
   deux boutons, on ne remplace pas la saisie. */
.qty-stepper{display:flex;align-items:stretch;border:1px solid var(--line);border-radius:12px;overflow:hidden;}
.qty-stepper input{
  border:none;border-radius:0;text-align:center;
  /* Les flèches natives feraient doublon avec les deux boutons. */
  -moz-appearance:textfield;
}
.qty-stepper input::-webkit-outer-spin-button,
.qty-stepper input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;}
.qty-stepper input:focus{outline-offset:-2px;}
.qty-step{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:48px;border:none;background:var(--beige);color:var(--ink);cursor:pointer;
  transition:background .15s ease;
}
.qty-step:hover{background:var(--beige-dark);}
.qty-step svg{width:18px;height:18px;}
/* Quantité verrouillée : le champ est `disabled`, les boutons ne sont pas
   rendus. Le cadre garde la même silhouette pour que la mise en page ne bouge
   pas d'un formulaire à l'autre. */
.qty-stepper--locked input{background:var(--beige);}

/* ---- Image : téléversement OU lien, côte à côte ----
   ⚠️ Les deux options sur la MÊME ligne, pour que le « ou » se lise d'un coup
   d'œil. L'une sous l'autre, on croit à deux champs à remplir. */
.image-choice{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;}
.dropzone{
  display:flex;align-items:center;gap:14px;
  padding:16px;border:2px dashed var(--line);border-radius:12px;
  background:var(--canvas);cursor:pointer;transition:.15s ease;
}
.dropzone:hover{border-color:var(--forest);background:var(--mint-soft);}
/* 🔴 Le vrai `input[type=file]` est MASQUÉ mais reste dans le label : c'est le
   label qui le déclenche, sans une ligne de JavaScript. Le retirer du DOM ou le
   mettre en `display:none` sur certains navigateurs le rendrait inatteignable
   au clavier. */
.dropzone input[type=file]{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
.dropzone-icon{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:50%;
  background:var(--mint-soft);color:var(--forest-dark);
}
.dropzone-icon svg{width:22px;height:22px;}
.dropzone-text{display:flex;flex-direction:column;gap:2px;min-width:0;}
.image-choice-url label{display:block;font-weight:700;font-size:.85rem;margin-bottom:6px;}
.gift-form-preview{max-width:140px;border-radius:12px;display:block;margin-bottom:12px;}

@media (max-width:560px){
  /* Les deux options s'empilent : à cette largeur, une zone de dépôt et un
     champ d'URL côte à côte deviennent illisibles tous les deux. */
  .image-choice{grid-template-columns:1fr;}
}
.radio-group{display:flex;flex-direction:column;gap:10px;}
/* ⚠️ `.radio-option` EST UN `<label>` (toute la carte est cliquable, voir
   templates/form/fields.html.twig) : il annule donc les marges et la graisse
   que la règle globale `label{}` lui donnerait. */
.radio-option{border:1px solid var(--line);border-radius:12px;padding:12px 14px;display:flex;gap:10px;align-items:flex-start;cursor:pointer;margin:0;font-weight:400;font-size:inherit;}
.radio-option:has(input:checked){border-color:var(--forest);background:var(--mint-soft);}
.radio-option input[type=radio],.radio-option input[type=checkbox]{width:auto;flex-shrink:0;margin:2px 0 0;}
.radio-option .radio-label{font-weight:600;font-size:.92rem;color:var(--ink);line-height:1.4;}
.radio-option > .radio-body{flex:1;}
.radio-option div strong{display:block;font-size:.92rem;}
.radio-option div span{font-size:.82rem;color:var(--ink-soft);}
/* Variante CARTES : deux choix côte à côte, chacun avec sa pastille d'icône et
   sa phrase d'explication. ⚠️ OPT-IN, posée par le thème de formulaire quand le
   champ déclare des icônes (voir templates/form/fields.html.twig) : sans ça,
   tous les choix développés du projet basculeraient en colonnes, y compris le
   mode de participation d'un cadeau, qu'aucune demande ne concernait.
   `auto-fit` + `minmax` : côte à côte quand la largeur le permet, empilés
   sinon — aucune media query à maintenir. */
.radio-group--cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px;}
.radio-option--card{align-items:center;padding:14px;}
.radio-icon{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:50%;
  background:var(--mint-soft);color:var(--forest-dark);
}
.radio-icon svg{width:20px;height:20px;}
/* Ton AMBRÉ pour le second choix de chaque paire. Il ne dit pas « attention » :
   il distingue deux options également valables, là où deux pastilles vertes
   se seraient confondues au premier coup d'œil. */
.radio-icon--warm{background:var(--gold-soft);color:#B87A14;}
.radio-body{display:flex;flex-direction:column;gap:2px;min-width:0;}
.radio-hint{font-size:.8rem;color:var(--ink-soft);line-height:1.4;}
.form-actions{display:flex;gap:12px;margin-top:26px;flex-wrap:wrap;}

/* Bouton « Continuer avec Google » — voir templates/_google_button.html.twig.
   ⚠️ Ces valeurs viennent des Google Sign-In Branding Guidelines, pas de notre
   charte : fond blanc, bordure #747775, texte #1F1F1F, Roboto. Ne pas les
   aligner sur --paper / --line / --ink, qui en sont proches mais différents —
   la conformité de marque prime ici sur l'homogénéité visuelle. */
.btn-google{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  width:100%;box-sizing:border-box;
  background:#FFFFFF;border:1px solid #747775;border-radius:4px;
  padding:11px 18px;
  font-family:'Roboto','Nunito Sans',system-ui,sans-serif;
  font-weight:500;font-size:.95rem;color:#1F1F1F;
  cursor:pointer;transition:.15s ease;
}
.btn-google:hover{background:#F7F8F8;border-color:#5F6368;text-decoration:none;color:#1F1F1F;}
.btn-google:focus-visible{outline:2px solid var(--gold);outline-offset:2px;}
.btn-google-icon{display:inline-flex;align-items:center;}

/* Séparateur « ou » entre le bouton Google et le formulaire classique. */
.auth-separator{
  display:flex;align-items:center;gap:12px;
  margin:18px 0;color:var(--ink-soft);font-size:.85rem;
}
.auth-separator::before,.auth-separator::after{
  content:"";flex:1;height:1px;background:var(--line);
}

/* ---- Tag / badge (nest-tag signature element) ---- */
.tag{
  position:relative;display:inline-flex;align-items:center;gap:6px;
  font-size:.75rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  padding:5px 12px 5px 16px;border-radius:2px 10px 10px 2px;color:#fff;
}
.tag::before{
  content:"";position:absolute;left:6px;top:50%;transform:translateY(-50%);
  width:5px;height:5px;border-radius:50%;background:rgba(255,255,255,.85);
}
.tag-brouillon{background:var(--ink-soft);}
.tag-prete{background:var(--gold);}
.tag-archivee{background:#8A8375;}
.tag-privee{background:var(--forest);}
.tag-publique{background:var(--berry);}
.tag-owner{background:var(--forest);}
.tag-moderator{background:var(--organizer);}
.tag-participant{background:var(--berry);}
/* Liste système « Mes envies ». Volontairement NEUTRE (gris ardoise) et non
   coloré comme les statuts : ce badge ne décrit pas une étape du cycle de vie
   d'une liste, il signale qu'elle n'en a pas. Le distinguer visuellement des
   tag-brouillon/prete/archivee évite de le lire comme un quatrième statut. */
.tag-default{background:#E7E9EC;color:#3F4A57;}
.tag-white{background:#FFFFFF;color:#3F4A57;}
/* Conteneur de badges, valable PARTOUT (vue de liste, cartes du tableau de
   bord…) : le `gap` s'applique aussi entre les lignes, sans quoi les badges se
   touchent verticalement dès qu'ils passent à la ligne (cas mobile à 3 badges).
   `.list-card .tags-row` ci-dessous n'en surcharge que les marges. */
.tags-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}

/* ---- Grid of list cards ---- */
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:20px;}
.list-card{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;box-shadow:var(--shadow);display:flex;flex-direction:column;
  transition:transform .15s ease;
}
.list-card:hover{transform:translateY(-3px);}
.list-card a.cover-link{display:block;}
.list-card .cover{height:140px;background:linear-gradient(135deg,var(--forest),var(--forest-dark));background-size:cover;background-position:center;position:relative;}
/* Indicateur « je vois / je ne vois pas les réservations » : pastille blanche
   opaque à icône sombre, pour rester lisible par-dessus n'importe quelle image
   de couverture. L'ombre portée évite qu'elle se fonde dans une couverture
   claire, où un simple rond blanc disparaîtrait. */
/* Badge « Liste permanente », posé SUR la couverture, en haut à gauche.
   ⚠️ Fond blanc opaque et ombre portée, comme `.cover-visibility` juste en
   dessous : il repose sur une photo dont on ne peut rien présumer. Un badge
   translucide ou coloré s'y perdrait sur une image claire — c'est exactement le
   problème réglé sur la bannière de `wishlist/view.html.twig`, traité ici par
   l'opacité plutôt que par un double liseré, la surface étant plus petite. */
.list-card .cover .cover-badge{
  position:absolute;top:15px;left:15px;
  display:inline-flex;align-items:center;gap:6px;
  padding:5px 10px;border-radius:999px;
  background:#fff;color:var(--ink);
  font-size:.72rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;
  box-shadow:0 1px 4px rgba(30,43,34,.35);
}
.list-card .cover .cover-badge svg{width:13px;height:13px;stroke-width:2.2;}

.list-card .cover .cover-visibility{
  position:absolute;top:15px;right:15px;
  display:flex;align-items:center;justify-content:center;
  width:28px;height:28px;border-radius:50%;
  background:#fff;color:var(--ink);
  box-shadow:0 1px 4px rgba(30,43,34,.35);
}
.list-card .cover .cover-visibility svg{width:15px;height:15px;stroke-width:2.2;}
.list-card .body{padding:15px;display:flex;flex-direction:column;gap:8px;flex:1;}
/* ⚠️ La teinte porte le STATUT, et uniquement sur le bloc d'information : la
   couverture reste intacte au-dessus. Une teinte appliquée à toute la carte
   aurait débordé sur la photo, qu'on ne maîtrise pas — un voile coloré sur une
   image téléversée donne un résultat sale.

   ⚠️ Elle DOUBLE le badge de statut plutôt que de le remplacer : on identifie
   la liste en survolant la page des yeux, et on confirme en lisant. Retirer le
   badge en croyant la teinte suffisante laisserait l'information portée par la
   seule couleur — invisible à qui ne la distingue pas. */
.list-card--prete .body{background:var(--bg-prete);}
.list-card--brouillon .body{background:var(--bg-brouillon);}
.list-card--archivee .body{background:var(--bg-archivee);}
/* « Retirer de mes listes » — uniquement sur les cartes où l'on est PARTICIPANT
   (voir dashboard/_list_card.html.twig).

   ⚠️ UN LIEN, PAS UN BOUTON : c'est une action de ménage, et un bouton lui
   donnerait autant de poids qu'ouvrir la liste — qui est la raison d'être de la
   carte.

   ⚠️ `margin-top:auto` le pousse au BAS du bloc, quelle qu'en soit la hauteur.
   Sans ça il suivrait immédiatement les badges, et deux cartes voisines dont
   l'une a une date et l'autre non afficheraient leur lien à deux hauteurs
   différentes. Même mécanisme que le prix d'une carte d'idée cadeau.

   ⚠️ `align-self:flex-start` : le corps de la carte est une colonne flex, donc
   les enfants s'étirent par défaut — la zone cliquable couvrirait toute la
   largeur, bien au-delà du texte. */
.list-card-unpin{margin:auto 0 0;align-self:flex-start;}
.list-card-unpin button{
  padding:0;border:0;background:none;cursor:pointer;
  font:inherit;font-size:.8rem;color:var(--ink-soft);
  text-decoration:underline;text-underline-offset:2px;
}
.list-card-unpin button:hover{color:var(--danger);}

/* ⚠️ Le titre passe de --berry à --berry-dark SUR CES FONDS. En --berry il
   tombait à 2,93:1 sur le beige archivé — sous le seuil AA « grand texte »
   (3:1), alors que ce h3 de 18,4px relève en réalité du texte NORMAL, dont le
   seuil est 4,5:1.
   ⚠️ --berry-dark remonte à 3,75:1 : c'est mieux, ce n'est PAS conforme. La
   correction complète demande de changer la couleur des titres de carte
   (--forest-dark passerait à 4,62:1), ce qui touche l'identité visuelle et
   dépasse ce chantier. Défaut PRÉEXISTANT : sur le fond de page, --berry était
   déjà à 3,26:1 avant l'ajout de ces teintes. */
.list-card h3 a{color:var(--berry-dark);}
.list-card h3{margin:0;}
.list-card .meta{font-size:.82rem;color:var(--ink-soft);}
/* Hérite de .tags-row (flex + gap) ; ne surcharge que le positionnement :
   margin-top:auto pousse la rangée en bas de la carte. */
.list-card .tags-row{margin-top:auto;padding-top:10px;}

.empty-state{text-align:center;padding:60px 20px;color:var(--ink-soft);}
.empty-state h3{color:var(--ink);}

/* ── ÉTAT VIDE DE LA SECTION « CADEAUX » ─────────────────────────────────────
   Illustration à gauche, texte à droite.

   🔴 CLASSE À PART, ET SURTOUT PAS UNE REFONTE DE `.empty-state` : celle-ci
   sert QUATRE écrans — celui-ci, « Lien invalide », « Accès refusé » et le
   panneau de tri. Les trois autres annoncent une absence ou un refus, où un
   cadeau festif serait déplacé. On ne fait donc que s'ajouter par-dessus.

   ⚠️ `text-align:left` ANNULE le centrage hérité : la maquette met le texte en
   regard de l'illustration, et centré à côté d'elle il flotterait. */
.gifts-empty{
  display:flex;align-items:center;justify-content:start;gap:32px;
  text-align:left;padding:40px 32px;
}
.gifts-empty-body{min-width:0;max-width:50ch;flex:1 1 auto;}

/* 🔴 460px, ET CE N'EST PAS UN CHIFFRE ROND CHOISI AU HASARD : c'est la largeur
   à partir de laquelle « Ajoutez des cadeaux en 1 clic partout sur le web ! »
   tient sur UNE ligne. Le titre perd une ligne, et la hauteur de la carte tombe
   d'environ 330px à ~230px — élargir RACCOURCIT, sans rien supprimer.

   ⚠️ C'est ici que la largeur se décide, jamais dans `_extension_promo` : le
   partiel n'en a aucune à lui. Au tableau de bord, c'est `.dashboard-side` qui
   lui impose ses 320px — d'où un titre sur deux lignes là-bas et sur une seule
   ici, ce qui est normal.

   🔴 `align-self:stretch` ANNULE le `align-items:center` du conteneur, et il
   est INDISPENSABLE : `.extension-promo--card` porte `height:100%` et
   `justify-content:space-between`, prévus pour s'étirer à la hauteur de sa
   voisine au tableau de bord. Centrée, la carte n'a pas de hauteur définie à
   laquelle se référer, `height:100%` se résout en `auto`, et le bouton cesse
   d'être ancré en bas. Vérifié à l'écran, pas supposé. */
.gifts-empty-promo{flex:0 0 460px;align-self:stretch;}

/* ⚠️ LE BOUTON D'INSTALLATION EST RAMENÉ À SA TAILLE DE CONTENU, et rapetissé.
   Ailleurs — tableau de bord, profil — il occupe toute la largeur de sa carte :
   c'est là l'action principale de l'écran. Ici il cohabite avec « Votre liste
   commence ici », qui EST l'action principale ; en pleine largeur et en taille
   normale, il prenait plus d'importance que l'invitation à côté de lui.

   ⚠️ Les deux valeurs sont celles de `.btn-sm`, recopiées faute de pouvoir
   étendre une classe en CSS — le partiel est partagé, on ne peut pas lui
   ajouter `btn-sm` sans inventer une variante. Les garder d'accord est à faire
   à la main si `.btn-sm` bouge ; l'écart serait purement visuel.

   ⚠️ Portée `.gifts-empty-promo` et RIEN d'autre : la règle de pleine largeur
   vaut toujours pour le tableau de bord et le profil.

   🔴 LE SÉLECTEUR NOMME `.extension-promo--card`, ET C'EST INDISPENSABLE. Sans
   lui — `.gifts-empty-promo .extension-promo-cta` — on est à ÉGALITÉ de
   spécificité avec `.extension-promo--card .extension-promo-cta{width:100%}`,
   déclarée 580 lignes plus bas : à égalité, c'est la dernière écrite qui
   l'emporte, et le bouton restait en pleine largeur. Défaut signalé à l'usage.

   ⚠️ On gagne par la SPÉCIFICITÉ et non par la position : déplacer la règle
   plus bas marcherait aussi, et se casserait au premier réarrangement du
   fichier. Même piège que les surcharges responsives de `.list-hero`. */
.gifts-empty-promo .extension-promo--card .extension-promo-cta{width:auto;padding:8px 16px;font-size:.85rem;}
/* ⚠️ Le titre est en `--forest-dark`, et le sélecteur est PORTÉ PAR
   `.gifts-empty` : `.empty-state h3` sert aussi à « Lien invalide » et « Accès
   refusé », qui doivent garder `--ink`. Colorer ces deux-là en vert de charte
   donnerait un ton engageant à un refus. 5,26:1 sur le blanc de la carte. */
.gifts-empty .gifts-empty-body h3{margin:0 0 6px;color:var(--forest-dark);}
.gifts-empty-body p{margin:0 0 10px;}
.gifts-empty-body p:last-child{margin-bottom:0;}

/* Le disque est du CSS, pas du dessin — rien à redessiner si la teinte change.
   ⚠️ `position:relative` : les traits décoratifs se posent PAR-DESSUS, dans un
   calque qui déborde du disque. */
.gifts-empty-art{
  position:relative;flex:none;
  width:132px;height:132px;
}
.gifts-empty-art > svg:first-child{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:56px;height:56px;
}
/* ⚠️ Le disque est un `::before` et non un fond sur le conteneur : celui-ci
   doit rester carré pour que le calque des traits garde ses coordonnées, alors
   que le disque, lui, est plus petit. */
.gifts-empty-art::before{
  content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:104px;height:104px;border-radius:50%;background:var(--mint-soft);
}
/* Les traits et le cœur DÉBORDENT du disque : c'est ce qui donne l'impression
   d'un dessin plutôt que d'une icône dans une pastille. */
.gifts-empty-sparks{position:absolute;inset:0;width:100%;height:100%;}

/* ⚠️ Sous 640px on empile et on recentre — à cette largeur, deux colonnes
   laisseraient au texte moins de la moitié de l'écran. Même seuil que la carte
   « Mes envies », pour que les deux basculent ensemble. */
/* ⚠️ On empile dès 900px, et non 640 : à trois colonnes, la carte de 460px ne
   laisse plus assez de place à l'illustration et au texte. C'est la carte qui
   dicte ce seuil, pas le texte. */
@media (max-width:900px){
  .gifts-empty{flex-wrap:wrap;}
  .gifts-empty-promo{flex:1 1 100%;align-self:auto;}
}
@media (max-width:640px){
  .gifts-empty{flex-direction:column;gap:18px;text-align:center;padding:32px 20px;}
  .gifts-empty-art{width:112px;height:112px;}
  .gifts-empty-art::before{width:88px;height:88px;}
  .gifts-empty-art > svg:first-child{width:48px;height:48px;}
}

/* ---- Dashboard : titres de section collants + section archivée repliable ---- */
/* Titres de section du tableau de bord, rendus « collants » : les cartes ne
   portent plus de tag de statut (cf. dashboard/_list_card.html.twig), c'est ce
   titre qui garde le repère visible pendant le défilement d'une grille longue.
   Le fond opaque est indispensable — sans lui les cartes défileraient sous le
   texte. Chaque titre est borné par sa <section>/<details> parente, sinon celui
   d'une section resterait figé au-dessus de la suivante. */
.list-section{margin-top:20px;}
.list-section > h2,
.archived-toggle > summary{
  position:sticky;top:0;z-index:5;
  background:var(--canvas);
  margin:0 0 16px;padding:10px 0;
  /* Icône, titre, puis un trait qui court jusqu'au bord. */
  display:flex;align-items:center;gap:10px;
  font-size:1.35rem;
}
/* 🔴 LE TRAIT EST UN `::after` EN `flex:1`, jamais une bordure basse : posé en
   bordure, il passerait SOUS le titre au lieu de le prolonger. Il occupe toute
   la place restante, quelle que soit la longueur du libellé — « Archivées (3) »
   comme « Prêtes ». */
.list-section > h2::after,
.archived-toggle > summary::after{
  content:'';flex:1;height:1px;background:var(--line);
}
/* ⚠️ Chaque section a SON icône, et c'est elle qui porte le repère de statut.
   🔴 LA COULEUR NE SUFFIT PAS, et c'est la leçon des bordures qu'on remplace :
   celles-ci ajoutaient un pointillé aux brouillons pour que la distinction ne
   tienne pas à la seule teinte. L'icône fait mieux — une FORME différente par
   statut, lisible sans distinguer les couleurs. */
.list-section > h2 > svg.icon,
.archived-toggle > summary > svg.icon{width:22px;height:22px;flex:none;}
.list-section--ready > h2 > svg.icon{color:var(--gold);}
.list-section--drafts > h2 > svg.icon{color:var(--organizer);}
.list-section--archived > summary > svg.icon{color:var(--ink-soft);}
/* Bouton d'aide de l'étiquette « Brouillons », et la bulle qu'il ouvre.

   🔴 MASQUÉ PAR DÉFAUT, RÉVÉLÉ PAR `@supports`. C'est ce qui évite le seul
   défaut du `popover` natif : sur un navigateur qui ne le connaît pas
   (avant Chrome 114, Safari 17, Firefox 125), `popovertarget` est ignoré et le
   bouton ne ferait RIEN. Ici il n'apparaît tout simplement pas.
   ⚠️ Et si `@supports selector()` lui-même n'est pas compris, le bloc entier
   est sauté — le bouton reste masqué. L'échec va donc dans le bon sens.

   ⚠️ La bulle s'affiche CENTRÉE, comportement par défaut du `popover` : l'ancrer
   près du bouton demanderait CSS Anchor Positioning, aujourd'hui limité à
   Chrome. Centré est prévisible partout, et confortable sur un téléphone. */
.status-help{display:none;}
@supports selector(:popover-open){
  .status-help{
    display:inline-flex;align-items:center;
    margin-left:6px;padding:0;border:0;background:none;
    /* `currentColor` : le bouton prend la couleur de son étiquette, qui
       change avec le statut. Rien à redéclarer par section. */
    color:inherit;opacity:.75;cursor:pointer;vertical-align:-.15em;
  }
  .status-help:hover{opacity:1;}
}
.status-popover{
  max-width:min(340px, calc(100vw - 32px));
  padding:16px 18px;border:1px solid var(--line);border-radius:14px;
  background:var(--paper);color:var(--ink);box-shadow:var(--shadow);
  font-size:.9rem;line-height:1.5;
}
.status-popover::backdrop{background:rgba(30,43,34,.35);}

/* Les archivées gardent leur repli natif : le TITRE est le bouton.
   ⚠️ `display:flex` est déjà posé par la règle commune plus haut — on ne le
   répète pas, sous peine de laisser deux définitions diverger. */
.archived-toggle{margin-top:36px;}
.archived-toggle > summary{cursor:pointer;list-style:none;}
.archived-toggle > summary::-webkit-details-marker{display:none;}
.archived-toggle > summary::before{content:"▸";font-size:.9em;transition:transform .15s ease;}
.archived-toggle[open] > summary::before{transform:rotate(90deg);}
/* ⚠️ Replié, le conteneur ne doit pas garder la hauteur d'une grille vide :
   le padding bas ne s'applique qu'une fois ouvert. */
.archived-toggle:not([open]){padding-bottom:6px;}
.archived-toggle .grid{margin-top:8px;}

/* ---- Carte de la liste permanente ----

   ⚠️ Deux blocs côte à côte, le texte À GAUCHE sur aplat vert, l'image à
   droite. La proportion 5/7 laisse à l'image de quoi rester lisible tout en
   donnant au texte la place d'une accroche sur deux lignes. */
.permanent-card{
  display:grid;grid-template-columns:5fr 7fr;
  min-height:210px;border-radius:14px;overflow:hidden;
  color:#fff;text-decoration:none;
}
.permanent-card:hover{text-decoration:none;}

/* Le bloc de texte : un aplat vert, sans découpe.

   ⚠️ Il déborde sous l'image (`margin-right` négatif) et passe DESSOUS
   (`z-index` inférieur). C'est le bloc image qui porte la vague et vient
   mordre sur lui — voir `.permanent-card-media`. Ne pas remettre de
   `clip-path` ici : les deux découpes se soustrairaient et laisseraient une
   bande vide entre les blocs. */
.permanent-card-body{
  position:relative;z-index:2;
  display:flex;flex-direction:column;justify-content:center;gap:10px;
  padding:26px 30px;margin-right:-44px;
  background:var(--forest-dark);
}
/* Badge « Liste permanente », dans le corps VERT de la carte.
   ⚠️ Blanc translucide et non un fond opaque : il repose sur l'aplat de la
   charte, pas sur une photo — contrairement à `.list-card .cover .cover-badge`,
   qui doit rester lisible par-dessus n'importe quelle image téléversée.
   ⚠️ `align-self:flex-start` : sans lui il s'étire sur toute la largeur du
   corps, le conteneur étant une colonne flex. */
.permanent-card-badge{
  align-self:flex-start;
  display:inline-flex;align-items:center;gap:6px;
  margin-bottom:10px;padding:4px 10px;border-radius:999px;
  background:rgba(255,255,255,.18);color:#fff;
  font-size:.7rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;
}
.permanent-card-badge svg{width:13px;height:13px;}
.permanent-card-title{font-size:1.9rem;font-weight:800;letter-spacing:-.01em;line-height:1.1;}
/* ⚠️ `text-wrap:balance` répartit l'accroche sur deux lignes de longueur
   voisine, ce qui fait tomber « Toujours disponible. » d'un bloc à la ligne
   suivante au lieu de laisser « Toujours » seul en fin de première ligne.
   🔴 Aucune traduction n'est touchée : une espace insécable aurait marché
   aussi, mais aurait dû être posée à la main dans les trois langues et refaite
   à chaque retouche du texte. Un navigateur qui ignore la propriété retombe
   sur la coupure ordinaire. */
.permanent-card-lead{font-size:.95rem;line-height:1.5;opacity:.92;max-width:34ch;text-wrap:balance;}

/* ⚠️ LA VAGUE VIT ICI. Le bloc image est découpé et posé AU-DESSUS du bloc de
   texte (`z-index:3` contre `2`), dont il mord le bord droit.

   🔴 LE TRACÉ EST ENTIÈREMENT EN POURCENTAGES, et ça n'a pas toujours été le
   cas. Ses ordonnées étaient en PIXELS (`1.17% 173px`), calées sur le
   `min-height:210px` de la carte : la vague ne pouvait donc pas suivre une
   carte plus haute, et c'est ce qui interdisait de l'étirer à la hauteur de sa
   voisine. Les valeurs ont été converties à cette hauteur de référence — à
   210px le dessin est pixel pour pixel le même qu'avant, et au-delà il
   s'allonge proportionnellement au lieu de se décaler.

   ⚠️ Ne pas y réintroduire de pixel : la carte s'étire désormais, et le tracé
   repartirait du haut pendant que le bas de la carte descend. */
.permanent-card-media{
  background-size:cover;background-position:center;
  /* Repli si l'image venait à manquer : un aplat de la charte, jamais un vide
     blanc qui passerait pour une image cassée. */
  background-color:var(--forest);
    clip-path: polygon(1.17% 82.38%, 1.1% 67.14%, 1.6% 50%, 2.44% 34.76%, 3.08% 23.33%, 4.23% 11.43%, 5.39% 0%, 5.39% 0%, 100% 0%, 100% 100%, 1.32% 99.52%);
    z-index: 3;
}

/* ⚠️ En mobile, les deux blocs s'EMPILENT et la vague passe à l'horizontale :
   à 380px de large, une découpe verticale laissait au texte moins de la moitié
   de l'écran — le titre y tenait sur trois lignes. C'est toujours le bloc IMAGE
   qui porte le tracé, repris sur l'axe des ordonnées. */
@media (max-width:640px){
  .permanent-card{grid-template-columns:1fr;min-height:0;}
  .permanent-card-body{order:2;margin-right:0;margin-top:-28px;padding:34px 22px 24px;}
  .permanent-card-media{order:1;min-height:150px;
      clip-path: polygon(64.41% 150px, 51.52% 146px, 41.27% 143px, 28.99% 138px, 18.59% 134px, 8.4% 131px, 0.3% 129px, 0% 0px, 100% 0px, 100% 100%, 82.17% 102.19%);
      z-index: 3;}
  .permanent-card-title{font-size:1.5rem;}
}

/* ---- Gift list ---- */
.gift-category-heading{
  margin:28px 0 12px;font-size:.8rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-soft);padding-bottom:8px;border-bottom:1px solid var(--line);
}
.gift-category-heading:first-of-type{margin-top:16px;}
/* Carte d'un cadeau : visuel à gauche, informations à droite.

   ⚠️ `.gift-row` n'avait AUCUNE règle avant ce chantier — les cadeaux
   s'empilaient sans cadre et sans mise en colonnes, alors que `.gift-main`
   (`flex:1`) et `.gift-thumb` (`flex-shrink:0`) présupposaient déjà un parent
   flex. Ces deux règles ne servaient donc à rien ; elles servent maintenant. */
/* ⚠️ GRILLE et non flex, depuis que la rangée d'actions doit changer de place
   selon la largeur. Un flex ne sait pas « mettre cet élément sur toute la
   largeur en mobile mais dans la seconde colonne en desktop » sans réordonner
   le DOM ; une grille le fait avec deux jeux de zones nommées.

   Desktop : le visuel occupe les DEUX lignes de la première colonne, les infos
   et les boutons se partagent la seconde — les boutons démarrent donc sous le
   titre, comme la maquette. */
.gift-row{
  display:grid;
  grid-template-columns:180px 1fr;
  grid-template-areas:
    "thumb main"
    "thumb actions";
  gap:16px 20px;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:18px;margin-bottom:16px;
}

/* ⚠️ Largeur FIXE et carré : c'est ce qui aligne verticalement toutes les
   colonnes de texte d'une liste. Une image qui dicterait sa propre largeur
   ferait démarrer chaque titre à un endroit différent. */
/* ⚠️ Hauteur EXPLICITE (180px, la même que `.idea-card-media` du catalogue),
   et non une hauteur déduite d'un `aspect-ratio`. La carte est un conteneur
   flex : sans hauteur fixe, le visuel s'étire pour épouser celle de la colonne
   de texte à côté, et la boîte cadeau d'un cadeau très commenté sort deux fois
   plus haute que celle du cadeau voisin. `align-self:flex-start` empêche
   l'étirement, mais poser la hauteur ne dépend d'aucun autre réglage — c'est
   plus robuste, et ça aligne les vignettes sur celles du catalogue. */
.gift-thumb-wrap{
  grid-area:thumb;align-self:start;width:180px;height:180px;
  display:flex;align-items:center;justify-content:center;
  background:var(--canvas);border-radius:12px;overflow:hidden;
}
/* ⚠️ `contain` et jamais `cover` : les photos de produit ont des proportions
   très variables, et `cover` en rognerait le sujet — même raisonnement que sur
   les cartes d'idées cadeaux du catalogue. Le fond clair comble le reste. */
.gift-thumb{max-width:100%;max-height:100%;object-fit:contain;}
.gift-thumb-placeholder{
  width:100%;height:100%;background:var(--mint-soft);
  display:flex;align-items:center;justify-content:center;font-size:1.6rem;
}

/* `min-width:0` : sans lui, un mot long (une URL collée en description) impose
   sa largeur au conteneur flex et fait déborder la carte. */
.gift-main{grid-area:main;min-width:0;}
.gift-main > h3{font-size:1.35rem;}
.gift-main h3{margin-bottom:2px;}
.gift-desc{color:var(--ink-soft);font-size:.92rem;margin:4px 0;}
.gift-meta{display:flex;gap:14px;flex-wrap:wrap;font-size:.85rem;color:var(--ink-soft);margin-top:6px;}
.gift-meta span{white-space:nowrap;}
/* « Cadeau en participation » : doit se distinguer du reste de la ligne de
   méta, volontairement grise et discrète — c'est une information de NATURE du
   cadeau, pas un détail. Pas un .tag : la ligne resterait lisible, sans le
   poids visuel d'un badge coloré à côté des badges de statut. */
.gift-meta-funding{font-weight:700;color:var(--primary);}

/* Commentaire du propriétaire : un encart, pas une ligne de méta de plus. */
.gift-comment{
  display:flex;align-items:flex-start;gap:8px;
  background:var(--beige);border-radius:10px;padding:10px 12px;
  margin:12px 0 0;font-size:.9rem;color:var(--ink);
}
.gift-comment svg{flex:none;margin-top:2px;color:var(--ink-soft);}

/* ⚠️ `margin-top:auto` : colle les boutons en bas de la carte, quelle que soit
   la longueur du texte au-dessus. Sans lui, chaque rangée flotte à la fin de
   son propre paragraphe et deux cartes voisines n'alignent plus rien. */
/* ⚠️ `align-self:end` remplace l'ancien `margin-top:auto` : le visuel occupant
   les deux lignes, la seconde s'étire quand le texte est court, et les boutons
   flotteraient en haut d'un vide. Collés en bas, ils s'alignent sur le pied du
   visuel — et sur ceux de la carte voisine. */
.gift-actions{grid-area:actions;align-self:end;display:flex;flex-wrap:wrap;gap:8px;align-items:stretch;}
/* ⚠️ « Supprimer » est enveloppé dans un `<form>` (POST + jeton CSRF) : c'est
   donc le FORMULAIRE qui est l'enfant flex, pas le bouton. Une règle posée sur
   `.btn` seul laissait ce troisième élément à sa largeur naturelle pendant que
   les deux autres s'étiraient — les boutons n'étaient jamais de largeur égale.
   D'où la mise à plat du formulaire et la règle posée sur `> *`. */
.gift-actions form{display:flex;margin:0;}
.gift-actions form > .btn{width:100%;}
.avail-bar{width:100%;height:6px;background:var(--line);border-radius:3px;overflow:hidden;margin-top:4px;}
.avail-bar span{display:block;height:100%;background:var(--gold);}
.reserved-list{font-size:.8rem;color:var(--ink-soft);margin-top:4px;}

/* ---- Share box ---- */
.share-box{display:flex;gap:10px;align-items:center;background:var(--canvas);border-radius:12px;padding:12px 14px;margin-top:8px;}
.share-box input{background:#fff;font-size:.82rem;}

/* ---- Cover image picker ---- */
.cover-field{display:flex;flex-direction:column;gap:12px;align-items:flex-start;}
/* L'attribut `hidden` (basculé en JS) doit l'emporter sur les `display:flex`
   ci-dessous : sans ça, .cover-empty / .cover-remove resteraient visibles une
   fois masqués. Spécificité 0,2,0 > les règles à une seule classe. */
.cover-field [hidden]{display:none;}
.cover-preview{
  position:relative;width:100%;height:150px;border:1px solid var(--line);border-radius:12px;
  background:var(--canvas);overflow:hidden;display:flex;align-items:center;justify-content:center;
}
.cover-preview img[data-cover-img]{width:100%;height:100%;object-fit:cover;display:block;}
.cover-empty{display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ink-soft);pointer-events:none;}
.cover-empty svg{width:40px;height:40px;stroke-width:1.6;opacity:.55;}
.cover-empty span{font-size:.9rem;font-weight:600;}
.cover-remove{
  position:absolute;top:8px;right:8px;width:30px;height:30px;border-radius:50%;border:none;
  background:rgba(30,43,34,.55);color:#fff;cursor:pointer;font-size:.8rem;line-height:1;
  display:flex;align-items:center;justify-content:center;transition:.15s ease;
}
.cover-remove:hover{background:var(--danger);}
.cover-picker{position:relative;display:flex;align-items:center;gap:12px;flex-wrap:wrap;}
.cover-formats{font-size:.78rem;color:var(--ink-soft);}
.cover-toggle{display:inline-flex;align-items:center;gap:8px;}
.cover-menu{
  position:absolute;z-index:30;top:calc(100% + 6px);left:0;width:340px;max-width:80vw;max-height:400px;
  overflow-y:auto;background:var(--paper);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow);padding:6px;
}
.cover-option{
  display:flex;align-items:center;gap:10px;width:100%;text-align:left;background:none;border:none;
  padding:8px 10px;border-radius:8px;cursor:pointer;font:inherit;font-size:.9rem;color:var(--ink);
}
.cover-option:hover{background:var(--canvas);}
.cover-option-device{font-weight:600;border-bottom:1px solid var(--line);border-radius:0;margin-bottom:6px;padding-bottom:12px;}
/* Presets : uniquement l'image, en pleine largeur du menu. */
.cover-preset-option{display:block;padding:4px;}
.cover-preset-option + .cover-preset-option{margin-top:4px;}
.cover-preset-option .cover-thumb{
  display:block;width:100%;height:auto;aspect-ratio:8 / 3;object-fit:cover;
  border-radius:8px;background:var(--canvas);border:1px solid var(--line);
}
.cover-preset-option:hover .cover-thumb{border-color:var(--forest);}

/* ---- Misc ---- */
.divider{height:1px;background:var(--line);margin:24px 0;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:24px;}
.center{text-align:center;}
.muted{color:var(--ink-soft);}
.small{font-size:.85rem;}
/* L'avatar d'un compte, rendu par `templates/_avatar.html.twig`.
   🔴 LA TAILLE VIT ICI, JAMAIS EN `style=""` DANS LE GABARIT : un style en
   ligne l'emporte sur toute règle d'auteur, media queries comprises — l'avatar
   garderait alors sa taille de bureau sur un téléphone, sans erreur et sans
   qu'aucune règle ne paraisse fautive. D'où la variable, posée par la classe
   que le partiel reçoit en paramètre. */
.avatar{width:var(--avatar-size,40px);height:var(--avatar-size,40px);border-radius:50%;object-fit:cover;background:var(--canvas);}
/* Repli sans photo — et c'est le cas COURANT, l'avatar étant facultatif aux
   deux endroits où il est demandé. Aplat de la charte et texte blanc (5,26:1),
   donc lisible quel que soit ce qu'il y a derrière : ce disque se pose aussi
   bien sur une photo de couverture quelconque que sur un fond de carte.
   `font-size` proportionnel à la variable : une seule déclaration sert toutes
   les tailles, là où des paliers en dur se seraient désaccordés au premier
   ajout. */
.avatar--initials{
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--forest-dark);color:#fff;
  font-weight:700;font-size:calc(var(--avatar-size,40px) * .38);line-height:1;
  letter-spacing:.02em;
}
.avatar--initials svg{width:calc(var(--avatar-size,40px) * .5);height:calc(var(--avatar-size,40px) * .5);}
/* Panneau de profil : la photo qu'on vient d'y téléverser. Elle y était rendue
   par un <img> écrit à la main, sous `{% if user.avatar %}` — donc rien du tout
   pour la plupart des comptes. Le partiel lui apporte le repli au passage. */
.profile-avatar{--avatar-size:64px;}
/* Le disque et sa croix de retrait. `inline-block` : le conteneur épouse le
   disque au lieu de prendre toute la largeur de la carte, sans quoi la croix —
   positionnée par rapport à lui — se retrouverait à l'autre bout de l'écran. */
.profile-avatar-wrap{position:relative;display:inline-block;margin-bottom:16px;}
/* ⚠️ La croix chevauche le bord du disque plutôt que de se poser dedans : sur
   une photo quelconque, dont on ne peut rien présumer, un bouton posé à
   l'intérieur se fondrait dans l'image. Le fond blanc et l'anneau de la carte
   le détachent de tout. */
.profile-avatar-remove{position:absolute;top:-4px;right:-4px;line-height:0;}
.profile-avatar-remove button{
  width:24px;height:24px;padding:0;border-radius:50%;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  background:var(--paper);border:1px solid var(--line);color:var(--ink-soft);
  font-size:1.05rem;line-height:1;
}
/* ⚠️ 24px est le MINIMUM de WCAG 2.5.8 pour une cible tactile, et il n'y a ici
   aucune cible voisine dont on risque de se tromper. Ne pas descendre en
   dessous pour « faire plus discret ». */
.profile-avatar-remove button:hover{border-color:var(--danger);color:var(--danger);}

/* ================================================================
   Landing page components
   ================================================================ */

/* Inline line-icons (Lucide-style) : couleur pilotée par `color` du parent */
/* ⚠️ `.ic` A ÉTÉ SUPPRIMÉE. C'était un SECOND mécanisme d'icône, à côté de
   `.icon` posée par la macro de `_icons.html.twig` : mêmes propriétés de
   trait, appliquées à des SVG recopiés à la main dans les gabarits. Elle
   n'a plus aucun porteur — la garder inviterait à réutiliser le motif
   qu'on vient précisément de retirer. Pour une icône : la macro. */

.hero{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center}
.hero-badge{
  display:inline-flex;align-items:center;gap:6px;background:var(--mint-soft);color:var(--forest-dark);
  font-weight:700;font-size:.82rem;padding:7px 16px;border-radius:999px;margin-bottom:18px;
}
/* 🔴 LE TITRE DU HERO N'AVAIT AUCUNE TAILLE MOBILE : les 2.7rem (43px) du
   bureau étaient servis tels quels sur un téléphone. Sur 312px utiles, ça fait
   une douzaine de caractères par ligne — le titre occupait SIX lignes et
   poussait tout le reste sous la ligne de flottaison, appels à l'action
   compris. Défaut signalé à l'usage, capture à l'appui.

   Repère de calcul, pour ne pas régler ça à l'œil : à 360px de large il reste
   312px une fois le padding de `.wrap` retiré, et un caractère de cette
   graisse mesure ~0,52em. Quatre lignes pour ~85 caractères demandent donc
   ~1,6rem — la taille que porte déjà `.list-hero h1` en mobile.

   ⚠️ `clamp()` PLUTÔT QU'UNE MEDIA QUERY, et c'est délibéré : la taille suit
   alors la largeur en continu, sans palier visible, et surtout la règle reste
   DANS le sélecteur de base. Une surcharge en `@media` aurait dû être placée
   APRÈS cette ligne pour l'emporter — les media queries n'ajoutent aucune
   spécificité — et c'est exactement le piège déjà rencontré sur `.list-hero`. */
.hero h1{font-size:clamp(1.6rem, 0.85rem + 3.3vw, 2.7rem);margin-bottom:.3em;}
.hero .accent{color:var(--forest);}
.hero-lead{font-size:1.05rem;color:var(--ink-soft);max-width:460px;}
.hero-actions{display:flex;gap:14px;flex-wrap:wrap;margin:26px 0 22px;}
.hero-actions .btn{padding:14px 26px;}
.hero-proof{display:flex;align-items:center;gap:12px;color:var(--ink-soft);font-size:.88rem;}
/* ⚠️ `.avatar-stack` A ÉTÉ SUPPRIMÉE. Une pile de vignettes rondes qui se
   chevauchent, sans le moindre porteur dans les gabarits : la trace d'une
   intention jamais terminée, au même titre que l'avatar lui-même, qui était
   demandé deux fois et affiché nulle part.
   🔴 Et ce n'est pas un motif à ressusciter tel quel : empiler les visages des
   gens qui ont réservé reviendrait à les nommer, alors que chacun choisit son
   anonymat réservation par réservation (`Reservation::$isAnonymous`). UN AVATAR
   EST UN NOM. */

.hero-visual{position:relative;}
/* ⚠️ `max-width:100%` + `height:auto` : l'illustration s'adapte à la colonne
   sans jamais déborder, et garde ses proportions. `display:block` supprime
   l'espace sous la ligne de base que laisse une image en `inline`. */
.hero-visual img{max-width:100%;height:auto;display:block;}

/* ---- Stats bar ---- */
.stats-bar{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-lg);
  box-shadow:var(--shadow);display:flex;margin:44px 0;overflow:hidden;
}
.stat{flex:1;display:flex;align-items:center;gap:14px;padding:26px 30px;}
.stat + .stat{border-left:1px solid var(--line);}
.stat-icon{
  width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:1.3rem;flex-shrink:0;
}
.stat-icon svg{width:24px;height:24px;}
.stat-icon.mint{background:var(--mint-soft);color:var(--forest);}
.stat-icon.coral{background:var(--berry-soft);color:var(--berry);}
.stat-icon.gold{background:var(--gold-soft);color:var(--gold);}
.stat strong{display:block;font-size:1.4rem;color:var(--ink);}
.stat span{display:block;font-size:.85rem;color:var(--ink-soft);}

/* ---- Section headings ---- */
.section-head{text-align:center;max-width:560px;margin:0 auto 36px;}
.section-eyebrow{display:block;color:var(--berry);font-weight:800;font-size:.8rem;letter-spacing:.08em;text-transform:uppercase;margin-bottom:10px;}
.section-head h2{font-size:1.9rem;}

/* ---- « Explorer Surprizio » (accueil) ---- */
.explore-section{margin:44px 0;}
/* Titre encadré de deux traits, avec un picto au-dessus — repris de la
   maquette. Les traits sont des pseudo-éléments en flex : pas d'image, pas de
   balise vide, et ils s'adaptent seuls à la longueur du titre. */
.explore-head{text-align:center;margin-bottom:24px;}
.explore-head-icon{
  display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;border-radius:50%;background:var(--mint-soft);color:var(--forest);margin-bottom:6px;
}
.explore-head-icon svg{width:22px;height:22px;}
.explore-head h2{display:flex;align-items:center;justify-content:center;gap:16px;font-size:1.6rem;margin:0;}
.explore-head h2::before,.explore-head h2::after{
  content:"";height:1px;background:var(--line);flex:1;max-width:90px;
}

/* ⚠️ `auto-fit` + `justify-content:center` + largeur plafonnée : quand
   PUBLIC_GIFT_IDEAS_ENABLED est à false, il ne reste qu'UNE carte. Sans ces
   trois réglages, elle s'étirerait sur toute la largeur ou laisserait un vide
   asymétrique à droite. Avec, elle se centre proprement. */
/* 🔴 LA LARGEUR MINIMALE EST PASSÉE DE 240 À 200px, ET C'EST CE QUI FAIT TENIR
   LES QUATRE TUILES SUR UNE LIGNE. Quatre pistes de 240 réclament 1014px
   (4×240 + 3×18 de gouttière) : ça passe tout juste dans les 1072px utiles
   d'un `.wrap` maximal, et plus du tout dès que la fenêtre n'est pas
   entièrement déployée. La quatrième repartait alors seule à la ligne.

   ⚠️ CE N'EST PAS LA LARGEUR RÉELLE DES CARTES : le `1fr` les fait toutes
   grandir jusqu'à ~254px sur un écran large. Le `minmax` ne décide que du
   NOMBRE de colonnes.

   🔴 ET SURTOUT PAS `repeat(4, 1fr)` COMME `.feature-grid`. Le nombre de
   cartes VARIE ici : deux quand la vitrine d'idées cadeaux est coupée, trois
   quand Secret Santa l'est. Avec quatre pistes en dur, deux cartes se
   serreraient à gauche d'un vide de 500px. `auto-fit` replie les pistes vides ;
   c'est la raison d'être de cette grille depuis le début.

   ⚠️ Surcharges déclarées JUSTE APRÈS la règle de base : une media query
   n'ajoute aucune spécificité, et placée plus haut dans le fichier elle serait
   écrasée. Même piège que `.list-hero`. */
.explore-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:18px;justify-content:center;
}
/* 🔴 1000px ET NON 900 COMME `.feature-grid`, et le chiffre est calculé : en
   dessous de ~950px utiles, `auto-fit` ne tient plus quatre pistes de 200 et
   en pose trois — donc une orpheline. On passe donc à deux AVANT d'atteindre
   cette bande, plutôt que de laisser apparaître un 3+1.

   🔴 …MAIS SEULEMENT QUAND IL Y A QUATRE CARTES. Cette règle existe pour éviter
   le 3+1 de l'accueil ; appliquée à la grille de la page d'un participant, qui
   n'en rend que TROIS (sa propre carte en est retirée), elle CRÉAIT un 2+1 là
   où trois tiennent très bien — 3×200 + 2×18 font 636px, disponibles dès ~684px
   de fenêtre.

   ⚠️ `:has(> :nth-child(4))` est une requête de QUANTITÉ : la règle ne
   s'applique qu'aux grilles d'au moins quatre cartes. Le projet utilise déjà
   `:has()` pour `.modal-panel:has(.kanban-board)`.

   ⚠️ Un navigateur sans `:has()` laisse tomber la règle et retombe sur
   `auto-fit` : au pire une orpheline sur l'accueil, dans une bande de largeur
   étroite. Dégradation acceptable — jamais une mise en page cassée. */
@media (max-width:1000px){
  .explore-grid:has(> :nth-child(4)){grid-template-columns:repeat(2,1fr);}
}
@media (max-width:640px){
  .explore-grid{grid-template-columns:1fr;}
}
.explore-card{
  display:flex;flex-direction:column;gap:8px;padding:20px;
  border:1px solid var(--line);border-radius:16px;color:var(--ink);transition:.15s ease;
}
.explore-card:hover{text-decoration:none;transform:translateY(-3px);border-color:var(--ink-soft);}
.explore-card-head{display:flex;align-items:center;gap:12px;}
/* La pastille perd sa marge basse ici : dans cette carte elle est À CÔTÉ du
   titre, pas au-dessus comme dans .feature-card. */
.explore-card .icon-badge{margin-bottom:0;flex:none;}
.explore-card-title{font-weight:800;font-size:1.05rem;}
.explore-card-text{font-size:.9rem;line-height:1.55;color:var(--ink-soft);flex:1;}
.explore-card-link{font-weight:700;font-size:.88rem;margin-top:4px;}

/* Fond très légèrement teinté, accordé à la pastille — toutes les teintes
   viennent de la palette existante, aucune n'est inventée pour l'occasion. */
.explore-card--mint{background:#F2FAF6;}
.explore-card--mint .explore-card-link{color:var(--forest-dark);}
.explore-card--coral{background:#FEF6F5;}
.explore-card--coral .explore-card-link{color:var(--berry-dark);}
/* ⚠️ Fond plus PÂLE que `--gold-soft`, qui est celui de la pastille : c'est le
   rapport qu'ont déjà les trois autres variantes, et il est ce qui détache le
   badge de sa carte. */
.explore-card--gold{background:#FEF8EE;}
.explore-card--gold .explore-card-link{color:var(--gold-dark);}
.explore-card--slate{background:#F4F6F8;}
.explore-card--slate .explore-card-link{color:var(--organizer);}

@media (max-width:640px){
  .explore-head h2{font-size:1.35rem;gap:10px;}
  .explore-head h2::before,.explore-head h2::after{max-width:40px;}
}

/* ---- Feature grid ---- */
.feature-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-items:stretch;}
/* Neutralise la marge générique `.card + .card` qui, dans cette grille,
   décalait les tuiles 2-4 et rendait la 1re plus haute que les autres. */
.feature-grid .card + .card{margin-top:0;}
.feature-card{text-align:left;display:flex;flex-direction:column;height:100%;}
.icon-badge{
  width:52px;height:52px;border-radius:16px;display:flex;align-items:center;justify-content:center;
  font-size:1.4rem;margin-bottom:16px;
}
.icon-badge svg{width:26px;height:26px;}
.icon-badge.mint{background:var(--mint-soft);color:var(--forest);}
.icon-badge.coral{background:var(--berry-soft);color:var(--berry);}
.icon-badge.gold{background:var(--gold-soft);color:var(--gold);}
/* Quatrième variante, pour les séquences à 4 items (grille « Pourquoi » et
   parcours « 4 étapes ») : sans elle, le 4ᵉ item rebouclait sur .mint et se
   confondait avec le 1er. Nommée d'après la teinte, pas d'après --organizer :
   le badge « Organisateur » et ces pastilles n'ont aucun lien sémantique, ils
   partagent seulement la couleur. */
.icon-badge.slate{background:var(--organizer-soft);color:var(--organizer);}
.feature-card p{font-size:.9rem;}
.feature-card .learn-more{margin-top:auto;font-weight:700;font-size:.85rem;color:var(--forest-dark);display:inline-flex;align-items:center;gap:6px;}
.feature-card .learn-more svg{width:16px;height:16px;}
.feature-card .learn-more:hover{text-decoration:none;color:var(--forest);}

/* ---- Promotion de l'extension navigateur ---- */
/* Deux variantes d'un même composant : bandeau horizontal sur l'accueil,
   carte verticale dans la colonne du tableau de bord. Markup unique, voir
   templates/_extension_promo.html.twig. */
.extension-promo{
  background:var(--mint-soft);border:1px solid var(--line);border-radius:16px;
  padding:22px 24px;margin:28px 0;
}
/* ⚠️ `--forest-dark` et non `--ink`, comme les titres de l'assistant de
   création : les deux blocs reposent sur le même fond `--mint-soft`, et le vert
   profond de la charte les y rattache à l'application plutôt qu'au texte
   courant. 4,70:1 — au-dessus du seuil AA du texte normal (4,5), mais la marge
   est mince : ne pas éclaircir le vert ni assombrir le fond sans refaire le
   calcul.

   ⚠️ Posé sur la règle de BASE, donc sur les DEUX variantes — le bandeau de
   l'accueil comme la carte du tableau de bord et du profil. Elles partagent ce
   fond ; leur donner deux couleurs de titre serait une divergence de plus à
   tenir, pour un composant dont le projet veut un seul rendu à connaître. */
.extension-promo-title{font-size:1.15rem;line-height:1.3;margin:0 0 10px;color:var(--forest-dark);}
.extension-promo-list{list-style:none;margin:0;padding:0;display:grid;gap:6px;}
/* Coche verte devant chaque puce, posée en `::before` : un caractère décoratif
   dans le HTML serait lu par les lecteurs d'écran devant chaque ligne. */
.extension-promo-list li{
  position:relative;padding-left:22px;font-size:.9rem;color:var(--ink-soft);
}
.extension-promo-list li::before{
  content:"";position:absolute;left:2px;top:.45em;width:9px;height:5px;
  border-left:2px solid var(--forest);border-bottom:2px solid var(--forest);
  transform:rotate(-45deg);
}
.extension-promo-stores{display:flex;align-items:center;gap:10px;}
/* ⚠️ Les logos de navigateur ne suivent PAS `currentColor`, contrairement au
   jeu d'icônes maison : ce sont des marques déposées, voir le partiel. */
.browser-logo{display:block;}
.extension-promo-cta{white-space:nowrap;}
.extension-promo-note{margin:0;font-size:.8rem;color:var(--ink-soft);}

/* Bandeau (accueil) : icône | texte | actions, sur une rangée. */
.extension-promo--banner{display:flex;align-items:center;gap:24px;}
.extension-promo--banner .extension-promo-body{flex:1;min-width:0;}
.extension-promo--banner .extension-promo-actions{
  display:flex;flex-direction:column;align-items:center;gap:10px;flex-shrink:0;
}

/* Carte (tableau de bord ET profil) : tout empilé, aligné à gauche.

   ⚠️ CES RÈGLES VALENT AUSSI POUR LE PROFIL, qui rend la même carte — c'est
   voulu, il n'y a qu'un rendu à connaître (voir le partiel). Resserrer
   l'interlignage et le rembourrage l'y resserre donc aussi.

   🔴 `justify-content:space-between` + `height:100%` : la carte s'étire à la
   hauteur de sa voisine sur la ligne du tableau de bord, et son contenu se
   RÉPARTIT au lieu de se tasser en haut — le bouton d'installation et la
   mention de gratuité restent ancrés en bas. Sans le second, la carte
   garderait sa hauteur naturelle et laisserait un vide sous elle ; sans le
   premier, elle s'étirerait avec tout son contenu collé en haut.

   ⚠️ `height:100%` est SANS EFFET sur le profil, et c'est ce qui rend la
   règle sûre là-bas : la carte y a un parent sans hauteur définie, la valeur
   se résout donc en `auto`. */
.extension-promo--card{
  display:flex;flex-direction:column;justify-content:space-between;gap:14px;
  margin:0;padding:15px;height:100%;
}
.extension-promo--card .extension-promo-title{font-size:1.05rem;line-height:1;}
.extension-promo--card .extension-promo-list li{line-height:1;}
/* ⚠️ La coche se recale avec l'interlignage : posée à `.45em` du haut, elle
   était centrée sur une ligne de 1.5em ; sur une ligne de 1em elle tomberait
   sous le texte. Les trois puces tiennent chacune sur une ligne — l'interligne
   ne resserre donc rien, il ne fait que réduire la hauteur de leur boîte. */
.extension-promo--card .extension-promo-list li::before{top:.28em;}
.extension-promo--card .extension-promo-actions{display:grid;gap:10px;justify-items:center;}
.extension-promo--card .extension-promo-cta{width:100%;}

/* Tableau de bord : une LIGNE DU HAUT (« Mes envies » + promotion de
   l'extension), puis les sections de statut sur toute la largeur.

   🔴 LA COLONNE LATÉRALE S'ÉTENDAIT AUPARAVANT SUR TOUTE LA HAUTEUR, si bien
   que « Prête », « Brouillon » et « Archivées » étaient elles aussi amputées
   de ses 280px. C'est le vrai gain de ce découpage, et il ne se lisait pas
   depuis l'écran : on voyait une colonne latérale, pas trois sections
   rétrécies. Ce sont pourtant elles qu'on vient consulter.

   ⚠️ UNE colonne par défaut, deux seulement avec `--split`. L'inverse a été
   essayé sur l'ancienne mise en page — deux colonnes en permanence, la
   seconde en `auto` censée se réduire à zéro quand rien ne l'occupe — et ça ne
   marche pas : la largeur fixe de `.dashboard-side` et la gouttière de la
   grille laissaient le contenu tassé à gauche d'un vide. Le gabarit ne pose le
   modificateur que s'il a réellement DEUX blocs à placer, et le cas se produit
   vraiment (compte antérieur à la liste système, extension non publiée). */
/* 🔴 `stretch` SUR LA GRILLE, MAIS `start` SUR LA SECTION — et c'est cette
   paire qui fait tout le travail.

   `stretch` seul étirerait AUSSI la section « Mes envies » le jour où la carte
   de promotion serait la plus haute (traduction plus verbeuse, troisième
   puce plus longue). Sa carte verte, elle, garde `min-height:210px` : on
   obtiendrait un blanc sous elle, à l'intérieur du cadre bordé.
   ⚠️ Et la faire grandir avec `flex:1` n'est PAS une option — la vague a ses
   ordonnées en pixels, elle se décalerait. Voir `.permanent-card-media`.

   Avec `align-self:start` sur la section, la hauteur de la ligne reste dictée
   par le plus haut des deux, la section garde sa hauteur naturelle, et seul le
   panneau s'étire. Les deux cas sont couverts sans jamais toucher à la vague. */
.dashboard-top{display:grid;grid-template-columns:minmax(0,1fr);gap:24px;align-items:stretch;}
.dashboard-top--split{grid-template-columns:minmax(0,1fr) 320px;}
/* 🔴 LA CARTE S'ÉTIRE À LA HAUTEUR DE LA LIGNE — elle portait `align-self:start`
   et s'arrêtait donc plus haut que le panneau de l'extension, les deux bas ne
   s'alignant jamais.

   ⚠️ CE `start` N'ÉTAIT PAS UN CAPRICE : il protégeait la vague, dont les
   ordonnées étaient en pixels et se seraient décalées. C'est la VAGUE qui a été
   corrigée (voir `.permanent-card-media`), ce qui a rendu la contrainte
   caduque — on ne l'a pas simplement levée. */
.dashboard-top > .permanent-card{align-self:stretch;}
.dashboard-main{min-width:0;}

/* ⚠️ 320px et non un pourcentage. Une proportion — 60/40, 65/35 — donnerait au
   panneau 380 à 420px sur un écran large, alors que la carte de l'extension est
   dessinée pour une colonne étroite : ses deux boutons de boutique sont en
   `width:100%` et s'étireraient. La carte « Mes envies » prend donc tout le
   reste, soit environ 700px sur 1120 — un rapport de l'ordre de 68/32.

   ⚠️ `width` + `justify-self:end` plutôt que de compter sur la piste de
   grille : sans eux, le panneau SEUL (compte sans « Mes envies ») s'étirerait
   sur toute la largeur dans la grille à une colonne.

   ⚠️ `position:sticky` A ÉTÉ RETIRÉ : il n'avait de sens qu'en face d'une
   colonne haute qu'on faisait défiler. Sur une ligne unique, il ne peut rien
   coller.

   🔴 AUCUNE MARGE HAUTE — il y en avait une de 24px, et elle est devenue un
   défaut. Elle alignait le sommet du panneau sur celui de la SECTION BORDÉE
   qui entourait « Mes envies », dont l'étiquette débordait par le haut. Cette
   section a été retirée : la marge décalait donc le panneau vers le bas, et
   comme la carte voisine s'étire à la hauteur de la LIGNE — marge comprise —
   elle se retrouvait 24px plus haute que lui, et commençait 24px plus haut.

   ⚠️ Le symptôme trompe : on voit une carte trop haute, alors que c'est le
   panneau qui est trop bas. Défaut signalé à l'usage. */
.dashboard-side{width:320px;justify-self:end;}

/* 🔴 LA LIGNE REPASSE À UNE COLONNE SOUS 900px, ET CE N'EST PAS QU'UNE
   QUESTION D'ESPACE. La vague de `.permanent-card` est découpée avec des
   ordonnées en PIXELS (`173px`) : elle reste juste tant que la hauteur de la
   carte est portée par `min-height:210px`, et se décale dès que le texte la
   dépasse. Or entre 760 et ~850px de fenêtre, la carte tombait à ~370px de
   large en restant côte à côte avec le panneau — l'accroche y passait à quatre
   lignes et poussait la carte à 220px, décalant le tracé d'une dizaine de
   pixels.

   ⚠️ Vérifié par le calcul, pas à l'œil : à 1120, 1024 et 900px la hauteur
   reste sous `min-height`. La bande à problème était bien la seule.

   ⚠️ En dessous de 760px, `.dashboard-side` disparaît (bloc promotionnel
   masqué en mobile) : cette règle ne couvre donc que l'intervalle où le
   panneau est encore là mais la place manque. */
@media (max-width:900px){
  .dashboard-top--split{grid-template-columns:minmax(0,1fr);}
}

/* ---- Barre de retour fixée ---- */
/* ⚠️ `z-index:20` : au-dessus des titres de section, qui sont eux-mêmes
   `sticky top:0 z-index:5` (voir .list-section > h2). Sans ça, la barre
   passerait DERRIÈRE eux au défilement. En dessous du menu déroulant du
   sélecteur de langue (z-index:30) et des modales (1000), qui doivent rester
   au-dessus de tout. */
.sticky-back{
  position:sticky;top:0;z-index:20;
  /* Fond OPAQUE : le contenu défile dessous, un fond translucide rendrait le
     libellé illisible dès qu'une image ou un titre passe derrière. */
  background:var(--beige);border-bottom:1px solid var(--line);
}
.sticky-back a{
  /* Cliquable sur toute la largeur, pas seulement sur le texte. */
  display:flex;align-items:center;gap:10px;
  padding:11px 24px;font-weight:700;font-size:.92rem;color:var(--ink);
}
.sticky-back a:hover{text-decoration:none;color:var(--forest-dark);}
.sticky-back-arrow{font-size:1.1rem;line-height:1;}

/* ⚠️ Décale les titres de section collants pour qu'ils s'arrêtent SOUS la
   barre au lieu de disparaître dessous. Le combinateur `~` fait que la règle
   ne s'applique QUE sur les pages qui incluent réellement la barre : rien à
   activer à la main, rien à désactiver ailleurs. */
.sticky-back ~ main .list-section > h2,
.sticky-back ~ main .archived-toggle > summary{top:45px;}

/* ---- Catalogue d'idées cadeaux ---- */
/* Grille de catégories. Tuiles carrées, teinte douce tournant sur quatre
   valeurs — toutes tirées de la palette existante (--berry-soft, --gold-soft,
   --beige-dark et un vert dérivé), jamais de couleur inventée pour l'occasion.
   La rotation se fait sur l'index d'affichage : stable d'une visite à l'autre,
   et rien à stocker en base. */
.category-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:14px;margin-bottom:8px;
}
.cat-tile{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;
  padding:20px 14px;background:#fff;border:1px solid var(--line);border-radius:14px;
  text-align:center;color:var(--ink);transition:.15s ease;
}
.cat-tile:hover{text-decoration:none;transform:translateY(-2px);border-color:var(--ink-soft);}

/* ── ASSISTANT DE CRÉATION (tableau de bord, affiché en permanence) ──────────
   Deux colonnes : l'accroche à gauche, les cinq occasions à droite.

   🔴 LES CINQ CARTES N'Y TENAIENT PAS AU PREMIER JET — la quatrième passait, la
   cinquième repartait seule à la ligne. Ce n'est pas la grille qui était en
   cause mais le RATIO des colonnes : à `1fr / 1.35fr`, la droite n'offrait que
   564px là où il en fallait 642. L'accroche est donc resserrée à ~292px.

   ⚠️ Le chiffre ne se devine pas à l'écran : il vient du libellé le plus long,
   « Anniversaire », qui est INSÉCABLE et impose donc à lui seul la largeur
   minimale d'une carte (voir `.occasion-label`).

   ⚠️ Il ne s'affiche QUE tant qu'aucune liste n'existe (voir la condition du
   gabarit). Permanent, il repousserait vers le bas les sections de statut,
   c'est-à-dire précisément ce qu'on vient consulter.

   ⚠️ Les pastilles réutilisent `.cat-tile-media` et la rotation de teintes des
   rubriques : cinq occasions pour six teintes, aucune ne se répète et rien de
   nouveau n'est inventé. */
.creation-assistant{
  display:grid;grid-template-columns:minmax(0,.42fr) minmax(0,1fr);gap:28px;
  align-items:center;
  /* ⚠️ Marge HAUTE explicite : `.dashboard-top` est une grille et n'en porte
     aucune, si bien que ce panneau venait se coller sous la carte
     « Mes envies ». */
  margin:28px 0 24px;padding:26px 28px;
  background:var(--mint-soft);border:1px solid var(--line);border-radius:var(--radius-lg);
}
/* ⚠️ Les deux titres sont en `--forest-dark` et non en `--ink` : sur le fond
   menté du bloc, le vert profond de la charte les rattache à l'application
   plutôt qu'au texte courant qui les suit. Contraste 4,70:1 sur `--mint-soft`,
   au-dessus du seuil AA du texte normal (4,5) — la marge est mince, ne pas
   éclaircir le vert ni assombrir le fond sans repasser par le calcul. */
/* ⚠️ `clamp()` DANS le sélecteur de base, jamais une surcharge en `@media` :
   la colonne d'accroche est étroite par construction, et une media query
   devrait être placée APRÈS cette ligne pour l'emporter — elles n'ajoutent
   aucune spécificité. Même règle que le titre du hero. */
.creation-assistant-intro h2{margin:0 0 8px;font-size:clamp(1.25rem, 1rem + .9vw, 1.5rem);line-height:1.25;color:var(--forest-dark);}
.creation-assistant-intro p{margin:0;color:var(--ink-soft);}
.creation-assistant-choices h3{margin:0 0 14px;font-size:1rem;color:var(--forest-dark);}

/* 🔴 UN NOMBRE DE COLONNES EXPLICITE, ET NON `auto-fit`.

   C'est tout l'objet de la correction : `auto-fit` case autant de cartes qu'il
   peut et renvoie le reste à la ligne — donc une orpheline dès que la largeur
   disponible tombe entre quatre et cinq cartes. Le nombre étant fixé, la seule
   chose qui varie est la largeur des cartes, et jamais leur agencement.

   ⚠️ Il vit donc à TROIS endroits (5 / 3 / 2), et la contrepartie est qu'une
   sixième occasion ajoutée un jour n'apparaîtra PAS toute seule : il faudra
   passer les trois valeurs en revue. `ListOccasion::all()` reste la source du
   nombre réel — c'est elle qu'il faut lire avant de toucher à ces lignes. */
.occasion-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;}
.occasion-card{
  display:flex;flex-direction:column;align-items:center;gap:10px;
  padding:18px 12px;text-align:center;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  color:var(--ink);text-decoration:none;
  transition:border-color .15s, transform .15s;
}
.occasion-card:hover{border-color:var(--forest);transform:translateY(-2px);text-decoration:none;}
/* 🔴 C'EST LE LIBELLÉ LE PLUS LONG QUI DICTE LA LARGEUR D'UNE CARTE, et il est
   INSÉCABLE : « Anniversaire » ne peut pas se replier. À .95rem il réclame
   119px par carte, donc 642px de colonne — la disposition ne tenait alors qu'au
   delà de 1080px de fenêtre. À .88rem il descend à 112px, ce qui la fait tenir
   jusqu'à 1000px, seuil où le bloc s'empile.

   ⚠️ `overflow-wrap` est un FILET, pas la mise en page : ces largeurs sont
   calculées sur une approximation des métriques de la police. Si elle se
   trompe — ou si une future langue apporte un mot plus long — le mot se coupe
   au lieu de déborder de sa carte. Un débordement, lui, ne se rattrape pas. */
.occasion-label{font-weight:600;font-size:.88rem;overflow-wrap:break-word;}
/* La flèche reste un repère discret : c'est la carte entière qui est cliquable,
   pas elle. */
.occasion-go{display:flex;color:var(--ink-soft);}
.occasion-go svg{width:18px;height:18px;}

/* Sous 700px, cinq cartes tomberaient à ~110px : on passe à trois, et on y
   reste jusqu'aux plus petits écrans.

   ⚠️ Trois et non QUATRE : à cinq occasions, quatre colonnes marooneraient une
   carte au bout d'une rangée large et vide. 3+2 se lit comme un choix, 4+1
   comme un accident. */
/* 🔴 LE BLOC S'EMPILE SOUS 1000px, ET CE SEUIL EST CALCULÉ, PAS CHOISI.
   C'est la largeur en dessous de laquelle la colonne de droite ne peut plus
   offrir 112px par carte, c'est-à-dire la place d'« Anniversaire ». En dessous,
   l'accroche passe au-dessus et les cinq cartes prennent toute la largeur —
   elles y gagnent même en confort. */
@media (max-width:1000px){
  .creation-assistant{grid-template-columns:minmax(0,1fr);gap:22px;}
}
@media (max-width:700px){
  .occasion-grid{grid-template-columns:repeat(3,minmax(0,1fr));}
}
/* 🔴 TROIS COLONNES JUSQU'EN BAS — il y a eu une règle à DEUX ici, elle a été
   retirée. Deux colonnes donnaient 2+2+1 et surtout des cartes deux fois plus
   larges que hautes, alors que la place manque en hauteur sur un téléphone,
   jamais en largeur. Trois tiennent très bien : ce qui les en empêchait, ce
   n'était pas la largeur d'écran mais le PADDING des cartes.

   ⚠️ Ce qui borne réellement, c'est encore « Anniversaire », insécable. À
   .8rem il réclame ~80px et la carte en offre 87 dès 375px de fenêtre — donc
   tous les téléphones courants le gardent sur une ligne. Seul un écran de
   360px passe en dessous.

   ⚠️ `hyphens:auto` pour ce cas-là : le mot se coupe alors PROPREMENT, avec un
   trait d'union, au lieu d'être tranché n'importe où par `overflow-wrap`. Il
   s'appuie sur `<html lang>`, qui suit la locale de la requête depuis qu'il a
   cessé d'être écrit « fr » en dur. */
@media (max-width:520px){
  .creation-assistant{padding:20px 14px;}
  .occasion-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;}
  .occasion-card{padding:12px 6px;gap:8px;}
  .occasion-label{font-size:.8rem;hyphens:auto;}
  /* ⚠️ La pastille rétrécit au lieu de disparaître : c'est elle qui distingue
     les cartes d'un coup d'œil, le libellé demande d'être lu. */
  .occasion-card .cat-tile-media{width:48px;height:48px;border-radius:12px;}
  .occasion-card .cat-tile-media svg{width:22px;height:22px;}
}


.cat-tile-media{
  display:flex;align-items:center;justify-content:center;
  width:64px;height:64px;border-radius:16px;flex:none;overflow:hidden;
}
/* Une vignette téléversée n'est pas forcément carrée : `cover` recadre au
   centre plutôt que de déformer. */
.cat-tile-media img{width:100%;height:100%;object-fit:cover;}

/* ⚠️ Vignette SVG inlinée. C'est TOUT l'intérêt de l'inliner : un SVG chargé en
   `<img>` serait hors d'atteinte d'ici, et garderait la taille et la couleur de
   son fichier d'origine. La couleur vient de `currentColor`, posé sur la
   racine du SVG par SvgSanitizer — et non par une règle `fill:` ici, qui
   l'emporterait sur `fill="none"` et transformerait une icône « contour » en
   aplat plein. */
.tile-svg{display:flex;align-items:center;justify-content:center;width:56%;height:56%;}
.tile-svg svg{width:100%;height:100%;display:block;}
.guide-row .tile-svg,.cat-tile .tile-svg{width:60%;height:60%;}
.cat-tile-media svg{width:28px;height:28px;}
.cat-tile-title{font-weight:700;font-size:.92rem;line-height:1.3;}

/* 🔴 SIX TEINTES, ET L'ORDRE N'EST PAS ARBITRAIRE : rosé, ambre, vert, bleu,
   violet, neutre. La rotation suit le rang d'affichage (`loop.index0 % 6` dans
   les deux gabarits) ; deux tuiles voisines doivent donc se distinguer, et le
   ton neutre est placé en dernier plutôt qu'au milieu, où il aurait coupé la
   progression chaud → froid.

   ⚠️ LE MODULO DES GABARITS ET LE NOMBRE DE RÈGLES ICI VONT ENSEMBLE. Un
   `% 6` avec cinq règles rendrait des tuiles SANS pastille colorée, et un
   `% 4` avec six règles laisserait deux teintes inutilisées — dans les deux
   cas sans la moindre erreur. Les deux gabarits (catalogue et guides)
   partagent ces classes : les trois endroits bougent ensemble.

   ⚠️ Une seule teinte a été inventée pour l'occasion, le violet ; le bleu est
   `--organizer`, déjà en palette. Ne pas en ajouter d'autres sans repasser par
   le calcul de contraste — ces pastilles portent une icône, le seuil est celui
   du non-texte (3:1). */
.cat-tile--0 .cat-tile-media{background:var(--berry-soft);color:var(--berry-dark);}
.cat-tile--1 .cat-tile-media{background:var(--gold-soft);color:#9A6612;}
.cat-tile--2 .cat-tile-media{background:#E4F3EC;color:var(--forest-dark);}
.cat-tile--3 .cat-tile-media{background:var(--organizer-soft);color:var(--organizer);}
.cat-tile--4 .cat-tile-media{background:var(--violet-soft);color:var(--violet);}
.cat-tile--5 .cat-tile-media{background:var(--beige-dark);color:var(--ink-soft);}

/* Libellé réservé aux lecteurs d'écran.
   ⚠️ Ni `display:none` ni `visibility:hidden` : les deux retirent l'élément de
   l'arbre d'accessibilité, donc du texte qu'on cherche précisément à lui
   donner. On le sort de l'écran en gardant une boîte de 1px, seule technique
   qui le laisse lisible par une synthèse vocale. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;
}

/* Indicateur de chargement du panneau de résultats.

   ⚠️ On se greffe sur `aria-busy`, que le script pose déjà pour les lecteurs
   d'écran : un seul état, deux publics servis. Ajouter une classe `.is-loading`
   en parallèle aurait créé deux sources de vérité pour la même chose.

   ⚠️ Le `transition-delay` ne s'applique QUE dans le sens « devient occupé ».
   Une réponse rapide — le cas normal en local comme sur un petit catalogue —
   ne produit donc aucun clignotement : la requête est finie avant que
   l'atténuation ne commence. Au retour, pas de délai : le contenu redevient net
   immédiatement. Sans cette asymétrie, chaque frappe ferait palpiter la page.

   ⚠️ Une atténuation plutôt qu'un remplacement du panneau par un « chargement
   en cours » : les résultats précédents restent lisibles pendant la requête, ce
   qui est exactement ce qu'on veut en tapant — on affine, on ne repart pas de
   zéro. */
#results-panel{transition:opacity .12s ease;}
#results-panel[aria-busy="true"]{opacity:.45;transition-delay:.18s;}

/* Champ de recherche du catalogue. La saisie prend toute la place restante,
   le bouton garde la sienne — d'où `flex:1` sur l'un et rien sur l'autre. */
.idea-search{margin:0 0 28px;}
.idea-search input{
  /* Plus de bouton à côté : le champ occupe toute la largeur. */
  width:100%;padding:11px 14px;font:inherit;color:var(--ink);
  background:#fff;border:1px solid var(--line);border-radius:10px;
}
.idea-search input:focus-visible{outline:2px solid var(--gold);outline-offset:1px;}

/* Mention « constaté le … », en mode d'affichage daté uniquement.
   ⚠️ En BLOC sous le montant : en ligne, elle doublait la hauteur de la ligne
   de prix sur les cartes étroites et décalait les boutons d'une carte à
   l'autre — or `margin:auto 0 0` sur `.idea-card-price` existe précisément
   pour que les prix s'alignent d'une carte à l'autre. */
.idea-card-price-checked{display:block;font-size:.72rem;font-weight:500;color:var(--ink-soft);}

/* ---- Barre de filtres du catalogue -----------------------------------------
   Pastilles de tranches d'âge et de prix. Le markup vit dans
   gift_idea/_filter_bar.html.twig, DANS le fragment rejoué en AJAX : l'état
   actif est donc rendu par le serveur, jamais tenu en JavaScript. */
.filter-bar{display:flex;flex-direction:column;gap:10px;margin:0 0 20px;}
.filter-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;}
/* ⚠️ Largeur fixe pour que les rangées s'alignent : « Âge » et « Prix » n'ont
   pas la même longueur, et sans elle les pastilles démarrent en escalier.
   ⚠️ `flex:none` — sans lui, l'étiquette se comprime au lieu de tenir sa
   largeur dès que les pastilles remplissent la ligne. */
.filter-row-label{
  flex:none;width:58px;font-size:.78rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.04em;color:var(--ink-soft);
}
.filter-pill{
  display:inline-flex;align-items:center;gap:5px;
  padding:6px 13px;border-radius:999px;
  border:1px solid var(--line);background:var(--paper);
  font-size:.85rem;font-weight:600;color:var(--ink-soft);
}
.filter-pill:hover{border-color:var(--forest);color:var(--forest);text-decoration:none;}
/* ⚠️ L'état actif ne tient pas qu'à la COULEUR : la pastille porte en plus une
   croix (voir le gabarit), et `aria-pressed`. Une teinte seule laisserait sans
   réponse quiconque la distingue mal — et ne dirait pas qu'un second clic
   retire le filtre. */
.filter-pill--active{
  background:var(--forest-dark);border-color:var(--forest-dark);color:#fff;
}
.filter-pill--active:hover{background:var(--primary-dark);border-color:var(--primary-dark);color:#fff;}
.filter-pill svg{width:13px;height:13px;}
/* Retour à l'index en CONSERVANT la sélection — la seule sortie qui ne la
   défait pas. Rendue uniquement depuis une rubrique. */
.filter-scope{
  display:inline-flex;align-items:center;gap:6px;align-self:flex-start;
  font-size:.82rem;font-weight:600;color:var(--berry-dark);
}
.filter-scope svg{width:14px;height:14px;}
/* La tuile de la rubrique CONSULTÉE. Elle n'avait aucune raison d'exister tant
   que la grille ne s'affichait que sur l'index, où l'on n'est dans aucune
   rubrique — elle est née avec la convergence des deux écrans.
   ⚠️ Un ANNEAU, pas un fond : les tuiles portent déjà six teintes de palette,
   en repeindre une la ferait passer pour une septième catégorie. */
.cat-tile--active{
  border-color:var(--forest-dark);
  box-shadow:0 0 0 2px var(--forest-dark) inset;
}
@media (max-width:520px){
  /* L'étiquette passe au-dessus : à 58px de gouttière, il ne reste pas la
     place de deux pastilles par ligne sur un téléphone. */
  .filter-row{align-items:flex-start;}
  .filter-row-label{width:100%;}
}

/* Cartes d'idées cadeaux — composant PARTAGÉ par l'index, la page d'une
   rubrique et la sélection d'un guide. Le markup vit dans
   gift_idea/_idea_card.html.twig ; ces règles vont par paires avec lui.
   ⚠️ Pas de note ni d'étoiles : aucun champ de notation n'existe sur
   PublicGiftIdea (voir CLAUDE.md). */
.idea-grid{
  /* ⚠️ 200 et non 230 : cinq colonnes de 202px sur 1120 au lieu de quatre de
     256. Plus de produits d'un coup d'œil, chacun plus petit — arbitrage
     assumé.
     🔴 CETTE VALEUR PILOTE LE SEUIL DU MODE COMPACT, plus bas : deux colonnes
     tiennent dès 464px de fenêtre, il doit donc rester SOUS ce chiffre. La
     baisser encore sans l'y suivre mettrait les deux boutons côte à côte dans
     une carte deux fois trop étroite. `IdeaCardTest` compare les deux. */
  display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;
  /* `stretch` : toutes les cartes d'une rangée prennent la hauteur de la plus
     haute. C'est la condition pour que `margin-top:auto` puisse ensuite
     plaquer les boutons en bas de CHACUNE. */
  align-items:stretch;
}
.idea-card{
  display:flex;flex-direction:column;padding:14px;
  background:#fff;border:1px solid var(--line);border-radius:14px;
  color:var(--ink);transition:.15s ease;height:100%;
}
.idea-card:hover{border-color:var(--ink-soft);transform:translateY(-2px);}

/* ⚠️ Hauteur FIXE et `contain`, jamais `cover` : les photos produit ont des
   proportions très variables et `cover` en rognerait le sujet. Le fond clair
   comble ce que l'image ne remplit pas. */
.idea-card-media{
  display:flex;align-items:center;justify-content:center;
  height:180px;flex:none;margin-bottom:12px;
  /* ⚠️ Fond TRANSPARENT, pas le beige de la charte : les photos produit sont
     très majoritairement détourées sur blanc, et un fond crème derrière faisait
     ressortir un rectangle blanc au milieu de la carte. Transparent plutôt que
     `#fff` en dur : la zone suit alors le fond de la carte, quel qu'il devienne. */
  background:transparent;border-radius:10px;overflow:hidden;
}
.idea-card-media img{max-width:100%;max-height:100%;object-fit:contain;}
.idea-card-media svg{width:32px;height:32px;color:var(--ink-soft);}

/* `flex:1` : le corps absorbe l'espace disponible, ce qui pousse la rangée de
   boutons vers le bas quelle que soit la longueur du texte. */
.idea-card-body{display:flex;flex-direction:column;gap:4px;flex:1;}

/* ⚠️ La troncature à 2 lignes a besoin des QUATRE propriétés ensemble :
   `-webkit-line-clamp` seul ne fait rien sans `display:-webkit-box`,
   `-webkit-box-orient` et `overflow:hidden`. `line-clamp` (sans préfixe) est
   ajouté pour les moteurs qui l'implémentent désormais sans. */
.idea-card-title,.idea-card-desc{
  display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;
  -webkit-line-clamp:2;line-clamp:2;
}
.idea-card-title{margin:0;font-size:.98rem;line-height:1.35;font-weight:800;}
/* ⚠️ `.84rem` et non `.88` : le gain n'est pas la hauteur (4px) mais ~12 % de
   caractères par ligne, donc plus de texte visible dans les deux lignes de la
   troncature. ⚠️ On s'arrête là — `--ink-soft` est à 4,81:1 sur blanc, tout
   juste au-dessus du seuil AA du texte normal (4,5). Descendre plus bas en
   taille rendrait ce gris inconfortable. */
.idea-card-desc{margin:4px 0 0;font-size:.84rem;line-height:1.5;color:var(--ink-soft);}

/* Lien « en savoir plus » : un bouton pour l'accessibilité et le clavier, mais
   présenté comme un lien — c'est une action de lecture, pas une action sur le
   produit, et il ne doit pas concurrencer les deux boutons du bas. */
.idea-card-more{
  align-self:flex-start;margin-top:2px;padding:0;border:none;background:none;
  color:var(--berry-dark);font:inherit;font-size:.85rem;font-weight:700;cursor:pointer;
}
.idea-card-more:hover{text-decoration:underline;}

/* 🔴 `margin-top:auto` : c'est LUI qui pose le prix au BAS du corps de carte,
   et donc sur la même ligne d'une carte à l'autre. Sans lui, il suit
   immédiatement la description — dont la hauteur varie selon qu'elle tient sur
   une ou deux lignes et qu'un lien « en savoir plus » l'accompagne ou non. Le
   prix faisait alors du yo-yo d'une carte à l'autre, ce qui a été signalé à
   l'usage : sur une rangée, l'œil ne peut plus comparer les montants.

   ⚠️ IL DÉPEND de `.idea-card-body{display:flex;flex-direction:column}` juste
   au-dessus. « Simplifier » ce corps en bloc ordinaire ferait retomber le prix
   sous la description, sans rien casser d'autre — même mécanique que le
   `margin-top:auto` des boutons, un cran plus bas.

   ⚠️ Le prix est FACULTATIF : sans lui, rien ne pousse, le corps garde son
   espace libre en bas et les boutons restent alignés par leur propre règle.

   ⚠️ L'espacement passe en `padding-top` et non plus en `margin-top` : ce
   dernier vaut désormais `auto`, qui absorbe tout l'espace libre. Sur une carte
   dont le contenu remplit exactement le corps, il n'en resterait aucun et le
   prix collerait à la description. */
.idea-card-price{margin:auto 0 0;padding-top:10px;font-weight:800;font-size:1.05rem;color:var(--forest-dark);}

/* ⚠️ `margin-top:auto` : c'est LUI qui aligne les boutons de toutes les cartes
   sur la même ligne horizontale, qu'une carte porte ou non un lien « en savoir
   plus ». Sans lui, chaque rangée flotterait à la fin de son propre texte. */
.idea-card-actions{
  display:flex;flex-direction:column;gap:8px;margin-top:auto;padding-top:14px;
}
.idea-card-actions .btn{width:100%;}

/* 🔴 CARTE RESSERRÉE SOUS 520px, où la grille est en UNE colonne.
   Défaut signalé à l'usage : sur un téléphone, une carte occupait ~490px de
   haut, soit tout l'écran — on ne voyait qu'un produit à la fois et il fallait
   faire défiler pour chacun.

   ⚠️ LE SEUIL EST CELUI OÙ LA GRILLE PASSE À UNE COLONNE, pas un « mobile »
   choisi au hasard. `minmax(200px,1fr)` demande 464px de fenêtre pour tenir
   deux colonnes : en dessous il n'y en a qu'une, la carte fait toute la
   largeur, et c'est là qu'elle est la plus haute. ⚠️ Conséquence
   contre-intuitive à connaître — la carte est PLUS LARGE sur un téléphone de
   360px (une colonne de 312px) que sur une fenêtre de 640px (deux colonnes de
   288px). Étendre ces règles au-delà de 520px les appliquerait à des cartes
   plus étroites, où les deux boutons ne tiendraient plus côte à côte.

   Deux postes pesaient la moitié de la hauteur : l'image (180px) et les deux
   boutons empilés (96px). On gagne ~100px, soit un cinquième, SANS retirer la
   moindre information — la description et le prix restent.

   ⚠️ Voir DEUX produits à l'écran demanderait d'en RETIRER un, pas d'en
   rétrécir : la description est le seul candidat, et c'est un arbitrage
   produit. Assumé de ne pas le faire.

   ⚠️ `flex:1 1 0` — base ZÉRO et non `auto` : avec `auto`, chaque bouton part
   de la largeur de son texte et « Ajouter à une liste » ressortirait bien plus
   large que « Voir le produit ». Base zéro donne deux moitiés exactes.
   `min-width:0` autorise la réduction sous la largeur du contenu.

   ⚠️ Les icônes disparaissent, les libellés restent — même arbitrage que la
   rangée d'actions d'un cadeau sous 400px : entre une icône décorative et le
   mot qui porte le sens, on garde le mot. */
@media (max-width:460px){
  .idea-card-media{height:130px;}
  .idea-card-actions{flex-direction:row;}
  .idea-card-actions .btn{
    flex:1 1 0;min-width:0;width:auto;
    font-size:.78rem;padding-left:8px;padding-right:8px;
  }
  .idea-card-actions .btn .icon{display:none;}
}

/* Sous-titre de l'écran « Ajouter à une liste » : le nom du produit, en gras,
   dans une phrase.

   🔴 PAS DE TRONCATURE, ET C'EST UNE DÉCISION — un `-webkit-line-clamp:2` a
   été posé ici puis RETIRÉ. Le titre n'est pas seul dans son bloc : il est au
   MILIEU d'une phrase qui se termine par « à la liste : ». Tronquer le
   paragraphe coupait donc la fin de la phrase avec lui, et l'écran ne disait
   plus ce qu'on était en train de faire.

   ⚠️ Ne pas non plus tronquer le seul `<strong>` : le rendre `-webkit-box` en
   ferait un bloc, et la phrase se briserait en trois morceaux empilés.

   ⚠️ CONSÉQUENCE ASSUMÉE : un titre de 180 caractères — la limite que l'API de
   capture applique aux titres Amazon — occupe cinq lignes sur un téléphone.
   C'est le prix d'une phrase entière, et ça se règle en amont, à la saisie du
   nom interne, pas en CSS. */
.choose-list-lead{
  margin:0 0 16px;color:var(--ink-soft);
}
.choose-list-lead strong{color:var(--ink);}

/* Modale de description : réutilise .modal-overlay / .modal-panel du composant
   générique, on n'ajoute ici que la mise en forme de son contenu. */
#idea-modal .modal-panel img{
  display:block;max-width:100%;max-height:260px;margin:0 auto 16px;object-fit:contain;
}
#idea-modal .modal-panel p{white-space:pre-line;line-height:1.6;}

/* Guides d'achat. Une ligne par guide : vignette teintée, titre + accroche,
   flèche. Reprend la palette et les teintes de .cat-tile--n ci-dessus plutôt
   que d'en introduire de nouvelles. */
/* ⚠️ DEUX colonnes en bureau, pas trois, et le minimum de 420px le décide
   seul : le conteneur fait 1120px, donc trois cartes n'y tiennent pas
   (3×420 > 1120) mais deux oui. Pas de media query — la grille retombe
   d'elle-même à une colonne dès que la largeur ne suffit plus, ce qui couvre
   aussi bien le mobile que la fenêtre à moitié réduite.

   Trois colonnes donneraient ~360px par carte : la carte étant HORIZONTALE
   (icône, puis titre et accroche à côté), l'accroche passerait sur deux ou
   trois lignes et l'icône se retrouverait à l'étroit. C'est la forme de la
   carte qui impose le nombre de colonnes, pas l'inverse. */
/* ⚠️ `min(420px,100%)` et NON `420px` seul. Le minimum d'un `minmax()` est une
   valeur DURE : `auto-fit` sait retomber à une colonne, il ne sait pas la
   rétrécir en dessous de ce minimum. Sur un téléphone de 412px, `.wrap` en
   retire 48px de marges — la colonne réclamait 420px pour 364px disponibles et
   dépassait de la fenêtre, emportant la flèche hors de l'écran. Le `min()`
   plafonne le minimum à la largeur réellement offerte.

   ⚠️ Piège à reconnaître : tout `minmax()` dont le minimum dépasse la largeur
   utile d'un mobile produit ce défilement horizontal. Les autres grilles du
   projet restent en dessous (140 à 260px), d'où ce correctif ici seulement. */
.guide-list{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));
  gap:12px;align-items:stretch;
}
.guide-row{
  /* `height:100%` : sans lui, deux cartes voisines dont les accroches
     n'occupent pas le même nombre de lignes n'auraient pas la même hauteur. */
  display:flex;align-items:center;gap:16px;padding:16px;height:100%;
  background:#fff;border:1px solid var(--line);border-radius:14px;color:var(--ink);transition:.15s ease;
}
.guide-row:hover{text-decoration:none;transform:translateY(-2px);border-color:var(--ink-soft);}
.guide-row-body{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0;}
.guide-row-title{font-weight:800;font-size:1rem;}
.guide-row-teaser{font-size:.9rem;line-height:1.5;color:var(--ink-soft);}
.guide-row-arrow{flex:none;color:var(--forest-dark);display:flex;}
.guide-row-arrow svg{width:20px;height:20px;}

/* Corps d'un guide. ⚠️ Seules ces balises peuvent apparaître : le contenu est
   filtré par RichTextSanitizer, qui n'en laisse pas passer d'autres. */
.guide-content{line-height:1.7;}
/* ⚠️ `div` autant que `p` : Trix, l'éditeur du back-office, enveloppe chaque
   paragraphe dans un <div>. Sans cette règle, tout le texte se collait en un
   seul bloc sans respiration. */
.guide-content div{margin:0 0 14px;}
.guide-content div:last-child{margin-bottom:0;}
/* ⚠️ Le bouton « titre » de Trix ne produit qu'un h1. La page en a déjà un
   (le titre du guide) : celui-ci est donc ramené visuellement au rang d'un
   sous-titre, faute de pouvoir changer la balise au nettoyage. */
.guide-content h1{font-size:1.2rem;margin:24px 0 10px;}
.guide-content h2{font-size:1.2rem;margin:24px 0 10px;}
.guide-content h3{font-size:1.05rem;margin:20px 0 8px;}
.guide-content p{margin:0 0 14px;}
.guide-content ul,.guide-content ol{margin:0 0 14px;padding-left:22px;}
.guide-content li{margin-bottom:6px;}
.guide-content blockquote{
  margin:0 0 14px;padding:12px 16px;background:var(--beige);
  border-left:3px solid var(--line);border-radius:8px;color:var(--ink-soft);
}
.guide-content hr{border:none;border-top:1px solid var(--line);margin:24px 0;}
.guide-content pre{
  margin:0 0 14px;padding:12px 16px;background:var(--beige);border-radius:8px;
  overflow-x:auto;font-size:.88rem;
}

/* ---- FAQ ---- */
/* Renvoi vers /faq depuis l'accueil. Reprend l'aspect de .learn-more des
   cartes, qui est scopée à .feature-card et ne s'appliquait donc pas ici. */
.faq-teaser{margin:20px 0 0;text-align:center;}
.faq-teaser .learn-more{font-weight:700;font-size:.9rem;color:var(--forest-dark);display:inline-flex;align-items:center;gap:6px;}
.faq-teaser .learn-more svg{width:16px;height:16px;}
.faq-teaser .learn-more:hover{text-decoration:none;color:var(--forest);}

.faq-section{margin-bottom:32px;}
/* Questions fréquentes en bas de la page Secret Santa : le `.cta-banner` qui
   les précède n'a que 8px de marge basse, faite pour le pied de page. */
.faq-section.santa-faq{margin-top:40px;}
.faq-section > h2{font-size:1.15rem;margin:0 0 12px;padding-bottom:8px;border-bottom:1px solid var(--line);}
/* Accordéon 100% natif : <details>/<summary>, aucune ligne de JS. Le navigateur
   gère l'ouverture, le clavier et l'exposition aux lecteurs d'écran. */
.faq-item{background:#fff;border:1px solid var(--line);border-radius:4px;margin-bottom:8px;}
.faq-item > summary{
  cursor:pointer;padding:14px 16px;font-weight:700;font-size:.95rem;color:var(--ink);
  display:flex;align-items:center;justify-content:space-between;gap:12px;list-style:none;
}
/* Le marqueur par défaut diffère d'un navigateur à l'autre (triangle, flèche,
   position). On le retire des deux façons nécessaires — ::-webkit-details-marker
   pour les moteurs WebKit, `list-style:none` ci-dessus pour les autres — et on
   pose notre propre chevron, identique partout. */
.faq-item > summary::-webkit-details-marker{display:none;}
.faq-item > summary::after{
  content:"";flex:none;width:9px;height:9px;
  border-right:2px solid var(--ink-soft);border-bottom:2px solid var(--ink-soft);
  transform:rotate(45deg);transform-origin:center;transition:transform .15s ease;
}
.faq-item[open] > summary::after{transform:rotate(-135deg);}
.faq-item > summary:hover{background:var(--beige);}
/* ⚠️ Le focus doit rester visible : c'est le seul repère au clavier, et
   l'accordéon n'est utilisable que comme ça sans souris. */
.faq-item > summary:focus-visible{outline:2px solid var(--gold);outline-offset:-2px;}
.faq-item > p{margin:0;padding:0 16px 16px;color:var(--ink-soft);font-size:.95rem;line-height:1.6;}
.faq-footnote{margin-top:24px;}

/* ---- Steps ---- */
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin:44px 0;position:relative;}
.step{text-align:center;padding:0 16px;position:relative;}
.step-connector{position:absolute;top:36px;left:calc(50% + 50px);right:calc(-50% + 50px);height:0;border-top:2px dashed var(--line);z-index:0;}
.step:last-child .step-connector{display:none;}
.step-icon{
  position:relative;width:72px;height:72px;border-radius:50%;margin:0 auto 16px;
  display:flex;align-items:center;justify-content:center;z-index:1;
}
.step-icon svg{width:30px;height:30px;}
.step-icon.mint{background:var(--mint-soft);color:var(--forest);}
.step-icon.coral{background:var(--berry-soft);color:var(--berry);}
.step-icon.gold{background:var(--gold-soft);color:var(--gold);}
.step-icon.slate{background:var(--organizer-soft);color:var(--organizer);}
.step-badge{
  position:absolute;top:-2px;right:2px;width:24px;height:24px;border-radius:50%;
  color:#fff;font-size:.78rem;font-weight:800;display:flex;align-items:center;justify-content:center;
  border:3px solid var(--canvas);
}
.step-badge.mint{background:var(--forest);}
.step-badge.coral{background:var(--berry);}
.step-badge.gold{background:var(--gold);}
.step-badge.slate{background:var(--organizer);}
.step h3{font-size:1.02rem;}
.step p{font-size:.88rem;color:var(--ink-soft);}

/* ---- CTA banner ---- */
.cta-banner{
  background:linear-gradient(120deg, var(--mint-soft), var(--gold-soft));
  border-radius:var(--radius-lg);padding:36px 44px;margin:20px 0 8px;
  display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;
}
.cta-inner{display:flex;align-items:center;gap:22px;}
.cta-gift{width:84px;height:84px;flex-shrink:0;}
.cta-gift svg{width:100%;height:100%;display:block;}
.cta-banner h2{font-size:1.5rem;margin-bottom:.2em;}
.cta-banner p{margin:0;color:var(--ink-soft);}

@media (max-width:900px){
  .hero{grid-template-columns:1fr;}
  /* ⚠️ Le CONTENEUR est masqué, pas seulement l'image : `display:none` sur la
     seule `<img>` laisserait une ligne de grille vide et son `gap` de 48px —
     un blanc résiduel sous le texte, sans rien dedans. La colonne de gauche
     occupe ainsi naturellement toute la largeur.
     ⚠️ L'ancien `order:-1` disparaît avec elle : il remontait la carte de démo
     au-dessus du texte, ce qui n'a plus d'objet. */
  .hero-visual{display:none;}
  .feature-grid{grid-template-columns:repeat(2,1fr);}
  /* ⚠️ SOUS 900px, LE TITRE PASSE À CÔTÉ DE LA PASTILLE — comme le fait déjà
     `.explore-card` juste au-dessus sur la même page. La borne n'est pas
     choisie au hasard : c'est celle où la grille quitte ses 4 colonnes, donc
     exactement le moment où la tuile devient assez large pour ça.

     Le chiffre qui tranche : à 4 colonnes une tuile fait 253px, moins son
     padding de 28px et la pastille de 52px + 12 d'écart, il resterait 133px
     pour le titre — « Confidentialité, à votre façon » y tomberait sur trois
     lignes pendant qu'« Anti-doublons » tiendrait sur une, et la rangée
     afficherait quatre hauteurs de tête différentes. À 2 colonnes il reste
     406px, en une seule 470 : le problème disparaît.

     En mono-colonne, le gain est le vrai motif — la pastille seule sur sa
     ligne coûte 52px + 16 de marge PAR carte, soit ~270px sur les quatre,
     plus d'un demi-écran de téléphone pour ne rien montrer de plus.

     ⚠️ AUCUN CHANGEMENT DE MARKUP : la grille place la pastille et le `h3`
     sur la première ligne, tout le reste passe en pleine largeur en dessous.
     Un enfant ajouté plus tard (le lien « En savoir plus » qui dort en
     commentaire dans le gabarit) tombera donc naturellement sous la ligne de
     tête, sans règle supplémentaire. */
  .feature-card{display:grid;grid-template-columns:auto 1fr;column-gap:12px;row-gap:10px;}
  .feature-card > *{grid-column:1 / -1;margin:0;}
  .feature-card > .icon-badge{grid-column:1;grid-row:1;}
  .feature-card > h3{grid-column:2;grid-row:1;align-self:center;}
  .steps{grid-template-columns:repeat(2,1fr);row-gap:32px;}
  .step-connector{display:none;}
  .stats-bar{flex-direction:column;}
  .stat + .stat{border-left:none;border-top:1px solid var(--line);}
  .footer-top{grid-template-columns:1fr 1fr;}
}

@media (max-width:760px){
  .two-col{grid-template-columns:1fr;}
  /* ---- Header mobile : MÊME première ligne dans les deux états ----
     marque à gauche, bouton d'action + sélecteur de langue à droite. Le
     header connecté ajoute simplement une SECONDE ligne pour « Mes listes »
     et « Profil ». Cette symétrie est voulue : le repère visuel ne bouge pas
     entre visiteur et connecté.

     ⚠️ Ces réductions ne sont PAS cosmétiques — sans elles la ligne déborde
     sur un téléphone étroit (~360px). Ne pas les retirer sans remesurer. */
  .topbar-inner{padding:10px 16px;gap:8px;}
  .brand{font-size:1.05rem;gap:8px;}
  .brand-badge{width:36px;height:36px;border-radius:10px;}
  .brand-mark{width:28px;height:28px;}
  .nav{gap:8px;flex-wrap:nowrap;}
  .nav-login{padding:7px 12px !important;}

  /* Header CONNECTÉ : deux lignes.
     ⚠️ `display:contents` sur la nav — ses enfants deviennent des items flex
     de `.topbar-inner`, ce qui permet de les répartir sur deux lignes SANS
     sortir les liens du `<nav>` (ils doivent rester dans le repère de
     navigation). La nav garde donc son rôle sémantique tout en cessant
     d'être une boîte de mise en page. */
  .topbar-authenticated .topbar-inner{flex-wrap:wrap;}
  .topbar-authenticated .nav{display:contents;}
  /* Pousse « Déconnexion » + le sélecteur contre le bord droit, la marque
     restant à gauche. Une marge auto absorbe l'espace libre AVANT que
     `justify-content:space-between` n'ait quelque chose à distribuer — sans
     elle, les trois éléments de la ligne 1 s'écarteraient également et le
     bouton flotterait au milieu. */
  .topbar-authenticated .nav-login{margin-left:auto;}
  /* `flex-basis:100%` force le passage à la ligne suivante ; `order:1` la
     place APRÈS le bouton et le sélecteur, malgré sa position antérieure
     dans le DOM (voulue : ces liens restent les premiers pour la navigation
     au clavier et les lecteurs d'écran). */
  /* ⚠️ `flex-wrap` : les trois entrées ordinaires tiennent sur une ligne
     (285px des 312 d'un écran de 360), mais un ADMINISTRATEUR en a une
     quatrième. Sans ça elles se comprimeraient ou déborderaient ; avec, la
     rangée passe proprement à la ligne — et pour lui seul. */
  /* 🔴 `align-items:center` EST INDISPENSABLE DEPUIS QUE « PROFIL » EST UN
     AVATAR. Sans lui, la rangée retombe sur `stretch` : chaque entrée prend la
     hauteur de la plus grande — le disque de 32px et ses marges, soit 40px —
     et les libellés, qui n'en font que ~35, se collent en HAUT de leur boîte.
     Leur texte passait donc au-dessus du centre de l'avatar.

     ⚠️ Le défaut n'existait pas avant : trois libellés de même hauteur
     s'alignaient par accident, `stretch` ne se voyant pas. Et il ne se voit
     QU'EN MOBILE — en bureau, `.nav-links` est en `display:contents`, ses
     liens sont donc des enfants directs de `.nav`, qui centre déjà. Défaut
     signalé à l'usage, capture à l'appui. */
  .topbar-authenticated .nav-links{display:flex;align-items:center;flex-wrap:wrap;gap:14px;order:1;flex-basis:100%;}
  /* ⚠️ AUCUNE surcharge de `.lang-menu`, dans AUCUN des deux états, et c'est
     délibéré. Une règle `right:auto;left:0` a existé ici deux fois, chaque
     fois rendue nécessaire par un `flex-direction:column` qui collait la
     pastille au bord GAUCHE — un menu ancré à droite y débordait hors de
     l'écran. Les deux headers gardant maintenant leur pastille à DROITE de
     la première ligne, le `right:0` du desktop est le bon ancrage partout.
     La réintroduire recréerait le défaut en miroir. */
  .page-head{flex-direction:column;align-items:flex-start;}
  .gift-actions{align-items:flex-start;width:100%;}
  /* Boutons pleine largeur en mobile. `width:100%` sur le conteneur est
     indispensable : .page-head passe en colonne avec align-items:flex-start
     juste au-dessus, sans quoi .form-actions se réduirait à son contenu.
     `flex:1 1 auto` (plutôt que width:100% sur chaque bouton) laisse deux
     boutons courts se partager une même ligne, et étire tout seul celui qui
     se retrouve seul sur la sienne. */
  .form-actions{width:100%;}
  .form-actions .btn{flex:1 1 auto;}
  /* 🔴 LES DEUX ACTIONS DU TABLEAU DE BORD S'EMPILENT, ACTION PRINCIPALE EN
     HAUT. Côte à côte elles se partageaient une ligne — c'est ce que permet le
     `flex:1 1 auto` ci-dessus — et « Nouvelle liste », le geste qu'on vient
     faire, se retrouvait à droite et à moitié de largeur.

     ⚠️ `column-reverse` : le markup garde l'ordre habituel (secondaire puis
     principal, convention du bureau où le bouton plein est à droite), et c'est
     l'AFFICHAGE qui s'inverse. Même mécanisme et même raison que
     `.panel-actions` sous 520px — sur un écran étroit, l'action principale est
     celle que le pouce doit atteindre en premier.

     ⚠️ CONSÉQUENCE CONNUE, la même que pour `.panel-actions` : l'ordre de
     TABULATION suit le DOM et reste secondaire → principal, à rebours de ce
     qu'on voit. Sur deux boutons, c'est le compromis que le projet a déjà
     retenu ailleurs plutôt que de dupliquer le markup.

     ⚠️ DEUX EN-TÊTES, NOMMÉMENT : celui de la page (« Mes listes ») et celui de
     la section des cadeaux. Élargir à `.form-actions` empilerait les boutons de
     TOUS les formulaires du site — vérifié, `.page-head` et `.gifts-head` sont
     les seuls en-têtes qui portent des actions.

     ⚠️ La barre des cadeaux en compte TROIS : le markup y est en ordre bureau
     (Trier, Importer, Ajouter), et l'affichage mobile le retourne. */
  .page-head .form-actions,
  .gifts-head .form-actions{flex-direction:column-reverse;}
  /* Appels à l'action du hero, pleine largeur et empilés. ⚠️ `column` et non
     le `flex:1 1 auto` de .form-actions juste au-dessus : ces deux boutons-là
     ne doivent JAMAIS se partager une ligne. Ce sont les deux entrées
     principales du site (créer un compte / se connecter), pas deux boutons de
     formulaire — côte à côte sur un écran de téléphone, ils tomberaient à une
     cinquantaine de pixels chacun. Le libellé reste centré grâce au
     `justify-content:center` de .btn. */
  .hero-actions{flex-direction:column;}
  /* 🔴 Promotion de l'extension masquée en mobile — mais SEULEMENT le bandeau
     de l'accueil et la carte du tableau de bord (celle-ci via
     `.dashboard-side` juste en dessous). Ce sont des blocs PROMOTIONNELS
     posés devant quelqu'un qui ne demandait rien : sur un téléphone, ils
     invitent à faire quelque chose d'impossible sur l'appareil qu'on a en
     main.
     ⚠️ La carte du PROFIL, elle, reste visible : on y arrive volontairement,
     pour gérer ses jetons d'accès — qui ne servent qu'à l'extension. Y masquer
     l'endroit où la récupérer laisserait la question sans réponse, sur le seul
     écran où elle se pose vraiment. On peut très bien préparer depuis son
     téléphone une extension qu'on installera sur son ordinateur.
     ⚠️ `display:none` et non une détection serveur : c'est une décision
     d'affichage, et un test d'agent utilisateur se tromperait au premier
     navigateur inhabituel.
     ⚠️ Ne concerne QUE ces blocs : la question de FAQ correspondante reste du
     texte normal, lisible à toutes les tailles d'écran. */
  .extension-promo--banner{display:none;}
  /* La colonne latérale disparaît avec elle : la grille repasse à une colonne
     plutôt que de réserver une gouttière pour un conteneur vide. */
  .dashboard-top--split{grid-template-columns:1fr;}
  .dashboard-side{display:none;}
  .feature-grid{grid-template-columns:1fr;}
  .steps{grid-template-columns:1fr;}
  .footer-top{grid-template-columns:1fr;}
}

/* ---- Vue liste en 2 colonnes : cadeaux (grande) + config/discussion (petite) ---- */
/* ---- Bannière de couverture d'une liste (wishlist/view.html.twig) ---- */
/* Pleine largeur : émise par le bloc Twig `hero` de base.html.twig, donc HORS
   de <main class="wrap"> qui est plafonné à 1120px. N'existe que si la liste a
   une image — sans image aucun élément n'est produit, donc aucune hauteur
   réservée et aucune régression sur l'ancien rendu.
   `background-size:cover` plutôt qu'un <img object-fit:cover> : cadrage
   identique, et cohérent avec .cover (tuiles du tableau de bord), qui pose
   déjà l'image de liste en fond. */
.list-hero{position:relative;height:240px;background-size:cover;background-position:center;}
/* Dégradé NON décoratif : le titre est blanc et l'image est choisie par
   l'utilisateur — claire, sombre ou saturée, on ne peut rien en présumer. Le
   dégradé garantit un fond sombre sous le texte quelle que soit l'image ; le
   text-shadow est la seconde ligne de défense.
   PALIER plutôt qu'une rampe linéaire jusqu'en bas : la rampe atteint son
   maximum à 75%, donc AVANT le titre, qui repose ainsi sur un fond d'opacité
   constante. Une rampe simple (transparent -> .65) laissait le HAUT du titre
   sur un voile deux fois plus clair que son bas — 3,25:1 sur image blanche en
   mobile, tout juste au seuil AA du grand texte. Ici : 6,13:1 dans ce même
   pire cas, et le sommet de l'image est même PLUS dégagé (transparent
   jusqu'à 25% au lieu de 0%). */
.list-hero::after{content:'';position:absolute;inset:0;background:linear-gradient(to bottom,transparent 25%,rgba(0,0,0,.72) 75%);}
/* z-index indispensable : sans lui le ::after, postérieur dans l'ordre de
   peinture, recouvrirait le titre. Padding réécrit en entier (et non un simple
   padding-bottom) pour ne pas dépendre de l'ordre des règles avec .wrap. */
.list-hero-inner{
  position:relative;z-index:1;height:100%;
  display:flex;flex-direction:column;justify-content:space-between;
  padding:18px 24px 22px;
  /* `gap` et non une marge : avec justify-content:space-between il agit comme
     un PLANCHER. Tant que la bannière a de la hauteur en trop (desktop), c'est
     space-between qui écarte les blocs et le gap ne se voit pas ; dès que la
     hauteur devient juste (mobile, badges sur deux lignes), il garantit que le
     titre ne vienne pas se coller sous les badges. */
  gap:14px;
}
/* Statut à gauche, rôle(s) à droite : deux natures d'information opposées dans
   l'espace (la liste vs la relation du visiteur à la liste). Le libellé
   « Vous êtes : » a disparu avec ce placement — la position le dit. */
.list-hero-badges{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;}
.list-hero-roles{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;}
/* Colonne droite de la bannière : les badges de rôle et le bouton « Modifier ».
   `flex-wrap` fait tout le travail responsive — côte à côte tant que la largeur
   le permet, bouton sous les badges sinon, et dans les deux cas à l'intérieur de
   la bannière. Pas de media query. */
.list-hero-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px 10px;}
/* Les trois actions de pilotage (Personnaliser / Gérer / Partager). Groupe
   propre plutôt que trois enfants directs de .list-hero-actions : elles doivent
   passer à la ligne ENSEMBLE sous les badges de rôle, jamais se disperser de
   part et d'autre d'eux. */
.list-hero-tools{display:flex;flex-wrap:wrap;gap:8px;}
/* Le bouton « Partager » porte ses deux états (repos / « Copié ! ») en enfants,
   ce qui interpose un <span> entre .btn et son icône : sans ces trois
   propriétés, l'icône et le libellé perdent l'alignement et l'écart que .btn
   applique à ses propres enfants. */
.list-hero-tool-state{display:inline-flex;align-items:center;gap:8px;}
/* Les .tag sont conçus pour un fond CLAIR ; ici ils reposent sur une photo
   quelconque ou sur le vert du fond de secours. On ne touche PAS à leurs
   couleurs de fond (elles doivent rester celles du tableau de bord) : on ajoute
   seulement un double liseré, chacun couvrant un cas.
   - anneau blanc translucide : détache le badge d'un fond SOMBRE. Indispensable,
     tag-owner (--forest) sur le fond de secours vert atteint 1,00:1 — la même
     couleur exactement, le badge disparaîtrait purement et simplement.
   - ombre portée noire : détache le badge d'un fond CLAIR, où l'anneau blanc
     ne peut rien. */
.list-hero .tag{box-shadow:0 0 0 1px rgba(255,255,255,.7), 0 2px 6px rgba(0,0,0,.35);}
/* Même problème que les .tag pour le bouton de la bannière, mais à moitié
   seulement : son fond --beige est déjà très clair, il se détache tout seul
   d'une photo sombre ou du fond de secours vert — inutile d'y ajouter l'anneau
   blanc. Il ne lui manque que l'ombre portée, pour ne pas se fondre dans une
   photo claire (--beige sur du blanc, c'est 1,08:1). */
.list-hero .btn{box-shadow:0 2px 6px rgba(0,0,0,.35);}
.list-hero h1{color:#fff;margin:0;text-shadow:0 2px 6px rgba(0,0,0,.45);}
/* Bas de bannière : identité de la liste à gauche, visage du propriétaire à
   droite. Il répond à une question qu'un lien de partage pose forcément —
   « chez qui suis-je ? » — et qui ne se lisait que dans la petite ligne grise
   « Créée par X », juste en dessous de lui.
   🔴 UNE RANGÉE FLEX, ET SÛREMENT PAS UN AVATAR CENTRÉ EN ABSOLU. Le motif
   « avatar centré sur une couverture » vient des pages de profil, où le titre
   est SOUS la couverture et centré lui aussi. Ici il est dedans, en bas à
   gauche, et il peut faire 60 caractères : à 360px il occupe deux ou trois
   lignes sur toute la largeur, donc exactement la place du centre. En rangée,
   la collision devient structurellement impossible. */
.list-hero-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;}
/* `min-width:0` : sans lui, un mot long du titre impose sa largeur au bloc et
   pousse l'avatar hors de la bannière. Même garde-fou que `.gift-main`. */
.list-hero-identity{min-width:0;}
/* Même raisonnement que `.list-hero .tag` : le disque repose sur une photo dont
   on ne peut rien présumer. Anneau blanc pour s'en détacher sur un fond sombre,
   ombre portée pour un fond clair — chaque moitié couvre un cas.
   `flex:none` : c'est le titre qui absorbe la largeur disponible, jamais lui. */
.list-hero-avatar{
  --avatar-size:56px;
  flex:none;
  box-shadow:0 0 0 2px rgba(255,255,255,.8), 0 2px 6px rgba(0,0,0,.35);
}
/* Fond de secours quand la liste n'a pas d'image : la bannière garde exactement
   les mêmes dimensions et la même disposition, seul le fond change. Pas d'image
   générique — l'image de couverture ne doit jamais devenir obligatoire.
   Moitié SOMBRE de la palette (--forest-dark -> --primary-dark) plutôt que
   --forest-dark -> --forest : sur la variante claire, l'anneau des badges ne
   se détachait qu'à 2,3:1 du fond, contre 3,9:1 ici (seuil de 3:1 des
   contrastes non textuels). Le titre blanc y gagne aussi. */
.list-hero-fallback{background-image:linear-gradient(135deg,var(--forest-dark),var(--primary-dark));}
/* Surcharges responsives déclarées ICI, après la règle de base : les media
   queries n'ajoutent aucune spécificité, une @media placée plus haut dans le
   fichier serait donc écrasée par le .list-hero{height:240px} ci-dessus.
   Mêmes points de rupture que le reste de la feuille (900 / 760px). */
@media (max-width:900px){
  /* min-height, même raison qu'au palier suivant : les badges peuvent déjà se
     replier à cette largeur, la bannière doit pouvoir suivre. Elle ne descend
     jamais sous l'ancienne hauteur fixe. */
  .list-hero{height:auto;min-height:200px;}
}
@media (max-width:760px){
  /* min-height et non height : les deux badges de rôle d'un propriétaire
     passent à la ligne sur un écran étroit, la bannière doit pouvoir grandir
     plutôt que de rogner son contenu. */
  .list-hero{height:auto;min-height:160px;}
  .list-hero h1{font-size:1.6rem;}
  /* La bannière descend à 160px et le titre passe à deux ou trois lignes :
     56px de disque y prendraient une part disproportionnée de la rangée.
     ⚠️ Cette surcharge n'est possible QUE parce que la taille vit en CSS —
     posée en `style=""` par le gabarit, elle serait imbattable ici. */
  .list-hero-avatar{--avatar-size:44px;}
  /* Les trois boutons quittent le flux et deviennent des pastilles rondes dans
     le coin. Ils étaient jusqu'ici sur la même ligne que les badges de rôle,
     or ceux-ci passent à la ligne quand ils sont deux : la zone devenait
     confuse. Hors flux, ils ne peuvent plus entrer en collision avec eux quel
     que soit leur nombre.
     Fond blanc semi-opaque : lisible aussi bien sur une photo claire que
     sombre ou sur le vert du fond de secours, sans dépendre de l'image. */
  .list-hero-tools{position:absolute;top:14px;right:24px;flex-wrap:nowrap;gap:8px;}
  .list-hero-tool{
    width:40px;height:40px;padding:0;border-radius:50%;
    background:rgba(255,255,255,.92);border-color:transparent;
    color:var(--forest-dark);
  }
  .list-hero-tool:hover{background:#fff;border-color:transparent;}
  /* Les icônes restent seules : le nom accessible passe par aria-label/title.
     ⚠️ 40px de côté, soit au-dessus du minimum de 24px de WCAG 2.5.8 —
     trois cibles voisines sur un écran tactile ne pardonnent pas l'à-peu-près. */
  .list-hero-tool-label{display:none;}
  .list-hero-tool svg{width:18px;height:18px;}
  /* Statut hors flux lui aussi, dans le coin opposé : les deux forment une
     ligne d'en-tête claire, statut à gauche / pastille à droite, et les badges
     de rôle restent seuls juste au-dessus du titre.
     top:21px et non 14px : le badge fait ~26px de haut contre 40px pour la
     pastille, ce décalage les centre l'un sur l'autre (14 + (40-26)/2).
     max-width : garde-fou pour que le badge ne puisse jamais atteindre les
     pastilles, même avec un libellé long sur un écran très étroit — 196px
     couvrent les trois pastilles (3 × 40 + 2 × 8 d'écart), leurs marges
     (24 + 24) et un écart de 12. ⚠️ À revoir si un quatrième bouton
     apparaissait : le badge de statut passerait sous les pastilles. */
  .list-hero-status{position:absolute;top:21px;left:24px;max-width:calc(100% - 196px);}
  /* Rôles alignés à GAUCHE, sur le même bord que le statut (left:24px = les
     24px de padding de .list-hero-inner) : en colonne étroite, un statut à
     gauche et des rôles à droite se lisaient comme deux fragments sans rapport.
     Les trois règles sont nécessaires — .list-hero-badges est en
     space-between, .list-hero-actions et .list-hero-roles en flex-end. */
  .list-hero-badges{justify-content:flex-start;}
  .list-hero-actions{justify-content:flex-start;}
  .list-hero-roles{justify-content:flex-start;}
  /* ⚠️ Alignés mais PAS accolés : statut et rôles décrivent deux choses
     différentes (l'état de la liste vs la relation du visiteur à elle), un
     empilement serré les ferait lire comme un seul groupe. Le statut hors flux
     s'arrête à 47px, la pastille à 54px ; démarrer les rôles à 78px laisse
     24px de vide franc sous la pastille. C'est ce padding qui porte la
     séparation — pas une marge sur les badges, qui suivrait mal le décalage
     vertical entre le badge (26px) et la pastille (40px). */
  .list-hero-inner{padding-top:78px;}
}

.list-view{display:block;}
.list-view.has-sidebar{
  display:grid;
  grid-template-columns:minmax(0,1fr) 360px;
  gap:28px;
  align-items:start;
}
.list-main{min-width:0;}
.list-side{display:flex;flex-direction:column;gap:20px;min-width:0;}
/* La colonne latérale gère son espacement via `gap` : on neutralise les marges
   propres des enfants (notamment la marge haute des .flash). */
.list-side > *{margin-top:0;}
.list-side .card + .card{margin-top:0;}

.discussion-panel{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);
  display:flex;flex-direction:column;
  overflow:hidden;
}
.discussion-header{padding:16px 18px 12px;border-bottom:1px solid var(--line);}
.discussion-header h3{margin:0;font-size:1.05rem;}

.discussion-messages{
  flex:1;overflow-y:auto;padding:14px 16px;
  display:flex;flex-direction:column;gap:10px;
  min-height:160px;max-height:420px;
}
.msg-bubble{
  align-self:flex-start;max-width:88%;
  background:var(--canvas);border-radius:12px 12px 12px 3px;
  padding:8px 12px;font-size:.88rem;
}
.msg-bubble.mine{
  align-self:flex-end;
  background:var(--berry);color:#fff;
  border-radius:12px 12px 3px 12px;
}
.msg-author{font-weight:700;font-size:.75rem;opacity:.75;margin-bottom:2px;}
.msg-bubble.mine .msg-author{opacity:.85;}
.msg-content{white-space:pre-wrap;word-break:break-word;}
.msg-time{font-size:.7rem;opacity:.6;margin-top:3px;text-align:right;}

.discussion-form{
  display:flex;flex-direction:column;gap:8px;
  padding:12px 16px 16px;border-top:1px solid var(--line);
}
.discussion-form input[type=text],.discussion-form textarea{font-size:.88rem;padding:9px 11px;}
.discussion-form textarea{min-height:auto;resize:vertical;}
/* Remplace le formulaire quand la liste est repassée en brouillon : mêmes
   marges et même bordure haute, pour que le panneau garde exactement sa
   silhouette. Un pied de discussion qui change de forme selon le statut
   attirerait l'œil sur le contenant plutôt que sur l'explication. */
.discussion-readonly{
  display:flex;align-items:flex-start;gap:8px;margin:0;
  padding:12px 16px 16px;border-top:1px solid var(--line);
  font-size:.8rem;color:var(--ink-soft);
}
.discussion-readonly .icon{flex:none;margin-top:2px;}

@media (max-width:900px){
  .list-view.has-sidebar{grid-template-columns:1fr;}
  .discussion-messages{max-height:320px;}
}

/* ---- Tri des cadeaux façon Kanban (SortableJS) ---- */
.kanban-board{
  display:flex;gap:16px;align-items:flex-start;
  overflow-x:auto;padding-bottom:12px;margin-top:16px;
}
/* ⚠️ Le panneau s'élargit POUR CE CONTENU seulement. Un Kanban dans 760px ne
   montre que deux colonnes et demie, et le défilement horizontal devient le
   geste principal — alors qu'on vient y faire du glisser-déposer.
   `:has()` plutôt qu'un modificateur posé par le JS : la largeur est une
   propriété du CONTENU, pas un état à synchroniser. Même mécanisme que
   `.radio-option:has(input:checked)`, déjà en place. */
.modal-panel:has(.kanban-board){max-width:1100px;}
/* Dans la modale, la hauteur de référence n'est plus la fenêtre mais le
   panneau : `calc(100vh - 220px)` y déborderait, et on obtiendrait DEUX
   ascenseurs verticaux imbriqués — celui de la colonne et celui du panneau. */
#panel-modal-body .kanban-column{max-height:min(52vh, 420px);}
.kanban-column{
  flex:0 0 270px;width:270px;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow-sm);
  display:flex;flex-direction:column;
  max-height:calc(100vh - 220px);
}
.kanban-column-header{
  padding:14px 16px 10px;border-bottom:1px solid var(--line);
  font-weight:800;font-size:.95rem;color:var(--ink);
  display:flex;align-items:center;justify-content:space-between;gap:8px;
}
.kanban-column-count{font-weight:600;font-size:.78rem;color:var(--ink-soft);}
.kanban-column-list{
  flex:1;overflow-y:auto;padding:10px;
  display:flex;flex-direction:column;gap:8px;
  min-height:80px;
}
.kanban-card{
  display:flex;align-items:center;gap:10px;
  background:var(--canvas);border:1px solid var(--line);border-radius:12px;
  padding:9px 10px;
}
.kanban-drag-handle{
  display:flex;align-items:center;justify-content:center;flex-shrink:0;
  width:26px;height:26px;border-radius:8px;color:var(--ink-soft);
  cursor:grab;touch-action:none;
}
.kanban-drag-handle:hover{background:var(--mint-soft);color:var(--ink);}
.kanban-card-body{min-width:0;flex:1;}
.kanban-card-name{font-weight:700;font-size:.9rem;color:var(--ink);word-break:break-word;}
.kanban-card-meta{font-size:.78rem;color:var(--ink-soft);}
.kanban-column-list .sortable-ghost{opacity:.4;}
.kanban-column-list .sortable-chosen .kanban-card{border-color:var(--forest);}
.kanban-column-list .sortable-drag{opacity:.95;}

/* ---- Modale générique (overlay + panneau centré) ---- */
.modal-overlay{
  position:fixed;inset:0;z-index:1000;
  background:rgba(30,43,34,.55);
  display:flex;align-items:center;justify-content:center;
  padding:20px;
}
.modal-overlay[hidden]{display:none;}
/* Verrou de défilement : posé sur <body> par l'observateur de base.html.twig
   dès qu'une .modal-overlay est visible. Sans lui, la molette faisait défiler
   la PAGE derrière la modale — le panneau restait immobile au milieu d'un
   contenu qui glissait, et on ressortait de la modale sur un tout autre
   endroit de la page. La compensation de la barre de défilement (padding-right)
   est calculée en JS : sa largeur dépend du système. */
body.modal-open{overflow:hidden;}
/* 🔴 LA CONFIRMATION PASSE TOUJOURS DEVANT. Elle est déclenchée DEPUIS une
   autre modale — supprimer une liste se fait depuis « Gérer » — et les deux
   panneaux sont alors visibles en même temps. À `z-index` égal, c'est l'ordre
   du DOCUMENT qui tranche : la confirmation, incluse dans le corps de la page,
   se retrouvait DERRIÈRE la modale d'écran, désormais rendue par
   `base.html.twig` juste avant `</body>`. On voyait donc le fond s'assombrir
   sans jamais voir la question.
   ⚠️ Ne pas compter sur l'ordre d'inclusion pour rétablir ça : il a déjà changé
   une fois, et rien ne le signalerait. */
#confirm-overlay{z-index:1010;}
.modal-panel{
  position:relative;width:100%;max-width:760px;max-height:90vh;overflow-y:auto;
  background:var(--paper);border-radius:var(--radius-lg);box-shadow:var(--shadow);
  padding:30px 28px 28px;
}
.modal-close{
  position:absolute;top:14px;right:14px;
  display:flex;align-items:center;justify-content:center;
  width:32px;height:32px;border-radius:999px;border:1px solid var(--line);
  background:var(--paper);color:var(--ink-soft);cursor:pointer;
}
.modal-close:hover{background:var(--canvas);color:var(--ink);}
.modal-body h1{font-size:1.4rem;padding-right:36px;}
/* Titre de la modale de confirmation : un `h2` (elle est sur toutes les pages,
   un second `h1` brouillerait leur structure), à la taille d'un titre de modale. */
.modal-body .confirm-title{font-size:1.4rem;padding-right:36px;margin-top:0;}

/* ── LE BANDEAU DES MODALES À FORMULAIRE ─────────────────────────────────────
   Les modales qui portent un FORMULAIRE ont une barre de titre verte, collante
   en haut du panneau — à toutes les largeurs.

   🔴 DEUX MODALES SONT NOMMÉMENT VISÉES, ET DEUX AUTRES NOMMÉMENT ÉPARGNÉES.
   `#confirm-overlay` (« Supprimer cette liste ? ») et `#idea-modal` (l'aperçu
   d'une idée cadeau) partagent les mêmes classes mais ne sont pas des écrans :
   une QUESTION coiffée d'une barre de titre serait disproportionnée. D'où des
   sélecteurs par IDENTIFIANT plutôt que par classe.

   ⚠️ La portée par `id` met aussi ces règles à l'abri de l'ordre du fichier —
   piège rencontré sur le bouton d'installation de l'extension, où deux
   sélecteurs à égalité se départageaient à la position.

   ⚠️ Elles ne touchent PAS les versions pleine page de ces mêmes fragments
   (`wishlist/manage.html.twig`, `gift/form.html.twig`, `profile/edit.html.twig`)
   : ce sont des pages, pas des panneaux, et elles n'ont pas de croix. */
#panel-modal-overlay .modal-panel,
#gift-modal-overlay .modal-panel{
  /* 🔴 PLUS DE REMBOURRAGE EN HAUT : c'est le bandeau qui fournit le sien, et
     il doit toucher le bord. Voir la règle du bandeau pour ce que coûtait
     l'autre méthode. */
  padding-top:0;
  /* Colonne flex uniquement pour que la croix, premier enfant, puisse se
     ranger à droite et devenir COLLANTE — voir la règle suivante. */
  display:flex;flex-direction:column;
}

/* 🔴 LA CROIX EST COLLANTE, ET C'EST LA SEULE FAÇON QU'ELLE SOIT VISIBLE.
   Deux défauts se cumulaient sur ces deux modales, et le premier la faisait
   disparaître DÈS L'OUVERTURE en bureau :

   1. `.modal-close` n'avait AUCUN `z-index` ; le bandeau collant est à 2. À
      `z-index` auto contre 2, le bandeau passe devant et la recouvre
      entièrement — alors que son `padding-right:56px` lui réserve la place.
      Défaut signalé à l'usage, et invisible en mobile : la règle du plein
      écran la repassait en `fixed` avec un `z-index:3`, qui corrigeait le
      symptôme sans que personne ne sache qu'il existait ailleurs ;
   2. en `absolute`, son bloc conteneur est le panneau — qui EST le conteneur
      de défilement. Elle défilait donc avec le formulaire et sortait de
      l'écran, pendant que le bandeau, lui, restait.

   ⚠️ `sticky` RÈGLE LES DEUX ET REMPLACE LE `fixed` DU PLEIN ÉCRAN : un seul
   mécanisme au lieu de deux. En mobile le panneau occupe la fenêtre, donc
   coller à 14px du haut de sa zone de défilement revient exactement à ce que
   faisait `position:fixed;top:14px`.

   ⚠️ LES MARGES REPRODUISENT AU PIXEL LES ANCIENS `top:14px;right:14px`, et
   elles ne s'y calculent PAS pareil : un décalage d'élément absolu part du
   bloc de PADDING (donc du bord du panneau), une marge part du bloc de
   CONTENU (donc 28px plus à l'intérieur). D'où le `-14px` à droite —
   28 de rembourrage moins les 14 voulus. En haut, le rembourrage vaut 0 sur
   ces deux modales, et `14px` suffit.

   🔴 LA MARGE BASSE NÉGATIVE N'EST PAS UN AJUSTEMENT COSMÉTIQUE : elle annule
   la place que la croix occupe en flux (32px de haut + 14 de marge), sans
   quoi elle POUSSERAIT le bandeau de 46px vers le bas et celui-ci cesserait
   de toucher le haut du panneau. Les trois valeurs vont ensemble. */
#panel-modal-overlay .modal-close,
#gift-modal-overlay .modal-close{
  position:sticky;top:14px;z-index:3;
  align-self:flex-end;flex:none;
  margin:14px -14px -46px 0;
}

#panel-modal-overlay .modal-body .panel-head,
#gift-modal-overlay .modal-body .panel-head{
  position:sticky;top:0;z-index:2;
  /* 🔴 AUCUNE MARGE NÉGATIVE EN HAUT — une première version en posait une de
     -30px pour annuler le rembourrage du panneau, et ça CHEVAUCHAIT LE
     FORMULAIRE DÈS L'OUVERTURE. Un élément collant est décalé depuis sa
     position de flux : tiré 30px au-dessus du haut du conteneur, `top:0` le
     ramenait à 0 — donc 30px plus bas que sa place — sans que le contenu qui
     suit ne bouge. Le premier champ passait dessous, avant même de défiler.
     C'est le rembourrage du PANNEAU qui a été retiré, pas la marge qui a été
     inventée. */
  margin:0 -28px 18px;padding:18px 28px;
  /* Bandeau VERT, comme la barre de titre d'une application : il donne au
     panneau une tête identifiable, et tient lieu de repère quand le formulaire
     défile sous lui.
     🔴 `--forest-deep` et NON `--primary` : celui-ci est exactement la couleur
     du bouton d'action qui se trouve au bas du même écran, les deux se
     confondaient. Blanc sur `--forest-deep` : 9,35:1 pour le titre, 6,92:1
     pour le sous-titre à 82%. Le fond étant sombre, le sous-titre garde de la
     HIÉRARCHIE — au-delà de 90% il devenait presque blanc et ne se distinguait
     plus du titre. */
  background:var(--forest-deep);color:#fff;
  padding-right:56px;
  /* ⚠️ Les coins HAUTS suivent ceux du panneau : sans ça, la barre sort en
     angle droit par-dessus l'arrondi et le déborde de quelques pixels. Le
     plein écran les remet à zéro plus bas, le panneau n'y étant plus arrondi. */
  border-radius:var(--radius-lg) var(--radius-lg) 0 0;
}
#panel-modal-overlay .modal-body .panel-head h1,
#gift-modal-overlay .modal-body .panel-head h1{color:#fff;}
#panel-modal-overlay .modal-body .panel-head p,
#gift-modal-overlay .modal-body .panel-head p{color:rgba(255,255,255,.82);}
/* ⚠️ La pastille passe en blanc translucide : son fond menté disparaîtrait sur
   le vert, et son icône avec. */
#panel-modal-overlay .modal-body .panel-head .panel-icon,
#gift-modal-overlay .modal-body .panel-head .panel-icon{
  background:rgba(255,255,255,.16);color:#fff;
}

/* ── …ET LE PLEIN ÉCRAN, SOUS 640px ──────────────────────────────────────────
   Pleine largeur, pleine hauteur, sans coins arrondis. À cette taille, un
   panneau centré perdait une trentaine de pixels de chaque côté au profit d'un
   fond assombri que personne ne regarde. */
@media (max-width:640px){
  #panel-modal-overlay,
  #gift-modal-overlay{padding:0;align-items:stretch;}

  /* 🔴 `100dvh` ET PAS `100vh`. Sur mobile, `100vh` ne compte pas la barre
     d'adresse du navigateur : le panneau dépasse le bas de l'écran visible, et
     le bouton d'action collant se retrouve DESSOUS — invisible, sur l'écran
     même où il est le plus nécessaire. ⚠️ `100vh` est déclaré d'abord comme
     repli : un navigateur qui ignore `dvh` garde une hauteur plausible. */
  #panel-modal-overlay .modal-panel,
  #gift-modal-overlay .modal-panel{
    max-width:none;max-height:none;border-radius:0;
    height:100vh;height:100dvh;
  }

  /* Le panneau n'étant plus arrondi, le bandeau ne l'est pas non plus. */
  #panel-modal-overlay .modal-body .panel-head,
  #gift-modal-overlay .modal-body .panel-head{border-radius:0;}

  /* ⚠️ AUCUNE RÈGLE POUR LA CROIX ICI, et son absence est délibérée. Elle a
     porté `position:fixed;z-index:3`, écrit pour qu'elle cesse de défiler avec
     le formulaire en plein écran. Le `sticky` déclaré plus haut fait la même
     chose — le panneau occupant la fenêtre, coller à 14px du haut de sa zone
     de défilement revient au même — et le fait AUSSI en bureau, où le défaut
     existait sans que personne ne l'ait vu. Ne pas le rétablir : deux
     mécanismes pour un même besoin, et `fixed` annulerait au passage les
     marges qui tiennent la géométrie. */
}

/* ---- Panneau d'écran (modale « Gérer », et tout écran de même facture) ----
   Sections empilées dans un cadre unique, chacune introduite par une pastille
   d'icône et un titre.

   ⚠️ Les icônes sont celles du jeu maison (_icons.html.twig), en
   `currentColor` : elles prennent donc la couleur de leur pastille sans une
   règle de plus. Aucun emoji — ils changent d'aspect d'une plateforme à
   l'autre et ne suivent ni la couleur ni la taille du texte. Même règle que le
   badge « Liste permanente ». */
.panel-head{display:flex;gap:14px;align-items:flex-start;margin-bottom:20px;padding-right:36px;}
.panel-head h1{margin:0 0 2px;font-size:1.35rem;}
.panel-head p{margin:0;}
/* Pastille ronde. `flex:none` : sans lui elle s'écrase quand le titre est long,
   et l'icône déborde de son cercle devenu ovale. */
.panel-icon{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:50%;
  background:var(--mint-soft);color:var(--forest-dark);
}
.panel-icon svg{width:22px;height:22px;}
.panel-icon--sm{width:34px;height:34px;}
.panel-icon--sm svg{width:18px;height:18px;}
/* ⚠️ Réutilise --berry-soft plutôt qu'un token neuf : --danger vaut exactement
   --berry-dark, son compagnon clair existe déjà. Un --danger-soft ferait deux
   noms pour la même couleur. */
.panel-icon--danger{background:var(--berry-soft);color:var(--danger);}

/* ⚠️ Chaque section de « Gérer » est une CARTE À PART ENTIÈRE (`card
   panel-section`), plus une tranche d'un cadre unique découpé par des
   séparateurs. Deux règles de la forme précédente ont donc été retirées :
   `.panel-section + .panel-section{border-top}`, qui reposait la bordure haute
   que `.card` pose déjà à l'identique, et `.panel-card`, dont le nom désignait
   ce cadre unique — ses trois déclarations étaient soit identiques à celles de
   `.card` (bordure, rayon), soit sans effet (`overflow:hidden`, sur des cartes
   dont aucun enfant ne déborde). Inertes toutes les deux, mais elles
   décrivaient une structure qui n'existe plus.

   ⚠️ Le `padding` doit rester APRÈS `.card` dans le fichier : à spécificité
   égale, c'est l'ordre qui tranche, et `.card` en pose 28. */
.panel-section{padding:20px;}
.panel-section-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px;}
.panel-section-title{margin:0;font-size:1.05rem;}
.panel-section-text{margin:0 0 14px;font-size:.88rem;color:var(--ink-soft);}
/* Pastille d'information à côté du titre : dit ce que la section ACCORDE
   (« Consultation et réservation »), là où le titre ne dit que ce qu'elle est. */
.panel-badge{
  display:inline-block;padding:4px 10px;border-radius:999px;
  background:var(--mint-soft);color:var(--forest-dark);
  font-size:.75rem;font-weight:700;
}

/* Champ + bouton sur une ligne. `min-width:0` sur l'input : sans lui, une URL
   longue impose sa largeur intrinsèque et pousse le bouton hors du cadre. */
.panel-input-row{display:flex;gap:10px;align-items:center;margin-bottom:12px;}
.panel-input-row input{min-width:0;flex:1;}
.panel-input-row .btn{flex:none;white-space:nowrap;}
.panel-inline-form{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
.panel-inline-form select{max-width:240px;}
.panel-field-label{display:block;font-weight:700;font-size:.85rem;margin-bottom:6px;}

/* Ligne d'aide sous une action. Trois tons, une seule structure. */
.panel-note{display:flex;gap:8px;align-items:flex-start;margin:10px 0 0;font-size:.8rem;line-height:1.45;}
.panel-note svg{flex:none;margin-top:2px;}
.panel-note--warning{color:var(--ink-soft);}
.panel-note--warning svg{color:var(--gold);}
.panel-note--info{color:var(--ink-soft);}
.panel-note--info svg{color:var(--forest-dark);}
/* Variante encadrée, pour un texte qui explique un POUVOIR accordé — il pèse
   plus lourd qu'une simple note et mérite d'être détaché du flux. */
.panel-note--soft{
  margin-top:12px;padding:12px 14px;border-radius:12px;
  background:var(--mint-soft);color:var(--ink);
}
.panel-note--soft svg{color:var(--forest-dark);}

/* État vide : un encart, jamais une ligne grise perdue au milieu du reste —
   c'est une réponse à la question « qui a accès ? », elle doit se voir. */
.panel-empty{
  display:flex;gap:8px;align-items:center;margin:0 0 14px;
  padding:14px;border-radius:12px;background:var(--beige);
  color:var(--ink-soft);font-size:.88rem;
}
.panel-empty svg{flex:none;}

.panel-list{list-style:none;padding:0;margin:0 0 14px;display:grid;gap:8px;}
.panel-list li{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:10px 12px;border:1px solid var(--line);border-radius:12px;
}
.panel-list-label{display:inline-flex;align-items:center;gap:6px;font-size:.9rem;word-break:break-all;}
/* Le formulaire de retrait est un enfant flex : sans `margin-left:auto` il se
   colle au badge au lieu de tenir le bord droit de la ligne. */
.panel-list form{margin-left:auto;}

/* Badge neutre — « Facultatif » — à côté d'un libellé de champ. Gris et non
   vert : il ne dit pas ce qu'on gagne, seulement qu'on peut passer. */
.panel-badge--muted{background:var(--beige-dark);color:var(--ink-soft);}

/* Barre d'actions d'un panneau. En flux normal par défaut (page complète). */
.panel-actions{
  display:flex;justify-content:flex-end;gap:12px;flex-wrap:wrap;
  margin-top:24px;padding-top:18px;border-top:1px solid var(--line);
}
/* Note à GAUCHE des boutons : `margin-right:auto` la décolle vers le bord
   opposé sans toucher au `justify-content` des autres formulaires. */
.panel-actions .panel-actions-note{margin:0 auto 0 0;align-self:center;}
/* ⚠️ COLLANTE en modale UNIQUEMENT. Le formulaire est long : devoir le faire
   défiler jusqu'en bas pour trouver « Enregistrer » est exactement ce qu'on
   évite. Les marges négatives la font déborder dans le padding du panneau, pour
   que sa bordure aille d'un bord à l'autre — elles valent `-28px` parce que
   c'est le padding latéral de `.modal-panel`. Hors modale, la page entière
   défile et une barre collée au bas de la fenêtre suivrait le visiteur sur
   toute la page, d'où la portée restreinte.

   🔴 PORTÉE SUR LA CLASSE `.modal-body`, ET SURTOUT PAS SUR UN `id`. La règle
   visait `#panel-modal-body`, donc la seule modale des écrans de liste : celle
   des CADEAUX (`#gift-modal-body`) porte le même partiel d'actions et n'en
   bénéficiait pas — sa barre restait en bas du formulaire, à faire défiler.
   Défaut signalé à l'usage. Les deux modales partagent `.modal-panel`, donc le
   même padding : une seule règle les couvre, et couvrira la prochaine. La
   modale de confirmation porte aussi `.modal-body` mais aucune
   `.panel-actions` — elle n'est pas concernée. */
.modal-body .panel-actions{
  position:sticky;bottom:-28px;z-index:1;
  margin:24px -28px -28px;padding:18px 28px;
  background:var(--paper);
}
@media (max-width:520px){
  /* Boutons pleine largeur, et l'action principale EN PREMIER visuellement :
     sur un écran étroit, c'est celle que le pouce doit atteindre. */
  .panel-actions{flex-direction:column-reverse;}
  .panel-actions .btn{width:100%;}
  /* `column-reverse` la fait passer SOUS le bouton : centrée, elle se lit
     comme sa légende. */
  .panel-actions .panel-actions-note{margin:0;text-align:center;}
}

@media (max-width:520px){
  /* Le bouton passe SOUS le champ : à cette largeur, « Copier » et une URL
     complète ne tiennent pas sur une ligne sans que l'un des deux devienne
     illisible. */
  .panel-input-row{flex-wrap:wrap;}
  .panel-input-row input{flex:1 1 100%;}
  .panel-input-row .btn{flex:1 1 auto;}
  .panel-inline-form select{max-width:none;flex:1 1 100%;}
}

/* ---- Jetons d'accès API (profil) ---- */
/* ⚠️ `word-break` indispensable : un jeton fait 64 caractères sans espace et
   déborderait de la carte sur mobile, empêchant justement de le copier. */
.api-token-value{
  display:block;padding:10px 12px;background:#fff;border:1px solid var(--line);
  border-radius:8px;font-family:ui-monospace,monospace;font-size:.85rem;word-break:break-all;
}
.api-token-heading{margin:0 0 10px;font-size:.95rem;}
/* Intertitres du formulaire de profil (Vous, Notifications, Connexion). Un
   filet au-dessus sépare les groupes, sauf pour le premier, collé à l'avatar.
   ⚠️ `:first-of-type` compte les `h3` du formulaire : le premier titre est
   bien le premier `h3` de son parent. */
.profile-group-title{
  margin:26px 0 12px;padding-top:18px;border-top:1px solid var(--line);
  font-size:.95rem;color:var(--forest-dark);
}
.profile-group-title:first-of-type{margin-top:8px;padding-top:0;border-top:none;}
.api-token-list{list-style:none;padding:0;margin:0 0 16px;display:grid;gap:12px;}
/* Une CARTE par jeton, en colonne : nom, création, dernière utilisation,
   action. ⚠️ La rangée compacte précédente (flex, tout sur une ligne) tassait
   les deux dates dans un même bloc gris où l'on ne distinguait plus laquelle
   était laquelle. */
.api-token-card{
  display:flex;flex-direction:column;gap:4px;
  padding:14px 16px;background:var(--beige);
  border:1px solid var(--line);border-radius:12px;
}
.api-token-name{font-size:.95rem;}
/* ⚠️ `flex-end` sur le FORMULAIRE, pas sur le bouton : c'est le formulaire
   qui est l'enfant flex de la carte (le bouton est dedans). Une règle posée
   sur `.btn` ne déplacerait rien. */
.api-token-revoke{align-self:flex-end;margin-top:6px;}
/* ⚠️ Le `input[name=label]` que cette règle accompagnait a été RETIRÉ : la
   création d'un jeton est un simple bouton (voir profile/edit.html.twig).
   Le bouton prend toute la largeur — c'est l'action principale de ce bloc,
   et rien ne l'accompagne plus sur sa ligne. */
.api-token-create{display:flex;gap:10px;flex-wrap:wrap;}
.api-token-create .btn{width:100%;}

/* Cartes de cadeau en mobile.
   ⚠️ Le visuel RÉTRÉCIT au lieu de passer pleine largeur : une photo de 100%
   de large pousserait les informations si loin sous la ligne de flottaison que
   la liste cesserait d'être parcourable d'un coup d'œil — or c'est exactement
   ce qu'on vient y faire. */
@media (max-width:720px){
  /* ⚠️ Les boutons passent sur une LIGNE À EUX, sur toute la largeur de la
     carte — ils démarrent donc sous le visuel. Coincés dans la seconde colonne,
     trois boutons devaient tenir dans les deux tiers d'un écran de téléphone :
     « Supprimer » débordait de sa propre bordure. */
  .gift-row{
    grid-template-columns:104px 1fr;
    grid-template-areas:
      "thumb main"
      "actions actions";
    gap:14px;padding:14px;
  }
  /* ⚠️ La HAUTEUR suit la largeur. Ne changer que la largeur laissait un
     visuel de 104×180 : un rectangle étiré, exactement le défaut que la
     hauteur fixe était censée corriger. */
  .gift-thumb-wrap{width:104px;height:104px;}
  .gift-main > h3{font-size:1.1rem;}
  /* ⚠️ UNE SEULE LIGNE, trois parts égales. `flex:1 1 0` (base zéro) et non
     `1 1 auto` : avec `auto`, chaque bouton part de la largeur de son texte, et
     « Supprimer » ressort plus large que « Copier ». Base zéro = tiers exacts.
     `min-width:0` autorise la réduction sous la largeur du contenu, sans quoi
     `nowrap` ferait déborder la rangée. */
  .gift-actions{flex-wrap:nowrap;}
  .gift-actions > *{flex:1 1 0;min-width:0;}
  .gift-actions .btn{
    justify-content:center;white-space:nowrap;
    font-size:.78rem;padding-left:8px;padding-right:8px;gap:5px;
  }
  .gift-actions .btn .icon{flex:none;}
}

/* ⚠️ Sous 400px, l'icône disparaît et le LIBELLÉ reste. Trois boutons dans
   ~76px chacun ne laissent pas la place aux deux : entre une icône décorative
   et le mot qui porte le sens, on garde le mot. Le `title` du bouton « Copier »
   continue d'ailleurs de porter l'intitulé complet. */
@media (max-width:400px){
  .gift-actions .btn{font-size:.72rem;padding-left:6px;padding-right:6px;}
  .gift-actions .btn .icon{display:none;}
}

/* ---- Repli de langue du catalogue ----

   ⚠️ Discret mais LISIBLE : ce badge est la seule chose qui distingue « fiche
   pas encore traduite » de « traduction bâclée ». L'effacer au point qu'on ne
   le voie plus reviendrait à servir le repli en silence, ce que tout ce
   mécanisme existe pour éviter. */
.fallback-badge{
  display:inline-block;margin-top:4px;padding:2px 8px;border-radius:999px;
  background:var(--beige-dark);color:var(--ink-soft);
  font-size:.68rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;
  white-space:nowrap;
}
.fallback-notice{
  display:flex;align-items:center;gap:10px;
  margin:0 0 20px;padding:12px 16px;border-radius:12px;
  background:var(--gold-soft);border:1px solid var(--gold);
  color:var(--ink);font-size:.92rem;
}

/* ================================================================
   Secret Santa
   ================================================================ */
/* Liste de participants / d'exclusions : le libellé à gauche, son action à
   droite. Un <ul> dépouillé de ses puces plutôt qu'un tableau — il n'y a
   qu'une colonne de données, et un tableau déborderait en mobile. */
/* Écran de gestion : une CARTE par participant et par exclusion.
   ⚠️ Classes À PART de la bande du tableau de bord (`.santa-band-*`).
   ⚠️ `auto-fill` + `minmax` : c'est la largeur des cartes qui décide du nombre
   de colonnes, sans media query — trois ou quatre en bureau, une en mobile. */
.santa-cards{
  list-style:none;padding:0;margin:0 0 18px;
  display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;
}
/* Texte à gauche (nom + adresse, ou la phrase d'exclusion), action à droite
   (Retirer, ou l'état d'envoi après le tirage) : deux lignes par carte.
   ⚠️ `min-width:0` sur le texte, sinon une adresse longue repousse le bouton
   hors de la carte ; `overflow-wrap:anywhere` parce qu'une adresse e-mail est
   un seul « mot ». */
.santa-card{
  background:var(--paper);border:1px solid var(--line);border-radius:14px;
  box-shadow:var(--shadow);padding:12px 14px;
  display:flex;align-items:center;justify-content:space-between;gap:12px;
}
.santa-card-body{display:flex;flex-direction:column;min-width:0;}
.santa-card-body > *{overflow-wrap:anywhere;}
/* La phrase d'une exclusion (« **Alice** n'offre pas à **Bob** ») : en flux
   normal, sinon la colonne flex empile ses morceaux. */
.santa-card-body.santa-card-body--sentence{display:block;overflow-wrap:anywhere;}
.santa-card > form,
.santa-card > .santa-state{flex-shrink:0;}

/* La carte d'AJOUT, dernière de la grille : pointillés et fond transparent,
   pour se lire comme « la prochaine carte à remplir » et non comme une entrée
   de plus. Ses champs sont empilés : la carte n'a qu'une colonne de large.
   ⚠️ `.santa-card.santa-card--add` : on gagne sur `.santa-card` par la
   SPÉCIFICITÉ, jamais par la position dans le fichier. */
.santa-card.santa-card--add{
  background:transparent;box-shadow:none;border:2px dashed var(--line);
  display:block;
}
.santa-card--add form{display:flex;flex-direction:column;gap:8px;}
/* UNE TEINTE PAR SECTION : vert pour ceux qui participent, corail pour ceux
   qui ne doivent pas se tirer. Les deux grilles étaient identiques, on ne
   voyait pas d'un coup d'œil dans laquelle on se trouvait.
   ⚠️ CORAIL (`--berry-soft`) et non un rouge franc : une grille rouge se
   lirait comme une série d'erreurs.
   ⚠️ La grille des ADRESSES (après le tirage) reste blanche : aucune demande.
   ⚠️ La carte d'AJOUT garde la teinte de sa section mais des pointillés
   FONCÉS — c'est le pointillé qui dit « à remplir » (3:1 au moins sur le fond,
   vérifié : 4,70 vert, 3,68 corail). Sélecteur à trois classes pour gagner sur
   `.santa-cards--x .santa-card` par la spécificité, pas par l'ordre. */
.santa-cards--participants .santa-card{background:var(--mint-soft);border-color:#C6E9D5;}
.santa-cards--exclusions .santa-card{background:var(--berry-soft);border-color:#F6CFC9;}
.santa-cards--participants .santa-card.santa-card--add{background:var(--mint-soft);border-color:var(--forest-dark);}
.santa-cards--exclusions .santa-card.santa-card--add{background:var(--berry-soft);border-color:var(--berry-dark);}
/* 🔴 CONTRASTE : le gris `--ink-soft` de l'adresse passe sous 4,5:1 sur ces
   fonds (4,29 vert, 4,14 corail). Un gris un cran plus sombre y tient
   (5,20 et 5,02). */
.santa-cards--participants .santa-card .muted,
.santa-cards--exclusions .santa-card .muted{color:#5E685B;}
/* « Retirer » sur FOND BLANC : son rouge tombait à 3,81 et 3,68:1 sur ces
   teintes, il retrouve 4,27 — son contraste sur fond blanc partout ailleurs.
   ⚠️ `:not(:hover)` : sans lui, ce sélecteur plus spécifique écraserait
   l'aplat rouge de `.btn-danger:hover`. */
.santa-cards--participants .btn-danger:not(:hover),
.santa-cards--exclusions .btn-danger:not(:hover){background:var(--paper);}

.santa-card--add input[type=text],
.santa-card--add input[type=email],
.santa-card--add select{width:100%;min-width:0;}
.santa-card-add-title{display:flex;align-items:center;gap:6px;margin:0 0 2px;font-weight:700;color:var(--forest-dark);}
.santa-card-add-title svg{width:18px;height:18px;}

/* Correction d'adresse après le tirage : prénom en tête, champ + bouton sur
   une ligne. ⚠️ `min-width:0` sur le champ, sinon une adresse longue pousse
   le bouton hors de la carte. */
.santa-card.santa-card--email{display:block;}
.santa-card--email form{display:flex;flex-direction:column;gap:8px;}
.santa-email-row{display:flex;align-items:center;gap:8px;}
.santa-email-row input{flex:1 1 auto;min-width:0;}
.santa-email-row .btn{flex-shrink:0;}

/* « Envoyer les invitations » : de l'air avant la section Participants, et
   pleine largeur en mobile — c'est l'action principale de l'écran une fois
   le tirage fait. ⚠️ `.santa-send-form .btn` (0,2,0) l'emporte sur la
   largeur de `.btn` par la spécificité, pas par la position. */
.santa-send-form{margin-bottom:28px;}
@media (max-width:640px){
  .santa-send-form .btn{width:100%;}
}
/* ⚠️ `flex-wrap` et non une media query : ces rangées portent deux ou quatre
   champs selon le formulaire, et c'est leur contenu qui décide quand passer à
   la ligne — pas une largeur d'écran devinée à l'avance. */
.santa-add{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 24px;}
.santa-add input[type=text],
.santa-add input[type=email],
.santa-add select{flex:1 1 160px;min-width:0;}
/* ---- Page d'un participant : la carte de révélation ---- */
.santa-reveal-card{padding:28px 24px;}
.santa-reveal-card > p{margin:0 0 10px;}
.santa-reveal-card form{margin-top:18px;}
@media (max-width:640px){
  /* ⚠️ `.santa-reveal-card .santa-reveal-btn` (0,2,0) l'emporte sur la
     largeur de `.btn` par la spécificité, pas par la position. */
  .santa-reveal-card .santa-reveal-btn{width:100%;}
}

.santa-reveal{position:relative;margin:22px 0 4px;}
.santa-reveal-label{display:block;font-size:1.1rem;color:var(--ink-soft);}
.santa-reveal-name{display:inline-block;font-size:2rem;line-height:1.2;color:var(--forest-dark);margin-top:6px;}

/*
   🔴 L'ANIMATION NE JOUE QUE SOUS `:target`, c'est-à-dire juste après le clic :
   le contrôleur revient sur `#revele`. Revenir plus tard sur la page — au
   moment d'acheter — affiche le nom sans rejouer l'effet. CSS seul, aucun
   script : rien à câbler, rien qui puisse casser sans JavaScript.

   Le nom surgit (échelle + flou qui se dissipe), dix confettis partent du
   centre. Les confettis sont des `<i>` vides, `aria-hidden` sur leur conteneur.
*/
.santa-confetti{position:absolute;inset:0;pointer-events:none;overflow:visible;}
.santa-confetti i{
  position:absolute;left:50%;top:65%;width:8px;height:12px;border-radius:2px;
  opacity:0;background:var(--gold);
}
.santa-confetti i:nth-child(3n+1){background:var(--berry);}
.santa-confetti i:nth-child(3n+2){background:var(--forest);}
.santa-confetti i:nth-child(1){--x:-120px;--y:-70px;--r:-200deg;}
.santa-confetti i:nth-child(2){--x:-80px;--y:-110px;--r:160deg;}
.santa-confetti i:nth-child(3){--x:-30px;--y:-130px;--r:-120deg;}
.santa-confetti i:nth-child(4){--x:25px;--y:-125px;--r:220deg;}
.santa-confetti i:nth-child(5){--x:75px;--y:-105px;--r:-160deg;}
.santa-confetti i:nth-child(6){--x:120px;--y:-65px;--r:190deg;}
.santa-confetti i:nth-child(7){--x:-140px;--y:-10px;--r:140deg;}
.santa-confetti i:nth-child(8){--x:140px;--y:-15px;--r:-140deg;}
.santa-confetti i:nth-child(9){--x:-60px;--y:40px;--r:260deg;}
.santa-confetti i:nth-child(10){--x:65px;--y:35px;--r:-240deg;}

#revele:target .santa-reveal-label{animation:santa-fade-up .45s ease-out both;}
#revele:target .santa-reveal-name{animation:santa-pop .75s cubic-bezier(.2,1.5,.4,1) .2s both;}
#revele:target .santa-confetti i{animation:santa-burst 1.1s cubic-bezier(.1,.7,.3,1) .35s both;}

@keyframes santa-fade-up{
  from{opacity:0;transform:translateY(8px);}
  to{opacity:1;transform:none;}
}
@keyframes santa-pop{
  0%{opacity:0;transform:scale(.3) rotate(-8deg);filter:blur(8px);}
  60%{opacity:1;filter:blur(0);}
  100%{opacity:1;transform:none;filter:none;}
}
@keyframes santa-burst{
  0%{opacity:1;transform:translate(-50%,-50%) rotate(0);}
  100%{opacity:0;transform:translate(calc(-50% + var(--x)),calc(-50% + var(--y))) rotate(var(--r));}
}

/* ⚠️ Mouvement réduit : le nom s'affiche directement, sans confettis. */
@media (prefers-reduced-motion:reduce){
  #revele:target .santa-reveal-label,
  #revele:target .santa-reveal-name,
  #revele:target .santa-confetti i{animation:none;}
}
/* ⚠️ Écartées du reste et discrètes : ce sont des actions qu'on ne fait qu'une
   fois, et jamais par hasard. Elles portent toutes les deux une confirmation. */
/* ⚠️ Après le tirage seulement : en préparation, « Supprimer » est à côté de
   « Lancer le tirage », dans `.panel-actions`. */
.santa-delete{margin-top:28px;padding-top:18px;border-top:1px solid var(--line);}
/* Retrait d'adresse, en bas de la carte de révélation : une ligne grise et
   un bouton qui a l'air d'un lien. ⚠️ C'est bien un `<button>` dans un
   formulaire POST, jamais un `<a>` : les filtres de messagerie préchargent
   les liens, et un retrait en GET se déclencherait tout seul. */
.santa-forget-inline{margin:22px 0 0;font-size:.82rem;color:var(--ink-soft);}
.santa-forget-link{
  background:none;border:0;padding:0;font:inherit;color:inherit;
  text-decoration:underline;cursor:pointer;
}
.santa-forget-link:hover{color:var(--ink);}
.santa-forget-link:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:2px;}
/* Barre de progression de l'envoi. ⚠️ Un <div> et non un <progress> : celui-ci
   ne se stylise pas de la même façon d'un navigateur à l'autre, et la valeur
   est déjà annoncée en toutes lettres juste en dessous — le rôle `status` et
   `aria-live` du panneau font le travail pour les lecteurs d'écran. */
.santa-progress{text-align:center;max-width:420px;}
.santa-progress-bar{height:10px;border-radius:999px;background:var(--beige);overflow:hidden;margin:18px 0 12px;}
.santa-progress-bar span{display:block;height:100%;width:0;background:var(--primary);transition:width .2s ease;}
.santa-state{display:flex;align-items:center;gap:8px;flex:none;}
/* Bande des tirages, au tableau de bord. ⚠️ Émise seulement s'il y a un tirage
   — d'où l'absence de tout état vide ici : le gabarit ne rend rien du tout
   plutôt qu'un cadre annonçant qu'il n'a rien à annoncer. */
/* Bande Secret Santa du tableau de bord : le sapin à gauche identifie le bloc,
   une ligne par tirage à droite (voir dashboard/index.html.twig). */
.santa-band{
  background:var(--mint-soft);border:1px solid var(--line);border-radius:16px;
  padding:18px 22px;margin:0 0 24px;
  display:flex;align-items:flex-start;gap:18px;
}
.santa-band-tree{flex:none;width:48px;height:64px;}
.santa-band-body{flex:1;min-width:0;}
.santa-band h2{margin:0 0 10px;font-size:1.05rem;color:var(--forest-dark);}
.santa-band-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:8px;}
/* ⚠️ `flex-wrap` : en bureau tout tient sur une ligne ; en mobile le nom
   reste seul en tête et le reste passe dessous, « Gérer » toujours au bout. */
.santa-band-row{
  display:flex;align-items:center;flex-wrap:wrap;gap:8px 16px;
  background:var(--paper);border:1px solid var(--line);border-radius:12px;
  padding:10px 14px;
}
.santa-band-title{min-width:0;overflow-wrap:anywhere;}
.santa-band-count{display:inline-flex;align-items:center;gap:6px;}
.santa-band-count svg{width:16px;height:16px;}
.santa-band-manage{margin-left:auto;}
@media (max-width:520px){
  .santa-band{padding:16px;gap:12px;}
  .santa-band-tree{width:36px;height:48px;}
  .santa-band-title{flex:1 1 100%;}
}
/* Page publique de Secret Santa.
   ⚠️ NI CARTE NI CONTENEUR BORNÉ : elle occupe toute la largeur du `.wrap`,
   comme la FAQ et le catalogue. Un `max-width` de 720px a existé ici et se
   lisait comme un gros rembourrage à côté des autres pages publiques, qui n'en
   ont aucun.
   ⚠️ Section et titre calqués sur `.faq-section` : même forme pour la même
   chose — un intertitre suivi d'une liste, sur une page faite pour être lue. */
.santa-section{margin-bottom:32px;}

/* Les quatre étapes : le `.steps` de l'accueil, tel quel. On ne neutralise
   ici que ce qu'apporte la balise `<ol>` (puces, retrait) et la marge haute,
   déjà portée par l'en-tête de section.
   ⚠️ `.steps.santa-steps` : on gagne par la SPÉCIFICITÉ, jamais par la
   position dans le fichier. */
.steps.santa-steps{list-style:none;padding:0;margin-top:0;}
.santa-how-head{margin-top:56px;}

/* Titre du « pourquoi » : le `.section-eyebrow` de l'accueil, tel quel
   (« Pourquoi Surprizio ? »). Ce conteneur ne fait que le centrer.
   ⚠️ PAS `.section-head` : sa règle `.section-head h2{font-size:1.9rem}`
   (0,1,1) l'emporterait sur `.section-eyebrow` (0,1,0) et regonflerait le
   titre — celui-ci est un `<h2>`, contrairement au `<span>` de l'accueil.
   ⚠️ Même limite de contraste que sur l'accueil : `--berry` à .8rem reste
   sous le seuil AA du texte normal. Assumé, décision « pareil que l'accueil » ;
   à corriger aux DEUX endroits d'un coup si le sujet revient. */
.santa-why-head{text-align:center;margin-bottom:24px;}
/* Les trois arguments : trois `.card.feature-card`, comme « Pourquoi
   Surprizio » sur l'accueil.
   ⚠️ `.card + .card` pose une marge haute générique : neutralisée ici, sinon
   les cartes 2 et 3 descendent d'un cran dans la grille — même piège que
   `.feature-grid`. */
.santa-arg-grid{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);gap:20px;align-items:stretch;}
.santa-arg-grid .card + .card{margin-top:0;}

@media (max-width:900px){
  .santa-arg-grid{grid-template-columns:1fr;}
}
/* 🔴 Panneau d'aide au DÉVELOPPEMENT — rendu sous `app.environment == 'dev'` et
   nulle part ailleurs. Volontairement laid : hachuré, monospace, marqué
   « [dev] ». Il doit être impossible de le confondre avec l'interface, et
   impossible de ne pas le remarquer s'il apparaissait en production. */
.dev-panel{
  margin:28px 0;padding:14px 16px;border:2px dashed var(--danger);border-radius:10px;
  background:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem;
}
.dev-panel ul{margin:8px 0 0;padding-left:18px;}
/* Page personnelle d'un participant. ⚠️ Centrée et bornée : elle ne porte
   qu'UNE information — sur qui l'on est tombé — et c'est la seule page du
   projet dans ce cas. La laisser courir sur 1120px la ferait flotter dans le
   vide, à l'inverse de la page publique, qui porte des listes. */
.santa-participant{max-width:520px;margin:0 auto;text-align:center;}
/* « Explorer Surprizio » juste sous la carte : ses 44px de marge haute, faits
   pour séparer deux sections de l'accueil, laissaient ici un vide.
   ⚠️ Portée par le combinateur `+` : la règle ne vaut que sur cette page,
   l'accueil garde son espacement. (0,2,0) l'emporte sur `.explore-section`
   par la spécificité. */
.santa-participant + .explore-section{margin-top:20px;}

/* Champ piège du formulaire d'inscription (voir RegistrationBotGuard) : hors de
   l'écran sans être `display:none`, que certains robots savent ignorer. */
.hp-field{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden;}
