/* ═══════════════════════════════════════════════════════════
   ROUZES HUB — portail des modules
   Rail « pilule » sombre flottant ; le nom du module apparaît dans une
   étiquette qui glisse d'une icône à l'autre au survol.
   Icônes : SVG maison, traits 1,5 px (aucune fonte d'icônes).
   Palette : marine #001D57 · azur #008CF8 (identité Rouzes)
   ═══════════════════════════════════════════════════════════ */

/* ── Jetons LOCAUX de hub (15/09/2026) ─────────────────────────────
   Valeurs sans équivalent dans tokens.css, nommées d'après leur premier emploi.
   Un littéral n'est autorisé QUE dans une définition --x: (banc css-harmonie). */
:root {
  --hub-esp-login: 46px;
  --hub-fs-form-boite: 25px;
  --hub-esp-f-inscription: 42px;
  --hub-fs-overlay: clamp(27px,3vw,36px);
  --hub-esp-champ-int: 44px;
  --hub-rail-item: #8C9BC4;
  --hub-zone: rgba(255,255,255,.92);
  --hub-fs-accueil: clamp(25px,3.2vw,34px);
  --hub-etiq-rail: rgba(16,16,22,.92);
  --shadow-hub-etiq-rail: 0 1px 0 0 rgba(255,255,255,.07) inset, 0 10px 30px rgba(4,8,24,.42), 0 2px 6px rgba(4,8,24,.3);
  --hub-fs-etiq-rail: 8.5px;
  --shadow-hub-carte-profil: 0 18px 40px rgba(4,8,24,.28);
  --hub-fs-tv-n: 24px;
  --hub-hover: rgba(255,255,255,.34);
  --hub-hover-2: rgba(255,255,255,.30);
  --hub-webkit-scrollbar-thumb: rgba(255,255,255,.50);
  --hub-webkit-scrollbar-thumb-2: rgba(255,255,255,.68);
  --shadow-hub-conteneur: 0 26px 64px rgba(6,14,44,.34), 0 8px 18px rgba(6,14,44,.2);
  --hub-champ-int: #FBFCFF;
  --hub-champ-int-2: #687491;
  --shadow-hub-champ-int: 0 0 0 4px rgba(0,140,248,.13);
  --hub-overlay: rgba(0,140,248,.32);
  --hub-overlay-2: rgba(79,70,229,.42);
  --hub-overlay-3: #05050A;
  --hub-overlay-4: #7CE7DC;
  --hub-overlay-5: #B9C6E8;
  --hub-iso-grille: rgba(124,196,255,.07);
  --hub-facette: rgba(0,140,248,.5);
  --hub-facette-2: rgba(79,70,229,.08);
  --hub-teasers: #D7E4FB;
  --hub-teasers-2: rgba(124,196,255,.26);
  --hub-teasers-3: rgba(0,140,248,.10);
  --hub-teasers-4: #93A7D2;
  --hub-champ-ico: #9AACD0;
  --hub-oeil: rgba(0,140,248,.08);
  --hub-btn-pilule: #0072D6;
  --shadow-hub-btn-pilule: 0 10px 22px rgba(0,102,214,.32);
  --hub-tourne: rgba(255,255,255,.35);
  --hub-login-pied: #FFFFFF;
  --hub-login-pied-2: rgba(4,8,24,.4);
  --hub-login-pied-voile: rgba(4,8,24,.6);
  --hub-dock: rgba(8,14,40,.30);
  --hub-dock-2: rgba(8,14,40,.16);
  --hub-rail-trait: rgba(255,255,255,.12);
  /* .rail-item.bientot : nommé --hub-rail-item au lot D alors que l'icône active l'avait déjà (16/09/2026). */
  --hub-rail-bientot: #7A88AD;
  --hub-rail-bientot-2: #C9D3EA;
  --hub-rail-avatar: #123C8F;
  --shadow-hub-rail-avatar: inset 0 0 0 1px rgba(255,255,255,.2);
  /* Avatar du créateur de l'app (06/10/2026) : le même or que `.pastille-or` du socle. */
  --hub-avatar-or: linear-gradient(140deg,#F7E08A 0%,#D4A72C 50%,#A9801A 100%);
  --hub-avatar-or-fg: #3A2A00;
  --shadow-hub-avatar-or: inset 0 0 0 1px rgba(255,255,255,.45), 0 0 0 1px rgba(169,128,26,.6);
  /* .etiq-soon (badge « bientôt ») : nommé --hub-etiq-rail au lot D, comme le FOND de la bulle —
     le second écrasait le premier et la bulle du nom des modules était jaune (16/09/2026). */
  --hub-etiq-soon: #FFCE8A;
  --hub-etiq-soon-2: rgba(255,196,107,.14);
  --shadow-hub-etiq-soon: 0 0 0 1px rgba(255,196,107,.22) inset;
  --shadow-hub-cp-past: inset 0 0 0 1px rgba(4,8,24,.16);
  --hub-cp-voile: rgba(4,8,24,.55);
  --hub-cp-sortir: #C1272D;
  --hub-dock-voile: rgba(8,14,40,.28);
  --shadow-hub-btn-ajout-tache: 0 6px 16px rgba(37,99,235,.25);
  --hub-taches-vide: #F8FAFD;
  --hub-tv-retard: #FCE9E9;
  --hub-tv-retard-2: #F3D3D3;
  --hub-tv-retard-3: #A32222;
  --hub-tv-jour: #FBEECB;
  --hub-tv-jour-2: #F0DFA8;
  --hub-tv-jour-3: #7A5A05;
  --hub-tv-ligne: #FAFBFD;
  --hub-tv-a-retard: #FBE0E0;
  --hub-tv-a-calme: #F1F3F7;
}



:root{
  --marine:#001D57;
  --abysse:#0B0B10;      /* corps du rail */
  --abysse-2:#15151C;    /* survols dans le rail */
  --color-rail:var(--abysse);  /* le corps du rail, en jeton de couleur : l'anneau des bulles */
  /* L'ACTION, une seule couleur pour tout l'ERP (décision Cyril, 22/08/2026)
     — et le contraste tranche laquelle : blanc sur --azur donne 3,4:1, sous
     le seuil AA du petit texte ; sur --azur-fonce il donne 5,2:1. Le hub
     employait déjà #0066D6 comme SURVOL de ses boutons : l'ERP converge donc
     sur une couleur qu'il possédait, sans en inventer une. L'azur clair garde
     tout ce qui ne porte pas de texte : décor, filets, teinte de Planning. */
  --action:#0066D6;
  --action-fonce:#0055BC;
  /* LA MARQUE. Volontairement séparée de --azur, qui vaut pourtant la
     même chose aujourd'hui : les ambiances remappent --azur, et le cube
     Rouzès se serait retrouvé orange en « Braise », vert en « Forêt ».
     Une identité de marque ne suit pas une préférence d'affichage. */
  --marque:#008CF8;
  --indigo:#4F46E5;

  /* La palette française (--azur, --encre, --ligne…), les polices (--titres,
     --texte, --mono), --rayon, --ombre/--ombre-lg, --ressort/--doux et le PONT du
     21/08 ont disparu le 15/09/2026 : le hub parle en --color-*, --font-*,
     --radius-*, --shadow-*, --ease-* du socle. --action garde sa valeur FIXE :
     les ambiances remappent --color-primary, pas la couleur d action (22/08). */

  --pied-form:112px;   /* place réservée sous le bouton des formulaires */
  /* LE DOCK EST UN GALET, PAS UN RECTANGLE ARRONDI (02/09/2026, demande de
     Cyril : « plus fin, pour que le haut et le bas soient bien arrondis »).
     Largeur = 2 × rayon : chaque bout est un demi-cercle exact. À 64 px, les
     deux arcs de 26 laissaient 12 px de plat entre eux et les bouts se
     lisaient « coins arrondis ». Dérivée du rayon pour que l'un ne puisse
     plus bouger sans l'autre ; les 12 px rendus vont à la zone de travail. */
  --rail-l:calc(2 * var(--radius-dock));      /* 52 px */
  /* Rembourrage haut du rail. Nommé parce que le TROU de la marque s'y
     réfère : le déplacer déplace le trou avec lui (cf. .rail).
     Rayon moins la demi-boîte du logo (34 px) : le cube est CENTRÉ sur le
     centre du demi-cercle du haut, comme l'avatar sur celui du bas. */
  --rail-haut:calc(var(--radius-dock) - 17px); /* 9 px */
  /* Silhouette du cube Rouzès, reprise trait pour trait du fichier
     logo-rouzes.svg. Elle ne sert qu'à DÉCOUPER : la couleur du remplissage
     n'a aucune importance, seule compte l'opacité. */
  --cube-masque:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30.7 38'%3E%3Cpath fill='%23000' d='M30.1 8.18247L15.92 0V11.0432L20.5322 13.7069L30.1 8.18247Z M14.76 11.0432V0L0.580017 8.18247L10.1478 13.7069L14.76 11.0432Z M30.68 9.19043V20.2393L15.9257 28.7597V17.7108L30.68 9.19043Z M14.76 17.7108V28.7597L0 20.2393V9.19043L14.76 17.7108Z M0 37.9504L14.1743 29.7679L0 21.5854V37.9504Z M30.6801 26.9068L26.0679 24.2432L16.5057 29.7676L30.6801 37.9501V26.9068Z'/%3E%3C/svg%3E");
  --radius-dock:26px;
  --shadow-dock:0 18px 50px rgba(8,14,40,.28), 0 2px 6px rgba(8,14,40,.14);

  /* ─── Clartés imposées aux teintes de MODULES[] ───────────────────────────
     Brutes, les treize teintes sont illisibles dans le rail : elles ont été
     choisies pour des TUILES sur fond blanc, où c'est leur profondeur qui
     porte. Posées sur du #0B0B10, neuf d'entre elles tombent sous 4,5:1.
     D'où le principe repris de la maquette : on impose la CLARTÉ et on ne
     garde de la teinte que son chroma et sa nuance — l'identité du module
     survit, sa lisibilité cesse de dépendre du hasard du nuancier.

     LES VALEURS NE SONT PAS CELLES DE LA MAQUETTE, et c'est délibéré : elle
     réglait un panneau BLANC. --L-actif 0,77 s'y transpose (le fond reste
     sombre des deux côtés), mais son --L-survol 0,515 était une clarté
     BASSE pour du texte sombre sur blanc. Reprise ici, elle donnerait 2,84
     à 3,51:1 — les TREIZE sous le seuil. Recalculées sur les fonds réels du
     rail, avec l'écrêtage de gamut que Chrome applique réellement (et non la
     réduction de chroma du CSS Color 4, qui rend d'autres couleurs) :
       --L-actif  0,77 sur la pilule → 4,89:1 mini (RH), 6,69:1 maxi
       --L-survol 0,66 sur --abysse-2 → 4,98:1 mini (Clients), 6,20:1 maxi
     Plancher mesuré du survol : 0,63 rend 4,51:1 et 0,62 déjà 4,35:1. 0,66
     est une marge, pas une limite — la falaise est à trois centièmes.
     `--L-pilule` a été SUPPRIMÉE avec la pilule teintée : plus rien ne s'en
     servait depuis que la languette porte l'état actif et que son icône prend
     l'encre du dock. Un jeton que personne ne lit est un piège pour le
     prochain, qui le croira signifiant. */
  --L-actif:0.77;
  --L-survol:0.66;

  /* Glissement de la pilule d'une icône à l'autre. Valeurs de la maquette,
     reprises telles quelles ; le facteur .6 qui les ramène à 324 ms vit dans
     hub.js, avec la raison. --rebond est en PIXELS et non en fraction de la
     course : la course dépend de la hauteur d'entrée (7 px au bureau, 9 px
     en tiroir), une ordonnée de Bézier ferait donc varier le dépassement
     avec elle. Une image clé en pixels dépasse de la valeur écrite. */
  --duree-pilule:900ms;
  --rebond:2.5px;
  --languette-corps:41px;   /* hauteur du corps : lue par le rail ET l'étiquette */

}

html,body{margin:0;padding:0;height:100%}
/* les vues sont basculées via l'attribut hidden : il doit battre
   les display:grid/flex posés par les classes (.login, .app, …) */

body{
  font-family:var(--font-body);
  color:var(--color-foreground);
  -webkit-font-smoothing:antialiased;
  line-height:1.5;
  /* LE FOND A DÉMÉNAGÉ dans css/ambiances.css, qui le dérive de
     l'ambiance choisie. Le laisser ici en dur aurait été du code mort et
     trompeur : ambiances.css étant chargé après, il gagnait de toute
     façon — mais la prochaine personne aurait cherché la couleur au
     mauvais endroit. Le dégradé y est le REPLI ; par-dessus vient la
     toile peinte par js/ambiance.js. */
}

/* Le temps que le démarrage décide (session reprise ou connexion à
   demander), on n'affiche ni l'un ni l'autre — sinon l'écran de connexion
   clignote et accepte une saisie qui serait balayée. */
body.demarrage #vueLogin,
body.demarrage #vueApp{visibility:hidden}

button{font-family:inherit;cursor:pointer}
a{color:inherit;text-decoration:none}

/* ── Barre de défilement ─────────────────────────────────────────────
 *
 * Même geste que le socle (pouce en pilule, sans flèches, invisible au repos
 * et révélé au survol), mais en BLANC : le hub n'est pas une page claire, il
 * est posé sur le satin coloré de l'ambiance. `--color-foreground`, qui vaut
 * l'encre sombre, y serait presque invisible — d'où une règle à part plutôt
 * qu'un import de composants.css, que le hub ne charge pas.
 *
 * PIÈGE — depuis Chrome 121, poser `scrollbar-width` fait IGNORER tout le
 * bloc ::-webkit-* : ces deux propriétés sont réservées à Firefox.
 *
 * L'ordre repos → bloc survolé → pouce survolé compte : ces sélecteurs
 * pèsent le même poids, c'est le dernier écrit qui gagne.
 *
 * `.rail-liste`, qui masque sa barre, garde le dernier mot : sa classe est
 * plus spécifique que ces sélecteurs nus. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: transparent transparent; }
  *:hover,
  *[data-defile] { scrollbar-color: var(--hub-hover) transparent; }
}
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background-color: transparent; }
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
::-webkit-scrollbar-thumb {
  background-color: transparent;
  background-clip: content-box;
  border: 3px solid transparent;
  border-radius: var(--radius-full);
  transition: background-color .2s ease;
}
:hover::-webkit-scrollbar-thumb,
[data-defile]::-webkit-scrollbar-thumb { background-color: var(--hub-hover-2); }
::-webkit-scrollbar-thumb:hover { background-color: var(--hub-webkit-scrollbar-thumb); }
::-webkit-scrollbar-thumb:active { background-color: var(--hub-webkit-scrollbar-thumb-2); }
/* --azur-fonce, PAS --azur : l'anneau de focus de toute l'application se pose
   presque partout sur du blanc (`.zone` est en rgba(255,255,255,.92)), et
   `--azur` y tombe à 3,44:1 par défaut et à 1,92:1 en ambiance « jaune »
   (#EAB308) — sous les 3:1 exigés par SC 1.4.11, donc invisible au clavier.
   `--azur-fonce` est la valeur que CHAQUE ambiance calibre déjà pour le fond
   blanc (voir l'en-tête d'ambiances.css). */

/* icônes : un seul jeu de règles pour tout le hub */
.sprite{position:absolute;width:0;height:0;overflow:hidden}

.sr{
  position:absolute;width:1px;height:1px;
  margin:calc(-1 * var(--space-05));padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0;
}

/* Deux fonds pour un même sur-titre : le volet SOMBRE de la connexion, où
   `--azur` est la bonne valeur, et le tableau de bord, en BLANC, où il ne
   donne que 3,44:1 (1,92:1 en ambiance « jaune »). D'où la surcharge scopée
   plus bas — changer la règle générale aurait éteint le sur-titre du volet. */
.sur-titre{
  font:600 var(--fs-2xs)/1 var(--font-mono);
  letter-spacing:.22em;text-transform:uppercase;
  color:var(--color-primary);margin:0 0 var(--space-25);
}
#contenu .sur-titre{color:var(--color-primary-hover)}


/* Le socle remonte `--fs-control` a 16px sous 640px de large : sous 16px,
   Safari iOS agrandit la page au focus d'un champ et ne la reduit JAMAIS
   ensuite. Le hub declare 13px a :root, plus tard dans la cascade que le bloc
   @media de tokens.css — il l'annulait donc, et le portail etait le seul ecran
   de l'ERP a zoomer a chaque saisie. Les 13px restent l'idiome de ses modales
   au clavier ; au doigt, c'est 16. */
@media (max-width: 640px) {
  :root { --fs-control: 16px; }
}

/* ═══════════════ CONNEXION ═══════════════
   Deux formulaires côte à côte sous un volet coulissant (le motif du
   « double slider »), habillé avec les couleurs du rail : abysse #0B0B10,
   indigo #4F46E5, azur #008CF8. Le volet glisse d'un côté à l'autre et
   révèle le formulaire qui était dessous. */

.login{
  display:grid;place-items:center;
  min-height:100vh;min-height:100dvh;
  padding:var(--space-5) var(--space-4) var(--hub-esp-login);
}

.conteneur{
  position:relative;overflow:hidden;
  width:min(880px,100%);min-height:552px;
  background:var(--color-surface);
  border-radius:var(--radius-dock);
  box-shadow:var(--shadow-hub-conteneur);
  animation:monte .5s var(--ease-ressort) both;
}
@keyframes monte{from{opacity:0;transform:translateY(16px)}}

/* ─── le formulaire de connexion ───
   Depuis le retrait de la demande d'accès (16/09/2026) il n'y en a plus
   qu'un : il tient la moitié gauche, le volet la moitié droite, et plus
   rien ne glisse. */
.form-boite{
  position:absolute;top:0;height:100%;width:50%;
}
/* Contenu ANCRÉ EN BAS et liens sortis du flux : le bouton principal tombe
   toujours à la même hauteur, quel que soit le nombre de champs. */
.form-boite form{
  position:relative;height:100%;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding:0 clamp(26px,4vw,44px) var(--pied-form);
  text-align:left;
  /* fond OPAQUE : le formulaire se détache du volet et de l'ambiance */
  background:var(--color-surface);
}
/* Un seul formulaire à la fois : connexion OU nouveau mot de passe (PRD-02) */
.form-boite form[hidden]{display:none}
.form-pied{
  position:absolute;left:0;right:0;bottom:44px;
  display:flex;flex-direction:column;align-items:center;gap:var(--space-05);
  padding:0 clamp(26px,4vw,44px);
}
.logo-form{width:132px;height:32px;fill:var(--marine);stroke:none;margin-bottom:var(--space-5)}
.form-boite h2{font:800 var(--hub-fs-form-boite) var(--font-titres);letter-spacing:-.01em;margin:0 0 var(--space-5)}

.f-connexion{left:0}          /* moitié gauche, à demeure */

.champ-int select{
  width:100%;height:48px;padding:0 var(--space-4);
  font:400 var(--fs-control) var(--font-body);color:var(--color-foreground);
  background:var(--hub-champ-int);
  border:1.5px solid var(--color-border);border-radius:var(--radius-xl);
  appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--hub-champ-int-2) 50%),linear-gradient(135deg,var(--hub-champ-int-2) 50%,transparent 50%);
  background-position:calc(100% - 19px) 21px, calc(100% - 14px) 21px;
  background-size:5px 5px, 5px 5px;
  background-repeat:no-repeat;
}
.champ-int select:focus{
  outline:none;border-color:var(--color-primary);
  box-shadow:var(--shadow-hub-champ-int);
}

/* ─── volet coulissant ─── */
.overlay-boite{
  position:absolute;top:0;left:50%;z-index:var(--z-toast);
  width:50%;height:100%;overflow:hidden;
  transition:transform .62s var(--ease-sortie);
}

.overlay{
  position:relative;left:-100%;
  width:200%;height:100%;
  color:var(--color-surface);
  transform:translateX(0);
  transition:transform .62s var(--ease-sortie);
  background:
    radial-gradient(680px 460px at 78% 8%, var(--hub-overlay), transparent 58%),
    radial-gradient(560px 480px at 12% 96%, var(--hub-overlay-2), transparent 58%),
    linear-gradient(152deg, var(--abysse-2) 0%, var(--abysse) 52%, var(--hub-overlay-3) 100%);
}

.overlay-panneau{
  position:absolute;top:0;height:100%;width:50%;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding:0 clamp(26px,3.4vw,42px) var(--pied-form);
  transform:translateX(0);
  transition:transform .62s var(--ease-sortie);
}
.overlay-droite{right:0;transform:translateX(0)}

.overlay h2{
  position:relative;
  font:900 var(--hub-fs-overlay)/1.1 var(--font-titres);
  letter-spacing:-.015em;margin:0 0 var(--space-4);
}
.overlay h2 span{
  background:linear-gradient(92deg,var(--color-hover-border),var(--hub-overlay-4));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.overlay p{position:relative;margin:0;font-size: var(--fs-body);color:var(--hub-overlay-5)}
.overlay .sur-titre{position:relative;color:var(--color-hover-border)}

/* décor du volet (grille isométrique, facettes, cube) */
.iso-grille{
  position:absolute;inset:0;opacity:.5;pointer-events:none;
  background:
    repeating-linear-gradient(60deg,  var(--hub-iso-grille) 0 1px, transparent 1px 48px),
    repeating-linear-gradient(-60deg, var(--hub-iso-grille) 0 1px, transparent 1px 48px);
  -webkit-mask-image:radial-gradient(120% 100% at 30% 40%, var(--color-foreground) 30%, transparent 78%);
          mask-image:radial-gradient(120% 100% at 30% 40%, var(--color-foreground) 30%, transparent 78%);
}
.facettes{position:absolute;inset:0;pointer-events:none}
.facette{
  position:absolute;display:block;
  background:linear-gradient(135deg, var(--hub-facette), var(--hub-facette-2));
  clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%);
  animation:flotte 15s ease-in-out infinite alternate;
  will-change:transform;
}
.facette.f1{width:96px;height:56px;top:12%;right:12%}
.facette.f2{width:54px;height:32px;top:30%;right:31%;opacity:.6;animation-delay:-4s}
.facette.f3{width:40px;height:46px;top:58%;right:8%;opacity:.5;animation-delay:-7s}
.facette.f4{width:74px;height:42px;bottom:19%;right:25%;opacity:.3;animation-delay:-2s}
.facette.f5{width:28px;height:28px;top:21%;left:42%;opacity:.5;animation-delay:-9s}
.facette.f6{width:122px;height:70px;bottom:6%;left:-34px;opacity:.18;animation-delay:-11s}
@keyframes flotte{
  from{transform:translateY(-9px) rotate(-1deg)}
  to{transform:translateY(12px) rotate(2deg)}
}
.cube-fond{
  position:absolute;right:-70px;bottom:-56px;width:300px;height:372px;
  opacity:.07;fill:var(--color-surface);stroke:none;pointer-events:none;
}
.cube-fond use{fill:var(--color-surface)}

.teasers{display:flex;flex-wrap:wrap;gap:var(--space-2);list-style:none;margin:var(--space-5) 0 0;padding:0;position:relative}
.teasers li{
  display:inline-flex;align-items:center;gap:var(--space-2);
  font:500 var(--fs-xs) var(--font-body);color:var(--hub-teasers);
  border:1px solid var(--hub-teasers-2);
  background:var(--hub-teasers-3);
  padding:var(--space-15) var(--space-3);border-radius:var(--radius-full);
}
.teasers li .ico{width:14px;height:14px;color:var(--color-hover-border)}
.teasers .teaser-plus{border-style:dashed;color:var(--hub-teasers-4);background:transparent}

/* ─── champs (communs aux deux formulaires) ─── */
form .champ{display:block;margin-bottom:var(--space-4)}   /* .champ du socle = colonne ; les formulaires du hub empilent en bloc */
.champ-nom{display:block;font:600 var(--fs-xs) var(--font-body);color:var(--color-foreground);margin-bottom:var(--space-15)}
.champ-int{position:relative;display:block}
.champ-ico{
  position:absolute;left:14px;top:50%;transform:translateY(-50%);
  width:17px;height:17px;color:var(--hub-champ-ico);pointer-events:none;
}
.champ-int input{
  width:100%;height:48px;
  padding:0 var(--hub-esp-champ-int) 0 var(--hub-esp-f-inscription);
  font:400 var(--fs-control) var(--font-body);color:var(--color-foreground);
  background:var(--hub-champ-int);
  border:1.5px solid var(--color-border);border-radius:var(--radius-xl);
  transition:border-color .15s, box-shadow .15s, background .15s;
}
.champ-int input::placeholder{color:var(--hub-champ-int-2)}
.champ-int input:focus{
  outline:none;border-color:var(--color-primary);
  box-shadow:var(--shadow-hub-champ-int);
  background:var(--color-surface);
}
.oeil{
  position:absolute;right:7px;top:50%;transform:translateY(-50%);
  display:grid;place-items:center;
  width:34px;height:34px;border:0;background:transparent;border-radius:var(--radius-full);
  color:var(--hub-champ-ico);
}
.oeil:hover{color:var(--color-primary);background:var(--hub-oeil)}
.oeil .ico{width:18px;height:18px}

/* zone d'erreur toujours présente (role=alert) : hauteur nulle quand vide */
.erreur{
  margin:0;font-size: var(--fs-sm);color:var(--color-destructive);
  display:flex;align-items:flex-start;gap:var(--space-2);
}
.erreur:not(:empty){margin:calc(-1 * var(--space-05)) 0 var(--space-3)}
.erreur:not(:empty)::before{
  content:"";flex:none;width:14px;height:14px;margin-top:var(--space-05);
  border-radius:50%;background:var(--color-destructive);
  -webkit-mask:radial-gradient(circle at 50% 78%, var(--color-foreground) 1.4px, transparent 1.5px),
          linear-gradient(var(--color-foreground) 0 0) 50% 25%/1.6px 6px no-repeat, linear-gradient(var(--color-foreground) 0 0);
  -webkit-mask-composite:xor;
          mask:radial-gradient(circle at 50% 78%, var(--color-foreground) 1.4px, transparent 1.5px),
          linear-gradient(var(--color-foreground) 0 0) 50% 25%/1.6px 6px no-repeat, linear-gradient(var(--color-foreground) 0 0);
  mask-composite:exclude;
}
.erreur.ok{color:var(--color-success)}
.erreur.ok::before{background:var(--color-success)}

.btn-pilule{
  width:100%;height:48px;min-height:48px;border:0;border-radius:var(--radius-full);
  display:inline-flex;align-items:center;justify-content:center;gap:var(--space-25);
  font:700 var(--fs-xs) var(--font-titres);letter-spacing:.09em;text-transform:uppercase;color:var(--color-surface);
  background:linear-gradient(135deg,var(--hub-btn-pilule),var(--action-fonce));
  box-shadow:var(--shadow-hub-btn-pilule);
  transition:transform .18s var(--ease-ressort), box-shadow .18s, filter .15s;
}
.btn-pilule:hover{transform:translateY(-1px);box-shadow:var(--shadow-hub-btn-pilule)}
.btn-pilule:active{transform:scale(.97)}
.btn-pilule[disabled]{opacity:.75;cursor:wait;transform:none}
.tourne{
  width:16px;height:16px;flex:none;border-radius:50%;
  border:2px solid var(--hub-tourne);border-top-color:var(--color-surface);
  animation:tourne .7s linear infinite;
}
@keyframes tourne{to{transform:rotate(360deg)}}

.lien-discret{
  display:block;margin:var(--space-3) auto 0;
  border:0;background:transparent;
  font:500 var(--fs-xs) var(--font-body);color:var(--color-muted-fg);
  text-decoration:underline;text-underline-offset:3px;
  transition:color .15s;
}
.lien-discret:hover{color:var(--color-primary-hover)}
.lien-discret[disabled]{opacity:.6;cursor:wait}

.login-pied{
  /* Blanc opaque sur un voile sombre : le fond suit l'ambiance, et le blanc
     nu tombait à 1,2-1,9:1 en « Jaune » (CSA-09). Avec le voile : 6,2:1 au pire. */
  display:table;margin:var(--space-5) auto 0;padding:var(--space-1) var(--space-3);
  border-radius:var(--radius-md);background:var(--hub-login-pied-voile);text-align:center;
  font:500 var(--fs-2xs) var(--font-mono);letter-spacing:.04em;color:var(--hub-login-pied);
  text-shadow:0 1px 3px var(--hub-login-pied-2);
}

/* ═══════════════ APPLICATION ═══════════════ */

.app{min-height:100vh}
.scene{
  /* Une seule gouttière, 12 px, autour du dock comme entre lui et la zone
     (02/09/2026, demande de Cyril : « resserrer les marges sur le côté du
     dock »). 18 px laissaient le galet flotter loin de tout ; à 12 il reste
     détaché du bord — le rayon de 26 fait le reste. Sous 920 px, la règle du
     bas resserre encore à 10.
     À DROITE, RIEN : la zone de travail FILE jusqu'au bord de la fenêtre
     (« que l'arête haute et basse file jusqu'à la fin de la fenêtre, donc
     plus d'arrondi »). Seul le dock flotte ; la zone est une feuille posée
     contre le bord, arrondie du seul côté qui regarde le galet (cf. .zone). */
  display:flex;gap:var(--space-3);align-items:stretch;
  height:100vh;height:100dvh;
  padding:var(--space-3) 0 var(--space-3) var(--space-3);
}

/* ─── dock : le rail seul. Le nom du module apparaît dans une étiquette
       flottante au survol (voir .etiq-rail) — rien ne pousse le contenu. ─── */
.dock{
  position:relative;z-index:var(--z-menu);
  width:var(--rail-l);flex:none;
}

.rail{
  position:relative;z-index:2;height:100%;
  display:flex;flex-direction:column;align-items:center;
  /* Bas : l'avatar (38 px) CONCENTRIQUE au demi-cercle du bas — le même
     anneau de 7 px dessous que sur les côtés. C'est lui qui fait lire un
     galet, et non un rectangle dont on aurait arrondi les coins. */
  padding:var(--rail-haut) 0 calc((var(--rail-l) - 38px) / 2);
  background:var(--abysse);
  border-radius:var(--radius-dock);

  /* ─── LA MARQUE EST UN TROU, PAS UN DESSIN ───────────────────────────
     Le cube n'est pas peint : il est DÉCOUPÉ dans le rail, et c'est le
     fond animé qui apparaît au travers. La marque prend donc la couleur
     de l'ambiance sans jamais être repeinte — ce qui aurait été une
     faute de charte (un cube Rouzès orange n'existe pas).

     Deux calques de masque : le rectangle plein, moins la silhouette du
     cube. `exclude` est le nom standard, `xor` celui de l'ancien WebKit.

     LA POSITION EST CALCULÉE, PAS ÉCRITE : le trou doit tomber
     exactement sur .rail-logo. Horizontalement `center` s'en charge
     (le rail centre ses enfants, y compris en tiroir large).
     Verticalement, c'est le rembourrage du haut plus la moitié de ce qui
     reste dans la boîte de 34 px. Toucher à --rail-haut ou à la taille du
     cube déplace le trou avec eux ; les écrire en dur ici aurait produit
     un décalage silencieux à la première retouche du rail. */
  --cube-l:21px;
  --cube-h:26px;
  /* ─── LA LANGUETTE : le même geste que la marque, porté sur l'entrée
     active. Elle n'est pas peinte, elle est DÉCOUPÉE dans le rail, à ras du
     bord droit — et c'est l'ambiance qui apparaît au travers. C'est la
     transposition littérale de la découpe de la maquette : là-bas un panneau
     clair percé laissait voir la page sombre ; ici un rail sombre percé
     laisse voir le fond animé.

     POURQUOI UN VRAI TROU, et non une pastille qui recopierait le fond : le
     fond n'est pas un dégradé CSS mais une TOILE peinte par js/ambiance.js
     (css/ambiances.css n'en porte que le repli). Aucune copie ne peut le
     suivre ; seul un masque laisse passer ce qui est réellement derrière.

     Bord droit franc, bord gauche arrondi au rayon de .rail-item : la
     languette sort du rail au lieu d'y flotter. Sa largeur va du bord gauche
     de l'entrée au bord du rail — (52 + 40) / 2 = 46 px — pour que la pilule
     teintée, qui fait 40 px, tombe ENTIÈREMENT dans le trou et ne laisse pas
     un croissant sombre sur le côté.

     `--languette-y` est posée en JS, seul lien avec la position de l'entrée
     active. Sa valeur par défaut la met hors champ : l'état « aucun module
     ouvert » ne demande donc aucune règle.

     TROIS calques, DEUX opérateurs. Le composite s'applique par paires en
     remontant : cube XOR (languette XOR plein) = le rail moins ses deux
     trous. Ajouter un calque sans son opérateur laisserait `add` par défaut
     sur la paire manquante, et le rail redeviendrait plein — sans erreur. */
  /* LES COINS RENTRANTS. Sans eux la languette est une morsure rectangulaire :
     le bord du rail passe du plein au vide d'un coup, et l'entaille ne se
     raccorde à rien. Ce sont eux qui font lire une LANGUETTE plutôt qu'un trou
     — c'est exactement ce que la maquette obtenait avec ses deux `.coin`.
     La forme dépasse donc de 10 px en haut et en bas (40 + 2 × 10 = 60), et
     ces deux débords ne retirent de matière qu'au VOISINAGE DU BORD DROIT, en
     courbe rentrante. Le rayon des fillets est plus petit que celui du corps
     (10 contre 13) parce que l'écart entre deux entrées n'est que de 4 px :
     à 13 px les courbes mordraient franchement sur les icônes voisines.
     Le décalage de -10 px à la position verticale remet le CORPS de la
     languette en face de l'entrée — sans lui, tout glisserait vers le bas. */
  /* LES QUATRE MESURES DE LA LANGUETTE, réglables. Le chemin du masque est
     ENGENDRÉ par js/hub.js (formeLanguette) à partir d'elles : un data-URI ne
     lit pas les variables CSS, écrire les rayons en dur dans le SVG les aurait
     rendus intouchables. Toute modification ici doit donc passer par un rappel
     de formeLanguette() — c'est ce que fait le panneau de réglages. */
  /* Valeurs réglées à l'écran par Cyril le 14/08/2026, aux curseurs de
     ?reglages, et figées ici. `--languette-r` vaut 21 px mais la construction
     du tracé la borne à la demi-hauteur du corps, soit 20,5 : c'est la limite
     géométrique au-delà de laquelle les deux arrondis du flanc se rejoindraient
     et le tracé se replierait sur lui-même. */
  /* --languette-corps est déclarée sur :root (cf. son @property) : l'étiquette
     la lit aussi, et elle n'est pas descendante du rail. */
  --languette-r:21px;       /* arrondi du corps, côté rail — borné à 20,5 */
  --languette-fillet:20px;  /* rayon des deux coins rentrants */
  /* DÉRIVÉE de la largeur du rail, plus le pixel que Cyril avait ajouté aux
     curseurs (53 pour 52) : le trou traverse franchement le bord droit.
     Enregistrée en @property, elle est CALCULÉE avant que formeLanguette()
     ne la lise — c'est ce qui rend ce calc() possible (cf. plus bas). */
  --languette-l:calc((var(--rail-l) + 40px) / 2 + 1px);   /* 47 px */
  --languette-h:calc(var(--languette-corps) + 2 * var(--languette-fillet));
  --languette-masque:none;  /* posé par formeLanguette() */

  -webkit-mask-image:var(--cube-masque), var(--languette-masque), linear-gradient(var(--color-foreground),var(--color-foreground));
          mask-image:var(--cube-masque), var(--languette-masque), linear-gradient(var(--color-foreground),var(--color-foreground));
  -webkit-mask-position:center calc(var(--rail-haut) + (34px - var(--cube-h)) / 2), right calc(var(--languette-y, -200px) - var(--languette-fillet)), 0 0;
          mask-position:center calc(var(--rail-haut) + (34px - var(--cube-h)) / 2), right calc(var(--languette-y, -200px) - var(--languette-fillet)), 0 0;
  -webkit-mask-size:var(--cube-l) var(--cube-h), var(--languette-l) var(--languette-h), 100% 100%;
          mask-size:var(--cube-l) var(--cube-h), var(--languette-l) var(--languette-h), 100% 100%;
  -webkit-mask-repeat:no-repeat, no-repeat, no-repeat;
          mask-repeat:no-repeat, no-repeat, no-repeat;
  -webkit-mask-composite:xor, xor;
          mask-composite:exclude, exclude;
}

/* SANS CET ENREGISTREMENT, PAS DE REBOND. Une propriété personnalisée non
   déclarée est un simple flot de jetons : le navigateur ne sait pas que
   « 274px » et « 281px » sont des longueurs, il les permute donc à mi-course
   au lieu de les interpoler. La languette sauterait d'une entrée à l'autre,
   sans une erreur ni un avertissement. `@property` lui donne un type, et
   c'est ce type qui rend l'animation possible. */
@property --languette-y{
  syntax:"<length>";
  inherits:false;
  initial-value:-200px;
}
/* LES MESURES DE FORME AUSSI, et pour une raison différente du rebond : une
   propriété NON enregistrée rend son texte BRUT à getPropertyValue. Sur
   `--languette-l: calc((var(--rail-l) + 40px) / 2)`, le JS qui engendre le
   masque recevait donc la chaîne `calc(…)` et non `52px` — parseFloat en
   tirait NaN, et la forme retombait en silence sur sa valeur par défaut.
   Enregistrées, elles sont CALCULÉES avant d'être lues — c'est ce qui
   autorise --languette-l à redevenir un calc() sur --rail-l (02/09/2026). */
@property --languette-r      { syntax:"<length>"; inherits:false; initial-value:13px }
@property --languette-fillet { syntax:"<length>"; inherits:false; initial-value:10px }
@property --languette-l      { syntax:"<length>"; inherits:false; initial-value:46px }
/* `inherits:true` et déclarée sur :root, à la différence des trois autres :
   la hauteur du corps est lue par le rail ET par l'étiquette, qui n'est pas
   son descendante. Laissée sur .rail en `inherits:false`, l'étiquette
   retombait sur la valeur initiale — 40 px au lieu des 41 réglés — sans que
   rien ne le signale. */
@property --languette-corps  { syntax:"<length>"; inherits:true;  initial-value:40px }


/* ─── L'ICÔNE ACTIVE, HORS DU MASQUE ──────────────────────────────────────
   Un masque rogne TOUT le sous-arbre de l'élément qui le porte : percer le
   rail perce aussi l'icône de l'entrée active, qui disparaîtrait dans son
   propre trou. Elle est donc redoublée par un élément posé sur le .dock, en
   dehors de .rail — même principe que l'étiquette .etiq-rail, qui vit déjà hors du
   rail pour échapper au rognage de .rail-liste.
   Sa teinte est INVERSÉE par rapport à l'entrée : dans la maquette le trou
   laissait voir un fond sombre et l'icône passait en clair ; ici le trou
   laisse voir l'ambiance, plus claire, donc l'icône passe en sombre. */
.rail-icone-active{
  position:fixed;left:0;top:0;z-index:calc(var(--z-menu) + 1);
  display:grid;place-items:center;
  width:19px;height:19px;
  pointer-events:none;
  transform:translate3d(var(--ix,0), var(--iy,0), 0);
  /* LA COULEUR DU DOCK, ET NON LA TEINTE DU MODULE. L'icône est ce qui reste
     de la matière du rail dans le trou : elle en prend donc l'encre exacte,
     et le geste se lit comme une découpe et non comme un dessin pose sur le
     fond. Contraste au passage bien meilleur qu'une teinte assombrie —
     #0B0B10 sur une ambiance claire est le maximum atteignable.
     CONSÉQUENCE ASSUMÉE : la teinte du module ne paraît plus du tout sur
     l'entrée ouverte. Elle ne vit désormais qu'au survol (--t-survol). */
  color:var(--abysse);
  opacity:0;
  transition:opacity .18s var(--ease-sortie);
}
.rail-icone-active.pose{opacity:1}
.rail-icone-active svg{width:19px;height:19px}
/* La pilule teintée n'a plus lieu d'être sur l'entrée active : elle tombe
   intégralement dans le trou, donc elle n'y peindrait rien. La garder aurait
   été du code qui ne se voit pas — le pire à relire. */
/* L'ombre a QUITTÉ .rail : un masque rogne aussi ce que l'élément peint
   au-dehors, l'ombre portée disparaissait donc avec lui. `drop-shadow`
   sur le parent suit la silhouette RÉELLE — trou compris — là où un
   box-shadow n'aurait connu que le rectangle arrondi. */
.dock{filter:drop-shadow(0 18px 30px var(--hub-dock))
             drop-shadow(0 2px 4px var(--hub-dock-2))}

/* Le SVG ne sert plus qu'à réserver la place — le trou du masque l'efface
   de toute façon. On le GARDE pour que la mise en page ne dépende pas du
   masque, et surtout pour le repli : un navigateur sans mask-composite
   n'aurait aucun trou, et sans ce dessin le rail n'aurait plus de marque
   du tout. Il la montre alors en azur, comme avant. */
@supports (mask-composite: exclude) or (-webkit-mask-composite: xor){
  .rail-logo svg{visibility:hidden}
}

/* LA MARQUE, telle qu'elle est dans la charte : le cube azur, nu.
   Le carré à dégradé indigo → violet qui l'habillait ne venait d'aucune
   palette Rouzès, et il peignait le cube en blanc — donc la marque n'y
   était plus reconnaissable. Sans habillage, le cube retrouve son
   #008CF8 (le jeton --azur vaut exactement la couleur du fichier
   logo-rouzes.svg), et il cesse de concurrencer les pilules des modules
   juste en dessous : une seule forme pleine dans le rail, l'active. */
.rail-logo{
  display:grid;place-items:center;
  width:34px;height:34px;flex:none;
}
.rail-logo svg{width:21px;height:26px;fill:var(--marque);stroke:none}
.rail-logo svg use{fill:var(--marque)}

.rail-liste{
  position:relative;
  flex:1;min-height:0;
  display:flex;flex-direction:column;align-items:center;gap:var(--space-1);
  margin:var(--space-5) 0 var(--space-3);padding:var(--space-05) 0;
  overflow-y:auto;overflow-x:visible;
  scrollbar-width:none;
  /* dégradés de bord : indiquent qu'il reste des icônes à faire défiler */
  -webkit-mask-image:linear-gradient(transparent 0, var(--color-foreground) 12px, var(--color-foreground) calc(100% - 12px), transparent 100%);
          mask-image:linear-gradient(transparent 0, var(--color-foreground) 12px, var(--color-foreground) calc(100% - 12px), transparent 100%);
}
.rail-liste:not(.defile){-webkit-mask-image:none;mask-image:none}
.rail-liste::-webkit-scrollbar{display:none}
.rail-modules{display:flex;flex-direction:column;align-items:center;gap:var(--space-1)}
.rail-trait{width:18px;height:1px;flex:none;margin:var(--space-2) 0;background:var(--hub-rail-trait)}


.rail-item{
  position:relative;
  display:grid;place-items:center;
  width:40px;height:40px;flex:none;
  border:0;padding:0;border-radius:var(--radius-xl);
  color:var(--hub-rail-item);
  /* LONGHAND, et non le raccourci `background`. Le raccourci remet
     background-image à `none` : il effacerait le linear-gradient(C,C) qui
     porte le fond de la pilule active. Une seule règle laissée en raccourci
     et le premier survol de l'entrée courante efface la pilule, sans une
     erreur. Même exigence sur les trois autres fonds du rail, plus bas. */
  background-color:transparent;
  /* `background` a QUITTÉ cette liste : l'entrée n'a plus de fond du tout,
     c'est la pilule qui en porte un — et c'est exactement le fondu croisé
     que la refonte supprime. Il ne reste que de la couleur et une
     transformation ; aucune propriété de mise en page. */
  transition:color .18s var(--ease-sortie), transform .2s var(--ease-ressort);

  /* Teinte du module. Posée en JS sur les entrées de modules (renderRail),
     laissée à l'indigo du hub pour Tableau de bord, qui n'est pas un module
     et n'a pas de teinte au nuancier. */
  --t:var(--indigo);
  /* Repli des navigateurs sans syntaxe de couleur relative. Il conserve la
     teinte mais PAS la garantie de contraste : le mélange à pourcentage fixe
     PART de la clarté de chaque teinte au lieu de l'imposer, donc il l'étale
     au lieu de l'aligner. C'est un filet, pas l'égal du bloc @supports.
     Les pourcentages ne sont pas au jugé : ils minimisent l'écart moyen à la
     clarté visée sur les treize teintes (mesuré 0,031 pour l'actif, 0,047
     pour le survol, 0,026 pour la pilule).
     ATTENTION AU SENS : plus de teinte = moins de blanc = PLUS SOMBRE. Le vif
     doit donc en contenir MOINS que le survol, sans quoi l'entrée active
     ressort plus terne que celle qu'on survole — l'inverse de l'intention. */
  --t-vif:color-mix(in oklab, var(--t) 50%, #fff);      /* ≈ 0,77 */
  --t-survol:color-mix(in oklab, var(--t) 70%, #fff);   /* ≈ 0,66 */
}
/* On IMPOSE la clarté, on ne garde que le chroma et la nuance. Isolé en
   @supports parce qu'une valeur contenant var() n'échoue qu'au calcul :
   sans ce garde, un navigateur sans syntaxe relative n'aurait AUCUNE
   couleur d'icône, pas même celle du repli. */
@supports (color: oklch(from red 0.75 c h)){
  .rail-item{
    --t-vif:oklch(from var(--t) var(--L-actif) c h);
    --t-survol:oklch(from var(--t) var(--L-survol) c h);
  }
}

/* Repos → survol : la teinte s'annonce, sans encore s'installer. Le fond du
   survol reste --abysse-2, il descend simplement sur la pilule — sans quoi
   il repeindrait par-dessus elle (la pilule est en z-index:-1). */
.rail-item:hover{color:var(--t-survol);transform:translateY(-1px)}
.rail-item.actif{color:var(--t-vif)}
/* Ces deux règles restent APRÈS les précédentes : à spécificité égale
   (.rail-item.bientot vaut .rail-item:hover), c'est l'ordre du fichier qui
   tranche. Un module « bientôt » garde donc son gris, la teinte ne
   promettant que ce qui s'ouvre. */
.rail-item.bientot{color:var(--hub-rail-bientot)}
.rail-item.bientot:hover{color:var(--hub-rail-bientot-2)}

/* ─── La pilule : le marquage de l'entrée active ───────────────────────────
   UN CALQUE PAR ENTRÉE, jamais un indicateur unique partagé dans le rail.
   Ce n'est pas un détail d'implémentation, c'est ce qui évite trois pannes
   mesurées : un élément logé dans #listeNav est détruit à chaque
   renderRail() ; logé dans #railListe il est rogné par le masque de bord,
   et il compte dans le scrollHeight qui allume la classe .defile.
   Enfant de l'entrée, la pilule échappe aux trois — et elle
   suit gratuitement le translateY(-1px) du survol, qu'un calque partagé
   aurait laissé filer d'un pixel.

   `position:absolute` est OBLIGATOIRE, pas une commodité : .rail-item est
   une grille sans template ; un calque dans le flux y créerait une seconde
   ligne et décentrerait l'icône.
   `pointer-events:none` l'est tout autant : sans lui, la pilule devient la
   cible des événements et `closest('.rail-item')` cesse de la retrouver —
   l'étiquette se rangerait au clic.
   `z-index:-1` : .rail-item ne crée pas de contexte d'empilement, la pilule
   se peint donc sous l'icône mais au-dessus du fond de .rail.
   `border-radius:inherit` : le rayon suit celui de l'entrée sans qu'aucune
   media query n'ait à le redire. */
.rail-item .pilule{
  position:absolute;
  inset:0;
  z-index:-1;
  border-radius:inherit;
  background-color:transparent;
  pointer-events:none;
  transition:background-color .18s var(--ease-sortie);
}
.rail-item:hover .pilule{background-color:var(--abysse-2)}

/* LE FOND DE LA PILULE ACTIVE N'EST PAS UNE COULEUR, C'EST UNE IMAGE.
   Une image de fond s'interpole en valeur DISCRÈTE, et elle n'est pas dans
   la liste de transitions ci-dessus, réduite au longhand background-color.
   Il n'existe donc aucune valeur « à moitié » de la pilule : elle est
   pleine, ou elle n'est pas. C'est ce qui referme le défaut visé — deux
   calques à ~50 % à mi-course, dont aucun ne valait plus la couleur voulue.
   Ce n'est plus un réglage, c'est une impossibilité de représentation.

   L'anneau intérieur d'1 px n'est pas décoratif : le fond de la pilule ne
   rend que 1,35 à 1,61:1 contre le rail — une teinte sombre sur un rail
   presque noir. Seul l'anneau donne à l'état actif une frontière visible
   sans couleur : 4,28:1 au pire (Documents), 5,72:1 au mieux, au-dessus du
   3:1 exigé d'un élément d'interface. La halo extérieur reprend la géométrie
   de l'ombre indigo d'avant, teinte comprise. */
/* LA PILULE NE PEINT PLUS L'ÉTAT ACTIF. C'est la languette qui le porte : le
   rail est percé à cet endroit, et la pilule — 40 px, entièrement dans un trou
   de 52 — n'y peindrait rien. Son fond teinté ET son anneau intérieur ont donc
   été retirés plutôt que neutralisés par une règle concurrente : du style qui
   ne se voit pas est ce qu'il y a de pire à relire.
   L'anneau assurait la frontière lisible de l'entrée active (4,68:1 mesuré).
   Le trou la donne bien plus franchement — c'est un changement de matière, pas
   une couleur de plus. La pilule reste en service pour le SURVOL. */

/* LA CEINTURE. C'est le style APRÈS changement qui décide si une transition
   démarre : `transition:none` posé dans la MÊME tâche que la bascule de
   classe l'empêche donc de NAÎTRE, des deux côtés à la fois. L'icône suit
   son fond dans la même image — il n'existe plus une seule image où la
   couleur du trait et celle de la pilule appartiennent à deux états
   différents.
   `.sortant` existe UNIQUEMENT pour que ce transition:none SURVIVE au
   recalcul qui suit le retrait de `.actif`. Il est posé dans la tâche de la
   bascule et levé plus tard — jamais dans la même, sinon le fondu qu'il
   sert à empêcher démarre quand même. */
.rail-item.actif,   .rail-item.actif .pilule  {transition:none}
.rail-item.sortant, .rail-item.sortant .pilule{transition:none}
.rail-item .ico{width:19px;height:19px}


/* LA PASTILLE « EN LIGNE » A ÉTÉ RETIRÉE, markup compris (cf. renderRail).
   Elle marquait tout module DISPONIBLE — sept entrées sur quinze, en
   permanence — et non le module ouvert : sur un rail qui ne liste que des
   modules qui marchent, elle ne distinguait presque rien. Son anneau de 2 px
   en --abysse la détourait du rail ; sur la languette, qui est un trou, ce
   même anneau devenait une tache sombre au milieu du fond animé. */

/* ─── avatar : un ROND, qui accueille une photo ───────────────────────
   Le dégradé d'initiales reste le fond : c'est lui qu'on voit tant que
   personne n'a déposé de photo, et c'est vers lui qu'on retombe si le
   fichier ne charge pas. `overflow:hidden` + `object-fit:cover` évitent
   d'imposer une image carrée : une photo de portrait ordinaire est
   recadrée par le centre sans se déformer. */
.rail-avatar{
  position:relative;overflow:hidden;
  display:grid;place-items:center;
  width:38px;height:38px;flex:none;margin-top:auto;
  border:0;padding:0;border-radius:50%;
  background:linear-gradient(140deg,var(--color-primary),var(--hub-rail-avatar));
  color:var(--color-surface);font:800 var(--fs-xs) var(--font-titres);
  box-shadow:var(--shadow-hub-rail-avatar);
  transition:transform .2s var(--ease-ressort), box-shadow .2s;
}
.rail-avatar:hover{transform:translateY(-2px);box-shadow:var(--shadow-hub-rail-avatar)}

/* La photo couvre le rond et passe DEVANT les initiales : pas besoin de
   les masquer, et si l'image échoue le JS la remet en `hidden` — les
   initiales réapparaissent d'elles-mêmes, sans état à tenir. */
.avatar-photo{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;border-radius:inherit;
  display:block;
}

/* ─── zone de travail ─── */
.zone{
  flex:1;min-width:0;
  display:flex;flex-direction:column;
  background:var(--hub-zone);
  backdrop-filter:blur(6px);
  /* Arrondie côté dock seulement : à droite elle touche la fenêtre, et un
     coin arrondi contre un bord d'écran ne serait qu'une échancrure. */
  border-radius:var(--radius-dock) 0 0 var(--radius-dock);
  box-shadow:var(--shadow-dock);
  overflow:hidden;
}

/* Compacte (04/08/2026, demande de Cyril) : chaque pixel de bandeau est pris
   sur les modules affichés dessous */
.topbar{
  display:flex;align-items:center;gap:var(--space-3);flex:none;
  padding:var(--space-2) var(--space-4);
  border-bottom:1px solid var(--color-border);
}
.topbar h1{margin:0;font:700 var(--fs-body) var(--font-titres);letter-spacing:-.01em}
/* L'icône du module ouvert, dans SA teinte. Elle rend au bandeau du hub ce que
   l'en-tête du module portait avant de s'effacer : de quoi reconnaître l'écran
   d'un coup d'œil, sans lire.
   La teinte est posée BRUTE et non éclaircie : sur le blanc du bandeau, les
   treize dépassent le seuil de 3:1 exigé d'un objet graphique — c'est le texte,
   plus exigeant, qui imposait la clarté calculée dans le rail sombre.
   `margin-right:-4px` mange une partie du gap de 12 px de la barre : l'icône
   appartient au titre, elle ne doit pas flotter à mi-chemin du bouton de dock. */
.ico-vue{
  display:none;
  flex:none;
  margin-right:calc(-1 * var(--space-1));
  color:var(--t-vue, var(--indigo));
}
.ico-vue.pose{display:inline-flex}
.ico-vue .ico{width:18px;height:18px}
.topbar-droite{margin-left:auto;display:flex;align-items:center;gap:var(--space-25)}
.puce{
  display:inline-flex;align-items:center;
  font:500 var(--fs-2xs) var(--font-mono);
  padding:var(--space-1) var(--space-25);border-radius:var(--radius-full);
}
/* `.puce-date` retirée (20/08/2026) : la puce de date de l'en-tête a été
   sortie du balisage (accueil épuré, décision Cyril) — plus aucune cible. */

/* Choix d'agence : le filtre d'affichage des modules (clé rouzes-agence,
   relue par chaque module). Les comptes cloisonnés voient la puce. */
.choix-agence select{
  height:28px;padding:0 var(--space-2);
  border:1px solid var(--color-border);border-radius:var(--radius-md);
  background:var(--color-surface);color:inherit;font:500 var(--fs-2xs) var(--font-mono);cursor:pointer;
}
.choix-agence select:hover{border-color:var(--color-primary)}
.sr-only{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;
}

.btn-onglet{
  display:grid;place-items:center;
  width:32px;height:32px;min-height:32px;flex:none;
  border:1px solid var(--color-border);border-radius:var(--radius-full);
  background:var(--color-surface);color:var(--color-muted-fg);
  transition:color .16s, border-color .16s, transform .16s var(--ease-ressort);
}
.btn-onglet:hover{color:var(--color-primary-hover);border-color:var(--color-primary);transform:translateY(-1px)}

.contenu{flex:1;min-height:0;overflow-y:auto;padding:var(--space-6) var(--space-5) var(--space-5)}

.accueil h2{
  font:800 var(--hub-fs-accueil)/1.1 var(--font-titres);
  letter-spacing:-.02em;margin:0 0 var(--space-15);color:var(--marine);
}
#salut:focus{outline:none}
/* `#salut`, pas `.accueil h2` : jusqu'au 12/09/2026, la classe `.accueil`
   était partagée avec l'en-tête de l'écran Répartition (même h2, même
   sous-salut #repartResume), et une règle sur `.accueil` ou `.accueil h2`
   aurait aussi déplacé cet écran, hors périmètre — la Répartition est
   partie, mais la règle reste scopée, personne n'a revérifié depuis qu'un
   sélecteur plus large serait sans risque. L'écart tenait au sur-titre et
   au sous-salut retirés du tableau de bord (20/08/2026, accueil épuré) : le
   dernier des trois n'avait lui-même aucune marge basse, donc rien ne
   séparait la section suivante une fois `#salut` seul dans `.accueil`.
   Marge posée ici, scopée au seul `#salut`, pour retrouver un écart
   comparable aux autres sections (26px, voir `.taches`). */
#salut{margin-bottom:var(--space-5)}

/* Les tuiles de modules (.grille / .tuile) ont été retirées avec leur markup
   le 15/08/2026 : le rail dit la même liste, en permanence. */

/* ─── module embarqué ─── */
.cadres{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--color-surface)}
.cadres:focus{outline:none}
.cadre{flex:1;width:100%;border:0}

@keyframes fly-in{from{opacity:0;transform:translateX(-8px) scale(.96)}}

/* ═══════════ étiquette du rail ═══════════
   Un SEUL élément, réutilisé : il glisse d'une icône à l'autre au lieu de
   disparaître et réapparaître. Tout est animé en transform/opacity (donc
   composé par le GPU, sans recalcul de mise en page), et la position vient
   d'une variable --y posée par le JS : la transition sur --y n'existe pas,
   c'est translate3d qui la porte. */
/* ─── L'ÉTIQUETTE : la bulle d'information, revenue à sa forme d'origine.
   Elle avait été transformée en languette côté droit, dans la continuité du
   trou. Essayée à l'écran, écartée : deux languettes opposées se disputaient
   le regard, et la bulle dit mieux « ceci est un renseignement, pas un état ».
   Elle reste donc ce qu'elle était — flottante, arrondie, légèrement floutée,
   posée à 12 px du dock. C'est le trou vert qui porte l'état. */
.etiq-rail{
  position:fixed;left:var(--x,0);top:var(--y,0);z-index:var(--z-modal);
  display:flex;align-items:center;gap:var(--space-2);
  height:34px;padding:0 var(--space-4);
  border-radius:var(--radius-full);
  background:var(--hub-etiq-rail);
  /* pas de backdrop-filter : il fait de la pilule un calque GPU posé sur des
     demi-pixels physiques (écran à 125/150 %) → texte flou. Fond à 92 % : invisible. */
  box-shadow:
    var(--shadow-hub-etiq-rail);
  color:var(--color-surface);
  font:600 var(--fs-xs)/1 var(--font-body);
  letter-spacing:.005em;
  white-space:nowrap;
  pointer-events:none;
  transform-origin:left center;
  /* PAS de will-change:transform : avec le scale(.94) de l'état rangé, Chrome
     rastérise le calque à l'échelle réduite et ne le redessine plus → texte flou. */

  /* état rangé */
  opacity:0;
  transform:translateX(-7px) scale(.94);
  transition:
    opacity .14s var(--ease-sortie),
    transform .16s var(--ease-sortie);
}
.etiq-rail.pose{
  opacity:1;
  /* au repos AUCUN transform : pas de calque, texte rendu au pixel près */
  transform:none;
  transition:
    opacity .16s var(--ease-sortie),
    transform .34s cubic-bezier(.19,.91,.23,1),
    top .34s cubic-bezier(.19,.91,.23,1);
}
/* première apparition : léger dépassement, puis calage */
.etiq-rail.entre{transition:opacity .16s var(--ease-sortie), transform .26s var(--ease-ressort), top 0s}

/* le libellé se fond quand il change, sans que la pilule clignote.
   `>` : le badge « bientôt » vit DANS ce span, il ne doit pas devenir un
   bloc (il passerait à la ligne et serait rogné par la hauteur fixe). */
.etiq-rail > span{
  display:flex;align-items:center;gap:var(--space-2);
  animation:etiq-txt .22s var(--ease-sortie) both;
}
@keyframes etiq-txt{from{opacity:0;transform:translateX(-3px)}}

.etiq-rail .etiq-soon{
  display:inline-block;flex:none;
  font:600 var(--hub-fs-etiq-rail) var(--font-mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--hub-etiq-soon);
  padding:var(--space-1) var(--space-2);border-radius:var(--radius-full);
  background:var(--hub-etiq-soon-2);
  box-shadow:var(--shadow-hub-etiq-soon);
  animation:none;
}

/* ─── carte du compte ─── */
/* Deux rangées désormais : la fiche, puis le choix d'ambiance. La rangée
   du haut garde EXACTEMENT la disposition d'avant (.cp-haut), pour que
   rien n'ait à être repositionné. */
.carte-profil{
  position:fixed;z-index:var(--z-modal);
  display:flex;flex-direction:column;align-items:stretch;gap:var(--space-25);
  padding:var(--space-25) var(--space-3);
  /* La carte se rendait à 384 px de large (libellés `nowrap` + bouton
     `flex:none`) : à 360 px d'écran elle sortait de 34 px, et comme elle est
     en `position:fixed`, la page ne défile pas — « Se déconnecter » devenait
     inatteignable. Le plafond rend aussi vrai le garde-fou de `ouvrirProfil()`
     (hub.js), que le plancher `Math.max(10, …)` annulait. */
  max-width:calc(100vw - 20px);
  background:var(--color-surface);border-radius:var(--radius-xl);
  box-shadow:var(--shadow-hub-carte-profil);
  animation:fly-in .2s var(--ease-ressort) both;
}
.cp-haut{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}

.cp-ambiances{
  display:flex;flex-direction:column;gap:var(--space-15);
  padding-top:var(--space-25);border-top:1px solid var(--color-border);
}
.cp-legende{font:500 var(--fs-3xs) var(--font-mono);letter-spacing:.06em;
  text-transform:uppercase;color:var(--color-muted-fg)}
.cp-pastilles{display:flex;gap:var(--space-15);flex-wrap:wrap}
/* Une pastille = une ambiance. Le rond porte SA couleur en style en
   ligne (posé par hub.js depuis la liste AMBIANCES) : l'écrire ici
   aurait fait une seconde liste à tenir en accord avec la première. */
.cp-past{
  width:22px;height:22px;flex:none;padding:0;
  border:2px solid transparent;border-radius:50%;
  box-shadow:var(--shadow-hub-cp-past);
  transition:transform .15s var(--ease-ressort), border-color .15s;
}
.cp-past:hover{transform:translateY(-2px)}
.cp-past[aria-checked="true"]{border-color:var(--color-foreground)}
/* La carte du compte est blanche : même raison qu'au `:focus-visible` global. */
.cp-past:focus-visible{outline:2px solid var(--color-primary-hover);outline-offset:2px}
/* Devenu un BOUTON : c'est la cible naturelle pour changer sa photo —
   on clique là où on la voit. Les deux liens texte à côté restent la
   voie accessible au clavier et disent explicitement ce qui va arriver. */
.cp-avatar{
  position:relative;overflow:hidden;
  display:grid;place-items:center;
  width:40px;height:40px;flex:none;border-radius:50%;
  border:0;padding:0;
  font:800 var(--fs-sm) var(--font-titres);color:var(--color-surface);
  background:linear-gradient(140deg,var(--color-primary),var(--hub-rail-avatar));
}
/* Le voile n'apparaît qu'au survol ou au focus clavier : sans photo, le
   rond d'initiales doit rester lisible au repos. */
.cp-voile{
  position:absolute;inset:0;display:grid;place-items:center;
  background:var(--hub-cp-voile);color:var(--color-surface);
  opacity:0;transition:opacity .15s var(--ease-sortie);
}
.cp-voile .ico{width:16px;height:16px}
.cp-avatar:hover .cp-voile,
.cp-avatar:focus-visible .cp-voile{opacity:1}

/* Créateur de l'app : rond d'initiales DORÉ (06/10/2026). Le texte passe en
   brun foncé, le blanc tombait sous 2:1 sur l'or (#3A2A00 sur #D4A72C : 6,2:1). */
.rail-avatar.avatar-or,
.cp-avatar.avatar-or{
  background:var(--hub-avatar-or);color:var(--hub-avatar-or-fg);
  box-shadow:var(--shadow-hub-avatar-or);
}
.rail-avatar.avatar-or:hover{box-shadow:var(--shadow-hub-avatar-or)}

.cp-photo-actions{display:flex;gap:var(--space-25);margin-top:var(--space-1)}
.cp-photo-actions .lien-discret{font:500 var(--fs-2xs) var(--font-body);padding:0;border:0;background:none}
.cp-retirer{color:var(--color-destructive)}
/* pendant l'envoi : on interdit le double clic et on le dit */
.cp-photo-actions.envoi{opacity:.55;pointer-events:none}
.cp-txt{display:flex;flex-direction:column;min-width:0;padding-right:var(--space-1)}
/* `nowrap` conservé (une adresse coupée en deux se lit mal) mais bornée :
   sans `overflow:hidden`, le texte débordait de `.cp-txt` malgré son
   `min-width:0` et faisait grandir toute la carte. */
.cp-nom{font:700 var(--fs-sm) var(--font-titres);color:var(--color-foreground);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cp-role{font:500 var(--fs-3xs) var(--font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--color-primary-hover);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cp-mail{font:500 var(--fs-2xs) var(--font-mono);color:var(--color-muted-fg);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cp-sortir{
  display:inline-flex;align-items:center;gap:var(--space-2);flex:none;
  padding:var(--space-2) var(--space-3);border:0;border-radius:var(--radius-full);
  background:var(--abysse);color:var(--color-surface);
  font:600 var(--fs-xs) var(--font-body);
  transition:background .16s, transform .16s var(--ease-ressort);
}
.cp-sortir .ico{width:15px;height:15px}
.cp-sortir:hover{background:var(--hub-cp-sortir);transform:translateY(-1px)}

/* Les états de glisser-déposer (.fantome, .ins-*) sont partis avec le
   réordonnancement des modules, retiré le 15/08/2026 : plus rien ne se
   déplace à la souris dans le hub. */

/* Le toast maison (une pilule sombre au centre-bas) est parti avec les
   toasts « satin » du socle, le 19/08/2026 : plus aucun JS ne visait
   `#toast`, il ne restait que sa balise et ses trois règles. Les toasts
   vivent maintenant dans css/socle/toasts.css. */

.evitement{
  position:fixed;left:16px;top:-52px;z-index:var(--z-sommet);
  background:var(--abysse);color:var(--color-surface);
  font:600 var(--fs-sm) var(--font-body);
  padding:var(--space-25) var(--space-4);border-radius:var(--radius-lg);
  transition:top .15s var(--ease-sortie);
}
.evitement:focus{top:16px}

/* ═══════════════ RESPONSIVE ═══════════════ */


@media (max-width:920px){
  /* — connexion : plus de place pour le volet coulissant. Le formulaire
       prend toute la largeur. — */
  .login{padding:var(--space-4) var(--space-4) var(--space-6)}
  .conteneur{min-height:0}
  .overlay-boite{display:none}
  .form-boite{
    position:relative;left:auto;width:100%;height:auto;
    transform:none !important;transition:opacity .3s var(--ease-sortie), visibility 0s;
  }
  .form-boite form{
    justify-content:flex-start;padding:var(--space-6) var(--space-5) var(--space-5);
  }
  .form-pied{position:static;padding:0;margin-top:var(--space-3)}
  .f-connexion{opacity:1;visibility:visible}
  .logo-form{width:118px;height:29px;margin-bottom:var(--space-4)}

  /* — LE DOCK NE SE TRANSFORME PLUS (18/08/2026, demande de Cyril).
       Il n'existe qu'une seule géométrie de dock, celle du bureau : rail
       d'icônes de 52 px, flottant, arrondi, posé sur l'ambiance. Le tiroir
       sombre plein écran qui s'ouvrait au burger a été retiré — il donnait
       deux identités au portail selon la taille de l'écran, et c'est la
       version large qui débordait sur les Surface Pro (voir plus bas).
       Sur mobile, seuls les ÉCARTS se resserrent. — */
  .scene{padding:var(--space-25) 0 var(--space-25) var(--space-25);gap:var(--space-25)}
  .topbar{padding:var(--space-2) var(--space-3)}
  .contenu{padding:var(--space-5) var(--space-4) var(--space-4)}
  /* La zone GARDE le rayon du dock (--radius-dock, 26 px). Elle descendait
     ici à 20 : dock ouvert par-dessus, les deux arrondis se lisaient côte à
     côte et ne se répondaient pas (02/09/2026, capture de Cyril : « le même
     arrondi que le dock »). */
}

/* ─── Le dock GLISSE hors écran au téléphone (29/08/2026, demande Cyril).
   Ce n'est PAS une seconde géométrie — la règle du 18/08 tient : rail de
   52 px, arrondi, unique. Le MÊME objet est TRANSLATÉ à gauche quand on ne
   s'en sert pas, et la zone de travail récupère toute la largeur. La
   poignée le rappelle ; choisir un module ou toucher le voile le replie
   (hub.js). Le voile n'est pas un décor : l'iframe du module avale les
   taps, qui ne remontent jamais au portail — sans lui, « toucher à côté
   pour fermer » ne marcherait nulle part sur un module embarqué. */
.dock-poignee, .dock-voile { display: none; }
/* DEUX conditions, pas une (02/09/2026, constat de Cyril : « des bugs dans le
   dock, les icônes restent apparentes »). Le repli ne se déclenchait que sur
   la LARGEUR — or ce qui rend ce rail impraticable, c'est le manque de
   HAUTEUR, et un téléphone COUCHÉ a justement beaucoup de largeur et très peu
   de hauteur. Mesuré à 812 × 375 (iPhone en paysage) : `transform` restait à
   `none`, la poignée à `display:none`, et la liste offrait 235 px de haut pour
   658 px de contenu — 10 modules sur 14 hors du cadre, un dock planté sur
   l'écran que RIEN ne permettait de replier.
   La virgule est un OU : même bloc, même géométrie unique (règle du 18/08),
   simplement deux façons de manquer de place. 520 px couvre tous les
   téléphones couchés (375 à 430 de haut) sans toucher aux tablettes ni aux
   portables, dont la hauteur utile part de 600. */
@media (max-width:640px), (max-height:520px){
  .dock{
    position:fixed;left:10px;top:10px;bottom:10px;
    transform:translateX(calc(-100% - 14px));
    transition:transform .28s ease;
  }
  .scene.dock-ouvert .dock{ transform:none; }
  .dock-poignee{
    display:grid;place-items:center;
    position:fixed;left:0;top:50%;
    transform:translateY(-50%);z-index:calc(var(--z-menu) + 1);
    width:26px;height:64px;border:0;cursor:pointer;
    border-radius:0 var(--radius-xl) var(--radius-xl) 0;
    background:var(--abysse);color:var(--color-surface);
    box-shadow:var(--shadow-dock);
    font-size: var(--fs-lg);font-weight:700;line-height:1;
  }
  .scene.dock-ouvert .dock-poignee{ display:none; }
  .dock-voile{
    display:block;position:fixed;inset:0;z-index:calc(var(--z-menu) - 1);
    background:var(--hub-dock-voile);
    opacity:0;pointer-events:none;transition:opacity .28s ease;
  }
  .scene.dock-ouvert .dock-voile{ opacity:1;pointer-events:auto; }

  /* L'ICÔNE REDOUBLÉE NE SURVIT PAS AU REPLI — EN CSS (02/09/2026).
     Elle vit sur le BODY et non dans le dock (le `filter` de celui-ci ferait
     de lui le bloc conteneur des `position:fixed`), donc le transform qui
     glisse le rail ne l'emporte pas : seul du JS peut la déplacer, et c'est
     ce JS qui a laissé passer le défaut DEUX FOIS — d'abord parce que rien ne
     le rappelait au repli, ensuite parce que le rappel dépend d'un
     `transitionend` que `prefers-reduced-motion` supprime. Une règle CSS,
     elle, ne peut pas « oublier » de s'appliquer : elle n'a ni appelant ni
     minuterie. Le JS garde son rôle (placer l'icône dans son trou) ; le CSS
     garantit le seul invariant qui compte à l'œil — dock replié, aucune icône
     à l'écran.
     `body:has(…)` parce que l'icône n'est PAS un descendant de .scene : on ne
     peut pas l'atteindre par un sélecteur descendant depuis le dock. La
     spécificité suffit à battre `.rail-icone-active.pose` — `:has()` prend
     celle de son argument, ce qui joue ici en notre faveur, et c'est mesuré
     (opacité 0 dock replié, 1 dock ouvert) et non supposé. */
  body:has(.scene:not(.dock-ouvert)) .rail-icone-active{ opacity:0; }
}

/* ─── PLUS AUCUNE RÈGLE SUR LA NATURE DU POINTEUR ────────────────────────
   Il y avait ici un `@media (pointer:coarse)` qui élargissait le rail à 238 px
   et y écrivait les noms. C'est LUI qui rendait une Surface Pro « mobile » :
   l'écran est tactile, donc la requête répond vrai même quand la machine est
   posée sur son clavier et pilotée au trackpad. `pointer` et `hover` décrivent
   le dispositif déclaré comme PRINCIPAL, jamais celui qui fait le geste — ils
   ne peuvent donc pas servir à choisir une géométrie. La place disponible, si.
   Reste l'avatar, qui gardait 44 px au doigt contre 38 ailleurs : supprimé
   aussi, par cohérence — un dock unique ne change pas de mesures selon la
   machine, et 38 px passent largement le seuil de cible de WCAG 2.2. */

/* L'ÉTIQUETTE N'A AUCUNE MÉDIA-REQUÊTE, ET C'EST VOULU. La tentation était de
   la masquer sous `hover:none` ; mais ces deux requêtes décrivent le dispositif
   de pointage DÉCLARÉ COMME PRINCIPAL, pas celui qui fait le geste — sur une
   Surface Pro pilotée au trackpad, elles répondent quand même « tactile », et
   l'étiquette aurait disparu pour de bon. C'est le JS qui tranche, au bon
   grain : `brancherEtiquette()` ignore tout pointerover dont le `pointerType`
   n'est pas "mouse". Au doigt elle ne s'ouvre donc jamais, sans qu'une seule
   règle de style ait à le supposer. */
/* écran court : le rail doit rester utilisable (11 modules) */
@media (max-height:620px){
  .rail-liste{margin:var(--space-4) 0 var(--space-2)}
  .rail-logo{width:30px;height:30px}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none !important;transition:none !important}
}

/* Le vrai bouton « Ajouter une tâche » (19/08/2026) : le geste principal du
   tableau de bord méritait mieux qu'un lien discret. Il a suivi le bouton
   dans le bloc du tableau de bord le 16/09/2026, avec le retrait du mode
   jeu : `margin-left:auto` le pousse toujours à droite de sa tête. */
.btn-ajout-tache{
  margin-left:auto;border:0;cursor:pointer;
  display:inline-flex;align-items:center;gap:var(--space-15);
  padding:var(--space-2) var(--space-4);border-radius:var(--radius-full);
  background:var(--action);color:var(--color-surface);font:600 var(--fs-sm) var(--font-body);
  box-shadow:var(--shadow-hub-btn-ajout-tache);
  transition:transform .16s var(--ease-ressort), box-shadow .16s, background .16s;
}
.btn-ajout-tache:hover{background:var(--action-fonce);transform:translateY(-1px);
  box-shadow:var(--shadow-hub-btn-ajout-tache)}
.btn-ajout-tache span{font-size: var(--fs-body);line-height:1}

/* Première connexion (19/08/2026) : les trois gestes du rôle. */
/* Sélecteur DOUBLE (0,2,0) et non `.dlg-bienvenue` seul : le dialogue porte
   `class="dlg-tache dlg-bienvenue"`, et `.dlg-tache{max-width:440px}` est
   écrit plus bas dans ce fichier — à spécificité égale, c'est l'ordre qui
   tranchait, et les 520 px voulus n'étaient jamais appliqués. */
.dlg-tache.dlg-bienvenue{max-width:520px}
.bienvenue{padding:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3)}
.bienvenue h3{margin:0;font:800 var(--fs-xl) var(--font-titres);color:var(--marine)}
.bienvenue-sous{margin:0;font-size: var(--fs-body);color:var(--color-muted-fg);line-height:1.5}
.bienvenue ol{margin:0;padding-left:0;list-style:none;counter-reset:geste;
  display:flex;flex-direction:column;gap:var(--space-25)}
.bienvenue ol li{counter-increment:geste;display:flex;gap:var(--space-3);
  font-size: var(--fs-body);line-height:1.55;color:var(--color-foreground)}
.bienvenue ol li::before{content:counter(geste);flex:none;
  width:26px;height:26px;border-radius:50%;display:grid;place-items:center;
  background:var(--color-primary-hover);color:var(--color-surface);font:700 var(--fs-sm) var(--font-titres)}
.bienvenue .dlg-actions{display:flex;justify-content:flex-end}
/* La tête du tableau de bord : elle ne porte plus que le bouton d'ajout
   depuis le retrait du mode jeu (16/09/2026) — le compteur, la bascule de
   portée, les pilules de domaine et le tri vivaient dans la liste par
   domaines, partie avec lui. */
.tv-tete{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-25);
  margin-bottom:var(--space-25)}

.dlg-tache{border:0;border-radius:var(--radius-xl);padding:0;box-shadow:var(--shadow-lg);max-width:440px;width:calc(100vw - 32px)}
.dlg-tache::backdrop{background:var(--overlay-fort)}
.dlg-tache form{padding:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3)}
.dlg-tache h3{margin:0;font:600 var(--fs-md) var(--font-titres)}
.dlg-tache label{display:flex;flex-direction:column;gap:var(--space-1);font-size: var(--fs-xs);color:var(--color-muted-fg)}
.dlg-tache input[type=text],.dlg-tache input[type=date],.dlg-tache select{
  height:36px;padding:0 var(--space-25);border:1px solid var(--color-border);border-radius:var(--radius-md);
  font:400 var(--fs-sm) var(--font-body);color:var(--color-foreground);background:var(--color-surface);
}
/* ─── Boutons génériques du socle (21/08/2026) ─────────────────────────
   Les trois modales du hub (nouvelle tâche, « Donner à… », écran de
   bienvenue) posent `class="btn btn-plat"` et `class="btn btn-primaire"`.
   Ces classes vivent dans composants.css, que le hub NE CHARGE PAS : les
   boutons tombaient donc sur le rendu du navigateur — gris, coins carrés,
   bordure de 2 px — et « Annuler » était le SOSIE de « Créer ». Le geste
   principal ne se distinguait pas de l'abandon.

   Écrits ici dans l'idiome des modales du hub (36 px de haut, 13 px de
   texte, cf. `.dlg-tache input`) et non recopiés de composants.css, qui
   vise des écrans de module plus denses. Le RAYON, lui, est celui de tout
   l'ERP depuis la décision Cyril du 02/09/2026 : la pilule (999px), comme
   `.btn` de socle-erp/css/composants.css. Seuls les CHAMPS gardent 8 px. */
.btn[disabled]{opacity:.6;cursor:not-allowed}
.btn-plat:hover:not([disabled]){border-color:var(--color-primary)}
/* SURCHARGE VOULUE du socle : le bouton primaire du hub garde la couleur d action
   FIXE (--action), là où --color-primary suit l ambiance choisie. */
.btn-primaire{background:var(--action);color:var(--color-on-primary);border:1px solid var(--action)}
.btn-primaire:hover:not([disabled]){background:var(--action-fonce);border-color:var(--action-fonce)}

/* La puce d'agence de la barre haute. `.puce` ne donne que la géométrie —
   volontairement, les clartés du dock ne se transposent pas — mais la
   pilule elle-même (fond, bordure) vient de composants.css, absent ici :
   l'agence s'affichait en texte nu, avec du rembourrage autour de rien. */

/* Avertissement « Liste partielle » du tableau de bord : produit par
   tableau-taches.js, jamais habillé. Un compte approché servi comme exact
   est le mensonge que ce texte est là pour empêcher — il doit se voir.

   S'appelait `.taches-panne` et vivait dans le pied de la liste par
   domaines ; renommé le 16/09/2026 avec le retrait du mode jeu, qui
   emportait ce pied. Il se pose maintenant en tête des deux listes, seul —
   PAS combiné à `.tv-mot`, dont le panneau calme dirait le contraire. */
.tv-partiel{
  display:block;margin:0 0 var(--space-3);
  font:500 var(--fs-xs) var(--font-body);color:var(--color-warning);
}

.dlg-case{flex-direction:row!important;align-items:center;gap:var(--space-2)}
.dlg-actions{display:flex;gap:var(--space-2);justify-content:flex-end;margin-top:var(--space-1)}

/* ── Au doigt ─────────────────────────────────────────────────────────────
   Les cibles de 44 px des cartes de tâches (mesurées le 27/08/2026 à 375 px)
   ont suivi la carte dans le socle (css/socle/taches.css). LA HAUTEUR ENTRE
   DANS LA CONDITION (02/09/2026), pour la même raison que le repli du dock
   plus haut : un téléphone COUCHÉ fait 812 px de large et ne déclenchait
   aucune de ces règles — c'est toujours un pouce qui appuie. */
@media (max-width: 640px), (max-height: 520px) {
  /* Le selecteur d'agence de la barre : 28 px de haut mesures, et il decide
     de ce que TOUS les modules affichent. Se tromper d'agence au doigt fait
     travailler sur le mauvais perimetre sans que rien ne l'annonce. */
  .choix-agence select { min-height: 44px; }

  /* Les trois DERNIERS contrôles sous le seuil, mesurés le 02/09/2026 à
     375 px sur le tableau de bord connecté — la passe du 27/08 les avait
     manqués comme l'audit d'origine avait manqué les siens : elle regardait
     les cartes de tâches, pas l'en-tête ni le pied de la liste.
     Relevé avant : « Ajouter une tâche » 31 px et le lien du pied 31 px.
     Les bascules de tri étaient du lot ; elles ont disparu avec le tri
     par date (12/09/2026), leur règle avec elles.

     Seule la HAUTEUR bouge, comme pour les cartes. */
  .btn-ajout-tache { min-height: 44px; }

  /* Audit 01/10/2026 (CSA-12) : gestes du tableau de bord et de la carte de
     compte, sous 44 px au doigt. */
  .tv-geste, .cp-sortir { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  .lien-discret, .cp-photo-actions .lien-discret { min-height: 44px; display: flex; width: fit-content; align-items: center; }
  .btn-onglet, .oeil { width: 44px; height: 44px; min-height: 44px; }
  .cp-past { width: 36px; height: 36px; }
}

/* ─── Changer son mot de passe (31/08/2026) ─────────────────────────────
   Même géométrie que .dlg-qui, nom propre : réutiliser .dlg-qui aurait
   collé « donner cette tâche à… » sur un formulaire de mot de passe, et
   la prochaine retouche de l'un aurait déplacé l'autre. */
.dlg-mdp{padding:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);min-width:min(92vw,340px)}
.dlg-mdp h3{margin:0;font:600 var(--fs-md) var(--font-titres)}
.dlg-mdp-regle{
  margin:0;font-size: var(--fs-sm);color:var(--color-muted-fg);
  border-left:2px solid var(--color-border);padding-left:var(--space-25);
}
.dlg-mdp .erreur:empty{display:none}

/* ── Les bulles des tuiles (23/09/2026) ───────────────────────────────────
   Le compte des nouveautés de CHAQUE tuile, coin haut droit ; posées et
   retirées par tableau-taches.js (peindreBulles). Elles remplacent la cloche,
   entrée à part qui décalait le rail en apparaissant. En position absolue :
   une bulle ne déplace rien. L'anneau couleur du rail la détache de l'icône. */
.rail-badge{
  position:absolute;top:1px;right:1px;
  min-width:16px;height:16px;padding:0 var(--space-05);
  border-radius:var(--radius-full);
  background:var(--color-destructive);color:var(--color-on-primary);
  box-shadow:0 0 0 2px var(--color-rail);
  font-size:var(--fs-3xs);font-weight:700;line-height:16px;text-align:center;
  pointer-events:none;
}
/* Le mot « nouveau » sur la carte (.tache-neuve) : avec la carte, dans le
   socle (css/socle/taches.css). */

/* ═══════════════════════════════════════════════════════════════════════
   LE TABLEAU DE BORD (13/09/2026 ; seul visage depuis le 16/09/2026).

   Tout ce qui portait le JEU a été retiré d'ici le 16/09/2026 avec le mode
   lui-même : le bandeau HUD et sa réglette, la route et son épine, les
   cartes de quête, le collectif, la liste par domaines et ses cartes, le
   focus, l'étagère des trophées, le signe du gain, et la centaine de
   règles `[data-theme="borne"]` qui repeignaient tout cela en borne
   d'arcade. Environ 1 700 lignes. Elles sont dans git au commit c2a18de.

   Le squelette de chargement et l'état vide retombent désormais sur ceux
   du socle (design-system.css) : le hub ne les rhabillait en carton que
   sous `.taches`, qui n'existe plus.
   ═══════════════════════════════════════════════════════════════════════ */

/* LES QUATRE COMPTEURS. Deux portent une couleur — le retard et le jour
   même — parce que ce sont les deux seuls sur lesquels on peut encore agir
   aujourd'hui. Les deux autres informent sans presser : une couleur qui
   crie partout ne crie plus nulle part. */
.tv-compteurs{display:grid;gap:var(--space-2);
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  margin:0 0 var(--space-4)}
.tv-compteur{display:flex;flex-direction:column;gap:var(--space-05);
  padding:var(--space-2) var(--space-25) var(--space-25);
  border:1px solid transparent;border-radius:var(--radius-lg)}
.tv-nom{font-size:var(--fs-xs);font-weight:600}
.tv-n{font:700 var(--hub-fs-tv-n)/1.1 var(--font-titres)}
.tv-retard{background:var(--hub-tv-retard);border-color:var(--hub-tv-retard-2);color:var(--hub-tv-retard-3)}
.tv-jour{background:var(--hub-tv-jour);border-color:var(--hub-tv-jour-2);color:var(--hub-tv-jour-3)}
.tv-sobre{color:var(--color-muted-fg)}
.tv-sobre .tv-n{color:var(--color-foreground)}

/* LE MOT QUAND IL N'Y A PAS DE LISTE — journée à zéro tâche, ou panne de
   chargement. Deux dessins, jamais un seul : le mode Borne a appris à ses
   dépens qu'« une panne d'API se présentant comme un ERP à jour est le
   pire des mensonges d'un écran qui n'a rien pu lire » (voir
   `.taches-echec`). Le calme est réservé à ce qui est vraiment calme. */
.tv-mot{margin:0;padding:var(--space-4);background:var(--hub-taches-vide);
  border:1px dashed var(--color-border);border-radius:var(--radius-lg);
  font-size:var(--fs-sm);color:var(--color-muted-fg);line-height:1.6}
/* L'alerte emprunte la teinte du compteur « En retard » juste au-dessus —
   même écran, même langue. Elle n'emprunte PAS `--hud-vermillon-t`, qui
   est le vermillon de TEXTE (ligne 1741, « celui-ci porte les mots ») et
   ne doit jamais remplir une surface. */
.tv-mot.tv-echec{background:var(--hub-tv-retard);border-style:solid;border-color:var(--hub-tv-retard-2);
  color:var(--hub-tv-retard-3)}

/* LES DEUX LISTES. Le sous-titre n'est pas une décoration : « actions de
   votre côté » est ce qui distingue les deux groupes, et sans lui la
   seconde liste ressemble à une liste de choses à faire qui ne dépendent
   pas de soi. */
.tv-groupe{margin:0 0 var(--space-4)}
.tv-groupe > h3{display:flex;align-items:baseline;gap:var(--space-15);
  margin:0 0 var(--space-15);font:700 var(--fs-body) var(--font-titres)}
.tv-quoi{font:400 var(--fs-sm) var(--font-body);color:var(--color-muted-fg)}
.tv-liste{border:1px solid var(--color-border);border-radius:var(--radius-lg);
  overflow:hidden;background:var(--rz-blanc)}
/* LES SIX JETONS DE LA CARTE DE TÂCHE, rebranchés sur la palette du hub.
   `.tache-actions`, `.tache-action` et `.tache-epingle` viennent du socle
   (design-system.css, chargé avant cette feuille) et lisent `--t-encre`,
   `--t-azur` et `--t-texte`. Sans ces trois lignes les boutons revenus sur
   les lignes le 16/09/2026 s'afficheraient dans les valeurs de repli du
   socle : gris sur gris, sans la teinte d'action au survol — visible à
   l'œil seulement, aucune erreur nulle part. La règle portait sur `.tache`
   avant le retrait du mode jeu ; elle porte sur la ligne, maintenant que
   c'est la ligne qui accueille les gestes. */
.tv-ligne{
  --t-sourdine:var(--color-muted-fg);--t-encre:var(--color-foreground);--t-azur:var(--color-primary);
  --t-ligne:var(--color-border);--t-texte:var(--font-body);--t-mono:var(--font-mono);
}
.tv-ligne{display:flex;align-items:center;gap:var(--space-25);
  padding:var(--space-2) var(--space-25);border-top:1px solid var(--color-border)}
.tv-ligne:first-child{border-top:0}
.tv-ligne:hover{background:var(--hub-tv-ligne)}
/* AU TÉLÉPHONE, les gestes passent sous le titre (audit du 01/10/2026,
   TAC-02). Sous 640 px le socle donne à `.tache-actions` toute la largeur
   (`flex:1 0 100%`, pensé pour la carte) : dans une ligne sans retour, le
   titre d'une tâche manuelle tombait à 0 px et l'étoile sortait de l'écran,
   coupée par `.tv-liste{overflow:hidden}`. */
@media (max-width:640px){
  .tv-ligne{flex-wrap:wrap}
}

/* L'ÂGE EN PASTILLE — le seul endroit de cet écran où une couleur veut
   dire quelque chose. Son texte et sa teinte sortent du même calcul
   (`pastilleEcheance`) : les séparer a déjà produit ici une pastille rouge
   portant un chiffre calme. */
.tv-age{flex:none;min-width:38px;text-align:center;
  padding:var(--space-1) var(--space-1);border-radius:var(--radius-sm);
  font:700 var(--fs-2xs) var(--font-mono)}
.tv-a-retard{background:var(--hub-tv-a-retard);color:var(--hub-tv-retard-3)}
.tv-a-jour{background:var(--hub-tv-jour);color:var(--hub-tv-jour-3)}
.tv-a-calme{background:var(--hub-tv-a-calme);color:var(--color-muted-fg)}

.tv-corps{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-05)}
.tv-t{font:600 var(--fs-control)/1.35 var(--font-body)}
.tv-s{font-size:var(--fs-xs);color:var(--color-muted-fg);line-height:1.35}
.tv-geste{flex:none;padding:var(--space-2) var(--space-25);text-decoration:none;
  border:1px solid var(--color-border);border-radius:var(--radius-md);
  background:var(--rz-blanc);font:600 var(--fs-sm) var(--font-body);
  color:var(--color-foreground)}
.tv-geste:hover{border-color:var(--color-primary);color:var(--color-primary-hover)}
.tv-geste:focus-visible{outline:2px solid var(--color-primary-hover);outline-offset:2px}

/* La pastille d'une tâche SANS échéance : elle garde sa place pour que les
   titres restent alignés, mais ne montre rien. Une gélule grise et vide se
   lit comme une donnée manquante ; un blanc se lit comme « pas de date ». */
.tv-a-vide{background:transparent}
