/* Angler — 15 · the developer block.

   x.ai's "One API. Every modality." block, measured off a capture of the real page at
   1440 (device pixels halved):

     content column   104.5 -> 1335.5   (1231 wide, symmetric 104px margins)
     copy column      ~535 wide, plate 568 wide, ~128px gutter
     air above        196px of empty ground between the section above and the plate
     eyebrow -> head  25.5px · head -> para 21.5px · para -> button 37px
     headline         two lines, ~48px/1.01, line 1 near-black, line 2 grey
     corner cutouts   14px squares of page ground, notched out of the plate's corners

   Reproduced here in Angler's own system: Fraunces for the display line, --ink over
   --muted for the two tones, the house pill button, and the dithered sunset plate
   instead of x.ai's orange gradient. Our plate is proportionally wider than theirs
   (58% vs 46%) because the artefact it carries is 692px wide and has to clear an 8px
   type floor — see the fit note further down.

   Everything from `the pill` onward is the shipped overlay facsimile, geometry and
   easings lifted from frontend/src/components/overlay/NotchShell.tsx. The fake macOS
   desktop that used to sit behind it is gone: with no screen edge to be cut into, the
   pill is a floating panel, not a notch, so the concave fillets went too. */

/* ============================================================ the section */

.dev {
  /* scene scale; JS recomputes it per viewport. .92 is the no-JS fallback, not the
     desktop value — with JS this runs at 1.0 from 1400px up. */
  --s: .92;
  /* the corner notches. 14px on x.ai's 568px plate; ours is wider, so is this. */
  --cut: clamp(11px, 1.5vw, 20px);
  position: relative;
  background: var(--paper);
  /* Top was clamp(88px,13vw,200px) — x.ai's ~196px of empty ground above their block.
     The lede now occupies that space, so the air moves above the LEDE instead of above
     the grid, and the section starts closer to the hero's fade.
     Bottom is deliberately short: #formats is the mirror of this section and reads as
     its pair, so the two are pulled together. The old symmetric padding met #formats'
     own --sec-y for a 302px trough at 1440 — more separation than the page gives two
     unrelated sections. */
  padding-block: clamp(56px, 7vw, 104px) clamp(40px, 5vw, 72px);
}

/* ---------------------------------------------------------------- the lede */

.dev__lede {
  margin: 0 auto clamp(56px, 7vw, 104px);
  /* One line at desktop, as in the reference. Wide enough for the full 30 characters
     at the top of the clamp; balance handles the break when a narrow viewport forces
     one, so it splits at the sentence rather than mid-clause. */
  max-width: min(100% - 2 * var(--gutter), 1060px);
  text-align: center;
  text-wrap: balance;
  font-family: var(--serif);
  /* 700, the one place on the site that uses it — see the @font-face note in base.css.
     Bigger too: the glyphs are a window onto a photograph now, and at the old 3.4rem/600
     the strokes were too narrow to show anything but a smear of colour. */
  font-weight: 700;
  font-size: clamp(2.3rem, 4.9vw, 4rem);
  line-height: 1.04;
  letter-spacing: -0.024em;
}
/* Brand colour, not the photograph, and not the --ink/--muted pair the section
   headlines use. Those recede the second clause; here the second clause is the payoff,
   so it takes --lake and the setup stays --ink. Teal against a page of near-black
   display type is what makes this one line read as the band's opening statement.
   Measured on --paper: --ink 15.7:1, --lake 7.3:1. */
.dev__lede--ink  { color: var(--ink); }
.dev__lede--soft { color: var(--lake); }

.dev__wrap {
  margin-inline: auto;
  width: calc(100% - 2 * clamp(16px, 3vw, 44px));
  max-width: 1280px;
  display: grid;
  /* Not 1fr 1fr. The plate has to hold a 692px artefact at close to 1.0 or the mock's
     9px type drops under 8 rendered px; .54fr of copy is what pays for that, and it is
     still a comfortable measure for a 31px display line. */
  /* .54fr gave the plate 65% of the block and a 718px scroller. Shifted toward the
     copy so the plate reads as a panel beside the text rather than the other way
     round. .80fr is the floor: --s = scroller/692 capped at 1, and below a ~615px
     scroller the mock's 9px type renders under 8px. .80fr measured 7.88px; .72 holds 8.1. */
  grid-template-columns: minmax(0, .72fr) minmax(0, 1fr);
  gap: clamp(36px, 6vw, 86px);
  align-items: center;
}

/* ---- the copy column ---- */

/* Not base.css's .eyebrow: that one is gold with a trailing rule. x.ai's is a plain
   small grey line, and the client asked for grey. House mono caps otherwise. */
.dev__eyebrow {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
}

.dev__h {
  margin-top: 22px;
  font-family: var(--serif);
  font-weight: 600;
  /* The vw term tracks the column, so both lines keep the same slack at every width.
     Measured, not guessed: the longer line sets at 9.4x its own font-size in Fraunces
     600, and the copy column runs ~10.5x it, so there is ~10% to spare everywhere the
     two-column form exists. Deliberately not `nowrap` — a font substitution should
     wrap the line, not bleed it into the gutter. */
  font-size: clamp(1.85rem, 2.95vw, 2.5rem);
  line-height: 1.12;
  letter-spacing: -.02em;
}
.dev__h span { display: block; }
.dev__h--ink  { color: var(--ink); }
/* the second tone. --muted, not --faint: --faint is a hairline colour and measures
   2.3:1 on cream. --muted is 6.5:1 on --paper and still reads as a distinctly
   lighter grey beside --ink. */
.dev__h--soft { color: var(--muted); }

.dev__p {
  margin-top: 21px;
  max-width: 46ch;
  font-size: 16px;
  line-height: 1.62;
  color: var(--ink-soft);
}

.dev__act { margin-top: 30px; }

/* ---- the metrics ---- */

/* Two metrics, not three. max-content rather than 1fr: at two items, equal fractions
   stretch them to a third of the column each and the row reads as a table with a
   missing cell. Sized to their own content and set side by side, it reads as a pair. */
.dev__stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, max-content));
  gap: 12px clamp(34px, 5vw, 64px);
  margin-top: 44px;
  padding: 0;
  list-style: none;
}
.dev__stat__v {
  display: block;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.75rem, 2.4vw, 2.15rem);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--ink);
}
.dev__stat__l {
  display: block;
  margin-top: 10px;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--muted);
}

/* ============================================================ the plate

   x.ai's move: one image-backed rectangle with the product artefact floating on it and
   small squares notched out of every corner, so it reads as a cut plate rather than a
   rounded box. The notches are page-ground squares painted over the corners — that also
   hides the 6px radius underneath, which is why the corners read genuinely square while
   the element still carries a token radius. */

.dev__panel {
  position: relative;
  /* 2vw, not a bigger fixed inset. Above ~1300 the scene hits its 1.0 cap and stops
     consuming width, so the leftover becomes centring margin: the artefact ends up
     ~42px off the plate edge at 1440 (5.4%, against x.ai's 8.4%) while the fit still
     runs at native scale. A larger pad would buy the same visual inset by shrinking
     the mock instead, which is the wrong end to pay from. The block padding runs a
     shade wider than the inline value so the two visual insets land together: the
     inline side gets the centring slack on top of its padding, the block side does
     not. */
  padding: clamp(15px, 2.6vw, 40px) clamp(15px, 2vw, 41px);
  border-radius: var(--r-panel);
  background-color: var(--lake-deep);   /* holds the shape before the image lands */
  background-image: url(../assets/img/demo-bg.webp);
  background-size: cover;
  background-position: center;
  /* deliberately NOT overflow:hidden — that would clip the cutouts back to the radius
     and round the very corners the notch is meant to square off. */
}
.dev__panel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(var(--paper), var(--paper)) 0    0    / var(--cut) var(--cut) no-repeat,
    linear-gradient(var(--paper), var(--paper)) 100% 0    / var(--cut) var(--cut) no-repeat,
    linear-gradient(var(--paper), var(--paper)) 0    100% / var(--cut) var(--cut) no-repeat,
    linear-gradient(var(--paper), var(--paper)) 100% 100% / var(--cut) var(--cut) no-repeat;
}

/* width:100%, never content-sized — the scene's own width comes from --s, which comes
   from this box, so a content-sized scroller would feed back on itself. */
.demo__scroller {
  width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.demo__scroller::-webkit-scrollbar { display: none; }

/* Sized for the OPEN panel at all times, so results landing never resize the plate.
   Keep 692x632 in step with SCENE_W / SCENE_H in js/15-demo.js. */
.demo__box {
  margin-inline: auto;
  width:  calc(692px * var(--s));
  height: calc(632px * var(--s));
}

/* ============================================================ the scene */

.scene {
  /* WEB-ONLY lift of --notch-text-3. The app draws that tier on a physical notch at
     full size; here the facsimile is scaled and the tier lands at 10-11px, where 0.30
     alpha measures 2.5:1. 0.48 clears 4.5:1 on both the pill and the card plate.
     Do not push this back into tokens.css — the app's value is correct for the app. */
  --notch-text-3-web: rgb(255 255 255 / .48);
  --toast-bg: rgb(30 30 32 / .95);

  position: relative;
  width: 692px;
  height: 632px;
  transform: scale(var(--s));
  transform-origin: 0 0;
  font-family: var(--sans);
}

/* ============================================================ the pill */

/* Open from the first frame. There is no collapsed 190x33 state any more, and no
   `is-open` class — the width never changes, and the only geometry left in motion is
   the height, which grows once when the three results land.
   62px is the field-only height measured at 1x, used so the very first frame matches
   what JS publishes into --panel-h a moment later. */
.notch {
  position: absolute;
  /* Centred, not pinned to the top. Pinned was right when the pill hung off a screen
     edge; on a plate it left a small object stuck to the top of a 692x632 field, which
     reads as broken rather than composed. Full height, the panel measures the whole
     box, so `top:50% / translateY(-50%)` resolves to exactly the same rect. */
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
  width: 692px;
  height: var(--panel-h, 62px);
  background: var(--pill);
  /* All four corners, not `0 0 r r`. It no longer hangs off a screen edge — it is an
     object floating on the plate, so it needs its own bottom AND top geometry. */
  border-radius: var(--r-panel);
  /* black-on-warm-dark: a teal-tinted lift is invisible here, so this is a plain drop */
  box-shadow: 0 2px 8px rgb(0 0 0 / .28), 0 34px 74px -26px rgb(0 0 0 / .64);
}
/* Added by JS immediately after the first remeasure, so the ~8px correction between the
   62px fallback and the real measured field height is not itself animated. */
.scene.is-live .notch {
  transition: height 620ms var(--ease-open);
}
/* Once the results have landed and settled, later height changes (a late font swap,
   a resize) ease out instead of bouncing — mirrors NotchShell's `settled` switch. */
.scene.is-live.is-settled .notch {
  transition: height 280ms var(--ease-close);
}
/* The section's only remaining transition. The reduced-motion guard used to live on
   `.notch__cue`, which went with the collapsed state — without this, the results
   landing would still animate the panel's height by ~570px. */
@media (prefers-reduced-motion: reduce) {
  .scene .notch { transition: none; }
}

.notch__clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  display: flex;
  justify-content: center;
  align-items: flex-start;
}
/* Always laid out at the expanded width, so its measured height is correct even
   while the pill is still small. */
.notch__inner {
  flex: none;
  width: 692px;
  padding: 12px 12px 10px;
}

/* Deleted with the collapsed state: `.notch__cue` (the breathing presence glow drawn
   around the 190x33 pill, and the only idle animation this page allowed), `.notch__hot`
   (the invisible 240x64 hotzone that stood in for the app's native hover monitor) and
   `.notch__prompt` / `__promptline` / `__promptchip` (the connector line, its rotated
   arrowhead, and the dark "Move your cursor to the panel" chip — the chip was dark
   because the dithered plate underneath runs 0.17–0.73 luminance and no text token
   cleared 4.5:1 on it directly). Nothing else referenced them. */

/* ============================================================ panel content */

.notch__field {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 38px;
  padding: 8px 12px;
  background: var(--notch-field);
  border-radius: var(--r-field);
  color: var(--notch-text-3-web);   /* the search glyph inherits this */
}
.notch__field > svg { flex: none; }
.notch__q  { font-size: 13px; color: var(--notch-text); white-space: nowrap; }
.notch__ph { font-size: 13px; color: var(--notch-text-3-web); white-space: nowrap; }
.notch__caret {
  flex: none;
  width: 1.5px;
  height: 15px;
  background: var(--notch-text);
  opacity: 0;
}
.scene.is-typing .notch__caret { opacity: .85; }   /* solid, never blinking */
.notch__wm {
  margin-left: auto;
  flex: none;
  font-family: var(--serif);
  /* No italic face ships, so `italic` here bought a synthesised skew inside a
     facsimile of the real app. Tracking does the differentiating instead. */
  font-style: normal;
  letter-spacing: .08em;
  font-size: 11px;
  color: var(--notch-text-2);
}

.notch__results {
  display: none;
  margin-top: 8px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .42s var(--ease-house) .06s, transform .42s var(--ease-house) .06s;
}
.scene.has-results .notch__results { display: block; }
.scene.has-results.is-resolved .notch__results { opacity: 1; transform: none; }

/* ---- canvas header ---- */
.rhead {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 26px;
  padding: 0 2px;
  font-size: 12px;
  color: var(--notch-text);
}
.badge {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 3px;
  background: var(--fc);
  color: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 9.5px;
}
.badge[hidden] { display: none; }
.rhead__file  { text-decoration: underline; text-underline-offset: 2px; white-space: nowrap; }
.rhead__crumb { color: var(--notch-text-3-web); }
.rhead__loc   { color: var(--notch-text-2); white-space: nowrap; }
.rhead__title {
  flex: 1;
  min-width: 0;
  color: var(--notch-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rhead__flash {
  flex: none;
  font-size: 10.5px;
  font-weight: 500;
  white-space: nowrap;
  opacity: 0;
  color: var(--fc, var(--notch-text));
  transition: opacity .3s var(--ease-house);
}
.rhead__flash.is-on { opacity: 1; }

/* ---- canvas ---- */
.canvas {
  position: relative;
  width: 668px;
  height: 376px;
  margin-top: 4px;
  border-radius: var(--r-card);
  overflow: hidden;
  background: var(--ink);
}
.cdoc {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity .22s var(--ease-house);
}
.cdoc.is-show { opacity: 1; }
.cdoc[hidden] { display: none; }

.page {
  position: relative;
  background: var(--paper);
  box-shadow: 0 4px 22px rgb(0 0 0 / .5);
  color: var(--ink);
}
.page--slide { width: 620px; height: 349px; padding: 22px 26px; }
.page--sheet { width: 620px; height: 349px; padding: 0; overflow: hidden; }
.page--pdf   { width: 262px; height: 349px; padding: 20px 18px; }

/* ---- the matched atom ---- */
.atom { position: relative; }
.atom.is-match {
  border: 1.5px solid var(--fc);
  border-radius: 4px;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fc) 20%, transparent);
  transition: box-shadow .16s var(--ease-nav);
}
/* The mock's type floor. 9px authored; the fit keeps --s at 1.0 from 1400px up and
   never below ~0.89 while the block is two columns, so nothing here renders under
   8 device-independent px at any width the two-column layout survives. */
.atom__badge {
  position: absolute;
  top: -2px;
  left: 100%;
  margin-left: 6px;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--fc);
  padding: 1.5px 6px 2px;
  border-radius: 2px;
  white-space: nowrap;
}

/* ---- the slide facsimile ---- */
.sl__title { font-size: 19px; font-weight: 600; letter-spacing: -.01em; }
.sl__rule  { width: 44px; height: 2.5px; background: var(--lake); margin-top: 7px; }

.atom--chart {
  position: absolute;
  left: 26px;
  top: 84px;
  width: 292px;
  height: 172px;
  padding: 7px 11px 6px;
  background: var(--paper);
}
.ch__head { display: flex; justify-content: space-between; align-items: baseline; }
.ch__head b { font-size: 10px; font-weight: 600; color: var(--ink-soft); }
/* Only a 500 face of JetBrains Mono ships, so every --mono rule below pins 500 —
   inheriting 400 (or asking for 600) buys a synthesised face, which at this size
   is mush. */
.ch__head span {
  font-family: var(--mono); font-weight: 500; font-size: 9px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.ch__plot {
  display: flex;
  align-items: flex-end;
  gap: 13px;
  height: 86px;
  margin-top: 6px;
  border-bottom: 1.5px solid var(--ink-soft);
  background-image: repeating-linear-gradient(to top, var(--line) 0 1px, transparent 1px 21px);
}
.ch__grp { flex: 1; display: flex; align-items: flex-end; gap: 2px; height: 100%; }
.ch__grp i { flex: 1; height: calc(var(--h) * 1%); border-radius: 1.5px 1.5px 0 0; }
.ch__grp i.a { background: var(--lake); }
.ch__grp i.b { background: var(--dawn); }
.ch__axis { display: flex; gap: 13px; margin-top: 3px; }
.ch__axis span {
  flex: 1; text-align: center;
  font-family: var(--mono); font-weight: 500; font-size: 9px; color: var(--muted);
}
.ch__legend { display: flex; gap: 12px; margin-top: 4px; }
.ch__legend span {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--mono); font-weight: 500; font-size: 9px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
.ch__legend i { width: 7px; height: 7px; border-radius: 1px; }
.ch__legend i.a { background: var(--lake); }
.ch__legend i.b { background: var(--dawn); }

.sl__kpi { position: absolute; left: 386px; top: 84px; width: 208px; }
.sl__kpi .num { font-family: var(--serif); font-weight: 600; font-size: 30px; line-height: 1; }
.sl__kpi .lbl {
  margin-top: 3px; font-family: var(--mono); font-weight: 500; font-size: 9px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}

.sl__table { position: absolute; left: 386px; top: 150px; width: 208px; }
.sl__table .g { display: grid; grid-template-columns: 1.6fr .8fr .8fr .8fr; row-gap: 5px; }
.sl__table b {
  font-family: var(--mono); font-size: 9px; font-weight: 500; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--line); padding-bottom: 3px;
}
.sl__table span { font-size: 10.5px; color: var(--ink-soft); }
.sl__table .r { text-align: right; font-family: var(--mono); font-weight: 500; font-size: 10.5px; }

.sl__foot {
  position: absolute; left: 26px; right: 26px; bottom: 16px;
  font-size: 9.5px; line-height: 1.5; color: var(--muted); max-width: 470px;
}

/* ---- the sheet facsimile ---- */
.sh__head { display: flex; background: var(--vellum-2); border-bottom: 1px solid var(--line); }
.sh__head span {
  flex: 1; padding: 4px 0; text-align: center;
  font-family: var(--mono); font-weight: 500; font-size: 9px; color: var(--muted);
  border-right: 1px solid var(--line);
}
.sh__grid {
  position: relative;
  height: 325px;
  background-image:
    repeating-linear-gradient(to bottom, transparent 0 26px, var(--line-soft) 26px 27px),
    repeating-linear-gradient(to right, transparent 0 77px, var(--line-soft) 77px 78px);
}
.atom--range {
  position: absolute; left: 78px; top: 27px; width: 388px; height: 156px;
  padding: 7px 10px;
  background: color-mix(in srgb, var(--ft-xlsx) 4%, transparent);
}
.atom--range .g { display: grid; grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr; row-gap: 5px; }
.atom--range b {
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft);
}
.atom--range span { font-family: var(--mono); font-weight: 500; font-size: 9px; color: var(--ink-soft); }
.atom--range .r { text-align: right; }
.sh__label {
  position: absolute; left: 80px; top: 194px;
  font-family: var(--mono); font-weight: 500; font-size: 9px; color: var(--muted);
}

/* ---- the pdf facsimile ---- */
.pdf__h { font-family: var(--serif); font-weight: 600; font-size: 13px; }
.pdf__sk { height: 6px; border-radius: 2px; background: var(--vellum-2); margin-top: 9px; }
.atom--text { margin-top: 20px; padding: 4px 5px; }
.atom--text p { font-size: 9px; line-height: 1.55; color: var(--ink-soft); }
.atom--text .atom__badge { left: auto; right: -2px; top: -19px; margin-left: 0; }

/* ---- best matches ---- */
.bm { margin-top: 9px; }
.bm__label {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--notch-text-3-web);
}
.cards { display: flex; gap: 8px; margin-top: 6px; }

.card {
  flex: 0 0 180px;
  padding: 7px 8px;
  border-radius: var(--r-card);
  text-align: left;
  /* .04 fill measured rgb(11,13,13) against a rgb(1,2,3) panel — 1.06:1, i.e. no card
     at all — and the .5px hairline times the scene scale was a sub-pixel ring, which
     the compositor rounds away. Both raised until the card is an object. */
  background: rgb(255 255 255 / .10);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .16);
  transition:
    background-color .15s var(--ease-nav),
    box-shadow .15s var(--ease-nav),
    opacity .18s var(--ease-nav);
}
/* the old hover delta was 7 levels out of 255 — not a state change, a rounding error */
@media (hover: hover) and (pointer: fine) {
  .card:hover { background: rgb(255 255 255 / .16); box-shadow: inset 0 0 0 1px rgb(255 255 255 / .28); }
}
.card:active { background: rgb(255 255 255 / .20); }

/* The three matches arrive one after another instead of with the block that holds
   them. This is the demo's whole point — Angler returns RANKED matches, 94/88/81 —
   and three cards landing simultaneously demonstrates nothing about ranking.
   --ease-close, not --ease-open: three overshoots side by side reads as jelly.
   Delays start after .notch__results' own .06s+.42s reveal is most of the way done. */
.scene.has-results.is-resolved .card {
  animation: demo-card-in .34s var(--ease-close) both;
}
.scene.has-results.is-resolved .card:nth-child(1) { animation-delay: .26s; }
.scene.has-results.is-resolved .card:nth-child(2) { animation-delay: .32s; }
.scene.has-results.is-resolved .card:nth-child(3) { animation-delay: .38s; }
@keyframes demo-card-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  /* base.css clamps animation-duration to 1ms but NOT animation-delay, so without
     this the cards would still wait a third of a second to pop in. */
  .scene.has-results.is-resolved .card { animation-delay: 0s; }
}
.card.is-active {
  background: rgb(255 255 255 / .16);
  box-shadow: inset 0 0 0 1.5px var(--fc-active, var(--notch-border));
}
.card[data-doc="p"].is-active { --fc-active: var(--ft-pptx); }
.card[data-doc="x"].is-active { --fc-active: var(--ft-xlsx); }
.card[data-doc="d"].is-active { --fc-active: var(--ft-pdf); }

.card__r1 { display: flex; align-items: center; gap: 6px; }
.card__type  { font-size: 12px; font-weight: 500; color: var(--notch-text-2); }
.card__score { margin-left: auto; font-family: var(--mono); font-weight: 500; font-size: 12px; color: var(--notch-text-2); }
.card__prev {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 6px; height: 30px;
  font-size: 11px; line-height: 1.35; color: var(--notch-text);
}
.card__loc {
  display: block; margin-top: 5px;
  font-size: 12px; color: var(--notch-text-3-web);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* spotlight: hovering a match dims the others and rings its atom in the canvas */
/* .6 made the UI look like it was degrading when you pointed at it; .8 still reads as
   a spotlight without the other two dropping out of the design. */
.scene[data-hover] .card { opacity: .8; }
.scene[data-hover="p"] .card[data-doc="p"],
.scene[data-hover="x"] .card[data-doc="x"],
.scene[data-hover="d"] .card[data-doc="d"] { opacity: 1; }
.scene[data-hover="p"] .atom[data-doc="p"],
.scene[data-hover="x"] .atom[data-doc="x"],
.scene[data-hover="d"] .atom[data-doc="d"] {
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--fc) 20%, transparent),
    0 0 0 7px color-mix(in srgb, var(--fc) 40%, transparent);
}

.hintbar {
  margin-top: 8px;
  min-height: 24px;
  display: flex;
  align-items: center;
  font-size: 12px;
  color: var(--notch-text-3-web);
}

/* ---- toast ----
   The app puts this 52px off the bottom of the screen. Here it sits 52px off the
   bottom of the PANEL instead — inside it, as an overlay. */
.scene__toast {
  position: absolute;
  left: 50%;
  top: calc(var(--panel-h, 70px) - 52px);
  z-index: 9;
  transform: translateX(-50%);
  max-width: 520px;
  padding: 8px 16px 9px;
  border-radius: var(--r-field);
  background: var(--toast-bg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  /* it lands ON the black pill, so it needs its own edge to read at all */
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .18), 0 14px 32px rgb(0 0 0 / .45);
  font-size: 11px;
  color: var(--notch-text);
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity 250ms var(--ease-house), visibility 0s 250ms;
}
.scene__toast.is-on {
  opacity: 1;
  visibility: visible;
  transition: opacity 250ms var(--ease-house), visibility 0s 0s;
}

/* ============================================================ responsive

   1180, not 900. Below 1180 the two-column form stops working twice over: the plate
   can no longer hold the 692px artefact above ~0.89 scale (the mock's 9px type falls
   under 8 rendered px), and the copy column drops under ~370px, which is too narrow
   for a display line plus three metrics. Stacked, the plate is full width and the
   scene runs at 1.0 down to ~750px, so stacking earlier is strictly better for both. */
@media (max-width: 1180px) {
  .dev__wrap {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(34px, 5vw, 56px);
  }
  .dev__copy { max-width: 62ch; }
  /* Stacked, the plate has width to spare, so it can afford a proper margin round the
     artefact — the two-column padding is sized against a 675px plate and leaves a 15px
     hairline here. The cap stops the plate becoming a full-bleed band at 1180, where
     it would otherwise run 1109 wide around a 692 panel. */
  .dev__panel {
    max-width: 820px;
    padding: clamp(18px, 4.4vw, 56px);
  }
}

@media (max-width: 600px) {
  .dev__stats { gap: 14px; }
  .dev__stat__l { font-size: 12px; }
  /* The scroller pans sideways down here with its scrollbar suppressed, so nothing
     tells you the panel continues past either edge. Fade the cut instead. */
  .demo__scroller {
    -webkit-mask-image: linear-gradient(to right, transparent 0, black 14px,
                                        black calc(100% - 14px), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, black 14px,
                                black calc(100% - 14px), transparent 100%);
  }
}

/* Two metrics now, so the old three-column crush at 320 no longer applies — but
   "Fewer tokens to your agent" is the longest label the row has ever carried, and at
   max-content it can push the pair wider than a 288px column. Stack them here. */
@media (max-width: 420px) {
  .dev__stats { grid-template-columns: minmax(0, 1fr); }
  /* the headline's own clamp minimum still measures 278px, which is 10px of slack in a
     288px column at 320. Step it down rather than ship a line that nearly touches. */
  .dev__h { font-size: 1.6rem; }
  .dev__stats { gap: 10px; }
  .dev__stat__v { font-size: 1.5rem; }
  .dev__stat__l { font-size: 11.5px; }
}

/* No JS: --s is never recomputed, so pick something sane per breakpoint. */
@media (max-width: 620px) { .dev { --s: .62; } }
