/* Thème : clair par défaut ; sombre si le système le demande (sauf choix « Clair ») ou si
   l'utilisateur a choisi « Sombre » (data-theme sur <html>, mémorisé dans le navigateur).
   --sky-* et --glow habillent le fond de la page publique : en sombre, une nuit étoilée en trois
   plans (grain profond, plan moyen, premier plan, en tuiles) ; en clair, des orbites en traits fins,
   avec une lueur d'aube. Fichiers SVG faits par tools/ciel.py. */
:root {
  --bg: #f6f7f9; --fg: #1d1f23; --muted: #6b7280; --card: #fff; --line: #e3e6ea;
  --ok: #1f9d55; --amber: #d98c00; --orange: #e5661b; --down: #d11a2a; --maint: #2563eb; --unk: #8a8f98;
  --glow: linear-gradient(#e3e9fb, transparent);
  --sky-back: url("ciel-clair-orbites.svg"); --sky-back-size: cover; --sky-front: none; --sky-front-size: auto;
  --link: #1d4ed8; --none: #dfe3e8;
  --pill-mix: #000 28%; --pill-fg: #fff;
  --ev-tint: 7%; --ev-band: 20%;
  --wm-edge: #00003e; --wm-0: #00003e; --wm-1: #00003e; --wm-2: #00003e; --wm-3: #00003e; --wm-4: #00003e;
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0d0f1a; --fg: #e8eaee; --muted: #9aa0a9; --card: #171a26; --line: #2a2e40; color-scheme: dark;
    --glow: linear-gradient(#1a1f55, transparent);
    --sky-back: url("ciel-sombre-fond.svg"), url("ciel-sombre-moyen.svg"); --sky-back-size: 256px 256px, 520px 520px;
    --sky-front: url("ciel-sombre-avant.svg"); --sky-front-size: 730px 730px;
    --link: #9db8ff; --none: #2b3042;
    --pill-mix: #fff 20%; --pill-fg: #0d0f1a;
    --ev-tint: 12%; --ev-band: 30%;
    --wm-edge: #5d78ba; --wm-0: #fbe7d0; --wm-1: #f6dfcf; --wm-2: #eed4ce; --wm-3: #ecd0ce; --wm-4: #d3e2ec;
    --logo-disc: none; --logo-planet: block;
  }
}
:root[data-theme="dark"] {
  --bg: #0d0f1a; --fg: #e8eaee; --muted: #9aa0a9; --card: #171a26; --line: #2a2e40; color-scheme: dark;
  --glow: linear-gradient(#1a1f55, transparent);
  --sky-back: url("ciel-sombre-fond.svg"), url("ciel-sombre-moyen.svg"); --sky-back-size: 256px 256px, 520px 520px;
  --sky-front: url("ciel-sombre-avant.svg"); --sky-front-size: 730px 730px;
  --link: #9db8ff; --none: #2b3042;
  --pill-mix: #fff 20%; --pill-fg: #0d0f1a;
  --ev-tint: 12%; --ev-band: 30%;
  --wm-edge: #5d78ba; --wm-0: #fbe7d0; --wm-1: #f6dfcf; --wm-2: #eed4ce; --wm-3: #ecd0ce; --wm-4: #d3e2ec;
  --logo-disc: none; --logo-planet: block;
}
/* Typographie de la charte graphique : Inter pour le texte, Montserrat pour les titres. Les
   fichiers sont embarqués dans le binaire et servis par capcom (voir fonts/LICENCE.txt), jamais
   chargés depuis un tiers. La télémétrie et les consoles gardent la chasse fixe du système. */
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("fonts/inter-400.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("fonts/inter-500.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("fonts/inter-600.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("fonts/inter-700.woff2") format("woff2"); }
@font-face { font-family: "Montserrat"; font-style: normal; font-weight: 500; font-display: swap; src: url("fonts/montserrat-500.woff2") format("woff2"); }
@font-face { font-family: "Montserrat"; font-style: normal; font-weight: 600; font-display: swap; src: url("fonts/montserrat-600.woff2") format("woff2"); }
@font-face { font-family: "Montserrat"; font-style: normal; font-weight: 700; font-display: swap; src: url("fonts/montserrat-700.woff2") format("woff2"); }
:root {
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-head: "Montserrat", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
* { box-sizing: border-box; }
body { margin: 0; font: 16px/1.5 var(--font-body); background: var(--bg); color: var(--fg); }
h1, h2, h3, .banner, .brandline, .adminbar .brand { font-family: var(--font-head); }
h2, h3 { font-weight: 600; }
/* Un état affiché dans un titre (en-tête de section, page d'un service) reste du texte courant. */
h1 .state, h2 .state { font-family: var(--font-body); }
/* Un bouton posé dans un titre (« + Nouvelle » de l'admin) garde la taille et la police d'un bouton. */
h1 .btn, h1 button, h2 .btn, h2 button { font: 600 0.95rem/1.5 var(--font-body); vertical-align: middle; margin-left: 8px; }
header.top, main, footer { max-width: 860px; margin: 0 auto; padding: 0 16px; }
header.top { padding-top: 24px; }
h1 { font-size: 1.5rem; margin: 0 0 12px; }
h1 .sub { font-weight: 500; color: var(--muted); font-size: 1rem; margin-left: 8px; }
/* Page publique : le ciel couvre tout le fond et reste fixe pendant que le contenu défile
   par-dessus : des orbites en clair, en sombre trois plans d'étoiles de pas différents
   pour qu'on ne voie pas le motif se répéter ; une lueur en haut de page. Les cartes, opaques,
   passent devant. */
body.public, body.admin { position: relative; background-image: var(--glow); background-repeat: no-repeat; background-size: 100% 190px; }
body.public::before, body.public::after, body.admin::before, body.admin::after { content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0; }
body.public::before, body.admin::before { background-image: var(--sky-back); background-size: var(--sky-back-size); background-position: center; }
body.public::after, body.admin::after { background-image: var(--sky-front); background-size: var(--sky-front-size); background-position: center; }
body.public header.top, body.public main, body.public footer, body.public .stale, body.admin main, body.admin footer { position: relative; z-index: 1; }
/* Lisibilité : aucune étoile contre un texte. Dans toute la colonne de contenu, en-tête compris,
   les étoiles sont atténuées par un même voile aux bords fondus, sans raccord : en-tête et page
   ne laissent pas leurs marges internes déborder (flow-root), le ciel ne passe pas entre eux
   (15 octobre 2026 ; l'en-tête opaque coupait le ciel en rectangle). Les pastilles sont pleines.
   Le ciel reste entier dans les marges. */
body.public header.top, body.public main, body.admin main { display: flow-root; }
body.public header.top, body.public main, body.public footer, body.admin main, body.admin footer { --veil: color-mix(in srgb, var(--bg) 72%, transparent); background: linear-gradient(90deg, transparent, var(--veil) 22px, var(--veil) calc(100% - 22px), transparent); }
/* L'en-tête, sous la lueur d'aube : son voile est un calque du fond lui-même (lueur comprise),
   à la même opacité et aux mêmes bords fondus. La couleur est celle des marges ; seules les
   étoiles sont atténuées, et la colonne ne se détache pas du haut de la page. */
body.public header.top { background: none; }
body.public header.top::before { content: ""; position: absolute; inset: 0; z-index: -1; opacity: .72; pointer-events: none;
  background: var(--glow) top / 100% 190px no-repeat, var(--bg);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 22px, #000 calc(100% - 22px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 22px, #000 calc(100% - 22px), transparent); }
body.public .chip { background: var(--card); }
.brandbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.brandbar h1 { margin: 0; }
/* Le logo : une seule mise en page, celle de la page 5 de la charte (mesurée au pixel), quel que
   soit le thème. Tout se règle sur --logo-d, le diamètre du disque : le mot fait la moitié du
   disque en hauteur, commence 0,286 diamètre sous son haut et 0,181 après lui.
   - Clair : la planète sur son disque bleu nuit (apple-touch-icon.png, fichier officiel).
   - Sombre (blocs sombres en tête de feuille) : le fond tient lieu de disque. La planète nue
     (embleme.png, fichier officiel, 885 × 431 ; la planète y occupe 760 × 418 px à partir de
     x = 64, y = 0) prend exactement la place et la taille qu'elle a dans le disque (180 px de
     côté ; la planète y occupe 138 × 76 px à partir de x = 21, y = 52).
   Changer de thème ne déplace rien : seul le disque apparaît ou disparaît. --logo-sub-top pose le
   sous-titre sur la ligne de base du mot, aux 3/4 de sa hauteur (décalage mesuré dans le
   navigateur, moins 5,5 px pour la police du sous-titre). */
:root { --logo-d: 64px; --logo-disc: block; --logo-planet: none; --logo-sub-top: calc(var(--logo-d) * 0.1613 - 5.5px); }
.brand { display: inline-flex; align-items: flex-start; gap: calc(var(--logo-d) * 0.181); min-height: var(--logo-d); color: inherit; text-decoration: none; }
.brand img.logo-disc { display: var(--logo-disc); width: var(--logo-d); height: var(--logo-d); }
.brand img.logo-planet {
  display: var(--logo-planet); width: calc(var(--logo-d) * 138 / 180 * 885 / 760); height: auto;
  margin: calc(var(--logo-d) * 52 / 180) calc(var(--logo-d) * (180 - 21 - 138 * 821 / 760) / 180) 0 calc(var(--logo-d) * (21 - 138 * 64 / 760) / 180);
}
.theme { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--card); }
.theme button { border: 0; border-radius: 0; background: none; color: var(--muted); padding: 4px 10px; font-size: 0.8rem; }
.theme button + button { border-left: 1px solid var(--line); }
.theme button[aria-pressed="true"] { background: var(--bg); color: var(--fg); font-weight: 600; }
.theme button:focus-visible { outline: 2px solid var(--maint); outline-offset: -2px; }
/* Heure de référence (Paris ou UTC) : même allure que le thème, mais ce sont des liens, le serveur
   note le choix. Les deux réglages restent groupés à droite du bandeau. */
.prefs { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px 8px; }
.theme a { color: var(--muted); padding: 4px 10px; font-size: 0.8rem; text-decoration: none; }
.theme a + a { border-left: 1px solid var(--line); }
.theme a[aria-current="true"] { background: var(--bg); color: var(--fg); font-weight: 600; }
.theme a:focus-visible { outline: 2px solid var(--maint); outline-offset: -2px; }
/* Écart à un instant de référence (« T+ 1:26 ») et heure d'une mise à jour : chiffres alignés,
   comme sur une console, jamais coupés en fin de ligne. */
/* Lien discret vers le flux d'un service, sous sa description. */
.follow { margin: 0 0 10px; font-size: 0.85rem; }
.follow a { color: var(--muted); }
.chrono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.92em; white-space: nowrap; }
.update .chrono { color: var(--muted); margin-right: 4px; }
.telemetry { margin: 8px 2px 0; color: var(--muted); font: 0.8rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
/* Sur un écran étroit la ligne se coupe entre deux mentions, jamais au milieu d'une. */
.telemetry span { white-space: nowrap; }
h2 { font-size: 1.1rem; margin: 28px 0 10px; }
.banner { padding: 14px 16px; border-radius: 10px; color: #fff; font-weight: 600; }
.st-operational { --c: var(--ok); } /* Ordre de gravité (16 octobre 2026) : partiellement indisponible (une partie touchée) en ambre,
   moins appuyé que dégradé (tout le monde touché) en orange. */
.st-degraded { --c: var(--orange); } .st-partial { --c: var(--amber); }
.st-down { --c: var(--down); } .st-maintenance { --c: var(--maint); } .st-unknown { --c: var(--unk); }
.banner { background: var(--c); }
/* Seconde ligne du bandeau : ce qui s'écarte quand les services, eux, vont bien. */
.banner .aside { display: block; margin-top: 4px; font: 400 0.88rem/1.4 var(--font-body); opacity: 0.95; }
.rows { list-style: none; margin: 0; padding: 0; background: var(--card); border: 1px solid var(--line); border-radius: 10px; }
.row + .row { border-top: 1px solid var(--line); }
/* Safari iOS ignore le clic si <summary> n'est pas en display: list-item : la mise en page va dans un span interne. */
summary { display: list-item; list-style: none; padding: 12px 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
summary::-webkit-details-marker { display: none; }
summary .sumline { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
/* Lignes dépliables : un chevron devant le nom dit qu'elles s'ouvrent, comme celui des sections
   en plus léger. Il est posé dans la marge du résumé (--lead), pour qu'un nom sur deux lignes
   reste aligné ; ce qui se déplie s'aligne sous le nom. Au téléphone la marge est plus serrée :
   chaque pixel pris au nom en fait passer un de plus sur deux lignes. */
.row > details { --lead: 32px; --chev: 15px; }
.row > details > summary { position: relative; padding-left: var(--lead); }
.row > details > summary::before { content: ""; position: absolute; left: var(--chev); top: 20px; width: 5px; height: 5px; border: solid var(--muted); border-width: 0 1.5px 1.5px 0; transform: rotate(-45deg); opacity: .8; }
.row > details[open] > summary::before { transform: translate(1px, -2px) rotate(45deg); }
.row > details > .desc, .row > details > .more, .row > details > .probes { margin-left: var(--lead); }
.row > details > .objects { padding-left: var(--lead); }
/* Pastilles montrées dans une ligne (Core, Edge, Collecte dans Équipements actifs) : alignées sur
   la description, au-dessus de la liste des équipements. */
.row > details > .facets { margin: 0 14px 10px var(--lead); gap: 6px; }
.row > details > .facets .chip { font-size: 0.8rem; padding: 2px 10px; }
@media (max-width: 480px) {
  .row > details { --lead: 24px; --chev: 10px; }
  .row > details > summary .sumline { gap: 10px; }
}
.state { color: var(--c); font-weight: 600; white-space: nowrap; font-size: 0.9rem; }
.state::before { content: "●"; margin-right: 6px; }
.desc { margin: 0 14px 10px; color: var(--muted); font-size: 0.9rem; }
.muted { color: var(--muted); }
.objects { list-style: none; margin: 0 0 10px; padding: 0 14px 0 28px; font-size: 0.9rem; }
.objects li { padding: 5px 0; }
.objects li + li { border-top: 1px dashed var(--line); }
.filters { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 10px; }
/* Parentes d'une section par nature : opérateurs de collecte à gauche, partenaires à droite,
   chaque groupe sous son intitulé ; sur un écran étroit, les partenaires passent dessous. */
.parentgroups { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 28px; margin: 0 0 10px; }
.parentgroups .filters { margin: 0; align-content: flex-start; }
.parentgroups .filters.partners { justify-content: flex-end; }
.grouphead { flex-basis: 100%; color: var(--muted); font-size: 0.78rem; }
.parentgroups .partners .grouphead { text-align: right; }
body.searching .grouphead { display: none; }
@media (max-width: 600px) {
  .parentgroups { grid-template-columns: minmax(0, 1fr); }
  .parentgroups .filters.partners { justify-content: flex-start; }
  .parentgroups .partners .grouphead { text-align: left; }
}
.chip { padding: 4px 10px; border-radius: 999px; font-size: 0.85rem; border: 1px solid var(--c); color: var(--c); white-space: nowrap; }
/* Cartes des incidents et des maintenances (cahier des charges, 3.7) : fond teinté et contour fin à
   la couleur de l'événement, sans liseré ; trois zones, le bandeau de console (voyant, genre,
   horloge, puis où en est l'événement), le second bandeau plus foncé (titre et services), le
   journal. Les teintes sont celles du thème (--ev-tint, --ev-band). */
.incident, .maintenance { --c: var(--amber); margin-bottom: 10px; padding: 0 14px 12px; background: color-mix(in srgb, var(--c) var(--ev-tint), var(--card)); border: 1px solid color-mix(in srgb, var(--c) 45%, var(--line)); border-radius: 10px; overflow: hidden; }
/* Couleur du niveau, celle de l'état imposé (16 octobre 2026) : impact partiel en ambre (par
   défaut), performances dégradées en orange, panne totale en rouge. */
.incident.impact-minor { --c: var(--orange); }
.incident.impact-critical { --c: var(--down); }
.incident.impact-none { --c: var(--unk); }
.maintenance { --c: var(--maint); }
.incident > :last-child, .maintenance > :last-child { margin-bottom: 0; }
.eventbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 1px 12px; margin: 0 -14px; padding: 7px 14px 6px; border-bottom: 1px solid color-mix(in srgb, var(--c) 28%, var(--line)); font: 600 0.72rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0.1em; text-transform: uppercase; color: color-mix(in srgb, var(--c), var(--pill-mix)); }
.eventbar .kind::before { content: "●"; margin-right: 7px; }
.eventbar .chrono { font-size: 1em; letter-spacing: 0.02em; text-transform: none; }
.eventbar .when { flex-basis: 100%; font-weight: 400; letter-spacing: 0.02em; text-transform: none; color: var(--muted); }
.eventhead { margin: 0 -14px 10px; padding: 9px 14px 10px; background: color-mix(in srgb, var(--c) var(--ev-band), var(--card)); border-bottom: 1px solid color-mix(in srgb, var(--c) 28%, var(--line)); }
.eventhead:last-child { margin-bottom: -12px; border-bottom: 0; }
.eventhead h3 { margin: 0; font-size: 1rem; }
.eventhead + p:not([class]) { margin-top: 0; }
/* Les services d'une carte : des liens à la couleur de l'événement, un par ligne, en colonnes
   sur un écran large ; ni intitulé visible ni repère (un chevron faisait penser à un dépliant). */
.svclist { list-style: none; margin: 5px 0 0; padding: 0; columns: 2 15rem; column-gap: 20px; font-size: 0.88rem; line-height: 1.55; }
.svclist li { break-inside: avoid; }
body.public main .svclist a { color: color-mix(in srgb, var(--c), var(--pill-mix)); text-decoration-color: color-mix(in srgb, var(--c) 45%, transparent); text-underline-offset: 2px; }
.meta { color: var(--muted); font-size: 0.85rem; margin: 0 0 8px; }
.update { margin: 6px 0; font-size: 0.95rem; }
/* Qui écrit : 🤖 publié automatiquement, 👤 par l'équipe. */
.who { font-size: 0.85em; cursor: help; }
footer { color: var(--muted); font-size: 0.85rem; padding: 30px 16px; }
footer a { color: inherit; }
/* Liens vers les autres sites de l'opérateur : une ligne qui se replie sans séparateur orphelin. */
footer .sites { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 0 0 10px; font-size: 0.9rem; }
footer .sites a { color: var(--fg); }
/* Nom à gauche, état à droite sur le même bord que l'état de l'étiquette (colonne lisible). */
.objline { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.objline .name { min-width: 0; overflow-wrap: anywhere; }
.objects .state { font-weight: 500; }
.objects .state.off::before { content: "○"; }
/* Mention discrète à côté du nom d'un objet (« secours »). */
.note { margin-left: 6px; padding: 1px 7px; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); font-size: 0.72rem; white-space: nowrap; }
/* Rôles et site d'un équipement (cahier des charges, 3.3) ; sur un écran étroit, sur une seconde
   ligne, sous le nom. */
.objline .place { margin-left: 2px; }
@media (max-width: 480px) { .objline .place { display: block; margin: 3px 0 0; } .objline .place .role { margin: 0 4px 0 0; } }
.objline .role { display: inline-block; margin-left: 4px; padding: 0 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); font-size: 0.72rem; text-transform: lowercase; white-space: nowrap; }
.objline .site { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }
.stale { max-width: 860px; margin: 0 auto 24px; padding: 8px 16px; color: var(--amber); font-size: 0.85rem; }
.probes { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 8px; }
.probe { font-size: 0.78rem; padding: 2px 8px; border-radius: 999px; background: color-mix(in srgb, var(--c) 15%, transparent); color: var(--c); border: 1px solid color-mix(in srgb, var(--c) 40%, transparent); }
.probe small { opacity: 0.8; }
.objects .probes { padding-left: 0; }

/* ---- admin ---- */
body.admin main { padding-bottom: 40px; }
.narrow { max-width: 420px; margin: 60px auto; padding: 0 16px; }
.adminbar { background: var(--card); border-bottom: 1px solid var(--line); padding: 8px 16px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; position: sticky; top: 0; z-index: 2; }
/* « capcom » n'est pas le logo de la page publique : pas de hauteur de disque (min-height de
   .brand), sinon la barre gagnait 64 px de vide. Les liens restent sur la même ligne et défilent
   horizontalement s'ils ne tiennent pas. */
.adminbar { flex-wrap: nowrap; }
.adminbar .brand { display: inline-block; min-height: 0; font-weight: 700; text-decoration: none; color: var(--fg); }
.adminbar nav { flex: 1 1 auto; min-width: 0; display: flex; gap: 4px; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; }
.adminbar nav a { color: var(--muted); text-decoration: none; padding: 6px 8px; border-radius: 6px; font-size: 0.92rem; }
.adminbar nav a.on { color: var(--fg); background: var(--bg); font-weight: 600; }
.flash { padding: 10px 14px; border-radius: 8px; margin: 12px 0; }
.flash.ok { background: color-mix(in srgb, var(--ok) 15%, transparent); color: var(--ok); }
.flash.err { background: color-mix(in srgb, var(--down) 15%, transparent); color: var(--down); }
.flash.info { background: color-mix(in srgb, var(--maint) 12%, transparent); }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 14px; margin: 14px 0; }
.card h2 { margin-top: 0; }
/* Clés d'accès : la liste (Mon compte, fiche d'un utilisateur) et le bouton de connexion. */
ul.keys { list-style: none; margin: 0 0 10px; padding: 0; }
ul.keys li { padding: 8px 0; border-top: 1px solid var(--line); }
ul.keys li:first-child { border-top: 0; }
.card.passkey #passkey-login { width: 100%; }
.card.passkey p { margin: 8px 0 0; }
label { display: block; margin: 10px 0; font-size: 0.95rem; }
label input:not([type=checkbox]), label select, label textarea { display: block; width: 100%; margin-top: 4px; padding: 10px; font: inherit; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--fg); }
label.box { display: flex; gap: 8px; align-items: center; margin: 6px 0; }
label.box input { width: auto; margin: 0; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
@media (max-width: 560px) { .grid2 { grid-template-columns: 1fr; } }
button, .btn { display: inline-block; font: inherit; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--line); background: var(--card); color: var(--fg); text-decoration: none; cursor: pointer; }
button.primary, .btn.primary { background: var(--maint); border-color: var(--maint); color: #fff; font-weight: 600; }
button.small, .btn.small { padding: 4px 10px; font-size: 0.85rem; }
button.link { border: 0; background: none; color: inherit; text-decoration: underline; padding: 0; }
form.inline { display: inline; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 12px 0; align-items: center; }
h1 { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
h1 small { font-size: 0.8rem; }
fieldset.boxes, details.boxes { border: 1px solid var(--line); border-radius: 8px; padding: 8px 12px; margin: 10px 0; }
fieldset.boxes legend, details.boxes summary { color: var(--muted); font-size: 0.9rem; cursor: pointer; }
fieldset.boxes label.box, details.boxes label.box { display: inline-flex; margin: 4px 12px 4px 0; }
table.list { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--line); border-radius: 10px; font-size: 0.92rem; }
table.list td { padding: 8px 10px; border-top: 1px solid var(--line); vertical-align: top; }
table.list tr:first-child td { border-top: 0; }
/* Tableau à deux colonnes égales (page Réglages : un seuil, sa valeur). */
table.halves td:first-child { width: 50%; }
.rows .row > a { display: flex; justify-content: space-between; gap: 12px; padding: 12px 14px; color: inherit; text-decoration: none; }
.rows .row > .objline { padding: 12px 14px 0; }
.rows .row > .objline:last-child { padding-bottom: 12px; }
.rows .objline a { color: inherit; text-decoration: none; }
.rows .objline a:hover, .rows .objline a:focus-visible { text-decoration: underline; }
.rows .row > strong, .rows .row > p { display: block; padding: 0 14px; }
.rows .row > strong { padding-top: 10px; }
.rows .row > strong + .muted { display: block; padding: 0 14px; }
/* Ligne du pupitre (nom, raison, pastilles de sondes) : tout part du même bord gauche. Les
   pastilles restent des boîtes entières qui passent à la ligne une par une, jamais coupées en deux
   (la règle « p » ci-dessus les remettait en texte courant). */
.rows .row > .desc { margin: 4px 0 8px; }
.rows .row > .probes { display: flex; margin: 6px 0 12px; }
.st-none { --c: var(--unk); } .st-minor { --c: var(--orange); } .st-major { --c: var(--amber); } .st-critical { --c: var(--down); }
code { background: var(--bg); padding: 2px 6px; border-radius: 4px; }
.selector { display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px; align-items: end; margin: 6px 0 10px; }
@media (max-width: 560px) { .selector { grid-template-columns: 1fr; } }
.selector label { margin: 0; }
.selector small { grid-column: 1 / -1; }
details.sub { margin-top: 6px; }
details.sub summary { color: var(--muted); font-size: 0.9rem; cursor: pointer; }
/* Sections repliables (page publique) : l'en-tête porte le titre et l'indicateur de la section ;
   repliée si tout y est opérationnel, dépliée sinon. Le chevron est dessiné en CSS (pas de glyphe
   qui change d'une plate-forme à l'autre). L'indicateur s'aligne sur le bord des états des lignes. */
details.group { margin-top: 20px; }
details.group > .secdesc { margin: 2px 0 10px; color: var(--muted); font-size: 0.92rem; }
details.group > summary { padding: 4px 15px 4px 0; border-radius: 8px; }
details.group > summary:focus-visible { outline: 2px solid var(--maint); outline-offset: 2px; }
details.group > summary h2 { margin: 0; flex-wrap: wrap; row-gap: 2px; }
details.group > summary .name { display: inline-flex; align-items: center; gap: 9px; }
details.group > summary .name::before { content: ""; flex: none; width: 6px; height: 6px; margin: 0 3px 0 2px; border: solid var(--muted); border-width: 0 2px 2px 0; transform: rotate(-45deg); }
details.group[open] > summary .name::before { transform: translate(1px, -2px) rotate(45deg); }
details.group > summary .state { font-size: 0.9rem; }
details.group[open] > summary { margin-bottom: 8px; }
/* Téléphone : les titres les plus longs (« Portail & services MilkyWan ») ne laissent pas la
   place à l'étiquette et au décompte sur la même ligne. Ils passent sous le titre, alignés sur
   lui, pour toutes les sections plutôt que pour certaines seulement. */
@media (max-width: 480px) {
  details.group > summary h2 { flex-direction: column; align-items: flex-start; gap: 3px; }
  details.group > summary .state { margin-left: 22px; }
}
/* ---- Pages publiques : menu, recherche, page d'un service, historique, à propos ---- */
[hidden] { display: none !important; }
body.public main a { color: var(--link); }
body.public main a.chip { color: var(--c); text-decoration: none; }
body.public main a.chip:hover, body.public main a.chip:focus-visible { background: color-mix(in srgb, var(--c) 14%, var(--card)); }
/* Le titre de marque est un <h1> sur la page d'état et un <p> ailleurs (le <h1> y est le titre de
   la page) : même rendu, nom et sous-titre centrés sur la hauteur de l'emblème. */
.brandline { font-size: 1.5rem; font-weight: 700; }
.brandline .sub { font-weight: 500; color: var(--muted); font-size: 1rem; }
.brandbar h1, .brandline { display: flex; align-items: center; flex-wrap: wrap; gap: 0 8px; margin: 0; }
/* Le sous-titre se pose sur la ligne de base du mot « MilkyWan » (voir .brand ; la règle .sub des
   listes de chaînes, plus bas, ne doit pas lui donner ses marges). */
.brandbar h1 .sub, .brandline .sub { margin: 0; position: relative; top: var(--logo-sub-top); }
/* « capcom », le nom de l'outil, en caractères de console devant « État des services ». */
.sub .tool { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.95em; }
/* Le mot « MilkyWan » du logo (tracé officiel, voir pub_wordmark) : bleu nuit de la charte en
   clair ; en sombre, le liseré et le dégradé du site. Sa hauteur suit la planète (voir .brand). */
.wordmark { display: block; height: calc(var(--logo-d) * 0.4995); width: auto; margin-top: calc(var(--logo-d) * 0.286); }
.wordmark .wm-edge { fill: var(--wm-edge); }
.wordmark stop:nth-child(1) { stop-color: var(--wm-0); }
.wordmark stop:nth-child(2) { stop-color: var(--wm-1); }
.wordmark stop:nth-child(3) { stop-color: var(--wm-2); }
.wordmark stop:nth-child(4) { stop-color: var(--wm-3); }
.wordmark stop:nth-child(5) { stop-color: var(--wm-4); }
.brandbar { margin-bottom: 8px; }
.pubnav { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 0 0 12px; font-size: 0.9rem; }
.pubnav a { color: var(--muted); text-decoration: none; padding: 2px 0; border-bottom: 2px solid transparent; }
.pubnav a:hover, .pubnav a:focus-visible { color: var(--fg); }
.pubnav a[aria-current="page"] { color: var(--fg); font-weight: 600; border-bottom-color: currentColor; }
/* Recherche : dans l'en-tête, qui n'est pas remplacé à l'actualisation (la saisie reste en place). */
.search { display: flex; align-items: center; gap: 12px; margin: 12px 0 0; }
.search input { flex: 1; min-width: 0; padding: 9px 12px; font: inherit; color: var(--fg); background: var(--card); border: 1px solid var(--line); border-radius: 8px; }
.search input:focus-visible { outline: 2px solid var(--maint); outline-offset: 1px; }
.search .count { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }
.search .count:empty { display: none; }
.searching .row { border-top: 0; }
.searching .row:not([hidden]) ~ .row:not([hidden]) { border-top: 1px solid var(--line); }
body.public header.top { padding-bottom: 4px; }
.more { margin: 0 14px 12px; font-size: 0.88rem; }
/* Page d'un service, historique, à propos. */
main.page { padding-top: 14px; }
.crumb { margin: 0 0 6px; font-size: 0.85rem; color: var(--muted); }
.pagehead { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 16px; margin: 0 0 8px; }
.pagehead h1 { margin: 0; font-size: 1.6rem; line-height: 1.25; }
.pagehead .state { font-size: 1rem; }
.lead { margin: 0 0 12px; font-size: 1.05rem; }
.block { margin-top: 28px; }
.block > h2 { margin: 0 0 10px; }
.block > p { margin: 0 0 10px; }
/* Frise de 90 jours : une case par jour, à la couleur du pire état du jour. */
.st-none { --c: var(--none); }
.days { display: flex; gap: 2px; height: 56px; padding: 10px; background: var(--card); border: 1px solid var(--line); border-radius: 10px; }
.days i { flex: 1 1 0; min-width: 0; border-radius: 2px; background: var(--c); }
.daysaxis { display: flex; justify-content: space-between; margin: 4px 2px 0 !important; font-size: 0.78rem; color: var(--muted); }
.daysum { margin: 10px 0 0 !important; }
.troubled { list-style: none; margin: 8px 0 0; padding: 0; font-size: 0.9rem; display: grid; gap: 3px; }
.troubled li { display: flex; gap: 12px; align-items: baseline; }
.troubled .state { font-variant-numeric: tabular-nums; }
.legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 12px 0 0 !important; }
.legend .state { font-size: 0.8rem; font-weight: 500; }
.legend .st-none { color: var(--muted); }
.legend .st-none::before { color: var(--none); }
.filters.small { margin: 6px 0 0; padding: 0 14px 12px; gap: 6px; }
.filters.small .chip { font-size: 0.76rem; padding: 2px 8px; }
.rows.tech .row > .objline:last-child { padding-bottom: 12px; }
.rows.reach .row > .desc { margin: 2px 0 10px; }
/* À propos. */
.about p, .about .rules, .about dl { max-width: 46rem; }
/* Console de la salle de contrôle : le message de capcom, puis l'horloge de mission. */
.console { margin: 4px 0 18px; padding: 12px 14px; background: color-mix(in srgb, var(--c, var(--ok)) var(--ev-tint), var(--card)); border: 1px solid color-mix(in srgb, var(--c, var(--ok)) 45%, var(--line)); border-radius: 8px; font: 0.86rem/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.console p { margin: 0; max-width: none; }
.console .who { text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin-right: 8px; }
.console .who::after { content: " ›"; }
.console .clock { margin-top: 4px; color: var(--muted); font-size: 0.8rem; }
/* Tour de table « go / no go » : un poste par ligne, pointillés de conduite, réponse à droite. */
.poll { list-style: none; margin: 0 0 10px; padding: 6px 14px; max-width: 46rem; background: var(--card); border: 1px solid var(--line); border-radius: 10px; font: 0.86rem/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.poll li { display: flex; align-items: baseline; gap: 10px; padding: 5px 0; }
.poll .station { text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap; }
.poll .leader { flex: 1; min-width: 12px; border-bottom: 1px dotted var(--muted); opacity: 0.5; transform: translateY(-4px); }
.poll .call { font-weight: 700; color: var(--c, var(--fg)); white-space: nowrap; }
.poll .word { color: var(--c); white-space: nowrap; }
.poll .verdict { margin-top: 4px; padding-top: 9px; border-top: 1px solid var(--line); }
.poll .verdict .station { color: var(--muted); }
.poll .verdict .call { font-weight: 600; white-space: normal; text-align: right; }
.signoff { margin-top: 18px !important; color: var(--muted); font: 0.8rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; text-transform: uppercase; letter-spacing: 0.12em; }
.rules { margin: 0; padding-left: 1.3em; display: grid; gap: 8px; }
.states, .lexicon { display: grid; grid-template-columns: max-content 1fr; gap: 8px 18px; margin: 0 0 14px; }
.states dt, .lexicon dt, .states dd, .lexicon dd { margin: 0; }
.lexicon dt { font-weight: 600; }
@media (max-width: 520px) {
  /* Téléphone : l'état d'un poste « no go » passe sur sa propre ligne, aligné à droite. */
  .poll li { flex-wrap: wrap; row-gap: 0; }
  .poll .word { flex-basis: 100%; text-align: right; }
  .states, .lexicon { grid-template-columns: 1fr; gap: 2px; }
  .states dd, .lexicon dd { margin-bottom: 8px; }
  .days { height: 46px; padding: 8px; gap: 1px; }
}
/* Les chaînes de télévision sur la page du service : noms en colonnes, numéro discret devant. */
.sub { font-family: var(--font-head); font-size: 0.95rem; font-weight: 600; margin: 16px 0 6px; }
.chans { list-style: none; margin: 0; padding: 0; columns: 3 11rem; column-gap: 20px; font-size: 0.9rem; }
.chans li { display: flex; gap: 10px; padding: 2px 0; break-inside: avoid; overflow-wrap: anywhere; }
.chans .num { flex: 0 0 2em; text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Réponse au tour de table (« Go », « No go », « En attente ») : une étiquette pleine à la couleur
   de l'état, qui tient lieu de voyant à la place du rond. Le fond est la couleur de l'état, foncée
   d'un cran sur le thème clair et éclaircie sur le thème sombre (--pill-mix), pour que le texte,
   blanc ou sombre selon le thème (--pill-fg), garde un contraste d'au moins 4,5 dans chaque état.
   Rien à voir avec les pastilles des opérateurs (.chip), qui restent un contour. */
.gopill { display: inline-block; padding: 0 9px; border-radius: 999px; font: 700 0.7rem/1.75 var(--font-body); letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap; color: var(--pill-fg); background: var(--c); background: color-mix(in srgb, var(--c), var(--pill-mix)); }
.state.called::before { content: none; }
/* L'étiquette vient après le décompte, au bord droit : d'une section à l'autre, les étiquettes
   forment une colonne alignée sur le bord des états des lignes. Au téléphone, où l'ensemble passe
   sous le titre et s'aligne à gauche, elle reste devant (ordre du document). */
.state.called { display: inline-flex; align-items: center; gap: 9px; }
.state.called .gopill { order: 1; }
@media (max-width: 480px) {
  .state.called .gopill { order: 0; }
}

/* Service opérationnel avec un écart toléré (réserve) : le rond est à moitié plein. Il est
   dessiné, pas tiré d'un caractère, pour avoir la même taille partout ; la mention en clair est
   une .note posée près du nom. */
.state.waived::before { content: ""; display: inline-block; box-sizing: border-box; width: 0.84em; height: 0.84em; margin-right: 6px; vertical-align: -0.07em; border: 0.11em solid var(--c); border-radius: 50%; background: linear-gradient(90deg, var(--c) 50%, transparent 50%); }
h1 .note { font: 500 0.78rem/1.4 var(--font-body); vertical-align: middle; }
/* Abonnement : le champ piège reste hors de la vue (et des lecteurs d'écran), pas des robots. */
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.subscribe fieldset.boxes { background: var(--card); }
/* Formulaire d'abonnement : regroupements (opérateurs de collecte, partenaires) à part des services. */
.pickrow { display: flex; flex-wrap: wrap; align-items: center; gap: 0 12px; margin: 4px 0 8px; }
.pickhead { width: 100%; color: var(--muted); font-size: 0.82rem; }
fieldset.boxes label.box.group { font-weight: 600; }
fieldset.boxes label.box.all { display: flex; margin-bottom: 8px; color: var(--muted); }
