/* ============================================================================
   Gimmelowdown live-local demo SHELL - model-agnostic black-on-white
   "compute instrument". Mirrors lowdown_lab/web/app/assets/stylesheets.
   Grayscale only; ONE reserved accent (--accent) for the live spectrum motion.
   Monospace for every meter/number. Thin 1px rules. Stacks on mobile,
   compute panel is the hero.

   ┌─────────────────────────────────────────────────────────────────────────┐
   │ SKINNING - override ANY of these on :root (or on .fela-demo) to reskin    │
   │ the whole widget wholesale. Every colour / border / key spacing is here.  │
   │                                                                           │
   │   BRAND TOKENS   --black --white --gray-50 … --gray-900                   │
   │   ACCENT         --accent            (the ONE live-spectrum colour)       │
   │   SURFACES       --surface --surface-alt --surface-panel                  │
   │   INK            --ink --ink-muted --ink-faint                            │
   │   LINES          --line --line-strong                                     │
   │   PIPELINE       --pipe-bg --pipe-ink --pipe-ink-dim --pipe-fill          │
   │   RADII          --radius --radius-lg --radius-pill                       │
   │   SHADOWS        --shadow-sm --shadow --shadow-md                         │
   │   FONTS          --sans --mono                                            │
   │   LAYOUT WIDTHS  --col-left --col-right --mid-max --pad-panel             │
   │                                                                           │
   │   e.g. to reskin:  :root{ --black:#0a2540; --white:#f7fafc;              │
   │                           --accent:#ff5c00; }                             │
   │   Overriding --black / --white / an --accent re-skins everything because  │
   │   all surface/ink/line/pipeline tokens DERIVE from the brand tokens.      │
   └─────────────────────────────────────────────────────────────────────────┘
   ============================================================================ */

:root {
  /* ---- brand tokens (override these to reskin) ---- */
  --black: #000000;
  --white: #ffffff;
  --gray-50: #fafafa;
  --gray-100: #f5f5f5;
  --gray-200: #e5e5e5;
  --gray-300: #d4d4d4;
  --gray-400: #a3a3a3;
  --gray-500: #737373;
  --gray-600: #525252;
  --gray-700: #404040;
  --gray-800: #262626;
  --gray-900: #171717;

  /* the ONE reserved accent - used only by the live spectrum animation */
  --accent: #2f6fff;

  /* ---- derived semantic tokens (all reference the brand tokens above) ---- */
  --surface:       var(--white);   /* page + card surfaces */
  --surface-alt:   var(--gray-50); /* the compute (hero) column */
  --surface-panel: var(--white);   /* inner blocks */
  --ink:        var(--black);      /* primary text */
  --ink-muted:  var(--gray-600);   /* secondary text */
  --ink-faint:  var(--gray-400);   /* eyebrows / units */
  --line:        var(--gray-200);  /* thin 1px rules */
  --line-strong: var(--gray-300);  /* card borders */

  /* pipeline (Pretty animation) surface - derives from the brand ramp */
  --pipe-bg:      var(--gray-900);
  --pipe-ink:     var(--white);
  --pipe-ink-dim: var(--gray-600);
  --pipe-fill:    var(--white);

  /* ---- radii ---- */
  --radius: 8px;
  --radius-lg: 12px;
  --radius-pill: 999px;

  /* ---- shadows ---- */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);

  /* ---- fonts ---- */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* ---- MOTION (the "speed is what we sell" feel) - retune globally here ----
     Fast, refined easings + short durations. Smooth fades/slides only -
     never a blink/strobe (see prefers-reduced-motion block at the bottom). */
  --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);    /* snappy decel - the house curve */
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out:   cubic-bezier(0.65, 0, 0.35, 1);
  --dur-snap:  140ms;   /* result cards / log line snap-in */
  --dur-fast:  180ms;   /* bar fills, fades, hovers */
  --dur-flow:  300ms;   /* pipeline left→right sweep */
  --dur-count: 260ms;   /* big latency odometer roll (driven in compute.js) */
  --lift: -2px;         /* hover lift distance for interactive cards */

  /* ---- layout widths (re-proportioned: wide card + wide compute, small canvas) ---- */
  --col-left:  320px;   /* LEFT model card (doubled on wide screens, see below) */
  --col-right: 360px;   /* RIGHT live compute */
  --mid-max:   640px;   /* MIDDLE canvas cap so the 96×96 field is not upscaled huge */
  --pad-panel: 1.5rem;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--sans);
  background: var(--surface);
  color: var(--ink);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
a:hover { color: var(--ink-muted); }

/* ---- embeddable component root: the whole demo is self-contained here ---- */
.fela-demo { display: block; background: var(--surface); color: var(--ink); }

/* ------------------------------------------------------------------ header */
.demo-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 56px;
  padding: 0 1.25rem;
  border-bottom: 1px solid var(--gray-200);
  background: var(--white);
  position: sticky;
  top: 0;
  z-index: 50;
}
.demo-brand {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  text-decoration: none;
  color: var(--black);
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
}
.demo-brand .mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  background: var(--black);
  color: var(--white);
  border-radius: 5px;
  font-size: 0.85rem;
  font-weight: 800;
}
.demo-header .crumb {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--gray-500);
  text-transform: lowercase;
}
.demo-header .cap-slot { margin-left: auto; }

/* ---------------------------------------------------------------- banners */
.degraded-banner {
  display: none;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 1.25rem;
  background: var(--gray-900);
  color: var(--white);
  font-size: 0.8125rem;
  border-bottom: 1px solid var(--black);
}
.degraded-banner.show { display: flex; }
.degraded-banner .mono { font-family: var(--mono); color: var(--gray-400); }

/* ---------------------------------------------------------- 3-panel shell */
/* Re-proportioned: LEFT (card) + RIGHT (compute) WIDE, MIDDLE (canvas) SMALL. */
.demo-shell {
  display: grid;
  grid-template-columns:
    minmax(0, var(--col-left))
    minmax(0, 1fr)
    minmax(0, var(--col-right));
  gap: 0;
  align-items: stretch;
  min-height: calc(100vh - 56px);
}
.panel {
  min-width: 0;
  padding: var(--pad-panel) var(--pad-panel) 2.5rem;
  border-right: 1px solid var(--line);
}
.panel:last-child { border-right: none; }
.panel-left  { background: var(--surface); }
.panel-mid   { background: var(--surface); }
.panel-right { background: var(--surface-alt); }

/* The middle canvas panel is CAPPED and centred so the small field is never
   upscaled into giant blocks, and EVERY block in the middle column shares one
   width + left edge (no per-page block sprawls full-width). This one rule keeps
   all demos aligned; individual pages no longer need to opt each block in. */
/* Higher specificity (0,2,0) than any single-class page rule so the centering
   ALWAYS wins: every direct child of the middle panel is capped to one width and
   centered, so all demos share the same column edges and nothing sprawls or drifts
   left. .demo-shell prefix beats page-local `.stage-hint{margin:0..}` etc. */
/* On a wide screen the left model card doubles, so the prose has room to breathe.
   The middle canvas keeps its own cap and stays on the page's centre line: the
   middle column sits between two unequal side panels, so centring content inside
   that column would leave it off-centre in the page. Bias the padding by the
   difference between the side panels and it lands on centre. Derived:
   content centre = W/2 + (colL - colR)/2 + (padL - padR)/2, so padR - padL = colL - colR.

   With padL = 0 and padR = colL - colR the canvas keeps its full --mid-max and still
   lands on the centre line: content width = midcol - (colL - colR), which reaches the
   cap once the viewport is wide enough. Any padL > 0 steals from the canvas twice.

   Gated at 1500px because 640 + 360 leaves the middle nothing on a smaller screen -
   at 1400px the canvas collapsed to 71px, at 1280px to zero. */
@media (min-width: 1360px) {
  /* Take everything the canvas does not need, up to double the base width. Solving
     canvas = viewport - 2*colL for canvas = --mid-max gives colL = (100vw - 640px)/2. */
  :root { --col-left: clamp(360px, calc((100vw - 640px) / 2), 640px); }
  .demo-shell .panel-mid {
    padding-left: 0;
    padding-right: calc(var(--col-left) - var(--col-right));
  }
}
.demo-shell .panel-mid > * { max-width: var(--mid-max); margin-left: auto; margin-right: auto; }
/* Inline-level toggles (.seg / .seg-tabs) ignore `margin:auto`, so the rule above
   can't center them and they fall to the column's left edge (looked pinned to the
   page corner). Make them a shrink-wrapped BLOCK box so `margin:auto` centers the
   pill in the same column as everything else. Direct children of panel-mid only. */
.demo-shell .panel-mid > .seg,
.demo-shell .panel-mid > .seg-tabs { display: flex; width: max-content; }

.panel-eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gray-400);
  margin: 0 0 1rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--gray-200);
}

/* ------------------------------------------------------- LEFT: model card */
/* Fix for the scroll-jumble bug: the panel is a self-contained flex column.
   The summary is a fixed (non-scrolling) header; ONLY .model-card-body scrolls
   inside its own box - the two never overlap. */
.panel-left {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 56px);
  position: sticky;
  top: 56px;                /* stick below the sticky header */
  overflow: hidden;         /* the panel itself never scrolls; its body does */
}
.panel-left .panel-eyebrow { flex: none; }
/* the fenced copy block is the flex region: fixed summary header + scrolling body */
.fela-card-copy {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.model-summary {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  margin-bottom: 1.25rem;
  flex: none;               /* fixed header - does not scroll or overlap */
}
.model-summary h1 {
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  margin: 0 0 0.25rem;
}
.model-summary .family {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gray-500);
  margin: 0 0 0.75rem;
}
.summary-line {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  font-size: 0.8125rem;
  color: var(--gray-700);
  padding: 0.3rem 0;
  border-top: 1px solid var(--gray-100);
}
.summary-line:first-of-type { border-top: none; }
.summary-line .k {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gray-400);
  width: 44px;
  flex: none;
}
.summary-line .v { color: var(--black); font-weight: 600; }

.model-card-body {
  font-size: 0.875rem;
  color: var(--gray-700);
  flex: 1 1 auto;           /* takes remaining panel height */
  min-height: 0;            /* allow the flex child to actually scroll */
  overflow-y: auto;
  padding-right: 0.4rem;
}
.model-card-body h2 { font-size: 1rem; margin: 1.25rem 0 0.5rem; }
.model-card-body h3 { font-size: 0.875rem; margin: 1rem 0 0.4rem; }
.model-card-body p { margin: 0 0 0.75rem; }
.model-card-body code {
  font-family: var(--mono);
  font-size: 0.8125rem;
  background: var(--gray-100);
  padding: 0.05rem 0.3rem;
  border-radius: 3px;
}
.hf-link {
  display: inline-block;
  margin-top: 1rem;
  font-size: 0.8125rem;
  font-weight: 600;
}

/* --------------------------------------------------------- MIDDLE: demo */
.demo-stage {
  border: 1px solid var(--gray-200);
  border-radius: var(--radius-lg);
  background: var(--white);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}
.demo-stage canvas,
.demo-stage svg,
.demo-stage img { display: block; width: 100%; border-radius: var(--radius); }

.payload-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.chip {
  font-family: var(--mono);
  font-size: 0.75rem;
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--gray-300);
  border-radius: 999px;
  background: var(--white);
  color: var(--gray-700);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.chip:hover { border-color: var(--black); color: var(--black); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.chip:active { transform: translateY(0); }
.chip:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.chip.active {
  background: var(--black);
  color: var(--white);
  border-color: var(--black);
}

.scrub {
  margin: 1rem 0;
}
.scrub label {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--gray-500);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.5rem;
}
.scrub label .val { color: var(--black); font-weight: 700; }
.scrub input[type="range"] {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 3px;
  background: var(--gray-300);
  border-radius: 2px;
  outline: none;
}
.scrub input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--black);
  cursor: pointer;
  border: 2px solid var(--white);
  box-shadow: var(--shadow);
}
.scrub input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--black);
  cursor: pointer;
  border: 2px solid var(--white);
}

.demo-controls {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 0.75rem;
}

.off-script-note {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  margin-top: 1.25rem;
  padding: 0.7rem 0.85rem;
  border: 1px dashed var(--gray-300);
  border-radius: var(--radius);
  font-size: 0.75rem;
  color: var(--gray-600);
  background: var(--gray-50);
}
.off-script-note .badge {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--gray-400);
  border-radius: 4px;
  color: var(--gray-600);
  flex: none;
}

/* ---------------------------------------------------------- shared buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.875rem;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid var(--gray-300);
  background: var(--white);
  color: var(--black);
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover { background: var(--gray-50); border-color: var(--gray-400); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn:active { transform: translateY(0); box-shadow: none; }
.btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.btn-primary { background: var(--black); color: var(--white); border-color: var(--black); }
.btn-primary:hover { background: var(--gray-800); }
.btn-sm { padding: 0.35rem 0.7rem; font-size: 0.8125rem; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }

/* -------------------------------------------- segmented toggle (Pretty/etc) */
.seg {
  display: inline-flex;
  border: 1px solid var(--gray-300);
  border-radius: 6px;
  overflow: hidden;
  background: var(--white);
}
.seg button {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  padding: 0.35rem 0.7rem;
  border: none;
  border-right: 1px solid var(--gray-300);
  background: var(--white);
  color: var(--gray-500);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.seg button:hover { color: var(--black); }
.seg button:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.seg button:last-child { border-right: none; }
.seg button.active { background: var(--black); color: var(--white); }

/* ------------------------------------------------- RIGHT: live compute panel */
.compute {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.compute-toprow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.compute-block {
  border: 1px solid var(--gray-200);
  border-radius: var(--radius);
  background: var(--white);
  padding: 0.9rem 1rem;
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.compute-block:hover {
  transform: translateY(var(--lift));
  box-shadow: var(--shadow-md);
  border-color: var(--line-strong);
}
/* gentle entrance stagger for the compute cards on load */
.compute > .compute-block {
  animation: cmp-enter var(--dur-fast) var(--ease-out) both;
}
.compute > .compute-block:nth-child(1) { animation-delay: 0ms; }
.compute > .compute-block:nth-child(2) { animation-delay: 55ms; }
.compute > .compute-block:nth-child(3) { animation-delay: 110ms; }
.compute > .compute-block:nth-child(4) { animation-delay: 165ms; }
.compute-block .cb-title {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gray-400);
  margin: 0 0 0.6rem;
}

/* big meter number */
.meter {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.meter-xl { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
.meter-lg { font-size: 1.4rem; font-weight: 700; }
.meter-sm { font-size: 0.8125rem; color: var(--gray-600); }
.meter-plain {
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--gray-500);
  margin-top: 0.35rem;
}
.meter .unit { font-size: 0.7em; color: var(--gray-400); font-weight: 500; margin-left: 0.15rem; }

/* ACTIVITY card - status LED + live solve log + session totals (all REAL) */
.act-status {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--gray-500);
  margin-bottom: 0.7rem;
}
.act-status .dot {
  width: 8px; height: 8px; flex: none;
  border-radius: 50%;
  border: 1px solid var(--gray-400);
  background: transparent;             /* hollow = idle */
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.act-status.busy { color: var(--black); }
.act-status.busy .dot {
  background: var(--accent);
  border-color: var(--accent);
  /* smooth expanding-glow ring - reads as "live compute", never a blink */
  animation: cmp-pulse 1.4s var(--ease-out) infinite;
}
@keyframes cmp-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  70%  { box-shadow: 0 0 0 7px color-mix(in srgb, var(--accent) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}

/* live solve log - last few completed forwards, newest on top */
.solve-log {
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.55;
  color: var(--gray-600);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 0.5rem 0;
  margin-bottom: 0.7rem;
  min-height: 4.4rem;
}
.solve-log .sl-empty { color: var(--gray-400); font-style: italic; }
.solve-log .sl-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--gray-500);
}
.solve-log .sl-line.sl-new {
  color: var(--black);
  /* newest line slides in from the top on each solve */
  animation: cmp-slide-down var(--dur-snap) var(--ease-out);
}

/* session totals */
.act-totals {
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--gray-600);
  line-height: 1.5;
  margin-bottom: 0.55rem;
}
.act-totals .tv {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--black);
}

/* plain hardware line */
.act-hw {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gray-400);
}

/* histogram */
.histo {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 48px;
  margin-top: 0.5rem;
}
.histo .hb {
  flex: 1;
  min-width: 2px;
  background: var(--gray-300);
  border-radius: 1px 1px 0 0;
  transition: height var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.histo .hb.recent { background: var(--gray-800); }

/* PORTABILITY card - the same real forward, EXTRAPOLATED to other hardware.
   Estimates only (mono numbers, thin rules); the measured CPU latency card is
   the sole measured figure. Kept visually consistent with the other cards. */
.port-list {
  border-top: 1px solid var(--line);
  padding-top: 0.5rem;
  margin-bottom: 0.55rem;
}
.port-list .port-empty {
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--gray-400);
  font-style: italic;
}
.port-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.28rem 0;
  border-top: 1px solid var(--gray-100);
}
.port-row:first-child { border-top: none; }
.port-row .pl {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--gray-600);
  white-space: nowrap;   /* device label stays on one line, fully legible */
  flex: none;
}
.port-row .pv {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--black);
  text-align: right;
  min-width: 0;          /* the estimate+verdict wraps here (not the label) */
}
.port-row .pv .pn {
  font-weight: 400;
  font-size: 0.625rem;
  color: var(--gray-500);
}
.port-foot {
  font-family: var(--sans);
  font-size: 0.625rem;
  line-height: 1.45;
  color: var(--gray-400);
  border-top: 1px solid var(--line);
  padding-top: 0.5rem;
}

/* ENERGY & CARBON card - same instrument style (1px rules, mono numbers,
   grayscale + single accent). Three rows: Speed, To train, To run. Every
   number traces to a measured, published, or labelled-estimate source. */
.impact-list {
  border-top: 1px solid var(--line);
  padding-top: 0.5rem;
  margin-bottom: 0.55rem;
}
.impact-row {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.4rem 0;
  border-top: 1px solid var(--gray-100);
}
.impact-row:first-child { border-top: none; }
.impact-row .ir-label {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gray-400);
  white-space: nowrap;
  flex: none;
  width: 3.6rem;
}
.impact-row .ir-body { flex: 1; min-width: 0; }
.impact-row .ir-main {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--black);
  line-height: 1.35;
}
.impact-row .ir-sub {
  font-family: var(--sans);
  font-size: 0.6875rem;
  color: var(--gray-500);
  line-height: 1.4;
  margin-top: 0.12rem;
}
.impact-caveat {
  font-family: var(--sans);
  font-size: 0.6875rem;
  line-height: 1.45;
  color: var(--gray-600);
  border-left: 2px solid var(--accent);
  padding: 0.1rem 0 0.1rem 0.55rem;
  margin: 0 0 0.55rem;
}
.impact-note {
  font-family: var(--sans);
  font-size: 0.625rem;
  line-height: 1.45;
  color: var(--gray-400);
  margin: 0 0 0.5rem;
}
.impact-method {
  font-family: var(--sans);
  font-size: 0.625rem;
  color: var(--gray-400);
}
.impact-method > summary {
  font-family: var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gray-500);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.impact-method > summary::-webkit-details-marker { display: none; }
.impact-method > summary::before { content: '+ '; color: var(--gray-400); }
.impact-method[open] > summary::before { content: '- '; }
.impact-method .im-body {
  line-height: 1.5;
  color: var(--gray-400);
  padding-top: 0.4rem;
}

/* Pretty pipeline canvas */
.pipe-wrap {
  position: relative;
  background: var(--pipe-bg);
  border-radius: var(--radius);
  overflow: hidden;
}
.pipe-wrap canvas { display: block; width: 100%; }
.pipe-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
  margin-top: 0.55rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  color: var(--gray-500);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ============================================================================
   MOTION LIBRARY - reusable keyframes + utility classes ("speed is the pitch").
   compute.js drives the per-result triggers (count-up + .cmp-flow / re-play of
   .motion-snap-in). Per-demo pages can opt into the SAME motion by adding these
   classes to their own elements. Everything here is fast, smooth, and
   softened/disabled under prefers-reduced-motion (block at the very bottom).
   ============================================================================ */

/* ---- keyframes ---- */
@keyframes cmp-enter      { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes cmp-snap-in    { from { opacity: 0; transform: translateY(6px);  } to { opacity: 1; transform: translateY(0); } }
@keyframes cmp-slide-down { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes cmp-flow-sweep {
  0%   { transform: translateX(-55%); opacity: 0; }
  18%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { transform: translateX(155%); opacity: 0; }
}

/* ---- reusable utility classes (opt-in for per-demo pages) ---- */
.motion-snap-in    { animation: cmp-snap-in var(--dur-fast) var(--ease-out); }
.motion-slide-down { animation: cmp-slide-down var(--dur-snap) var(--ease-out); }
.motion-enter      { animation: cmp-enter var(--dur-fast) var(--ease-out) both; }
/* smooth fill for probability / quantile / progress bars (transition width OR height) */
.u-bar-fill { transition: width var(--dur-fast) var(--ease-out), height var(--dur-fast) var(--ease-out); }
/* subtle hover lift for any interactive card */
.u-hover-lift { transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.u-hover-lift:hover { transform: translateY(var(--lift)); box-shadow: var(--shadow-md); }

/* ---- PIPELINE FLOW - a light pulse sweeps L→R across the pipe on each solve.
   Add .cmp-flow to a .pipe-wrap (compute.js re-plays it per inference); reusable
   on any positioned container via .motion-flow. Smooth, ~300ms, never a strobe. */
.pipe-wrap, .motion-flow { position: relative; }
.pipe-wrap::after, .motion-flow::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transform: translateX(-55%);
  background: linear-gradient(100deg,
    transparent 34%,
    rgba(255, 255, 255, 0.16) 50%,
    transparent 66%);
}
.pipe-wrap.cmp-flow::after, .motion-flow.cmp-flow::after {
  animation: cmp-flow-sweep var(--dur-flow) var(--ease-out);
}

/* the per-inference result cards get a crisp snap when compute.js re-plays this */
.cmp-snap-in { animation: cmp-snap-in var(--dur-fast) var(--ease-out); }

/* ============================================================================
   LOADING STATES - a reusable centered spinner + a first-appearance ease-in.
   Anything waiting on its first real inference shows the spinner IN ITS OWN
   RESERVED SPACE (no layout jump), then swaps to the result eased in via
   .fela-fade-in. Both honour prefers-reduced-motion (block at the very bottom):
   the ring stops spinning and holds as a soft pulsing dot; the fade becomes a
   clean, motionless appear. Never a flash or strobe.
   ============================================================================ */

/* centered wrapper: flex-centers the spinner inside whatever box reserves the
   height. Pages give this a min-height matching the result so the swap is jump
   -free. */
.fela-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
  color: var(--gray-400);
}
.fela-loading .fela-loading-label {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gray-400);
}

/* the spinner itself - a smooth CSS-rotate ring, grayscale with the one accent
   on the leading arc. Centered by its .fela-loading parent (or margin auto). */
.fela-spinner {
  display: block;
  width: 26px;
  height: 26px;
  margin: 0 auto;
  border-radius: 50%;
  border: 2px solid var(--gray-200);
  border-top-color: var(--accent);
  animation: fela-spin 0.8s linear infinite;
}
.fela-spinner-sm { width: 18px; height: 18px; border-width: 2px; }
.fela-spinner-lg { width: 34px; height: 34px; border-width: 3px; }
@keyframes fela-spin { to { transform: rotate(360deg); } }

/* first-appearance ease-in: opacity 0→1 + a slight lift, house easing/tokens.
   Add this to a result element right before it is revealed. */
.fela-fade-in { animation: fela-fade-in var(--dur-flow) var(--ease-out) both; }
@keyframes fela-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* staggered variant for lists (e.g. genomics candidate rows) - pages set
   --fela-stagger per row (or use the nth-child helpers below). */
.fela-fade-in-stagger { animation: fela-fade-in var(--dur-flow) var(--ease-out) both; animation-delay: var(--fela-stagger, 0ms); }

.hidden { display: none !important; }

/* ------------------------------------------------------------- responsive */
@media (max-width: 1000px) {
  .demo-shell { grid-template-columns: 1fr; }
  .panel { border-right: none; border-bottom: 1px solid var(--line); }
  /* Stacked layout: keep source order (model card -> demo -> live compute) so
     the reader meets the readout only after they know what produced it. A
     one-column flow can't support the two-column `order: -1` hoist that made
     sense at >=1000px, so do not reorder here. */
  /* on mobile the whole page scrolls - un-pin the left panel, let content flow */
  .panel-left {
    position: static;
    max-height: none;
    overflow: visible;
    display: block;
  }
  .model-card-body { overflow-y: visible; min-height: 0; }
  /* touch tap targets: .btn computes to ~34px tall from its padding, under the
     44px guideline. Grow the box (not the desktop look) on narrow/touch
     viewports only; centered flex content keeps the same visual weight. */
  .btn { min-height: 44px; }
}
@media (max-width: 520px) {
  .demo-header .crumb { display: none; }
  .ls-grid { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------- prefers-reduced-motion
   Honour the OS setting: kill the looping/entrance/sweep animations and make
   transitions effectively instant. Interactive hover/focus feedback is kept but
   without motion (no translate). compute.js also checks the same media query and
   sets the latency number directly (no odometer roll) when reduced. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .compute > .compute-block,
  .motion-snap-in, .cmp-snap-in, .motion-slide-down, .motion-enter,
  .solve-log .sl-line.sl-new { animation: none !important; }
  .act-status.busy .dot { animation: none !important; }        /* LED holds solid, no pulse */
  .pipe-wrap::after, .motion-flow::after { display: none !important; }
  /* keep hover legible but remove the lift so nothing moves */
  .btn:hover, .chip:hover, .compute-block:hover, .u-hover-lift:hover { transform: none !important; }

  /* LOADING: the ring stops spinning and becomes a soft, slow pulsing dot so
     there is still a "still working" cue without any rotation. */
  .fela-spinner {
    animation: fela-pulse 1.6s var(--ease-out) infinite !important;
    border-color: var(--gray-300);
    border-top-color: var(--gray-300);
    background: var(--gray-300);
  }
  @keyframes fela-pulse { 0%,100% { opacity: 0.35; } 50% { opacity: 0.8; } }
  /* the fade-in becomes a clean, motionless appear (no translate/opacity ramp) */
  .fela-fade-in, .fela-fade-in-stagger { animation: none !important; }
}

/* ===========================================================================
   Embedded on /models. The parent controller adds .is-embedded and sizes the
   iframe to this document's height, so the demo must lay out at its natural
   height: no viewport-pinned panels, no inner scroll regions. One page, one
   scrollbar. Standalone pages are unaffected.
   ======================================================================== */
.is-embedded .demo-shell { min-height: 0; }
.is-embedded .panel-left,
.is-embedded .panel-right {
  position: static;
  max-height: none;
  overflow: visible;
  top: auto;
}
.is-embedded .fela-card-copy,
.is-embedded .panel-right .panel-body,
.is-embedded .panel-mid .panel-body {
  max-height: none;
  overflow: visible;
}
/* The model card body is its own scroll region: a flex child that takes the
   remaining panel height and scrolls inside it. Embedded, there is no fixed
   panel height to take, so it must lay out at its natural height like the rest. */
.is-embedded .model-card-body {
  flex: 0 0 auto;
  max-height: none;
  overflow: visible;
}
.is-embedded .demo-header { position: static; }
