/* Polices du tableau de bord Pilotage Chantiers, hébergées ici (licence
   OFL, voir fonts/LICENCES-OFL.txt). Deux sous-ensembles chacune : latin,
   et latin étendu pour « KOVAČEVIĆ » ou « Łukasz ». `?v=1` : le fichier ne
   change jamais sous ce nom, il est servi immuable. `swap` : le texte
   s'affiche tout de suite dans la police du système. */
@font-face{font-family:"Manrope";font-style:normal;font-weight:600 800;font-display:swap;
  src:url(/fonts/manrope-latin.woff2?v=1) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Manrope";font-style:normal;font-weight:600 800;font-display:swap;
  src:url(/fonts/manrope-latin-ext.woff2?v=1) format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C4,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Nunito Sans";font-style:normal;font-weight:400 700;font-display:swap;
  src:url(/fonts/nunito-sans-latin.woff2?v=1) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Nunito Sans";font-style:normal;font-weight:400 700;font-display:swap;
  src:url(/fonts/nunito-sans-latin-ext.woff2?v=1) format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C4,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:500;font-display:swap;
  src:url(/fonts/plex-mono-latin.woff2?v=1) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:500;font-display:swap;
  src:url(/fonts/plex-mono-latin-ext.woff2?v=1) format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C4,U+2113,U+2C60-2C7F,U+A720-A7FF}

:root{
  --paper:#F3F5F6; --surface:#FFFFFF; --surface-2:#E9EEF1; --surface-3:#DFE5E9;
  /* --muted et --line-strong ont été assombris : le premier tombait à
     4,20:1 sur le fond de page (il faut 4,5:1 pour du texte), le second
     à 1,73:1 alors qu'il dessine le contour des boutons, des champs et
     des créneaux — il en faut 3:1. Un bouton dont on ne voit pas le
     bord, sur l'écran d'un téléphone tenu dans une cage d'escalier,
     n'est pas un bouton. */
  --ink:#0F1417; --ink-2:#3A444B; --muted:#56616A;
  --line:#D6DDE1; --line-strong:#737D84;
  /* La charte du tableau de bord Pilotage Chantiers : le cyan de marque.
     `--accent-strong` porte un texte blanc (bouton principal). */
  --accent:#006CA0; --accent-ink:#FFFFFF; --accent-soft:#EAF4FA; --accent-line:#8CC3E0;
  --accent-strong:#0074AB;
  --ok:#1E6E4A; --ok-soft:#DCEEE3;
  --warn:#8A6410; --warn-soft:#F5EBD3; --warn-ink:#FFFFFF;
  /* `--alert-ink` existe pour la meme raison que `--accent-ink` : le
     toast d'erreur portait `color:#fff` en dur. En theme sombre
     `--alert` s'eclaircit pour rester lisible sur fond noir, et le
     blanc dessus tombait a 2,62:1 — sur le canal d'erreur unique du
     produit, affiche cinq secondes en `aria-live=assertive`. */
  --alert:#A3402E; --alert-soft:#F6E1DC; --alert-ink:#FFFFFF;
  /* Echelle typographique — SIX tailles, et rien entre elles.

     Il y en avait vingt-trois, dont dix-sept dans un intervalle de
     6,7 px : des pas de 0,32 rem, sous le seuil de perception. Huit
     n'apparaissaient que sur un seul ecran. Ce n'etait pas une
     echelle, c'etait une sedimentation — et c'est elle qui empechait
     les ecrans de bureau d'avoir l'autorite tranquille de l'affiche.

     Les rapports sont nets : 12 / 14 / 16 / 20 / 24 / 32. Chaque pas se
     voit. L'affiche du hall garde son propre systeme, en points : c'est
     une piece imprimee, pas un ecran. */
  --t-xs:.75rem; --t-sm:.875rem; --t-md:1rem;
  --t-lg:1.25rem; --t-xl:1.5rem; --t-2xl:2rem;

  /* Pas de jetons d'espacement ici.

     Il y en avait dix, `--e-1` a `--e-16`, declares en 1.12 et
     utilises ZERO fois : `grep -c 'var(--e-'` rendait 0. Le README
     annoncait pourtant « 16 espacements hors trame sur 24 ramenes a
     1 ». J'avais mesure l'avant, ecrit les jetons, et rapporte
     l'apres sans le mesurer. Sur le rendu reel : 24 valeurs hors
     trame sur 52.

     Et elles ne sont pas dans cette feuille : elles vivent dans les
     attributs `style=` ecrits par `app.js` — `margin-top:6px` a
     soixante-sept endroits. Un jeu de jetons ne les atteint pas. Les
     declarer sans les appliquer donnait le decor d'un systeme sans le
     systeme ; ils sont retires plutot que laisses en promesse. */

  /* `--r-sm` était employé six fois et DÉCLARÉ NULLE PART : les
     vignettes de photo, le cadre de signature, les signatures de la
     fiche et la visionneuse calculaient `border-radius: 0px`. Perdu en
     silence, sans erreur ni avertissement — une variable CSS absente ne
     se signale pas, elle rend la déclaration invalide. */
  --r-sm:4px; --r:6px; --r-lg:12px;
  --shadow:0 1px 2px rgba(20,26,31,.06), 0 6px 20px -12px rgba(20,26,31,.25);
  /* Les polices du tableau de bord, servies par l'application elle-même
     (fonts/) : aucun appel à Google depuis le téléphone d'un occupant. */
  --f-display:"Manrope","Segoe UI",system-ui,sans-serif;
  --f-body:"Nunito Sans","Segoe UI",system-ui,sans-serif;
  --f-mono:"IBM Plex Mono",ui-monospace,"Cascadia Mono",Consolas,monospace;
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
  --paper:#08090A; --surface:#131517; --surface-2:#1B1E21; --surface-3:#24282C;
  --ink:#F2F4F5; --ink-2:#B4BBC1; --muted:#8C949B;
  --line:#24282C; --line-strong:#60686F;
  --accent:#1A9AD6; --accent-ink:#04141C; --accent-soft:#0F2A38; --accent-line:#1F5C7C;
  --accent-strong:#0074AB;
  --ok:#3CC43C; --ok-soft:#13261A;
  --warn:#F5BD3D; --warn-soft:#2C2410; --warn-ink:#2A1F06;
  --alert:#EF7B7B; --alert-soft:#321718; --alert-ink:#2E0F0F;
  --shadow:0 1px 0 rgba(255,255,255,.03) inset, 0 12px 32px rgba(0,0,0,.35);
}}
:root[data-theme="dark"]{
  --paper:#08090A; --surface:#131517; --surface-2:#1B1E21; --surface-3:#24282C;
  --ink:#F2F4F5; --ink-2:#B4BBC1; --muted:#8C949B;
  --line:#24282C; --line-strong:#60686F;
  --accent:#1A9AD6; --accent-ink:#04141C; --accent-soft:#0F2A38; --accent-line:#1F5C7C;
  --accent-strong:#0074AB;
  --ok:#3CC43C; --ok-soft:#13261A;
  --warn:#F5BD3D; --warn-soft:#2C2410; --warn-ink:#2A1F06;
  --alert:#EF7B7B; --alert-soft:#321718; --alert-ink:#2E0F0F;
  --shadow:0 1px 0 rgba(255,255,255,.03) inset, 0 12px 32px rgba(0,0,0,.35);
}
/* Les surfaces que nous ne dessinons pas portent le design quand meme.

   Sans `color-scheme`, le theme sombre laissait le navigateur peindre
   en CLAIR ses propres pieces : selecteurs de date, listes deroulantes,
   barres de defilement, champ de recherche. Le reste suit : la
   selection de texte, le curseur de saisie, les cases a cocher, et les
   chiffres des tableaux, qui doivent s'aligner en colonne. */
:root{color-scheme:light}
:root[data-theme="dark"]{color-scheme:dark}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){color-scheme:dark}}

::selection{background:var(--accent-soft);color:var(--ink)}
:root{accent-color:var(--accent);caret-color:var(--accent)}
table,.tile,.kv dd,.heure{font-variant-numeric:tabular-nums}

*{box-sizing:border-box}
/* Les messages des serveurs d'envoi contiennent des URL de
   documentation sans espace : sans cette règle, une seule d'entre elles
   élargit la page entière et le texte sort de l'écran. */
/* `anywhere` coupe N'IMPORTE OÙ, y compris au milieu d'un numéro :
   « 066228166 / 2 » sur l'écran que le bureau lit pour composer, et
   « raphael.pitzus@gmail. / com » juste à côté. La coupure reste
   nécessaire — une adresse longue ne doit pas élargir le tableau —
   mais elle appartient aux blocs de prose, pas aux données. */
.notice,.err,.toast,.dlg-b,.badge{overflow-wrap:anywhere}
td{overflow-wrap:break-word}
/* Un numéro, une adresse, un lot : jamais coupés. La colonne s'élargit
   ou le tableau défile ; c'est préférable à un numéro faux. */
td.num,a.tel{overflow-wrap:normal;white-space:nowrap}
/* Le `<dd>` d'une liste de définitions porte AUSSI le texte libre que
   l'occupant a tapé — sa consigne d'accès, son point de vigilance.
   Avec `nowrap`, « digicode 12A45, sonnette au nom de FAURE, 3e étage
   gauche » élargissait la grille : le récapitulatif débordait de 96 px
   sur un téléphone, et la ligne « Accès » — celle qu'il relit devant sa
   porte — sortait de l'écran. Les numéros qu'il contient restent
   protégés par `a.tel`. */
.kv dd{overflow-wrap:normal}
td.small{overflow-wrap:anywhere}
/* La colonne « E-mail » est un `td.small`, et `anywhere` la coupait
   n'importe où : « raphael.pitzus@gmail. / com » sur l'écran que le
   bureau lit justement pour composer une adresse. Le téléphone était
   protégé, l'adresse non — la moitié du défaut restait. Déclarée
   APRÈS, à spécificité égale, elle reprend la main. */
td.mail{overflow-wrap:normal}
/* Une ancre ou un élément qui reçoit le focus au clavier atterrissait
   sous la barre du haut, qui est collante : on ne voyait pas ce qui
   venait d'être sélectionné. */
html{scroll-padding-top:calc(var(--h-topbar,0px) + 8px)}
:focus-visible{scroll-margin-top:calc(var(--h-topbar,0px) + 8px);
  scroll-margin-bottom:88px}
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--f-body);font-size:16px;line-height:1.5;-webkit-text-size-adjust:100%}
h1,h2,h3,h4{font-family:var(--f-display);margin:0;text-wrap:balance;letter-spacing:-.012em;line-height:1.2}
h1{font-size:var(--t-xl);font-weight:700}
h2{font-size:var(--t-lg);font-weight:600}
h3{font-size:var(--t-md);font-weight:600}
p{margin:0}
a{color:var(--accent)}
/* `color:inherit` autant que `font:inherit` : sans lui, un bouton prend
   la couleur de texte par défaut du navigateur — un noir fixe — et
   devient illisible dès qu'il pose un fond sombre. C'était le cas des
   créneaux et de la liste des logements en thème nuit, c'est-à-dire sur
   les deux écrans que voit l'occupant. */
button{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
.mono{font-family:var(--f-mono);font-variant-numeric:tabular-nums}
/* Longueur de ligne : la prose se borne, pas les tableaux.
   Les blocs de texte des ecrans de bureau couraient sur 171 caracteres,
   plus du double de ce qui se lit sans effort. */
p,.small,li{max-width:74ch}
/* …mais pas sur l'affiche du hall. Elle a sa propre composition, en
   points, et doit sortir identique quel que soit l'affichage. Le
   plafond bridait 45 de ses éléments sur l'A4 : la signature
   « Entreprise SAVIOLI » s'arrêtait 43 mm avant la marge au lieu
   d'être au fer à droite, et l'URL de secours passait sur deux lignes
   en coupant « navigateur » au milieu — sur une feuille qu'on lit de
   loin, dans un hall. */
.affiche p,.affiche li,.affiche .small{max-width:none}

.eyebrow{font-family:var(--f-display);font-size:var(--t-xs);font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.muted{color:var(--muted)}
.small{font-size:var(--t-sm)}

/* ---- shell ---- */
.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:12px;padding:8px 20px;background:var(--surface);border-bottom:1px solid var(--line)}
/* `center` et non `baseline` : le logo est une image, l'aligner sur la
   ligne de base du texte le décalerait vers le haut. */
.brand{display:flex;align-items:center;gap:12px;font-family:var(--f-display);font-weight:700;letter-spacing:-.02em;font-size:var(--t-md)}
.brand small{font-weight:500;color:var(--muted);font-size:var(--t-xs);letter-spacing:.08em;text-transform:uppercase}
.topbar .sp{flex:1}
/* Nom de la personne connectée : simple repère, pas une commande. */
.topnav .qui{margin:0 4px}
.wrap{max-width:1180px;margin:0 auto;padding:24px 20px 80px}
.wrap.narrow{max-width:640px}

/* ---- buttons ---- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:8px 12px;border-radius:var(--r);border:1px solid var(--line-strong);background:var(--surface);color:var(--ink);cursor:pointer;font-weight:600;font-size:var(--t-sm);font-family:var(--f-body);transition:background .12s,border-color .12s}
.btn:hover{background:var(--surface-2)}
.btn.primary:hover{filter:brightness(1.08)}
.btn.ghost{border-color:transparent;background:transparent;color:var(--ink-2)}
.btn.ghost:hover{background:var(--surface-2)}
/* Déclaré APRÈS `.btn.ghost`, à spécificité égale, il lui reprenait sa
   bordure : dans la liste des utilisateurs, « Supprimer » était le seul
   bouton de la ligne à avoir un contour visible. L'action la plus rare
   et la plus destructrice était la plus voyante. */
.btn.danger{color:var(--alert)}
.btn.ghost.danger{border-color:transparent;background:transparent}
.btn.ghost.danger:hover{background:var(--alert-soft)}
.btn.big{padding:12px 20px;font-size:var(--t-md);width:100%}
/* Pas d'opacité : elle délave aussi la bordure, et le bouton
   disparaissait — 2,9:1 sur son libellé. Un état inactif se dit avec
   des couleurs, pas avec de la transparence.

   Le remplaçant faisait pire sur ce qui compte : le libellé se lisait
   à 7,99:1, mais le FOND du bouton ressortait à 1,06:1 sur la page et
   sa bordure à 1,25:1. Sur « Réserver » — inactif tant qu'aucun
   créneau n'est choisi, c'est-à-dire à l'arrivée — il ne restait plus
   de bouton, seulement des mots. `--line-strong` est précisément le
   trait qui tient 3:1 ; c'est celui qu'il faut ici aussi. */
.btn[disabled]{cursor:not-allowed;background:var(--surface-2);color:var(--ink-2);
  border-color:var(--line-strong);box-shadow:none}
/* Sinon la barre collante lui rend une ombre portée : un bouton
   inactif ne doit pas paraître en relief. */
.barre-action .btn[disabled]{box-shadow:none}
.btnrow{display:flex;flex-wrap:wrap;gap:8px}

/* ---- fields ---- */
label.f{display:flex;flex-direction:column;gap:4px;font-size:var(--t-sm);font-weight:600;color:var(--ink-2)}
input[type=text],input[type=date],input[type=number],input[type=email],input[type=tel],
input[type=search],input[type=password],select,textarea{
  font:inherit;font-size:var(--t-md);padding:8px 12px;border:1px solid var(--line-strong);border-radius:var(--r);background:var(--surface);color:var(--ink);width:100%}
textarea{resize:vertical;min-height:80px;font-size:var(--t-sm)}
input:focus,select:focus,textarea:focus{outline:2px solid var(--accent);outline-offset:0;border-color:var(--accent)}
.grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px}
.check{display:flex;gap:8px;align-items:flex-start;font-size:var(--t-sm);font-weight:400;color:var(--ink);cursor:pointer}
/* 24 px : la taille de cible minimale de WCAG 2.2 (2.5.8). À 17 px, la
   case se manquait au doigt — sur un téléphone tenu d'une main dans une
   cage d'escalier, c'est le geste qui décide si l'occupant signale une
   attention particulière ou renonce. L'étiquette entière reste
   cliquable, la case n'est que la partie visible de la cible. */
/* TOUTE case à cocher, pas seulement celles enveloppées d'un
   `label.check`. Six des sept l'étaient ; la septième — la sélection
   ligne à ligne de l'écran de relecture d'import, celle qu'on coche
   141 fois — est posée nue dans un `<td>` et faisait 13 px. 24 px est
   le plancher de la WCAG 2.2 pour une cible de pointage : il ne doit
   pas dépendre de la façon dont la case est enveloppée. */
input[type=checkbox]{width:24px;height:24px;accent-color:var(--accent);flex:none}
.check input{margin-top:1px}
/* Le sélecteur de fichier n'était dans aucune règle : 21 px de haut,
   seule cible sous 24 px relevée sur les écrans du bureau. */
input[type=file]{min-height:44px;padding:8px 0;font:inherit;color:var(--ink)}
/* Un champ dont la saisie sera refusée le montre avant l'envoi. Posé
   aujourd'hui par le seul aperçu des zones (Réglages). */
input[aria-invalid=true]{border-color:var(--alert)}

/* Les zones du quitus, montrées sous leur champ au fil de la frappe
   (Réglages). Numérotées : l'ordre est celui des blocs du quitus et du
   document remis à la régie. Le nom revient à la ligne — une zone
   compte jusqu'à 60 caractères, plus qu'une largeur de téléphone. */
.zones-bloc{display:flex;flex-direction:column;gap:6px;margin-top:-4px}
.zones-apercu{display:flex;flex-wrap:wrap;gap:6px;list-style:none;margin:0;padding:0}
.zones-apercu:empty{display:none}
.zone-puce{display:inline-flex;align-items:center;gap:8px;max-width:100%;padding:4px 10px 4px 4px;
  border:1px solid var(--accent-line);border-radius:var(--r-lg);background:var(--accent-soft);
  color:var(--ink);font-size:var(--t-sm);font-weight:600;line-height:1.3}
.zone-nom{min-width:0;overflow-wrap:anywhere}
.zone-num{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;
  padding:0 6px;border-radius:var(--r);background:var(--accent);color:var(--accent-ink);
  font-size:var(--t-xs);font-variant-numeric:tabular-nums}
.zone-note{flex:none;font-size:var(--t-xs);font-weight:400}
.zone-puce.refusee{border-color:var(--alert);background:var(--alert-soft);color:var(--alert)}
.zone-puce.refusee .zone-num{background:var(--alert);color:var(--alert-ink)}
/* Le doublon reste visible, barré : le serveur l'écartera sans un mot,
   et le bureau doit pouvoir comprendre pourquoi sa zone n'apparaît
   qu'une fois sur le téléphone. */
.zone-puce.double{padding-left:10px;border-style:dashed;border-color:var(--line-strong);background:none;
  color:var(--muted);font-weight:400}
.zone-puce.double .zone-nom{text-decoration:line-through}
.zones-etat{margin:0;color:var(--muted)}
.zones-etat.erreur{color:var(--alert);font-weight:600}
.zones-rappel{margin:0;color:var(--warn);font-weight:600}

/* ---- panels ---- */
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);padding:16px}
.panel + .panel{margin-top:16px}
.panel-h{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px}
.panel-h .sp{flex:1}
.stack{display:flex;flex-direction:column;gap:12px}

/* ---- tiles ---- */
/* Mise en route d'un chantier : trois étapes, la suivante en avant.
   L'écran d'atterrissage montrait cinq tuiles à zéro et 900 px de
   vide, avec deux boutons actifs qui ne pouvaient rien faire. */
.etapes-mer{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:0}
.etapes-mer li{display:flex;gap:12px;align-items:flex-start;padding:16px 0;
  border-top:1px solid var(--line)}
.etapes-mer li:first-child{border-top:0;padding-top:4px}
.etapes-mer .num{flex:none;width:28px;height:28px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-weight:700;
  font-size:var(--t-sm);background:var(--surface-2);color:var(--muted)}
.etapes-mer li.courante .num{background:var(--accent);color:var(--accent-ink)}
.etapes-mer li.faite .num{background:var(--ok-soft);color:var(--ok)}
.etapes-mer li.faite b{color:var(--muted);font-weight:600}
.etapes-mer p{margin:2px 0 0}
.etapes-mer .btn{margin-top:8px}

.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden}
.tile{background:var(--surface);padding:12px 16px;display:flex;flex-direction:column;gap:3px}
.tile b{font-family:var(--f-display);font-size:var(--t-2xl);font-weight:700;font-variant-numeric:tabular-nums;line-height:1}
.tile span{font-size:var(--t-xs);letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:600}
.tile.hl b{color:var(--accent)}

/* ---- tabs ---- */
.tabs{display:flex;gap:2px;overflow-x:auto;border-bottom:1px solid var(--line);margin-bottom:16px}
.tab{padding:8px 12px;border:0;background:none;color:var(--muted);font-weight:600;font-size:var(--t-sm);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap}
.tab.actif{color:var(--ink);border-bottom-color:var(--accent)}

/* ---- table ---- */
/* Les en-têtes `position:sticky` ne collaient à rien.
   `overflow-x:auto` — nécessaire, un tableau de huit colonnes ne tient
   pas — force l'axe vertical à `auto` lui aussi (et `clip` y devient
   `hidden`, ce qui revient au même) : le conteneur est donc un cadre
   défilant, et c'est à son bord haut que les en-têtes se collaient. Or
   ce cadre ne défilait jamais, faute de hauteur. Sur cent quarante et
   un logements, on perdait le nom des colonnes dès le premier
   défilement de la page.
   On lui donne donc une hauteur maximale : un tableau court garde
   exactement l'allure qu'il avait, un tableau long défile sous ses
   propres en-têtes, qui restent lisibles. */
/* Pas de `min-height` : elle s'appliquait à tous les tableaux, y compris
   ceux d'une ou deux lignes (« Services d'envoi », l'aperçu d'import,
   les annulations), qui traînaient un blanc inutile en dessous.
   Pas de `max-height` par défaut non plus : l'écran de suivi comptait
   douze cadres défilants imbriqués, un par journée de planning, et la
   molette s'arrêtait au bord de chacun au lieu de continuer la page.
   Seuls les tableaux réellement longs en reçoivent une (classe posée
   par etiqueterTableau au-delà de 25 lignes), et c'est là que les
   en-têtes collants servent. */
.tbl-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface)}
/* La réserve est PROPORTIONNELLE, pas en pixels fixes.
   `calc(100dvh - 230px)` ne suivait ni le zoom ni la taille de texte :
   à 200 % — ce que la WCAG 1.4.4 demande de supporter — le cadre
   tombait à 220 px, soit UNE ligne et demie pour parcourir 141
   logements. Le tableau restait atteignable et devenait inutilisable.
   `rem` suit la taille de police choisie dans le navigateur. */
.tbl-wrap.long{overflow:auto;max-height:max(60dvh,22rem);overscroll-behavior:contain}
/* À l'impression, rien ne défile : le tableau doit sortir en entier.

   `.tbl-wrap.long` doit être nommé explicitement — une media query
   n'ajoute aucune spécificité, et `.tbl-wrap.long` (0-2-0) écrasait la
   correction (0-1-0). Sur une journée de 42 lignes, 26 rendez-vous ne
   s'imprimaient pas ; sur l'écran des logements, 74 % du tableau
   manquait à la feuille.

   `.day` clippe, lui, en LARGEUR : sa colonne « Accès » — le digicode
   que l'équipe cherche debout devant la porte — sortait rognée
   jusqu'à 65 %. */
@media print{
  .tbl-wrap,.tbl-wrap.long{max-height:none;overflow:visible}
}
table{border-collapse:collapse;width:100%;font-size:var(--t-sm)}
/* `top:0` : le cadre défilant est `.tbl-wrap.long`, pas la page. Les
   en-têtes se collent donc au bord haut du tableau. Sur un tableau
   court, sans `.long`, rien ne défile et la règle est sans effet. */
/* top:0 — le cadre défilant est .tbl-wrap, pas la page. */
th{text-align:left;font-family:var(--f-display);font-size:var(--t-xs);letter-spacing:.07em;text-transform:uppercase;color:var(--muted);font-weight:600;padding:8px 12px;border-bottom:1px solid var(--line);background:var(--surface-2);position:sticky;top:0;z-index:1;white-space:nowrap}
td{padding:8px 12px;border-bottom:1px solid var(--line);vertical-align:top}
tr:last-child td{border-bottom:0}
tbody tr:hover td{background:var(--surface-2)}
td.num,th.num{font-family:var(--f-mono);font-variant-numeric:tabular-nums}

/* ---- badges ---- */
.badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:100px;font-size:var(--t-xs);font-weight:600;letter-spacing:.02em;white-space:nowrap}
.badge.ok{background:var(--ok-soft);color:var(--ok)}
.badge.wait{background:var(--warn-soft);color:var(--warn)}
.badge.none{background:var(--surface-3);color:var(--muted)}
.badge.risk{background:var(--alert-soft);color:var(--alert)}
/* `warn` était posé par le code et n'avait AUCUNE règle : cinq
   situations sur huit sortaient en texte nu dans une pastille
   invisible — « Relancé », « Clé chez le gardien », « Clé chez un
   voisin », « Courrier envoyé », et « Créneau libéré — à rappeler »,
   celle que l'occupant pose lui-même et que le bureau doit traiter. */
.badge.warn{background:var(--warn-soft);color:var(--warn)}
.dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}

/* ---- chantier cards ---- */
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);padding:16px;display:flex;flex-direction:column;gap:8px}
.bar{height:5px;border-radius:100px;background:var(--surface-3);overflow:hidden}
.bar i{display:block;height:100%;background:var(--accent)}

/* ---- barre d'action collante (occupant) ---- */
/* Le bouton « Réserver » vivait à la fin d'une page de deux écrans et
   demi : sur un téléphone, on choisissait son créneau et rien ne
   semblait se passer. Il reste maintenant sous les yeux. */
.barre-action{position:sticky;bottom:0;z-index:20;margin:4px -2px 0;padding:8px 2px
  calc(10px + env(safe-area-inset-bottom,0px));
  background:linear-gradient(to bottom,transparent,var(--paper) 26%);
  }
.barre-action .btn{width:100%;min-height:52px;font-size:var(--t-md);box-shadow:var(--shadow)}
@media print{.barre-action{position:static;background:none}}

/* ---- slots (occupant) ---- */
.day{border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;background:var(--surface)}
/* `overflow:hidden` arrondit joliment les coins à l'écran, où le
   tableau défile de toute façon dans `.tbl-wrap`. À l'impression il
   COUPE : jusqu'à 65 % de la colonne « Accès » — le digicode que
   l'équipe cherche debout devant la porte. Cette annulation doit être
   écrite APRÈS la règle qu'elle annule : une media query n'ajoute
   aucune spécificité, et posée plus haut dans la feuille elle ne
   servait à rien. */
@media print{ .day{overflow:visible} }
.day-h{padding:8px 12px;background:var(--surface-2);border-bottom:1px solid var(--line);font-family:var(--f-display);font-weight:600;font-size:var(--t-sm);display:flex;justify-content:space-between;align-items:baseline;gap:8px}
/* « complet » et « n places » : à lire, pas à deviner. */
.day-h em{font-style:normal;font-size:var(--t-sm);color:var(--ink-2);font-weight:500}
.slots{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;padding:12px}
.slot{position:relative;padding:12px 12px;border:1px solid var(--line-strong);border-radius:var(--r);background:var(--surface);color:var(--ink);cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:2px;font-family:var(--f-body);min-height:56px}
.slot b{font-family:var(--f-mono);font-size:var(--t-md);font-weight:500}
/* « 2 places » est une information utile, pas une mention légale :
   --muted la rendait presque invisible, à 0,74 rem de surcroît. */
.slot span{font-size:var(--t-sm);color:var(--ink-2)}
.slot:hover:not([disabled]){border-color:var(--accent);background:var(--accent-soft)}
/* Sélection : bordure épaisse, fond teinté ET une coche. La couleur
   seule ne suffit pas — ni pour qui ne la distingue pas, ni au soleil
   sur l'écran d'un téléphone dans une cour d'immeuble. */
.slot[aria-pressed=true]{border-color:var(--accent);background:var(--accent-soft);
  box-shadow:inset 0 0 0 2px var(--accent);font-weight:600}
/* En bas à droite, et non en haut : en haut elle passait par-dessus
   l'heure, et lui réserver de la place faisait passer « 13h00 – 15h00 »
   sur deux lignes dans tous les créneaux. En bas, la ligne est courte
   (« 2 places »), il y a la place. */
/* `.slot .` et non `.` seul : `.slot span` (0-1-1) l'emportait, et la
   coche sortait grise, à la taille du texte secondaire, au lieu du
   turquoise de l'accent. La redondance bordure + fond + coche tenait
   quand même — c'est une intention perdue, pas un défaut de lecture. */
.slot .slot-coche{position:absolute;right:10px;bottom:9px;font-size:var(--t-md);line-height:1;
  color:var(--accent);font-weight:700;visibility:hidden}
.slot[aria-pressed=true] .slot-coche{visibility:visible}
.slot[aria-pressed=true] b{font-weight:700}
/* Idem : l'opacité effaçait aussi le « complet » en rouge, qui est
   précisément ce qui explique pourquoi le créneau est refusé. */
.slot[disabled]{cursor:not-allowed;background:var(--surface-2);color:var(--ink-2);
  border-style:dashed;border-color:var(--line-strong)}
.slot[disabled] span{color:var(--alert)}
/* Les bâtiments d'un créneau réservé (onglet Créneaux du bureau). Même
   piège de spécificité que la coche : `.slot .` l'emporte sur `.slot span`. */
.slot .slot-bats{align-self:flex-start;margin:2px 0;padding:1px 8px;border-radius:var(--r-sm);
  background:var(--accent-soft);color:var(--ink);font-size:var(--t-xs);font-weight:600}

/* ---- occupant chrome ---- */
.occ-head{background:var(--accent);color:var(--accent-ink);padding:20px}
/* Logo à gauche, identité du chantier à droite, sur une seule ligne.
   Même largeur que le contenu de la page, pour que tout s'aligne. */
.occ-head .in{max-width:640px;margin:0 auto;display:flex;align-items:center;gap:16px}
.occ-txt{display:flex;flex-direction:column;gap:4px;min-width:0}
/* Pastille blanche : le tracé du logo est cyan, illisible sur le
   turquoise sans elle. */
.occ-marque{background:#fff;border-radius:10px;padding:8px 12px;flex:none;
  display:flex;align-items:center}
.occ-marque .logo{height:46px;width:auto}
@media screen and (max-width:520px){
  .occ-head .in{gap:12px}
  .occ-marque{padding:4px 8px}
  .occ-marque .logo{height:36px}
}
.occ-head h1{font-size:var(--t-lg);color:var(--accent-ink)}
/* Le surtitre et l'adresse s'effacent un peu sous le nom du chantier,
   mais restent au-dessus de 4,5:1 sur le bandeau turquoise. À 0,78 le
   thème sombre tombait à 4,49:1 — juste en dessous. */
.occ-head p{font-size:var(--t-sm);opacity:.88}
/* Ce qui remplace les chapeaux : des sous-titres, sous le titre.
   Au-dessus, ils volaient au titre son ouverture — l'œil lisait
   « ESPACE ENTREPRISE » avant « Chantiers ». Dessous, ils précisent. */
/* Nommé `p` comme `.occ-jour` juste en dessous : `.occ-head p`
   (0-1-1) écrasait son opacité, et « Prise de rendez-vous » — le
   sous-titre censé remplacer le chapeau supprimé — rendait EXACTEMENT
   comme l'adresse et la nature des travaux.

   Rendre la déclaration active ne suffisait pas : 0,9 contre 0,88 ne
   se voit pas. Un sous-titre se distingue ou n'existe pas — même
   traitement que la date, sans changer de taille. */
.occ-head p.occ-quoi{font-size:var(--t-sm);opacity:1;font-weight:600;margin:4px 0 0}
/* `.occ-head p` l'emportait en spécificité et la rendait aussi
   discrète que l'adresse. C'est pourtant elle qui répond à la seule
   question que pose le bouton « Aujourd'hui » : quel jour ? */
.occ-head p.occ-jour{font-size:var(--t-md);font-weight:600;opacity:1;margin:4px 0 0}
/* L'heure du rendez-vous : le seul moment d'affichage du produit.
   C'est ce que l'occupant est venu lire, et ce qu'il relira sur son
   téléphone devant sa porte. */
/* `.recap .when` (0-2-0) est écrit dix-neuf lignes plus bas et gagnait
   à spécificité égale : l'heure du rendez-vous sortait à 20 px, la
   taille exacte de la date au-dessus. Sur le seul moment d'affichage
   du produit — ce que l'occupant relira sur son téléphone devant sa
   porte — il n'y avait plus aucune emphase. */
.when.grand,.recap .when.grand{font-size:var(--t-2xl)}

.recap-titre{font-family:var(--f-display);font-size:var(--t-lg);font-weight:700;
  color:var(--accent);margin:0 0 4px}
.steps{display:flex;gap:4px;align-items:center;margin-bottom:16px;font-size:var(--t-xs);color:var(--muted);font-weight:600}
.steps i{font-style:normal;padding:3px 8px;border-radius:100px;background:var(--surface-2)}
.steps i.on{background:var(--accent);color:var(--accent-ink)}
.pick{width:100%;text-align:left;padding:12px 12px;border:1px solid var(--line-strong);border-radius:var(--r);background:var(--surface);color:var(--ink);cursor:pointer;display:flex;align-items:center;gap:12px;font-family:var(--f-body)}
.pick:hover{border-color:var(--accent);background:var(--accent-soft)}
.pick .lot{font-family:var(--f-mono);font-size:var(--t-sm);color:var(--ink-2);flex:none;min-width:58px}
.pick .nm{font-weight:600}
/* Un logement déjà pourvu : grisé, mais lisible. L'opacité de 0,55
   qui tenait ce rôle rendait illisible la phrase qui explique quoi
   faire — « utilisez votre lien personnel, ou appelez le… ». */
.pick.pris{cursor:not-allowed;background:var(--surface-2);color:var(--ink-2);
  border-style:dashed}
.pick.pris .nm{font-weight:600}
.picklist{display:flex;flex-direction:column;gap:8px}
.recap{border:1px solid var(--accent-line);background:var(--accent-soft);border-radius:var(--r-lg);padding:16px;display:flex;flex-direction:column;gap:8px}
.recap .when{font-family:var(--f-display);font-size:var(--t-lg);font-weight:700}
/* `margin:0` : un `<dl>` porte par defaut la marge du navigateur,
   `1em` — soit 14 px ici, et une valeur qui CHANGE avec la taille de
   police. C'est l'exact contraire d'un rythme, et c'etait le seul
   espacement hors trame de cette feuille a ne pas etre delibere. */
.kv{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;font-size:var(--t-sm);margin:0}
.kv dt{color:var(--muted);font-weight:600;font-size:var(--t-sm);padding-top:2px}
.kv dd{margin:0}

/* ---- misc ---- */
.notice{border-left:3px solid var(--accent);background:var(--surface-2);padding:12px 12px;border-radius:0 var(--r) var(--r) 0;font-size:var(--t-sm);color:var(--ink-2)}
.notice.warn{border-left-color:var(--warn)}
.notice.alert{border-left-color:var(--alert)}
.empty{text-align:center;padding:44px 20px;color:var(--muted);display:flex;flex-direction:column;gap:12px;align-items:center}
/* Conteneur permanent : une region live doit exister dans le document
   avant que le message n'y soit inséré, sinon les lecteurs d'écran ne
   l'annoncent pas. */
.toasts{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:100;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none}
/* Deux zones live distinctes, empilées : une polie pour les
   confirmations, une assertive pour les erreurs. Elles n'ont pas de
   présentation propre — elles n'existent que pour que la politesse
   soit posée AVANT le message, et jamais réécrite. */
.toasts-zone{display:flex;flex-direction:column;gap:8px;align-items:center}
.toast{background:var(--ink);color:var(--paper);padding:12px 16px;border-radius:100px;font-size:var(--t-md);font-weight:600;box-shadow:var(--shadow);max-width:min(92vw,520px);text-align:center;pointer-events:auto}
/* Une erreur reste jusqu'à ce qu'on la ferme : elle porte donc un
   bouton, et l'arrondi en pilule laisse la place à droite. */
.toast.erreur{display:flex;align-items:center;gap:12px;text-align:left;
  border-radius:var(--r-lg);padding-right:8px}
.toast-x{flex:none;min-width:32px;min-height:32px;display:inline-flex;
  align-items:center;justify-content:center;border:0;border-radius:var(--r);
  background:transparent;color:inherit;font-size:var(--t-md);cursor:pointer}
.toast-x:hover{background:rgba(255,255,255,.18)}
/* Lien d'évitement : hors écran tant qu'il n'a pas le focus, puis posé
   par-dessus tout, en haut à gauche. Il ne se contente pas d'exister —
   il doit être VU quand on l'atteint, sinon il n'aide personne. */
.evitement{position:absolute;left:8px;top:-60px;z-index:200;
  background:var(--accent);color:var(--accent-ink);padding:8px 16px;
  border-radius:var(--r);font-weight:600;text-decoration:none;
  transition:top .12s ease-out}
.evitement:focus{top:8px}
#app:focus{outline:none}
#app:focus-visible{outline:2px solid var(--accent);outline-offset:-4px}

/* Visible des seuls lecteurs d'écran. `display:none` et
   `visibility:hidden` les retirent aussi de l'arbre d'accessibilité :
   il faut sortir le texte du cadre, pas le masquer. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
/* Le focus posé par programme sur un titre ne doit pas dessiner un
   contour permanent ; il reste visible au clavier grâce à :focus-visible. */
#app [tabindex="-1"]:focus{outline:none}
#app [tabindex="-1"]:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* Fiche complète d'un logement : faite pour être lue à voix haute au
   téléphone, ou imprimée et remise à l'occupant qui la demande.

   Elle est composée comme un courrier, pas comme un écran : en-tête à
   logo, bandeau d'identité, blocs. Sa version imprimée est le vrai
   destinataire de cette mise en page — c'est elle qu'on remet. */
.dlg-fiche{max-width:min(96vw,880px);width:880px}
.fiche-page{display:flex;flex-direction:column}

.fiche-tete{display:flex;align-items:flex-start;gap:12px;
  padding-bottom:12px;border-bottom:2px solid var(--ink)}
.fiche-tete .logo-fiche{height:40px;width:auto;flex:none}
.fiche-tete-g{flex:1 1 auto;min-width:0}
.fiche-marque{font-family:var(--f-display);font-weight:700;font-size:var(--t-md);letter-spacing:-.01em}
.fiche-doc{font-size:var(--t-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-top:1px}
.fiche-tete-d{text-align:right;font-size:var(--t-sm);color:var(--muted);max-width:48%;line-height:1.45}
.fiche-chantier{font-weight:600;color:var(--ink)}

.fiche-id{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;
  padding:16px 0 16px;border-bottom:1px solid var(--line)}
.fiche-id-t{min-width:0}
.fiche-nom{margin:0;font-family:var(--f-display);font-size:var(--t-xl);line-height:1.12;
  letter-spacing:-.02em;overflow-wrap:anywhere}
.fiche-porte{margin:4px 0 0;color:var(--muted);font-size:var(--t-sm)}
.fiche-etat{flex:none;margin-top:4px}
/* Les états d'un logement — sa situation, ses particularités — forment
   UN bloc aligné à droite du nom. Posés en enfants directs de
   `.fiche-id`, ils étaient répartis par `space-between` et le premier
   dérivait au milieu de la ligne. Ils s'empilent sous 620 px, où deux
   pastilles côte à côte ne tiennent plus à côté du nom. */
.fiche-etats{flex:none;display:flex;flex-wrap:wrap;justify-content:flex-end;
  gap:6px;max-width:50%}
@media (max-width:620px){.fiche-etats{max-width:100%;justify-content:flex-start}}

/* Deux colonnes : joindre l'occupant d'un côté, ce que le bureau a noté
   de l'autre. Sur un téléphone, elles s'empilent. */
.fiche-cols{display:grid;grid-template-columns:1fr 1fr;gap:0 32px;align-items:start}
.fiche-cols>.fiche-bloc{border-top:0;margin-top:0;padding-top:16px}
@media (max-width:640px){
  .fiche-cols{grid-template-columns:1fr}
  .fiche-cols>.fiche-bloc+.fiche-bloc{border-top:1px solid var(--line);margin-top:12px;padding-top:12px}
}

.fiche-bloc{border-top:1px solid var(--line);padding-top:12px;margin-top:12px;
  break-inside:avoid;page-break-inside:avoid}
.fiche-bloc h3{font-size:var(--t-xs);letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted);margin:0 0 8px;font-weight:700}
.fiche-liste{margin:0;padding-left:16px}
.fiche-liste li{margin-bottom:4px}
.fiche .kv{grid-template-columns:minmax(84px,auto) 1fr;gap:8px 16px;font-size:var(--t-sm)}
.fiche .kv dt{padding-top:2px}
.fiche .kv dd{font-weight:500}

.fiche-cr{margin:4px 0 0;padding:4px 8px;border-left:1px solid var(--line-strong);
  background:var(--surface-2);font-size:var(--t-sm);line-height:1.45}
.fiche-cr b{display:inline;font-size:var(--t-xs);letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted)}
/* Les deux signatures du quitus, côte à côte. Fond blanc : le PNG est
   déjà aplati à l'export, mais le cadre doit l'être aussi pour que le
   trait reste lisible en thème sombre comme sur le papier. */
.quitus-sigs{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px}
.quitus-sig{margin:0}
.quitus-sig img{display:block;width:100%;height:auto;background:#FFFFFF;
  border:1px solid var(--line);border-radius:var(--r-sm)}
.quitus-sig figcaption{margin-top:3px;font-size:var(--t-xs);color:var(--muted)}

/* Photos sur la fiche. Une grille qui s'adapte plutôt qu'un nombre de
   colonnes figé : la fiche se lit aussi bien sur l'écran du bureau que
   sur une A4, et les logements n'ont pas tous le même nombre de
   photos. */
.fiche-photos{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.fiche-photo{margin:0}
.fiche-photo img{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;
  border-radius:var(--r-sm);border:1px solid var(--line)}
.fiche-photo figcaption{margin-top:3px;font-size:var(--t-xs);color:var(--muted);line-height:1.35}
.fiche-note{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--line);
  font-size:var(--t-xs);color:var(--muted);line-height:1.5}
.fiche-pied{display:flex;flex-wrap:wrap;gap:4px 16px;margin-top:12px;padding-top:8px;
  border-top:1px solid var(--line);font-size:var(--t-xs);color:var(--muted)}
.fiche-pied span:last-child{margin-left:auto;text-align:right}

/* À l'impression d'une fiche, seule la fiche sort : le reste de
   l'application n'a rien à faire sur la feuille remise à un occupant.

   **Conditionné à la classe posée par dlgFiche**, et surtout pas à la
   seule règle `@media print`. Écrite sans condition, elle masquait
   `<main id="app">` — enfant direct de <body> — à CHAQUE impression :
   les affiches de hall et le planning sortaient en page blanche, et
   toute la mise en page A4 construite plus bas devenait inatteignable.
   L'affiche porte le QR code et le code du bâtiment ; sans elle, la
   page du hall ne sert à rien. Une classe plutôt qu'un `:has()` : ça
   marche partout, et l'intention est lisible dans le HTML. */
@media print{
  /* La page elle-meme redevient blanche, et le voile du dialogue
     disparait. Ce voile — un noir a 50 % pose sur toute la fenetre —
     s'imprime : c'est lui qui encadrait la feuille d'un liseré gris et
     couvrait d'un aplat tout le bas d'une fiche courte. */
  body.impression-fiche{background:#FFFFFF}
  body.impression-fiche dialog.dlg-fiche::backdrop{background:transparent}
  body.impression-fiche>*:not(dialog){display:none!important}
  body.impression-fiche dialog.dlg-fiche{display:block;position:static;max-width:none;
    width:auto;border:0;border-radius:0;box-shadow:none;padding:0;margin:0;overflow:visible;
    /* Le thème sombre ne s'imprime pas. Les fonds ne sortent pas de
       l'imprimante par défaut : un texte clair prévu pour un fond
       sombre sortait en gris pâle sur du papier blanc. La feuille
       impose donc sa propre palette, claire, quel que soit le thème. */
    --paper:#FFFFFF; --surface:#FFFFFF; --surface-2:#FFFFFF; --surface-3:#FFFFFF;
    --ink:#141A1F; --ink-2:#3D4952; --muted:#4A555C;
    --line:#B9C0BA; --line-strong:#6B736C; --accent:#0074AB;
    background:#FFFFFF;color:var(--ink)}
  /* `@page` porte une marge nulle pour supprimer l'en-tête ajouté par
     le navigateur : la feuille tient donc sa marge elle-même, comme
     l'affiche du hall. Le bas est dégagé pour le pied de page, qui y
     est fixé. */
  body.impression-fiche dialog.dlg-fiche .dlg-b{padding:15mm 16mm 26mm;height:auto;
    overflow:visible;gap:0;display:block}
  /* Le titre du dialogue redit le nom que porte déjà le bandeau. */
  body.impression-fiche dialog.dlg-fiche .dlg-b>h2{display:none}
  body.impression-fiche dialog.dlg-fiche .btnrow{display:none}
  body.impression-fiche dialog.dlg-fiche .tbl-wrap{max-height:none;overflow:visible;border:0}
  /* Un aplat de couleur ne sort pas de l'imprimante : l'état se lit à
     son contour. */
  body.impression-fiche .fiche-etat{background:transparent;border:1px solid var(--line-strong);
    color:var(--ink)}
  /* Photos : trois par ligne sur une A4, et jamais coupées en deux par
     un saut de page — une photo à cheval ne prouve rien. Un NOMBRE de
     colonnes fixe, et non le `minmax` en pixels de l'écran : celui-ci
     donne un nombre de colonnes qui dépend du zoom d'impression. */
  body.impression-fiche .fiche-photos{grid-template-columns:repeat(3,1fr);gap:4mm}
  body.impression-fiche .fiche-photo{break-inside:avoid;page-break-inside:avoid}
  /* À l'écran, un cadrage 4/3 uniforme rend la grille lisible. Sur la
     feuille, non : une photo de téléphone est portrait, et `cover` en
     4/3 lui retirait un peu plus de la moitié de sa hauteur — le
     plafond et la plinthe partaient. Or c'est cette feuille qu'on
     ressort trois semaines après quand un occupant signale une
     dégradation, et une preuve recadrée ne prouve rien. */
  body.impression-fiche .fiche-photo img{aspect-ratio:auto;object-fit:contain;height:auto}
  /* Sur plusieurs pages, l'en-tête du tableau des messages se répète. */
  body.impression-fiche thead{display:table-header-group}
  body.impression-fiche tr{break-inside:avoid;page-break-inside:avoid}
  /* Le pied revient au bas de chaque feuille. Sans lui, une fiche
     courte sortait sur une A4 aux trois quarts vide, sans date ni
     numéro à rappeler. */
  body.impression-fiche .fiche-pied{position:fixed;left:16mm;right:16mm;bottom:10mm;
    margin:0;padding-top:3mm}
  /* Un dialogue ouvert qui n'est pas une fiche ne doit pas s'imprimer
     par-dessus l'affiche. */
  body:not(.impression-fiche) dialog{display:none!important}
}
/* Relecture des logements relevés sur un plan : un tableau de champs
   modifiables, serré pour en voir beaucoup d'un coup. */
input.cand{padding:4px 8px;font-size:var(--t-sm);min-width:70px;width:100%}
td:has(> input.cand){padding:3px 4px}
.chips{display:flex;flex-wrap:wrap;gap:4px}
.chip{padding:4px 12px;border-radius:100px;border:1px solid var(--line-strong);background:var(--surface);font-size:var(--t-sm);cursor:pointer;font-weight:600;color:var(--ink-2);font-family:var(--f-body)}
.chip[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
dialog{border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);color:var(--ink);padding:0;max-width:min(620px,94vw);width:100%;box-shadow:var(--shadow)}
dialog::backdrop{background:rgba(10,14,17,.5)}
.dlg-b{padding:20px;display:flex;flex-direction:column;gap:12px}
/* Les messages d'un dialogue, juste au-dessus de ses boutons.
   Un `<dialog>` modal vit dans le *top layer* : les toasts posés dans
   le corps de page sont peints DESSOUS et retirés de l'arbre
   d'accessibilité. Ils sont donc reroutés ici — voir `zoneToast()`.
   Vide, le conteneur ne doit rien occuper : `:empty` retire l'écart
   que `gap` mettrait sinon sous chaque formulaire. */
.dlg-msg{display:flex;flex-direction:column;gap:8px}
.dlg-msg:empty{display:none}
/* Ils reprennent la forme des toasts, sans la pilule ni l'ombre : ici
   ils sont DANS le cadre, pas flottants au-dessus de la page. */
.dlg-msg .toast{border-radius:var(--r);box-shadow:none;text-align:left;
  max-width:none;font-size:var(--t-sm)}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
@media print{
  .topbar,.tabs,.no-print{display:none!important}
  body{background:#fff}
  /* La marge de page est nulle (voir plus bas) pour supprimer l'en-tête
     que le navigateur ajoute de lui-même : la marge réelle est portée
     ici, sinon le planning imprimé toucherait le bord du papier. */
  .wrap{padding:14mm 12mm;max-width:none}

  /* Le planning imprimé ne porte QUE le planning.

     Il sortait sur deux pages — 400 mm mesurés — en emportant sur le
     papier les tuiles de compteurs, le panneau des annulations à
     traiter, le bandeau des échecs d'envoi, la mise en route et le
     champ de recherche. Rien de tout cela ne sert à l'équipe qui tient
     la feuille devant une porte : elle veut l'heure, le lot, le nom et
     le numéro. */
  .demarrage,.recherche{display:none!important}
  .panel:has(> .empty),.panel-h .btnrow{display:none!important}
  /* Les commandes d'un en-tête de panneau — « Imprimer », « Exporter »
     — n'ont rien à faire sur le papier qu'elles produisent. */
  .panel-h .btn,.panel-h a.btn{display:none!important}

  /* Ni ombre ni fond : l'imprimante ne les sort pas, et là où elle les
     sort ils mangent l'encre pour rien. */
  .panel,.card,.day,.tbl-wrap{box-shadow:none!important;background:#fff!important}

  /* ---- Le planning : la feuille qu'on tient devant une porte ---- */

  /* Largeurs DÉCLARÉES. Le navigateur les répartissait au contenu, et
     donnait 65 px à « Accès » : une consigne réelle — « digicode
     12A45, sonnette au nom de NGUYEN, 3e étage gauche » — sortait sur
     quatorze lignes, « sonnette » coupé en « sonne / tte », et cette
     seule cellule portait la ligne à 26 % d'une page A4. Le nom de
     l'occupant avait moins de place que le code bâtiment/étage.

     Le digicode décide si l'équipe entre ou repart : c'est la colonne
     qui doit être large. 186 mm = 210 moins les deux marges de 12. */
  .day table{table-layout:fixed;width:100%}
  .day th:nth-child(1),.day td:nth-child(1){width:20mm}   /* Créneau */
  .day th:nth-child(2),.day td:nth-child(2){width:16mm}   /* Bât. / étage */
  .day th:nth-child(3),.day td:nth-child(3){width:12mm}   /* Lot */
  .day th:nth-child(4),.day td:nth-child(4){width:34mm}   /* Occupant */
  .day th:nth-child(5),.day td:nth-child(5){width:26mm}   /* Téléphone */
  .day th:nth-child(6),.day td:nth-child(6){width:26mm}   /* Vigilance */
  .day th:nth-child(7),.day td:nth-child(7){width:12mm}   /* Passage */
  .day th:nth-child(8),.day td:nth-child(8){width:38mm}   /* Accès */
  /* L'HEURE PASSE À LA LIGNE au lieu de déborder. `td.num` interdit la
     coupure — à l'écran, c'est voulu ; sur une colonne de 18 mm, « 08h00
     – 10h00 » débordait sur le bâtiment voisin et s'imprimait « 08h00–
     10h@0· R+1 ». */
  .day td:nth-child(1){white-space:normal}

  /* Une coupure au milieu d'un mot n'a aucun bénéfice sur du papier :
     personne ne défile, et « sonne / tte » se lit plus mal qu'un
     retour propre. */
  .day td,.day td.small{overflow-wrap:break-word;word-break:normal;hyphens:none}

  /* Une journée sans rendez-vous n'a rien à faire sur la feuille de
     route : douze lignes « libre — 2 places » prenaient une page
     entière sur trois semaines. */
  .day tr.libre{display:none}
  .day:not(:has(tbody tr:not(.libre))){display:none}

  /* ---- Le tableau des logements ---- */

  /* Des boutons ne se cliquent pas sur du papier. Ils occupaient une
     colonne entière, qui sortait intégralement de la feuille. */
  .tbl-wrap td .btnrow{display:none!important}

  /* Laissées libres, les colonnes ne tenaient pas sur une A4 portrait :
     la table sortait à 226,6 mm sur 210, « RDV » amputée de 22,5 mm.
     Cet écran n'est pas l'une des trois pièces que ce produit dessine
     pour le papier — mais le bureau imprime ce qu'il a sous les yeux,
     et une colonne coupée en deux est pire qu'une colonne étroite.
     Les largeurs se règlent PAR POSITION : retirer une colonne les
     décale toutes. La place de l'ancienne colonne « Propriétaire »
     (1.59.1) va aux rendez-vous : à 12 mm, une étiquette
     « lun. 12/10 10h00 », qui ne se coupe pas, ne pouvait pas y tenir. */
  table.tbl-logements{table-layout:fixed;width:100%}
  table.tbl-logements th:nth-child(1),table.tbl-logements td:nth-child(1){width:12mm}   /* Bât. */
  table.tbl-logements th:nth-child(2),table.tbl-logements td:nth-child(2){width:14mm}   /* Étage */
  table.tbl-logements th:nth-child(3),table.tbl-logements td:nth-child(3){width:16mm}   /* Lot */
  table.tbl-logements th:nth-child(4),table.tbl-logements td:nth-child(4){width:36mm}   /* Occupant */
  table.tbl-logements th:nth-child(5),table.tbl-logements td:nth-child(5){width:26mm}   /* Téléphone */
  table.tbl-logements th:nth-child(6),table.tbl-logements td:nth-child(6){width:38mm}   /* E-mail */
  table.tbl-logements th:nth-child(7),table.tbl-logements td:nth-child(7){width:40mm}   /* RDV */
  table.tbl-logements th:nth-child(8),table.tbl-logements td:nth-child(8){width:0;padding:0}   /* actions */
  /* Une journée ne se coupe pas en deux entre deux pages. */
  .day,tr{break-inside:avoid;page-break-inside:avoid}
  thead{display:table-header-group}
}

/* ---- compléments application serveur ---- */
a.brand{text-decoration:none;color:var(--ink)}
a.tab{text-decoration:none;display:inline-block}
.topnav{display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.toast.erreur{background:var(--alert);color:var(--alert-ink)}
.btn.small{padding:4px 8px;font-size:var(--t-sm)}
a.btn{text-decoration:none}
input[disabled],select[disabled],textarea[disabled]{background:var(--surface-2);color:var(--muted)}
@media print{ .topnav,.no-print{display:none!important} }

/* ================================================================== */
/* Parcours occupant : lisibilité et cibles tactiles                  */
/* ================================================================== */
/* L'occupant réserve debout dans un hall, sur un téléphone, souvent en
   mauvais réseau, et une part non négligeable du public est âgée. Deux
   planchers s'appliquent donc à tout l'espace occupant : 15 px de
   texte, 44 px de cible tactile. */
.occ-head ~ .wrap, .occ-head + .wrap{font-size:var(--t-md)}
.occ-head ~ .wrap .small, .occ-head + .wrap .small{font-size:var(--t-md)}
.occ-head ~ .wrap .eyebrow, .occ-head + .wrap .eyebrow{font-size:var(--t-sm)}
/* `:where()` ne pèse RIEN dans la cascade, et c'est tout l'objet ici :
   44 px est un PLANCHER, pas un plafond. Écrit en spécificité pleine
   (0-2-1), il écrasait `.slot{56px}`, `.jour-btn{52px}` et
   `.barre-action .btn{52px}` — les quatre cibles de pointage que le
   commentaire du jour J décrit comme « franches : on pointe avec un
   gant » faisaient 44 px. À spécificité nulle, la règle ne s'applique
   plus qu'aux boutons qui n'ont rien demandé, et les composants qui
   déclarent davantage gardent leur taille. */
:where(.occ-head ~ .wrap, .occ-head + .wrap) :where(button, .pick, .slot){min-height:44px}

/* Chips de bâtiment : de vrais boutons, pas des pastilles de 26 px. */
.chips.grands{gap:8px;margin-top:4px}
.chip.grand{padding:12px 20px;font-size:var(--t-md);min-height:44px;border-radius:var(--r)}

.btn.bloc{display:block;width:100%;padding:12px 16px;font-size:var(--t-md);min-height:48px}

/* Le planning des jours à venir (lien d'équipe, 1.60). Une ligne par
   rendez-vous, l'heure en tête et en chiffres de largeur fixe : c'est
   elle qu'on suit du doigt en descendant la journée. */
.jour-planning-btn{width:100%}
.planning-jour{border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);padding:12px 14px}
.planning-jour-h{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin-bottom:6px}
.planning-jour-h h2{margin:0;font-size:var(--t-md)}
.planning-jour-h .btn{margin-left:auto}
.planning-liste{list-style:none;margin:0;padding:0}
.planning-liste li{display:flex;flex-wrap:wrap;align-items:center;gap:2px 12px;max-width:none;
  padding:8px 0;border-top:1px solid var(--line);font-size:var(--t-sm);color:var(--ink)}
.planning-liste li:first-child{border-top:0}
.planning-liste li.pointe{color:var(--muted)}
.planning-heure{font-family:var(--f-mono);font-variant-numeric:tabular-nums;font-weight:600;min-width:7.5em}

/* Code de l'affiche : gros, espacé, en majuscules — il se recopie
   depuis une feuille A4 dans un hall parfois mal éclairé. */
/* `input.` et non `.` seul : la règle générique des champs de saisie
   pèse 0-1-1 (`input[type=text]`) et emportait six déclarations sur
   sept. Le champ où l'occupant recopie le code lu sur l'affiche
   sortait à 16 px en police système, au lieu de 24 px en chasse fixe —
   sur l'écran d'entrée du parcours hall, un code qu'on recopie d'une
   feuille A4 dans un hall parfois mal éclairé. */
input.code-champ{width:100%;font-size:var(--t-xl);letter-spacing:.28em;text-align:center;
  text-transform:uppercase;font-family:var(--f-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  padding:12px 8px;min-height:56px}

.err{color:var(--alert);font-weight:600;margin:8px 0 0}

/* Issue de secours, permanente et cliquable. */
/* L'information due à l'occupant sur ses données : repliée, discrète,
   mais présente et à un clic. */
.notice-rgpd{margin-top:12px;border:1px solid var(--line);border-radius:var(--r);
  background:var(--surface);font-size:var(--t-sm)}
.notice-rgpd>summary{cursor:pointer;padding:12px 12px;font-weight:600;color:var(--ink-2);
  min-height:44px;display:flex;align-items:center}
.notice-rgpd>summary:hover{color:var(--ink)}
.notice-rgpd .stack{padding:0 12px 12px;gap:8px}
.notice-rgpd p{margin:0;color:var(--ink-2);line-height:1.55}
@media print{.notice-rgpd{display:none}}
.secours{margin-top:16px;padding:12px 16px;border-radius:var(--r);
  background:var(--surface-2);border:1px solid var(--line);text-align:center;font-size:var(--t-md)}
/* Cible de 44 px de haut au minimum : ce lien se touche du pouce, dans
   une cage d'escalier, avec des gants. Il mesurait 34 px. */
a.tel{font-weight:700;color:var(--accent);text-decoration:none;white-space:nowrap;
  font-size:var(--t-md);display:inline-flex;align-items:center;min-height:44px;
  padding:4px 8px;margin:0 -8px}
a.tel:hover,a.tel:focus-visible{text-decoration:underline}
/* L'opacité de 0,85 n'a jamais été appliquée — `.occ-head p` gagnait —
   et l'appliquer ne se verrait pas davantage face au 0,88 hérité.
   L'italique, lui, distingue réellement : on garde ce qui marche. */
.occ-mo{font-style:italic}

/* ================================================================== */
/* Logo                                                                */
/* ================================================================== */
/* Tracé cyan sur fond transparent : lisible sur fond clair seulement.
   Partout où le fond est coloré, il est posé sur un bandeau blanc. */
/* `object-fit` : le tracé ne se déforme JAMAIS, quelle que soit la
   boîte qu'on lui donne. Sans lui, il suffit de poser ce logo dans une
   colonne flex — `align-items:stretch` par défaut — pour qu'il s'étire
   sur toute la largeur et s'écrase en hauteur. C'est arrivé sur la page
   de confidentialité, la seule page publique que l'occupant peut
   atteindre sans lien valide. Une marque déformée sur la page qui dit
   « voici qui traite vos données » inspire exactement ce qu'il ne faut
   pas. La cause est corrigée à l'endroit fautif ; cette ligne empêche
   la prochaine. */
.logo{display:block;object-fit:contain}

.logo-barre{height:42px;width:auto;flex:none}
/* Le tracé cyan reste lisible sur la barre sombre du thème nuit, mais
   il y perd du contraste : un fond blanc arrondi le détache. */
:root[data-theme=dark] .logo-barre,
:root[data-theme=dark] .logo-fiche,
.logo-connexion{background:#fff;border-radius:4px}
:root[data-theme=dark] .logo-barre,
:root[data-theme=dark] .logo-fiche{padding:2px 4px}
@media (prefers-color-scheme:dark){
  :root:not([data-theme=light]) .logo-barre,
  :root:not([data-theme=light]) .logo-fiche{background:#fff;border-radius:4px;padding:2px 4px}
}
.logo-connexion{height:58px;width:auto;padding:4px 8px}

@media print{ .logo-barre{display:none} }

/* Deux passages : l'occupant doit voir d'un coup d'œil où il en est. */
.passages{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.passage{border:1px solid var(--line);border-radius:var(--r);padding:12px 12px;background:var(--surface)}
.passage.ok{border-color:var(--accent)}
.passage.courant{box-shadow:0 0 0 2px var(--accent) inset}
.passage .etat{font-weight:700;margin-top:4px}

.renoncer{margin-top:4px;padding-top:12px;border-top:1px solid var(--line);text-align:center}
.renoncer p{margin:0 0 8px;color:var(--muted);font-size:var(--t-md)}

.recherche{width:100%;margin:8px 0 4px;min-height:44px}
/* L'opacité tombait à 2,6:1 sur le texte secondaire de la ligne. Un
   fond légèrement teinté dit la même chose sans toucher au texte. */
tr.hors-cible{background:var(--surface-2);color:var(--ink-2)}
tr.hors-cible td{font-style:italic}

/* ================================================================== */
/* Vue « jour J » — un téléphone tenu d'une main dans un escalier      */
/* ================================================================== */
/* Sur la fiche « jour J », le numéro est le geste principal : on le
   veut large, pas seulement haut. */
.jour-carte a.tel{padding:4px 12px;margin:0;border:1px solid var(--accent-line);
  border-radius:var(--r);background:var(--accent-soft);min-width:132px;
  justify-content:center}
.jour-carte{border:1px solid var(--line);border-left:4px solid var(--line-strong);
  border-radius:var(--r);background:var(--surface);padding:12px 16px;
  display:flex;flex-direction:column;gap:4px;font-size:var(--t-md)}
/* Une carte pointée reste parfaitement lisible : c'est celle que le
   compagnon relit pour vérifier ce qu'il vient de saisir. */
.jour-carte.pointe{background:var(--surface-2)}
.jour-carte.statut-fait{border-left-color:var(--ok,#2e7d32)}
.jour-carte.statut-absent,.jour-carte.statut-refus{border-left-color:var(--alert)}
.jour-carte.statut-a_revoir{border-left-color:var(--warn)}
.jour-tete{display:flex;justify-content:space-between;align-items:center;gap:8px}
.jour-h{font-size:var(--t-lg);font-weight:800;letter-spacing:-.01em}
.jour-lot{font-size:var(--t-md);color:var(--ink-2)}
.jour-nom{font-size:var(--t-lg);font-weight:700}
.jour-info{font-size:var(--t-md);color:var(--ink-2)}
.jour-vig{background:var(--surface-2);border-left:3px solid var(--warn);
  padding:8px 8px;border-radius:0 var(--r) var(--r) 0;font-size:var(--t-md)}
/* Quatre cibles franches : on pointe avec un gant, parfois d'une main. */
.jour-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:4px}
/* Le quitus occupe la largeur : il ne fait pas partie du groupe des
   quatre statuts, où un appui en désélectionne un autre. Le travail
   peut être fait et le papier pas revenu — ce sont deux faits. */
.jour-actions.une{grid-template-columns:1fr;margin-top:8px}
/* Passage pointé « Fait », quitus pas encore signé : l'état que ce
   bouton existe pour attraper. Relevé tel quel sur un chantier réel —
   une date de dépose en face d'un quitus vide — et c'est le papier qui
   se facture. Il se signale tant qu'il dure, puis redevient ordinaire. */
.jour-quitus.manquant{border-color:var(--warn);background:var(--warn-soft);color:var(--warn)}

/* Les déclencheurs de photo, dans le formulaire du quitus — la carte
   n'en porte plus depuis la 1.23.
   Chacun est un <span> parce qu'il porte aussi le <input file> caché ;
   c'est lui qui occupe la case de la grille d'actions, le bouton doit
   donc la remplir. */
.jour-photo-cmd{display:block}
.jour-photo-cmd .btn{width:100%}
/* Des vignettes, pas une galerie : sur le chantier on vérifie qu'on a
   bien pris la photo, on ne la contemple pas. Elles défilent
   horizontalement plutôt que de repousser le compte rendu hors de
   l'écran. */
/* Le défilement horizontal force AUSSI le rognage vertical : par
   spécification, un `overflow-x` non `visible` rend `overflow-y`
   calculé à `auto`. La croix de suppression, posée en `top:-14px`,
   perdait donc ses 14 px hauts — cible réellement atteignable 44 × 30,
   sous le seuil de 2.5.8 que ce produit s'applique. La réserve rend la
   place au débordement sans décaler la rangée. */
.jour-photos{display:flex;gap:8px;overflow-x:auto;margin-top:8px;padding-bottom:4px}
/* La réserve haut/droite appartient à la FIGURE, et non au conteneur :
   posée en négatif, la croix débordait sur la vignette suivante, qui
   est une sœur positionnée plus loin dans le document — donc peinte
   par-dessus. Le tiers supérieur de la cible ne répondait pas, alors
   même qu'elle mesurait ses 44 px. */
.jour-photo{position:relative;flex:none;margin:0;width:110px;padding:14px 14px 0 0}
.jour-photo img{display:block;width:96px;height:96px;object-fit:cover;
  border-radius:var(--r-sm);border:1px solid var(--line)}
.jour-photo figcaption{font-size:var(--t-xs);color:var(--muted);margin-top:2px;text-align:center}
/* Le cadre de signature.
   `touch-action:none` n'est pas décoratif : sans lui, le geste de
   signature fait DÉFILER la page au lieu de tracer, et le compagnon
   croit que le cadre ne marche pas. La hauteur est généreuse — on signe
   au doigt, pas au stylet. */
.signature{display:flex;flex-direction:column;gap:6px}
.signature-toile{display:block;width:100%;height:140px;touch-action:none;
  background:#FFFFFF;border:1px dashed var(--line-strong);border-radius:var(--r-sm);
  cursor:crosshair}
.signature .btnrow{justify-content:flex-end}
/* La signature déjà enregistrée, à la place du cadre : même boîte, mais
   un trait PLEIN — le pointillé invite à signer, le plein dit « fait ».
   Le fond reste blanc : c'est l'image de la pièce, pas un élément du
   thème. */
.signature-image{display:block;width:100%;height:140px;object-fit:contain;
  background:#FFFFFF;border:1px solid var(--line-strong);border-radius:var(--r-sm)}
.signature-legende{margin:0;color:var(--ok);font-weight:600}

/* La vignette est un BOUTON : il doit se comporter comme l'image qu'il
   remplace, sans bordure ni fond propres. */
.photo-voir{display:block;padding:0;border:0;background:none;cursor:zoom-in;
  border-radius:var(--r-sm)}
.photo-voir:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* La photo en grand.
   Le dialogue ordinaire plafonne à 620 px : ici on veut la place que
   l'écran peut donner, et une image qui tient en entier — c'est le seul
   écran où l'on REGARDE la photo, pas où on vérifie qu'elle existe. */
.dlg-photo{max-width:min(1100px,96vw)}
.vue-photo-img{display:block;width:100%;height:auto;max-height:70dvh;object-fit:contain;
  background:var(--surface-2);border-radius:var(--r-sm)}
@media (max-width:620px){
  /* Plein écran sur un téléphone : c'est le seul écran du produit où
     l'on REGARDE une image, et la place vaut mieux là qu'en marges. */
  .dlg-photo{max-width:100vw;width:100vw;height:100dvh;border:0;border-radius:0}
  /* Le contenu se centre verticalement. Sans cela, il restait collé en
     haut d'un dialogue pleine hauteur, et la moitié basse de l'écran
     paraissait vide — on ne sait plus si la page a fini de charger. */
  .dlg-photo .dlg-b{justify-content:center;min-height:100dvh}
  .vue-photo-img{max-height:62dvh}
}

/* La cible fait 44 px comme partout ailleurs, mais la pastille visible
   n'en couvre que 28 : à 44 px pleins, la croix mangerait la vignette
   qu'elle sert à juger. Le bouton est donc transparent et c'est son
   <span> qui se voit. */
.photo-x{position:absolute;top:0;right:0;width:44px;height:44px;
  display:flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;cursor:pointer}
.photo-x span{display:flex;align-items:center;justify-content:center;
  width:28px;height:28px;border-radius:50%;
  background:var(--surface);border:1px solid var(--line);
  color:var(--alert);font-size:14px;line-height:1}
.photo-x:focus-visible{outline:none}
.photo-x:focus-visible span{outline:2px solid var(--accent);outline-offset:2px}
.jour-btn{min-height:52px;font-size:var(--t-md);font-weight:700}
.jour-btn[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
.jour-plus{margin-top:4px;font-size:var(--t-md)}
.jour-plus summary{cursor:pointer;padding:8px 0;min-height:44px;display:flex;align-items:center;
  gap:10px;font-weight:600;color:var(--ink-2)}
.jour-plus summary:hover{color:var(--ink)}
/* Le chevron.
   `display:flex` sur un <summary> supprime la puce que le navigateur
   dessine d'ordinaire : il ne restait donc AUCUN signe qu'il y avait
   quelque chose à ouvrir. Sur « Logement — corriger si ce n'est pas
   bon », cela veut dire un technicien qui lit une ligne de texte,
   n'essaie pas d'appuyer dessus, et repart avec un quitus portant des
   informations fausses qu'il aurait pu rectifier.
   Tracé en CSS — deux bords d'un carré tourné — parce que ce produit ne
   charge aucune ressource extérieure, ni police d'icônes, ni image. */
.jour-plus summary::-webkit-details-marker{display:none}
.jour-plus summary{list-style:none}
.jour-plus summary::before{
  content:"";flex:none;width:7px;height:7px;margin-left:2px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:translateY(-2px) rotate(-45deg);
  transition:transform .15s ease-out}
.jour-plus[open]>summary::before{transform:translateY(-2px) rotate(45deg)}
/* Le mouvement en moins pour qui en a demandé moins (WCAG 2.3.3) : le
   chevron tourne quand même — c'est une information, pas une
   décoration — mais sans transition. */
@media (prefers-reduced-motion: reduce){ .jour-plus summary::before{transition:none} }
.jour-cr{width:100%;margin:4px 0}
@media screen and (max-width:380px){ .jour-actions{grid-template-columns:1fr} }

/* ================================================================== */
/* Téléphone                                                           */
/* ================================================================== */
/* L'occupant réserve debout dans un hall, sur un écran de 375 px de
   large, souvent d'une seule main. Tout ce qui suit corrige ce qui se
   cassait à cette largeur. */
@media screen and (max-width:560px){
  .wrap{padding:16px 12px 60px}

  /* Le fil d'étapes se brisait en trois pavés sur deux lignes chacun,
     et mangeait un quart de l'écran. Il devient une ligne discrète :
     ce n'est pas une commande, seulement un repère. */
  .steps{gap:0;font-size:var(--t-sm);margin-bottom:12px;
    display:flex;align-items:center;justify-content:center;flex-wrap:nowrap}
  .steps i{padding:4px 8px;white-space:nowrap;background:none;border-radius:0;flex:none}
  .steps i.on{background:none;color:var(--accent);font-weight:700}
  /* `> span` : les chevrons « › », enfants directs. Sans le
     combinateur, la règle attrapait aussi le libellé de l'étape en
     cours — un <span class="lbl"> à l'intérieur du <i> — et le rendait
     en `--line-strong`, une couleur calibrée pour des CONTOURS (3:1),
     pas pour du texte (4,5:1). Résultat : 3,79:1 sur « Votre
     créneau », en haut de la page que des centaines d'occupants
     ouvrent sur leur téléphone. */
  .steps > span{color:var(--muted);padding:0 1px;flex:none}
  /* Seule l'étape en cours porte son libellé : les autres se réduisent
     à leur numéro, et les trois tiennent sur une ligne. Le libellé
     reste dans le document pour les lecteurs d'écran. */
  .steps i:not([aria-current]) .lbl{
    position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

  /* Deux créneaux par ligne : à une seule colonne, une journée de huit
     créneaux demandait huit écrans de défilement. */
  .slots{grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:8px;padding:8px}

  /* « mercredi 9 septembre » et « 7 créneaux libres » se coupaient
     chacun en deux. Ils s'empilent proprement. */
  .day-h{flex-direction:column;align-items:flex-start;gap:2px}
  .day-h em{font-size:var(--t-sm)}

  /* ---- Tous les tableaux deviennent des fiches ----
     Un tableau de six à huit colonnes ne se lit pas sur 375 px : il
     déborde, et faire glisser fait perdre la colonne de gauche, celle
     qui identifie la ligne. Chaque ligne devient donc une fiche, et
     chaque cellule affiche l'intitulé de sa colonne (posé par
     etiqueterTableau, dans app.js). */
  /* `.tbl-wrap.long` doit être nommé, comme à l'impression : sans lui,
     les 141 logements « transformés en fiches » restaient dans un
     cadre à défilement imbriqué de 582 px pour 12 952 px de contenu. */
  .tbl-wrap,.tbl-wrap.long{overflow:visible;max-height:none;min-height:0;border:0;background:none;border-radius:0}
  .tbl-wrap table,.tbl-wrap tbody,.tbl-wrap tr,.tbl-wrap td{display:block;width:auto}
  /* `display:block` est indispensable ici : sur un thead resté en
     table-header-group, overflow:hidden ne clippe pas ses colonnes.
     Elles restaient donc dans le flux, invisibles, et élargissaient la
     page de plusieurs centaines de pixels. */
  .tbl-wrap thead{display:block;position:absolute;width:1px;height:1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  .tbl-wrap tr{background:var(--surface);border:1px solid var(--line);
    border-radius:var(--r);margin-bottom:8px;padding:8px 12px}
  .tbl-wrap tr:last-child{margin-bottom:0}
  .tbl-wrap tbody tr:hover td{background:none}
  .tbl-wrap td{border:0;padding:3px 0;display:flex;gap:8px;align-items:baseline}
  /* Cellule vide, ou colonne sans intitulé (actions) : pas de rangée
     fantôme, pas d'étiquette vide. */
  .tbl-wrap td:empty{display:none}
  .tbl-wrap td[data-l]::before{content:attr(data-l);flex:none;min-width:92px;
    font-size:var(--t-xs);color:var(--muted);text-transform:uppercase;letter-spacing:.04em;
    line-height:1.5}
  /* Cellule couvrant toute la ligne — « aucun résultat », un total :
     elle occupe la fiche entière, sans étiquette de colonne. */
  .tbl-wrap td[data-pleine]{display:block;padding:4px 0;font-style:italic;color:var(--ink-2)}
  /* Sur téléphone, ces boutons se touchent du pouce : 32 px de haut,
     c'est en dessous du minimum utilisable, et « Supprimer » est juste
     à côté de « Mot de passe ». */
  .tbl-wrap td .btnrow .btn{min-height:44px;padding-left:12px;padding-right:12px}
  .tbl-wrap td .btnrow{gap:8px}
  .tbl-wrap td .btnrow{margin-top:4px}

  /* Dans le planning, l'heure fait le titre de la fiche. */
  .day .tbl-wrap tr{border-radius:0;border-width:0 0 1px;margin-bottom:0;background:none}
  .day td.heure{font-size:var(--t-lg);font-weight:700;font-family:var(--f-mono);
    padding-bottom:4px;display:block}
  .day td.heure::before{content:none}
  .day tr.libre td:not(.heure){color:var(--muted);font-style:italic}
  .day tr.libre td:not(.heure)::before{content:none}

  /* Un dialogue plein écran plutôt qu'une fenêtre étriquée. */
  /* `.dlg-fiche` (0-1-0) doit etre nomme, sinon la fiche garde ses
     8 px de gouttiere de chaque cote au lieu d'etre a fond perdu. */
  dialog,dialog.dlg-fiche{max-width:100vw;width:100vw;height:100dvh;max-height:100dvh;border-radius:0;border:0}
  .dlg-b{padding:16px;height:100%;overflow:auto}

  /* Cinq tuiles de statistiques côte à côte devenaient illisibles. */
  .tiles{grid-template-columns:1fr 1fr}
}

/* ------------------------------------------------------------------ */
/* Écrans étroits du bureau                                            */
/* ------------------------------------------------------------------ */
/* La barre et les onglets cassent bien avant la largeur d'un téléphone :
   dès qu'une fenêtre est réduite, « Prise de rendez-vous » se brisait en
   quatre lignes et la navigation s'empilait à côté. */
/* ---- menu replié ---- */
.burger{display:none;flex-direction:column;justify-content:center;gap:4px;
  width:44px;height:44px;padding:0 12px;border:1px solid var(--line-strong);
  border-radius:var(--r);background:var(--surface);cursor:pointer;flex:none}
.burger span{display:block;height:2px;background:var(--ink);border-radius:2px;
  transition:transform .16s,opacity .16s}
.burger[aria-expanded=true] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.burger[aria-expanded=true] span:nth-child(2){opacity:0}
.burger[aria-expanded=true] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
@media (prefers-reduced-motion:reduce){ .burger span{transition:none} }

@media screen and (max-width:860px){
  .topbar{gap:8px;padding:8px 12px;position:relative}
  .topbar .sp{flex:1}
  .brand{white-space:nowrap;min-width:0;font-size:var(--t-md)}
  .brand .titre{overflow:hidden;text-overflow:ellipsis}
  .burger{display:flex}

  /* La navigation devient un panneau sous la barre, refermé par
     défaut. Les entrées y sont pleine largeur, à 44 px de haut. */
  .topnav{display:none;position:absolute;top:100%;left:0;right:0;z-index:40;
    flex-direction:column;align-items:stretch;gap:2px;padding:8px;
    background:var(--surface);border-bottom:1px solid var(--line);box-shadow:var(--shadow)}
  .topnav.ouvert{display:flex}
  .topnav .btn{justify-content:flex-start;min-height:44px;font-size:var(--t-md)}
  .topnav .qui{padding:8px 12px;border-top:1px solid var(--line);margin-top:4px}
  .logo-barre{height:34px}

  /* Les onglets défilent au doigt plutôt que de s'empiler. */
  .tabs{overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .tabs::-webkit-scrollbar{display:none}
  .tab{white-space:nowrap;flex:none}

  /* Les en-têtes de panneau avec boutons débordaient. */
  .panel-h{flex-wrap:wrap;gap:8px}
  .panel-h .sp{display:none}
}
/* Sous 480 px, le logo suffit à identifier l'application : le titre
   n'apporte rien et vole la place de la référence du chantier. */
@media screen and (max-width:480px){
  .brand .titre{display:none}
}

/* ================================================================== */
/* Affiche de hall                                                     */
/* ================================================================== */
/* Charte reprise de la note aux occupants déjà utilisée par
   l'entreprise, relevée sur le document d'origine : deux couleurs, un
   corps de texte en bleu et non en noir, un bloc destinataire à droite,
   un titre centré souligné d'un filet, des sections en encadrés.

   Ces couleurs sont volontairement en dur et non tirées du thème :
   c'est une pièce imprimée, elle doit sortir identique quel que soit
   l'affichage de l'écran, y compris en thème sombre. */
.affiches{display:flex;flex-direction:column;gap:24px}
.affiche{
  --af-bleu:#1F4E79;
  --af-vert:#2E7D32;
  background:#fff;color:var(--af-bleu);
  border:1px solid var(--line);border-radius:var(--r-lg);
  /* 1rem en clair, pas `--t-md` : l'affiche a son propre systeme,
     en rem a l'ecran et en points au tirage. Branchee sur l'echelle
     des ecrans, elle aurait suivi en silence le prochain reglage de
     `--t-md` — l'apercu et le papier auraient divergé sans que rien
     ne le signale. Meme valeur rendue qu'aujourd'hui. */
  padding:32px 40px;font-size:1rem;line-height:1.5;
  font-family:Calibri,Carlito,"Segoe UI",Arial,sans-serif;
}
.affiche p,.affiche li,.affiche div{color:var(--af-bleu)}

.af-entete{display:flex;justify-content:space-between;align-items:flex-start;gap:24px;margin-bottom:26px}
.af-logo{height:64px;width:auto}
.af-dest{text-align:right;font-size:.95rem;line-height:1.5;color:#000!important}
.af-dest div{color:#000!important}

.af-titre{text-align:center;letter-spacing:.06em;margin:0 auto 4px;font-size:1.45rem;
  font-weight:700;color:var(--af-bleu);width:max-content;max-width:100%;
  border-bottom:2px solid var(--af-bleu);padding-bottom:4px}
.af-sous{text-align:center;font-size:1.1rem;font-weight:700;margin:10px 0 22px;color:var(--af-bleu)}
.af-soustitre{font-weight:700;margin:20px 0 8px;color:var(--af-bleu)}
.af-corps{margin:0 0 12px}
.af-liste{margin:0;padding-left:20px}
.af-liste li{margin-bottom:5px}

/* Encadrés de section, comme les blocs PHASE 1 / PHASE 2 de la note. */
.af-section{border:1.5px solid var(--af-bleu);border-radius:4px;margin:18px 0;overflow:hidden}
.af-section.vert{border-color:var(--af-vert)}
.af-section-t{background:var(--af-bleu);color:#fff!important;font-weight:700;
  font-size:1rem;letter-spacing:.04em;padding:8px 16px}
.af-section.vert .af-section-t{background:var(--af-vert)}
.af-section-c{padding:16px}
.af-section.vert .af-section-c,.af-section.vert .af-section-c *{color:var(--af-vert)}

.af-qr{display:flex;gap:24px;align-items:center;flex-wrap:wrap}
.af-qr img{width:190px;height:190px;background:#fff;flex:none}
.af-qr-txt{flex:1;min-width:220px}
.af-code-l{margin-top:14px;font-size:.8rem;letter-spacing:.1em;text-transform:uppercase;font-weight:700}
/* Le code se recopie à la main, depuis un mur, parfois mal éclairé. */
.af-code{font-family:Consolas,"Courier New",monospace;
  font-size:2.5rem;font-weight:700;letter-spacing:.2em;line-height:1.15}
.af-url{font-size:.9rem;margin:14px 0 0;word-break:break-all}
.af-tel{margin:10px 0 0;font-size:1.05rem}
.af-signature{margin:4px 0 0;text-align:right;font-weight:700}
/* Borné à cinq lignes : le champ accepte 2000 caractères, et une
   affiche qui déborde sur une seconde page ne sert à rien. */
.af-message{display:-webkit-box;-webkit-line-clamp:5;line-clamp:5;
  -webkit-box-orient:vertical;overflow:hidden}

@media print{
  /* Marge nulle sur la page : c'est ce qui supprime l'en-tête que le
     navigateur ajoute de lui-même (date, titre de l'onglet, URL). La
     marge réelle est portée par l'affiche, qui maîtrise ainsi sa mise
     en page. Si l'en-tête réapparaît, c'est la case « En-têtes et pieds
     de page » de la boîte d'impression : elle est plus forte que le CSS. */
  @page{size:A4 portrait;margin:0}
  /* L'affiche porte sa propre marge : le .wrap qui la contient ne doit
     pas l'ajouter par-dessus. */
  .wrap:has(.affiches){padding:0}
  .affiches{gap:0}

  /* UNE affiche par page, exactement.
     Elles partent dans des halls différents, et une signature seule sur
     une seconde feuille n'a aucun sens. La hauteur est donc bornée à la
     page, et toute la typographie resserrée pour y tenir même avec un
     nom de résidence long et un message d'accueil. */
  .affiche{
    break-after:page;page-break-after:always;break-inside:avoid;
    height:297mm;width:210mm;overflow:hidden;
    border:none;border-radius:0;padding:13mm 14mm;
    font-size:10.5pt;line-height:1.35;
    display:flex;flex-direction:column;
  }
  .affiche:last-child{break-after:auto;page-break-after:auto}

  .af-entete{margin-bottom:10px}
  .af-logo{height:46px}
  .af-dest{font-size:9.5pt;line-height:1.35}
  .af-titre{font-size:16pt;padding-bottom:4px;margin-bottom:2px}
  .af-sous{font-size:12pt;margin:7px 0 12px}
  .af-corps{margin:0 0 8px}
  .af-soustitre{margin:12px 0 5px}
  .af-liste li{margin-bottom:3px}
  .af-section{margin:11px 0}
  .af-section-t{font-size:11pt;padding:6px 12px;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  .af-section-c{padding:11px 12px}
  .af-qr{gap:16px}
  .af-qr img{width:150px;height:150px}
  .af-code{font-size:21pt;letter-spacing:.16em}
  .af-code-l{margin-top:9px}
  .af-url{margin-top:9px;font-size:9pt}
  .af-tel{margin-top:7px;font-size:10.5pt}
  /* La signature est poussée en bas de page plutôt que de flotter
     derrière un contenu de longueur variable. */
  .af-signature{margin-top:auto;padding-top:8px}

  /* Aucun bouton sur une feuille de papier.

     Sauf celui qui ne porte pas une commande mais une IMAGE. Depuis que
     la vignette est devenue un bouton — pour être atteignable au
     clavier et annoncée comme telle — cette règle faisait disparaître
     les photos de la fiche imprimée : très exactement le document qu'on
     ressort trois semaines après, quand un occupant signale une
     dégradation. La croix de suppression, elle, reste cachée : c'est
     une vraie commande. */
  button,.btn{display:none!important}
  button.photo-voir{display:block!important;cursor:default}
}

[hidden]{display:none!important}

td.apercu{white-space:pre-line;max-width:44ch}

/* Le lien de bas de page des écrans occupant. Discret — ce n'est pas
   une commande du parcours — mais atteignable au doigt : c'est la
   seule adresse stable où relire ce qui est fait de ses données une
   fois le chantier terminé. */
.pied-legal{margin-top:24px;padding-top:12px;border-top:1px solid var(--line);
  font-size:var(--t-sm);text-align:center}
.pied-legal a{color:var(--ink-2);display:inline-block;padding:8px 12px;min-height:44px;
  display:inline-flex;align-items:center}
.pied-legal a:hover{color:var(--ink)}
@media print{.pied-legal{display:none}}

/* En-tête du planning imprimé : invisible à l'écran, où le titre du
   panneau et la barre du haut disent déjà de quel chantier il s'agit.
   Sur le papier, c'est la seule identification de la feuille. */
.planning-tete{display:none}
@media print{
  .planning-tete{display:flex;align-items:flex-start;gap:16px;
    padding-bottom:8px;margin-bottom:4px;border-bottom:1.5px solid var(--ink)}
  .planning-tete > div:first-of-type{flex:1}
  .planning-tete-d{text-align:right;font-weight:600}
}

/* Le nom d'un occupant, dans la liste d'appels : il ouvre sa fiche.
   Un bouton, pas un lien — il n'y a pas d'adresse derrière — mais qui
   se lit comme du texte et non comme une commande. */
.lien-nom{background:none;border:0;padding:2px 0;font:inherit;font-weight:600;
  color:var(--ink);text-align:left;cursor:pointer;text-decoration:underline;
  text-decoration-color:var(--line-strong);text-underline-offset:3px;
  /* 24 px : plancher de cible de la WCAG 2.2 (2.5.8). Sans lui, le
     nom faisait 21 px de haut et la sonde d'accessibilité de la suite
     l'a signalé — c'est elle qui a attrapé ce défaut, pas moi. */
  min-height:24px;display:inline-flex;align-items:center}
.lien-nom:hover{color:var(--accent);text-decoration-color:currentColor}
@media print{.lien-nom{text-decoration:none;color:inherit}}

/* ------------------------------------------------------------------ */
/* Ce qui n'est pas encore parti                                       */
/* ------------------------------------------------------------------ */
/* Un bandeau permanent, en bas de l'écran, tant que la file n'est pas
   vide. C'est la pièce la plus importante du mode hors ligne, et ce
   n'est pas une figure de style : une file d'attente INVISIBLE qui perd
   une signature d'occupant est pire que pas de file du tout — elle fait
   croire que c'est parti. Le compagnon doit pouvoir vérifier d'un coup
   d'œil, avant de quitter le chantier, qu'il ne reste rien.

   En bas et non en haut : c'est là que le pouce arrive, et le bouton
   « Envoyer maintenant » doit être atteignable à une main, l'autre
   tenant l'échelle ou le carton. */
.file-barre{position:fixed;left:0;right:0;bottom:0;z-index:120;
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:10px 14px;padding-bottom:calc(10px + env(safe-area-inset-bottom));
  background:var(--warn-soft);color:var(--ink);
  border-top:2px solid var(--warn);font-size:var(--t-sm);
  box-shadow:0 -6px 20px -14px rgba(20,26,31,.5)}
.file-barre .file-compte{font-weight:700}
.file-barre .file-detail{color:var(--ink-2);flex:1 1 auto;min-width:0}
.file-barre .btn{flex:none}
/* Une pastille qui bat : sur un écran qu'on regarde trois secondes
   entre deux portes, le mouvement se remarque là où une couleur de
   plus ne se remarque pas. */
.file-point{flex:none;width:10px;height:10px;border-radius:50%;
  background:var(--warn);animation:file-bat 2s ease-in-out infinite}
@keyframes file-bat{0%,100%{opacity:1}50%{opacity:.25}}
/* Un refus du serveur n'est plus une attente : c'est un incident, et il
   se dit avec la couleur des incidents. */
.file-barre.bloquee{background:var(--alert-soft);border-top-color:var(--alert)}
.file-barre.bloquee .file-point{background:var(--alert);animation:none}
/* Le mouvement retiré pour qui en a demandé moins (WCAG 2.3.3) : la
   pastille reste, elle ne bat plus. */
@media (prefers-reduced-motion: reduce){.file-point{animation:none}}
/* Sans cette réserve, le bandeau recouvre le dernier bouton de la
   dernière carte — précisément celui qu'on cherche en fin de journée. */
body:has(.file-barre:not([hidden])) .wrap{padding-bottom:var(--haut-barre, 84px)}
/* Et les messages avec. Le bandeau est peint PAR-DESSUS la zone des
   toasts — mesuré : soixante pixels sur cent quatre masqués — et le
   message qu'il recouvrait était justement celui qu'on a rendu
   persistant pour qu'il soit lu : « Quitus gardé sur ce téléphone […]
   ne fermez pas l'application avant. » Le compagnon voyait le bandeau,
   pas la phrase. */
body:has(.file-barre:not([hidden])) .toasts{bottom:calc(24px + var(--haut-barre, 84px))}
/* « Gardé sur ce téléphone » n'est pas une alarme : rien n'est perdu.
   Mais ce message doit être LU, donc il reste jusqu'à ce qu'on le
   ferme — comme une erreur, sans sa couleur. */
.toast.garde{display:flex;align-items:center;gap:12px;text-align:left;
  border-radius:var(--r-lg);padding-right:8px;
  /* `--warn-ink` et non `#FFFFFF` : sur le thème sombre, `--warn` est
     un ambre clair (#D8AC55) et le blanc dessus donnait **2,11:1** —
     très en dessous des 4,5 exigés. C'est le message « Quitus gardé sur
     ce téléphone… ne fermez pas l'application avant », celui qui reste
     à l'écran PARCE QU'IL DOIT ÊTRE LU, et il était illisible sur le
     thème que porte un téléphone de chantier. Le jumeau rouge avait été
     corrigé de cette façon ; l'ambre avait été oublié. 7,67:1. */
  background:var(--warn);color:var(--warn-ink)}
@media print{.file-barre{display:none}}

/* Le compte de la file ouvre son détail : c'est un bouton, pas un
   libellé. Il garde l'apparence du texte du bandeau — un bouton peint
   comme un bouton au milieu d'un bandeau d'alerte ferait deux commandes
   concurrentes là où il n'y a qu'une action principale, « Envoyer
   maintenant ». */
.file-ouvrir{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:10px;
  flex-wrap:wrap;border:0;background:none;padding:4px 0;margin:0;cursor:pointer;
  color:inherit;font:inherit;text-align:left;min-height:36px}
.file-ouvrir:hover .file-compte{text-decoration:underline}
.file-ouvrir:focus-visible{outline:2px solid var(--ink);outline-offset:2px;border-radius:var(--r)}

/* Une photo prise mais pas encore envoyée : elle n'existe que dans la
   file du téléphone, donc pas d'image à demander. La case dit qu'elle
   est là et qu'elle attend — sans elle, la grille restait vide et le
   compagnon reprenait le même cliché. */
.jour-photo.attente .photo-attente{
  width:96px;height:96px;border-radius:var(--r);
  border:1px dashed var(--warn);background:var(--warn-soft);color:var(--warn);
  display:flex;align-items:center;justify-content:center;font-size:1.6rem}
.jour-photo.attente figcaption{color:var(--warn)}

/* LES ACTIONS D'UNE LIGNE DE LOGEMENT, sur deux rangs au plus. À 1280 px,
   la dernière colonne était si étroite que les quatre boutons
   s'empilaient un par un : des lignes de 87 à 165 px, une douzaine de
   logements par écran au lieu d'une trentaine. */
@media (min-width: 900px){
  .tbl-logements td:last-child{min-width:240px}
  .tbl-logements td:last-child .btnrow{gap:4px}
}

/* ==================================================================
   CHARTE « PILOTAGE CHANTIERS » (1.62)
   Le bureau et l'équipe prennent le style du tableau de bord de
   l'entreprise : fond noir, halo et quadrillage cyan façon plan, cartes
   anthracite, libellés en capitales espacées. Les pages des occupants
   restent claires, aux mêmes couleurs et polices — beaucoup lisent sur
   un téléphone, parfois en plein jour (décision du 22 octobre 2026).
   Le thème est posé par page, `data-theme` sur <html> : par le serveur
   dès la première peinture, puis par `rendre()` à chaque navigation.
   ================================================================== */
h1,h2,h3,h4{font-weight:800;letter-spacing:-.015em}
h1{letter-spacing:-.02em}
.brand{font-weight:800}
.eyebrow{font-weight:800;letter-spacing:.16em;color:var(--accent)}
.btn.primary{background:var(--accent-strong);border-color:var(--accent-strong);color:#FFFFFF}
th{letter-spacing:.12em;font-weight:800}
.tile b{font-weight:800;letter-spacing:-.02em}
.tile span{letter-spacing:.12em;font-weight:800}
.card:hover{border-color:var(--accent-line)}

/* Le fond « plan » et la barre translucide : à l'écran seulement. */
@media screen{
  /* Le quadrillage vaut pour les DEUX thèmes ; seules ses teintes
     changent. En clair, il reste pâle : la page de l'occupant doit se
     lire d'abord.
     Pas de `background-attachment:fixed` sur le halo : sous Android, il
     faisait repeindre toute la page à chaque défilement. Le
     quadrillage, lui, reste fixe (`body::before`). */
  :root{--grille-forte:rgba(0,116,171,.10);--grille-fine:rgba(15,20,23,.035);--halo:rgba(0,116,171,.07)}
  :root[data-theme="dark"]{--grille-forte:rgba(0,126,185,.11);--grille-fine:rgba(255,255,255,.028);--halo:rgba(0,126,185,.16)}
  /* Le halo sur un calque FIXE, comme la grille. Posé sur `body` sans
     `fixed`, son dégradé prenait la hauteur du contenu et se répétait
     en bandes sous une page courte. Un calque `position:fixed` reste
     composité : le défilement ne le repeint pas. */
  body{background:var(--paper)}
  body::after{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
    background:radial-gradient(900px 600px at 12% 8%,var(--halo),transparent 70%),
      radial-gradient(700px 500px at 95% 100%,rgba(0,126,185,.07),transparent 70%)}
  /* Trame fine tous les 24 px, trait plus marqué tous les 120 px,
     estompée vers les bords. */
  body::before{
    content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
    background-image:
      linear-gradient(var(--grille-forte) 1px,transparent 1px),
      linear-gradient(90deg,var(--grille-forte) 1px,transparent 1px),
      linear-gradient(var(--grille-fine) 1px,transparent 1px),
      linear-gradient(90deg,var(--grille-fine) 1px,transparent 1px);
    background-size:120px 120px,120px 120px,24px 24px,24px 24px;
    background-position:-1px -1px;
    -webkit-mask-image:radial-gradient(ellipse 85% 75% at 30% 20%,#000 20%,transparent 85%);
    mask-image:radial-gradient(ellipse 85% 75% at 30% 20%,#000 20%,transparent 85%)}
  :root[data-theme="dark"] .topbar{background:rgba(8,9,10,.78);
    -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    box-shadow:0 1px 0 rgba(0,116,171,.55)}
  :root[data-theme="dark"] .panel,
  :root[data-theme="dark"] .card,
  :root[data-theme="dark"] .tiles{box-shadow:var(--shadow)}
}

/* La signature typographique du tableau de bord, sur les écrans du
   bureau et de l'équipe. Pas chez l'occupant : des capitales espacées se
   lisent moins bien, et ses boutons portent des phrases. */
:root[data-theme="dark"] .btn.primary{text-transform:uppercase;letter-spacing:.1em;
  font-family:var(--f-display);font-weight:800;font-size:var(--t-xs)}
:root[data-theme="dark"] .btn.primary.big{font-size:var(--t-sm)}
/* L'en-tête du lien d'équipe : un bandeau noir souligné de cyan, comme
   celui du tableau de bord, et non un aplat cyan. */
:root[data-theme="dark"] .occ-head{background:var(--surface);color:var(--ink);
  border-bottom:1px solid var(--line);box-shadow:0 1px 0 rgba(0,116,171,.55)}
:root[data-theme="dark"] .occ-head h1{color:var(--ink)}
:root[data-theme="dark"] label.f{font-size:var(--t-xs);font-weight:800;
  text-transform:uppercase;letter-spacing:.12em}
:root[data-theme="dark"] .kv dt{font-size:var(--t-xs);font-weight:800;
  text-transform:uppercase;letter-spacing:.14em}
:root[data-theme="dark"] input[type=text],:root[data-theme="dark"] input[type=date],
:root[data-theme="dark"] input[type=number],:root[data-theme="dark"] input[type=email],
:root[data-theme="dark"] input[type=tel],:root[data-theme="dark"] input[type=search],
:root[data-theme="dark"] input[type=password],:root[data-theme="dark"] select,
:root[data-theme="dark"] textarea{background:#0B0C0D}

/* À L'IMPRESSION, TOUJOURS CLAIR. Le bureau est désormais en sombre en
   permanence : sans cette palette, le planning et les listes sortaient
   en texte pâle sur papier blanc (les fonds ne s'impriment pas). */
@media print{
  :root,:root[data-theme="dark"]{color-scheme:light;
    --paper:#FFFFFF; --surface:#FFFFFF; --surface-2:#F1F4F6; --surface-3:#E6EBEE;
    --ink:#0F1417; --ink-2:#3A444B; --muted:#4A555C;
    --line:#C4CCD1; --line-strong:#737D84;
    --accent:#0074AB; --accent-ink:#FFFFFF; --accent-soft:#EAF4FA; --accent-line:#8CC3E0;
    --accent-strong:#0074AB;
    --ok:#1E6E4A; --ok-soft:#DCEEE3; --warn:#8A6410; --warn-soft:#F5EBD3; --warn-ink:#FFFFFF;
    --alert:#A3402E; --alert-soft:#F6E1DC; --alert-ink:#FFFFFF; --shadow:none}
  body{background:#FFFFFF}
}

/* ==================================================================
   PAGES DES OCCUPANTS — finition (1.62)
   Même monde que le bureau, en clair : le cyan de la charte, le plan
   en filigrane, des feuilles blanches posées dessus. Tout est limité au
   thème clair, c'est-à-dire aux seules pages de l'occupant : les
   créneaux de l'onglet du bureau ne bougent pas.
   ================================================================== */
@media screen{
  /* Le bandeau porte le quadrillage du plan, en blanc, et une
     profondeur de cyan : la marque de l'entreprise, pas un aplat. */
  :root[data-theme="light"] .occ-head{position:relative;overflow:hidden;
    padding:24px 20px 26px;
    /* Texte blanc ≥ 4,5:1 partout, trait de la grille compris : le cyan
       ne s'éclaircit pas au-delà de celui de la marque. */
    background:
      linear-gradient(rgba(255,255,255,.07) 1px,transparent 1px),
      linear-gradient(90deg,rgba(255,255,255,.07) 1px,transparent 1px),
      linear-gradient(160deg,#0074AB 0%,#006A9D 55%,#005E8C 100%);
    background-size:24px 24px,24px 24px,auto;
    background-color:#0074AB;
    box-shadow:0 10px 30px -18px rgba(0,60,90,.55)}
  :root[data-theme="light"] .occ-marque{box-shadow:0 6px 16px -8px rgba(0,30,50,.45)}
  :root[data-theme="light"] .occ-head h1{font-size:var(--t-xl);letter-spacing:-.02em}
  :root[data-theme="light"] .occ-head p{opacity:1}

  /* Les feuilles posées sur le plan. */
  :root[data-theme="light"] .occ-head ~ .wrap .panel,
  :root[data-theme="light"] .day,
  :root[data-theme="light"] .recap{
    box-shadow:0 1px 2px rgba(15,20,23,.05),0 10px 28px -16px rgba(0,60,90,.28)}

  /* L'étape en cours, à la couleur du bouton. */
  :root[data-theme="light"] .steps i.on{background:none;color:var(--accent-strong);font-weight:700}
  :root[data-theme="light"] .steps i[aria-current]{background:var(--accent-strong);color:#FFFFFF;
    border-radius:100px;padding:4px 12px}

  /* La journée : une en-tête qui se lit d'un coup d'œil. */
  :root[data-theme="light"] .day{border-color:#CFE3EF}
  :root[data-theme="light"] .day-h{background:linear-gradient(180deg,#F2F8FC,#E8F2F9);
    border-bottom-color:#CFE3EF;padding:12px 14px;font-weight:800;font-size:var(--t-md);
    color:var(--ink)}
  :root[data-theme="light"] .day-h em{font-size:var(--t-xs);font-weight:700;color:var(--accent);
    background:#FFFFFF;border:1px solid #CFE3EF;border-radius:100px;padding:2px 10px;white-space:nowrap}
  :root[data-theme="light"] .slots{gap:10px;padding:14px}

  /* Le créneau : une carte qui répond au doigt. */
  :root[data-theme="light"] .slot{border-radius:10px;padding:14px 14px 12px;
    transition:border-color .15s,background-color .15s,box-shadow .2s,transform .2s cubic-bezier(.2,.8,.2,1)}
  :root[data-theme="light"] .slot:hover:not([disabled]){transform:translateY(-1px);
    box-shadow:0 8px 18px -12px rgba(0,60,90,.45)}
  :root[data-theme="light"] .slot[aria-pressed=true]{background:#E3F1FA;
    box-shadow:inset 0 0 0 2px var(--accent),0 8px 18px -12px rgba(0,60,90,.45)}
  /* La coche devient une pastille pleine, lisible au soleil. */
  :root[data-theme="light"] .slot .slot-coche{right:10px;bottom:10px;width:24px;height:24px;
    border-radius:50%;background:var(--accent-strong);color:#FFFFFF;font-size:var(--t-sm);
    display:flex;align-items:center;justify-content:center}

  /* La confirmation : le moment que l'occupant relira devant sa porte. */
  :root[data-theme="light"] .recap{position:relative;overflow:hidden;padding:20px;
    background:
      radial-gradient(420px 200px at 100% 0%,rgba(0,116,171,.10),transparent 70%),
      #F3F9FD;
    border-color:#BFDDEE}
  :root[data-theme="light"] .recap .when{font-weight:800;letter-spacing:-.015em}

  /* Le bouton principal : la forme du tableau de bord, une phrase
     plutôt que des capitales. */
  :root[data-theme="light"] .barre-action .btn.primary{border-radius:10px;font-family:var(--f-display);
    font-weight:800;letter-spacing:.01em;
    box-shadow:0 10px 24px -12px rgba(0,90,135,.7)}
  :root[data-theme="light"] .barre-action .btn[disabled]{box-shadow:none}
}
@media screen and (prefers-reduced-motion:reduce){
  :root[data-theme="light"] .slot{transition:none}
  :root[data-theme="light"] .slot:hover:not([disabled]){transform:none}
}
