/* Plumb - the room-builder front end.
   Translated from design_template/Plumb.dc.html, whose inline styles are the spec.
   Modernist rules apply: zero border-radius, 2px rules (never hairlines), button labels
   flush left, every colour from a token - see the readme in design_template/_ds/. */

/* 1. Font -------------------------------------------------------------- */

@font-face {
  font-family: "Archivo";
  src: url("fonts/archivo-latin.woff2") format("woff2");
  font-weight: 100 900;          /* one variable file covers 400, 600 and 800 */
  font-style: normal;
  font-display: swap;
}

/* 2. Tokens -------------------------------------------------------------
   Copied verbatim from Plumb.dc.html's light body{} block and its
   .dark-root / [data-theme="dark"] block, plus the two font roles the mockup
   keeps outside the ramp. Both palettes live here; no component below branches
   on theme - only the token values change. */

:root {
  --color-bg: #f7f6f1;
  --color-surface: #edebe2;
  --color-text: #2b2d28;
  --color-divider: rgba(43, 45, 40, 0.2);
  --color-neutral-100: #f2f1ea;
  --color-neutral-200: #e6e4da;
  --color-neutral-300: #d3d1c4;
  --color-neutral-400: #b4b2a2;
  --color-neutral-500: #8f8d7c;
  --color-neutral-600: #767466;
  --color-neutral-700: #5d5c51;
  --color-neutral-800: #46463d;
  --color-neutral-900: #2b2d28;
  --color-accent: #8a6a1f;
  --color-accent-100: #f6efdc;
  --color-accent-200: #ecdfbe;
  --color-accent-300: #ddc691;
  --color-accent-400: #c39c3d;
  --color-accent-500: #a8842a;
  --color-accent-600: #755c19;
  --color-accent-700: #6b5317;
  --color-accent-800: #574212;
  --color-accent-900: #3c2d0c;
  --field-bg: #edebe2;
  --field-fg: #2b2d28;
  --field-sub: #767466;

  --font-heading: "Archivo", system-ui, sans-serif;
  --font-body: "Archivo", system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Not in the mockup's own token list (its canvas never defines --shadow-lg, only uses
     it) - the detail page's version-history popovers (Task: redesign) are the first thing
     in this app that floats over other content and needs one. One value for both themes:
     a soft black overlay reads as elevation against either a light or a dark surface, the
     same way .card's own hover shadow (style section 4) never branches on theme either. */
  --shadow-lg: 0 14px 32px rgba(0, 0, 0, 0.32);

  /* The one red in the product. The accent ramp is gold, so a failure styled from it
     (accent-200 on accent-800, as this was) reads as just another warm chip beside
     "running" - the mockup hardcodes a true red for exactly that reason (its STATUS
     table, Plumb.dc.html:753). One value for both themes, like --shadow-lg above: a
     deep brick red under near-white holds its contrast against either background. */
  --color-fail: #a6301c;
  --color-fail-fg: #fdeeea;
}

:root[data-theme="dark"] {
  --color-bg: #232420;
  --color-surface: #2d2e29;
  --color-text: #e3e2d7;
  --color-divider: rgba(227, 226, 215, 0.18);
  --color-neutral-100: #292a25;
  --color-neutral-200: #33342e;
  --color-neutral-300: #41423a;
  --color-neutral-400: #55564c;
  --color-neutral-500: #78796c;
  --color-neutral-600: #969588;
  --color-neutral-700: #b0afa2;
  --color-neutral-800: #c8c7ba;
  --color-neutral-900: #e3e2d7;
  --color-accent: #d3a03f;
  --color-accent-100: #2a2519;
  --color-accent-200: #362f1e;
  --color-accent-300: #4a3f25;
  --color-accent-400: #866a2c;
  --color-accent-500: #ad8632;
  --color-accent-600: #e2b559;
  --color-accent-700: #e8c680;
  --color-accent-800: #e0b96c;
  --color-accent-900: #f0d9a6;
  --field-bg: #2d2e29;
  --field-fg: #e3e2d7;
  --field-sub: #969588;
}

/* 3. Base ---------------------------------------------------------------- */

* { box-sizing: border-box; }

/* The app shell: the wedge header is fixed height and #app fills whatever is left, so the
   detail view (a fixed-height three-column instrument panel, Task: redesign) can occupy
   exactly the remaining viewport with no page-level scrollbar. html/body need the explicit
   height chain - a flex child only fills "the rest of the viewport" if its ancestors
   actually span the viewport, which neither does by default. The gallery is unaffected:
   it is simply a tall block that now scrolls inside #app instead of inside the page, and
   nothing about its own CSS changes. */
html, body { height: 100%; }
body {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  /* The design system's own base (its styles.css, line 82). Without these the browser
     default of 16px at `normal` leading applies to everything that does not set its own
     size, and the whole page drifts off the mockup's rhythm - every component below
     inherits from here. */
  font-size: 15px;
  line-height: 1.55;
  font-weight: 400;
}
#app { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

a { color: var(--color-accent-700); }

:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* 4. Chrome and components ------------------------------------------------
   Class names are the contract Tasks 7 and 8 write against. Each block below
   translates one region of the mockup; the line reference cited in the task
   brief is where the styling came from. */

/* .hdr - the wedge title bar, mockup titleBar="wedge" (:75-85).
   The accent plane is the OUTER element and the slab is clipped on top of it, so the
   gold reads as something revealed by the notch rather than a stripe drawn under it.
   Both clip-paths turn at x=252px; keep them in step or the notch splits in two. */
.hdr {
  flex: 0 0 auto;      /* fixed height in the app shell - #app claims everything else */
  background: var(--color-accent);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 22px), 252px 100%, 0 100%);
}
.hdr-slab {
  background: var(--color-text);
  color: var(--color-bg);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 29px), 252px calc(100% - 7px), 0 calc(100% - 7px));
  padding: 26px 32px 30px 32px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.hdr-brand { display: flex; align-items: center; gap: 18px; }
.hdr-word {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 38px;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.95;
}
.hdr-tag {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.7;
  align-self: center;
  padding-left: 18px;
  /* The mockup's rgba(255,255,255,0.22) only reads on the dark slab light theme gives.
     Mixed from currentColor it stays a soft rule against either slab. */
  border-left: 2px solid color-mix(in srgb, currentColor 30%, transparent);
}

/* .btn - buttons, mockup :72-76 and :102 (filter chips) */
.btn {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 10px 16px;
  border: 0;
  cursor: pointer;
  text-align: left;         /* labels are flush left, never centred */
  display: inline-flex;
  align-items: center;
  gap: 9px;
  white-space: nowrap;
}
.btn:disabled { cursor: not-allowed; opacity: 0.5; }
.btn--solid { background: var(--color-text); color: var(--color-bg); }
.btn--solid:hover:not(:disabled) { background: var(--color-accent); }
.btn--ghost {
  background: transparent;
  color: var(--color-text);
  border: 2px solid var(--color-neutral-500);
}
.btn--ghost:hover:not(:disabled) {
  border-color: var(--color-text);
  background: var(--color-neutral-100);
}
/* .btn--onslab - the one action on the wedge slab, mockup :80. Outlined in the slab's
   own foreground; hover fills with accent and flips the label to the slab colour. */
.btn--onslab {
  background: transparent;
  color: var(--color-bg);
  border: 2px solid var(--color-bg);
  align-self: flex-start;
  margin-top: 5px;
  text-decoration: none;
}
.btn--onslab:hover {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-text);
}
.btn--chip {
  font-size: 11px;
  letter-spacing: 0.1em;
  padding: 8px 14px;
  background: transparent;
  color: var(--color-text);
  border: 2px solid var(--color-divider);
}
.btn--chip:hover:not(:disabled) { border-color: var(--color-text); }
.btn--on {
  background: var(--color-text);
  color: var(--color-bg);
  border-color: var(--color-text);
}

/* .hero - the gallery header band, mockup :81-95 */
.hero {
  background: var(--field-bg);
  border-top: 3px solid var(--color-accent);
  padding: 44px 32px 36px 32px;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 32px;
  flex-wrap: wrap;
}
/* The kicker line ("Local · single operator · date") is gone from the mockup - the
   headline now carries the whole band on its own, with the agent in it. */
.hero-line {
  display: flex;
  align-items: center;
  gap: 20px;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 50px;
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--field-fg);
  white-space: nowrap;
}
.hero-visor { display: inline-flex; width: 72px; margin-bottom: -4px; }

/* Stats are ruled columns now, not free-standing numbers: a rule down each side so the
   group reads as one instrument panel. Mixed from --field-fg so it holds in both themes,
   where the mockup's baked rgba(227,226,215,.22) is the dark value only. */
.hero-stats {
  display: flex;
  color: var(--field-fg);
  border-left: 2px solid color-mix(in srgb, var(--field-fg) 22%, transparent);
}
.stat {
  padding: 2px 26px;
  border-right: 2px solid color-mix(in srgb, var(--field-fg) 22%, transparent);
}
.stat-row { display: flex; align-items: baseline; gap: 6px; }
.stat-v { font-family: var(--font-heading); font-weight: 800; font-size: 44px; line-height: 0.9; }
.stat-of { font-family: var(--font-mono); font-size: 11px; color: var(--field-sub); }
.stat-k {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--field-sub);
  margin-top: 8px;
  white-space: nowrap;
}

/* The agent sprite, mockup :765-802. Two 14x12 frames cross-faded on a 1s step cycle
   (pxA/pxB), the eye cells blinking on their own offset (blip), the whole head bobbing.
   steps(1,end) not a fade - it is pixel art and must cut, never tween. */
@keyframes pxA { 0%, 49.9% { opacity: 1 } 50%, 100% { opacity: 0 } }
@keyframes pxB { 0%, 49.9% { opacity: 0 } 50%, 100% { opacity: 1 } }
@keyframes blip { 0%, 60% { opacity: 1 } 61%, 100% { opacity: 0.15 } }
@keyframes bob { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-2.2%) } }
.visor { position: relative; width: 100%; aspect-ratio: 14 / 12; animation: bob 1s ease-in-out infinite; }
.visor-frame { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(14, 1fr); grid-template-rows: repeat(12, 1fr); }
.visor-frame--a { animation: pxA 1s steps(1, end) infinite; }
.visor-frame--b { animation: pxB 1s steps(1, end) infinite; }
.visor-eye { animation: blip 1s steps(1, end) infinite; }
@media (prefers-reduced-motion: reduce) {
  .visor, .visor-frame, .visor-eye { animation: none; }
  .visor-frame--b { opacity: 0; }
}

/* .filters - filter bar, mockup :99-110 */
.filters {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 32px;
  border-bottom: 2px solid var(--color-divider);
  flex-wrap: wrap;
}
.filter-meta {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-neutral-700);
}
/* .filter-group - the row of filter chips, mockup :100-104. Not in the original
   inventory: the mockup groups the buttons in an unnamed flex div. */
.filter-group { display: flex; gap: 8px; flex-wrap: wrap; }

/* .grid / .card - the run cards, mockup :111-140 */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(560px, 1fr));
  background: var(--color-bg);
  border-bottom: 2px solid var(--color-neutral-500);
}
.card {
  background: var(--color-bg);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  border-right: 2px solid var(--color-divider);
  border-bottom: 2px solid var(--color-divider);
  position: relative;
  transition: background 160ms ease, box-shadow 160ms ease, transform 160ms ease;
  transform: translateY(0);
  /* The mockup's card is a div with an onClick; ours is a real <a> (Task 8 routes off
     the hash), so the anchor defaults need cancelling here rather than in gallery.js. */
  color: inherit;
  text-decoration: none;
}
.card:hover {
  background: var(--color-neutral-100);
  box-shadow: inset 0 3px 0 0 var(--color-accent), 0 14px 28px rgba(0, 0, 0, 0.16);
  transform: translateY(-3px);
  z-index: 1;
}
.card-shots {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  background: var(--color-neutral-500);
  border-bottom: 2px solid var(--color-neutral-500);
}
.shot { position: relative; aspect-ratio: 16 / 10; background: var(--color-neutral-200); }
.shot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The design system desaturates content photographs (its styles.css, line 73) and the
   mockup applies that to the concept shot. Dropped by decision 2026-08-20: the concept is
   the reference the render is judged against, and colour is part of what is being judged -
   a greyscale reference cannot answer "is the wood too orange". */
.shot-label {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 5px 9px;
  background: var(--color-bg);
  color: var(--color-neutral-700);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  pointer-events: none;
}
.shot-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--color-neutral-200);
  color: var(--color-neutral-500);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.card-chips { position: absolute; top: 0; right: 0; display: flex; pointer-events: none; }
.chip {
  padding: 8px 12px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  background: var(--color-bg);
  color: var(--color-text);
  border-right: 2px solid var(--color-divider);
}
.chip:last-child { border-right: 0; }

/* .card-body - card text and metrics, mockup :142-152 */
.card-body { padding: 20px 22px 24px 22px; display: flex; flex-direction: column; gap: 16px; flex: 1; }
.card-path { font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-600); letter-spacing: 0.02em; }
.card-name {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 30px;
  line-height: 1;
  letter-spacing: -0.03em;
  margin-top: 5px;
}
.metrics { display: flex; gap: 26px; flex-wrap: wrap; }
.metric-v { font-family: var(--font-mono); font-size: 17px; color: var(--color-text); }
.metric-k {
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  margin-top: 3px;
}

/* .segs / .card-foot - the stage-duration strip, mockup :154-162 */
.segs { display: flex; gap: 2px; height: 8px; margin-top: auto; }
.seg { min-width: 4px; background: var(--color-neutral-300); }
.card-foot { display: flex; justify-content: space-between; gap: 12px; margin-top: 9px; flex-wrap: wrap; }
.live-line { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-700); }
.check-line { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }

/* .scene-head - the grouping heading above a scene's run cards (or the final "no scene"
   catch-all), Task 6. Not in the mockup - Plumb.dc.html predates scene versioning - so
   this follows the existing .filters/.sec-row rhythm: flush-left label, uppercase meta,
   token colours, a 2px rule. */
.scene-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 32px;
  background: var(--color-surface);
  border-top: 2px solid var(--color-neutral-500);
  border-bottom: 2px solid var(--color-divider);
  flex-wrap: wrap;
}
.scene-head-name {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 18px;
  letter-spacing: -0.01em;
  color: var(--color-text);
}
.scene-head-meta {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-neutral-700);
}
/* The catch-all section, marked with the same accent rule the hero band uses for its own
   top border - a visual cue that this is a distinct, named category (runs predating scene
   versioning), not a scene whose name failed to load. */
.scene-head--loose { border-top: 3px solid var(--color-accent); }
.scene-head--loose .scene-head-name { color: var(--color-neutral-700); font-weight: 600; }

/* .crumb - the "All runs" back link, mockup :182, :186-190, :227 */
.crumb {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 8px 14px;
  border: 2px solid var(--color-neutral-500);
  background: transparent;
  color: var(--color-text);
  cursor: pointer;
  text-align: left;
  text-decoration: none;   /* the mockup's crumb is a <button>; ours is an <a href="#/"> */
  display: inline-block;
}
.crumb:hover { background: var(--color-text); color: var(--color-bg); }

/* .d-page - the detail view's root: a fixed-height app shell (redesign), filling whatever
   #app leaves after the wedge header. `background` is the divider token so the 2px gaps in
   .d-cols below read as rules between the columns, matching the mockup's own trick
   (Plumb.dc.html:342) rather than a border on every column. */
.d-page { height: 100%; display: flex; flex-direction: column; overflow: hidden; background: var(--color-divider); }

/* .d-top - the bar above the columns: back link, run id, a spacer, the version note, and
   the scene-version stepper, mockup :343-356. Replaces the old two-column .d-grid header -
   the run's name and facts moved into the columns themselves. */
.d-top {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 20px;
  background: var(--color-bg);
  border-bottom: 2px solid var(--color-neutral-500);
  flex-wrap: wrap;
}
.d-id { font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-700); }
.d-spacer { flex: 1; }
.d-vernote { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); }
.d-vernote[data-current="true"] { color: var(--color-accent-700); }

/* .d-verbar / .d-verstep / .d-vertoggle - the stepper control in the top bar, mockup
   :348-356. `data-ver-anchor` (set in detail.js, not here) is what the document click
   handler's `.closest()` uses to tell "inside the control" from "outside it". */
.d-verbar { position: relative; display: flex; align-items: stretch; }
.d-verstep {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  padding: 6px 9px;
  border: 2px solid var(--color-neutral-500);
  background: transparent;
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
.d-verstep--older { border-right: 0; }
.d-verstep--newer { border-left: 0; }
.d-verstep:hover { background: var(--color-neutral-200); }
/* A step with nowhere to go is a <span>, not an <a> (detail.js's stepLink) - muted and
   inert rather than a link to nothing. */
.d-verstep--disabled { color: var(--color-neutral-500); cursor: default; }
.d-verstep--disabled:hover { background: transparent; }
.d-vertoggle {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 6px 12px;
  border: 2px solid var(--color-neutral-500);
  background: var(--color-neutral-200);
  color: var(--color-text);
  cursor: pointer;
  text-align: left;
}
.d-vertoggle:hover { border-color: var(--color-accent); }
.d-vertoggle-hash { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.02em; color: var(--color-neutral-700); text-transform: none; }
.d-vertoggle-pos { font-size: 9px; letter-spacing: 0.1em; color: var(--color-neutral-600); }

/* .d-vertag - the large version tag beside the h1 (mockup :409-413), the second anchor
   that opens the same panel as the bar stepper above. Its own button reset: the mockup's
   is a plain onClick <div>; a <button> keeps it keyboard-reachable without changing how it
   reads. */
.d-vertagwrap { position: relative; }
.d-vertag {
  font-family: inherit;
  padding: 6px 10px;
  background: transparent;
  color: var(--color-accent-700);
  border: 2px solid var(--color-accent-400);
  cursor: pointer;
  text-align: left;
  display: block;
}
.d-vertag[data-current="true"] { background: var(--color-text); color: var(--color-bg); border-color: var(--color-text); }
.d-vertag:hover { border-color: var(--color-accent); }
.d-vertag-label { font-family: var(--font-heading); font-weight: 800; font-size: 24px; line-height: 1; letter-spacing: -0.02em; }
.d-vertag-hash { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.02em; margin-top: 2px; opacity: 0.75; white-space: nowrap; }

/* .d-verpanel - the version-history dropdown, mockup :358-382 (identical markup at both
   anchors - the panel content is anchor-agnostic, only its trigger differs). */
.d-verpanel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 320px;
  max-height: 60dvh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 2px solid var(--color-neutral-500);
  box-shadow: var(--shadow-lg);
  z-index: 40;
}
.d-verpanel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border-bottom: 2px solid var(--color-neutral-500); }
.d-verpanel-title { font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-accent-700); font-weight: 700; }
.d-verpanel-count { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.d-verpanel-search { padding: 8px 12px; border-bottom: 2px solid var(--color-divider); }
.d-verpanel-search input {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 9px;
  background: var(--color-bg);
  color: var(--color-text);
  border: 2px solid var(--color-neutral-500);
  outline: none;
}
.d-verpanel-rows { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--color-neutral-500) transparent; }
.d-verrow {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 8px 12px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid var(--color-divider);
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  color: inherit;
}
.d-verrow:hover { background: var(--color-neutral-200); }
.d-verrow[data-on="true"] { background: var(--color-neutral-200); }
.d-verrow-accent { flex: 0 0 6px; height: 22px; background: transparent; }
.d-verrow[data-on="true"] .d-verrow-accent { background: var(--color-accent); }
.d-verrow-label { flex: 0 0 auto; font-family: var(--font-body); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-800); white-space: nowrap; }
.d-verrow[data-on="true"] .d-verrow-label { color: var(--color-text); }
.d-verrow-hash { flex: 0 0 auto; font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-700); white-space: nowrap; }
.d-verrow-note { flex: 1 1 auto; min-width: 0; font-size: 10px; color: var(--color-neutral-600); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.d-verrow-dot { flex: 0 0 auto; width: 7px; height: 7px; background: var(--color-neutral-700); }
.d-verrow-dot[data-status="failed"] { background: var(--color-accent-400); }
.d-verrow-dot[data-status="orphaned"] { background: var(--color-neutral-500); }
.d-verrow-dot[data-status="running"] { background: var(--color-accent); }
.d-verrow-when { flex: 0 0 auto; font-size: 9px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-neutral-600); white-space: nowrap; }
.d-verpanel-foot { display: flex; gap: 6px; padding: 10px 12px; border-top: 2px solid var(--color-neutral-500); }
.d-verpanel-jump {
  flex: 1;
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 8px 10px;
  background: var(--color-text);
  color: var(--color-bg);
  border: 0;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.d-verpanel-jump:hover { background: var(--color-accent); }
.d-verpanel-close {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 8px 10px;
  background: transparent;
  color: var(--color-text);
  border: 2px solid var(--color-neutral-500);
  cursor: pointer;
}
.d-verpanel-close:hover { background: var(--color-neutral-200); }

/* .live-band - the live progress strip above the columns, mockup :387-397. Padding matches
   the columns' own 20px now (was 32px under the old two-column header). */
.live-band {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 12px 20px;
  background: var(--color-accent-100);
  border-bottom: 2px solid var(--color-neutral-500);
  flex-wrap: wrap;
}
.live-bar { flex: 1; min-width: 200px; height: 10px; background: var(--color-neutral-300); position: relative; overflow: hidden; }
.live-bar > i {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--color-accent);
  font-style: normal;       /* it's an <i> only for a free block-level bar fill */
}
/* No token count yet - a fabricated percentage is worse than an honest shrug (the old
   web/index.html said exactly this). The bar sweeps instead of claiming a width. */
.live-bar > i[data-indeterminate="true"] {
  width: 34%;
  animation: d-sweep 1.15s ease-in-out infinite;
}
@keyframes d-sweep {
  from { left: -34%; }
  to   { left: 100%; }
}
.live-meta { font-family: var(--font-mono); font-size: 13px; color: var(--color-text); }

/* .d-statusrow / .d-titlerow - column 1's own header, mockup :403-443. Replaces the old
   .d-grid/.d-head split: status+name+brief+tags now sit directly in the padded column
   instead of a bordered half-width block. */
.d-statusrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-bottom: 8px; border-bottom: 2px solid var(--color-divider); }
/* .d-statuschip reuses .chip's [data-status] colour rules (style section 5) - only the
   size differs from the gallery card's chip, hence the smaller padding here. */
.d-statuschip { padding: 5px 9px; font-size: 10px; white-space: nowrap; }
.d-titlerow { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 14px; padding-top: 12px; }
/* .d-name - the run's name, mockup :408. 30px, not the old header's 44px: the redesign
   gives the name a whole column instead of half the page, so it no longer needs to carry
   the page on its own. */
.d-name { font-family: var(--font-heading); font-weight: 800; font-size: 30px; line-height: 1.05; letter-spacing: -0.03em; margin: 0; min-width: 0; overflow-wrap: anywhere; }
.d-brief { grid-column: 1 / -1; margin: 0; font-size: 13px; line-height: 1.45; color: var(--color-neutral-800); white-space: pre-line; }
.tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; }
.tag { padding: 5px 8px; border: 2px solid var(--color-divider); font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-700); }

/* .d-rule - the plain divider repeated between sections within a column, mockup :450, :473,
   :526 (and :543 with the tighter bottom margin used before Checks). */
.d-rule { height: 2px; background: var(--color-neutral-500); margin: 18px 0 12px 0; }
.d-rule--tight { margin: 18px 0 10px 0; }

/* .sec-title / .sec-meta / .sec-row - section headers reused across the detail page,
   mockup :451-454 (and repeated at :474, :527). */
.sec-title { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--color-accent-700); }
.sec-meta { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-700); }
.sec-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; flex-wrap: wrap; }

/* .d-cols - the three-column instrument panel, mockup :400. The 2px gap on the
   divider-coloured .d-page background is what draws the rules between columns. */
.d-cols { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: 1.45fr 1fr 1.05fr; gap: 2px; }
/* .d-col - columns 1 and 2: independently scrolling, padded blocks (mockup :402, :516). */
.d-col { background: var(--color-bg); min-height: 0; overflow-y: auto; padding: 20px; scrollbar-width: thin; scrollbar-color: var(--color-neutral-500) transparent; }
/* .d-col3 - column 3: a flex column whose ONLY scrolling element is .log below (mockup
   :556) - the header and the next-action footer stay put while the stream scrolls. */
.d-col3 { background: var(--color-bg); min-height: 0; display: flex; flex-direction: column; }

/* .stages - the stage timeline, mockup :455-471. */
.stages { border-top: 2px solid var(--color-neutral-500); }
.stage-row {
  display: grid;
  grid-template-columns: 112px 1fr 118px;
  gap: 12px;
  align-items: center;
  padding: 8px 0;
  border-bottom: 2px solid var(--color-divider);
}
.stage-track { position: relative; height: 12px; background: var(--color-neutral-200); }
.stage-bar { position: absolute; top: 0; bottom: 0; background: var(--color-neutral-400); }
.stage-state { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-600); }
.stage-label { display: flex; align-items: baseline; gap: 6px; }
.stage-num { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-600); }
.stage-name { font-family: var(--font-heading); font-weight: 800; font-size: 14px; color: var(--color-text); }
.stage-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.stage-dur { font-family: var(--font-mono); font-size: 11px; color: var(--color-text); }

/* .hero-pair - the concept/render 2-up, mockup :478-497. Unchanged from the previous
   layout (still shared conceptually with the gallery's own .shot/.shot-label/.shot-empty,
   style section 4) - only where it sits on the page moved. */
.hero-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  background: var(--color-neutral-500);
  border: 2px solid var(--color-neutral-500);
}

/* .assets-head / .assets - the object-asset grid, mockup :498-513. Built only when
   laneRows(run) has real entries (detail.js's buildAssetsGrid) - there is no per-object
   crop/part image on disk for any real run today, so this never claims a photo it does not
   have. */
.assets-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 14px 0 8px 0; flex-wrap: wrap; }
.assets-head-title { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-700); }
.assets-head-note { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.assets { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 2px; background: var(--color-neutral-500); border: 2px solid var(--color-neutral-500); }
.asset { background: var(--color-bg); }
.asset-thumb { position: relative; aspect-ratio: 1 / 1; background: var(--color-neutral-200); overflow: hidden; }
/* The "authored blind" hatch is a REAL claim (the object was occluded in the concept), not
   decoration for an empty tile - see detail.js's own comment on why a non-blind lane gets
   neither this hatch nor an image. */
.asset-hatch {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: repeating-linear-gradient(45deg, var(--color-neutral-200) 0 5px, var(--color-neutral-100) 5px 10px);
  color: var(--color-neutral-600);
  font-size: 8px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
}
.asset-kind { position: absolute; left: 0; top: 0; padding: 3px 5px; background: var(--color-bg); color: var(--color-neutral-600); font-size: 8px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; }
.asset-kind[data-blind="true"] { color: var(--color-accent-700); }
.asset-file { padding: 5px 6px; font-family: var(--font-mono); font-size: 9px; color: var(--color-neutral-700); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* .ladder - the critic ladder (Task: shown as artifacts of the run, not a second version
   selector - see detail.js's own comment). Not in the mockup, which predates it - kept
   visually quiet and in the mockup's own language (2px rules, uppercase micro-labels, mono
   numbers) to read as part of the Artifacts block above it, not a new loud section. */
.ladder { border-top: 2px solid var(--color-divider); margin-top: 14px; }
.ladder-row { display: grid; grid-template-columns: 56px 1fr; gap: 10px; align-items: start; padding: 10px 0; border-bottom: 2px solid var(--color-divider); }
.ladder-thumb { aspect-ratio: 1 / 1; background: var(--color-neutral-200); overflow: hidden; }
.ladder-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ladder-body { min-width: 0; }
.ladder-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.ladder-label { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: var(--color-text); }
.ladder-settings { font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-700); }
.ladder-ts { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-600); }
.ladder-summary { font-size: 11px; line-height: 1.4; color: var(--color-neutral-700); margin-top: 4px; }

/* .facts - the fact grid in column 2, mockup :517-524. A grid on the divider background
   (the gap draws the rules) rather than bordered cells - three columns now, not two, to
   match the mockup exactly. */
.facts { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2px; background: var(--color-divider); border: 2px solid var(--color-divider); }
.fact { background: var(--color-bg); padding: 10px 12px; }
.fact-k { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-700); }
.fact-v { font-family: var(--font-mono); font-size: 15px; margin-top: 4px; color: var(--color-text); }

/* .lanes - the prototype lane rows, mockup :531-540. Three columns now (name·parts, bar,
   source pill) - the old five-column row's separate duration/token text is gone; the bar's
   own width already carries that, and the source pill's `title` still names the full
   source word. */
.lanes { border-top: 2px solid var(--color-divider); }
.lane-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 48px 46px;
  gap: 8px;
  align-items: center;
  padding: 8px 0;
  border-bottom: 2px solid var(--color-divider);
}
.lane-name { font-family: var(--font-mono); font-size: 12px; color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lane-bar { height: 8px; background: var(--color-neutral-200); }
.lane-bar > i { display: block; height: 8px; background: var(--color-neutral-500); font-style: normal; }
.lane-bar > i[data-blind="true"] { background: var(--color-accent); }
.lane-src { justify-self: end; padding: 4px 5px; background: transparent; color: var(--color-neutral-700); font-size: 8px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 700; white-space: nowrap; overflow: hidden; }
.lane-src[data-blind="true"] { background: var(--color-accent); color: var(--color-bg); }

/* .checks - the check panel, mockup :546-553. */
.checks { border-top: 2px solid var(--color-divider); }
.check-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 2px solid var(--color-divider);
}
.check-row > .check-key { font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-700); }
.check-verdict { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; white-space: nowrap; }
.check-caveat { font-size: 11px; line-height: 1.4; color: var(--color-neutral-700); margin-bottom: 10px; }
/* .check-offenders - the specific parts a flagged check named. Not in the mockup's own
   markup (its fixture never lists offenders) - what turns "flagged" into something
   actionable rather than a bare verdict. */
.check-offenders { font-size: 11px; line-height: 1.5; color: var(--color-neutral-600); padding: 0 0 10px 0; }
/* .check-meaning - the plain-English gloss for the check key above it (fmt.js's MEANING
   map). Same muted token as .check-offenders - a caption, not a new colour. */
.check-meaning { font-size: 11px; line-height: 1.5; color: var(--color-neutral-600); padding: 0 0 6px 0; }

/* .evstream-head - column 3's own header, mockup :557-560 (the only header not living
   inside a padded .d-col - column 3 is a flex column and pads this row itself). */
.evstream-head { flex: 0 0 auto; display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 20px 20px 10px 20px; }
.evstream-count { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); }

/* .log - the event stream, mockup :561-570. The ONLY scrolling element in column 3 (see
   .d-col3 above) - `flex: 1` is load-bearing, not decorative. */
.log {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 20px;
  border-top: 2px solid var(--color-neutral-500);
  scrollbar-width: thin;
  scrollbar-color: var(--color-neutral-500) transparent;
}
.log-row { display: grid; grid-template-columns: 58px 1fr; gap: 10px; padding: 8px 0; border-bottom: 2px solid var(--color-divider); }
.log-t { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-600); }
.log-type { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-700); }
.log-msg { font-size: 12px; line-height: 1.4; color: var(--color-neutral-800); margin-top: 2px; }

/* .next-foot - the next-action footer, mockup :572-579. The other fixed (non-scrolling)
   piece of column 3, alongside .evstream-head. */
.next-foot { flex: 0 0 auto; padding: 14px 20px 18px 20px; border-top: 2px solid var(--color-neutral-500); }
.next-body { font-size: 12px; line-height: 1.45; color: var(--color-neutral-800); margin-bottom: 10px; }
.next { display: flex; gap: 6px; flex-wrap: wrap; }

/* .zoom - full-size image dialog opened from a thumbnail, web/app.js */
#zoom {
  padding: 0;
  border: 2px solid var(--color-neutral-500);
  border-radius: 0;
  background: var(--color-bg);
  max-width: 90vw;
  max-height: 90vh;
}
#zoom img { display: block; max-width: 90vw; max-height: 90vh; }
#zoom::backdrop { background: var(--color-text); opacity: 0.6; }

/* 5. Status colours -------------------------------------------------------
   Driven by data-* attributes so no view branches on colour. */

.chip[data-status="ok"]       { background: var(--color-text);        color: var(--color-bg); }
.chip[data-status="running"]  { background: var(--color-accent);      color: var(--color-bg); }
.chip[data-status="failed"]   { background: var(--color-fail);        color: var(--color-fail-fg); }
.chip[data-status="orphaned"] { background: var(--color-neutral-300); color: var(--color-neutral-800); }
.chip[data-status="legacy"]   { background: var(--color-neutral-200); color: var(--color-neutral-700); }

.seg[data-ev="done"]    { background: var(--color-text); }
.seg[data-ev="running"] { background: var(--color-accent); }
.seg[data-ev="skip"]    { background: var(--color-neutral-400); }
.seg[data-ev="fail"]    { background: var(--color-accent-400); }
.seg[data-ev="pending"] { background: var(--color-neutral-300); }

.stage-bar[data-ev="done"]    { background: var(--color-text); }
.stage-bar[data-ev="running"] { background: var(--color-accent); }
.stage-bar[data-ev="skip"]    { background: transparent; border: 2px dashed var(--color-neutral-500); }
.stage-bar[data-ev="fail"]    { background: var(--color-accent-400); }
.stage-bar[data-ev="pending"] { background: var(--color-neutral-400); }

.check-row[data-verdict="clear"]         .check-verdict { color: var(--color-neutral-600); }
.check-row[data-verdict="informational"] .check-verdict { color: var(--color-neutral-700); }
.check-row[data-verdict="flagged"]       .check-verdict { color: var(--color-accent-700); }
.check-row[data-verdict="flagged"]       .check-key     { color: var(--color-text); }

.stage-state[data-ev="running"] { color: var(--color-accent-700); }
.stage-state[data-ev="fail"]    { color: var(--color-accent-700); }

/* .log-type[data-type] - event stream colouring, ported from the pre-Plumb
   web/index.html (:146-156): fail/retry/rate_limit take the accent, skip/working stay
   faint (working heartbeats are the bulk of the log), done/start/etc. keep .log-type's
   base neutral-700. Driven by data-type so the view sets an attribute, not a class. */
.log-type[data-type="fail"],
.log-type[data-type="retry"],
.log-type[data-type="rate_limit"] { color: var(--color-accent-700); }
.log-type[data-type="skip"],
.log-type[data-type="working"]    { color: var(--color-neutral-600); }

/* Card-foot state colours, mockup :164 (liveFg) and :166 (checkFg) - a live stage or a
   flagged check earns the accent; otherwise both stay neutral. */
.live-line[data-live="true"]     { color: var(--color-accent-700); }
.check-line[data-flagged="true"] { color: var(--color-accent-700); }
