/* Minimal, self-contained styling for the ops console skeleton (issue 0129).
   Deliberately tiny — the real design pass lands with later ops issues. */
:root {
  --bg: #0f1419;
  --surface: #1a2027;
  --surface-2: #232b34;
  --ink: #e6edf3;
  --muted: #8b949e;
  --accent: #4c8dff;
  --up: #2ea043;
  --down: #6e7681;
  --starting: #d29922;
  --unhealthy: #f85149;
  --unknown: #6e7681;
  --border: #30363d;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--ink);
}
.shell { min-height: 100vh; }

/* login */
.login-card {
  max-width: 340px;
  margin: 12vh auto;
  padding: 32px;
}
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.brand { font-size: 22px; margin: 0; font-weight: 700; }
.brand span { color: var(--accent); }
.subtitle { color: var(--muted); margin: 4px 0 20px; }
form label { display: block; margin: 14px 0 4px; color: var(--muted); font-size: 13px; }
form input {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--ink);
  font-size: 15px;
}
.btn {
  margin-top: 20px;
  padding: 10px 16px;
  border: 0;
  border-radius: 8px;
  font-size: 15px;
  cursor: pointer;
}
.btn.primary { background: var(--accent); color: #fff; width: 100%; }
.btn.ghost { background: transparent; color: var(--muted); border: 1px solid var(--border); margin: 0; }
.error { color: var(--unhealthy); background: rgba(248,81,73,.12); padding: 8px 12px; border-radius: 8px; }

/* topbar */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 24px; border-bottom: 1px solid var(--border);
}
.tabs { margin-top: 6px; }
.tab { color: var(--muted); font-size: 13px; }
.tab.active { color: var(--ink); border-bottom: 2px solid var(--accent); padding-bottom: 4px; }
.topbar-right { display: flex; align-items: center; gap: 12px; }
.who { color: var(--muted); font-size: 13px; }
.inline { display: inline; }

/* content */
.content { padding: 24px; max-width: 900px; margin: 0 auto; }
.content h2 { font-size: 16px; color: var(--muted); font-weight: 600; }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.status-card { padding: 16px; }
/* Carte « Moteur de routing » (PRD 0016 lot 3) : même intérieur qu'une carte de statut, mais elle
   vit HORS de la grille `.cards` (elle porte ce que le moteur dit de lui-même, pas un conteneur)
   → sa marge haute la détache du rang de conteneurs. */
.routing-card { padding: 16px; margin-top: 16px; }
.status-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.cname { font-weight: 600; font-size: 14px; }
.status-meta { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 0; font-size: 13px; }
.status-meta dt { color: var(--muted); }
.status-meta dd { margin: 0; }
.tag { font-family: ui-monospace, SFMono-Regular, monospace; }

.pill {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: #fff;
}
.pill-up { background: var(--up); }
.pill-down { background: var(--down); }
.pill-starting { background: var(--starting); color: #1a1200; }
.pill-unhealthy { background: var(--unhealthy); }
.pill-unknown { background: var(--unknown); }

.banner { padding: 10px 14px; border-radius: 8px; margin-bottom: 16px; }
.banner.warn { background: rgba(210,153,34,.14); color: var(--starting); border: 1px solid rgba(210,153,34,.4); }
.hint { color: var(--muted); font-size: 13px; margin-top: 16px; }

/* ── live metrics (issue 0130) ─────────────────────────────────────────────── */
.conn {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 8px; border-radius: 999px; margin-left: 8px; vertical-align: middle;
}
.conn[data-state="live"] { background: rgba(46,160,67,.16); color: var(--up); }
.conn[data-state="connecting"] { background: rgba(210,153,34,.16); color: var(--starting); }
.conn[data-state="down"] { background: rgba(248,81,73,.16); color: var(--unhealthy); }

.metrics-host {
  display: flex; flex-wrap: wrap; gap: 24px; padding: 16px; margin-bottom: 16px;
}
.host-item { flex: 1 1 220px; min-width: 200px; }
.peak { font-size: 20px; font-weight: 700; font-family: ui-monospace, SFMono-Regular, monospace; }

.metrics-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px;
}
.metric-card { padding: 16px; }
.metric-head { margin-bottom: 12px; }
.gauge { margin-bottom: 14px; }
.gauge:last-child { margin-bottom: 0; }
.gauge-label { font-size: 13px; color: var(--muted); margin-bottom: 4px; display: flex; justify-content: space-between; }
.gauge-val { color: var(--ink); font-family: ui-monospace, SFMono-Regular, monospace; }

.bar {
  height: 8px; background: var(--surface-2); border-radius: 999px; overflow: hidden;
}
.bar-fill { height: 100%; width: 0; border-radius: 999px; transition: width .4s ease; }
.bar-fill.cpu { background: var(--accent); }
.bar-fill.mem { background: #a371f7; }
.bar-fill.disk { background: #2ea043; }

.spark {
  display: block; width: 100%; height: 32px; margin-top: 6px;
}
.spark .spark-cpu { stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.spark .spark-mem { stroke: #a371f7; stroke-width: 1.5; vector-effect: non-scaling-stroke; }

/* ── live logs (issue 0131) ─────────────────────────────────────────────────── */
.logs-content { max-width: 1100px; }
.logs-toolbar {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin: 12px 0 16px;
}
.logs-field { display: flex; flex-direction: column; gap: 4px; }
.logs-field > span { color: var(--muted); font-size: 12px; }
.logs-toolbar select, .logs-search {
  padding: 8px 10px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; color: var(--ink); font-size: 14px;
}
.logs-search { flex: 1 1 200px; min-width: 160px; }
.logs-toolbar .btn { margin-top: 0; background: var(--surface-2); color: var(--ink); border: 1px solid var(--border); }
.logs-toolbar .btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.logs-count { margin-left: auto; color: var(--muted); font-size: 12px; align-self: center; }

.logs-scrollback {
  margin: 0; height: 60vh; min-height: 320px; overflow-y: auto; overflow-x: auto;
  padding: 12px 14px; background: #0a0d11; border: 1px solid var(--border);
  border-radius: 12px; font: 12.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--ink); white-space: pre; tab-size: 4;
}
.log-line { white-space: pre-wrap; word-break: break-word; }
.log-line.log-warn { color: var(--starting); }
.log-line.log-error { color: var(--unhealthy); }

/* ── containers tab (issue 0132) ────────────────────────────────────────────── */
.btn.danger {
  margin-top: 0; background: rgba(248,81,73,.14); color: var(--unhealthy);
  border: 1px solid rgba(248,81,73,.4);
}
.btn.danger:hover { background: rgba(248,81,73,.24); }

.container-list { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.container-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 16px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; border-left: 4px solid var(--border);
}
.container-row.status-up { border-left-color: var(--up); }
.container-row.status-down { border-left-color: var(--down); }
.container-row.status-starting { border-left-color: var(--starting); }
.container-row.status-unhealthy { border-left-color: var(--unhealthy); }
.container-row.status-unknown { border-left-color: var(--unknown); }
.container-main { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.container-state { color: var(--muted); font-size: 12px; }
.container-actions { display: flex; align-items: center; gap: 12px; }

.toast {
  font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 999px;
}
.toast-ok { background: rgba(46,160,67,.16); color: var(--up); }
.toast-fail { background: rgba(248,81,73,.16); color: var(--unhealthy); }

/* ── history tab (issue 0132) ───────────────────────────────────────────────── */
.history-content { max-width: 1100px; }
.history-table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 13px; }
.history-table th, .history-table td {
  text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.history-table th { color: var(--muted); font-weight: 600; font-size: 12px; }
.history-table td.mono, .mono { font-family: ui-monospace, SFMono-Regular, monospace; }
.history-table td.detail { color: var(--muted); word-break: break-word; }
.history-table .pill { font-size: 10px; padding: 1px 8px; }
.empty { color: var(--muted); margin-top: 16px; }

/* ── data jobs tab (issue 0134) ─────────────────────────────────────────────── */
/* Une console de bureau qu'on lit vingt fois par jour : on assume la densité. Le resserrage est
   volontairement porté par `.data-content` et pas par `.card` / `.btn` — les mêmes cartes de job
   servent l'onglet Catalogue OSM, qui n'a pas demandé cette passe. */
.data-content { max-width: 900px; }
.data-content h2 { margin: 0 0 6px; }
.data-content .hint { margin-top: 6px; font-size: 12px; }
.data-content .card { padding: 14px 16px; margin-top: 10px; }
.data-content .btn { margin-top: 0; padding: 6px 12px; font-size: 13px; }
.data-content .job-head { margin-bottom: 8px; }
.data-content .job-final { margin-bottom: 10px; }
.job-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.job-pill { background: var(--unknown); }
.job-pill[data-state="RUNNING"] { background: var(--accent); }
.job-pill[data-state="COMPLETED"] { background: var(--up); }
.job-pill[data-state="FAILED"] { background: var(--unhealthy); }
.job-pill[data-state="INTERRUPTED"] { background: var(--starting); color: #1a1200; }
.job-pill[data-state="CANCELED"] { background: var(--starting); color: #1a1200; }
.job-pill[data-state="SKIPPED"] { background: var(--down); }

/* L'identité du lancement en cours, à lire à côté de son état : c'est ce qui permet de savoir
   qu'on regarde CELUI qu'un terminal a démarré. */
.job-run-id { margin-left: 8px; font-size: 12px; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* Le temps restant, à côté de la pastille : « reste ≥ 2h10 ». Le « ≥ » vient du backend et dit
   qu'au moins un job n'a rien à estimer — c'est une borne basse, jamais une promesse. */
.job-eta { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Timetable staleness badge on the Overview transit card. Amber while the loaded window is past
   its midpoint, red once it no longer covers today (transit already dead). */
.freshness-AGEING { background: var(--starting); color: #1a1200; }
.freshness-EXPIRED { background: var(--unhealthy); }

/* Une barre sans dénominateur ne ment pas : elle balaie au lieu de se remplir. La règle porte sur
   l'ATTRIBUT et non sur une variante de barre — la seule qui existait la portait, et les lignes de
   la chaîne, qui sont désormais les seules barres de la page, ne l'auraient jamais reçue. */
.bar-fill[data-indeterminate="true"] {
  width: 100% !important;
  background: linear-gradient(90deg, rgba(76,141,255,0) 0%, var(--accent) 50%, rgba(76,141,255,0) 100%);
  background-size: 40% 100%; background-repeat: no-repeat;
  animation: job-indeterminate 1.2s ease-in-out infinite;
}
@keyframes job-indeterminate {
  0% { background-position: -40% 0; }
  100% { background-position: 140% 0; }
}

.job-final { font-size: 13px; color: var(--muted); min-height: 18px; margin-bottom: 14px; }
.job-controls { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin: 0; }

/* La purge n'est pas un job : elle ne doit pas se lire dans la même foulée. Un filet et une vraie
   respiration l'en séparent — c'est la seule action irréversible de la page. */
.data-content .danger-sep {
  border: 0; border-top: 1px solid var(--border);
  margin: 56px 0 0;
}
.data-content .purge-card {
  margin-top: 28px;
  border-left: 3px solid var(--unhealthy);
}
.data-content .purge-card::before {
  content: "Zone destructive";
  display: block; margin-bottom: 10px;
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--unhealthy);
}

/* Les sections dans le bandeau du pipeline : même vocabulaire que les jobs individuels, en plus
   resserré — le bandeau est un récapitulatif, pas une liste d'actions. */
.pipeline-group {
  margin: 8px 0 2px; padding-left: 2px;
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
  list-style: none;
}
.pipeline-group:first-child { margin-top: 0; }

.job-controls form { display: inline; margin: 0; }
.job-force { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--muted); font-size: 13px; }
.job-force input { width: auto; margin: 0; }
.job-controls .btn.primary { width: auto; margin-top: 0; }
.job-toast { font-size: 12px; font-weight: 600; }
.job-lock { font-size: 12px; color: var(--starting); font-weight: 600; }
.job-reset { margin-top: 10px; }
.job-reset-hint { margin: 0; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn.danger { background: var(--unhealthy); color: #fff; width: auto; margin-top: 0; }
.toast-ok { color: var(--up); }
.toast-fail { color: var(--unhealthy); }

/* ── full pipeline « Tout réimporter » (issue 0136) ─────────────────────────── */
/* Pas de liseré d'état : la pastille, en haut à droite, dit déjà l'état — et elle le dit avec un
   mot, là où une couleur seule ne se lit pas. Les liserés par état ont été supprimés plutôt que
   neutralisés par un sélecteur d'attribut qui surclassait leur propre fichier. */
.pipeline-card { border-left: 1px solid var(--border); }

/* Une ligne par étape, pas une carte. Le chevauchement d'origine venait d'une piste de 110 px que le
   libellé et l'état se disputaient ; ici chaque colonne a la sienne et aucune ne peut pousser sa
   voisine. Les deux flexibles (libellé, état) se réduisent jusqu'à 0 et coupent en points de
   suspension — donc pas de débordement, à aucune largeur de fenêtre. */
.pipeline-steps {
  list-style: none; margin: 8px 0 10px; padding: 0;
  border-top: 1px solid var(--border);
}
.pipeline-step {
  display: grid;
  grid-template-columns: 12px 20px minmax(0, 1.4fr) minmax(0, 1fr) 60px 54px 26px;
  align-items: center; gap: 8px;
  padding: 2px 0; border-bottom: 1px solid var(--border);
  font-size: 12px; line-height: 1.5;
}
.pipeline-step > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Sauf la cellule des gestes : son menu sort de la ligne, et `overflow: hidden` le couperait. */
.pipeline-step > .step-action { overflow: visible; }
.pipeline-step.step-ABORTED,
.pipeline-step.step-NOT_RUN { opacity: .55; }

/* La pastille dit l'état sans la couleur : cercle creux = à venir, demi-cercle = en cours, cercle
   plein = fait, losange = la famille de l'échec (plein : échoué ; creux : jamais exécuté). */
.pipeline-step .step-dot {
  width: 10px; height: 10px; justify-self: center;
  border: 2px solid var(--muted); border-radius: 999px; background: transparent;
}
.pipeline-step.step-RUNNING .step-dot {
  border-color: var(--accent);
  background: linear-gradient(90deg, var(--accent) 50%, transparent 50%);
}
.pipeline-step.step-COMPLETED .step-dot { border-color: var(--up); background: var(--up); }
.pipeline-step.step-SKIPPED .step-dot { border-color: var(--down); background: var(--down); }
.pipeline-step.step-FAILED .step-dot {
  border-radius: 2px; transform: rotate(45deg);
  border-color: var(--unhealthy); background: var(--unhealthy);
}
.pipeline-step.step-ABORTED .step-dot,
.pipeline-step.step-NOT_RUN .step-dot {
  border-radius: 2px; transform: rotate(45deg); border-color: var(--unknown);
}
/* Coupé (le processus est mort) ou figé (il tourne peut-être, mais plus rien ne bouge) : ni un
   échec, ni une réussite — l'ambre, et le mot dans la colonne d'à côté. */
.pipeline-step.step-INTERRUPTED .step-dot,
.pipeline-step.step-STALLED .step-dot {
  border-color: var(--starting); background: var(--starting);
}

.pipeline-step .step-num {
  color: var(--muted); font-size: 11px; text-align: right; font-variant-numeric: tabular-nums;
}
.pipeline-step.structural .step-num { color: var(--ink); font-weight: 700; }
.pipeline-step .step-label { color: var(--ink); font-weight: 600; }
.pipeline-step .step-state { color: var(--muted); font-size: 11px; }
.pipeline-step.step-RUNNING .step-state { color: var(--accent); }
.pipeline-step.step-FAILED .step-state { color: var(--unhealthy); font-weight: 600; }
.pipeline-step.step-STALLED .step-state,
.pipeline-step.step-INTERRUPTED .step-state { color: var(--starting); font-weight: 600; }

/* Progression et annulation : les colonnes existent avant les données qui les rempliront
   (jobs-progression-annulation) — vides, elles gardent leur largeur sans décaler le reste. */
.pipeline-step .step-progress .bar { display: block; height: 5px; }
.pipeline-step .step-progress .bar[hidden] { display: none; }
/* `display: block` : ces deux-là sont des <span> (une ligne de grille n'accueille pas un <div>
   sans casser ses colonnes), et une boîte en ligne ignore width/height. */
.bar-fill.step { display: block; background: var(--accent); }
.pipeline-step .step-dur {
  color: var(--muted); text-align: right; font-variant-numeric: tabular-nums;
}
/* Les gestes d'une ligne : un menu natif (`<details>`), fermé par défaut. Vingt lignes portant
   trois boutons chacune feraient soixante boutons entre l'œil et l'information. */
.pipeline-step .step-action { justify-self: end; }
.step-menu { position: relative; }
.step-menu > summary {
  list-style: none; cursor: pointer; padding: 0 4px; border-radius: 6px;
  color: var(--muted); line-height: 1;
}
.step-menu > summary::-webkit-details-marker { display: none; }
.step-menu > summary:hover { color: var(--ink); background: var(--surface-2); }
.step-menu-items {
  position: absolute; right: 0; top: 100%; z-index: 5;
  display: grid; gap: 6px; min-width: 210px; padding: 8px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
}
.step-menu-items .btn { width: 100%; margin: 0; text-align: left; }

/* ── « Aperçu » : ce qu'un lancement ferait ─────────────────────────────────────
   Le plan répond à la MÊME demande que le lancement qui le suit, donc il se lit juste sous les
   commandes qui l'ont produit — pas dans un autre écran d'où on ne verrait plus l'état courant. */
.pipeline-plan:empty { display: none; }
.pipeline-plan {
  margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--border);
}
.plan-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.plan-title { font-weight: 700; font-size: 13px; }
.plan-eta { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.plan-rows { list-style: none; margin: 8px 0 0; padding: 0; }
.plan-row {
  display: grid; grid-template-columns: minmax(0, 1.2fr) 92px minmax(0, 1fr) 54px;
  align-items: center; gap: 8px; padding: 2px 0;
  font-size: 12px; line-height: 1.5;
}
.plan-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan-label { color: var(--ink); font-weight: 600; }
.plan-verdict { font-size: 11px; color: var(--muted); }
.plan-row.verdict-WILL_RUN .plan-verdict { color: var(--accent); font-weight: 600; }
.plan-row.verdict-BLOCKED .plan-verdict { color: var(--unhealthy); font-weight: 600; }
.plan-row.verdict-OUT_OF_SCOPE { opacity: .55; }
.plan-reason { font-size: 11px; color: var(--muted); }
.plan-cost { font-size: 11px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }

/* Le bilan et les commandes partagent une ligne : deux blocs empilés pour six mots et un bouton. */
.pipeline-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}
.pipeline-foot .job-final { margin: 0; flex: 1 1 200px; }

/* Les deux respirations qu'on garde : le titre des jobs individuels et la carte destructive. */
.data-content .jobs-subhead { margin: 20px 0 6px; }
/* (l'écart de la purge est décidé plus haut, avec sa zone destructive) */

/* ── update backend + clean images (issue 0138) ─────────────────────────────── */
.update-content { max-width: 900px; }
.update-card { padding: 20px; margin-top: 16px; }
.clean-card { padding: 20px; margin-top: 16px; border-left: 4px solid var(--starting); }
.update-current { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
.update-current .tag { color: var(--ink); }
.update-form { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.update-label { display: block; margin: 0 0 4px; color: var(--muted); font-size: 13px; }
#update-tags-slot { display: flex; flex-direction: column; gap: 4px; flex: 1 1 260px; min-width: 220px; }
#update-tags-slot select {
  width: 100%; padding: 8px 10px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; color: var(--ink); font-size: 14px;
}
.update-form .btn.primary { width: auto; margin-top: 0; }
.update-msg { color: var(--starting); font-size: 12px; margin: 0; }

.update-verdict { margin-top: 14px; }
.update-verdict:empty { display: none; }
.verdict { padding: 10px 14px; border-radius: 8px; font-size: 14px; }
.verdict-ok { background: rgba(46,160,67,.14); color: var(--up); border: 1px solid rgba(46,160,67,.4); }
.verdict-ok .pill { margin-right: 8px; }
.verdict-fail { background: rgba(248,81,73,.14); color: var(--unhealthy); border: 1px solid rgba(248,81,73,.4); }
.banner.fail { background: rgba(248,81,73,.14); color: var(--unhealthy); border: 1px solid rgba(248,81,73,.4); padding: 12px 14px; border-radius: 8px; }
.banner.fail .tag { color: var(--ink); }

/* ── carte de synthèse « Mises à jour » ────────────────────────────────────────
   Même carte que les panneaux qu'elle résume (`.card`), posée en tête de l'onglet. Le liseré
   gauche porte l'état d'ensemble : vert quand tout est à jour, ambre quand quelque chose est en
   retard. Ambre et non rouge — une version de retard n'est pas une panne. Chaque ligne reprend le
   liseré de son propre état, gris pour « indéterminé » : ne pas savoir se distingue à l'œil de
   savoir que c'est en retard. */
.update-summary-content { max-width: 900px; }
.update-summary-card { padding: 20px; margin-top: 16px; border-left: 4px solid var(--starting); }
.update-summary-card.is-fresh { border-left-color: var(--up); }
.update-summary-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.update-summary-row {
  padding: 12px 14px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; border-left: 3px solid var(--starting);
}
.update-summary-row.state-indetermine { border-left-color: var(--unknown); }
.update-summary-row .row-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.update-summary-row .row-detail { margin-top: 6px; font-size: 13px; }
.update-summary-row .row-arrow { color: var(--muted); margin: 0 4px; }
.update-summary-row .row-msg { margin-top: 6px; }
.update-summary-row .row-pending { color: var(--muted); font-style: italic; }
.update-summary-row .row-pending[hidden] { display: none; }
.vintage-flag {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px;
}
.update-summary-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.update-summary-actions .btn.primary { width: auto; }
.update-summary-actions .btn[disabled] { opacity: .5; cursor: not-allowed; }
.update-summary-actions .hint { margin-top: 20px; }

.verdict-logs-wrap { margin-top: 12px; }
.verdict-logs-wrap summary { color: var(--muted); font-size: 13px; cursor: pointer; }
.verdict-logs {
  margin: 8px 0 0; max-height: 40vh; overflow: auto; padding: 12px 14px;
  background: #0a0d11; border: 1px solid var(--border); border-radius: 8px;
  font: 12.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--ink);
  white-space: pre-wrap; word-break: break-word;
}

/* ── Onglet Transit (PRD 0018) ─────────────────────────────────────────────
   Carte de santé du moteur : même gabarit que les cartes de moteur de la vue
   d'ensemble (`.routing-card`), plus un liseré à gauche qui donne l'état à
   distance — vert « répond », ambre « en chargement », gris « muet ». L'ambre
   est délibérément distinct du rouge : « en chargement » n'est PAS un échec. */
.transit-content .hint em { color: var(--ink); font-style: normal; }
.engine-card { padding: 16px; margin-top: 16px; border-left: 3px solid var(--unknown); }
.engine-card.state-SERVING { border-left-color: var(--up); }
.engine-card.state-LOADING { border-left-color: var(--starting); }
.engine-card.state-SILENT  { border-left-color: var(--down); }
.engine-card .engine-name { color: var(--muted); font-size: 12px; margin-left: auto; }
.engine-actions { display: flex; gap: 10px; align-items: center; margin-top: 14px; }
.engine-actions .btn { width: auto; }

/* ── Filtre client des longues listes (list-filter.js) ────────────────────
   Partagé par l'onglet Carte et l'onglet Catalogue OSM, comme le script qui le
   pilote : deux copies divergeraient. Ne porte que le champ, son compteur et son
   message « aucun résultat » — la grille des lignes appartient à la feuille de
   chaque page. */
.list-filter-input {
  flex: 1;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}
.list-filter-input:focus { outline: none; border-color: var(--accent); }
.list-filter-count { color: var(--muted); font-size: 13px; white-space: nowrap; }
.list-filter-empty { margin: 4px 12px 10px; }

/* ── Fermetures de route au niveau de l'arête (PRD 0003) ─────────────────────
   Les gestes de la carte trafic : une rangée de boutons qui se replie sur les
   écrans étroits. Chaque bouton est dans SON `<form>` (cible et intention
   distinctes), d'où le flex sur le conteneur plutôt que sur un formulaire. */
.traffic-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}
.traffic-actions form { margin: 0; }
/* Les constats s'empilent : sans marge propre ils se colleraient les uns aux
   autres et se liraient comme un seul bloc. */
#traffic-card .banner { margin-top: 10px; }
#traffic-card .verdict { margin-top: 12px; }
