/* =============================================================================
   DLSS 5 Video Player — project site

   An image-quality breakdown, not a product page. True black under fine grain,
   one ink carried at real scale, hairline rules, and no containers the content
   does not need. Everything on this page is load-bearing.
   ========================================================================== */

@font-face {
  font-family: 'Archivo';
  src: url('assets/fonts/archivo-latin.woff2') format('woff2-variations');
  font-weight: 400 900;
  font-stretch: 62% 125%;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122, U+2191, U+2193, U+2212, U+2215;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('assets/fonts/jbmono-latin.woff2') format('woff2-variations');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+2122, U+2212;
}

:root {
  --ground:      #050506;
  --line:        rgba(255, 255, 255, 0.15);
  --line-soft:   rgba(255, 255, 255, 0.075);

  --text:        #ecebe8;
  --text-dim:    #a4a099;
  --text-meta:   #837e77;

  --flag:        #ff6a1a;
  --flag-wash:   rgba(255, 106, 26, 0.09);

  --sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', 'Consolas', monospace;

  --gutter: clamp(1.25rem, 4vw, 2.75rem);
  --measure: 64ch;

  color-scheme: dark;
}

/* --- reset ---------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-color: #2b2b30 var(--ground);
  scrollbar-width: thin;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.62;
  font-variation-settings: 'wdth' 100;
  -webkit-font-smoothing: antialiased;
  caret-color: var(--flag);
  overflow-x: hidden;
}

img, video, svg { display: block; max-width: 100%; }
h1, h2, h3, p, ol, ul, figure { margin: 0; }

/* Browser surfaces carry the design too. */
::selection { background: var(--flag); color: #120602; }

a { color: inherit; text-decoration-color: color-mix(in srgb, var(--flag) 55%, transparent); text-underline-offset: 0.22em; }
a:hover { text-decoration-color: var(--flag); }

:focus-visible { outline: 2px solid var(--flag); outline-offset: 3px; }

kbd {
  font-family: var(--mono);
  font-size: 0.82em;
  padding: 0.12em 0.4em;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  color: var(--text);
  white-space: nowrap;
}

code { font-family: var(--mono); font-size: 0.88em; color: var(--text-dim); }

/* Parked above the viewport rather than at left: -9999px: the page sets
   overflow-x: hidden, which clips a horizontally-parked link and can leave the
   skip target unreachable. Vertical parking is unaffected by that. */
.skip {
  position: absolute; top: 0; left: 0; z-index: 99;
  background: var(--flag); color: #120602; padding: 0.75rem 1.25rem;
  font-weight: 650; text-decoration: none;
  transform: translateY(-130%);
  transition: transform 0.15s ease;
}
.skip:focus { transform: translateY(0); }
/* The skip link lands here. Focus is moved, not shown: an outline round the
   whole page would say nothing the next Tab does not. */
#main:focus { outline: none; }

/* Film grain: a material from the subject's world, not decoration.
   It sits over everything, fixed, so it has to be cheap to keep there: its own
   compositor layer (will-change) and full containment mean a scroll moves the
   content under it without repainting the grain, and the SVG noise is
   rasterised once per tile rather than with every frame of the page. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0.06;
  will-change: transform;
  contain: strict;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- shared type ---------------------------------------------------------- */

.label, .compare__tag, .readout__k, .pkg__kind, .masthead__nav a, .button__meta, .pkg__cta-size {
  font-size: 0.7rem;
  font-weight: 650;
  font-variation-settings: 'wdth' 78;
  text-transform: uppercase;
  letter-spacing: 0.15em;
}

.data, .readout__v, .pkg__file, .compare__caption-data, .footer__meta, .pkg__sum {
  font-family: var(--mono);
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* --- masthead ------------------------------------------------------------- */

.masthead {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 20;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: clamp(1.1rem, 2.4vw, 1.9rem) var(--gutter);
}

.wordmark {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  text-decoration: none;
  font-weight: 800;
  font-variation-settings: 'wdth' 88;
  letter-spacing: -0.015em;
}
.wordmark__dlss { color: var(--flag); }
.wordmark__rest { color: var(--text); }

.masthead__nav { display: flex; gap: clamp(0.9rem, 2vw, 1.9rem); }
.masthead__nav a { color: var(--text-dim); text-decoration: none; }
/* A 24px target (WCAG 2.5.8) without moving the label: the padding grows the
   hit box and the negative margin gives the room straight back. */
.masthead__nav a { padding-block: 0.45rem; margin-block: -0.45rem; }
.masthead__nav a:hover { color: var(--text); }

/* --- hero ----------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
}

/* The scrim lands on both plates equally, so the comparison stays fair. */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(98deg, rgba(5,5,6,0.94) 0%, rgba(5,5,6,0.86) 24%, rgba(5,5,6,0.42) 45%, rgba(5,5,6,0) 63%),
    linear-gradient(to top, rgba(5,5,6,0.85) 0%, rgba(5,5,6,0) 32%);
}

/* The seam settles at 55%: the face occupies roughly 49-62% of the frame at
   this crop and object-position, so anything further left puts the whole face
   on one side and the comparison proves nothing. */
.compare { position: absolute; inset: 0; margin: 0; --seam: 55%; }
/* The sweep starts at 88%. Drawing it there from the first paint (the class is
   set in <head>) means main.js picks the seam up where it already is, rather
   than moving it from 55% to 88% after the frame has shown. */
@media (prefers-reduced-motion: no-preference) {
  .js .compare { --seam: 88%; }
}
.compare__frame { position: absolute; inset: 0; overflow: hidden; }

.compare__plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 58% 50%;
}
.compare__plate--neural   { z-index: 0; }
.compare__plate--original { z-index: 1; clip-path: inset(0 calc(100% - var(--seam)) 0 0); }

.compare__tag {
  position: absolute;
  top: clamp(4.5rem, 9vh, 6.5rem);
  z-index: 4;
  color: var(--text);
  text-shadow: 0 1px 6px rgba(0,0,0,0.8);
  transition: opacity 0.2s ease;
}
.compare__tag--left  { right: calc(100% - var(--seam) + 0.85rem); }
.compare__tag--right { left: calc(var(--seam) + 0.85rem); color: var(--flag); }

/* The seam carries a dark edge so it stays visible on bright footage. */
.compare__seam {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--seam);
  width: 2px;
  margin-left: -1px;
  z-index: 4;
  background: var(--flag);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.compare__grip {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46px;
  height: 46px;
  transform: translate(-50%, -50%);
  border: 2px solid var(--flag);
  background: rgba(5, 5, 6, 0.5);
  backdrop-filter: blur(2px);
  transition: box-shadow 0.18s ease, background 0.18s ease;
}
/* Two ticks, like a splice mark: a mark, not a hue. */
.compare__grip::before,
.compare__grip::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 2px;
  height: 14px;
  background: var(--flag);
  transform: translateY(-50%);
}
.compare__grip::before { left: 13px; }
.compare__grip::after  { right: 13px; }

.compare__label {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.compare__range {
  position: absolute;
  inset: 0;
  z-index: 5;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  touch-action: pan-y;
}
.compare__range::-webkit-slider-thumb { -webkit-appearance: none; width: 60px; height: 100%; }
.compare__range::-moz-range-thumb { width: 60px; height: 100%; border: 0; opacity: 0; }

.compare:has(.compare__range:focus-visible) .compare__grip {
  box-shadow: 0 0 0 3px rgba(5, 5, 6, 0.9), 0 0 0 6px var(--flag);
  background: rgba(5, 5, 6, 0.75);
}
.compare:hover .compare__grip { background: rgba(5, 5, 6, 0.7); }

.compare__caption {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(1.1rem, 3vh, 2rem);
  z-index: 6;
  max-width: min(46rem, calc(100% - 2 * var(--gutter)));
  display: grid;
  gap: 0.3rem;
  border-left: 1px solid var(--flag);
  padding-left: 0.85rem;
}
.compare__caption-line { color: var(--text-dim); font-size: 0.82rem; }
.compare__caption-data { color: var(--text-meta); line-height: 1.5; }

.hero__type {
  position: relative;
  z-index: 10;
  pointer-events: none;
  padding: 0 var(--gutter);
  max-width: min(41rem, 92%);
  margin-bottom: clamp(3rem, 10vh, 7rem);
}
.hero__type a { pointer-events: auto; }

.hero__headline {
  font-size: clamp(2.7rem, 6.2vw, 5.1rem);
  font-weight: 800;
  font-variation-settings: 'wdth' 92;
  letter-spacing: -0.038em;
  line-height: 0.96;
  text-wrap: balance;
  margin-bottom: 1.4rem;
}

.hero__lede {
  max-width: 34rem;
  color: var(--text-dim);
  font-size: clamp(1rem, 1.35vw, 1.13rem);
  margin-bottom: 2rem;
}

.hero__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-bottom: 1.35rem; }

.hero__caveat { color: var(--text-meta); font-size: 0.85rem; max-width: 30rem; }

.hero__hint {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(1.1rem, 3vh, 2rem);
  z-index: 6;
  color: var(--text-meta);
  font-size: 0.7rem;
  font-weight: 650;
  font-variation-settings: 'wdth' 78;
  text-transform: uppercase;
  letter-spacing: 0.15em;
}

/* --- GitHub link ------------------------------------------------------------
   The mark, the word, and the star count baked in at build time. The count is
   a measured value, so it is set in the Data voice and parted from the label
   by a hairline rather than a badge. The mark is GitHub's own, inlined once
   per use as a filled 16px path in currentColor: it takes the link's
   colour in every state, and there is no icon font behind it.
   -------------------------------------------------------------------------- */

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.gh { display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none; }
.gh__mark { flex: none; width: 1rem; height: 1rem; }
.gh__stars {
  padding-left: 0.5rem;
  border-left: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text-meta);
}
.gh:hover .gh__stars { color: var(--text-dim); }

/* In the masthead the count stands alone; the noun is still spoken. */
.masthead__gh .gh__mark { width: 0.95rem; height: 0.95rem; }
.masthead__gh .gh__stars-noun,
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.footer__gh { margin-bottom: 2.25rem; color: var(--text-dim); font-size: 0.93rem; }
.footer__gh .gh__mark { width: 1.15rem; height: 1.15rem; color: var(--text); }
.footer__gh:hover { color: var(--text); }
.footer__gh:hover .gh__label { text-decoration: underline; text-decoration-color: var(--flag); }

/* The masthead sits on footage, and the fence in this frame is as bright as
   its labels, so they borrow the comparison tags' legibility shadow. */
.masthead__nav a { text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8); }

/* --- buttons -------------------------------------------------------------- */

.button {
  display: inline-flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.85rem 1.5rem;
  text-decoration: none;
  border: 1px solid transparent;
  transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}
.button__label { font-weight: 700; font-size: 1rem; letter-spacing: -0.01em; }
.button__meta { opacity: 0.72; }

.button--primary { background: var(--flag); color: #120602; }
.button--primary:hover { background: #ff8340; }

.button--ghost { border-color: var(--line); color: var(--text); align-self: stretch; justify-content: center; }
.button--ghost:hover { border-color: var(--flag); color: var(--flag); }

/* The secondary action in the hero: the source, in the primary's two-line
   shape so the pair reads as one row of choices, weighted by fill alone. */
.button--gh .button__label { display: inline-flex; align-items: center; gap: 0.55rem; }
.button--gh .gh__mark { width: 1.05rem; height: 1.05rem; }

/* --- download acknowledgement ---------------------------------------------
   A download link leaves the page sitting there with no sign the click landed,
   and the complete package is over 300 MB, so the browser's own indicator can
   take a moment to arrive. The link says it itself, in the slot already
   holding its size.

   No spinner, for two reasons. The page has one authored motion moment and
   this is not it. And a spinner would be claiming to track a transfer this
   page cannot see: the asset is served cross-origin, so there is no progress
   to read and anything moving here would be invented. A state change is the
   honest shape, and it is the same one the checksum button already uses.
   -------------------------------------------------------------------------- */

.download-status {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

[data-downloading] .button__meta,
[data-downloading] .pkg__cta-size { opacity: 1; font-weight: 650; }
.pkg__cta[data-downloading] .pkg__cta-size { color: inherit; }

/* --- sections ------------------------------------------------------------- */

.section {
  max-width: 76rem;
  margin-inline: auto;
  padding: clamp(3.5rem, 8vw, 7rem) var(--gutter);
}
.section--wide { max-width: 84rem; }
/* Two sections in a row would otherwise stack both pads (224px on a wide
   screen) into a gap that reads as the end of the page. The second keeps half,
   so the rhythm stays generous but still reads as one argument. */
.section + .section { padding-top: clamp(1.75rem, 4vw, 3.5rem); }

.section__title {
  font-size: clamp(1.65rem, 3.1vw, 2.6rem);
  font-weight: 750;
  font-variation-settings: 'wdth' 94;
  letter-spacing: -0.028em;
  line-height: 1.08;
  text-wrap: balance;
  margin-bottom: 1.1rem;
}

.section__note { max-width: var(--measure); color: var(--text-dim); margin-bottom: 2.5rem; }
.section__head { margin-bottom: 2.5rem; }
.section__head .section__note { margin-bottom: 0; }

/* --- readout -------------------------------------------------------------- */

.readout { border-top: 1px solid var(--line); }
.readout__row {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 1.5rem;
  align-items: baseline;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.readout__k { color: var(--flag); }
.readout__v { color: var(--text-dim); }
/* The limits ledger: the same key/value shape, but its values are sentences,
   so they are set in the body voice. Only the measured figures inside them
   switch to the Data voice. */
.readout__v--prose {
  font-family: var(--sans);
  font-size: 0.95rem;
  line-height: 1.55;
  letter-spacing: 0;
  max-width: var(--measure);
}
.readout__v--prose .data { font-size: 0.86em; color: var(--text); }

/* --- beats ---------------------------------------------------------------- */

.beat {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.25fr);
  gap: clamp(1.75rem, 5vw, 4.5rem);
  align-items: center;
  padding-block: clamp(2.25rem, 5vw, 4rem);
  border-top: 1px solid var(--line-soft);
}
.beat:first-of-type { border-top-color: var(--line); }

/* Alternating rows swap sides by placement, not by `order`: with `order` the
   figure lands in whichever column comes first and the screenshot gets the
   narrow one. The image keeps the wide column on both sides.

   The explicit `grid-row: 1` is what makes the flipped rows work. Text comes
   first in the DOM and is placed in column 2 there, which moves the auto
   placement cursor past column 1; grid's default sparse packing never goes
   back, so the figure would start a second row and the pair would stack.
   Pinning both to row 1 keeps them side by side. */
.beat__text   { grid-column: 1; grid-row: 1; }
.beat__figure { grid-column: 2; grid-row: 1; }
.beat--flip   { grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.85fr); }
.beat--flip .beat__text   { grid-column: 2; grid-row: 1; }
.beat--flip .beat__figure { grid-column: 1; grid-row: 1; }

.beat__title {
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 0.7rem;
}
.beat__text p { color: var(--text-dim); max-width: 38ch; }

.beat__figure img { width: 100%; height: auto; border: 1px solid var(--line-soft); }
.beat__figure figcaption { margin-top: 0.7rem; color: var(--text-meta); font-size: 0.78rem; }

/* Each step carries its own provenance, in the hero band's vocabulary: the
   density the page uses to back a picture, rather than a caption alone. */
.beat__tick {
  display: block;
  margin-top: 0.4rem;
  padding-left: 0.7rem;
  border-left: 1px solid var(--flag);
  font-family: var(--mono);
  font-size: 0.68rem;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--text-meta);
}

.demo__credit {
  margin-top: 0.7rem;
  color: var(--text-meta);
  font-size: 0.78rem;
  max-width: var(--measure);
}

/* --- gallery --------------------------------------------------------------
   One frame per scene, two plates stacked exactly: the neural plate sets the
   box and the original lies over it. Flip is a state change - the top plate is
   shown or hidden, instantly, because a cross-fade would put a blend of the
   two on screen and call it one of them. The 1:1 view is the captured file
   itself at one image pixel per device pixel, above the grain: it is the one
   place on the page where pixels have to be exactly the file's.
   -------------------------------------------------------------------------- */

.scene { margin: 0; padding-block: clamp(1.75rem, 4vw, 3rem); border-top: 1px solid var(--line-soft); }
.scene:first-of-type { border-top-color: var(--line); }

.scene__frame { position: relative; border: 1px solid var(--line-soft); background: var(--ground); }
.scene__canvas { position: relative; }
.scene__plate { display: block; width: 100%; height: auto; }
.scene__plate--original { position: absolute; inset: 0; height: 100%; }
.scene[data-view='neural'] .scene__plate--original { visibility: hidden; }

/* Which plate is on screen, said beside the frame rather than over it: two of
   the scenes are window captures whose own title bar would sit under a tag. */
.scene__tag {
  min-width: 6.5em;
  color: var(--flag);
  font-size: 0.7rem;
  font-weight: 650;
  font-variation-settings: 'wdth' 78;
  text-transform: uppercase;
  letter-spacing: 0.15em;
}
.scene[data-view='original'] .scene__tag { color: var(--text); }

.scene__loupe {
  position: absolute;
  inset: 0;
  z-index: 61;
  overflow: auto;
  background: var(--ground);
  cursor: grab;
  overscroll-behavior: contain;
  scrollbar-color: #2b2b30 var(--ground);
}
.scene__loupe[data-dragging] { cursor: grabbing; }
.scene__loupe .scene__plate { width: 100%; height: 100%; }
.scene__loupe:focus-visible { outline-offset: -2px; }

.scene__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem 1.25rem;
  margin-top: 0.85rem;
}

.scene__button {
  padding: 0.55rem 1rem;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-weight: 650;
  font-size: 0.9rem;
  cursor: pointer;
  transition: border-color 0.16s ease, color 0.16s ease;
}
.scene__button[hidden] { display: none; }
.scene__button:hover { border-color: var(--flag); color: var(--flag); }
/* Pressed is a mark on the control, not a fill: the flag edge and ink. */
.scene__button[aria-pressed='true'] { border-color: var(--flag); color: var(--flag); }

.scene__files, .scene__link {
  font-size: 0.7rem;
  font-weight: 650;
  font-variation-settings: 'wdth' 78;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--text-meta);
}
.scene__files a { margin-left: 0.6rem; color: var(--text-dim); }
.scene__link { text-decoration: none; }
/* Vertical padding widens the hit box to 24px (WCAG 2.5.8); on the inline
   file links it leaves the line box, and the layout, exactly as it was. */
.scene__files a, .scene__link { padding-block: 0.45rem; }
/* The link alone is a flex item, so it hands the padding back the same way. */
.scene__link { margin-block: -0.45rem; }
.scene__link:hover, .scene__files a:hover { color: var(--text); }

.scene__caption {
  display: grid;
  gap: 0.3rem;
  margin-top: 0.85rem;
  border-left: 1px solid var(--flag);
  padding-left: 0.85rem;
}
.scene__title { color: var(--text-dim); font-size: 0.82rem; }
.scene__data {
  color: var(--text-meta);
  font-family: var(--mono);
  font-size: 0.76rem;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* --- stills ---------------------------------------------------------------
   The comparison stills: one PNG each, the source and the render side by
   side as the player's media tools wrote them, labels included. They are
   shown whole rather than split into a flip, because both halves at once is
   what they were made to show; the hero's seam and the scene below are the
   page's two in-place comparisons. Each carries its provenance line and links
   to its record, the Real Pixels Rule in this component's shape.
   -------------------------------------------------------------------------- */

.stills {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 3rem) clamp(1.5rem, 3vw, 2.5rem);
  padding-top: clamp(1.75rem, 4vw, 3rem);
  border-top: 1px solid var(--line);
  margin-bottom: clamp(3rem, 6vw, 5rem);
}
.stills--single { grid-template-columns: minmax(0, 46rem); margin-bottom: 0; }
.still { margin: 0; }
.still__plate { display: block; width: 100%; height: auto; border: 1px solid var(--line-soft); }
.still__caption {
  display: grid;
  gap: 0.3rem;
  margin-top: 0.85rem;
  border-left: 1px solid var(--flag);
  padding-left: 0.85rem;
}
.still__title { color: var(--text-dim); font-size: 0.82rem; }
.still__data {
  color: var(--text-meta);
  font-family: var(--mono);
  font-size: 0.76rem;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.still__data a { color: var(--text-dim); }
.still__data a:hover { color: var(--text); }

/* A titled group inside a section: the gallery's player pair, and the one
   frame in the limits where the model does not help. */
.gallery__sub { font-size: 1.3rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.3; margin-bottom: 0.7rem; }
.gallery__note { color: var(--text-dim); max-width: var(--measure); margin-bottom: 0.5rem; }
#limits .gallery__sub { margin-top: clamp(2.5rem, 5vw, 4rem); margin-bottom: 1.25rem; }

/* --- New: in main, after the last release ----------------------------------
   A label on the thing it qualifies, in the Label voice and the ink, as the
   readout keys are: a word, not a badge. build.ps1 removes every one, and the
   notes that explain them, once the release the page offers carries them.
   -------------------------------------------------------------------------- */

.new {
  margin-left: 0.35em;
  color: var(--flag);
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 650;
  font-variation-settings: 'wdth' 78;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  white-space: nowrap;
  vertical-align: 0.1em;
}
.beat__title .new { font-size: 0.6rem; vertical-align: 0.3em; }
.unreleased-note { display: block; margin-top: 0.6rem; }

/* --- screens: the player's own windows under the feature matrix ----------- */

.screens {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

/* --- matrix --------------------------------------------------------------- */

.matrix {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0 clamp(1.5rem, 4vw, 3.5rem);
  border-top: 1px solid var(--line);
}

.matrix__group { padding: 1.5rem 0 1.75rem; border-bottom: 1px solid var(--line-soft); }
.matrix__title { color: var(--flag); margin-bottom: 0.9rem;
  font-size: 0.7rem; font-weight: 650; font-variation-settings: 'wdth' 78;
  text-transform: uppercase; letter-spacing: 0.15em; }

.matrix__list { list-style: none; padding: 0; display: grid; gap: 0.6rem; }
.matrix__list li {
  color: var(--text-dim);
  font-size: 0.93rem;
  line-height: 1.5;
  padding-left: 1rem;
  position: relative;
}
/* A flag or a command is one word to the reader: never break it at a hyphen. */
.matrix__list code { white-space: nowrap; }
/* A tick on the rule, not a bullet glyph. */
.matrix__list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 6px;
  height: 1px;
  background: var(--flag);
}

/* --- demo ----------------------------------------------------------------- */

.demo { position: relative; border: 1px solid var(--line-soft); }
.demo__poster { width: 100%; height: auto; }

.demo__play {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  /* No wash over the frame: this section exists to show image quality, so the
     footage keeps its luminance and the plate below carries the label. */
  background: transparent;
  border: 0;
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: background 0.18s ease;
}
.demo__play:hover { background: rgba(5, 5, 6, 0.16); }

.demo__plate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  padding: 1.5rem 2.25rem 1.35rem;
  background: rgba(5, 5, 6, 0.88);
}

.demo__play-icon {
  display: grid;
  place-items: center;
  width: 60px;
  height: 60px;
  border: 2px solid var(--flag);
  color: var(--flag);
  margin-bottom: 0.4rem;
  padding-left: 3px;
}
.demo__play-label { font-weight: 700; font-size: 1rem; }
.demo__play-meta { color: var(--text-dim); font-size: 0.78rem; }

.demo video { width: 100%; height: auto; display: block; }

/* --- packages ------------------------------------------------------------- */

.packages {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 0 clamp(1.5rem, 4vw, 3.5rem);
  border-top: 1px solid var(--line);
  margin-bottom: 3rem;
}

.pkg {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 1.75rem 0 2rem;
  border-bottom: 1px solid var(--line-soft);
}
.pkg__kind { color: var(--flag); }
.pkg--pending .pkg__kind { color: var(--text-meta); }
.pkg__summary { color: var(--text-dim); font-size: 0.95rem; max-width: 34ch; flex: 1; }

.pkg__cta {
  display: inline-flex;
  align-items: baseline;
  gap: 0.75rem;
  margin-top: 0.4rem;
  padding: 0.7rem 1.25rem;
  text-decoration: none;
  border: 1px solid var(--line);
  transition: background 0.16s ease, color 0.16s ease, border-color 0.16s ease;
}
.pkg__cta-label { font-weight: 700; }
.pkg__cta-size { color: var(--text-meta); }
.pkg__cta:hover { border-color: var(--flag); color: var(--flag); }
.pkg__cta:hover .pkg__cta-size { color: var(--flag); }

.pkg--primary .pkg__cta { background: var(--flag); border-color: var(--flag); color: #120602; }
.pkg--primary .pkg__cta .pkg__cta-size { color: rgba(18, 6, 2, 0.72); }
.pkg--primary .pkg__cta:hover { background: #ff8340; border-color: #ff8340; color: #120602; }
.pkg--primary .pkg__cta:hover .pkg__cta-size { color: rgba(18, 6, 2, 0.72); }

.pkg__file { color: var(--text-meta); word-break: break-all; }

/* The source row: the secondary action beside the downloads, in their shape. */
/* Its summary does not stretch: with no checksum beneath it, a stretched
   summary would push this CTA below the two download buttons. */
.pkg--source .pkg__summary { flex: none; }
.pkg__cta--gh { align-items: center; }
.pkg__cta--gh .gh__mark { flex: none; width: 1rem; height: 1rem; }

.pkg__sum {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  background: none;
  border: 0;
  border-bottom: 1px dashed var(--line);
  padding: 0.2rem 0;
  color: var(--text-meta);
  font-family: var(--mono);
  font-size: 0.72rem;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.16s ease, border-color 0.16s ease;
}
.pkg__sum:hover { color: var(--flag); border-color: var(--flag); }
.pkg__sum-action { font-size: 0.64rem; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.75; }
.pkg__sum[data-copied='1'] .pkg__sum-action { color: var(--flag); opacity: 1; }

/* --- requirements & notice ------------------------------------------------ */

.requires {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: start;
  margin-bottom: 2.5rem;
}
/* The start screen beside the first-run steps: what the player shows first. */
/* A player window with its caption and provenance tick, outside the beats: the
   screens under the feature matrix, and the start screen here. Its own class
   rather than .beat__figure, whose grid placement belongs to the beats. */
.screen, .requires__figure { margin: 0; }
.screen img, .requires__figure img { width: 100%; height: auto; border: 1px solid var(--line-soft); }
.screen figcaption, .requires__figure figcaption { margin-top: 0.7rem; color: var(--text-meta); font-size: 0.78rem; }
.requires__title { font-size: 1.05rem; font-weight: 700; margin-bottom: 0.9rem; }
.requires__list { color: var(--text-dim); max-width: var(--measure); padding-left: 1.2rem; }
.requires__list li { margin-bottom: 0.5rem; }
.requires__list li::marker { color: var(--flag); font-family: var(--mono); font-size: 0.85em; }

.notice {
  border: 1px solid rgba(255, 106, 26, 0.32);
  background: var(--flag-wash);
  padding: clamp(1.1rem, 2.5vw, 1.6rem);
  max-width: 62rem;
}
.notice p { color: var(--text-dim); font-size: 0.93rem; }
.notice p + p { margin-top: 0.8rem; }
.notice strong { color: var(--text); }

/* --- faq ------------------------------------------------------------------ */

.faq { border-top: 1px solid var(--line); max-width: 58rem; }
.faq__item { border-bottom: 1px solid var(--line-soft); }
.faq__item summary {
  cursor: pointer;
  padding: 1.1rem 0;
  font-weight: 650;
  font-size: 1.02rem;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}
.faq__item summary::-webkit-details-marker { display: none; }
/* Drawn at the page's hairline weight rather than typed: a '+' set in mono is a
   glyph standing in for an icon, and monospace worn as costume. */
.faq__item summary::after {
  content: '';
  flex: none;
  width: 12px;
  height: 12px;
  margin-top: 0.45em;
  background:
    linear-gradient(var(--flag), var(--flag)) center / 12px 1px no-repeat,
    linear-gradient(var(--flag), var(--flag)) center / 1px 12px no-repeat;
  transition: transform 0.18s ease;
}
.faq__item[open] summary::after {
  background: linear-gradient(var(--flag), var(--flag)) center / 12px 1px no-repeat;
  transform: rotate(180deg);
}
.faq__item p { padding-bottom: 1.2rem; color: var(--text-dim); max-width: var(--measure); font-size: 0.96rem; }

/* --- footer --------------------------------------------------------------- */

.footer { border-top: 1px solid var(--line); padding: clamp(2.5rem, 6vw, 4.5rem) var(--gutter) 3rem; max-width: 76rem; margin-inline: auto; }
.footer__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 2rem; margin-bottom: 3rem; }
.footer__col { display: flex; flex-direction: column; gap: 0.5rem; align-items: flex-start; }
.footer__title {
  font-size: 0.7rem; font-weight: 650; font-variation-settings: 'wdth' 78;
  text-transform: uppercase; letter-spacing: 0.15em; color: var(--flag); margin-bottom: 0.35rem;
}
.footer__col a { color: var(--text-dim); text-decoration: none; font-size: 0.93rem; }
.footer__col a:hover { color: var(--text); text-decoration: underline; }

.footer__base { border-top: 1px solid var(--line-soft); padding-top: 1.5rem; }
.footer__base p { color: var(--text-meta); font-size: 0.82rem; max-width: 62rem; }
.footer__meta { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; margin-top: 1rem; }

/* --- responsive ----------------------------------------------------------- */

@media (max-width: 60rem) {
  /* The stack is tightened so the download button lands whole inside the first
     viewport on a 390x844 phone: STORY ends in the download, and a primary
     action cut by the fold does not close it. */
  .hero { min-height: 0; display: block; padding-top: 4rem; }
  .hero::after { display: none; }
  .hero__headline { margin-bottom: 1rem; }
  .hero__lede { margin-bottom: 1.35rem; font-size: 1rem; }
  .hero__caveat { margin-top: 0.15rem; }

  /* The figure becomes a normal block: the frame keeps its own box and the
     caption flows underneath it instead of sitting on top of the picture.
     A taller crop than the source's 16:9 so the face is still legible on a
     phone - the comparison is the argument, and it has to survive the width. */
  .compare { position: static; }
  .compare__frame { position: relative; inset: auto; aspect-ratio: 3 / 2; }
  .compare__plate { object-position: 60% 42%; }
  .compare__tag { top: 0.85rem; }
  .compare__caption {
    position: static;
    max-width: none;
    margin: 0.85rem var(--gutter) 0;
    padding-left: 0.75rem;
  }

  .hero__type { margin: 1.5rem 0 0; max-width: none; padding-bottom: 2.5rem; }
  .hero__hint { display: none; }

  .beat { grid-template-columns: 1fr; gap: 1.5rem; }
  /* One column here, so both the column and the pinned row are released and
     the pair stacks in document order. */
  .beat__text, .beat__figure,
  .beat--flip .beat__text, .beat--flip .beat__figure { grid-column: auto; grid-row: auto; }
  .beat--flip { grid-template-columns: 1fr; }
  .beat__text p { max-width: var(--measure); }

  .readout__row { grid-template-columns: 1fr; gap: 0.2rem; }

  .stills, .screens, .requires { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 34rem) {
  /* Keep the primary action reachable; the secondary links are the ones to drop. */
  .masthead__nav a:not(.masthead__nav-primary):not(.masthead__gh) { display: none; }
  /* GitHub keeps its place as the mark alone, its name still in the link. The
     negative margin grows the hit area to 44px without moving the row. */
  .masthead__gh .gh__label,
  .masthead__gh .gh__stars {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .masthead__gh { padding: 0.85rem; margin: -0.85rem -0.85rem -0.85rem -0.35rem; }
  .masthead__gh .gh__mark { width: 1.1rem; height: 1.1rem; }
  .button { width: 100%; }
  .hero__actions { flex-direction: column; align-items: stretch; }
}

/* --- motion --------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
