/* 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;

  /* Resonance's --inset ("inset fields, viewport, thumbnails"): the ground the hero
     canvas and the shelf's asset thumbs sit on, one step DEEPER than the surfaces
     around it - their thumbnails are transparent sprites and read as objects only
     against this, not against a light card (found live 2026-08-27: "a weird white
     border around our thumbnails"). */
  --color-inset: #e9e7db;

  --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;

  /* The second (and, deliberately, only other) departure from the gold ramp - the
     inventory step's percept-status chip needs three visually distinct states
     (matched/no_mask/count_disagrees) and grey-vs-gold alone cannot carry three. One
     value for both themes, like --color-fail above: a muted forest green holds its own
     against either background without reading as a brand colour. */
  --color-ok: #3f7a3f;
}

: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;
  --color-inset: #191a17;
}

/* 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;
}
/* overflow-anchor off: app.js restores #app's scroll offset across each redraw itself
   (draw()'s own comment), and the browser's anchoring would fight that restore by
   re-anchoring to a node the redraw just destroyed. One mechanism, not two. */
#app { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-anchor: none; }

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-actions - groups the queue link and the sign-in button on the right of the slab,
   so a third top-level entry point (the queue page) does not just float loose between
   `justify-content: space-between`'s two ends. */
.hdr-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.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: var(--color-neutral-200);
  color: var(--color-text);
  border: 0;
}
.btn--ghost:hover:not(:disabled) {
  background: var(--color-neutral-300);
}
/* .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: var(--color-neutral-200);
  color: var(--color-text);
  border: 0;
}
.btn--chip:hover:not(:disabled) { background: var(--color-neutral-300); }
.btn--on {
  background: var(--color-text);
  color: var(--color-bg);
}

/* .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;
}
/* An <a href="#/team"> now (gallery.js) - the robot head is the entry point to the team
   page, no header nav button. text-decoration:none because the global `a` rule (above)
   would otherwise underline a box with no text in it in some browsers. */
.hero-visor { display: inline-flex; width: 72px; margin-bottom: -4px; text-decoration: none; }

/* 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; }

/* .visor-move - the idle-behavior layer, mockup scheduleBehavior(). One wrapper between
   the slot and the bobbing head so a behavior transform composes with (never fights) the
   bob: gallery.js sets `animation` inline to one of the vX keyframes below, one-shot
   (`both`), then rests and picks another. transform-origin 70% down so tilts pivot at
   the chin, not the visor. The slot takes z-index because vDrift and vDash leave the
   72px box - the head crossing the headline is the point, clipping it is not. */
.hero-visor { position: relative; z-index: 30; }
.visor-move { display: block; width: 100%; transform-origin: 50% 70%; will-change: transform; }
@keyframes vLook {
  0%, 12%   { transform: rotate(0deg) translate(0,0); animation-timing-function: cubic-bezier(.4,0,.2,1); }
  28%       { transform: rotate(-13deg) translate(-5px,1px); animation-timing-function: ease-in-out; }
  46%, 54%  { transform: rotate(-13deg) translate(-5px,1px); animation-timing-function: cubic-bezier(.4,0,.2,1); }
  72%       { transform: rotate(12deg) translate(5px,1px); animation-timing-function: ease-in-out; }
  88%, 100% { transform: rotate(0deg) translate(0,0); }
}
@keyframes vNod {
  0%        { transform: translateY(0) scaleY(1); animation-timing-function: cubic-bezier(.3,0,.2,1); }
  14%       { transform: translateY(6px) scaleY(0.9); animation-timing-function: cubic-bezier(.34,1.5,.64,1); }
  30%       { transform: translateY(-4px) scaleY(1.06); animation-timing-function: cubic-bezier(.3,0,.2,1); }
  44%       { transform: translateY(5px) scaleY(0.92); animation-timing-function: cubic-bezier(.34,1.5,.64,1); }
  60%, 100% { transform: translateY(0) scaleY(1); }
}
@keyframes vHop {
  0%, 10%   { transform: translateY(0) scale(1,1); animation-timing-function: cubic-bezier(.4,0,.6,1); }
  18%       { transform: translateY(3px) scale(1.1,0.86); animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  34%       { transform: translateY(-26px) scale(0.92,1.12); animation-timing-function: cubic-bezier(.5,0,.6,1); }
  50%       { transform: translateY(2px) scale(1.12,0.88); animation-timing-function: cubic-bezier(.34,1.5,.64,1); }
  62%       { transform: translateY(-6px) scale(0.98,1.03); animation-timing-function: cubic-bezier(.4,0,.2,1); }
  76%, 100% { transform: translateY(0) scale(1,1); }
}
@keyframes vSway {
  0%        { transform: translateX(0) rotate(0deg); animation-timing-function: ease-in-out; }
  25%       { transform: translateX(-9px) rotate(-5deg); animation-timing-function: ease-in-out; }
  50%       { transform: translateX(0) rotate(0deg); animation-timing-function: ease-in-out; }
  75%       { transform: translateX(9px) rotate(5deg); animation-timing-function: ease-in-out; }
  100%      { transform: translateX(0) rotate(0deg); }
}
@keyframes vShake {
  0%, 100%  { transform: translateX(0) rotate(0deg) }
  8%        { transform: translateX(-3px) rotate(-4deg) }
  16%       { transform: translateX(3px) rotate(4deg) }
  24%       { transform: translateX(-2px) rotate(-3deg) }
  32%       { transform: translateX(2px) rotate(3deg) }
  40%       { transform: translateX(0) rotate(0deg) }
}
@keyframes vLean {
  0%        { transform: translate(0,0) rotate(0deg); animation-timing-function: cubic-bezier(.34,1.3,.64,1); }
  22%       { transform: translate(-26px,-6px) rotate(-11deg); animation-timing-function: ease-in-out; }
  46%       { transform: translate(-26px,-6px) rotate(-11deg); animation-timing-function: cubic-bezier(.4,0,.2,1); }
  72%       { transform: translate(24px,-4px) rotate(10deg); animation-timing-function: cubic-bezier(.34,1.3,.64,1); }
  100%      { transform: translate(0,0) rotate(0deg); }
}
@keyframes vDrift {
  0%        { transform: translate(0,0) scale(1) rotate(0deg); animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  26%       { transform: translate(10px,-38px) scale(1.06) rotate(6deg); animation-timing-function: ease-in-out; }
  52%       { transform: translate(-8px,-44px) scale(1.08) rotate(-5deg); animation-timing-function: cubic-bezier(.4,0,.4,1); }
  82%       { transform: translate(2px,4px) scale(0.96) rotate(2deg); animation-timing-function: cubic-bezier(.34,1.5,.64,1); }
  100%      { transform: translate(0,0) scale(1) rotate(0deg); }
}
@keyframes vDash {
  0%, 8%    { transform: translate(0,0) scale(1,1); animation-timing-function: cubic-bezier(.4,0,.9,.4); }
  16%       { transform: translate(-14px,2px) scale(0.94,1.05); animation-timing-function: cubic-bezier(.15,.9,.3,1); }
  34%       { transform: translate(96px,-10px) scale(1.12,0.94); animation-timing-function: ease-in-out; }
  50%       { transform: translate(104px,-6px) scale(1.06,1); animation-timing-function: cubic-bezier(.4,0,.7,.6); }
  76%       { transform: translate(-8px,6px) scale(1.08,0.92); animation-timing-function: cubic-bezier(.34,1.5,.64,1); }
  88%       { transform: translate(3px,-4px) scale(0.97,1.04); animation-timing-function: cubic-bezier(.4,0,.2,1); }
  100%      { transform: translate(0,0) scale(1,1); }
}
@keyframes vDip {
  0%        { transform: translate(0,0) rotate(0deg) scale(1,1); animation-timing-function: cubic-bezier(.4,0,.6,1); }
  24%       { transform: translate(-6px,26px) rotate(-7deg) scale(1.06,0.9); animation-timing-function: ease-in-out; }
  48%       { transform: translate(6px,30px) rotate(7deg) scale(1.06,0.9); animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  74%       { transform: translate(0,-12px) rotate(0deg) scale(0.96,1.06); animation-timing-function: cubic-bezier(.34,1.5,.64,1); }
  100%      { transform: translate(0,0) rotate(0deg) scale(1,1); }
}
@keyframes vPeek {
  0%, 20%   { transform: translate(0,0) scale(1); animation-timing-function: cubic-bezier(.4,0,.2,1); }
  38%       { transform: translate(0,10px) scale(0.86); animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  58%, 70%  { transform: translate(0,-14px) scale(1.16); animation-timing-function: cubic-bezier(.4,0,.4,1); }
  86%, 100% { transform: translate(0,0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .visor, .visor-frame, .visor-eye { animation: none; }
  .visor-move { animation: none !important; }   /* inline-set by gallery.js, hence the ! */
  .visor-frame--b { opacity: 0; }
}

/* .grid / .card - the run cards, mockup :405-437 (0bde115's `visibleRuns` grid). A
   single 16:9 media box per card - the concept/render pair, the body, the metrics row,
   the live band and the filter toolbar all gone; the strip's status chip and the agent
   faces say what the metrics row used to. The old .filters/.filter-group/.filter-meta
   toolbar and its mobile .m-menubar counterpart are deleted with it: the mock computes
   that same "filters"/"viewTabs"/"countLabel" set but never renders it
   (`showFilters: false`), and this file now matches. */
.grid {
  display: grid;
  /* Four across, stated outright (David, 2026-08-29: "4 cards should fill an entire
     row") - the mock's auto-fill minmax(480px,1fr) yields four only on a bare 1920px
     canvas; a real window's scrollbar leaves 1903px and the same rule snaps to three. */
  grid-template-columns: repeat(4, 1fr);
  background: var(--color-bg);
  border-bottom: 2px solid var(--color-neutral-500);
}
@media (max-width: 1500px) { .grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 1100px) { .grid { grid-template-columns: repeat(2, 1fr); } }
.card {
  display: block;
  position: relative;
  cursor: pointer;
  border-right: 2px solid var(--color-divider);
  border-bottom: 2px solid var(--color-divider);
  /* 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-media - the one shot, 16:9, clipped so the hover scale never spills past the
   card's own border. */
.card-media {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--color-neutral-300);
  overflow: hidden;
}
/* No hover ring: the mock carried an inset accent ring on hover and it shipped, and
   David looked at it live and said lose the orange outline (2026-08-29). The image
   scale below is the whole hover story now. */
/* No hover zoom either (David, 2026-09-04: "lets get rid of the hover zoom, and do play
   on hover"): the card is its still until the pointer is on it, then the clip plays. */
.card-render {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.card-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;
}
/* the card's pulse clip: PLAYS ON HOVER over the poster image, invisible until it has
   frames to show (.on lands on the "playing" event) and hidden again when the pointer
   leaves, so the poster owns the still state. Never scaled. */
.card-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity 120ms linear;
  pointer-events: none;
}
.card-video.on { opacity: 1; }
/* the engine tag (the Unreal / Blender mark beside the chips) is gone - David,
   2026-09-04: "remove the unreal and blender tags from the site". */

/* .card-strip - the bottom-left overlay: title plate, status chip, version chip, the
   unread badge when there is one, then the agent faces. `max-width:96%` and
   `flex-wrap:wrap` (mockup :415) so a long title still leaves the right edge clear
   rather than running the strip off the card. */
.card-strip {
  position: absolute; left: 0; bottom: 0; z-index: 2;
  display: flex; align-items: stretch; gap: 0; flex-wrap: wrap;
  max-width: 96%;
}
.card-plate {
  padding: 6px 10px 7px 10px;
  background: var(--color-bg);
  min-width: 0;
  display: flex;
  align-items: center;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 13px;                 /* was 17 (David, 2026-09-14: "shrink the card titles") */
  line-height: 1.1;
  letter-spacing: -0.015em;
  overflow-wrap: anywhere;
}
/* .card-story - the narrated status line (D7, story.js), a full-width row under
   the title/chips so the strip's first row keeps its shape. */
.card-story {
  flex-basis: 100%;
  padding: 4px 12px 6px;
  background: var(--color-bg);
  font-size: 11px;
  line-height: 1.3;
  color: var(--color-neutral-700);
}
.chip {
  display: flex;
  align-items: center;
  padding: 5px 9px;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  background: var(--color-bg);
  color: var(--color-neutral-700);
  white-space: nowrap;
}
/* .chip--unread - Task 8's badge, trailing the version chip when fmt.unreadCount(run,
   seen) is non-zero (deliberate deviation from the mock, which has no fixture with
   unread notes at all - see AGENTS.md's deviations list). Accent-filled so it reads as
   "new" at a glance rather than blending into the neutral status/version chips. */
.chip--unread { background: var(--color-accent); color: var(--color-bg); }
/* the version chip on a gold plate, so it reads apart from the unreal mark's chip
   (David, 2026-08-29: "the tag bg color not the font") */
.chip--version { background: var(--color-accent); color: var(--color-bg); }

/* .card-agent - one 22px sprite per running stage that has a face (fmt.runAgents).
   #232420 is the artwork's own dark plate, not a theme token - same reasoning as the
   visor's palette (crewbots.js's header comment): this is a character, not chrome that
   should re-tint with the site's theme. */
.card-agent {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px 6px;
  background: #232420;
}
.card-agent-sprite {
  position: relative;
  width: 22px;
  flex: 0 0 22px;
  aspect-ratio: 14 / 12;
  animation: bob 1s ease-in-out infinite;    /* duration overridden per-member in JS */
}
.card-agent-frame {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: repeat(14, 1fr);
  grid-template-rows: repeat(12, 1fr);
}
.card-agent-frame--a { animation: pxA 1s steps(1, end) infinite; }
.card-agent-frame--b { animation: pxB 1s steps(1, end) infinite; }
/* the hover tooltip - CSS-driven (:hover/:focus-within), not JS state, unlike the
   mock's onMouseEnter/onMouseLeave hoverAgent id. tabIndex on .card-agent (gallery.js)
   makes :focus-within reachable by keyboard. */
.card-agent-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  background: #232420;
  border-bottom: 3px solid #d3a03f;
  padding: 7px 11px 8px 11px;
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  z-index: 5;
}
/* only a pointer that can hover gets the hover state (David, 2026-09-08: "on mobile,
   for the cards get rid of the hover state"): a touch screen's emulated hover stuck
   the tip on the last-tapped face. gallery.js makes the same call for the clips. */
@media (hover: hover) {
  .card-agent:hover .card-agent-tip { opacity: 1; visibility: visible; }
}
.card-agent:focus-within .card-agent-tip { opacity: 1; visibility: visible; }
.card-agent-tip-name {
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700;
  color: #e8c680;
}
.card-agent-tip-role { font-size: 11px; color: #c8c7ba; margin-top: 3px; }
@media (prefers-reduced-motion: reduce) {
  .card-agent-sprite { animation: none; }
  .card-agent-frame { animation: none; }
  .card-agent-frame--b { opacity: 0; }
}

/* .team-* - the Meet the Team page (web/team.js, route #/team). Translated from
   design_template/Meet the Team.dc.html @ 0bde115, values verbatim. The mock is
   dark-fixed and this page follows it literally (hex, not tokens) rather than branch on
   the site's light/dark theme - the same choice already made for .card-agent's #232420
   above: these are the studio's characters, not chrome that should re-tint. */
.team-page { background: #1c1d1a; color: #e3e2d7; font-family: var(--font-body); }

.team-hero {
  padding: 44px 40px 30px 40px;
  border-bottom: 2px solid #78796c;
  background: #232420;
}
.team-back { margin-bottom: 18px; background: rgba(227, 226, 215, 0.14); color: #e3e2d7; }
.team-back:hover { background: #e3e2d7; color: #232420; }
.team-title {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 64px;
  line-height: 0.98;
  letter-spacing: -0.04em;
  max-width: 20ch;
}
.team-hero-row {
  display: flex;
  gap: 44px;
  margin-top: 24px;
  flex-wrap: wrap;
  align-items: flex-start;
}
.team-blurb {
  font-size: 15px;
  line-height: 1.55;
  color: #c8c7ba;
  max-width: 62ch;
  text-wrap: pretty;
}
.team-stats { display: flex; border-left: 2px solid rgba(227, 226, 215, 0.22); }
.team-stat { padding: 0 22px; border-right: 2px solid rgba(227, 226, 215, 0.12); }
.team-stat-v { font-family: var(--font-heading); font-weight: 800; font-size: 34px; line-height: 1; }
.team-stat-k {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #b0afa2;
  margin-top: 5px;
}

/* member grid - `minmax(330px,1fr)`, the dark band the cards sit on. */
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 24px;
  padding: 34px 40px 40px 40px;
  background: #1c1d1a;
}
.team-card {
  background: #232420;
  border-top: 3px solid #d3a03f;
  box-shadow: 0 12px 26px rgba(0, 0, 0, 0.34);
  display: flex;
  flex-direction: column;
  transition: transform 160ms ease, box-shadow 160ms ease;
}
.team-card:hover { transform: translateY(-4px); box-shadow: 0 20px 38px rgba(0, 0, 0, 0.45); }
.team-card-head { display: flex; align-items: flex-start; gap: 18px; padding: 22px 22px 18px 22px; }
.team-avatar { width: 76px; flex: 0 0 76px; background: #292a25; padding: 9px; }
/* The sprite's own OUTER box - sizing/bob only. The frame grid + pxA/pxB step-cycle are
   `.card-agent-frame`/`--a`/`--b` (above), shared verbatim with the card face. */
.team-avatar-sprite {
  position: relative;
  width: 100%;
  aspect-ratio: 14 / 12;
  animation: bob 1s ease-in-out infinite;    /* duration overridden per-member in JS */
}
.team-card-id { min-width: 0; padding-top: 2px; }
.team-name {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 30px;
  line-height: 1;
  letter-spacing: -0.025em;
}
.team-role {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #e8c680;
  font-weight: 700;
  margin-top: 8px;
  text-wrap: pretty;
}
.team-handle { font-family: var(--font-mono); font-size: 10px; color: #8f8e82; margin-top: 6px; }
.team-duty { padding: 0 22px 18px 22px; font-size: 13px; line-height: 1.55; color: #c8c7ba; text-wrap: pretty; }
.team-footer {
  margin-top: auto;
  padding: 14px 22px 18px 22px;
  border-top: 2px solid rgba(227, 226, 215, 0.16);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.team-footer-row { display: flex; align-items: baseline; gap: 10px; }
.team-footer-label {
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8f8e82;
  width: 44px;
  flex: 0 0 44px;
}
.team-footer-value { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #e3e2d7; }
.team-footer-value--mono {
  font-family: var(--font-mono);
  letter-spacing: normal;
  text-transform: none;
  word-break: break-all;
}
.team-footer-value--writes { color: #e8c680; }

/* hand-off strip - the ORDER, 22px sprites on #292a25 chips, the closing redo line. */
.team-handoff { padding: 34px 40px 60px 40px; background: #1c1d1a; border-top: 2px solid #78796c; }
.team-handoff-title { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: #e8c680; font-weight: 700; }
.team-handoff-row { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin-top: 14px; }
.team-handoff-chip { display: flex; align-items: center; gap: 9px; padding: 8px 12px; background: #292a25; }
.team-handoff-name { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: #e3e2d7; font-weight: 700; white-space: nowrap; }
.team-handoff-note { font-size: 12px; color: #8f8e82; margin-top: 16px; letter-spacing: 0.03em; }

@media (prefers-reduced-motion: reduce) {
  .team-avatar-sprite { animation: none; }
}

@media (max-width: 720px) {
  .team-hero, .team-grid, .team-handoff { padding-left: 20px; padding-right: 20px; }
  .team-title { font-size: 40px; }
  .team-hero-row { gap: 24px; }
}

/* .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: 0;
  background: var(--color-neutral-200);
  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, filling whatever #app
   leaves after the wedge header. `background` is the divider token so the 2px gaps in
   .vp and .gr below read as rules between the panes, matching the mockup's own trick
   rather than a border on every pane. */
.d-page { height: 100%; display: flex; flex-direction: column; overflow: hidden; background: var(--color-divider); }

/* .d-top - the bar above the tabs: back link, the run's identity (name + dir + tags,
   moved up from the retired left column - mockup :536-548), a spacer, the version note,
   and the scene-version stepper. */
.d-top {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 20px;
  background: var(--color-bg);
  flex-wrap: wrap;
}
.d-ident { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.d-ident-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.d-ident .tags { margin-top: 0; gap: 3px; }
.d-ident .tag { padding: 3px 6px; }
.d-id { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-600); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.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: 0;
  background: var(--color-neutral-200);
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
/* G6: was `var(--color-neutral-200)`, byte-identical to the base rule above (:628) -
   the CSS sweep left this stepper with no hover feedback at all. One step darker on
   the same neutral scale the other flat controls already use for hover (.btn--chip,
   .btn--ghost, .d-gh-act, .queue-btn all step 200 -> 300 on :hover). */
.d-verstep:hover { background: var(--color-neutral-300); }
/* 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: var(--color-neutral-200); }
.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: 0;
  background: var(--color-neutral-200);
  color: var(--color-text);
  cursor: pointer;
  text-align: left;
}
/* G6: was `border-color: var(--color-accent)`, but the base rule's border became
   `border: 0` in the CSS sweep - nothing left to colour, so this toggle lost hover
   feedback too. Same fix as .d-verstep above: a one-step darker fill on the neutral
   scale, matching this control's own base background (:652) the same way the
   stepper's hover matches its own. */
.d-vertoggle:hover { background: var(--color-neutral-300); }
.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-tabs - the Viewport | Pipeline row (mockup :591-595). */
.d-tabs {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 0 20px 10px 20px;
  background: var(--color-bg);
  border-bottom: 2px solid var(--color-neutral-500);
}
.d-tab {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 8px 14px;
  background: var(--color-neutral-200);
  color: var(--color-neutral-700);
  border: 0;
  cursor: pointer;
  text-align: left;
}
.d-tab[data-on="true"] { background: var(--color-text); color: var(--color-bg); }
.d-tab:hover { background: var(--color-accent); color: var(--color-bg); }
.d-tab:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* .d-verpanel - the version-history dropdown, mockup :560-587 (bar anchor only since
   the viewport redesign retired the concept-image tag). */
.d-verpanel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 320px;
  max-height: 60dvh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  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-neutral-200);
  color: var(--color-text);
  border: 0;
  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: var(--color-neutral-200);
  color: var(--color-text);
  border: 0;
  cursor: pointer;
}
.d-verpanel-close:hover { background: var(--color-neutral-300); }

/* .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); }
.live-story { flex-basis: 100%; font-size: 13px; color: var(--color-text); }

/* .checkpoint-error - a failed note write, surfaced verbatim in the viewport panel
   (named for the band that first carried it, long gone). */
.checkpoint-error { font-size: 11px; line-height: 1.35; color: var(--color-fail); margin-top: 8px; }

/* .d-name - the run's name, in the top bar since the viewport redesign (mockup :540):
   19px, a line in the chrome rather than a headline in a column. */
.d-name { font-family: var(--font-heading); font-weight: 800; font-size: 19px; line-height: 1.05; letter-spacing: -0.025em; margin: 0; min-width: 0; overflow-wrap: anywhere; }

.tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; }
.tag { padding: 5px 8px; background: var(--color-neutral-200); border: 0; 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-gh-row - the standing "am I signed in" answer above the cards (Plumb.dc.html:611-620).
   The per-card prompt still exists; this row is what stops a reader having to infer it from
   three identical cards. */
.d-gh-row {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 20px 12px 20px;
}
.d-gh-who { display: flex; align-items: center; gap: 7px; min-width: 0; }
.d-gh-dot { width: 7px; height: 7px; flex: none; background: var(--color-neutral-500); }
.d-gh-dot[data-on="1"] { background: var(--color-accent); }
.d-gh-label {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--color-neutral-700);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.d-gh-act {
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 5px 8px;
  background: var(--color-neutral-200);
  color: var(--color-text);
  border: 0;
  text-decoration: none;
  white-space: nowrap;
}
.d-gh-act:hover { background: var(--color-neutral-300); }


/* .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); }
.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; }

/* .log - the event stream, mockup :561-570. The ONLY scrolling element in column 3 (see
   .d-col3 above). It no longer fills the column: the stream is a collapsed section
   inside .d-col3-scroll now, capped at 340px (Plumb.dc.html:724) so it scrolls
   within itself rather than pushing the checks above it off the top. */
.log {
  max-height: 340px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
  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 { 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); }

.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); }

/* .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); }

/* 6. Queue page -----------------------------------------------------------
   web/queue.js - translated from design_template/Plumb.dc.html:238-345 (the mock's
   queue markup, which runs against a fictional gate simulation) rather than copied
   verbatim: this page reads real jobqueue.jobs() records, so its numbers can be honest
   in places the mock could afford to invent (a worker count, a per-pass duration
   estimate) - see fmt.js's queueStats/jobLiveText for what that means for the two
   stat tiles that read "-" here. Tonal fills throughout, matching the sweep above -
   this page never had a stroke-based draft to begin with. */
.queue-page { padding: 22px 32px 44px 32px; }
.queue-top { margin-bottom: 14px; }
.queue-head-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: 6px; }
.queue-title { font-family: var(--font-heading); font-weight: 800; font-size: 30px; letter-spacing: -0.025em; }
/* The 2px-gap-over-background trick, same as .facts/.hero-pair above - a grid with no
   outer border, its cells' shared edges drawn by the gap alone. */
.queue-stats { display: grid; grid-template-columns: repeat(4, minmax(120px, 1fr)); gap: 2px; background: var(--color-divider); }
.queue-stat { background: var(--color-bg); padding: 9px 12px; }
.queue-stat-v { font-family: var(--font-heading); font-weight: 800; font-size: 22px; line-height: 1; }
.queue-stat-k { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-700); margin-top: 4px; }
.queue-stat-of { font-size: 9px; letter-spacing: 0.06em; color: var(--color-neutral-600); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.queue-note { font-size: 13px; line-height: 1.5; color: var(--color-neutral-700); max-width: 70ch; margin-bottom: 20px; }
/* .queue-gh-row reuses detail.js's .d-gh-row/.d-gh-who/.d-gh-dot/.d-gh-label/.d-gh-act
   classes (style section 4) rather than a second copy of the same five rules - only
   the outer spacing differs, hence this one override. */
.queue-gh-row { padding: 0 0 16px 0; }

.queue-section-head { display: flex; align-items: baseline; gap: 10px; padding: 22px 0 8px 0; border-bottom: 2px solid var(--color-neutral-500); flex-wrap: wrap; }
.queue-top + .queue-head .queue-section-head,
.queue-head + .queue-gh-row + .queue-section-head { padding-top: 8px; }
.queue-section-label { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--color-accent-700); }
.queue-section-meta { margin-left: auto; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.queue-empty { padding: 14px 0; font-size: 12px; color: var(--color-neutral-600); border-bottom: 2px solid var(--color-divider); }

/* A uuid pill and a redo-steps tag: both fills, not chips with a border - the exact
   sweep applied to .tag/.chip above, just introduced fresh here since this page has no
   older stroke-based draft to migrate. */
.queue-uuid { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-600); padding: 2px 5px; background: var(--color-neutral-200); user-select: all; }
.queue-tag { padding: 3px 6px; background: var(--color-neutral-200); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); white-space: nowrap; }
.queue-badge { padding: 3px 6px; background: var(--color-accent); color: var(--color-bg); font-size: 9px; font-weight: 700; letter-spacing: 0.06em; }

/* A queue row is a real <button> (web/queue.js's rowButton) styled to read as a row -
   keyboard-reachable, unlike the mockup's onClick <div>. */
.queue-row {
  display: grid;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 13px 0;
  border: 0;
  border-bottom: 2px solid var(--color-divider);
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.queue-row--running { grid-template-columns: minmax(0, 1fr) 240px 130px; }
.queue-row--running:hover { background: var(--color-neutral-100); }
.queue-row-head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.queue-name { font-family: var(--font-heading); font-weight: 800; font-size: 16px; letter-spacing: -0.01em; }
.queue-status { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; }
.queue-status--running { color: var(--color-accent-700); }
.queue-row-meta { display: flex; align-items: baseline; gap: 9px; margin-top: 3px; flex-wrap: wrap; }
.queue-stage { font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-600); }
.queue-progress-wrap { display: flex; flex-direction: column; gap: 5px; }
/* .queue-progress reuses .live-bar/.live-bar > i (style section 4) - the same
   indeterminate-sweep bar the detail page's live band already draws, so "no live data
   yet" reads identically wherever it appears rather than inventing a second bar style. */
.queue-progress { height: 8px; }
.queue-progress-meta { display: flex; justify-content: space-between; gap: 8px; font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.queue-eta { justify-self: end; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent-700); white-space: nowrap; }

.queue-row--queued { grid-template-columns: 36px minmax(0, 1fr) auto; align-items: start; }
.queue-pos { font-family: var(--font-mono); font-size: 16px; color: var(--color-neutral-600); padding-top: 1px; }
.queue-row--queued .queue-next--first,
.queue-row--queued .queue-pos:first-child { color: var(--color-accent-700); }
.queue-row-body { display: block; min-width: 0; border: 0; background: transparent; font: inherit; color: inherit; text-align: left; cursor: pointer; padding: 0; }
.queue-next { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-600); }
.queue-next--first { color: var(--color-accent-700); }
.queue-passinfo { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.queue-direction { font-size: 12px; line-height: 1.45; color: var(--color-neutral-800); margin-top: 4px; }
.queue-row-foot { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.queue-by { font-size: 10px; letter-spacing: 0.06em; color: var(--color-neutral-600); }
.queue-wait { font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-700); }

.queue-actions { display: flex; flex-direction: column; gap: 5px; justify-self: end; align-items: flex-end; }
.queue-btn {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 6px 8px;
  background: var(--color-neutral-200);
  color: var(--color-text);
  border: 0;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}
.queue-btn:hover:not(:disabled) { background: var(--color-neutral-300); }
.queue-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.queue-btn--cancel { background: var(--color-accent-100); color: var(--color-accent-700); }
.queue-btn--cancel:hover:not(:disabled) { background: var(--color-accent); color: var(--color-bg); }
.queue-action-error { font-size: 10px; line-height: 1.3; color: var(--color-fail); max-width: 160px; text-align: right; }

/* "Waiting on you" (Piece S3-5): a flex row rather than the grid the other two sections
   use - the mock (Plumb.dc.html:322-330) lays this one out as name/pass/notes/review
   wrapping freely, with no progress bar or action column to align against. */
.queue-row--blocked { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.queue-row--blocked:hover { background: var(--color-neutral-100); }
.queue-status--checkpoint { color: var(--color-accent-700); }
.queue-notes-label { font-size: 11px; color: var(--color-neutral-600); }
.queue-review { margin-left: auto; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: var(--color-text); white-space: nowrap; }

@media (max-width: 720px) {
  .queue-page { padding: 16px; }
  .queue-stats { grid-template-columns: 1fr 1fr; }
  .queue-row--running { grid-template-columns: 1fr; row-gap: 10px; }
  .queue-row--queued { grid-template-columns: 24px minmax(0, 1fr); }
  .queue-actions { grid-column: 1 / -1; flex-direction: row; justify-self: start; margin-top: 8px; }
  .queue-eta { justify-self: start; }
}

/* 7. Mobile ---------------------------------------------------------------
   Translated from design_template/Plumb Mobile.dc.html - the phone artboard. One
   breakpoint: below 720px the wedge flattens to the artboard's stacked slab over an
   accent strip, the hero headline wraps around its inline sprite, the stats become the
   artboard's 2x2 ruled grid, and the detail page unrolls from the fixed three-column
   instrument panel into the artboard's single scrolling column. The artboard's sticky
   filter/view menu (.m-menubar and its hamburger) is gone along with .filters - see
   the card section's own comment. */

@media (max-width: 720px) {
  /* Header - artboard :117-128. The notch is a desktop gesture; on the phone the slab
     sits square on the accent plane and the gold reads as a 4px strip beneath it. */
  /* z-index 50: the drop-down must sit over the hero's visor, whose slot is z 30 in the
     page's own stacking (David, 2026-09-14: "the robot draws on top of the drop down") */
  .hdr { clip-path: none; padding-bottom: 4px; position: relative; z-index: 50; }
  /* ONE LINE, ONE MENU (David, 2026-09-14: "move all buttons at the top to a drop
     down, so we can shrink the title bar"): the slab is the mark, the word and a menu
     button; the actions drop down beneath it when the menu opens. */
  .hdr-slab { clip-path: none; padding: 8px 12px 8px 14px; gap: 10px; flex-wrap: nowrap; }
  .hdr-brand { gap: 9px; }
  .hdr-brand svg { width: 26px; height: 26px; }
  .hdr-word { font-size: 20px; }
  .hdr-tag { font-size: 8px; padding-left: 9px; }   /* the tag line stays (David, 2026-09-14) */
  .btn--onslab { margin-top: 0; padding: 10px 12px; font-size: 11px; }
  .hdr-menu {
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 40px; height: 34px; padding: 0 9px; margin-left: auto;
    background: transparent; border: 2px solid var(--color-bg); cursor: pointer;
  }
  .hdr-menu span { display: block; height: 2px; background: var(--color-bg); }
  .hdr--menu-open .hdr-menu { background: var(--color-accent); border-color: var(--color-accent); }
  .hdr--menu-open .hdr-menu span { background: var(--color-text); }
  .hdr-actions {
    display: none;
    position: absolute; right: 0; top: 100%; z-index: 20;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--color-text); padding: 6px 0 8px 0; min-width: 200px;
    box-shadow: var(--shadow-lg);
  }
  .hdr--menu-open .hdr-actions { display: flex; }
  .hdr-actions .btn--onslab, .hdr-add .as-open { border: 0; border-top: 2px solid rgba(255,255,255,0.12); width: 100%; margin: 0; padding: 12px 16px; font-size: 11px; }
  .hdr-add { display: block; }
  .hdr-add .as-bar { display: block; margin: 0; }

  /* Hero - artboard :134-135. The sprite is a WORD in the sentence there too, so the
     line stops being a one-row flexbox and wraps as text around an inline-block slot. */
  .hero { display: block; padding: 28px 16px 18px 16px; }
  .hero-line { display: block; white-space: normal; font-size: 36px; line-height: 1.06; text-wrap: pretty; }
  .hero-visor { display: inline-block; width: 52px; vertical-align: -9px; margin: 0 4px; }

  /* Stats - artboard :136-143: a 2x2 grid whose 1px gaps on a divider background draw
     the rules, each cell back on the band's own field colour. */
  .hero-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: color-mix(in srgb, var(--field-fg) 22%, transparent);
    border-left: 0;
    margin-top: 18px;
  }
  .stat { background: var(--field-bg); border-right: 0; padding: 11px 12px; }
  .stat-v { font-size: 26px; }
  .stat-k { margin-top: 6px; white-space: normal; }
  /* THE PHONE GETS TO THE PICTURES FASTER (David, 2026-09-14: "get rid of the grid
     showing how many scenes etc, so you get to looking at images faster"; "get rid of
     rules button"; "mov the add scene button next to queue"): no stats grid, no Rules,
     and Add scene lives in the header beside Queue instead of above the grid. */
  .hero-stats { display: none; }
  .hdr-actions a[href="#/rules"] { display: none; }
  .as-host { display: none; }
  .hdr-add .as-open { background: transparent; color: var(--color-bg); text-align: left; display: flex; }
  .hdr-add .as-open:hover:not(:disabled) { background: var(--color-accent); color: var(--color-text); }

  /* Cards - one column, single-image cards keep their own 16:9 box and overlay strip;
     only the grid and the plate/chip sizing need to shrink for a narrow screen. */
  .grid { grid-template-columns: 1fr; }
  /* the card's title plate and chips smaller on a phone, so the picture is the card
     (David, 2026-09-14: "make the card title tag and version smaller") */
  .card-plate { font-size: 12px; padding: 4px 8px 5px 8px; }
  .card-strip .chip { font-size: 7px; padding: 3px 6px; letter-spacing: 0.1em; }
  .chip { font-size: 8px; padding: 4px 7px; }

  /* Detail - on the phone the fixed-height shell unrolls into normal flow and #app
     scrolls the lot; the tab panes do the same in their own media block at the end of
     this file. Only the event log keeps its own scrollbar, so the stream cannot
     swallow the page. */
  .d-page { height: auto; display: block; overflow: visible; background: var(--color-bg); }
  /* THE LEDGER IS THE EXCEPTION (David, 2026-09-14: "the chat in ledger is not
     scrollable on mobile, should work like mobile discord"): its shell stays the
     fixed-height column, so the stream scrolls between a fixed bar and composer. */
  .d-page--ledger { height: 100%; display: flex; flex-direction: column; overflow: hidden; }
  .d-top { padding: 10px 16px; }
  .d-gh-row { padding: 0 16px 12px 16px; }
  .log { max-height: 300px; overflow-x: hidden; }
  .log-msg { overflow-wrap: anywhere; }
  .facts { grid-template-columns: 1fr 1fr; }
  .d-verpanel { width: min(320px, calc(100vw - 32px)); }

}

/* --- the node panel's head (pipeline tab) ------------------------------------------- */
.step-eyebrow {
  font-size: 10px; line-height: 1.4; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--color-accent-700); font-weight: 700;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}

.step-title {
  display: flex; align-items: baseline; gap: 10px; margin-top: 4px; flex-wrap: wrap;
}
.step-title-name {
  font-family: var(--font-heading); font-weight: 800; font-size: 22px;
  line-height: 1.05; letter-spacing: -0.02em;
}
.step-title-dur {
  margin-left: auto; font-family: var(--font-mono); font-size: 11px;
  color: var(--color-neutral-600);
}
.step-lead {
  margin: 6px 0 0 0; font-size: 12px; line-height: 1.45;
  color: var(--color-neutral-800); text-wrap: pretty;
}

/* .semseg - the large hoverable/clickable segmentation viewer on the percept step
   (replacing the tiny percept.semseg.png thumbnail for that one purpose - the thumbnail
   itself stays in .art-grid below it, still pin-able). Full panel width, a region per
   detected segment positioned as PERCENTAGES so it tracks the image regardless of the
   panel's own width - see web/fmt.js's semsegRegions for why those are fractions of the
   record's own size_px and never a hardcoded frame size. */
.semseg { margin: 4px 0 16px 0; }
.semseg-frame {
  position: relative; display: block; width: 100%;
  background: var(--color-neutral-200); overflow: hidden;
}
.semseg-img { display: block; width: 100%; height: auto; pointer-events: none; }
/* Positioned and sized entirely by JS (left/top/width/height, all %) from fmt.semsegRegions
   - each box covers only its own segment's bbox, so the image stays hoverable/clickable
   everywhere a box does not cover, per the design's own constraint. */
.semseg-region {
  position: absolute; border: 2px solid transparent; box-sizing: border-box;
  cursor: pointer;
}
.semseg-region:hover, .semseg-region[data-selected="true"] {
  border-color: var(--color-accent); background: rgba(138, 106, 31, 0.12);
}
.semseg-region[data-selected="true"] { border-color: var(--color-accent-700); }
/* The hover chip - prompt/score/matched-id, positioned just inside its region's own
   top-left corner rather than centered, so it never has to guess the region's size to
   stay legible when the region itself is small. */
.semseg-chip {
  position: absolute; left: 0; top: 0; max-width: 260px;
  padding: 4px 7px; background: var(--color-text); color: var(--color-bg);
  font-family: var(--font-mono); font-size: 10px; line-height: 1.4;
  white-space: normal; pointer-events: none; z-index: 2;
}
/* The click-through info card, below the image - the matched inventory entry, or an
   unmatched segment's own prompt/score under status "no inventory match". */
.semseg-card { margin-top: 10px; border: 2px solid var(--color-divider); }
.semseg-card-title {
  padding: 8px 10px; font-family: var(--font-mono); font-size: 12px; font-weight: 700;
  color: var(--color-text); background: var(--color-neutral-100);
  border-bottom: 2px solid var(--color-divider);
}
.semseg-card-rows { display: flex; flex-direction: column; }
.semseg-card-row {
  display: grid; grid-template-columns: 120px 1fr; gap: 10px;
  padding: 6px 10px; border-bottom: 2px solid var(--color-divider);
}
.semseg-card-row:last-child { border-bottom: 0; }
.semseg-card-k { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-700); }
.semseg-card-v { font-family: var(--font-mono); font-size: 12px; color: var(--color-text); text-wrap: pretty; }

/* .camdiag - the camsolve step's own large image, same full-panel-width treatment as
   .semseg-frame/.semseg-img above, minus the hoverable regions: this picture already
   carries its own drawn lines and verdict card baked in (percept.draw_camsolve_diag), so
   there is nothing left here for JS to overlay. */
.camdiag { margin: 4px 0 16px 0; }
.camdiag-img {
  display: block; width: 100%; height: auto;
  background: var(--color-neutral-200); border: 2px solid var(--color-divider);
}

/* The camera card's one standout row - a refusal reason, same accent treatment
   .check-row[data-verdict="flagged"] already gives a fired check's key. */
.fact[data-refused] .fact-k, .fact[data-refused] .fact-v { color: var(--color-accent-700); }

/* .inv-table - the inventory step's objects table. Fixed columns rather than the
   semseg card's k/v grid (that layout fits one record; this is one row per object), each
   column left-aligned mono text matching .fact-v's own font choice for tabular data. */
.inv-table { border-top: 2px solid var(--color-divider); margin-top: 4px; }
.inv-row {
  display: grid; grid-template-columns: 1.1fr 1.3fr 1.3fr 46px 110px; gap: 10px;
  align-items: center; padding: 6px 4px; border-bottom: 2px solid var(--color-divider);
  font-family: var(--font-mono); font-size: 11px;
}
.inv-row--head {
  font-family: var(--font-body); font-size: 9px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--color-neutral-700);
}
.inv-id { color: var(--color-text); }
.inv-category, .inv-dims { color: var(--color-neutral-700); }
.inv-bbox { text-align: center; }
/* Undecorated status text, same as .check-row's own plain-coloured verdict word - no
   background pill, the colour alone carries matched/no_mask/count_disagrees apart. */
.inv-status[data-status="matched"]         { color: var(--color-ok); }
.inv-status[data-status="no_mask"]         { color: var(--color-neutral-600); }
.inv-status[data-status="count_disagrees"] { color: var(--color-accent-700); }

.inv-measurements {
  font-family: var(--font-mono); font-size: 11px; color: var(--color-neutral-700);
  margin: 2px 0 10px;
}

/* the brief, verbatim, on the prompt node's panel */
.prompt-text {
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1.6;
  color: var(--color-neutral-800); white-space: pre-wrap;
  margin: 4px 0 10px; max-width: 65ch;
}

.note-pin {
  width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  background: var(--color-accent); color: var(--color-bg); font-size: 10px; font-weight: 700;
}
.note-body { min-width: 0; display: block; }
.note-who {
  display: block; font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--color-neutral-600);
}
.note-text {
  display: block; font-size: 12px; line-height: 1.4; color: var(--color-text);
  margin-top: 3px; text-wrap: pretty;
}
.empty-note {
  padding: 12px 13px; background: var(--color-neutral-100); font-size: 11px;
  line-height: 1.45; color: var(--color-neutral-700); text-wrap: pretty;
}

/* The pass chips. A run accumulates a pass per invocation and each has its own render,
   notes and checks - so which one you are reading is a choice, and these are buttons. */
.pass-chips { display: flex; gap: 4px; flex-wrap: wrap; margin: 12px 0 10px 0; }
.pass-chip {
  display: flex; align-items: center; gap: 5px; padding: 5px 8px;
  background: var(--color-neutral-200); color: var(--color-text); border: 0;
  font-family: var(--font-body); font-size: 10px; letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 700; cursor: pointer;
}
.pass-chip:hover { background: var(--color-neutral-300); }
.pass-chip:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.pass-chip[data-selected="1"] { background: var(--color-text); color: var(--color-bg); }
.pass-dot { width: 6px; height: 6px; flex: none; background: var(--color-neutral-500); }
.pass-dot[data-kind="running"]    { background: var(--color-accent); }
.pass-dot[data-kind="checkpoint"] { background: var(--color-accent-700); }
.pass-chip[data-selected="1"] .pass-dot[data-kind="checkpoint"] { background: var(--color-accent); }


/* --- the viewport tab (2026-08-27 redesign, mockup :610-756) ------------------------
   A fixed-height working surface: the hero viewer fills, the Content shelf sits along
   the bottom of the left half, the 328px details panel scrolls on the right. The 2px
   gap on .d-page's divider background draws the rule between the halves, same trick
   the old columns used. */
.vp { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 328px; gap: 2px; }
.vp-left { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--color-bg); }
.vp-stage { flex: 1 1 0; min-height: 0; overflow: hidden; display: flex; flex-direction: column; padding: 12px 16px 0 16px; }
.vp-hero { position: relative; flex: 1 1 0; min-height: 96px; background: var(--color-inset); border: 1px solid var(--color-neutral-200); box-sizing: border-box; overflow: hidden; }
/* Resonance's pan/zoom canvas (their .ms-imgcard, ported): the inner layer carries the
   translate+scale transform, the img is CONTAINED (their 2026-08-14 crop lesson - the
   whole image at fit, letterboxed on the tight axis), and pins ride a layer sized to
   the image's contain box so a stored fraction stays pixel-true at any zoom. */
.vp-hero[data-placeable="true"] { cursor: crosshair; }
.vp-hero[data-pannable="true"] { cursor: grab; }
.vp-hero[data-panning="true"] { cursor: grabbing; }
.vp-hero-inner { position: absolute; inset: 0; }
/* the banked clip's player. `contain` for the same reason the still hero uses
   it - a 16:9 render in a taller panel must letterbox, never crop. Black
   behind it rather than --color-inset: a video's own bars read as part of the
   picture, and a pale surround makes the frame look mismatched. */
.vp-hero--video { background: #000; }
.vp-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; background: #000; }
.vp-hero-img { width: 100%; height: 100%; object-fit: contain; display: block; user-select: none; }
.vp-pinlayer { position: absolute; pointer-events: none; }
.vp-hero-missing {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(45deg, var(--color-inset) 0 8px, var(--color-neutral-100) 8px 16px);
  color: var(--color-neutral-600); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
}
.vp-hero-chips { position: absolute; left: 0; top: 0; display: flex; align-items: stretch; z-index: 2; }
.vp-hero-chip { padding: 5px 8px; background: var(--color-bg); color: var(--color-neutral-700); font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700; }
/* The pin markers: numbered accent squares centred on their point, counter-scaled by
   the canvas (--vz-inv, set by viewport.js's apply) so zooming the image never grows
   the marker itself. */
.vp-pin, .vp-pin-pending {
  position: absolute;
  width: 22px; height: 22px;
  transform: translate(-50%, -50%) scale(var(--vz-inv, 1));
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-body); font-size: 10px; font-weight: 700;
}
.vp-pin { background: var(--color-accent); color: var(--color-bg); }
.vp-pin-pending { background: var(--color-bg); border: 2px solid var(--color-accent); box-sizing: border-box; }
/* the zoom readout / reset chip - hidden at fit, bottom-right (Resonance's zoomchip) */
.vp-zoomchip {
  position: absolute; right: 6px; bottom: 6px; z-index: 2;
  padding: 3px 8px; border: 0; cursor: pointer;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.05em;
  background: var(--color-text); color: var(--color-bg);
}
.vp-zoomchip:hover { background: var(--color-accent); }
.vp-hero-cap { flex: none; display: flex; align-items: center; gap: 10px; padding: 7px 0 10px 0; flex-wrap: wrap; }
.vp-hero-file { font-family: var(--font-mono); font-size: 13px; color: var(--color-text); }
.vp-hero-meta { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); }

/* The type colour map - the mock's bar(): Render accent, Mesh text, Material
   neutral-600, Texture neutral-400, everything else neutral-300. */
.vp-hero-chip--role, .vp-panel-type, .vp-card-cat { background: var(--color-neutral-300); color: var(--color-text); }
[data-type="Render"].vp-hero-chip--role, [data-type="Render"].vp-panel-type, [data-type="Render"].vp-card-cat { background: var(--color-accent); color: var(--color-bg); }
[data-type="Mesh"].vp-hero-chip--role, [data-type="Mesh"].vp-panel-type, [data-type="Mesh"].vp-card-cat { background: var(--color-text); color: var(--color-bg); }
[data-type="Material"].vp-hero-chip--role, [data-type="Material"].vp-panel-type, [data-type="Material"].vp-card-cat { background: var(--color-neutral-600); color: var(--color-bg); }
[data-type="Texture"].vp-hero-chip--role, [data-type="Texture"].vp-panel-type, [data-type="Texture"].vp-card-cat { background: var(--color-neutral-400); color: var(--color-text); }

/* --- the content shelf --------------------------------------------------------------- */
.vp-shelf { flex: none; border-top: 2px solid var(--color-neutral-500); background: var(--color-neutral-100); }
.vp-shelf-head { display: flex; align-items: center; gap: 10px; padding: 9px 16px 0 16px; flex-wrap: wrap; }
.vp-shelf-filters { display: flex; gap: 2px; flex-wrap: wrap; }
.vp-filter {
  padding: 5px 9px; background: var(--color-neutral-200); color: var(--color-neutral-700);
  border: 0; cursor: pointer; font-family: var(--font-body); font-size: 9px;
  letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; white-space: nowrap;
  text-align: left;
}
.vp-filter[data-on="true"] { background: var(--color-text); color: var(--color-bg); }
.vp-filter:hover { background: var(--color-accent); color: var(--color-bg); }
.vp-shelf-count { margin-left: auto; font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.vp-shelf-toggle {
  display: flex; align-items: center; gap: 6px; font-family: var(--font-body);
  font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700;
  padding: 5px 8px; background: var(--color-neutral-200); color: var(--color-text);
  border: 0; cursor: pointer; text-align: left;
}
.vp-shelf-toggle:hover { background: var(--color-accent); color: var(--color-bg); }
.vp-shelf-toggle[data-collapsed="true"] svg { transform: rotate(180deg); }
.vp-shelf-body {
  height: min(38vh, 332px); overflow-x: auto; overflow-y: auto;
  padding: 9px 16px 13px 16px; transition: height 160ms ease-out;
  scrollbar-width: thin; scrollbar-color: var(--color-neutral-500) transparent;
}
.vp-shelf-body[data-collapsed="true"] { height: 119px; overflow-y: hidden; }
/* Resonance's asset grid: square tiles, auto-fill columns; collapsed keeps ONE
   horizontally scrolling row of fixed-width tiles instead. */
.vp-shelf-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 9px; align-content: start; }
.vp-shelf-row[data-collapsed="true"] { display: flex; gap: 9px; align-items: flex-start; flex-wrap: nowrap; }
.vp-shelf-row[data-collapsed="true"] .vp-card { width: 88px; flex: none; }

/* One tile - Resonance's .asset card in Plumb's tokens: a SQUARE thumb showing the
   whole asset (contain, their 2026-08-14 crop lesson), the type's colour as a small
   square top-left (their category square), a mono badge bottom-right, the name on its
   own line below. Selection is the accent border on the thumb, exactly theirs.
   appearance: none stays as belt-and-braces for a bare <button>; the frame David saw
   was this very rule going MISSING - a splice fused its selector into the collapsed-row
   rule above, so no tile ever got its reset and Chrome painted the native button face. */
.vp-card {
  appearance: none; -webkit-appearance: none;
  position: relative; display: flex; flex-direction: column; gap: 5px;
  padding: 0; border: 0; background: transparent; cursor: pointer; text-align: left;
  font-family: var(--font-body); min-width: 0;
}
.vp-card:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.vp-card-thumb {
  position: relative; display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1; overflow: hidden; background: var(--color-inset);
  border: 1px solid var(--color-neutral-200); box-sizing: border-box;
  transition: border-color 0.12s;
}
.vp-card:hover .vp-card-thumb { border-color: var(--color-neutral-400); }
.vp-card[data-on="true"] .vp-card-thumb,
.vp-card[data-on="true"]:hover .vp-card-thumb {
  border: 2px solid var(--color-accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 30%, transparent);
}
.vp-card-still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
.vp-card-spin { position: absolute; inset: 0; background-repeat: no-repeat; opacity: 0; transition: opacity 0.1s; pointer-events: none; }
.vp-card-thumb:hover { cursor: ew-resize; }
.vp-card-missing {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center;
  background: repeating-linear-gradient(45deg, var(--color-inset) 0 6px, var(--color-neutral-100) 6px 12px);
  color: var(--color-neutral-600); font-size: 8px; letter-spacing: 0.12em; text-transform: uppercase;
}
.vp-card-cat { position: absolute; left: 4px; top: 4px; width: 6px; height: 6px; }
.vp-card-badge {
  position: absolute; right: 4px; bottom: 4px; padding: 1px 4px;
  font-family: var(--font-mono); font-size: 8px; font-weight: 600;
  background: rgba(7, 7, 8, 0.7); color: #e3e2d7;
  max-width: calc(100% - 8px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vp-card-pins { position: absolute; right: 4px; top: 4px; padding: 1px 5px; background: var(--color-accent); color: var(--color-bg); font-family: var(--font-mono); font-size: 8px; font-weight: 700; }
.vp-card-name { display: block; font-family: var(--font-mono); font-size: 9.5px; color: var(--color-neutral-700); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vp-card[data-on="true"] .vp-card-name { color: var(--color-accent-700); }

/* --- the details panel ---------------------------------------------------------------- */
.vp-panel {
  min-width: 0; min-height: 0; overflow-y: auto; background: var(--color-neutral-100);
  padding: 16px; scrollbar-width: thin; scrollbar-color: var(--color-neutral-500) transparent;
}
.vp-panel-type { display: inline-block; padding: 4px 8px; font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; white-space: nowrap; }
.vp-panel-file { font-family: var(--font-mono); font-weight: 400; font-size: 16px; line-height: 1.2; margin: 10px 0 0 0; overflow-wrap: anywhere; color: var(--color-text); }
.vp-panel-kind { margin-top: 5px; font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); }
.vp-props { margin-top: 9px; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 5px 10px; align-items: baseline; }
.vp-prop-k { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); white-space: nowrap; }
.vp-prop-v { font-family: var(--font-mono); font-size: 11px; color: var(--color-text); overflow-wrap: anywhere; }
.vp-panel-path { margin-top: 9px; padding: 7px 8px; background: var(--color-neutral-200); font-family: var(--font-mono); font-size: 9px; color: var(--color-neutral-700); overflow-wrap: anywhere; }
.vp-panel .d-rule { margin: 14px 0 10px 0; }
.vp-panel .pass-chips { margin: 0; }

.vp-notes { display: flex; flex-direction: column; }
.vp-note { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; gap: 8px; align-items: start; padding: 8px 0; border-bottom: 2px solid var(--color-divider); }
.vp-note-body { min-width: 0; }
.vp-note-del { white-space: nowrap; }
.vp-pin-hint { margin-top: 9px; font-size: 10px; line-height: 1.4; color: var(--color-neutral-600); }
.vp-compose { display: flex; flex-direction: column; gap: 6px; margin-top: 9px; }
.vp-compose-title { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-accent-700); font-weight: 700; }
.vp-compose textarea {
  font-family: var(--font-body); font-size: 12px; line-height: 1.4; padding: 9px 10px;
  background: var(--color-bg); color: var(--color-text); border: 0; resize: vertical;
  width: 100%; box-sizing: border-box;
}
.vp-compose textarea:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.vp-compose-btns { display: flex; gap: 5px; }

.vp-scene-brief { font-size: 11px; line-height: 1.45; color: var(--color-neutral-700); margin-bottom: 9px; }
.vp-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; background: var(--color-divider); }
.vp-facts .fact { padding: 8px 9px; }
.fact[data-accent="1"] .fact-v { color: var(--color-accent-700); }

/* --- the pipeline tab: the node graph (mockup :758-846) ------------------------------- */
.gr { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 328px; gap: 2px; }
.gr-pane {
  position: relative; min-width: 0; min-height: 0; overflow: hidden; cursor: grab;
  background: var(--color-neutral-100);
  background-image:
    repeating-linear-gradient(to right, var(--color-neutral-300) 0 1px, transparent 1px 24px),
    repeating-linear-gradient(to bottom, var(--color-neutral-300) 0 1px, transparent 1px 24px);
}
.gr-pane[data-dragging="true"] { cursor: grabbing; }
.gr-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.gr-edges { position: absolute; left: 0; top: 0; overflow: visible; }
.gr-edge { fill: none; stroke-width: 2; }
.gr-edge--flow { stroke: var(--color-neutral-600); }
.gr-edge--redo { stroke: var(--color-accent); stroke-dasharray: 6 5; }

/* Role swimlanes - the band tint is the role's color at low alpha, the label
   sits in the left gutter graphView reserves (GRAPH_GUTTER). */
.gr-band {
  position: absolute; left: 0; right: 0;
  background: color-mix(in srgb, var(--role) 7%, transparent);
  border-top: 1px solid color-mix(in srgb, var(--role) 22%, transparent);
  pointer-events: none;
}
.gr-band-label {
  position: absolute; left: 12px; top: 10px; width: 128px;
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--role); opacity: .85;
}
.gr-roster {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 6px; flex-wrap: wrap; justify-content: center;
  max-width: calc(100% - 240px);
}
.gr-chip {
  font: inherit; font-size: 11px; padding: 2px 8px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--role) 45%, transparent);
  color: var(--role); background: color-mix(in srgb, var(--role) 10%, transparent);
  cursor: pointer;
}
.gr-chip[data-active="false"] { opacity: .35; cursor: default; }
.gr-chip[data-on="true"] {
  background: color-mix(in srgb, var(--role) 30%, transparent);
}

.gnode { position: absolute; background: var(--color-bg); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18); cursor: pointer; transition: box-shadow 110ms ease-out, opacity 110ms ease-out; border-left: 3px solid var(--role, transparent); }
.gnode:hover { box-shadow: var(--shadow-lg); }
.gnode[data-on="true"] { background: var(--color-accent-100); box-shadow: inset 0 0 0 2px var(--color-accent); }
.gnode[data-dim="true"] { opacity: .25; }
.gnode-head { display: flex; align-items: center; gap: 6px; padding: 5px 8px; background: var(--color-text); color: var(--color-bg); }
.gnode[data-ev="running"] .gnode-head { background: var(--color-accent); }
.gnode[data-ev="fail"] .gnode-head { background: var(--color-fail); color: var(--color-fail-fg); }
.gnode-num { font-family: var(--font-mono); font-size: 9px; opacity: 0.7; }
.gnode-name { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gnode-body { padding: 7px 8px 8px 8px; display: flex; flex-direction: column; gap: 3px; }
.gnode-row { display: flex; align-items: baseline; gap: 8px; }
.gnode-k { font-size: 8px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); white-space: nowrap; }
.gnode-v { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gnode-foot { display: flex; align-items: baseline; gap: 8px; margin-top: 4px; padding-top: 5px; border-top: 2px solid var(--color-divider); }
.gnode-state { font-size: 8px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-600); }
.gnode-state[data-ev="running"], .gnode-state[data-ev="fail"] { color: var(--color-accent-700); }
.gnode-dur { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-700); }
.gnode-port { position: absolute; top: 8px; width: 10px; height: 10px; box-sizing: border-box; }
.gnode-port--in { left: -5px; background: var(--color-bg); border: 2px solid var(--color-text); }
.gnode-port--out { right: -5px; background: var(--color-text); }

.gr-badges { position: absolute; left: 16px; bottom: 14px; display: flex; align-items: center; gap: 6px; pointer-events: none; }
.gr-badge { padding: 5px 8px; background: var(--color-bg); font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-700); }
.gr-badge--redo { background: var(--color-accent); color: var(--color-bg); }
.gr-controls { position: absolute; right: 14px; bottom: 14px; display: flex; align-items: stretch; gap: 2px; }
.gr-zoom-btn {
  font-family: var(--font-body); font-size: 11px; font-weight: 700; padding: 5px 10px;
  background: var(--color-bg); color: var(--color-text); border: 0; cursor: pointer;
  letter-spacing: 0.06em; text-transform: uppercase; text-align: left;
}
.gr-zoom-btn:hover { background: var(--color-accent); color: var(--color-bg); }
.gr-zoom-label { display: flex; align-items: center; padding: 5px 9px; background: var(--color-bg); font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-700); }

/* The node-details panel beside the graph (mockup :808-845). Same scroll treatment as
   .vp-panel; the run stats grid inside goes two-up rather than the old column's three. */
.gnode-panel {
  min-width: 0; min-height: 0; overflow-y: auto; background: var(--color-neutral-100);
  padding: 16px; scrollbar-width: thin; scrollbar-color: var(--color-neutral-500) transparent;
}
.gnode-panel .facts { grid-template-columns: 1fr 1fr; margin-top: 11px; }
.gnode-panel .d-rule { margin: 14px 0 9px 0; }

/* Small screens: the fixed-height surface unrolls into normal flow, the same rule the
   old columns followed - #app scrolls the lot, the shelf keeps one scrolling row, the
   graph pane gets a bounded height instead of a share of a fixed page. */
@media (max-width: 720px) {
  .vp, .gr { display: block; }
  .vp-left { display: block; }
  .vp-hero { height: 52vw; min-height: 180px; }
  .vp-shelf-body, .vp-shelf-body[data-collapsed="true"] { height: 119px; overflow-y: hidden; }
  .vp-shelf-row { display: flex; gap: 9px; align-items: flex-start; flex-wrap: nowrap; }
  .vp-shelf-row .vp-card { width: 88px; flex: none; }
  .vp-panel, .gnode-panel { overflow: visible; border-top: 2px solid var(--color-neutral-500); }
  .gr-pane { height: 60vw; min-height: 260px; }
}

/* --- the 3D asset viewport (viewport3d.js - Resonance's three.js rig) ---------------- */
.vp-hero--3d { cursor: default; }

.v3d { position: absolute; inset: 0; }
.v3d canvas { display: block; width: 100%; height: 100%; }
.v3d-note {
  position: absolute; left: 0; bottom: 0; z-index: 2; padding: 5px 8px;
  background: var(--color-bg); color: var(--color-neutral-700);
  font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700;
}
.v3d-hint {
  position: absolute; right: 6px; bottom: 6px; z-index: 2; padding: 3px 8px;
  background: var(--color-text); color: var(--color-bg);
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.05em;
  pointer-events: none;
}
/* The mesh loading screen: the Robot Head doc's "Visor" sprite over the
   viewport while a GLB downloads (a GEN_* mesh is tens of MB). Two frame
   layers alternate on a steps() loop - the lit visor segment jumping sides
   IS the animation - and the whole head bobs, both straight from the
   design doc's own keyframes. */
.v3d-loading {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; background: color-mix(in srgb, var(--color-inset) 82%, transparent);
  pointer-events: none;
}
.v3d-bot {
  position: relative; width: 84px;
  animation: v3d-bob 1s ease-in-out infinite;
}
.v3d-bot-frame { position: absolute; inset: 0; display: grid; }
.v3d-bot-f0 { animation: v3d-pxA 1s steps(1, end) infinite; }
.v3d-bot-f1 { animation: v3d-pxB 1s steps(1, end) infinite; }
.v3d-bot-glow { animation: v3d-blip 1s steps(1, end) infinite; }
.v3d-loading-title {
  margin-top: 10px; font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; font-weight: 700; color: var(--color-accent);
}
.v3d-loading-bytes {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.05em;
  color: var(--color-neutral-700); min-height: 12px;
}
/* The gallery card's loader: the viewer's robot, smaller, over the poster until the
   clip's first frame paints (gallery.js robotLoader). Same sprite classes, so the
   viewer's keyframes drive it. */
.card-loading {
  position: absolute; inset: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--color-inset) 55%, transparent);
  pointer-events: none;
}
.card-bot { width: 48px; }
@keyframes v3d-pxA { 0%, 49.9% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes v3d-pxB { 0%, 49.9% { opacity: 0; } 50%, 100% { opacity: 1; } }
@keyframes v3d-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.2%); } }
@keyframes v3d-blip { 0%, 60% { opacity: 1; } 61%, 100% { opacity: 0.15; } }

/* --- the pin table (store wave 3, Task 10): what the selected render was built from ---- */
.vp-pinrows { display: flex; flex-direction: column; }
.vp-pinrow {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; gap: 2px 8px;
  align-items: center; padding: 6px 0; border-bottom: 2px solid var(--color-divider);
  color: var(--color-text); text-decoration: none;
}
.vp-pinrow:hover .vp-pinrow-name { color: var(--color-accent-700); }
.vp-pinrow-thumb {
  width: 28px; height: 28px; grid-row: span 2; background: var(--color-neutral-200);
  display: grid; place-items: center; overflow: hidden;
  font-family: var(--font-mono); font-size: 8px; text-transform: uppercase; color: var(--color-neutral-600);
}
.vp-pinrow-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vp-pinrow-name { font-family: var(--font-mono); font-size: 11px; overflow-wrap: anywhere; }
.vp-pinrow-v { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-700); white-space: nowrap; }
.vp-pinrow-state { grid-column: 2 / span 2; font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); }
.vp-pinrow[data-head="1"] .vp-pinrow-state { color: var(--color-accent-700); }

/* --- the lineage page: one (kind, name) lineage, newest first ---------------------------- */
/* .d-page hides overflow (its columns scroll themselves); this page IS the one column */
.lin-page { display: block; overflow-y: auto; padding-bottom: 24px; }
.lin-list { display: flex; flex-direction: column; gap: 2px; padding: 0 16px; }
.lin-row { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 12px; padding: 12px; background: var(--color-neutral-100); }
.lin-thumb { width: 96px; height: 96px; background: var(--color-neutral-200); display: grid; place-items: center; overflow: hidden; }
.lin-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lin-thumb a { font-family: var(--font-mono); font-size: 10px; color: var(--color-text); text-align: center; padding: 6px; }
.lin-main { min-width: 0; }
.lin-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.lin-v { font-family: var(--font-mono); font-size: 16px; color: var(--color-text); }
.lin-chip { display: inline-block; padding: 2px 6px; font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; background: var(--color-accent); color: var(--color-bg); }
.lin-meta { font-family: var(--font-mono); font-size: 10px; color: var(--color-neutral-700); }
.lin-kv { margin-top: 8px; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 10px; align-items: baseline; }
.lin-k { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); white-space: nowrap; }
.lin-val { font-family: var(--font-mono); font-size: 11px; color: var(--color-text); overflow-wrap: anywhere; }
.lin-val a { color: var(--color-text); text-decoration: underline; margin-right: 8px; }
.lin-empty { padding: 16px; color: var(--color-neutral-700); }

/* --- the Brief tab (2026-09-03): the art director's read, then David's notes --------- */
/* the detail shell (.d-page) is a fixed-height flex column that hides overflow - the
   Viewport and Pipeline tabs manage their own space - so this pane is the scroller */
.brief-page { flex: 1 1 0; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; padding: 16px 16px 40px 16px; background: var(--color-bg); }
/* the Log tab (web/log.js, spec 2026-09-05-site-as-the-control-room-design.md): one row
   per entry - when, who, what and the words - the same 2px rules the notes use */
.log-page { padding-top: 10px; }
.log-list { display: flex; flex-direction: column; }
.log-entry {
  display: grid; grid-template-columns: 112px 110px minmax(0, 1fr); gap: 10px;
  align-items: start; padding: 8px 0; border-bottom: 2px solid var(--color-divider);
}
.log-when { font-size: 10px; letter-spacing: 0.06em; color: var(--color-neutral-600); white-space: nowrap; }
.log-who { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-700); font-weight: 700; overflow-wrap: anywhere; }
.log-body { min-width: 0; }
.log-what { font-size: 11px; color: var(--color-neutral-700); }
.log-kind { font-weight: 700; color: var(--color-accent-700); letter-spacing: 0.06em; text-transform: uppercase; font-size: 10px; }
.log-target { color: var(--color-text); text-decoration: none; }
a.log-target[href]:hover { text-decoration: underline; }
.log-text { font-size: 12px; line-height: 1.45; margin-top: 3px; overflow-wrap: anywhere; }
.log-entry[data-kind="outcome"] .log-text { color: var(--color-neutral-700); }
.log-entry[data-kind="brief"] .log-text { font-style: italic; }
.log-tail { font-size: 10px; color: var(--color-neutral-600); margin-top: 3px; }
@media (max-width: 640px) {
  .log-entry { grid-template-columns: 1fr; gap: 2px; }
}
/* the control room (web/brief.js buildControls): the finish chain's doors, on the local
   server only */
.ctl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; margin-top: 8px; }
.ctl-door { display: flex; flex-direction: column; gap: 5px; padding: 8px; background: var(--color-bg); }
.ctl-door-title { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-accent-700); font-weight: 700; }
.ctl-door textarea, .ctl-door select, .ctl-door input {
  font-family: var(--font-body); font-size: 12px; padding: 6px 8px; border: 0;
  background: var(--color-surface, #fff); color: var(--color-text); width: 100%; box-sizing: border-box;
}
.ctl-door textarea { resize: vertical; min-height: 44px; }
.ctl-status { font-size: 11px; color: var(--color-neutral-700); margin-top: 6px; }

.brief-box { border-top: 2px solid var(--color-neutral-500); padding-top: 10px; }
.brief-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 8px; }
.brief-eyebrow { font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--color-accent-700); }
.brief-title { font-family: var(--font-heading); font-weight: 800; font-size: 16px; }
.brief-lean { font-size: 12px; font-weight: 700; margin: 4px 0 8px 0; }
.brief-lean[data-lean="redo"] { color: var(--color-fail); }
.brief-lean[data-lean="approve"] { color: var(--color-accent-700); }
.brief-read { font-size: 13px; line-height: 1.5; margin-bottom: 10px; }
.brief-concerns { display: flex; flex-direction: column; }
.brief-concern { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 8px; align-items: start; padding: 8px 0; border-bottom: 2px solid var(--color-divider); }
.brief-concern[data-severity="major"] .brief-what { font-weight: 700; }
.brief-what { font-size: 13px; line-height: 1.4; }
.brief-evidence { font-size: 11px; line-height: 1.4; color: var(--color-neutral-700); margin-top: 3px; }
.brief-chips { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.brief-chip[data-kind="major"] { color: var(--color-fail); }
.brief-keep { margin-top: 12px; }
.brief-keep ul { margin: 6px 0 0 18px; padding: 0; font-size: 12px; line-height: 1.5; }

/* --- the queue page's Status section (2026-09-03) ------------------------------------ */
.status-list { display: flex; flex-direction: column; }
.status-row { display: grid; grid-template-columns: 70px minmax(0, 1fr) auto; gap: 10px; align-items: baseline; padding: 7px 0; border-bottom: 2px solid var(--color-divider); }
.status-k { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-600); }
.status-v { font-size: 12px; line-height: 1.4; overflow-wrap: anywhere; }
.status-state[data-state="running"], .status-state[data-state="reachable"], .status-state[data-state="busy"] { color: var(--color-accent-700); }
.status-state[data-state="dead"], .status-state[data-state="unreachable"], .status-state[data-state="down"], .status-state[data-state="stale"] { color: var(--color-fail); }
.status-state[data-state="fresh"], .status-state[data-state="generating"] { color: var(--color-accent-700); }
.status-row .status-k { white-space: nowrap; }
.status-row { grid-template-columns: 130px minmax(0, 1fr) auto; }
.status-state[data-state="idle"], .status-state[data-state="unknown"], .status-state[data-state="unconfigured"] { color: var(--color-neutral-600); }

/* --- the Brief tab, round 2 (2026-09-03): concept beside render, Vero's face -------- */
.brief-compare { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.brief-compare[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.brief-compare[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 720px) { .brief-compare { grid-template-columns: minmax(0, 1fr); } }
.brief-cps { margin-bottom: 14px; }
/* THE WHOLE PROCESS (David 2026-09-08): the five-step strip, then one section per
   checkpoint - a placeholder where the scene has not reached it */
.brief-process { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 6px; }
.brief-step { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; background: var(--color-neutral-200); color: inherit; text-decoration: none; border-top: 3px solid var(--color-neutral-500); min-width: 0; }
.brief-step:hover { background: var(--color-neutral-300, var(--color-neutral-200)); }
/* THE FINAL CLIP on the Brief (David, 2026-09-10: "I cant see the final render in the
   brief"): the pulse version's own mp4 at the page's width, its poster the clip's first
   frame, a caption of what it is. */
.brief-final { margin: 12px 0 0 0; }
.brief-final-video {
  display: block; width: 100%; max-width: 1280px; aspect-ratio: 16 / 9;
  background: var(--color-neutral-300); border-radius: 4px;
}
.brief-final figcaption {
  margin-top: 6px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-neutral-500);
}
.brief-step-num { font-family: var(--font-heading); font-weight: 800; font-size: 18px; line-height: 1; }
.brief-step-name { font-size: 12px; font-weight: 700; }
.brief-step-state { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-accent-700); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.brief-step[data-state="approved"] { border-top-color: var(--color-accent-700); }
.brief-step[data-state="awaiting"] { border-top-color: var(--color-text); background: var(--color-text); color: var(--color-bg); }
.brief-step[data-state="awaiting"] .brief-step-state { color: var(--color-bg); }
.brief-step[data-state="redo"] { border-top-color: var(--color-fail); }
.brief-step[data-state="pending"] { opacity: 0.55; }
.brief-checkpoint { display: flex; flex-direction: column; gap: 16px; border-top: 3px solid var(--color-text); padding-top: 12px; }
.brief-checkpoint[data-state="pending"] { opacity: 0.6; }
.brief-cp-head { display: flex; align-items: center; gap: 14px; }
.brief-cp-num { font-family: var(--font-heading); font-weight: 800; font-size: 34px; line-height: 1; width: 44px; flex: 0 0 44px; }
.brief-cp-state { margin-left: auto; }
.brief-cp-state[data-kind="approved"] { color: var(--color-accent-700); }
.brief-cp-state[data-kind="redo"] { color: var(--color-fail); }
/* THE ASSET CARDS at checkpoint 3 (2026-09-11): a tile beside its four choices as
   segmented buttons, notes under them, a counting filter bar above the grid */
.ac-bar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 12px; }
.ac-filter { font-family: var(--font-body); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; padding: 7px 10px; border: 0; background: var(--color-neutral-200); color: var(--color-text); cursor: pointer; }
.ac-filter:hover { background: var(--color-neutral-300, var(--color-neutral-200)); }
.ac-filter[aria-pressed="true"] { background: var(--color-text); color: var(--color-bg); }
.ac-search { font: inherit; font-size: 12px; padding: 7px 10px; border: 2px solid var(--color-neutral-500); background: var(--color-bg); color: var(--color-text); margin-left: auto; min-width: 160px; }
.ac-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(540px, 1fr)); gap: 12px; }
@media (max-width: 720px) { .ac-grid { grid-template-columns: minmax(0, 1fr); } }
.ac { display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: 12px; background: var(--color-neutral-200); padding: 10px; border-top: 3px solid var(--color-neutral-500); min-width: 0; }
.ac[data-generator="rodin"] { border-top-color: var(--color-accent-700); }
.ac[data-generator="astra"] { border-top-color: var(--color-accent); }
.ac[data-generator="stands"] { border-top-color: var(--color-neutral-700); }
@media (max-width: 480px) { .ac { grid-template-columns: minmax(0, 1fr); } }
.ac-fig img { display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: contain; background: #7f7f7f; }
.ac-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.ac-name { font-family: var(--font-heading); font-weight: 800; font-size: 15px; }
.ac-meta { font-size: 11px; line-height: 1.4; color: var(--color-neutral-700); }
.ac-choices { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 10px; align-items: center; }
.ac-choice-label { font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-accent-700); }
.seg { display: inline-flex; flex-wrap: wrap; gap: 2px; }
.seg-btn { font-family: var(--font-body); font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; padding: 5px 8px; border: 0; background: var(--color-bg); color: var(--color-text); cursor: pointer; }
.seg-btn:hover:not(:disabled) { background: var(--color-neutral-300, var(--color-neutral-200)); }
.seg-btn[aria-pressed="true"] { background: var(--color-text); color: var(--color-bg); }
.seg-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.ac-by { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-700); }
.ac[data-person="1"] .ac-by { color: var(--color-accent-700); font-weight: 700; }
.ac-notes { display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--color-divider); padding-top: 6px; }
.ac-note-row { display: flex; gap: 6px; align-items: flex-start; }
.ac-note-row textarea { flex: 1 1 auto; min-width: 0; box-sizing: border-box; font: inherit; font-size: 12px; }
.ac-sheet summary { cursor: pointer; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-accent-700); padding: 4px 0; }
.ac-sheet[open] summary { margin-bottom: 8px; }
.brief-placeholder { border: 2px dashed var(--color-neutral-500); padding: 34px 20px; text-align: center; font-size: 13px; color: var(--color-neutral-700, inherit); }
/* the views sheet is not 16:9 - it is however tall its rows make it */
.brief-figure--sheet img { aspect-ratio: auto; object-fit: contain; }
.brief-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.brief-tile { background: var(--color-neutral-200); padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.brief-tile img { width: 100%; aspect-ratio: 1 / 1; object-fit: contain; background: #7f7f7f; }
.brief-tile textarea { width: 100%; box-sizing: border-box; font: inherit; }
.brief-figure { margin: 0; background: var(--color-neutral-200); }
.brief-figure img, .brief-figure video { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: #000; }
.brief-figure figcaption { font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-neutral-700); padding: 6px 8px; }
.brief-noimg { aspect-ratio: 16 / 9; display: flex; align-items: center; justify-content: center; font-size: 12px; color: var(--color-neutral-600); }
.brief-head { align-items: center; }
.brief-head-words { display: flex; flex-direction: column; gap: 2px; }
.brief-avatar {
  position: relative;
  width: 56px;
  flex: 0 0 56px;
  aspect-ratio: 14 / 12;
  animation: bob 1s ease-in-out infinite;    /* duration per member, set in JS */
  background: #232420;
  padding: 4px;
  box-sizing: content-box;
}
.brief-avatar-frame { position: absolute; inset: 4px; display: grid; grid-template-columns: repeat(14, 1fr); grid-template-rows: repeat(12, 1fr); }
.brief-avatar-frame--a { animation: pxA 1s steps(1, end) infinite; }
.brief-avatar-frame--b { animation: pxB 1s steps(1, end) infinite; }

/* THE SHARE MENU (web/share.js; David 2026-09-10: "a right click context menu on plumb,
   for renders - videos and stills to share a direct link to it"): one small dark panel at
   the pointer, two actions and the link itself when the clipboard refuses. */
.share-menu {
  position: fixed; z-index: 1000; min-width: 220px; max-width: min(420px, 92vw);
  padding: 6px; border-radius: 8px;
  background: rgba(24, 25, 28, 0.97); color: #e8e8ea;
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  font: 13px/1.35 system-ui, -apple-system, "Segoe UI", sans-serif;
}
.share-menu-head {
  padding: 4px 8px 6px; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  color: rgba(232, 232, 234, 0.55); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.share-menu-item {
  display: block; width: 100%; text-align: left; padding: 7px 8px; border: 0; border-radius: 6px;
  background: transparent; color: inherit; font: inherit; cursor: pointer;
}
.share-menu-item:hover, .share-menu-item:focus-visible { background: rgba(255, 255, 255, 0.09); outline: none; }
.share-menu-link {
  display: block; width: 100%; margin-top: 6px; padding: 6px 8px; border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.12); background: rgba(0, 0, 0, 0.35);
  color: inherit; font: 12px/1.3 ui-monospace, SFMono-Regular, Consolas, monospace;
}

/* THE ROOM YOU CAN SPIN at the blockout gate (web/brief.js buildRoom3d; David,
   2026-09-12: "for the first blockout stage lets deliver it to plumb as a threejs scene
   as well"). The stage is POSITIONED and carries a height of its own because .v3d is
   `position: absolute; inset: 0` - a viewport with no box to fill renders nothing. */
.brief-room3d { margin: 10px 0 0; }
.brief-room3d-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.brief-room3d-toggle {
  font: inherit; cursor: pointer; padding: 6px 12px; border-radius: 6px;
  border: 1px solid var(--color-divider); background: var(--color-surface);
  color: var(--color-text);
}
.brief-room3d-toggle:hover { border-color: var(--color-accent); }
.brief-room3d-stage {
  position: relative; margin-top: 8px; height: min(62vh, 520px);
  border: 1px solid var(--color-divider); border-radius: 8px; overflow: hidden;
}
@media (max-width: 700px) { .brief-room3d-stage { height: 52vh; } }

/* THE DRIVER'S REPORT beside the pulse clip (web/brief.js buildPulseReport, 2026-09-12):
   what it keyed, what it held, what flew. Monospaced because every line is a measurement,
   and scrollable in its own box because a long bake has a lot to say. */
.brief-report { margin: 10px 0 0; }
.brief-report-text {
  margin: 6px 0 0; padding: 10px 12px; max-height: 240px; overflow: auto;
  font-family: var(--font-mono); font-size: 12px; line-height: 1.5;
  background: var(--color-inset); color: var(--color-text);
  border: 1px solid var(--color-divider); border-radius: 6px;
  white-space: pre-wrap; word-break: break-word;
}


/* --- ADD SCENE (web/addscene.js) ------------------------------------------
   The gallery's own door. Local only: the button mounts hidden and shows once
   /api/session says readonly: false, so the read-only site never grows a control
   that cannot work. */
.as-bar { display: flex; justify-content: flex-end; margin: 0 0 12px 0; }
/* the header's Add scene and menu button are the phone's: hidden ABOVE 720px in a
   min-width block, because a plain rule here sits after the phone block in the
   cascade and beat its display:flex (2026-09-14: "I dont see the drop down menu button") */
@media (min-width: 721px) {
  .hdr-menu { display: none; }
  /* ADD SCENE BESIDE QUEUE on the desk too (David, 2026-09-14: "on the main site, add
     the add scene button next to queue"): the header's door in the slab's own outline,
     the gallery's bar above the grid retired */
  .hdr-add { display: inline-flex; }
  .hdr-add .as-bar { margin: 0; }
  .hdr-add .as-open { background: transparent; color: var(--color-bg); border: 2px solid var(--color-bg); padding: 10px 16px; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; }
  .hdr-add .as-open:hover:not(:disabled) { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-text); }
  .as-host { display: none; }
}
.as-dialog {
  border: 0; padding: 0; max-width: 620px; width: calc(100% - 32px);
  background: var(--color-bg); color: var(--color-text);
}
.as-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.as-form { display: flex; flex-direction: column; gap: 12px; padding: 18px; }
.as-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.as-title { font-family: var(--font-heading); font-weight: 800; font-size: 18px; margin: 0; }
.as-tabs { display: flex; gap: 6px; }
.as-lede { font-size: 12px; line-height: 1.5; color: var(--color-neutral-700); margin: 0; }
.as-field { display: flex; flex-direction: column; gap: 4px; }
.as-label { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase;
            color: var(--color-accent-700); font-weight: 700; }
.as-hint { font-size: 11px; color: var(--color-neutral-700); }
.as-field input, .as-field textarea {
  font-family: var(--font-body); font-size: 12px; padding: 6px 8px; border: 0;
  background: var(--color-surface, #fff); color: var(--color-text);
  width: 100%; box-sizing: border-box;
}
.as-field textarea { resize: vertical; }
/* THREE NAMED SLOTS: wide front, left, right - the same views every time, so one
   room reads the same way as the one before it. The front is the concept. */
.as-slots { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
.as-slot { display: flex; flex-direction: column; gap: 4px; padding: 8px;
           background: var(--color-neutral-100, #f4f4f4); }
.as-slot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.as-slot input[type=file] { font-size: 11px; }
.as-slot--main { outline: 2px solid var(--color-accent); }
.as-shot-name { font-size: 10px; color: var(--color-neutral-700);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.as-shot-row { display: flex; gap: 4px; flex-wrap: wrap; }
.as-go { align-self: flex-start; }
.as-status { font-size: 11px; color: var(--color-neutral-700); margin: 0; }

/* the per-kind photograph control on a checkpoint-3 card (web/brief.js photoRow) */
.ac-photo { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.ac-photo input[type=file] { font-size: 11px; flex: 1 1 140px; min-width: 0; }
.ac-photo .ac-by { flex: 1 1 100%; }

/* THE OVERVIEW (web/overview.js; the Overview tab of design_template/Plumb.dc.html
   :569-640, 2026-09-14): two pictures and the clip in one ruled grid, then Progress
   beside the Scene's numbers. Values are the mock's. */
.ov { flex: 1 1 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain; background: var(--color-bg); scrollbar-width: thin; scrollbar-color: var(--color-neutral-500) transparent; }
.ov-inner { max-width: 1180px; margin: 0 auto; padding: 22px 24px 48px 24px; display: grid; gap: 28px; }
.ov-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2px; background: var(--color-neutral-500); }
.ov-cell { min-width: 0; background: var(--color-bg); }
.ov-cell--still { grid-column: span 2; }
.ov-cell--side { display: flex; flex-direction: column; }
.ov-shot { position: relative; background: var(--color-neutral-200); overflow: hidden; }
.ov-shot--wide { aspect-ratio: 16 / 9; }
.ov-shot--concept { aspect-ratio: 16 / 10; }
.ov-shot--concept img { filter: grayscale(1); }
.ov-shot--clip { margin-top: auto; aspect-ratio: 16 / 9; border-top: 2px solid var(--color-neutral-500); }
.ov-shot img, .ov-shot video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.ov-shot--clip video { object-fit: contain; background: #000; }
.ov-none { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-neutral-500); }
.ov-cap { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 12px 14px 14px 14px; }
.ov-cap-label { font-family: var(--font-heading); font-weight: 800; font-size: 17px; letter-spacing: -0.01em; }
.ov-cap-meta { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 11px; color: var(--color-neutral-700); overflow-wrap: anywhere; }
.ov-concept-text { padding: 12px 14px 14px 14px; }
.ov-concept-title { font-family: var(--font-heading); font-weight: 800; font-size: 15px; }
.ov-concept-brief { font-size: 12.5px; line-height: 1.5; color: var(--color-neutral-700); margin-top: 4px; text-wrap: pretty; }
.ov-clip-bar { position: absolute; left: 0; right: 0; top: 0; padding: 8px 12px; background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent); color: #fff; display: flex; justify-content: space-between; gap: 8px; font-size: 11px; pointer-events: none; }
.ov-clip-label { font-weight: 700; }
.ov-clip-meta { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); }
.ov-noclip { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 16px; text-align: center; }
.ov-play { width: 40px; height: 40px; border-radius: 20px; border: 2px solid var(--color-neutral-400); display: flex; align-items: center; justify-content: center; color: var(--color-neutral-500); font-size: 14px; padding-left: 3px; }
.ov-play::before { content: "\25B6"; }
.ov-noclip-note { font-size: 12px; line-height: 1.45; color: var(--color-neutral-600); max-width: 26ch; text-wrap: pretty; }
.ov-lower { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 32px; align-items: start; }
.ov-eyebrow { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--color-accent-700); font-weight: 700; }
.ov-eyebrow-row { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; align-items: baseline; }
.ov-eyebrow-sub { font-size: 12px; color: var(--color-neutral-700); }
.ov-segs { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 3px; margin-top: 12px; }
.ov-seg { min-width: 0; }
.ov-seg-bar { height: 8px; background: var(--color-neutral-300); }
.ov-seg[data-state="done"] .ov-seg-bar { background: var(--color-text); }
.ov-seg[data-state="now"] .ov-seg-bar { background: var(--color-accent); }
.ov-seg-label { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-neutral-600); margin-top: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ov-seg[data-state="done"] .ov-seg-label, .ov-seg[data-state="now"] .ov-seg-label { color: var(--color-text); }
.ov-btns { display: flex; gap: 3px; margin-top: 18px; flex-wrap: wrap; }
.ov-btn { font-family: var(--font-body); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; padding: 9px 14px; border: 0; cursor: pointer; text-align: left; }
.ov-btn--solid { background: var(--color-text); color: var(--color-bg); }
.ov-btn--solid:hover { background: var(--color-accent); }
.ov-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 2px solid var(--color-neutral-500); margin-top: 12px; }
.ov-stat { padding: 12px 10px 12px 0; border-bottom: 2px solid var(--color-divider); min-width: 0; }
.ov-stat-k { font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-neutral-700); }
.ov-stat-v { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 16px; margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 720px) {
  /* The phone unrolls the shell (.d-page is a block; #app scrolls the lot), so .ov is a
     scroll container with nothing to scroll - and `overscroll-behavior: contain` on such
     a box still swallows the gesture instead of chaining it to #app: the overview that
     "cant scroll on mobile" of 2026-09-14 (reproduced in Chrome at 400px: contain left
     #app at 0, auto scrolled it). Contain is for the desk, where .ov is the scroller. */
  .ov { overscroll-behavior: auto; }
  .ov-inner { padding: 12px 12px 40px 12px; gap: 20px; }
  .ov-grid { grid-template-columns: minmax(0, 1fr); }
  .ov-cell--still { grid-column: auto; }
  .ov-shot--clip { margin-top: 0; }
  .ov-lower { gap: 22px; }
  .ov-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* THE LEDGER (web/ledger.js; design_template/Scene Ledger.dc.html + Ledger Mobile.dc.html,
   2026-09-14): an index of the checkpoints beside the stream of deliveries and feedback,
   the composer at the foot. Values are the mock's. */
.lg { flex: 1 1 0; min-height: 0; display: grid; grid-template-columns: minmax(0, 264px) minmax(0, 1fr); background: var(--color-bg); position: relative; }
.lg-index { min-height: 0; overflow-y: auto; background: var(--color-surface); display: flex; flex-direction: column; scrollbar-width: thin; }
.lg-index-head { padding: 20px 20px 18px 20px; }
.lg-index-thumb { aspect-ratio: 16 / 10; background: var(--color-inset); background-size: cover; background-position: center; filter: grayscale(1); }
.lg-eyebrow { font-family: var(--font-heading); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 700; color: var(--color-accent-700); margin-top: 11px; }
.lg-index-name { font-family: var(--font-heading); font-weight: 800; font-size: 20px; line-height: 1.1; letter-spacing: -0.015em; margin-top: 5px; overflow-wrap: anywhere; }
.lg-index-prompt { font-size: 13.5px; line-height: 1.55; color: var(--color-neutral-800); margin-top: 10px; text-wrap: pretty; }
.lg-index-facts { display: grid; gap: 5px; margin-top: 14px; font-size: 12.5px; color: var(--color-neutral-700); }
.lg-cps { padding: 4px 0 8px 0; }
.lg-cps-title { font-family: var(--font-heading); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700; color: var(--color-neutral-700); padding: 0 20px 10px 20px; }
.lg-cp { display: grid; grid-template-columns: 12px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 10px 20px; text-decoration: none; color: var(--color-neutral-700); }
.lg-cp:hover { background: var(--color-neutral-200); }
.lg-cp[data-state="open"] { background: var(--color-accent-100); color: var(--color-accent-800); }
.lg-cp[data-state="done"] { color: var(--color-text); }
.lg-cp-dot { width: 9px; height: 9px; border-radius: 9px; box-shadow: inset 0 0 0 2px var(--color-neutral-400); }
.lg-cp[data-state="done"] .lg-cp-dot { background: var(--color-text); box-shadow: none; }
.lg-cp[data-state="open"] .lg-cp-dot { box-shadow: inset 0 0 0 3px var(--color-accent); }
.lg-cp-text { min-width: 0; }
.lg-cp-label { display: block; font-family: var(--font-heading); font-size: 14px; font-weight: 700; letter-spacing: 0.01em; }
.lg-cp-sub { display: block; font-size: 12px; color: var(--color-neutral-700); margin-top: 3px; }
.lg-cp-mark { font-family: var(--font-heading); font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 700; white-space: nowrap; color: var(--color-accent-700); }
.lg-cp[data-state="done"] .lg-cp-mark { color: var(--color-ok, #3f7a3f); }
.lg-index-foot { margin-top: auto; padding: 16px 20px 20px 20px; }
.lg-machine { display: flex; align-items: center; gap: 8px; background: none; border: 0; padding: 0; cursor: pointer; color: var(--color-neutral-700); font-family: var(--font-heading); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 700; text-align: left; }
.lg-machine:hover { color: var(--color-text); }
.lg-machine-note { margin-top: 9px; font-size: 12px; line-height: 1.5; color: var(--color-neutral-700); text-wrap: pretty; }

.lg-main { min-height: 0; display: flex; flex-direction: column; }
.lg-stream { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 0 22px 0; scrollbar-width: thin; }
.lg-group-head { position: sticky; top: 0; z-index: 4; display: flex; align-items: baseline; justify-content: space-between; gap: 14px; flex-wrap: wrap; padding: 18px 30px 12px 30px; background: var(--color-bg); box-shadow: 0 8px 8px -8px var(--color-bg); }
.lg-group-title { display: flex; align-items: baseline; gap: 12px; }
.lg-group-n { font-family: var(--font-heading); font-size: 13px; font-weight: 700; color: var(--color-neutral-600); }
.lg-group-name { font-family: var(--font-heading); font-size: 22px; font-weight: 800; letter-spacing: -0.015em; }
.lg-group-status { font-size: 13.5px; font-weight: 600; text-align: right; color: var(--color-neutral-700); }
.lg-group-status[data-state="open"] { color: var(--color-accent-700); }
.lg-group-status[data-state="done"] { color: var(--color-ok, #3f7a3f); }
.lg-entries { display: grid; gap: 18px; padding: 6px 30px 28px 30px; }
.lg-note { font-size: 13px; color: var(--color-neutral-700); padding-left: 54px; }
.lg-msg { position: relative; padding: 6px 10px; margin: 0 -10px; }
.lg-msg:hover { background: var(--color-neutral-100); }
.lg-msg-grid { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 14px; align-items: start; }
.lg-face { position: relative; width: 40px; height: 40px; background: var(--color-inset); overflow: hidden; }
.lg-face-frame { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(14, 1fr); grid-template-rows: repeat(12, 1fr); }
.lg-face-frame--a { animation: pxA 1s steps(1, end) infinite; }
.lg-face-frame--b { animation: pxB 1s steps(1, end) infinite; }
.lg-initial { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; background: var(--color-text); color: var(--color-bg); font-family: var(--font-heading); font-weight: 800; font-size: 17px; }
.lg-msg-body { min-width: 0; }
.lg-msg-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.lg-who { font-family: var(--font-heading); font-size: 15px; font-weight: 800; }
.lg-head-rest { font-size: 12.5px; color: var(--color-neutral-700); }
.lg-quote { font-size: 15px; line-height: 1.6; margin-top: 4px; max-width: 70ch; text-wrap: pretty; }
.lg-mark { font-family: var(--font-heading); font-size: 11.5px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; margin-right: 8px; color: var(--color-fail); }
.lg-mark[data-mark="Approved"] { color: var(--color-accent-700); }
.lg-mark--kind { color: var(--color-neutral-700); }
.lg-pic { margin-top: 10px; width: 100%; max-width: 520px; background: var(--color-surface); padding: 8px; }
.lg-pic img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: var(--color-inset); }
.lg-pic-cap { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 8px; padding: 0 2px 2px 2px; font-size: 12.5px; color: var(--color-neutral-700); }
.lg-pic-label { font-weight: 700; color: var(--color-text); }
.lg-clip { display: block; width: 100%; max-width: 520px; margin-top: 8px; background: var(--color-inset); }
.lg-building { margin-top: 8px; font-size: 13px; color: var(--color-neutral-700); }
.lg-meta { margin-top: 8px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.lg-chip { font-family: var(--font-heading); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; padding: 3px 7px; background: var(--color-neutral-300); color: var(--color-text); }
.lg-chip[data-state="taken"] { background: var(--color-accent); color: var(--color-bg); }
.lg-chip[data-state="delivered"] { background: var(--color-bg); color: var(--color-text); }
.lg-chip[data-state="refused"] { background: var(--color-fail); color: var(--color-fail-fg); }
.lg-chip--target { background: var(--color-neutral-200); text-transform: none; letter-spacing: 0; font-size: 11.5px; font-family: var(--font-body); font-weight: 600; padding: 4px 9px; }
.lg-meta-text { font-size: 12px; color: var(--color-neutral-700); overflow-wrap: anywhere; }
.lg-verdict { display: flex; align-items: center; gap: 10px; padding: 2px 0 2px 54px; }
.lg-verdict-dot { width: 9px; height: 9px; border-radius: 9px; flex: none; background: var(--color-ok, #3f7a3f); }
.lg-verdict-text { font-size: 14px; font-weight: 600; color: var(--color-ok, #3f7a3f); }
.lg-studio { text-align: left; background: none; border: 0; padding: 0 0 0 54px; cursor: pointer; font-family: var(--font-mono); font-size: 12px; color: var(--color-neutral-600); display: grid; gap: 4px; width: 100%; }
.lg-studio:hover { color: var(--color-text); }
.lg-studio div { overflow-wrap: anywhere; }
.lg-busy { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 14px; align-items: center; opacity: 0.85; }
.lg-busy-box { display: flex; align-items: center; gap: 10px; }
.lg-typing { display: inline-flex; gap: 4px; align-items: center; padding: 8px 10px; background: var(--color-surface); }
.lg-typing span { width: 7px; height: 7px; border-radius: 7px; background: var(--color-neutral-700); animation: lgTyping 1.2s ease-in-out infinite; }
.lg-typing span:nth-child(2) { animation-delay: 0.2s; }
.lg-typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes lgTyping { 0%, 60%, 100% { transform: translateY(0); opacity: 0.45 } 30% { transform: translateY(-4px); opacity: 1 } }
.lg-busy-text { font-size: 13px; color: var(--color-neutral-700); }
.lg-busy-text strong { color: var(--color-text); }

.lg-composer { flex: none; background: var(--color-surface); border-top: 1px solid var(--color-divider); padding: 14px 30px 16px 30px; }
.lg-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.lg-route { font-family: var(--font-heading); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; padding: 4px 9px; background: var(--color-accent); color: var(--color-bg); }
.lg-compose-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; align-items: end; margin-top: 9px; }
.lg-compose-row textarea { width: 100%; box-sizing: border-box; font-family: var(--font-body); font-size: 14.5px; line-height: 1.55; padding: 11px 12px; background: var(--color-bg); color: var(--color-text); border: 0; resize: vertical; }
.lg-compose-row textarea:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.lg-compose-btns { display: flex; gap: 6px; flex-wrap: wrap; }
.lg-approve:hover:not(:disabled) { background: var(--color-accent-200); color: var(--color-accent-800); }
.lg-sendback:hover:not(:disabled) { color: var(--color-fail); }
.lg-confirm { margin-top: 9px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 10px; background: var(--color-accent-100); font-size: 13px; color: var(--color-accent-800); }
.lg-mini { padding: 5px 9px; font-size: 10px; }
.lg-error { margin-top: 8px; font-size: 12.5px; color: var(--color-fail); }
.lg-signed-out { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; font-size: 13.5px; color: var(--color-neutral-800); }
.lg-mobile-bar { display: none; }
.lg-scrim { display: none; }
.lg-rail { display: none; }
.lg-divider { display: none; }
.lg-cp-hash { display: none; }
.lg-me { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.lg-me .lg-initial { border-radius: 18px; width: 36px; height: 36px; font-size: 16px; }
.lg-me-name { font-size: 14.5px; font-weight: 700; }
.lg-me-sub { font-size: 12px; color: var(--color-neutral-700); }
.hscroll { scrollbar-width: none; }
.hscroll::-webkit-scrollbar { display: none; }

/* THE PHONE (design_template/Ledger Mobile.dc.html): screen 1 is the drawer - a rail of
   scenes beside the checkpoints, the signed-in foot - and screen 2 the stream as a chat:
   a divider line where a gate opens, round faces, rounded picture cards, a pill
   composer whose chips scroll sideways. Values are the mock's. */
@media (max-width: 720px) {
  .lg { grid-template-columns: minmax(0, 1fr); }
  .lg-index { position: fixed; left: 0; top: 0; bottom: 0; width: 100%; max-width: none; z-index: 60; transform: translateX(-100%); transition: transform 160ms ease-out; display: grid; grid-template-columns: 72px minmax(0, 1fr); background: #161714; overflow: hidden; }
  .lg-index[data-open="true"] { transform: none; }
  .lg-rail { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 16px 0 10px 0; overflow-y: auto; scrollbar-width: none; }
  .lg-rail-scene { width: 48px; height: 48px; flex: none; border: 0; padding: 0; border-radius: 24px; background: var(--color-inset) center / cover no-repeat; color: var(--color-neutral-700); font-family: var(--font-heading); font-weight: 800; font-size: 16px; filter: grayscale(1); cursor: pointer; }
  .lg-rail-scene[data-current="true"] { border-radius: 14px; box-shadow: 0 0 0 2px #161714, 0 0 0 4px var(--color-accent); filter: none; }
  .lg-rail-add { width: 48px; height: 48px; flex: none; border: 0; border-radius: 24px; background: var(--color-surface); color: var(--color-accent); font-size: 22px; line-height: 1; cursor: pointer; }
  .lg-index-panel { background: var(--color-bg); border-radius: 18px 0 0 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; scrollbar-width: none; }
  .lg-index-head { padding: 20px 18px 12px 18px; }
  .lg-index-thumb { display: none; }
  .lg-eyebrow { margin-top: 0; font-size: 10px; letter-spacing: 0.14em; color: var(--color-accent); }
  .lg-index-name { font-size: 20px; margin-top: 4px; }
  .lg-index-prompt { display: none; }
  .lg-index-facts { display: block; font-size: 13px; margin-top: 6px; }
  .lg-index-facts div { display: inline; }
  .lg-index-facts div + div::before { content: " · "; }
  .lg-cps { padding: 0 10px; display: grid; gap: 2px; }
  .lg-cps-title { padding: 8px 8px 6px 8px; color: var(--color-neutral-600); }
  .lg-cp { grid-template-columns: 22px minmax(0, 1fr) auto; gap: 10px; min-height: 46px; padding: 0 10px; border-radius: 8px; color: var(--color-neutral-600); }
  .lg-cp[data-state="done"] { color: var(--color-neutral-800); }
  .lg-cp[data-state="open"] { background: var(--color-surface); color: var(--color-text); }
  .lg-cp-dot { display: none; }
  .lg-cp-hash { display: block; font-family: var(--font-heading); font-size: 18px; font-weight: 700; color: var(--color-neutral-500); }
  .lg-cp[data-state="open"] .lg-cp-hash { color: var(--color-text); }
  .lg-cp-label { font-family: var(--font-body); font-size: 15.5px; font-weight: 600; text-transform: lowercase; letter-spacing: 0; }
  .lg-cp[data-state="open"] .lg-cp-label { font-weight: 800; }
  .lg-cp-sub { display: none; }
  .lg-cp-mark { font-family: var(--font-body); font-size: 12px; text-transform: none; letter-spacing: 0; padding: 3px 8px; border-radius: 10px; }
  .lg-cp[data-state="open"] .lg-cp-mark { background: var(--color-accent); color: var(--color-bg); }
  .lg-index-foot { padding: 12px 16px 24px 16px; border-top: 1px solid var(--color-divider); }
  .lg-me { margin-bottom: 0; }
  .lg-machine { display: none; }
  .lg-scrim { display: none; }
  .lg-mobile-bar { display: flex; align-items: center; gap: 8px; padding: 6px 8px 8px 8px; border-bottom: 1px solid var(--color-divider); background: var(--color-bg); }
  .lg-mobile-menu { width: 44px; height: 44px; border: 0; background: none; color: var(--color-text); cursor: pointer; font-size: 20px; }
  .lg-mobile-text { min-width: 0; flex: 1; }
  .lg-mobile-title { display: flex; align-items: baseline; gap: 6px; font-family: var(--font-heading); font-size: 17px; font-weight: 800; }
  .lg-mobile-title .lg-group-n { font-size: 18px; font-weight: 700; color: var(--color-neutral-600); }
  .lg-mobile-title .lg-group-n::before { content: "#"; }
  .lg-mobile-sub { font-size: 12px; color: var(--color-neutral-700); margin-top: 1px; }
  /* no align-content:end here: a grid that bottom-aligns content taller than itself
     overflows at the TOP, where no scroller can reach - the phone's "not scrollable"
     ledger of 2026-09-14. The landing scrolls to the bottom instead (ledger.js). */
  .lg-stream { padding: 8px 14px 12px 14px; scrollbar-width: none; }
  .lg-group-head { display: none; }
  .lg-divider { display: flex; align-items: center; gap: 10px; justify-content: center; padding: 6px 0; margin-top: 10px; }
  .lg-divider-rule { height: 1px; flex: 1; background: var(--color-divider); }
  .lg-divider-text { font-size: 12px; font-weight: 600; color: var(--color-neutral-700); text-align: center; text-wrap: pretty; }
  .lg-entries { padding: 8px 0 8px 0; gap: 18px; }
  .lg-note { padding-left: 0; }
  .lg-msg { margin: 0; padding: 0; }
  .lg-msg:hover { background: none; }
  .lg-msg-grid { gap: 12px; }
  .lg-face, .lg-initial { border-radius: 20px; }
  .lg-face { border-radius: 8px; }
  .lg-quote { max-width: none; margin-top: 3px; line-height: 1.5; }
  .lg-pic { max-width: none; padding: 0; margin-top: 8px; border-radius: 10px; overflow: hidden; background: var(--color-surface); }
  .lg-pic img { aspect-ratio: 16 / 10; }
  .lg-pic-cap { margin-top: 0; padding: 8px 10px; }
  .lg-clip { max-width: none; border-radius: 10px; }
  .lg-verdict { padding-left: 0; }
  .lg-verdict-text { font-size: 13px; }
  .lg-studio { padding-left: 0; }
  .lg-busy { opacity: 0.9; }
  .lg-typing { border-radius: 14px; padding: 9px 11px; }
  .lg-composer { padding: 8px 10px 12px 10px; display: grid; gap: 8px; background: var(--color-bg); }
  .lg-chips { flex-wrap: nowrap; overflow-x: auto; gap: 6px; }
  .lg-chips > * { flex: none; }
  .lg-route { display: inline-flex; align-items: center; min-height: 30px; padding: 0 10px; border-radius: 15px; background: var(--color-accent-200); color: var(--color-accent-700); font-family: var(--font-body); font-size: 12.5px; font-weight: 700; letter-spacing: 0; text-transform: none; }
  .lg-chip--target { display: inline-flex; align-items: center; min-height: 30px; padding: 0 10px; border-radius: 15px; background: var(--color-surface); font-size: 12.5px; }
  .lg-pill { min-height: 30px; padding: 0 12px; border-radius: 15px; font-size: 12px; letter-spacing: 0.04em; }
  .lg-compose-row { display: flex; align-items: center; gap: 8px; margin-top: 0; }
  .lg-compose-row textarea { flex: 1; min-height: 44px; border-radius: 22px; padding: 12px 16px; font-size: 15px; resize: none; background: var(--color-surface); }
  .lg-send-round { width: 44px; height: 44px; flex: none; padding: 0; border-radius: 22px; font-size: 18px; justify-content: center; }
  .lg-signed-out { justify-content: space-between; }
}
