/* ==========================================================================
   Fantastique OS, direction artistique (§11 du cahier des charges)
   Bleu nuit, blanc cassé, doré saturé. Une seule famille : Figtree.
   Règle : l'accent doré ne se dilue jamais sur le fond bleu nuit, or et bleu
   sont complémentaires, un doré en transparence sur du bleu donne un gris
   olive. L'accent s'emploie à pleine saturation sur des surfaces réduites.
   ========================================================================== */

:root {
  /* La hauteur de la barre d'écran : 44 px de cible tactile plus ses marges.
     Partagée, parce que le bandeau de mois de la programmation doit tomber
     exactement dessus. */
  --h-barre: 64px;
  --nuit-900:#060B15; --nuit-800:#0B1322; --nuit-700:#131D31; --nuit-600:#1C2942;
  --trait:rgba(255,255,255,.07);
  --trait-fort:rgba(255,255,255,.13);
  --trait-actif:#5D74A0;
  --blanc:#F8F7F3; --blanc-doux:#C9D0DE; --blanc-faible:#96A2BA;
  --or:#F9C846; --or-vif:#FFDC7E; --or-sourd:rgba(249,200,70,.55);
  --opt:#B3A4FF;
  --carte:#1E2C47; --carte-vive:#27395B;
  --erreur:#FF6B85; --alerte:#FFA24C; --succes:#4FCB97;
  --r1:4px; --r2:8px; --r3:12px; --r4:16px; --r5:24px; --r6:32px;
  --cible:48px; --ligne:40px;
  --t-micro:120ms; --t-ecran:240ms;
  --entree:cubic-bezier(.16,.84,.32,1); --rebond:cubic-bezier(.34,1.56,.64,1);
  --ui:Figtree,"Segoe UI",system-ui,-apple-system,sans-serif;
}

* { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
html, body { height:100%; }
body {
  margin:0; background:var(--nuit-900); color:var(--blanc);
  font-family:var(--ui); font-size:16px; line-height:1.5;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
body.plein { display:flex; flex-direction:column; }
button { font:inherit; color:inherit; background:none; border:none; padding:0; cursor:pointer; }
input, select { font:inherit; }
a { color:var(--or); text-underline-offset:3px; }
:focus-visible { outline:3px solid var(--or); outline-offset:2px; border-radius:6px; }

/* ---------- verrou ---------- */
.verrou {
  flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:var(--r5); padding:var(--r6) var(--r5); text-align:center;
}
.marque { font-size:32px; font-weight:800; letter-spacing:-.03em; }
.marque em { font-style:normal; color:var(--or); }
.sous { color:var(--blanc-faible); margin:0; font-size:14.5px; }
.points { display:flex; gap:var(--r3); justify-content:center; height:18px; }
.pt {
  width:14px; height:14px; border-radius:50%; border:2px solid var(--trait-actif);
  transition:transform var(--t-micro) var(--rebond), background var(--t-micro), border-color var(--t-micro);
}
.pt.plein { background:var(--or); border-color:var(--or); transform:scale(1.2); }
@keyframes secousse { 10%,90%{transform:translateX(-4px)} 30%,70%{transform:translateX(6px)} 50%{transform:translateX(-6px)} }
.points.faux { animation:secousse 400ms; }
.points.faux .pt { border-color:var(--erreur); background:var(--erreur); }
.pave-boite { display:flex; flex-direction:column; align-items:center; gap:var(--r5); }
.pave { display:grid; grid-template-columns:repeat(3,86px); gap:var(--r3); }
.pave button {
  height:64px; border-radius:16px; background:var(--nuit-700); border:1px solid var(--trait);
  font-size:22px; font-weight:600; font-variant-numeric:tabular-nums;
  transition:transform var(--t-micro) var(--entree), background var(--t-micro);
}
.pave button:active { transform:scale(.94); background:var(--nuit-600); }
.pave button.util { font-size:14px; font-weight:600; color:var(--blanc-doux); background:transparent; border-color:transparent; }
.msg-verrou { min-height:22px; font-size:13.5px; color:var(--erreur); }


/* ==========================================================================
   La coque, barre commune à tous les écrans
   --------------------------------------------------------------------------
   Elle ne bouge jamais. C'est sa seule justification : avant, chaque surface
   avait son propre en-tête, et passer du planning au matériel déplaçait le
   sélecteur d'univers, faisait disparaître l'accès aux réglages et changeait
   le bouton de verrouillage d'une icône en libellé.

   Grille en trois colonnes 1fr / auto / 1fr : les onglets sont centrés sur
   l'écran, pas posés à la suite de la marque. Un nom d'utilisateur plus long
   ou une action de plus ne les décale donc pas.
   ========================================================================== */
/* ---------- barres de défilement ----------
   Celle du navigateur est grise, large, et posée sur un fond bleu nuit : elle
   se voit plus que le contenu qu'elle sert.

   Les deux écritures ne font pas double emploi et aucune n'est morte :
   `scrollbar-width` / `scrollbar-color` sont les propriétés standard, comprises
   par Firefox et par Chrome depuis la version 121 ; la famille `::-webkit-`
   couvre Safari et les Chrome plus anciens. Là où les deux sont comprises, ce
   sont les standard qui l'emportent, et elles donnent le même résultat. */
* { scrollbar-width:thin; scrollbar-color:var(--carte-vive) transparent; }
::-webkit-scrollbar { width:11px; height:11px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb {
  background:var(--carte-vive); border-radius:99px;
  border:3px solid transparent; background-clip:padding-box;
}
::-webkit-scrollbar-thumb:hover { background:var(--trait-actif); background-clip:padding-box; }
::-webkit-scrollbar-corner { background:transparent; }

.coque { height:100dvh; display:flex; flex-direction:column; overflow:hidden; }

/* ---------- confirmation d'une action globale ----------
   Vit dans la coque, donc sur tous les écrans, y compris le back-office : d'où
   un vocabulaire à elle plutôt que celui de `.modale-admin`, qui est scopé sous
   `.admin` et ne s'appliquerait pas au planning. */
.modale-coque {
  border:1px solid var(--trait-fort); border-radius:18px; background:var(--nuit-700);
  color:var(--blanc); padding:var(--r5); max-width:440px; width:calc(100% - 2 * var(--r4));
  box-shadow:0 30px 80px -20px #000;
}
.modale-coque::backdrop { background:rgba(6,11,21,.74); }
.modale-coque h2 { font-size:20px; font-weight:800; letter-spacing:-.02em; margin:0 0 8px; }
.modale-coque .dc-sous { margin:0; font-size:14.5px; color:var(--blanc-doux); line-height:1.5; }
.dc-pied { display:flex; flex-wrap:wrap; gap:var(--r3); justify-content:flex-end; margin-top:var(--r5); }
.dc-pied button {
  min-height:48px; padding:0 var(--r4); border-radius:12px;
  font-size:15px; font-weight:700; transition:transform var(--t-micro);
}
.dc-pied button:active { transform:scale(.97); }
.dc-annul { border:1px solid var(--trait-actif); color:var(--blanc-doux); }
.dc-ok { background:var(--or); color:var(--nuit-900); }

.coque-haut {
  flex:none; display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
  gap:var(--r3); padding:7px var(--r3);
  background:var(--nuit-800); border-bottom:1px solid var(--trait-fort);
}
.coque-marque {
  justify-self:start; display:inline-flex; align-items:center; min-height:44px;
  padding:0 8px; border-radius:11px;
  font-size:17px; font-weight:800; letter-spacing:-.02em;
  color:var(--blanc); text-decoration:none; white-space:nowrap;
  transition:background var(--t-micro);
}
.coque-marque em { font-style:normal; color:var(--or); }
.coque-marque:hover { background:var(--nuit-700); }

/* ---------- onglets d'univers, grand écran ---------- */
.coque-onglets {
  justify-self:center; display:flex; gap:3px; padding:3px;
  background:var(--nuit-900); border:1px solid var(--trait); border-radius:14px;
}
.coque-onglets a {
  /* 44 px : les onglets restent affichés jusqu'à 820 px, donc sur tablette,
     où ils se touchent au doigt. */
  min-height:44px; display:inline-flex; align-items:center; gap:8px; padding:0 var(--r4);
  border-radius:11px; text-decoration:none; white-space:nowrap;
  font-size:14.5px; font-weight:600; color:var(--blanc-faible);
  border:1px solid transparent;
  transition:background var(--t-micro), color var(--t-micro), border-color var(--t-micro);
}
.coque-onglets a svg { width:17px; height:17px; flex:none; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.coque-onglets a:hover { color:var(--blanc); background:var(--nuit-700); }
.coque-onglets a[aria-current="page"] {
  background:var(--nuit-600); color:var(--blanc); border-color:var(--or-sourd);
}
.coque-onglets a[aria-current="page"] svg { stroke:var(--or); }

/* ---------- identité et actions ---------- */
.coque-droite { justify-self:end; display:flex; align-items:center; gap:6px; min-width:0; }
.identite {
  display:flex; flex-direction:column; justify-content:center; align-items:flex-end;
  min-height:44px; padding:0 var(--r3); border-radius:11px; min-width:0;
  background:var(--nuit-700); border:1px solid var(--trait-fort);
}
.identite .qui-nom {
  font-size:13.5px; font-weight:700; color:var(--blanc); line-height:1.2;
  max-width:22ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.identite .qui-lieu {
  font-size:11px; font-weight:600; color:var(--or); line-height:1.2;
  letter-spacing:.04em; max-width:22ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.coque-icone {
  width:44px; height:44px; flex:none; border-radius:11px; display:grid; place-items:center;
  border:1px solid transparent; color:var(--blanc-doux);
  transition:background var(--t-micro), border-color var(--t-micro), transform var(--t-micro);
}
.coque-icone svg { width:20px; height:20px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.coque-icone:hover { background:var(--nuit-700); color:var(--blanc); }
.coque-icone:active { transform:scale(.92); }
.coque-icone[aria-current="page"] {
  background:var(--nuit-600); border-color:var(--or-sourd); color:var(--or);
}
.coque-droite form { display:contents; }

/* ---------- la zone de l'univers ---------- */
/* `overflow-y:auto` : les écrans longs (réglages, inventaire) défilent ici.
   Le planning, lui, est un enfant `flex:1; min-height:0` : il occupe la
   hauteur disponible exactement et ne déclenche donc jamais ce défilement. */
/* `scrollbar-gutter` réserve la place de la barre de défilement même quand la
   page est courte : sans elle, passer d'un écran qui défile à un écran qui ne
   défile pas décale tout le contenu d'une quinzaine de pixels. */
.coque-corps { flex:1; min-height:0; display:flex; flex-direction:column; overflow-y:auto; overflow-x:hidden; scrollbar-gutter:stable; }

/* ---------- barre du bas, petit écran ---------- */
.coque-bas { display:none; }

@media (max-width:820px) {
  .coque-onglets { display:none; }
  .coque-haut { grid-template-columns:1fr auto; padding:6px var(--r2); }
  .coque-bas {
    flex:none; display:flex; align-items:stretch;
    background:var(--nuit-800); border-top:1px solid var(--trait-fort);
    padding-bottom:env(safe-area-inset-bottom);
  }
  .coque-bas a {
    position:relative; flex:1; min-height:56px; display:flex; flex-direction:column;
    align-items:center; justify-content:center; gap:3px; text-decoration:none;
    font-size:11.5px; font-weight:600; color:var(--blanc-faible);
  }
  .coque-bas a svg { width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
  .coque-bas a[aria-current="page"] { color:var(--or); }
  /* repère court et centré : un trait sur toute la largeur de l'onglet se
     confondait avec la séparation de la barre juste au-dessus */
  .coque-bas a[aria-current="page"]::before {
    content:''; position:absolute; top:0; left:50%; transform:translateX(-50%);
    width:30px; height:3px; border-radius:0 0 3px 3px; background:var(--or);
  }
  .identite .qui-nom { max-width:12ch; }
}
@media (max-width:420px) {
  .identite { padding:0 10px; }
  .identite .qui-nom { max-width:9ch; }
  .coque-marque { font-size:15px; padding:0 4px; }
}

/* ---------- barre d'application ---------- */
/* `min-height` et non une hauteur libre : sur une borne, la barre de la fiche
   se vide de son titre mais doit garder sa place, sinon la fiche remonte de
   soixante pixels et perd la ligne d'horizon commune à tous les écrans. C'est
   aussi cette valeur que reprend le bandeau de mois de la bande. */
.barre {
  display:flex; align-items:center; gap:6px; padding:11px var(--r3) 9px; flex:none;
  min-height:var(--h-barre);
}
.icone-b {
  width:44px; height:44px; border-radius:12px; display:grid; place-items:center; flex:none;
  transition:background var(--t-micro), transform var(--t-micro);
}
.icone-b:active { transform:scale(.9); background:var(--nuit-600); }
.icone-b svg { width:21px; height:21px; stroke:var(--blanc-doux); fill:none; stroke-width:2.1; stroke-linecap:round; stroke-linejoin:round; }

/* Les deux flèches de navigation, accolées en un seul bloc à place fixe.
   Elles n'avaient aucune surface : deux chevrons flottant sur le fond, qu'on
   ne repérait qu'en les cherchant. Elles ont maintenant un cadre, un fond, et
   48 px de côté. */
.pas { display:flex; flex:none; border:1px solid var(--trait-fort); border-radius:13px; overflow:hidden; background:var(--nuit-700); }
.pas .icone-b { width:50px; height:46px; border-radius:0; }
.pas .icone-b + .icone-b { border-left:1px solid var(--trait-fort); }
.pas .icone-b:hover { background:var(--nuit-600); }
.pas .icone-b:active { transform:none; background:var(--carte); }
.pas .icone-b svg { width:23px; height:23px; stroke:var(--blanc); stroke-width:2.4; }
/* `flex:1` : le titre absorbe la place restante, ce qui pousse « Aujourd'hui »
   contre le bord droit, où il ne bouge plus non plus. Les flèches sont avant
   lui, donc rien de ce sur quoi on clique ne dépend de la longueur du mois. */
.titre-mois {
  display:flex; flex-direction:column; align-items:flex-start; gap:3px; justify-content:center;
  padding:6px var(--r3); border-radius:12px; flex:1; min-width:0; min-height:var(--cible);
  transition:background var(--t-micro);
}
.titre-mois:active { background:var(--nuit-600); }
/* Même présentation que le titre du planning, mais purement informative :
   un écran d'univers annonce ce qu'il est, au même endroit et à la même
   taille partout. */
.titre-univers {
  display:flex; flex-direction:column; align-items:flex-start; gap:3px; justify-content:center;
  padding:6px var(--r3); flex:1; min-width:0; min-height:var(--cible);
}
.titre-univers .m { font-size:21px; font-weight:800; letter-spacing:-.025em; line-height:1.1; }
.titre-univers .s { font-size:13.5px; font-weight:500; color:var(--blanc-doux); }
/* Le titre du matériel n'a plus de voisin dans sa barre : il se centre sur la
   largeur, au-dessus du champ auquel il pose la question, et grossit un peu
   puisque plus rien ne se dispute la place. */
.barre.titre-seul { padding:var(--r6) var(--r3) var(--r3); }
.titre-univers.centre { align-items:center; text-align:center; }
.titre-univers.centre .m { font-size:26px; }
.titre-univers.centre .s { font-size:14px; }
@media (max-width:560px) {
  .titre-univers.centre .m { font-size:22px; }
}
.titre-mois .m { font-size:23px; font-weight:800; letter-spacing:-.025em; line-height:1.05; display:flex; align-items:center; gap:7px; white-space:nowrap; }
.titre-mois .m::first-letter { text-transform:uppercase; }
.titre-mois .m svg { width:16px; height:16px; stroke:var(--or); fill:none; stroke-width:2.8; stroke-linecap:round; stroke-linejoin:round; }
.titre-mois .s { font-size:13.5px; font-weight:500; color:var(--blanc-doux); font-variant-numeric:tabular-nums; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.b-plat {
  min-height:44px; padding:0 var(--r3); border-radius:12px; background:var(--nuit-700);
  border:1px solid var(--trait-fort); font-size:14px; font-weight:600; color:var(--blanc-doux);
  flex:none; display:inline-flex; align-items:center; gap:7px;
  transition:background var(--t-micro), transform var(--t-micro);
}
.b-plat { text-decoration:none; }
/* Le pendant doré de `.b-plat` : même gabarit, même seuil tactile, sans le
   `flex:1` de `.principal` qui est fait pour le pied d'un formulaire. Il doit
   marcher sur un lien autant que sur un bouton, d'où le `text-decoration`. */
.b-fort {
  min-height:44px; padding:0 var(--r4); border-radius:12px;
  background:var(--or); color:var(--nuit-900);
  border:1px solid var(--or); font-size:14.5px; font-weight:700;
  flex:none; display:inline-flex; align-items:center; justify-content:center; gap:7px;
  text-decoration:none;
  transition:background var(--t-micro), transform var(--t-micro);
}
.b-fort:active { transform:scale(.95); background:var(--or-vif); }
.b-fort svg { stroke:var(--nuit-900); fill:none; stroke-width:2.5;
              stroke-linecap:round; stroke-linejoin:round; }
.b-plat:active { transform:scale(.95); background:var(--nuit-600); }



/* ---------- borne : identification au moment de l'action ---------- */
.lieu-borne {
  display:inline-flex; align-items:center; min-height:44px; padding:0 var(--r3);
  border-radius:11px; background:var(--nuit-700); border:1px solid var(--trait-fort);
  font-size:13.5px; font-weight:700; color:var(--or); white-space:nowrap;
  max-width:26ch; overflow:hidden; text-overflow:ellipsis;
}
/* L'échappatoire d'une borne : présente pour dépanner, discrète pour ne pas
   inviter tout le monde à ouvrir une session sur la tablette du hall. */
.coque-icone.discret { opacity:.42; }
.coque-icone.discret:hover, .coque-icone.discret:focus-visible { opacity:1; }

/* La même échappatoire, quand il n'y a plus de barre du tout : une borne à
   univers unique montre son écran plein cadre. La cible reste à 44 px : c'est
   discret à l'œil, pas au doigt. */
.echappatoire {
  position:fixed; z-index:15; top:6px; right:6px;
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  color:var(--blanc-faible); opacity:.28; transition:opacity var(--t-ecran);
}
.echappatoire svg { width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:1.9; stroke-linecap:round; stroke-linejoin:round; }
.echappatoire:hover, .echappatoire:focus-visible { opacity:1; color:var(--or); }

/* Une identification passagère se voit : sinon on croit avoir ouvert une
   session, on pose la tablette, et on s'étonne de devoir retaper son code. */
.identite.passagere { border-color:var(--or-sourd); background:var(--carte); }
.identite.passagere .qui-lieu { color:var(--or); }

.modale-pave {
  position:fixed; z-index:40; left:50%; top:50%; transform:translate(-50%,-50%) scale(.96);
  width:min(360px, calc(100% - 2 * var(--r4)));
  background:var(--nuit-800); border:1px solid var(--or-sourd); border-radius:22px;
  padding:var(--r5) var(--r4) var(--r4); text-align:center;
  box-shadow:0 30px 90px -20px #000;
  /* `visibility` et pas seulement `opacity` : un pavé transparent resterait
     dans l'ordre de tabulation et dans l'arbre d'accessibilité, douze boutons
     de chiffres invisibles avant d'atteindre le formulaire, et un dialogue
     annoncé en permanence. Elle est dans la transition pour que le fondu
     d'entrée subsiste. */
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity var(--t-ecran), transform var(--t-ecran) var(--entree),
             visibility 0s linear var(--t-ecran);
}
.modale-pave.on {
  opacity:1; visibility:visible; pointer-events:auto; transform:translate(-50%,-50%) scale(1);
  transition-delay:0s;
}
.modale-pave h4 { margin:0 0 4px; font-size:20px; font-weight:800; letter-spacing:-.02em; }
.modale-pave .mp-sous { margin:0 0 var(--r4); font-size:13.5px; color:var(--blanc-doux); }
.modale-pave .pave-boite { display:flex; flex-direction:column; align-items:center; gap:var(--r3); }
/* `.m-annul` ne porte qu'une bordure et une couleur : sa taille vient de
   `.modale .bt button`, qui ne s'applique pas ici. Sans ces lignes, le bouton
   d'annulation du pavé est un mot souligné de 18 px de haut, au doigt, sur une
   tablette, c'est une cible manquée sur deux. */
.modale-pave .m-annul {
  margin-top:var(--r4); min-height:44px; padding:0 var(--r5);
  border-radius:14px; font-weight:600; font-size:15px;
  transition:transform var(--t-micro);
}
.modale-pave .m-annul:active { transform:scale(.97); }

/* Tant que le pavé est ouvert, ce qu'il y a derrière s'estompe : le formulaire
   reste lisible : c'est ce qu'on est en train de signer, mais cesse d'être la
   chose la plus contrastée de l'écran. */
.panneau.sous-pave { filter:brightness(.55) saturate(.7); transition:filter var(--t-ecran); }

/* ---------- calendrier ---------- */
/* La hauteur de la fenêtre appartient à la coque ; l'univers occupe ce qui
   reste. C'est ce qui permet au planning de recalculer ses 18 lignes pour
   qu'elles tiennent sans défilement, quelle que soit la barre au-dessus. */
.appli { flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden; position:relative; }
/* Le planning tient dans la fenêtre et fait défiler son calendrier lui-même ;
   le matériel, lui, s'allonge avec ses résultats et laisse la page défiler. */
.appli.libre { flex:0 0 auto; overflow:visible; }
.cal-wrap { --colw:138px; flex:1; min-height:0; overflow:auto; position:relative; margin:0 var(--r3); border-radius:18px 18px 0 0; background:var(--nuit-700); }
.cal-head, .cal-body { display:grid; }
.cal-wrap[data-mode="semaine"] .cal-head,
.cal-wrap[data-mode="semaine"] .cal-body { grid-template-columns:50px repeat(7,minmax(var(--colw),1fr)); min-width:calc(50px + 7 * var(--colw)); }
.cal-wrap[data-mode="jour"] .cal-head,
.cal-wrap[data-mode="jour"] .cal-body { grid-template-columns:46px 1fr; min-width:0; }
.cal-wrap[data-mode="jour"] .cal-head { display:none; }
.cal-head { position:sticky; top:0; z-index:4; background:var(--nuit-700); box-shadow:0 1px 0 var(--trait-fort); }
.cal-head .coin { position:sticky; left:0; z-index:5; background:var(--nuit-700); }
.jt { padding:8px 5px 9px; display:flex; flex-direction:column; align-items:center; gap:5px; }
.jt .d { font-size:10.5px; letter-spacing:.16em; text-transform:uppercase; color:var(--blanc-faible); font-weight:600; line-height:1; }
.jt .n { font-size:17px; font-weight:700; font-variant-numeric:tabular-nums; line-height:1; letter-spacing:-.02em; padding:5px 9px; border-radius:99px; }
.jt[data-auj="oui"] .d { color:var(--or); }
.jt[data-auj="oui"] .n { color:var(--nuit-900); background:var(--or); }

.gouttiere { position:sticky; left:0; z-index:3; background:var(--nuit-700); }
.gh { height:var(--ligne); font-size:10.5px; color:var(--blanc-faible); font-variant-numeric:tabular-nums; text-align:right; padding:6px 10px 0 0; font-weight:500; }
.col { position:relative; box-shadow:inset 1px 0 0 var(--trait); }
.col[data-auj="oui"] { background:rgba(249,200,70,.045); }
.cell { display:block; width:100%; height:var(--ligne); box-shadow:inset 0 -1px 0 var(--trait); transition:background var(--t-micro); }
.cell:hover { background:rgba(255,255,255,.045); }
.cell:active { background:rgba(249,200,70,.12); }
.maintenant { position:absolute; left:0; right:0; height:0; z-index:2; pointer-events:none; border-top:2px solid var(--or); }
.maintenant::before { content:''; position:absolute; left:-4px; top:-5px; width:8px; height:8px; border-radius:50%; background:var(--or); }

.strip { display:none; padding:0 var(--r3) 9px; flex:none; }
.appli[data-mode="jour"] .strip { display:block; }
.g-jours { display:grid; grid-template-columns:repeat(7,1fr); gap:5px; }
.j-chip {
  min-height:48px; border-radius:11px; border:1px solid var(--trait-fort); background:var(--nuit-700);
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
  color:var(--blanc-doux);
  transition:transform var(--t-micro) var(--entree), background var(--t-micro), border-color var(--t-micro), color var(--t-micro);
}
.j-chip:active { transform:scale(.94); }
.j-chip .jn { font-size:9.5px; letter-spacing:.1em; text-transform:uppercase; font-weight:700; opacity:.85; line-height:1; }
.j-chip .jd { font-size:15px; font-weight:700; font-variant-numeric:tabular-nums; line-height:1; letter-spacing:-.02em; }
.j-chip[aria-pressed="true"] { background:var(--or); border-color:var(--or); color:var(--nuit-900); }
.j-chip .auj-pt { width:5px; height:5px; border-radius:50%; background:var(--or); margin-top:1px; }
.j-chip[aria-pressed="true"] .auj-pt { background:var(--nuit-900); }

.ev {
  position:absolute; border-radius:10px; padding:6px 8px; overflow:hidden; text-align:left;
  font-size:12px; line-height:1.3; background:var(--carte); border:1.5px solid var(--or); color:var(--blanc);
  box-shadow:0 2px 8px -4px rgba(0,0,0,.6);
  display:flex; flex-direction:column; gap:1px;
  transition:transform var(--t-micro) var(--entree), background var(--t-micro);
  animation:evIn 300ms var(--entree) backwards;
}
@keyframes evIn { from{opacity:0; transform:translateY(5px) scale(.97)} to{opacity:1; transform:none} }
.ev:active { transform:scale(.97); }
.ev:hover { background:var(--carte-vive); border-color:var(--or-vif); }
.ev[data-etat="brouillon"] { border:1.5px dashed var(--opt); }
.ev[data-etat="brouillon"] .t { color:var(--opt); }
.ev .t { font-weight:600; letter-spacing:-.01em; overflow-wrap:break-word; hyphens:auto; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.ev .h { color:var(--blanc-doux); font-variant-numeric:tabular-nums; font-size:11px; font-weight:500; white-space:nowrap; }
.ev .e { color:var(--blanc-doux); font-size:10.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ev .badge { align-self:flex-start; margin-top:1px; font-size:9.5px; font-weight:700; letter-spacing:.09em; text-transform:uppercase; color:var(--opt); }
.ev[data-dense="oui"] { padding:5px 4px; font-size:9.5px; gap:0; }
.ev[data-dense="oui"] .t { -webkit-line-clamp:3; line-height:1.28; letter-spacing:-.035em; }
.ev[data-dense="oui"] .h { font-size:9.5px; margin-top:2px; letter-spacing:-.02em; }
.ev[data-min="oui"] { flex-direction:row; align-items:baseline; gap:5px; padding:4px 6px; }
.ev[data-min="oui"] .t { -webkit-line-clamp:1; flex:1; min-width:0; }

.pied { padding:9px var(--r4) 11px; display:flex; gap:var(--r3); flex:none; background:var(--nuit-800); }
.principal {
  flex:1; min-height:46px; border-radius:13px; background:var(--or); color:var(--nuit-900);
  font-size:15.5px; font-weight:700; letter-spacing:-.015em;
  display:flex; align-items:center; justify-content:center; gap:9px;
  transition:transform var(--t-micro) var(--entree), background var(--t-micro);
}
.pied .principal { max-width:380px; margin:0 auto; }
.principal:active { transform:scale(.97); background:var(--or-vif); }
.principal[disabled] { background:transparent; color:var(--blanc-faible); border:1px solid var(--trait-actif); cursor:not-allowed; }
.principal[disabled] svg { stroke:var(--blanc-faible); }
.principal svg { width:19px; height:19px; stroke:var(--nuit-900); fill:none; stroke-width:2.5; stroke-linecap:round; stroke-linejoin:round; }

/* Le bouton pendant l'aller-retour vers Odoo.
   Il garde ses couleurs d'action plutôt que celles d'un bouton désactivé : il
   travaille, il n'est pas hors service, et le griser dirait le contraire de ce
   qu'il fait. L'anneau est en `::before` pour ne rien devoir au HTML, et le
   bouton garde la largeur mesurée avant le clic, sinon la barre d'actions se
   réorganise sous le doigt au pire moment. */
/* Il sert au bouton doré de l'enregistrement comme au bouton plat de la
   synchronisation : il ne fixe donc aucune couleur et garde celles du bouton.
   L'anneau se dessine en `currentColor`, ce qui le rend juste sur un fond doré
   comme sur un fond sombre, sans une règle par variante. */
button.occupe[disabled] { cursor:progress; opacity:.9; }
.principal.occupe[disabled] { background:var(--or); color:var(--nuit-900); border:none; }
button.occupe::before {
  content:""; width:15px; height:15px; margin-right:9px; flex:none;
  /* La première déclaration est le repli : `color-mix` demande un navigateur
     récent, et sans elle l'anneau serait invisible sur les anciens. */
  border:2px solid rgba(128,128,128,.35);
  border:2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-top-color:currentColor;
  border-radius:50%; animation:tourne .7s linear infinite;
}
@keyframes tourne { to { transform:rotate(360deg); } }
/* Une animation qui tourne sans fin est exactement ce que ce réglage vise. Le
   bouton reste alors marqué, sans bouger. */
@media (prefers-reduced-motion: reduce) {
  button.occupe::before { animation:none; border-top-color:currentColor; opacity:.5; }
}

/* ---------- voile, panneaux, modale ---------- */
.voile { position:fixed; inset:0; background:rgba(6,11,21,.74); z-index:20; opacity:0; pointer-events:none; transition:opacity var(--t-ecran); }
.voile.on { opacity:1; pointer-events:auto; }
.panneau {
  position:fixed; left:0; right:0; bottom:0; z-index:21;
  background:var(--nuit-800); border-top:1px solid var(--or-sourd);
  border-radius:24px 24px 0 0; max-height:97%; display:none; flex-direction:column;
  box-shadow:0 -24px 70px -24px #000;
}
.panneau.on { display:flex; animation:monte var(--t-ecran) var(--entree); }
@keyframes monte { from{transform:translateY(26px); opacity:0} to{transform:none; opacity:1} }
.p-tete { display:flex; align-items:center; gap:var(--r2); padding:14px var(--r4) 12px; flex:none; box-shadow:0 1px 0 var(--trait); }
.p-tete h3 { font-size:23px; font-weight:800; margin:0; letter-spacing:-.025em; flex:1; min-width:0; }
.p-corps { overflow-y:auto; padding:20px var(--r4) 12px; display:grid; gap:20px var(--r6); align-content:start; }
@media (min-width:760px) {
  .p-corps { grid-template-columns:1fr 1fr; padding:20px var(--r5) 14px; max-width:1100px; margin:0 auto; width:100%; }
  .p-corps > .large { grid-column:1 / -1; }
}
.bloc { display:flex; flex-direction:column; gap:var(--r3); min-width:0; }
.etiquette { font-size:11px; letter-spacing:.15em; text-transform:uppercase; color:var(--blanc-faible); font-weight:600; }
.saisie {
  width:100%; min-height:56px; padding:0 var(--r4); border-radius:14px;
  border:1px solid var(--trait-actif); background:var(--nuit-700); color:var(--blanc);
  font-size:16.5px; font-weight:600; outline:none;
  transition:border-color var(--t-micro), background var(--t-micro);
}
.saisie::placeholder { color:var(--blanc-faible); font-weight:400; }
.saisie:focus { border-color:var(--or); background:var(--nuit-600); }
/* L'icône de calendrier, et la leçon qu'elle a coûtée deux fois.
   `color-scheme:dark` fait déjà deux choses : il rend le calendrier déroulant
   du navigateur en sombre, ET il dessine le pictogramme du champ en clair. Le
   `filter:invert()` posé par-dessus, censé éclaircir une icône noire,
   la **renoircissait** donc : deux corrections justes séparément, fausses
   ensemble, et le symptôme était exactement celui qu'on croyait avoir réparé.
   On ne corrige plus l'icône du système, on la remplace par la nôtre, dessinée
   dans la couleur du texte. Elle ne dépend alors d'aucun réglage du navigateur.
   Firefox ignore ce pseudo-élément et garde l'icône claire de `color-scheme`. */
input[type="date"], input[type="datetime-local"] { color-scheme:dark; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  width:18px; height:18px; cursor:pointer; opacity:.75; filter:none;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C9D0DE' stroke-width='2' stroke-linecap='round'%3E%3Crect x='3' y='4.6' width='18' height='16.4' rx='2'/%3E%3Cpath d='M3 9.6h18M8 2.8v3.6M16 2.8v3.6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:center; background-size:18px 18px;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator { opacity:1; }

/* Un nombre de colonnes figé (9, puis 6) tient sur un écran large et casse
   ailleurs : sur une tablette en portrait, où le formulaire passe à deux
   colonnes vers 800 px, chaque pastille d'heure tombait à 36 px de large. On
   laisse donc la grille se replier plutôt que se comprimer, 46 px est la
   largeur en dessous de laquelle « 12h » n'est plus une cible, seulement une
   étiquette. */
.g-heures, .g-durees { display:grid; grid-template-columns:repeat(auto-fit,minmax(46px,1fr)); gap:5px; }
.mini {
  min-height:48px; border-radius:11px; border:1px solid var(--trait-fort); background:var(--nuit-700);
  color:var(--blanc-doux); font-size:13.5px; font-weight:600; font-variant-numeric:tabular-nums;
  letter-spacing:-.02em; display:flex; align-items:center; justify-content:center; padding:0 2px;
  transition:transform var(--t-micro) var(--entree), background var(--t-micro), border-color var(--t-micro), color var(--t-micro);
}
.mini:active { transform:scale(.93); }
.mini[aria-pressed="true"] { background:var(--or); border-color:var(--or); color:var(--nuit-900); }
/* l'indisponibilité n'est jamais portée par la seule couleur (§11.7) */
.mini[disabled] { cursor:not-allowed; color:var(--blanc-faible); opacity:.45; border-style:dashed; background:transparent; text-decoration:line-through; text-decoration-thickness:1px; }
/* Occupé, mais ouvert à qui a le droit de superposer. Le trait discontinu dit
   « pris », comme sur un créneau fermé, et c'est voulu : l'information est la
   même. Ce qui change est qu'on peut le prendre, donc ni le texte barré, qui
   annonce un choix impossible, ni l'opacité de la version fermée, qui le sort
   du regard. La bordure d'alerte porte la nuance, et la question se pose à
   l'enregistrement, où l'on saura par-dessus quoi l'on passe. */
.mini.occupe-mais, .esp.occupe-mais { border-style:dashed; border-color:var(--alerte); }
.mini.occupe-mais { color:var(--alerte); }
.mini.occupe-mais[aria-pressed="true"], .esp.occupe-mais[aria-pressed="true"] { border-color:var(--or); }
.mini.occupe-mais[aria-pressed="true"] { color:var(--nuit-900); }

.espaces { display:flex; flex-direction:column; gap:6px; }
.esp {
  display:flex; align-items:center; gap:var(--r3); text-align:left; width:100%;
  min-height:56px; padding:7px var(--r3); border-radius:14px;
  border:1px solid var(--trait-fort); background:var(--nuit-700);
  transition:transform var(--t-micro) var(--entree), border-color var(--t-micro), background var(--t-micro);
}
.esp:active { transform:scale(.99); }
.esp[aria-pressed="true"] { border-color:var(--or); background:var(--carte); }
.esp[disabled] { opacity:.5; cursor:not-allowed; border-style:dashed; }
.esp .case { width:26px; height:26px; border-radius:9px; flex:none; border:2px solid var(--trait-actif); display:grid; place-items:center; transition:background var(--t-micro), border-color var(--t-micro), transform var(--t-micro) var(--rebond); }
.esp[aria-pressed="true"] .case { background:var(--or); border-color:var(--or); transform:scale(1.1); }
.esp .case svg { width:15px; height:15px; stroke:var(--nuit-900); stroke-width:3.2; fill:none; stroke-linecap:round; stroke-linejoin:round; opacity:0; transition:opacity var(--t-micro); }
.esp[aria-pressed="true"] .case svg { opacity:1; }
.esp .txt { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.esp .nom { font-weight:600; font-size:15.5px; letter-spacing:-.01em; }
.esp .det { font-size:12.5px; color:var(--blanc-doux); }
.esp .det.occ { color:var(--alerte); }
.esp.tout { border-style:dashed; border-color:var(--or-sourd); }

.duo { display:flex; gap:var(--r3); }
.duo > button { flex:1; min-height:58px; margin:0; max-width:none; }
.brouillon {
  border-radius:15px; border:1px solid var(--trait-actif); background:var(--nuit-700); color:var(--blanc);
  font-size:15.5px; font-weight:600; letter-spacing:-.015em;
  display:flex; align-items:center; justify-content:center; gap:8px;
  transition:transform var(--t-micro) var(--entree), background var(--t-micro);
}
.brouillon:active { transform:scale(.97); background:var(--nuit-600); }
.brouillon[disabled] { opacity:.4; cursor:not-allowed; }
.brouillon svg { width:18px; height:18px; stroke:var(--opt); fill:none; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round; }
@media (max-width:600px) { .duo { flex-direction:column-reverse; } }

.fiche { display:flex; flex-direction:column; gap:11px; margin:0; }
.fiche .l { display:flex; gap:var(--r3); font-size:14.5px; align-items:baseline; }
.fiche .l dt { color:var(--blanc-faible); min-width:104px; flex:none; margin:0; }
.fiche .l dd { margin:0; font-weight:600; }
.marqueur { display:inline-flex; align-items:center; gap:7px; font-size:11.5px; font-weight:700; letter-spacing:.09em; text-transform:uppercase; padding:6px 13px; border-radius:99px; background:var(--carte); }
.marqueur[data-etat="confirmee"] { color:var(--or); border:1px solid var(--or); }
.marqueur[data-etat="brouillon"] { color:var(--opt); border:1px dashed var(--opt); }
.actions-v { display:flex; flex-direction:column; gap:7px; }
.action-l {
  display:flex; align-items:center; gap:var(--r3); text-align:left; width:100%;
  min-height:56px; padding:0 var(--r4); border-radius:14px;
  border:1px solid var(--trait-fort); background:var(--nuit-700); font-weight:600; font-size:15.5px;
  transition:transform var(--t-micro), background var(--t-micro), border-color var(--t-micro);
}
.action-l:active { transform:scale(.98); background:var(--nuit-600); }
.action-l svg { width:20px; height:20px; fill:none; stroke-width:2.1; stroke-linecap:round; stroke-linejoin:round; stroke:var(--or); flex:none; }
.action-l.sup { border-color:rgba(255,107,133,.5); color:var(--erreur); }
.action-l.sup svg { stroke:var(--erreur); }

.modale {
  position:fixed; z-index:30; left:50%; top:50%; transform:translate(-50%,-50%);
  width:min(90%,420px); background:var(--nuit-700); border:1px solid rgba(255,107,133,.55);
  border-radius:20px; padding:var(--r5); display:none; flex-direction:column; gap:var(--r4);
  box-shadow:0 34px 90px -22px #000;
}
.modale.on { display:flex; animation:popm 220ms var(--rebond); }
@keyframes popm { from{transform:translate(-50%,-50%) scale(.9); opacity:0} to{transform:translate(-50%,-50%) scale(1); opacity:1} }
.modale h4 { font-size:22px; font-weight:800; margin:0; letter-spacing:-.025em; }
.modale p { margin:0; font-size:14.5px; color:var(--blanc-doux); }
.modale .bt { display:flex; gap:var(--r3); }
.modale .bt button { flex:1; min-height:52px; border-radius:14px; font-weight:600; font-size:15px; transition:transform var(--t-micro); }
.modale .bt button:active { transform:scale(.97); }
.m-annul { border:1px solid var(--trait-actif); color:var(--blanc-doux); }
.m-sup { background:var(--erreur); color:var(--nuit-900); font-weight:700; }

/* `position:absolute` dans `.appli`, sur toute la largeur : le panneau recouvre
   la barre de navigation entière, flèches, titre et « Aujourd'hui » compris, au
   lieu de s'ouvrir dessous en en laissant dépasser la moitié. Le voile
   (z-index 20) passe derrière lui et devant le reste, donc plus rien de ce
   qu'il cache n'est cliquable.

   Le panneau y gagne : à cette largeur les douze mois tiennent sur une ou deux
   rangées de grandes cibles, au lieu d'une colonne de trois à viser. */
.mois-pop {
  position:absolute; z-index:26; left:var(--r3); right:var(--r3); top:6px;
  background:var(--nuit-700); border:1px solid var(--or-sourd); border-radius:20px;
  padding:var(--r4); display:none; flex-direction:column; gap:var(--r3);
  box-shadow:0 28px 70px -22px #000;
}
.mois-pop.on { display:flex; animation:monte 200ms var(--entree); }
.mp-tete { display:flex; align-items:center; justify-content:center; gap:var(--r4); }
.mp-tete .an { font-size:18px; font-weight:800; font-variant-numeric:tabular-nums; }
.mp-grille { display:grid; grid-template-columns:repeat(auto-fit,minmax(92px,1fr)); gap:7px; }
.mp-grille button { min-height:var(--cible); border-radius:12px; border:1px solid var(--trait-fort); background:var(--nuit-600); font-size:13.5px; font-weight:600; transition:background var(--t-micro), transform var(--t-micro); }
.mp-grille button:active { transform:scale(.95); }
.mp-grille button[aria-pressed="true"] { background:var(--or); border-color:var(--or); color:var(--nuit-900); }

.toast {
  position:fixed; z-index:40; left:50%; bottom:100px; transform:translateX(-50%) translateY(12px);
  background:var(--or); color:var(--nuit-900); border-radius:99px;
  padding:13px 24px; font-weight:700; font-size:14.5px; letter-spacing:-.01em;
  display:flex; align-items:center; gap:9px; max-width:88%;
  opacity:0; pointer-events:none; transition:opacity 200ms, transform 260ms var(--rebond);
  box-shadow:0 14px 40px -12px #000;
}
.toast.on { opacity:1; transform:translateX(-50%) translateY(0); }
.toast.erreur { background:var(--erreur); }
.toast svg { width:18px; height:18px; stroke:var(--nuit-900); fill:none; stroke-width:3; stroke-linecap:round; stroke-linejoin:round; flex:none; }

@media (max-width:640px) {
  .titre-mois .m { font-size:19px; }
  .titre-mois .s { font-size:12.5px; }
  .barre { gap:3px; padding:10px 8px 8px; }
  .b-plat { padding:0 10px; font-size:13px; }
}

/* ==========================================================================
   Back-office
   --------------------------------------------------------------------------
   Vocabulaire volontairement court, pour que les écrans à venir (matériel,
   incidents, notes de frais) se composent sans réinventer de mise en page :

     .admin-corps > .rail + .admin-page   la charpente de toute page
     .cadre / .cadre-tete                 un bloc de travail titré
     .form-grille (+ .paire)              un formulaire de saisie
     .tw > table.t                        une liste
     .message / .encart-code              le retour d'une action
     .aide, .jeton, .pastille-etat        les petits éléments de texte

   Un style en ligne dans un gabarit d'administration est le signe qu'il
   manque un mot à cette liste : mieux vaut l'ajouter ici.

   TOUTES les règles ci-dessous sont préfixées par `.admin`. Ce n'est pas de la
   coquetterie : `.panneau` désignait déjà le tiroir de réservation du planning,
   `position:fixed; display:none`. Sans portée, le formulaire de création de
   compte héritait de ce `display:none` et disparaissait, sans erreur, sans
   rien dans la console. Un back-office qui va accueillir dix écrans de plus ne
   peut pas partager son vocabulaire avec le reste de l'application.
   ========================================================================== */
/* Pleine largeur, comme le planning et le matériel.
   Avant : `max-width:1320px; margin:0 auto`. Dans un conteneur flex, une marge
   automatique sur l'axe transverse **empêche l'étirement** : le bloc reprenait
   sa largeur de contenu, puis se recentrait. Résultat, le rail des rubriques
   sautait de 196 px entre deux pages, 316 sur « Utilisateurs » (tableau large),
   512 sur « Vue d'ensemble » (contenu court), et la personne devait retrouver
   le menu à chaque clic. Un menu doit être au même endroit, toujours. */
.admin { width:100%; padding:var(--r4) var(--r5) var(--r6); }

/* ---------- charpente : rail + page ---------- */
/* 244 et non 216 : l'icône et son écart prennent 29 px, et « Rôles et
   permissions » passait sur deux lignes. Un menu dont les entrées font une ou
   deux lignes selon le mot se lit moins vite qu'un menu régulier. */
.admin .admin-corps { display:grid; grid-template-columns:244px minmax(0,1fr); gap:var(--r5); align-items:start; }

.admin .rail-resume { display:none; }
.admin .rail { position:sticky; top:var(--r4); display:flex; flex-direction:column; gap:var(--r4); }
.admin .rail-groupe { display:flex; flex-direction:column; gap:2px; }
.admin .rail-titre {
  font-size:10.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--blanc-faible); margin:0 0 6px; padding:0 var(--r3);
}
/* L'icône n'est pas une décoration : à dix rubriques, elle permet de retrouver
   « Bornes » sans relire les dix libellés. Elle reste grise et prend la couleur
   du texte quand la rubrique est ouverte, pour ne pas faire dix taches dorées. */
.admin .rail-lien > svg {
  width:18px; height:18px; flex:none; fill:none; stroke:currentColor;
  stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; opacity:.72;
}
.admin .rail-lien[aria-current="page"] > svg { opacity:1; stroke:var(--or); }
.admin .rail-lien > span { min-width:0; overflow:hidden; text-overflow:ellipsis; }
.admin .rail-lien {
  min-height:44px; display:flex; align-items:center; gap:11px; padding:0 var(--r3);
  border-radius:11px; color:var(--blanc-doux); text-decoration:none;
  font-size:14.5px; font-weight:600; border:1px solid transparent;
  transition:background var(--t-micro), color var(--t-micro);
}
.admin .rail-lien:hover { background:var(--nuit-700); color:var(--blanc); }
.admin .rail-lien[aria-current="page"] {
  background:var(--nuit-600); color:var(--blanc); border-color:var(--trait-fort);
  box-shadow:inset 3px 0 0 var(--or);
}

.admin .page-tete { margin-bottom:var(--r4); }
.admin .page-tete h1 { font-size:27px; font-weight:800; letter-spacing:-.03em; margin:0; }
.admin .page-tete p { margin:4px 0 0; font-size:14.5px; color:var(--blanc-faible); }

/* ---------- blocs ---------- */
/* « cadre » et non « bloc » : `.bloc` désigne déjà une colonne du panneau de
   réservation du planning, en `display:flex; flex-direction:column`. Ces
   règles s'appliquaient à chaque panneau du back-office sans que rien ne le
   signale. Voir tests/test_css.py. */
.admin .cadre {
  border:1px solid var(--trait-fort); background:var(--nuit-700); border-radius:16px;
  padding:var(--r4); margin-bottom:var(--r4);
}
.admin .cadre-tete {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--r3);
  margin-bottom:var(--r4);
}
.admin .cadre-tete h2 { font-size:18px; font-weight:700; letter-spacing:-.02em; margin:0; flex:1; min-width:0; }
/* Un titre accompagné de sa description : c'est le bloc qui prend la place, et
   le titre ne s'étire plus lui-même, sinon il pousserait la description en bas
   de la colonne. */
.admin .cadre-tete > .nom-et-detail { flex:1; }
.admin .cadre-tete > .nom-et-detail > h2 { flex:none; }

.admin .cartes { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:var(--r3); margin-bottom:var(--r4); }
.admin .tuile {
  border:1px solid var(--trait-fort); background:var(--nuit-700); border-radius:16px;
  padding:var(--r4); display:flex; flex-direction:column; gap:2px;
  text-decoration:none; color:inherit;
  transition:border-color var(--t-micro), transform var(--t-micro) var(--entree);
}
.admin .tuile:hover { border-color:var(--trait-actif); transform:translateY(-2px); }
.admin .tuile .v { font-size:30px; font-weight:800; letter-spacing:-.03em; font-variant-numeric:tabular-nums; }
.admin .tuile .l { font-size:13px; color:var(--blanc-faible); }

/* ---------- formulaires ----------
   Grille, et non rangée en flex. La version précédente utilisait
   `display:flex; align-items:flex-end` : dès qu'un champ devenait plus haut
   que les autres, une liste de rôles qui passe sur deux lignes, tous les
   champs courts étaient poussés vers le bas de la rangée et se retrouvaient
   désalignés avec leurs propres libellés. `align-items:start` sur une grille
   supprime la cause : chaque champ commence en haut de sa cellule, quelle que
   soit la hauteur de ses voisins. */
.admin .form-grille {
  /* 230 et non 190 : en dessous, une liste déroulante coupe ses propres
     options, « tablette partagé », « Salles & réserva… ». Un champ dont on ne
     peut pas lire la valeur choisie ne remplit pas son office. */
  display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:var(--r3); align-items:start;
}
.admin .form-grille.paire { grid-template-columns:minmax(200px,260px) minmax(0,1fr); }
.admin .form-grille .actions {
  grid-column:1/-1; display:flex; flex-wrap:wrap; align-items:center; gap:var(--r3);
  padding-top:var(--r3); border-top:1px solid var(--trait);
}
.admin .champ { display:flex; flex-direction:column; gap:6px; min-width:0; }
/* `:not(.case-role)` n'est pas une precaution : sans lui, ce selecteur
   s'appliquait aussi aux etiquettes des cases a cocher, qui se retrouvaient en
   capitales de 11 px tres espacees, « ADMINISTRATION », « LECTURE SEULE »,
   illisibles alors qu'elles portent le texte que la personne doit lire. */
.admin .champ label:not(.case-role) {
  font-size:11px; letter-spacing:.13em; text-transform:uppercase;
  color:var(--blanc-faible); font-weight:600;
}
.admin .champ input, .admin .champ select {
  width:100%; min-height:48px; padding:0 var(--r3); border-radius:12px;
  border:1px solid var(--trait-actif); background:var(--nuit-600); color:var(--blanc);
  font-size:15px; font-weight:600; outline:none;
}
/* Une liste deroulante etiree sur toute la colonne laissait sa fleche a 150 px
   du mot qu'elle concerne : on ne lisait plus les deux comme un seul controle.
   Largeur plafonnee, et chevron dessine a nous plutot que celui du systeme. */
.admin .champ select {
  appearance:none; -webkit-appearance:none; max-width:320px; padding-right:40px;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2396A2BA' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 13px center; background-size:16px;
}
.admin .champ.plein select { max-width:none; }
.admin .champ input:focus, .admin .champ select:focus { border-color:var(--or); }
.admin .champ input:disabled { opacity:.55; cursor:not-allowed; }
.admin .champ.court { max-width:150px; }
.admin .champ.large { grid-column:1/-1; }
.admin .champ.bref { max-width:220px; }
/* Un champ posé directement dans un cadre, après un paragraphe d'explication :
   sans cet écart, son intitulé en capitales colle à la dernière ligne du
   paragraphe et se lit comme sa suite. Dans une `form-grille`, c'est la grille
   qui gère l'espacement, d'où le sélecteur direct. */
.admin .cadre > .champ { margin-top:var(--r4); }

/* ---------- le constructeur de rappel ---------- */
/* La zone de saisie du message. En monospace : on y écrit des emplacements
   entre doubles accolades, et une police proportionnelle rend « {{ » et « }} »
   presque identiques, si bien qu'une accolade manquante ne se voit pas. */
.admin .champ textarea {
  width:100%; min-height:180px; padding:var(--r3); border-radius:12px;
  border:1px solid var(--trait-actif); background:var(--nuit-600);
  color:var(--blanc); font-size:14.5px; line-height:1.6; outline:none;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; resize:vertical;
}
.admin .champ textarea:focus { border-color:var(--or); }
/* Le sélecteur de couleur natif : sans hauteur imposée il fait 24 px de haut au
   milieu de champs de 48, et la ligne se désaligne. */
.admin .champ input[type="color"] {
  padding:5px; height:48px; cursor:pointer;
}

.admin .jetons-emplacements { display:flex; flex-wrap:wrap; gap:8px; }
.admin .jeton-emplacement {
  display:flex; flex-direction:column; gap:2px; align-items:flex-start;
  min-height:44px; padding:7px 12px; border-radius:11px;
  border:1px solid var(--trait-fort); background:var(--nuit-600);
  color:var(--blanc-doux); font:inherit; font-size:12.5px; text-align:left;
  cursor:pointer;
}
.admin .jeton-emplacement:hover { border-color:var(--or); }
.admin .jeton-emplacement .mono {
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12.5px; color:var(--or); font-weight:600;
}
.admin .jeton-emplacement > span:last-child { color:var(--blanc-faible); }

/* Les réglages de la carte, quand la carte est éteinte. Estompés et non
   masqués : les faire disparaître fait sauter la page de trois cents pixels au
   moment où l'on coche, et l'on perd de vue ce qu'on était en train de lire. */
.admin .eteint-bloc { opacity:.4; pointer-events:none; }

/* L'aperçu, présenté comme un message : c'est ce qu'on cherche à juger, et un
   texte brut à côté d'une image ne dit pas à quoi ça ressemblera dans le
   salon. */
.admin .apercu-discord {
  border:1px solid var(--trait-fort); border-radius:14px;
  background:var(--nuit-800); padding:var(--r4);
  display:flex; flex-direction:column; gap:var(--r3);
}
.admin .apercu-message {
  white-space:pre-wrap; font-size:15px; line-height:1.55; color:var(--blanc);
  margin:0;
}
.admin .apercu-carte {
  display:block; width:100%; max-width:520px; height:auto; border-radius:10px;
  border:1px solid var(--trait);
}

/* Le formulaire d'un espace : sept champs sur une seule ligne, chacun à la
   largeur de ce qu'il reçoit. Une grille à colonnes égales donnait autant de
   place à « Ordre », qui prend un chiffre, qu'à « Nom », et repoussait la case
   « Louable » sur la ligne d'après. En flex, chaque champ déclare sa base et
   le repli se fait tout seul quand la fenêtre rétrécit. */
.admin .grille-espace {
  display:flex; flex-wrap:wrap; gap:var(--r3); align-items:flex-start;
}
.admin .grille-espace > .champ { flex:1 1 190px; min-width:0; }
.admin .grille-espace > .champ.bref { flex:0 1 130px; }
.admin .grille-espace > .champ.minute { flex:0 1 92px; }
.admin .grille-espace > .champ.bascule { flex:0 0 auto; }
.admin .grille-espace > .actions {
  flex:1 0 100%; display:flex; flex-wrap:wrap; align-items:center; gap:var(--r3);
  padding-top:var(--r3); border-top:1px solid var(--trait);
}
/* La case à cocher fait 44 px, un champ de saisie 48 : sans ce décalage la
   bascule flotterait deux pixels trop haut par rapport à ses voisines. */
.admin .grille-espace > .champ.bascule .case-role { min-height:48px; }
@media (max-width:560px) {
  .admin .grille-espace > .champ,
  .admin .grille-espace > .champ.bref,
  .admin .grille-espace > .champ.minute { flex:1 1 100%; }
}
/* Le champ qui porte l'essentiel de la saisie prend deux colonnes : à largeur
   égale, le nom de l'article se retrouvait plus étroit que « Colonne ». */
.admin .champ.large { grid-column:span 2; }
/* En dessous de 560 px la grille n'a qu'une colonne, et `span 2` en réclame
   deux : le navigateur en crée une seconde, implicite, et TOUS les champs de la
   grille passent à 160 px, « borne (libre accès) » s'affichait « bor… ».
   `1 / -1` occupe les colonnes existantes au lieu d'en inventer une. */
@media (max-width:560px) { .admin .champ.large { grid-column:1 / -1; } }
.admin .champ.court.etroit { max-width:112px; }
.admin .actions-pied { margin-top:var(--r3); padding-top:var(--r3); border-top:1px solid var(--trait); }

/* `inline-flex` + centrage, et pas seulement `min-height` : cette classe est
   posée aussi bien sur un <button> que sur un <a> (« Inventorier »). Sur un
   lien, elle héritait du soulignement des liens et laissait le libellé sur la
   ligne de base, collé en haut de son cadre. Une classe de bouton doit donner
   un bouton, quel que soit l'élément qui la porte. */
.admin .bouton {
  display:inline-flex; align-items:center; justify-content:center;
  min-height:48px; padding:0 var(--r4); border-radius:12px; background:var(--or); color:var(--nuit-900);
  font-weight:700; font-size:15px; text-align:center; text-decoration:none;
  transition:transform var(--t-micro), background var(--t-micro);
}
.admin .bouton:active { transform:scale(.97); background:var(--or-vif); }
.admin .bouton.plat { background:var(--nuit-600); color:var(--blanc-doux); border:1px solid var(--trait-actif); }
.admin .bouton.danger { background:transparent; color:var(--erreur); border:1px solid rgba(255,107,133,.5); }
.admin .bouton.petit { min-height:44px; padding:0 var(--r3); font-size:13.5px; }  /* 44 : seuil tactile */

.admin .cases { display:flex; flex-wrap:wrap; gap:var(--r2); align-items:center; }
.admin .case-role {
  display:inline-flex; align-items:center; gap:8px; min-height:44px; padding:0 var(--r3);
  border-radius:11px; border:1px solid var(--trait-fort); background:var(--nuit-600);
  font-size:14px; cursor:pointer; transition:border-color var(--t-micro);
}
.admin .case-role:hover { border-color:var(--trait-actif); }
.admin .case-role:has(input:checked) { border-color:var(--or-sourd); }
.admin .case-role input { width:18px; height:18px; accent-color:var(--or); flex:none; }
/* Une case par ligne, quand chacune porte une explication sous son intitulé :
   côte à côte, sept cases donnaient deux rangées de hauteurs différentes et
   l'on ne savait plus quelle aide allait avec quelle case. */
.admin .cases.cases-empilees { flex-direction:column; align-items:stretch; }
.admin .cases-empilees .case-role {
  align-items:flex-start; padding:10px var(--r3); line-height:1.4;
}
.admin .cases-empilees .case-role input { margin-top:3px; }
.admin .cases-empilees .aide { display:block; }

/* ---------- listes ---------- */
.admin .tw {
  overflow-x:auto; border:1px solid var(--trait-fort); border-radius:14px; background:var(--nuit-700);
  /* dégradé sur le bord droit : une liste qui continue se voit au lieu de
     paraître coupée. Il disparaît quand on est arrivé au bout. */
  background-image:linear-gradient(to left, rgba(6,11,21,.55), transparent 40px);
  background-attachment:local, scroll;
}
.admin table.t { border-collapse:collapse; width:100%; min-width:620px; font-size:14px; }
/* `middle` et non `top` : les cellules qui contiennent un depliant ont une
   hauteur de cible tactile (44 px) alors que les cellules voisines font une
   ligne de texte. Alignees en haut, elles se retrouvaient decalees d'une
   dizaine de pixels les unes par rapport aux autres sur toute la ligne. */
.admin table.t th, .admin table.t td { text-align:left; padding:10px 14px; border-bottom:1px solid var(--trait); vertical-align:middle; }
.admin table.t thead th {
  font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; color:var(--blanc-faible);
  font-weight:700; background:var(--nuit-600); white-space:nowrap; vertical-align:middle;
}
.admin table.t tbody tr:last-child td { border-bottom:none; }
.admin table.t tbody tr:hover { background:rgba(255,255,255,.02); }
.admin table.t td.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12.5px; color:var(--blanc-doux); }
.admin table.t .nowrap { white-space:nowrap; }
.admin table.t .fin { text-align:right; }

/* Colonnes de chiffres : alignées à droite et en chasse tabulaire. Deux tarifs
   l'un au-dessus de l'autre se comparent alors d'un regard au lieu de devoir
   être lus, et c'est tout l'intérêt de mettre une saison en tableau. */
.admin table.t .nombre { text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
.admin table.t .nombre .sature { color:var(--or); font-weight:700; }
/* Une soirée annulée reste dans la liste, en retrait et barrée : l'escamoter
   fait chercher si on a rêvé, et c'est justement celle qu'on cherche quand on
   se demande pourquoi une salle s'est libérée. */
.admin table.t tr.eteint td { color:var(--blanc-faible); }
.admin table.t tr.eteint strong { text-decoration:line-through; font-weight:600; }
/* La coupure entre ce qui vient et ce qui est passé : une barre dans le même
   tableau, plutôt qu'un second tableau avec un second en-tête à relire. */
.admin table.t tr.coupure td {
  background:var(--nuit-600); color:var(--blanc-faible); padding:6px 14px;
  font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; font-weight:700;
}
.admin .actions-ligne { display:flex; flex-wrap:wrap; gap:6px; justify-content:flex-end; }
.admin .actions-ligne form { display:contents; }

/* La colonne « Extérieur » : un droit, des appareils, un déblocage. Empilés et
   non alignés, parce qu'il s'y trouve des phrases et pas seulement des boutons,
   et qu'une phrase mise en ligne avec un bouton se lit comme sa légende. */
.admin .pile-actions { display:flex; flex-direction:column; gap:10px; padding:8px 0; }
.admin .pile-actions .aide { margin:0; }
.admin .ligne-appareil {
  display:flex; flex-wrap:wrap; align-items:center; gap:8px;
  border-top:1px solid var(--trait); padding-top:8px;
}
.admin .ligne-appareil input { flex:1; min-width:160px; }

/* Cases à cocher repliées dans une liste : lisible à vingt lignes. */
.admin details.replie summary {
  list-style:none; cursor:pointer; display:flex; flex-wrap:wrap; align-items:center; gap:6px;
  min-height:44px; border-radius:8px; padding:2px 0;
}
/* Le nom occupe la place, le lien va au bout : sinon « modifier » se colle au
   texte et donne l'impression d'en faire partie. */
.admin details.replie summary .nom-et-detail { flex:1; }
/* Empilés, deux dépliants se touchent : le bouton du premier jouxte le titre du
   second et on ne sait plus lequel on enregistre. */
.admin .cadre > details.replie + details.replie {
  margin-top:var(--r4); padding-top:var(--r4); border-top:1px solid var(--trait);
}
.admin details.replie summary::-webkit-details-marker { display:none; }
.admin details.replie[open] summary { margin-bottom:var(--r2); }
.admin details.replie .lien-faux { font-size:12.5px; color:var(--or); text-decoration:underline; text-underline-offset:3px; }
.admin details.replie[open] .lien-faux::after { content:" (fermer)"; }
.admin .jeton {
  display:inline-flex; align-items:center; padding:3px 9px; border-radius:99px;
  border:1px solid var(--trait-fort); background:var(--nuit-600);
  font-size:12.5px; font-weight:600; color:var(--blanc-doux); white-space:nowrap;
}

.admin .pastille-etat {
  display:inline-flex; align-items:center; gap:6px; font-size:11.5px; font-weight:700;
  letter-spacing:.06em; text-transform:uppercase; padding:4px 10px; border-radius:99px;
  border:1px solid var(--trait-actif); color:var(--blanc-faible); white-space:nowrap;
}
.admin .pastille-etat.oui { color:var(--succes); border-color:rgba(79,203,151,.5); }
.admin .pastille-etat.non { color:var(--blanc-faible); border-style:dashed; }
.admin .pastille-etat.bloque { color:var(--erreur); border-color:rgba(255,107,133,.5); }
/* Pas « verrou » : ce nom appartient déjà à l'écran de déverrouillage, dont la
   règle `.verrou { flex:1; display:flex; flex-direction:column }` s'appliquait
   à cette pastille et l'étirait sur toute la largeur. Voir tests/test_css.py,
   qui interdit désormais ce genre de collision. */
.admin .pastille-etat.moi { color:var(--or); border-color:var(--or-sourd); }

/* La nature d'un événement, en colonne. Un point de couleur et un mot, et non
   une pastille pleine : la colonne se parcourt verticalement, et trois aplats
   par ligne feraient un damier là où on cherche à repérer une exception. Les
   couleurs sont les mêmes que le filet des cartes de la programmation, sinon
   « la ligne violette » et « la carte violette » ne désigneraient pas la même
   chose d'un écran à l'autre. */
.admin .pastille-nature {
  display:inline-flex; align-items:center; gap:7px;
  font-size:12.5px; font-weight:600; color:var(--blanc-doux); white-space:nowrap;
}
.admin .pastille-nature::before {
  content:""; width:8px; height:8px; border-radius:50%; flex:none;
  background:var(--or);
}
.admin .pastille-nature[data-nature="libre"]::before { background:var(--succes); }
/* « Privé » suit la nature dans la même cellule, avec la teinte froide qu'il
   porte déjà sur les cartes : un seul code couleur pour la même notion, d'un
   écran à l'autre. L'écart le décolle de la nature, sans quoi les deux se
   lisent comme un seul libellé. */
.admin .pastille-nature.privee { margin-left:10px; color:var(--opt); }
.admin .pastille-nature.privee::before { background:var(--opt); }
/* Nom + pastille dans une seule boite : sur ecran etroit ils passaient sur deux
   lignes et la cellule ne s'alignait plus avec le reste de la ligne. */
.admin .ligne-nom { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }

/* Deux commandes côte à côte au bout d'une ligne de tableau. Une boîte plutôt
   que deux éléments posés l'un après l'autre : un formulaire est un bloc, et il
   passait donc sous le lien qui le précède. */
.admin .boutons-ligne {
  /* `nowrap` : la colonne est étroite, et les deux boutons se retrouvaient
     l'un sous l'autre. La liste défile déjà horizontalement quand la fenêtre
     est petite, c'est donc à elle de gérer le manque de place. */
  display:flex; flex-wrap:nowrap; gap:8px; justify-content:flex-end;
  align-items:center; white-space:nowrap;
}
.admin .boutons-ligne form { margin:0; }

.admin .pagination { display:flex; flex-wrap:wrap; align-items:center; gap:var(--r3); margin-top:var(--r4); }
/* Les deux bouts de la pagination gardent leur place même quand ils ne mènent
   nulle part : sinon « Plus ancien » se déplace sous le curseur au moment
   précis où l'on arrive à la dernière page. */
.admin .pagination .inerte { opacity:.35; cursor:default; }

/* ---------- retours d'action ---------- */
.admin .message {
  margin:0 0 var(--r4); padding:var(--r3) var(--r4); border-radius:12px;
  font-size:14.5px; font-weight:600; border:1px solid var(--trait-fort); background:var(--nuit-700);
  animation:apparait var(--t-ecran) var(--entree);
}
.admin .message.ok { color:var(--succes); border-color:rgba(79,203,151,.45); }
.admin .message.attention { color:var(--alerte); border-color:rgba(255,162,76,.45); }
@keyframes apparait { from { opacity:0; transform:translateY(-6px); } }

/* ---------- une borne, une carte ---------- */
/* `.fiche-borne` et pas `.fiche` : `.fiche` appartient au tiroir de détail du
   planning, et la reprendre ici la ferait dériver au premier ajustement.
   Voir tests/test_css.py. */
.admin .fiche-borne {
  display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:var(--r3) var(--r5); margin:0 0 var(--r4);
  padding-bottom:var(--r4); border-bottom:1px solid var(--trait);
}
.admin .fiche-borne > div { display:flex; flex-direction:column; gap:4px; min-width:0; }
.admin .fiche-borne dt {
  font-size:11px; letter-spacing:.13em; text-transform:uppercase;
  color:var(--blanc-faible); font-weight:600;
}
.admin .fiche-borne dd { margin:0; font-size:14.5px; font-weight:600; color:var(--blanc); }
.admin .fiche-borne dd .aide { display:block; font-weight:500; margin-top:2px; }

/* Une borne éteinte se voit sans qu'on ait à lire la pastille, mais reste
   lisible : on l'a désactivée, pas mise au rebut. */
.admin .cadre.borne.eteinte { border-style:dashed; }
.admin .cadre.borne.eteinte > .cadre-tete h2 { color:var(--blanc-doux); }

.admin .borne-pied {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--r3);
  margin-top:var(--r4); padding-top:var(--r4); border-top:1px solid var(--trait);
}
/* Ce qui construit à gauche, ce qui défait à droite. */
.admin .borne-pied .pousse { flex:1; min-width:var(--r5); }
/* Sur téléphone la barre s'empile, et l'écart gauche/droite ne protège plus
   rien : « Supprimer » se retrouverait juste sous « Enregistrer », là où le
   pouce revient. On l'écarte franchement, la boîte de confirmation reste le
   vrai garde-fou, mais elle n'a pas à servir tous les jours. */
@media (max-width:560px) {
  .admin .borne-pied { flex-direction:column; align-items:stretch; }
  .admin .borne-pied .pousse { display:none; }
  .admin .borne-pied form { display:contents; }
  .admin .borne-pied .bouton { width:100%; }
  .admin .borne-pied .bouton.danger { margin-top:var(--r4); }
}
/* Un bouton d'action à l'intérieur d'un message : « Débloquer maintenant » n'a
   de sens qu'à côté de la phrase qui explique pourquoi. */
.admin .message .en-ligne { display:inline-block; margin-left:var(--r3); vertical-align:middle; }

.admin .encart-code {
  border:1px solid var(--or); background:var(--carte); border-radius:16px;
  padding:var(--r5); margin-bottom:var(--r4); display:flex; flex-wrap:wrap;
  align-items:center; gap:var(--r4); animation:apparait var(--t-ecran) var(--entree);
}
.admin .encart-code .code {
  font-size:42px; font-weight:800; letter-spacing:.16em; color:var(--or);
  font-variant-numeric:tabular-nums; line-height:1.1;
}
.admin .encart-code .txt { flex:1; min-width:260px; }
.admin .encart-code .txt strong { display:block; font-size:16px; margin-bottom:4px; }
.admin .encart-code .txt span { font-size:13.5px; color:var(--blanc-doux); }

.admin .aide { font-size:13.5px; color:var(--blanc-faible); margin:0; }
/* Pas de largeur maximale : le texte d'explication occupe la ligne entière.
   Il était plafonné à 78ch, la mesure de confort pour un paragraphe long, ce
   qui le faisait revenir à la ligne cinq ou six fois au milieu d'un écran vide.
   La règle de lisibilité tient toujours, mais elle se respecte en écrivant
   court : ces textes tiennent maintenant en deux lignes. */
.admin .aide.large { margin-bottom:var(--r4); line-height:1.6; }
/* Deux explications qui se suivent dans un même cadre : sans cet écart elles se
   touchent et se lisent comme un seul paragraphe, si bien qu'un avertissement
   posé sous un autre disparaît dans le précédent. Sur le premier seulement,
   l'écart le décollerait du tableau au-dessus, qui porte déjà le sien. */
.admin .cadre > .aide + .aide { margin-top:var(--r3); }
/* Même raison dans une boîte de confirmation, où c'est encore plus coûteux :
   la première ligne y dit ce qui est en jeu (« deux bénévoles sont inscrits »,
   « cette soirée a une fiche Odoo ») et la seconde ce qui va se passer. Collées,
   on ne lit que la seconde, c'est-à-dire celle qu'on avait déjà devinée. */
.admin .modale-admin .aide + .aide { margin-top:var(--r3); }
/* La marque d'un webhook passe sous sa pastille et ne se coupe pas : « …ran_
   AbCdEfGh » sur deux lignes ne se compare plus à l'adresse qu'on a collée,
   ce qui est la seule chose qu'elle serve à faire. */
.admin table.t td > .mono.aide { display:block; margin-top:4px; white-space:nowrap; }
.admin .aide strong { color:var(--blanc-doux); }
/* Une explication qui devient un empêchement. Même bloc, même place, mais la
   couleur d'alerte : la boîte de suppression y écrit tantôt « personne n'est
   inscrit », tantôt « cette soirée a une fiche Odoo », et les deux ne se lisent
   pas de la même façon. Le bouton, lui, est déjà désactivé. */
.admin .aide.alerte { color:var(--alerte); }
.admin .aide.alerte strong { color:var(--alerte); }
.admin .avert { display:flex; gap:var(--r2); align-items:flex-start; font-size:13.5px; color:var(--alerte); border:1px dashed var(--alerte); border-radius:12px; padding:var(--r3); margin-bottom:var(--r4); }
.admin .vide { color:var(--blanc-faible); font-size:14.5px; padding:var(--r5); text-align:center; }
.admin .diff { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11.5px; line-height:1.5; white-space:pre-wrap; color:var(--blanc-doux); margin:0; }
.admin .diff .av { color:var(--erreur); }
.admin .diff .ap { color:var(--succes); }


/* ---------- stockage : saisie de l'inventaire ---------- */
.admin .champ textarea {
  width:100%; min-height:150px; padding:var(--r3); border-radius:12px;
  border:1px solid var(--trait-actif); background:var(--nuit-600); color:var(--blanc);
  font:inherit; font-size:15px; line-height:1.55; outline:none; resize:vertical;
}
.admin .champ textarea:focus { border-color:var(--or); }
/* La précision d'un libellé doit se lire COMME une précision. Héritant des
   capitales et de l'espacement de `.champ label`, elle s'y soudait :
   « CONSULTABLE SANS CODE BORNES UNIQUEMENT » se lisait d'un seul tenant. */
.admin .facultatif {
  font-size:11px; letter-spacing:0; text-transform:none; font-style:italic;
  font-weight:500; color:var(--blanc-faible); margin-left:6px;
}
.admin .avec-micro { display:flex; gap:var(--r2); align-items:stretch; }
.admin .avec-micro input { flex:1; min-width:0; }
.admin #micro { flex:none; white-space:nowrap; }
.admin #micro.ecoute { background:var(--or); color:var(--nuit-900); border-color:var(--or); }
.admin #aide-micro { margin-top:6px; }
.admin #aide-micro.alerte { color:var(--alerte); }

.admin .ajoutes { display:flex; flex-direction:column; gap:6px; margin-top:var(--r4); max-height:280px; overflow-y:auto; }
.admin .ajoute {
  display:flex; align-items:center; gap:var(--r3); padding:8px var(--r3);
  border:1px solid var(--trait); border-radius:10px; background:var(--nuit-600);
  font-size:14px; animation:apparait var(--t-ecran) var(--entree);
}
.admin .ajoute .mono {
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12.5px;
  font-weight:700; color:var(--or); white-space:nowrap;
}

/* Un dépliant dont le résumé est déjà stylé en bouton. */
/* Le dépliant d'ajout suit soit un tableau, soit la phrase « Aucun meuble dans
   cette salle » : dans le second cas il s'y collait, et la phrase avait l'air
   d'être le libellé du bouton. */
.admin details.ajout { display:block; margin-top:var(--r4); }
.admin details.ajout > summary { list-style:none; display:inline-flex; align-items:center; cursor:pointer; }
.admin details.ajout > summary::-webkit-details-marker { display:none; }
.admin details.ajout[open] > summary { margin-bottom:var(--r4); }
.admin details.ajout > form { margin-top:var(--r2); }
.admin .nom-et-detail { display:flex; flex-direction:column; gap:2px; min-width:0; }
.admin .jeton.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; color:var(--or); border-color:var(--or-sourd); }


/* ---------- modale de suppression ---------- */
.admin .modale-admin {
  border:1px solid var(--trait-fort); border-radius:18px; background:var(--nuit-700);
  color:var(--blanc); padding:var(--r5); max-width:560px; width:calc(100% - 2 * var(--r4));
  box-shadow:0 30px 80px -20px #000;
}
.admin .modale-admin::backdrop { background:rgba(6,11,21,.74); }
.admin .modale-admin h2 { font-size:20px; font-weight:800; letter-spacing:-.02em; margin:0 0 8px; }
.admin .modale-admin .champ { margin-top:var(--r3); }
/* Ce qu'on est sur le point de supprimer se lit avant le texte qui explique la
   conséquence : les deux avaient la même graisse et se confondaient. */
.admin .modale-admin .sup-nom {
  margin:0 0 var(--r3); font-size:16px; font-weight:700; color:var(--blanc);
}
.admin .modale-pied {
  display:flex; flex-wrap:wrap; gap:var(--r3); justify-content:flex-end;
  margin-top:var(--r5); padding-top:var(--r4); border-top:1px solid var(--trait);
}
.admin .modale-pied .bouton.danger:disabled { opacity:.5; cursor:not-allowed; }
.admin .modale-admin .message { margin:var(--r3) 0 0; }

/* ---------- import ---------- */
.admin .copiable { position:relative; }
.admin .copiable pre {
  margin:0; padding:var(--r4); padding-top:56px; border-radius:14px;
  background:var(--nuit-800); border:1px solid var(--trait);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12.5px;
  line-height:1.6; color:var(--blanc-doux); white-space:pre-wrap; word-break:break-word;
  max-height:420px; overflow-y:auto;
}
.admin .copiable > .bouton { position:absolute; top:var(--r3); right:var(--r3); z-index:1; }
.admin .refus { color:var(--alerte); font-size:13.5px; }
.admin input[type="file"] {
  padding:10px var(--r3); min-height:48px; width:100%; border-radius:12px;
  border:1px dashed var(--trait-actif); background:var(--nuit-600);
  color:var(--blanc-doux); font-size:14px; font-weight:600;
}
.admin input[type="file"]::file-selector-button {
  margin-right:var(--r3); padding:8px var(--r3); border-radius:9px;
  border:1px solid var(--trait-actif); background:var(--nuit-700);
  color:var(--blanc); font:inherit; font-size:13.5px; font-weight:600; cursor:pointer;
}
.admin code {
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.92em;
  background:var(--nuit-600); padding:1px 6px; border-radius:6px; color:var(--blanc-doux);
}

/* ---------- écrans étroits ----------
   Le rail passe au-dessus du contenu, en pastilles qui reviennent à la ligne.
   Pas de défilement horizontal, aucune entrée coupée. */
@media (max-width:900px) {
  .admin .admin-corps { grid-template-columns:minmax(0,1fr); gap:var(--r4); }
  .admin .rail-resume {
    list-style:none; cursor:pointer; min-height:48px; display:flex; align-items:center;
    gap:10px; padding:0 var(--r4); border-radius:12px; background:var(--nuit-700);
    border:1px solid var(--trait-fort); font-size:15px; font-weight:700;
  }
  .admin .rail-resume::-webkit-details-marker { display:none; }
  .admin .rail-resume span { font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--blanc-faible); font-weight:700; }
  .admin .rail-resume::after { content:'▾'; margin-left:auto; color:var(--or); }
  .admin .rail-boite[open] .rail-resume { margin-bottom:var(--r3); }
  .admin .rail-boite[open] .rail-resume::after { content:'▴'; }
  .admin .rail { position:static; flex-direction:row; flex-wrap:wrap; gap:var(--r3) var(--r4); }
  .admin .rail-groupe { flex-direction:row; flex-wrap:wrap; gap:6px; align-items:center; }
  .admin .rail-titre { margin:0; padding:0; width:100%; }
  .admin .rail-lien { border-color:var(--trait-fort); background:var(--nuit-700); padding:0 var(--r3); }
  .admin .rail-lien[aria-current="page"] { background:var(--or); color:var(--nuit-900); border-color:var(--or); box-shadow:none; }
  .admin .form-grille.paire { grid-template-columns:minmax(0,1fr); }
  .admin .page-tete h1 { font-size:23px; }
  .admin .marque-admin { flex-basis:100%; }
}
@media (max-width:560px) {
  .admin { padding:var(--r3) var(--r3) var(--r5); }
  .admin .form-grille { grid-template-columns:minmax(0,1fr); }
  .admin .champ.court { max-width:none; }
  .admin .encart-code { padding:var(--r4); gap:var(--r3); }
  .admin .encart-code .code { font-size:36px; }
}

@media (prefers-reduced-motion:reduce) {
  *,*::before,*::after { animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:80ms !important; }
}

/* ==========================================================================
   Stockage, l'écran de recherche
   Un seul champ, gros, au centre. La question posée est « où est-ce rangé »,
   la réponse doit se lire de loin, une main sur le chariot.
   ========================================================================== */
/* Pas d'`overflow` ici : cette colonne fait 820 px centrés, elle affichait donc
   sa barre de défilement en plein milieu de l'écran, à côté des résultats. Le
   défilement appartient à la coque, dont la barre est au bord de la fenêtre,
   là où on la cherche. */
.recherche { padding:var(--r4) var(--r4) var(--r6); max-width:820px; width:100%; margin:0 auto; }

.champ-recherche { position:relative; display:flex; align-items:center; }
.champ-recherche svg {
  position:absolute; left:18px; width:22px; height:22px; pointer-events:none;
  stroke:var(--blanc-faible); fill:none; stroke-width:2.2; stroke-linecap:round;
}
.champ-recherche input {
  width:100%; min-height:62px; padding:0 52px; border-radius:16px;
  border:1px solid var(--trait-actif); background:var(--nuit-700); color:var(--blanc);
  font-size:19px; font-weight:600; outline:none;
  transition:border-color var(--t-micro), background var(--t-micro);
}
.champ-recherche input::placeholder { color:var(--blanc-faible); font-weight:500; }
.champ-recherche input:focus { border-color:var(--or); background:var(--nuit-600); }
.champ-recherche input::-webkit-search-cancel-button { display:none; }
.vider {
  position:absolute; right:10px; width:44px; height:44px; border-radius:11px;
  font-size:26px; color:var(--blanc-faible); line-height:1;
}
.vider:active { background:var(--nuit-600); }
/* Centrée comme le titre et le champ : c'est une seule colonne, elle doit avoir
   un seul axe. */
.aide-recherche { margin:10px 4px 0; font-size:13.5px; color:var(--blanc-faible); text-align:center; }

.resultats { display:flex; flex-direction:column; gap:var(--r3); margin-top:var(--r5); }
.trouve {
  display:flex; align-items:center; gap:var(--r4); padding:var(--r4);
  border:1px solid var(--trait-fort); border-radius:16px; background:var(--nuit-700);
  animation:surgit var(--t-ecran) var(--entree) backwards;
}
.trouve:nth-child(2) { animation-delay:30ms; }
.trouve:nth-child(3) { animation-delay:60ms; }
.trouve:nth-child(n+4) { animation-delay:90ms; }
@keyframes surgit { from { opacity:0; transform:translateY(8px); } }
.trouve .ou {
  flex:none; font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:19px; font-weight:700; letter-spacing:.02em; color:var(--or);
  border:1px solid var(--or-sourd); border-radius:12px; padding:12px 14px;
  white-space:nowrap;
}
.trouve .quoi { flex:1; min-width:0; }
.trouve h2 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.02em; }
.trouve .phrase { margin:3px 0 0; font-size:14px; color:var(--blanc-doux); }
.trouve .detail { margin:2px 0 0; font-size:13px; color:var(--blanc-faible); }
.trouve.incertain { border-style:dashed; }
.trouve.incertain .ou { color:var(--blanc-doux); border-color:var(--trait-actif); }
.peut-etre {
  flex:none; font-size:10.5px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--blanc-faible); border:1px dashed var(--trait-actif); border-radius:99px; padding:4px 10px;
}
.rien { margin:var(--r5) 0 0; text-align:center; font-size:16px; color:var(--blanc-doux); }
.rien span { display:block; margin-top:6px; font-size:13.5px; color:var(--blanc-faible); }

.recherche { padding:var(--r4) var(--r3) var(--r5); }
  .champ-recherche input { font-size:17px; min-height:58px; padding:0 48px; }
  .trouve { flex-wrap:wrap; gap:var(--r3); }
  .trouve .ou { font-size:17px; padding:10px 12px; }
  .trouve .quoi { flex-basis:100%; }
}

/* ==========================================================================
   Événements, la programmation publique
   --------------------------------------------------------------------------
   Un calendrier de mois, pas une semaine : on programme une soirée trois mois
   à l'avance, on ne la cale pas au quart d'heure. Le planning garde la semaine
   et l'heure ; ici on répond à « qu'est-ce qu'on joue en novembre ».
   ========================================================================== */
.programmation { padding:var(--r3) var(--r4) var(--r6); max-width:1400px; width:100%; margin:0 auto; }

.note-odoo {
  margin:0 0 var(--r4); padding:var(--r3) var(--r4); border-radius:12px;
  border:1px dashed var(--trait-actif); background:var(--nuit-700);
  font-size:13.5px; color:var(--blanc-faible);
}

.fiche-ev { padding:var(--r3) var(--r4) var(--r6); max-width:900px; width:100%; margin:0 auto;
  display:flex; flex-direction:column; gap:var(--r4); }
.bloc-ev {
  border:1px solid var(--trait-fort); background:var(--nuit-700); border-radius:16px;
  padding:var(--r4);
}
.bloc-ev.manquant { border-color:rgba(255,162,76,.5); }
.bloc-ev h2 { font-size:16px; font-weight:700; margin:0 0 var(--r3); letter-spacing:-.02em; }
.paires { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:var(--r3); margin:0; }
.paires > div { display:flex; flex-direction:column; gap:3px; min-width:0; }
/* « nuit du lundi 21 au mardi 22 septembre, de 19:00 à 00:00 » tient sur trois
   lignes dans une colonne de 190 px, à côté de trois colonnes presque vides.
   La date prend donc deux colonnes quand la place existe. */
.paires > div.long { grid-column:span 2; }
@media (max-width:560px) { .paires > div.long { grid-column:1 / -1; } }
.paires dt { font-size:11px; letter-spacing:.13em; text-transform:uppercase; color:var(--blanc-faible); font-weight:600; }
.paires dd { margin:0; font-size:14.5px; font-weight:600; }
.desc-ev { margin:var(--r4) 0 0; font-size:14.5px; line-height:1.6; color:var(--blanc-doux); }

.marqueur-ev {
  flex:none; display:inline-flex; align-items:center; min-height:44px; padding:0 var(--r3);
  border-radius:99px; font-size:11.5px; font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; border:1px solid var(--trait-actif); color:var(--blanc-doux);
}
.marqueur-ev[data-etat="publie"] { color:var(--or); border-color:var(--or); }
.marqueur-ev[data-etat="brouillon"] { color:var(--opt); border-style:dashed; border-color:var(--opt); }
.marqueur-ev[data-etat="annule"] { color:var(--erreur); border-color:rgba(255,107,133,.5); }

.etat-staff { margin:0 0 var(--r3); display:flex; flex-direction:column; gap:3px; }
.liste-staff { list-style:none; margin:0 0 var(--r4); padding:0; display:flex; flex-direction:column; gap:7px; }
.liste-staff li { display:flex; align-items:center; gap:var(--r2); font-size:14.5px; }
.role {
  font-size:10.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  padding:3px 9px; border-radius:99px; border:1px solid var(--trait-fort); color:var(--blanc-faible);
}
.role.responsable { color:var(--or); border-color:var(--or-sourd); }
.actions-staff { display:flex; flex-wrap:wrap; gap:var(--r3); }
.actions-staff .retrait { color:var(--erreur); border-color:rgba(255,107,133,.5); }

/* ---------- le formulaire ---------- */
.form-ev { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:var(--r3); align-items:start; }
.form-ev > .large { grid-column:1 / -1; }
.form-ev > .bref { }
.champ-ev { display:flex; flex-direction:column; gap:6px; min-width:0; }
.champ-ev > label {
  font-size:11px; letter-spacing:.13em; text-transform:uppercase;
  color:var(--blanc-faible); font-weight:600;
}
/* `:not([type="checkbox"])` : sans cette exclusion, la case à cocher d'un
   espace héritait de `width:100%` et `min-height:48px`, et s'affichait en gros
   carré blanc qui poussait son libellé hors de la puce. */
.champ-ev input:not([type="checkbox"]), .champ-ev select, .champ-ev textarea {
  width:100%; min-height:48px; padding:0 var(--r3); border-radius:12px;
  border:1px solid var(--trait-actif); background:var(--nuit-600); color:var(--blanc);
  font-family:inherit; font-size:15px; font-weight:600;
}
.champ-ev textarea { min-height:88px; padding:var(--r3); font-weight:500; line-height:1.5; resize:vertical; }
/* Le chevron du menu déroulant, dessiné plutôt que laissé au système : celui du
   navigateur se collait au bord droit du champ, sans la marge qu'ont tous les
   autres contenus, et rendait un noir invisible sur fond sombre. Il est en
   image de fond et non en pseudo-élément, parce qu'un `select` n'en accepte
   pas, et que passer par le conteneur obligerait à le positionner. */
.champ-ev select {
  appearance:none; -webkit-appearance:none;
  padding-right:40px;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' fill='none' stroke='%23a8b3c7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right var(--r3) center;
  background-size:12px 8px;
  cursor:pointer;
}
.champ-ev input:focus, .champ-ev select:focus, .champ-ev textarea:focus { border-color:var(--or); }
.aide-ev { font-size:13px; color:var(--blanc-faible); margin:0; }
.cases-ev { display:flex; flex-wrap:wrap; gap:var(--r2); }
.case-ev {
  display:inline-flex; align-items:center; gap:8px; min-height:48px; padding:0 var(--r3);
  border-radius:11px; border:1px solid var(--trait-fort); background:var(--nuit-600);
  font-size:14px; font-weight:600; cursor:pointer;
}
/* La puce donne la cible tactile ; la case garde sa taille native. */
.case-ev input[type="checkbox"] { flex:none; width:18px; height:18px; margin:0; accent-color:var(--or); }
.case-ev:has(input:checked) { border-color:var(--or); background:var(--nuit-700); }
/* Une par ligne, quand chaque case porte une explication sous son intitulé :
   côte à côte, les quatre rappels tenaient sur deux lignes de hauteurs
   différentes, et l'on ne savait plus quelle aide allait avec quelle case. */
.cases-ev.colonne { flex-direction:column; flex-wrap:nowrap; gap:var(--r2); }
.cases-ev.colonne .case-ev {
  align-items:flex-start; padding:var(--r2) var(--r3); font-weight:500;
  line-height:1.35;
}
.cases-ev.colonne .case-ev input[type="checkbox"] { margin-top:2px; }
.cases-ev.colonne .aide-ev { font-weight:400; }
.actions-ev {
  grid-column:1 / -1; display:flex; flex-wrap:wrap; align-items:center; gap:var(--r3);
  padding-top:var(--r3); border-top:1px solid var(--trait);
}

@media (max-width:760px) {
}

/* Le refus d'une saisie, en tête du formulaire qu'il faut corriger. Il ouvre
   le bloc dépliant, donc il est déjà à l'écran quand la page arrive. */
.alerte-ev {
  grid-column:1 / -1; margin:0 0 var(--r3);
  display:flex; gap:var(--r2); align-items:flex-start;
  font-size:14px; line-height:1.45; color:var(--erreur);
  border:1px solid rgba(255,107,133,.45); border-radius:12px;
  background:rgba(255,107,133,.08); padding:var(--r3);
  flex-wrap:wrap; justify-content:space-between; align-items:center;
}
.alerte-ev a { color:var(--erreur); white-space:nowrap; }
/* Une aide qui devient un avertissement, sans changer de place ni de taille :
   c'est la durée saisie, qui passe en alerte au-delà de vingt-quatre heures.
   Pas un cadre ni une icône, parce que ce n'est pas une erreur : une plage de
   deux jours est légitime, elle mérite seulement d'être vue. */
.aide-ev.alerte-ev-mot { color:var(--alerte); font-weight:600; }

.vide-ev {
  margin:0 0 var(--r4); padding:var(--r4); text-align:center;
  font-size:14px; color:var(--blanc-doux);
  border:1px dashed var(--trait); border-radius:12px;
}

/* --------------------------------------------------------------------------
   La programmation : une bande de cartes, pleine hauteur
   --------------------------------------------------------------------------

   Une bande et non une grille : on lit une programmation comme une frise, la
   prochaine soirée d'abord. Les cartes prennent toute la hauteur disponible,
   donc l'affiche a la place d'exister, et le défilement est horizontal, dans
   l'ordre chronologique.
   -------------------------------------------------------------------------- */

.programmation {
  flex:1; min-height:0; display:flex; flex-direction:column; gap:var(--r3);
  padding:0 var(--r3) var(--r3);
}

/* La bande porte la même marge latérale que la barre au-dessus. Sans elle, les
   cartes étaient collées au bord gauche de l'écran alors que le titre, lui,
   respirait : deux alignements différents dans la même colonne. */
.bande-ev {
  flex:1; min-height:0; display:flex; gap:var(--r4);
  overflow-x:auto; overflow-y:hidden;
  scroll-snap-type:x proximity; scroll-padding-left:var(--r3);
  padding:2px var(--r3) var(--r3);
  margin:0 calc(-1 * var(--r3));   /* le défilement va d'un bord à l'autre… */
  /* …et un voile à droite dit qu'il reste des soirées derrière le bord. Sans
     lui, une bande qui déborde ressemble à une bande coupée. */
  -webkit-mask-image:linear-gradient(to right, #000 calc(100% - 48px), transparent);
  mask-image:linear-gradient(to right, #000 calc(100% - 48px), transparent);
}
/* Le voile disparaît quand tout tient à l'écran : masquer le bord d'une bande
   complète ferait croire à une suite qui n'existe pas. */
.bande-ev:not(.deborde) { -webkit-mask-image:none; mask-image:none; }
.bande-ev > * { scroll-snap-align:start; }
.bande-ev:focus-visible { outline:2px solid var(--or); outline-offset:-2px; border-radius:18px; }

/* ---------- l'ouverture d'une soirée ----------
   Une transition entre deux pages, pas une animation posée sur un écran : le
   navigateur s'en charge, donc elle ne coûte ni bibliothèque ni script de mise
   en scène, et elle ne s'exécute pas du tout là où elle n'est pas comprise. */
@view-transition { navigation: auto; }

/* L'affiche de la fiche porte le même nom que celle de la carte ouverte, posé
   au clic par `coque.js`. Le navigateur les reconnaît comme un seul objet.

   **Un seul élément**, et c'était le défaut : la règle nommait à la fois le
   cadre et l'image qu'il contient. Deux éléments portant le même nom rendent
   la transition invalide, et le navigateur l'abandonne en silence. On ne voyait
   donc aucune animation, sans rien pour dire pourquoi. */
.fiche-ev .affiche-ev { view-transition-name: affiche-ouverte; }

/* Le reste de la fiche monte pendant que l'affiche voyage : sans ça, le texte
   apparaît d'un bloc et l'œil ne sait plus quoi suivre. */
::view-transition-old(root) { animation: fondu-sortie .2s ease both; }
::view-transition-new(root) { animation: fondu-montee .42s cubic-bezier(.22,.9,.3,1) both; }
@keyframes fondu-sortie { to { opacity:0; } }
@keyframes fondu-montee { from { opacity:0; transform:translateY(18px); } }

/* Et l'animation d'arrivée, celle qui joue TOUJOURS.
   Les transitions de vue entre deux pages demandent un navigateur récent ; là
   où elles manquent, la fiche apparaissait d'un coup, ce qui est exactement ce
   qu'on voulait éviter. Ces deux animations-ci ne dépendent d'aucune API : la
   fiche s'ouvre de la même façon partout, et le navigateur qui sait faire mieux
   remplace simplement le premier mouvement par le vrai voyage de l'affiche. */
.fiche-ev .affiche-ev { animation: affiche-arrive .46s cubic-bezier(.2,.85,.3,1) both; }
.fiche-ev .colonne-ev { animation: colonne-arrive .5s cubic-bezier(.2,.85,.3,1) .06s both; }
.fiche-ev .actions-borne { animation: colonne-arrive .4s ease-out both; }
@keyframes affiche-arrive {
  from { opacity:0; transform:scale(.93) translateY(10px); }
}
@keyframes colonne-arrive {
  from { opacity:0; transform:translateY(22px); }
}

/* Le départ, côté bande : la carte touchée se soulève et les autres s'effacent.
   C'est la moitié qu'on ressent le plus, parce qu'elle répond au doigt avant
   même que la page suivante existe. */
.bande-ev.part .carte-ev { transition:opacity .22s ease, transform .22s ease; opacity:.25; }
.bande-ev.part .carte-ev.choisie {
  opacity:1; transform:scale(1.03) translateY(-6px); z-index:1;
}

/* Un hall passant voit cette transition cent fois par jour : qui a demandé
   moins de mouvement n'en voit aucun, et l'écran change sans plus. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Le repère de mois : un bandeau au-dessus de chaque carte, à la hauteur de la
   barre des autres écrans, et un trait fin quand le mois change.
   Le bandeau existe sur TOUTES les colonnes, vide la plupart du temps : c'est
   ce qui garde les affiches sur une même ligne, quel que soit l'endroit où un
   mois commence. Le libellé n'apparaît qu'au changement, en légende à droite du
   trait, assez discret pour ne pas concurrencer les affiches et assez présent
   pour qu'on sache de quel mois on parle. */
/* C'est la colonne qui porte désormais la largeur : la carte remplit ce qui
   reste sous le bandeau. La largeur suit la hauteur disponible (`24vw`), pour
   que l'affiche reste proche de son format naturel sur un écran haut. */
.pile-ev {
  flex:0 0 clamp(250px, 24vw, 330px); height:100%;
  display:flex; flex-direction:column;
  scroll-snap-align:start;
}
.bande-mois {
  height:var(--h-barre); flex:none;
  display:flex; align-items:center; padding-left:14px;
  user-select:none;
}
.pile-ev.change-mois { border-left:1px solid var(--trait-fort); }
/* Le trait tient toute la colonne, du bandeau au bas de la carte : c'est lui
   qui sépare deux mois, pas seulement leurs étiquettes. */
.bm-libelle {
  font-size:12px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;
  color:var(--blanc-faible); white-space:nowrap;
}
/* Le mois en cours se distingue du reste sans crier. */
.pile-ev:first-child .bm-libelle { color:var(--or); }

/* Largeur fixe, hauteur pleine : c'est l'affiche qui commande, et elle garde
   son format 663 x 830 quoi qu'il arrive. Un visuel conçu ailleurs qu'on rogne
   est un visuel trahi, et sur une carte à hauteur variable le rognage change
   d'un écran à l'autre. Le bas de la carte, lui, s'étire. */
.carte-ev {
  flex:1; min-height:0; width:100%;
  display:flex; flex-direction:column; overflow:hidden;
  border-radius:18px; border:1px solid var(--trait); background:var(--carte);
  text-decoration:none; color:var(--blanc);
  transition:transform var(--t-micro), border-color var(--t-micro),
             box-shadow var(--t-micro);
}
.carte-ev:hover {
  transform:translateY(-2px); border-color:var(--or-sourd);
  box-shadow:0 18px 40px -24px #000;
}
.carte-ev:active { transform:translateY(0) scale(.995); }
.carte-ev[data-etat="annule"] { opacity:.55; }
.carte-ev[data-etat="annule"] .ce-titre { text-decoration:line-through; }

.ce-affiche {
  position:relative; flex:none; aspect-ratio:663 / 830; overflow:hidden;
  background:
    radial-gradient(120% 120% at 20% 0%, rgba(240,185,66,.14), transparent 60%),
    linear-gradient(160deg, var(--nuit-600), var(--nuit-800));
  display:flex; align-items:center; justify-content:center;
}
.ce-affiche img { width:100%; height:100%; object-fit:cover; display:block; }

/* Faute d'affiche, la date en grand : le cadre dit alors quelque chose plutôt
   que d'attendre une image qui n'existe peut-être pas encore.

   Posée en absolu et non centrée par le flux : depuis que les soirées privées
   affichent un visuel générique, la date doit pouvoir se poser PAR-DESSUS une
   image. En flux, le cadre les aurait rangées côte à côte et l'affiche aurait
   été chassée hors du cadre. */
.ce-sans {
  position:absolute; inset:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
}
/* Sur une image, un voile pour que le chiffre reste lisible. Le fichier est
   déposé à la main : il peut être clair, chargé, ou les deux, et la date ne
   doit pas dépendre de ce qu'il contient. Un dégradé radial plutôt qu'un fond
   plein, qui ferait une pastille au milieu de l'image. */
.ce-sans.sur-image {
  background:radial-gradient(58% 42% at 50% 50%, rgba(6,9,18,.74), rgba(6,9,18,0) 72%);
}
.ce-sans.sur-image .j { text-shadow:0 2px 14px rgba(6,9,18,.9); }
.ce-sans.sur-image .m { color:var(--blanc-doux); text-shadow:0 1px 10px rgba(6,9,18,.9); }
.ce-sans .j {
  font-size:clamp(40px, 6vw, 60px); font-weight:800; line-height:.95;
  color:var(--or); font-variant-numeric:tabular-nums; letter-spacing:-.03em;
}
.ce-sans .m {
  font-size:12.5px; font-weight:700; letter-spacing:.16em;
  color:var(--blanc-faible); text-transform:uppercase;
}

.ce-affiche .marqueur-ev {
  position:absolute; top:var(--r3); right:var(--r3); min-height:28px;
  background:var(--nuit-900);
}

/* « Privé », en haut à gauche : le coin libre, à l'opposé de l'état, et le
   premier que l'œil rencontre en parcourant la frise de gauche à droite.
   Une teinte froide plutôt que l'or de la maison : ces soirées-là ne sont pas
   ce que le Fantastique programme, elles sont ce qu'il héberge. */
.marqueur-prive {
  position:absolute; top:var(--r3); left:var(--r3);
  display:inline-flex; align-items:center; min-height:28px; padding:0 var(--r3);

  border-radius:99px; font-size:11.5px; font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; background:var(--nuit-900);
  border:1px solid var(--opt); color:var(--opt);
}
/* Le même marqueur, mais dans le flux : sur la fiche il vit à côté du titre,
   pas par-dessus une image. Une classe plutôt qu'un second nom, pour que la
   couleur et la forme restent définies une seule fois. */
.marqueur-prive.posee { position:static; flex:none; }

/* Le corps prend ce que l'affiche laisse, et c'est la présentation qui absorbe
   la différence. Rien ici ne fixe un nombre de lignes : la quantité de texte
   suit la hauteur de l'écran. */
.ce-corps {
  padding:var(--r4); display:flex; flex-direction:column; gap:6px;
  flex:1; min-height:0; overflow:hidden;
}
.ce-desc {
  margin:2px 0 0; flex:1; min-height:0; overflow:hidden;
  font-size:13.5px; line-height:1.5; color:var(--blanc-doux);
  /* Un fondu plutôt qu'une coupe nette : une phrase tranchée au milieu d'un mot
     ressemble à un défaut d'affichage, un fondu dit « la suite est ailleurs ».
     Et il s'adapte tout seul, contrairement à un nombre de lignes. */
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 26px), transparent);
  mask-image:linear-gradient(to bottom, #000 calc(100% - 26px), transparent);
}
/* Sous une certaine hauteur, la présentation ne tiendrait qu'un mot : mieux
   vaut ne rien montrer qu'un fragment illisible. */
@media (max-height:620px) { .ce-desc { display:none; } }
.ce-meta .complet { color:var(--alerte); font-weight:700; }
.ce-quand { margin:0; font-size:12px; font-weight:700; letter-spacing:.04em; color:var(--or); }
/* Deux lignes au plus : un titre long faisait grandir sa carte et la ligne
   entière avec elle, pour un mot de plus. */
.ce-titre {
  margin:0; font-size:17px; font-weight:800; letter-spacing:-.01em; line-height:1.25;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.ce-meta { margin:0; font-size:13px; color:var(--blanc-doux); display:flex; flex-wrap:wrap; gap:5px; }
/* Le séparateur reste du texte lisible : `--trait-fort` est une couleur de
   filet, à 1,5:1 elle passe sous le seuil AA dès qu'on la pose sur des mots. */
.ce-meta .sep { color:var(--blanc-faible); }

.ce-equipe {
  display:flex; flex-direction:column; gap:6px; flex:none;
  padding:var(--r3) var(--r4); border-top:1px solid var(--trait);
  font-size:12.5px; color:var(--blanc-doux);
}
.ce-jauge { display:block; height:4px; border-radius:99px; background:var(--trait-fort); overflow:hidden; }
.ce-remplie { display:block; height:100%; background:var(--succes); border-radius:99px; }
.ce-equipe.manquant { color:var(--alerte); }
.ce-equipe.manquant .ce-remplie { background:var(--alerte); }

/* ---------- le tarif ----------
   Un prix se lit d'un coup d'œil et se compare. La première version était un
   gros aplat doré : voyant, mais lourd, et illisible dès qu'il tombait sur une
   zone claire de l'affiche. Celle-ci pose un fond presque noir, qui tient sur
   n'importe quelle image, et laisse l'or au chiffre. Les centimes et la devise
   descendent d'un cran, comme sur une étiquette de prix. */
.prix-ev {
  position:absolute; left:var(--r3); bottom:var(--r3);
  display:inline-flex; align-items:baseline; gap:1px;
  padding:7px 13px 8px; border-radius:14px;
  background:rgba(6,11,21,.86);
  border:1px solid var(--or-sourd);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  color:var(--or); font-variant-numeric:tabular-nums; line-height:1;
  box-shadow:0 10px 26px -14px #000;
}
.prix-ev .entier { font-size:23px; font-weight:800; letter-spacing:-.035em; }
.prix-ev .cents { font-size:12.5px; font-weight:700; letter-spacing:-.01em; opacity:.85; }
.prix-ev .devise { font-size:12.5px; font-weight:600; margin-left:3px; opacity:.85; }
.prix-ev .mot {
  font-size:12.5px; font-weight:800; letter-spacing:.1em; text-transform:uppercase;
}
/* Gratuit n'est pas un prix : ni chiffre, ni or, une couleur qui dit « rien à
   payer » sans crier plus fort que les tarifs voisins. */
.prix-ev.gratuit { color:var(--succes); border-color:rgba(79,203,151,.45); }

/* Sur la fiche, le tarif n'est plus posé sur une image : il prend sa place dans
   le flux, en plus grand. */
.prix-ev.grand {
  position:static; padding:10px 16px 11px; backdrop-filter:none;
  background:var(--nuit-800);
}
.prix-ev.grand .entier { font-size:34px; }
.prix-ev.grand .cents, .prix-ev.grand .devise { font-size:16px; }
.prix-ev.grand .mot { font-size:16px; }

.ic-plus { width:18px; height:18px; margin-right:6px; }
.b-plat .ic-plus { stroke:var(--blanc-doux); fill:none; stroke-width:2.2;
                   stroke-linecap:round; stroke-linejoin:round; }
/* Le bilan d'une synchronisation n'est pas un avertissement : trait plein et
   couleur de texte normale, pour le distinguer de la note « Odoo n'est pas
   configuré », qui elle est un état d'attente. */
.note-odoo.bilan { border-style:solid; border-color:var(--or-sourd); color:var(--blanc); }

/* ---------- les filtres, dans la barre ----------
   Deux menus déroulants plutôt qu'une rangée de pastilles : à trois périodes
   croisées avec quatre états, la rangée occupait toute la largeur et poussait
   les actions hors de la barre. Un `select` natif, habillé : il s'ouvre au
   doigt comme le système le fait partout ailleurs, et il n'a besoin d'aucun
   JavaScript pour fonctionner. */
.filtres-ev { display:flex; align-items:center; gap:var(--r2); flex:none; }
.choix { position:relative; display:inline-flex; align-items:center; }
.choix select {
  appearance:none; -webkit-appearance:none;
  min-height:44px; padding:0 34px 0 var(--r3);
  border-radius:12px; background:var(--nuit-700); border:1px solid var(--trait-fort);
  color:var(--blanc-doux); font-family:inherit; font-size:14px; font-weight:600;
  cursor:pointer; transition:background var(--t-micro), border-color var(--t-micro);
}
.choix select:hover { background:var(--nuit-600); color:var(--blanc); }
.choix select:focus-visible { outline:2px solid var(--or); outline-offset:2px; }
/* Le chevron est dessiné par la puce, puisque `appearance:none` a retiré celui
   du système. `pointer-events:none` pour que le clic atteigne le menu dessous. */
.choix::after {
  content:""; position:absolute; right:13px; width:9px; height:9px;
  border-right:2px solid var(--blanc-faible); border-bottom:2px solid var(--blanc-faible);
  transform:translateY(-2px) rotate(45deg); pointer-events:none;
}
/* La bascule des soirées privées, taillée comme les menus voisins : même
   hauteur, même rayon, même fond. Elle n'en est pas un, mais elle est dans la
   même barre et une hauteur différente y saute aux yeux plus que la nuance
   qu'elle porterait. Cochée, elle prend la teinte froide du marqueur « Privé » :
   c'est ce qu'elle cache, et le lien se lit sans légende. */
.bascule-ev {
  display:inline-flex; align-items:center; gap:var(--r2);
  min-height:44px; padding:0 var(--r3);
  border-radius:12px; background:var(--nuit-700); border:1px solid var(--trait-fort);
  color:var(--blanc-doux); font-size:14px; font-weight:600; cursor:pointer;
  white-space:nowrap;
  transition:background var(--t-micro), border-color var(--t-micro), color var(--t-micro);
}
.bascule-ev:hover { background:var(--nuit-600); color:var(--blanc); }
.bascule-ev:has(input:checked) { border-color:var(--opt); color:var(--opt); }
.bascule-ev:has(input:focus-visible) { outline:2px solid var(--or); outline-offset:2px; }
.bascule-ev input {
  appearance:none; -webkit-appearance:none; margin:0;
  width:17px; height:17px; flex:none; border-radius:5px;
  border:2px solid var(--trait-actif); background:transparent;
  display:grid; place-items:center;
  transition:background var(--t-micro), border-color var(--t-micro);
}
.bascule-ev input:checked { background:var(--opt); border-color:var(--opt); }
/* La coche est dessinée en CSS : pas d'image à charger, et elle suit la couleur
   du texte du fond, donc elle reste lisible dans les deux états. */
.bascule-ev input:checked::after {
  content:""; width:5px; height:9px; margin-top:-2px;
  border-right:2px solid var(--nuit-900); border-bottom:2px solid var(--nuit-900);
  transform:rotate(45deg);
}

/* Le libellé du menu est pour les lecteurs d'écran : à l'écran, le choix courant
   dit déjà de quoi il s'agit. */
.sr {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* `[hidden]` doit gagner : `.b-plat` pose `display:inline-flex`, qui l'emporte
   sur le `display:none` par défaut de l'attribut, et le bouton « Filtrer »
   restait visible alors que JavaScript venait de le masquer. */
[hidden] { display:none !important; }

.barre-prog { flex-wrap:wrap; gap:var(--r2); }
.barre-prog .titre-univers { flex:1 1 220px; }

@media (max-width:820px) {
  .filtres-ev { order:3; flex:1 1 100%; }
  .choix, .choix select { flex:1; }
}
@media (max-width:560px) {
  .carte-ev { flex-basis:min(78vw, 300px); }
}

/* L'écran d'édition : des blocs plutôt qu'une longue colonne de champs, pour
   que « quand et où » se relise sans traverser la billetterie. */
.atelier-ev { padding:var(--r3) var(--r4) var(--r6); max-width:900px; width:100%; margin:0 auto; }
.atelier-ev .bloc-ev { margin-bottom:var(--r4); }
.atelier-ev .form-ev { display:block; }
.atelier-ev .bloc-ev > h2 { margin:0 0 var(--r4); }
.atelier-ev .bloc-ev {
  display:grid; gap:var(--r4);
  grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));
}
.atelier-ev .bloc-ev > h2 { grid-column:1 / -1; }
/* Un titre ou une description sur un tiers de largeur à côté de deux autres
   champs se lit mal : ce qui est marqué « large » prend la ligne entière. */
.atelier-ev .champ-ev.large { grid-column:1 / -1; }
.atelier-ev .champ-ev.bref { grid-column:auto; }
.pied-atelier {
  display:flex; flex-wrap:wrap; gap:var(--r3); align-items:center;
  padding-top:var(--r3);
}
.pied-atelier .aide-ev { flex:1 1 220px; }
/* Les boutons du pied ne se coupent pas en deux lignes : « Enregistrer ce
   soir-là » et « Enregistrer toute la série » se distinguent par leur fin de
   phrase, et une coupure au milieu oblige à relire les deux pour choisir.
   Sur un écran étroit, `flex-wrap` du pied les met l'un sous l'autre, ce qui
   est la bonne réponse à l'étroitesse. */
.pied-atelier button, .pied-atelier > .b-plat { white-space:nowrap; }

/* Sur la fiche, l'affiche est un portrait de 663 x 830 : posée en bandeau
   pleine largeur et rognée à 360 px de haut, il n'en restait qu'une tranche
   centrale. Elle prend donc une colonne à côté du texte quand la place existe,
   et repasse au-dessus sur téléphone. */
.avec-affiche { display:grid; grid-template-columns:minmax(0, 260px) minmax(0, 1fr); gap:var(--r4); align-items:start; }
.affiche-ev { border-radius:18px; overflow:hidden; border:1px solid var(--trait); aspect-ratio:663 / 830; }
.affiche-ev img { display:block; width:100%; height:100%; object-fit:cover; }
/* La colonne de la fiche espace ses blocs, **qu'il y ait une affiche ou non**.
   La règle ne visait que `.avec-affiche`, c'est-à-dire le cas où une image est
   posée : sur une soirée sans affiche, le récapitulatif, l'équipe et le bloc
   interne se touchaient bord à bord et ne se lisaient plus comme trois blocs.
   Ça se voyait surtout sur les soirées sans billetterie, qui n'ont souvent pas
   encore d'affiche, mais la cause n'avait rien à voir avec leur nature. */
.colonne-ev { display:flex; flex-direction:column; gap:var(--r4); min-width:0; }
@media (max-width:700px) {
  .avec-affiche { grid-template-columns:minmax(0,1fr); }
  .affiche-ev { max-width:320px; }
}

.tete-bloc { display:flex; align-items:center; justify-content:space-between; gap:var(--r3); }
.tete-bloc h2 { margin:0; }

@media (max-width:560px) {
  .chercher-ev { margin-left:0; width:100%; }
  .chercher-ev input { flex:1; min-width:0; }
  .segments { width:100%; }
  .segments .seg { flex:1; justify-content:center; padding:0 var(--r2); }
  .cartes-ev { grid-template-columns:minmax(0,1fr); }
}

/* Une soirée au planning : même bloc, mais on doit voir d'un coup d'œil que
   c'est de la programmation et non une réservation ordinaire, parce qu'elle ne
   se modifie pas au même endroit. Un filet à gauche plutôt qu'une couleur de
   fond : les blocs sont déjà porteurs d'un état, et deux codes couleur sur le
   même objet ne se lisent plus. */
/* Une réservation qui traverse plusieurs journées est découpée en un morceau
   par colonne. Le côté qui continue perd son arrondi et sa bordure : une barre
   fermée des deux côtés se lit comme une réservation entière, et deux barres
   fermées côte à côte comme deux réservations, ce qui est faux. */
.ev[data-suite="avant"], .ev[data-suite="les-deux"] {
  border-top-color:transparent; border-top-left-radius:0; border-top-right-radius:0;
}
.ev[data-suite="apres"], .ev[data-suite="les-deux"] {
  border-bottom-color:transparent;
  border-bottom-left-radius:0; border-bottom-right-radius:0;
}

.ev[data-evenement="oui"] { border-left-width:5px; }
.ev .ev-badge { color:var(--or); }
.note-ev { margin:var(--r3) 0 0; font-size:13px; line-height:1.45; color:var(--blanc-faible); }

/* L'aperçu de synchronisation : des listes de titres, pas des compteurs.
   « 3 mises à jour » n'aide personne à vérifier, « Concert de la
   Saint-Patrick » se retrouve. */
.compte-lot {
  flex:none; display:inline-flex; align-items:center; justify-content:center;
  min-width:30px; min-height:28px; padding:0 9px; border-radius:99px;
  background:var(--nuit-600); border:1px solid var(--trait-fort);
  font-size:13px; font-weight:700; font-variant-numeric:tabular-nums;
}
.liste-lot { margin:var(--r3) 0 0; padding:0; list-style:none; display:flex;
             flex-direction:column; gap:6px; }
.liste-lot li {
  padding:9px var(--r3); border-radius:10px; background:var(--nuit-700);
  border:1px solid var(--trait); font-size:14px; font-weight:600;
}
.bloc-ev.manquant .compte-lot { border-color:var(--alerte); color:var(--alerte); }

/* Les étiquettes d'Odoo. Elles classent la programmation et porteront les
   restrictions de réservation : discrètes, mais lisibles. */
.ce-etiquettes { margin:2px 0 0; display:flex; flex-wrap:wrap; gap:5px; }
.etiq {
  display:inline-flex; align-items:center; padding:2px 9px; border-radius:99px;
  font-size:11.5px; font-weight:700; letter-spacing:.02em;
  color:var(--blanc-doux); background:var(--nuit-600);
  border:1px solid var(--trait-fort);
}
.fiche-ev .ce-etiquettes { margin-top:var(--r3); }
/* Un champ en lecture seule doit se voir : sans ça on tape dedans, on
   enregistre, et rien ne change. */
.champ-ev input[readonly] { background:var(--nuit-800); color:var(--blanc-doux); cursor:not-allowed; }

/* Le tarif en tête de fiche, avec la jauge à côté : la première question du
   public, et la deuxième de qui prépare la communication. */
.entree-ev { display:flex; align-items:center; gap:var(--r3); flex-wrap:wrap; margin:var(--r3) 0; }
/* Ce qui vient d'ailleurs ou d'avant : consultable, mais pas au même niveau que
   ce qu'on est venu lire. */
.bloc-ev.discret { background:transparent; border-style:dashed; }
.bloc-ev.discret h2 { color:var(--blanc-faible); }

/* ---------- la borne : retour, réserver, code QR ----------
   Ces trois-là ne servent qu'à un visiteur du hall. Identifié, on a la barre du
   haut et ses commandes ; ici l'écran est une vitrine, et tout ce qui se touche
   descend à hauteur de main. */
.actions-borne {
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--r3); margin:0 0 var(--r3);
}
.retour-borne, .b-reserver {
  display:inline-flex; align-items:center; gap:9px;
  /* Ce sont des boutons, pas des liens dans une phrase : le soulignement les
     faisait lire comme du texte cliquable posé sur une pastille. */
  text-decoration:none;
  min-height:54px; padding:0 var(--r4); border-radius:14px;
  font-size:16px; font-weight:700; letter-spacing:-.01em;
  transition:transform var(--t-micro) var(--entree), background var(--t-micro);
}
.retour-borne {
  background:var(--nuit-600); border:1px solid var(--trait-fort); color:var(--blanc-doux);
}
.retour-borne:active, .b-reserver:active { transform:scale(.97); }
.retour-borne svg { width:20px; height:20px; stroke:currentColor; fill:none; stroke-width:2.5; stroke-linecap:round; stroke-linejoin:round; }
/* Doré et large : c'est la seule chose qu'on attend d'un visiteur devant une
   affiche qui lui plaît. */
.b-reserver { background:var(--or); color:var(--nuit-900); font-weight:800; padding:0 var(--r5); }
.b-reserver:active { background:var(--or-vif); }

/* Le panneau du code, ouvert par l'adresse : `:target` plutôt qu'un script.
   Rien à charger, rien qui puisse échouer, et le bouton « retour » de la
   tablette le referme, ce qu'un visiteur essaie avant de chercher une croix. */
.voile-qr { display:none; }
.voile-qr:target {
  display:flex; position:fixed; inset:0; z-index:40;
  align-items:center; justify-content:center; padding:var(--r4);
  background:rgba(6,11,21,.86);
  animation:qr-entree .22s var(--entree) both;
}
@keyframes qr-entree { from { opacity:0; } }
/* Toute la surface derrière le panneau referme : sur une borne, on tape à côté
   pour sortir bien avant de chercher un bouton. */
.qr-sortie { position:absolute; inset:0; }
.qr-panneau {
  position:relative; display:flex; flex-direction:column; align-items:center;
  gap:var(--r3); padding:var(--r5); border-radius:24px;
  background:var(--nuit-700); border:1px solid var(--trait-fort);
  box-shadow:0 30px 80px rgba(0,0,0,.6);
  animation:qr-monte .26s cubic-bezier(.22,.9,.3,1) both;
}
@keyframes qr-monte { from { opacity:0; transform:translateY(18px) scale(.97); } }
.qr-panneau h2 { margin:0; font-size:24px; font-weight:800; letter-spacing:-.02em; }
.qr-sous { margin:0; font-size:15px; color:var(--blanc-doux); }
/* Fond blanc et marge réelle : un code QR se lit par le contraste, et la zone
   calme autour compte autant que le dessin. Sur fond sombre, beaucoup de
   téléphones renoncent. */
.qr-cadre {
  background:#fff; padding:18px; border-radius:16px;
  width:min(46vh, 320px); aspect-ratio:1;
  display:flex; align-items:center; justify-content:center;
}
.qr-cadre svg { width:100%; height:100%; shape-rendering:crispEdges; }
.qr-adresse {
  margin:0; font-size:15px; font-weight:700; letter-spacing:.01em; color:var(--or);
}
.qr-fermer { min-height:48px; padding:0 var(--r5); }

/* Le mot « Complet » sur une fiche de borne : pas de décompte, une réponse. */
.pastille-complet {
  display:inline-flex; align-items:center; min-height:32px; padding:0 14px;
  border-radius:99px; background:rgba(255,107,133,.14);
  border:1px solid rgba(255,107,133,.5); color:var(--erreur);
  font-size:13.5px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
}

/* Le titre de la vitrine. Centré dans la barre par un positionnement absolu :
   en flux, il aurait poussé les actions de droite dès qu'un mot s'allonge, et
   « centré » aurait voulu dire « centré sur ce qu'il reste », pas sur l'écran.
   Sans graisse et de taille moyenne : il annonce, il ne crie pas, et il laisse
   les affiches être ce qu'on regarde. */
.barre-prog { position:relative; }
.titre-vitrine {
  position:absolute; left:50%; transform:translateX(-50%);
  margin:0; padding:var(--r2) 0;
  font-size:22px; font-weight:400; letter-spacing:.16em;
  text-transform:uppercase;
  /* L'interlettrage ajoute de l'air à droite du dernier caractère, et le titre
     paraît alors décalé vers la gauche de la moitié de cette valeur. On le
     rattrape, sinon « centré » se voit comme « presque centré ». */
  text-indent:.16em;
  color:var(--blanc); white-space:nowrap; pointer-events:none;
}

/* ---------- la nature d'un événement ----------
   Pas de filet de couleur en tête de carte. Il en a existé un ; il posait une
   barre d'accent au-dessus de chaque affiche, c'est-à-dire un trait de plus
   dans le seul écran dont tout le propos est de laisser les visuels parler. La
   nature se lit déjà sur la pastille du tarif, « Entrée libre » et sa couleur
   propre, à l'endroit où l'œil va chercher le prix. Une information dite deux
   fois n'est pas dite deux fois mieux. */

/* « tous les jeudis », à deux endroits selon qui regarde.
   Dans le hall, à côté de la date, en petit : c'est un complément de la date,
   pas une seconde information, et on lit « jeudi 3 septembre, tous les jeudis »
   d'un seul mouvement.
   Sur l'écran de travail, il descend sur la ligne des faits, celle où les
   autres cartes disent « 12 places restantes », et il en prend donc la
   typographie : cette ligne existe pour être comparée d'une carte à l'autre, et
   deux styles sur la même ligne casseraient la comparaison. */
.ce-quand .ce-refrain {
  display:inline-block; margin-left:8px;
  font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--blanc-faible);
}
.ce-meta .ce-refrain { font:inherit; color:inherit; letter-spacing:normal; }

/* « Entrée libre » est plus long qu'un prix : la pastille garde sa hauteur et
   respire un peu moins sur les côtés, plutôt que de déborder de l'affiche. */
.prix-ev[data-nature="libre"] {
  color:var(--succes); border-color:rgba(79,203,151,.45);
  padding-left:11px; padding-right:11px;
}

/* ---------- le choix de la nature, en tête du formulaire ----------
   Deux cartes cliquables et non un menu déroulant : le choix décide de ce que
   le formulaire demande ensuite, il doit donc se voir en entier avant d'être
   fait. Un menu montre une ligne à la fois et cache justement la comparaison. */
.natures { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:var(--r3); }
.case-nature {
  position:relative; display:flex; flex-direction:column; gap:3px; cursor:pointer;
  padding:var(--r3) var(--r4); border-radius:14px;
  border:1px solid var(--trait-fort); background:var(--nuit-800);
  transition:border-color var(--t-micro), background var(--t-micro);
}
.case-nature input { position:absolute; opacity:0; width:1px; height:1px; }
.case-nature:has(input:checked) { border-color:var(--or); background:var(--nuit-700); }
.case-nature:has(input:focus-visible) { outline:3px solid var(--or); outline-offset:2px; }
.cn-nom { font-size:15px; font-weight:700; }
.cn-resume { font-size:12.5px; color:var(--blanc-faible); line-height:1.4; }

/* ---------- la bascule entre les deux moitiés du formulaire ----------
   Les deux versions sont dans la page ; c'est `data-nature`, posé par le
   serveur puis tenu à jour par `coque.js`, qui décide de celle qu'on voit.
   `!important` parce que ces blocs portent par ailleurs `display:flex` ou
   `display:grid` : sans lui, la règle la plus spécifique gagnerait et les deux
   moitiés s'afficheraient ensemble. */
.form-ev[data-nature="billetterie"] .si-local,
.form-ev[data-nature="libre"] .si-odoo { display:none !important; }

/* ---------- déposer une affiche ----------
   Une zone de dépôt au format de l'affiche, qui montre celle qui est déjà en
   place. Sans cet aperçu, on ne sait pas si la soirée en a une, et on redépose
   la même par précaution. */
.depot {
  position:relative; display:flex; align-items:center; justify-content:center;
  width:min(220px, 60vw); aspect-ratio:663 / 830; overflow:hidden;
  border-radius:14px; border:1.5px dashed var(--trait-actif);
  background:var(--nuit-800); cursor:pointer; text-align:center;
  transition:border-color var(--t-micro), background var(--t-micro);
}
.depot:hover, .depot.survol { border-color:var(--or); background:var(--nuit-700); }
.depot-vue { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.depot-vue.vide { display:none; }
.depot-mot {
  position:relative; display:flex; flex-direction:column; gap:4px;
  padding:var(--r3); font-size:13.5px; color:var(--blanc-doux);
}
/* Une affiche posée : le mot reste, en surimpression lisible, sinon on ne sait
   plus qu'on peut la remplacer. */
.depot-vue:not(.vide) ~ .depot-mot {
  background:rgba(6,11,21,.72); border-radius:10px;
  backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
}

/* La case qui déplie la répétition mérite d'être une vraie cible : elle décide
   de fabriquer une soirée ou trente.

   La règle `.champ-ev > label` habille les intitulés de champ, en petites
   capitales espacées : une case à cocher posée directement dans un `.champ-ev`
   en héritait, et sa phrase se lisait « OUI, TOUTES LES SEMAINES, LE MÊME JOUR
   ET À LA MÊME HEURE », un intitulé hurlé au lieu d'une question posée. */
.champ-ev > label.case-ev {
  min-height:48px; font-size:15px; font-weight:600;
  text-transform:none; letter-spacing:normal; color:var(--blanc);
}
.champ-ev.replie { margin-top:var(--r3); }

/* ---------- l'écran d'un rendez-vous récurrent ---------- */
.form-ev.en-ligne {
  display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--r3);
  margin-bottom:var(--r2);
}
.form-ev.en-ligne .champ-ev { flex:0 1 200px; }
/* `width:auto` : le bouton principal est large par défaut, il tirait donc toute
   la place restante de la rangée et le champ de date paraissait minuscule à
   côté. Ici, il n'a que la largeur de son texte. */
.form-ev.en-ligne button {
  flex:0 0 auto; width:auto; min-height:44px; padding:0 var(--r4); border-radius:12px;
}

.liste-dates { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px; }
.liste-dates li { display:flex; align-items:center; gap:var(--r2); }
.ld-fiche {
  flex:1; display:flex; align-items:center; gap:var(--r3); flex-wrap:wrap;
  min-height:44px; padding:0 var(--r3); border-radius:10px;
  color:var(--blanc); text-decoration:none;
}
a.ld-fiche:hover { background:var(--nuit-700); }
.ld-quand { flex:1 1 200px; font-weight:700; font-size:14px; }
.ld-etat, .ld-equipe { font-size:12.5px; color:var(--blanc-faible); }
/* L'action de la ligne : discrète tant qu'on ne la vise pas, et rouge dès
   qu'on la survole. Elle retire une date, ce n'est pas un geste de passage. */
.ld-action {
  flex:none; min-height:44px; padding:0 var(--r3); border-radius:10px;
  border:1px solid var(--trait-fort); background:var(--nuit-800);
  font-size:13px; font-weight:700; color:var(--blanc-faible);
  transition:color var(--t-micro), border-color var(--t-micro);
}
.ld-action:hover { color:var(--erreur); border-color:rgba(255,107,133,.5); }
.liste-dates li.eteint .ld-fiche { opacity:.55; }
.liste-dates li.eteint .ld-quand { text-decoration:line-through; }
.liste-dates li.eteint .ld-action:hover { color:var(--succes); border-color:rgba(79,203,151,.5); }

/* Le choix de la nature occupe toute la largeur du bloc. Sans cette règle, la
   grille de l'atelier le rangeait dans une colonne et posait l'explication dans
   la suivante : trois cartes empilées en filet d'un côté, un paragraphe orphelin
   de l'autre, alors que ce bloc est une question et sa réponse. */
.atelier-ev .choix-nature > * { grid-column:1 / -1; }

/* Une date et une heure côte à côte. L'heure est un menu de demi-heures : un
   `datetime-local` laisse saisir 20:07 et confie son sélecteur au navigateur,
   qui en fait ce qu'il veut. Le menu, lui, se comporte pareil partout et se
   choisit au doigt sur la tablette du hall.

   La date prend la place disponible, l'heure garde la sienne : un menu de
   demi-heures a une largeur connue, l'étirer ne le rend pas plus lisible. */
.duo-moment { display:flex; gap:var(--r2); align-items:stretch; min-width:0; }
.duo-moment input[type="date"] { flex:1 1 auto; min-width:0; }
.duo-moment select { flex:0 0 auto; width:auto; min-width:104px; }

/* Le sélecteur de fichier, habillé comme les autres champs. Le libellé de son
   bouton appartient au navigateur et à sa langue : c'est l'intitulé du champ et
   la ligne d'aide qui disent ce qu'on attend, pas lui. */
.champ-ev input[type="file"] {
  padding:8px var(--r3); font-size:14px; color:var(--blanc-doux); cursor:pointer;
}
.champ-ev input[type="file"]::file-selector-button {
  margin-right:var(--r3); min-height:34px; padding:0 var(--r3);
  border-radius:9px; border:1px solid var(--trait-fort);
  background:var(--nuit-700); color:var(--blanc);
  font:inherit; font-size:13.5px; font-weight:700; cursor:pointer;
}
.champ-ev input[type="file"]:hover::file-selector-button { border-color:var(--or-sourd); }

/* L'écran d'un rendez-vous récurrent réutilise la mise en page de l'atelier,
   mais pas sa grille de champs : ici, chaque bloc contient une seule chose,
   large. Sans cette règle, la grille rangeait la définition « du 3 septembre au
   22 octobre » dans une colonne de 190 px et la coupait sur quatre lignes. */
.ecran-serie .bloc-ev > * { grid-column:1 / -1; }

/* ==========================================================================
   Comptes
   ==========================================================================
   Un écran de lecture, dense mais pas serré : un trésorier le parcourt du
   regard avant de chercher un chiffre précis. Les nombres sont en chiffres
   tabulaires partout, sans quoi une colonne de montants ne s'aligne pas et se
   compare mal, ce qui est pourtant la seule chose qu'on lui demande. */

.compta {
  padding:var(--r4); display:flex; flex-direction:column; gap:var(--r4);
  max-width:1100px; width:100%; margin:0 auto;
}

.compta .aide-compta { font-size:13.5px; color:var(--blanc-faible); margin:0; line-height:1.55; }
.compta .aide-compta strong { color:var(--blanc-doux); }
.compta .pied-compta { padding-top:var(--r3); border-top:1px solid var(--trait); }

.compta .avis-compta {
  margin:0; padding:var(--r3) var(--r4); border-radius:12px;
  border:1px solid var(--trait-fort); background:var(--nuit-700);
  font-size:14px; line-height:1.55; color:var(--blanc-doux);
}
.compta .avis-compta strong { color:var(--blanc); }
.compta .avis-compta.attention { border-color:rgba(255,162,76,.5); }

/* Les périodes : des liens côte à côte. On passe de « ce mois-ci » à « cette
   année » vingt fois de suite, et un menu déroulant demande deux gestes. */
.onglets-periode { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.onglet-periode {
  display:inline-flex; align-items:center; min-height:44px; padding:0 var(--r3);
  border-radius:11px; border:1px solid var(--trait-fort); background:var(--nuit-700);
  color:var(--blanc-doux); font-size:14px; font-weight:600; text-decoration:none;
}
.onglet-periode[aria-current="page"] {
  border-color:var(--or); color:var(--blanc); background:var(--nuit-600);
}
.onglets-periode .aide-compta { margin-left:auto; }

/* Les trois chiffres. Le résultat est le seul qui décide de quelque chose :
   il porte donc la couleur, les deux autres restent neutres. Colorer les trois
   ferait un feu tricolore où plus rien ne ressort. */
.chiffres-compta {
  display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:var(--r3);
}
.chiffre-compta {
  border:1px solid var(--trait-fort); background:var(--nuit-700); border-radius:16px;
  padding:var(--r4); display:flex; flex-direction:column; gap:6px; min-width:0;
}
.chiffre-compta .etiquette {
  font-size:11px; letter-spacing:.13em; text-transform:uppercase;
  color:var(--blanc-faible); font-weight:600;
}
.chiffre-compta .valeur {
  font-size:30px; font-weight:700; letter-spacing:-.02em; color:var(--blanc);
  font-variant-numeric:tabular-nums; overflow-wrap:break-word;
}
.chiffre-compta.resultat { border-color:var(--trait-actif); }
.chiffre-compta.resultat[data-signe="positif"] .valeur { color:var(--succes); }
.chiffre-compta.resultat[data-signe="negatif"] .valeur { color:var(--erreur); }
.chiffre-compta .reference {
  font-size:12.5px; color:var(--blanc-faible); font-variant-numeric:tabular-nums;
}
.chiffre-compta .var { font-weight:700; margin-right:6px; }
.chiffre-compta .var.hausse { color:var(--succes); }
.chiffre-compta .var.baisse { color:var(--alerte); }
/* Volontairement non coloré : une hausse de dépenses n'est pas « bonne » et une
   baisse de recettes n'est pas « mauvaise » dans l'absolu. Sur les deux
   premières cartes, la variation reste grise et c'est au lecteur de juger. */
.chiffre-compta.recette .var, .chiffre-compta.depense .var { color:var(--blanc-doux); }

.bloc-compta {
  border:1px solid var(--trait-fort); background:var(--nuit-700); border-radius:16px;
  padding:var(--r4); display:flex; flex-direction:column; gap:var(--r3);
}
.bloc-compta h2 { font-size:16px; font-weight:700; margin:0; letter-spacing:-.02em; }
.compta .tresorerie {
  margin:0; font-size:34px; font-weight:700; letter-spacing:-.02em;
  font-variant-numeric:tabular-nums; color:var(--blanc);
}
.compta .tresorerie[data-signe="negatif"] { color:var(--erreur); }

/* Les barres. Deux par mois, jamais une barre nette : recettes et dépenses sont
   deux grandeurs, leur différence est déjà donnée en haut, et une barre signée
   cacherait un mois à gros volume dans les deux sens. */
.barres-compta {
  display:flex; align-items:flex-end; gap:var(--r2); height:170px;
  overflow-x:auto; padding-bottom:4px;
}
.mois-compta { display:flex; flex-direction:column; gap:6px; flex:1 1 44px; min-width:44px; height:100%; }
.paire-barres { flex:1; display:flex; align-items:flex-end; gap:3px; }
.barre-c { flex:1; border-radius:4px 4px 0 0; min-height:2px; }
/* Un mois sans écriture n'a pas de barre. Le filet minimal ci-dessus est là
   pour qu'un petit montant reste visible ; appliqué à zéro, il ferait
   ressembler « rien » à « presque rien », ce qui est l'inverse de ce qu'il
   faut lire. */
.barre-c[data-nul="1"] { min-height:0; }
.barre-c.recette { background:var(--succes); }
.barre-c.depense { background:var(--or); }
.mois-nom { font-size:11.5px; color:var(--blanc-faible); text-align:center; }
.legende-compta { margin:0; font-size:12.5px; color:var(--blanc-faible); display:flex; gap:var(--r3); }
.legende-compta .puce { width:10px; height:10px; border-radius:3px; display:inline-block; margin-right:5px; }
.legende-compta .puce.recette { background:var(--succes); }
.legende-compta .puce.depense { background:var(--or); }

.t-compta { border-collapse:collapse; width:100%; font-size:14px; }
.t-compta th, .t-compta td { text-align:left; padding:10px 12px; border-bottom:1px solid var(--trait); }
.t-compta thead th {
  font-size:11px; letter-spacing:.11em; text-transform:uppercase;
  color:var(--blanc-faible); font-weight:600;
}
.t-compta tbody tr:last-child td { border-bottom:none; }
.t-compta .fin { text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
.t-compta td[data-signe="negatif"] { color:var(--erreur); }
.t-compta td[data-signe="positif"] { color:var(--succes); }

@media (max-width:700px) {
  .compta { padding:var(--r3); }
  .chiffre-compta .valeur { font-size:26px; }
}
