/* ==========================================================================
   ovo.sx — one stylesheet, self-hosted, dark only.

   Order: tokens · fonts · reset · shell · mark · landing · uploader · prose
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Ground & surfaces. Pure #000 is deliberately rejected: it smears on OLED,
     maximises halation against light text, and leaves no room to build an
     elevation system downward. */
  --c-bg:          #131117;
  --c-bg-deep:     #0E0D12;
  --c-surface:     #1B1822;
  --c-surface-hi:  #241F2E;
  --c-surface-max: #2E2839;

  --c-hairline:    #2C2738;
  --c-hairline-hi: #3A3350;

  --c-text:        #ECE8F2;  /* 15.52:1 on the ground — AAA, and capped below
                                white so long sessions don't bloom. */
  --c-text-muted:  #A9A2B8;  /* 7.63:1  */
  --c-text-faint:  #8B839F;  /* 5.22:1  */
  --c-ink:         #191521;  /* dark text set on a pastel fill */

  --c-lilac:  #C3B2F5;  /* primary accent */
  --c-pink:   #F2B3CE;
  --c-mint:   #9FE3C4;  /* success */
  --c-sky:    #A6D4F2;
  --c-peach:  #F6C79F;
  --c-danger: #F79BA0;
  --c-focus:  #CBB9FF;

  --t-lilac-08:  rgba(195, 178, 245, 0.08);
  --t-lilac-16:  rgba(195, 178, 245, 0.16);
  --t-lilac-24:  rgba(195, 178, 245, 0.24);
  --t-mint-10:   rgba(159, 227, 196, 0.10);
  --t-danger-10: rgba(247, 155, 160, 0.10);
  --t-white-04:  rgba(255, 255, 255, 0.04);

  /* The one gradient on the site. Sky to peach: it warms as it advances, so
     "nearly done" reads warmer than "just started". The colour is the readout. */
  --g-dawn: linear-gradient(96deg, #A6D4F2 0%, #C3B2F5 38%, #F2B3CE 76%, #F6C79F 100%);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.40);
  --shadow-2: 0 8px 24px -8px rgba(0, 0, 0, 0.60);

  /* Type */
  --font-display: 'Fredoka', 'Trebuchet MS', system-ui, sans-serif;
  --font-ui: 'Hanken Grotesk', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'Commit Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;

  --fs-3xs: 0.6875rem;  /* 11 */
  --fs-2xs: 0.75rem;    /* 12 */
  --fs-xs:  0.8125rem;  /* 13 */
  --fs-sm:  0.875rem;   /* 14 */
  --fs-base: 1rem;      /* 16 */
  --fs-md:  1.125rem;   /* 18 */
  --fs-lg:  1.25rem;    /* 20 */
  --fs-xl:  1.5625rem;  /* 25 */
  --fs-2xl: 2rem;       /* 32 */
  --fs-link: 2.125rem;  /* 34 — the short code */

  /* Space & shape */
  --s-1: 4px;  --s-2: 8px;   --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 24px;  --s-7: 32px; --s-8: 40px;
  --s-9: 56px; --s-10: 72px; --s-11: 96px; --s-12: 128px;

  --r-xs: 4px; --r-sm: 8px; --r-md: 12px; --r-lg: 18px; --r-xl: 28px;
  --r-full: 999px;

  --w-stage: 560px;
  --w-prose: 640px;
  --w-shell: 960px;
  --gutter: 20px;

  /* Motion. Entrances ease out, exits ease in; --e-spring is the only
     overshoot and is reserved for things the user touched. */
  --e-out:    cubic-bezier(0.22, 0.61, 0.36, 1);
  --e-in:     cubic-bezier(0.40, 0.00, 1.00, 1);
  --e-inout:  cubic-bezier(0.65, 0.00, 0.35, 1);
  --e-spring: cubic-bezier(0.34, 1.40, 0.64, 1);
  --e-morph:  cubic-bezier(0.22, 1.00, 0.36, 1);

  --d-instant: 90ms;
  --d-fast:   160ms;
  --d-base:   220ms;
  --d-slow:   340ms;
  --d-morph:  520ms;

  --pill-w: 440px;   /* the progress track's full length */
  --pill-h: 56px;
}

/* Users who ask the OS for more contrast get firmer lines, not a new theme. */
@media (prefers-contrast: more) {
  :root {
    --c-hairline:   #4A4360;
    --c-text-muted: #C4BED4;
    --c-text-faint: #ADA5BE;
  }
}

/* --------------------------------------------------------------------------
   2. Fonts — all three served from this origin. Nothing leaves the site.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Fredoka';
  src: url('/assets/fonts/ovo-display.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Hanken Grotesk';
  src: url('/assets/fonts/ovo-text.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Commit Mono';
  src: url('/assets/fonts/ovo-mono.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   3. Reset
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 80px;
}

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font-ui);
  font-size: var(--fs-base);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; }
/* Several flex children here set an explicit display, which would otherwise
   win over the UA's [hidden] rule and keep them occupying layout space. */
[hidden] { display: none !important; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
a { color: var(--c-lilac); }

/* Keyboard users always get a ring; pointer users never do. */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100px;
  z-index: 100;
  padding: var(--s-2) var(--s-4);
  background: var(--c-surface-hi);
  color: var(--c-text);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: top var(--d-fast) var(--e-out);
}
.skip-link:focus { top: var(--s-4); }

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

/* --------------------------------------------------------------------------
   4. The mark
   -------------------------------------------------------------------------- */

.mark { display: block; height: 100%; width: auto; overflow: visible; }

/* The lockup is monoline: every letter, both eyes and both ears share one
   stroke weight. Nothing here is filled except the three lilac dots. */
.mark__eye,
.mark__muzzle,
.mark__letter,
.mark__ear-outer,
.mark__ear-tuft {
  fill: none;
  stroke: var(--c-text);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The only colour in the mark: two pupils and the dot of ".sx", identical. */
.mark__pupil-iris,
.mark__dot { fill: var(--c-lilac); }

.mark__tail {
  fill: none;
  stroke: var(--c-lilac);
  stroke-width: 5;
  stroke-linecap: round;
  opacity: 0;              /* only shown as the copy-success flick */
  transform-origin: 322px 103px;
}
.mark__tongue {
  fill: var(--c-pink);
  transform-origin: 105px 105px;
  transform: scale(1, 0);   /* the blep, retracted */
}

/* The blink squashes on its own group: asymmetric, fast down and slower up,
   because a symmetric blink looks mechanical. The outer .mark__pupil group is
   kept as a stable hook — it used to carry the cursor-tracking translate, see
   the note in site.js for why that was removed. */
.mark__lid {
  transform: scaleY(var(--blink, 1));
}

.mark__ear {
  transition: transform var(--d-base) var(--e-spring);
}
.mark__ear--l { transform-origin: 24px 56px; }
.mark__ear--r { transform-origin: 186px 56px; }

/* Ears perk while a file is being dragged over the window. */
body.is-dragging .mark__ear--l { transform: rotate(-7deg) translateY(-2px); }
body.is-dragging .mark__ear--r { transform: rotate(7deg) translateY(-2px); }

/* --------------------------------------------------------------------------
   5. Shell — header & footer
   -------------------------------------------------------------------------- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  width: 100%;
  max-width: var(--w-shell);
  margin-inline: auto;
  padding: var(--s-4) var(--gutter);
  min-height: 64px;
}

[data-variant="page"] .site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  max-width: none;
  background: color-mix(in srgb, var(--c-bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--c-hairline);
}
/* The bar spans the window; its contents stay inside the shell width. */
[data-variant="page"] .site-header {
  padding-inline: max(var(--gutter), (100% - var(--w-shell)) / 2);
}

.brand { display: block; height: 28px; line-height: 0; border-radius: var(--r-sm); }
.brand .mark { height: 28px; }

/* The landing page carries the mark in the stage instead. */
.site-nav { display: flex; gap: var(--s-5); }
.site-nav a,
.footer-nav a {
  font-size: var(--fs-xs);
  letter-spacing: 0.015em;
  color: var(--c-text-muted);
  text-decoration: none;
  padding-block: 10px;   /* 13px text still clears a 44px tap row */
  transition: color var(--d-fast) var(--e-out);
}
.site-nav a:hover, .footer-nav a:hover { color: var(--c-text); }

/* Colour is never the only signal for the current page. */
.site-nav a[aria-current="page"] {
  color: var(--c-lilac);
  border-bottom: 2px solid var(--c-lilac);
  padding-bottom: 8px;
}

.site-main { flex: 1; width: 100%; }
/* Without this the stage sizes to its min-height and the leftover space piles
   up beneath it, which reads as the whole page sitting high. */
[data-variant="landing"] .site-main { display: flex; flex-direction: column; }
[data-variant="landing"] .stage { flex: 1; }

.site-footer {
  margin-top: auto;
  border-top: 1px solid var(--c-hairline);
  background: var(--c-bg-deep);
  padding: var(--s-6) var(--gutter);
}
[data-variant="landing"] .site-footer { background: none; border-top: 0; }

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-5);
  margin-bottom: var(--s-1);
}
.footer-note {
  text-align: center;
  font-size: var(--fs-2xs);
  color: var(--c-text-faint);
}

/* --------------------------------------------------------------------------
   6. Landing stage
   -------------------------------------------------------------------------- */

.stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: calc(100dvh - 140px);
  width: 100%;
  max-width: var(--w-stage);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* Optical, not mathematical, centring — dead centre reads as sitting low. */
  padding-bottom: var(--s-11);
  position: relative;
  z-index: 1;
}

/* One static flourish on the whole site: a single lilac breath behind the
   logo at 4.5% alpha. It is never animated. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background: radial-gradient(1200px 700px at 50% 12%, rgba(195, 178, 245, 0.045), transparent 70%);
}

.stage__mark {
  display: block;
  height: clamp(38px, 8vw, 56px);
  margin-bottom: var(--s-8);
}
.stage__mark .mark { height: 100%; }

.hero-title {
  font: inherit;
  margin: 0;
  line-height: 0;
}

.filetypes {
  margin-top: var(--s-5);
  text-align: center;
  text-wrap: balance;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
.filetypes b { font-weight: 500; color: var(--c-text); }
.filetypes span {
  display: block;
  margin-top: var(--s-1);
  font-size: var(--fs-2xs);
  color: var(--c-text-faint);
}

/* --------------------------------------------------------------------------
   7. The uploader
   -------------------------------------------------------------------------- */

.zone {
  position: relative;
  isolation: isolate;
  display: grid;
  place-items: center;
  width: 100%;
  height: 300px;
  border-radius: var(--r-xl);
  background: var(--c-surface);
  border: 1px solid var(--c-hairline);
  /* A 1px inner top highlight reads as a soft light source above. */
  box-shadow: inset 0 1px 0 var(--t-white-04), var(--shadow-1);
  cursor: pointer;
  text-align: center;
  transition:
    background-color var(--d-base) var(--e-out),
    border-color var(--d-base) var(--e-out),
    transform var(--d-base) var(--e-spring);
}
/* No dashed border anywhere. It is the universal "generic upload widget" tell;
   the affordance is carried by the surface step, the arrow and the label. */

/* The trigger is the whole surface, so its ring is the zone's ring. */
.zone__trigger {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  border-radius: inherit;
  color: inherit;
  text-align: center;
}
.zone__trigger:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  box-shadow: 0 0 0 7px var(--t-lilac-24);
}
/* Once an upload starts the trigger is disabled in JS, which also takes it
   out of the tab order; this just stops it swallowing clicks meant for the
   chip underneath. */
.zone:not([data-state="idle"]) .zone__trigger { pointer-events: none; }

@media (hover: hover) and (pointer: fine) {
  .zone:hover {
    background: #1F1B29;
    border-color: var(--c-hairline-hi);
    transform: translateY(-1px);
  }
  .zone:hover .zone__arrow { color: var(--c-text); animation-duration: 2000ms; }
}

/* -- idle content ---------------------------------------------------------- */

.zone__idle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-6);
  transition:
    opacity 140ms var(--e-in),
    transform 140ms var(--e-in);
}
.zone[data-state="busy"] .zone__idle,
.zone[data-state="done"] .zone__idle,
.zone[data-state="error"] .zone__idle {
  opacity: 0;
  transform: translateY(-8px) scale(0.96);
  pointer-events: none;
}

.zone__arrow {
  width: 28px;
  height: 28px;
  color: var(--c-text-muted);
  transition: color var(--d-fast) var(--e-out);
  animation: drift 2600ms var(--e-inout) infinite alternate;
}
@keyframes drift { from { transform: translateY(-1.5px); } to { transform: translateY(1.5px); } }

.zone__label {
  display: block;
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.4;
  color: var(--c-text);
}
.zone__sub {
  display: block;
  margin-top: calc(var(--s-2) * -1 + var(--s-1));
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
.zone__sub b {
  font-weight: 500;
  color: var(--c-lilac);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* -- drag-over ------------------------------------------------------------- */

/* The whole window is the drop target — people aim badly with a file in hand. */
body.is-dragging .zone {
  transform: scale(1.018);
  border-color: var(--c-lilac);
  background: color-mix(in oklab, var(--c-surface) 88%, var(--c-lilac));
}
body.is-dragging .zone::before { opacity: 1; }

.zone::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: var(--t-lilac-08);
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity var(--d-base) var(--e-out);
}

/* Everything but the zone dims, so the zone is the only lit object. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 5;
  background: rgba(14, 13, 18, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms var(--e-out);
}
body.is-dragging::after { opacity: 1; }
body.is-dragging .stage { z-index: 6; }

/* -- the pill: progress track, then link chip. One node throughout. -------- */

.pill {
  position: absolute;
  left: 50%;
  top: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  width: 96px;
  max-width: calc(100% - 48px);
  height: var(--pill-h);
  padding: 0 var(--s-6);
  border-radius: var(--r-full);
  background: var(--c-surface-max);
  box-shadow: inset 0 0 0 1px var(--c-hairline-hi);
  overflow: hidden;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.86);
  pointer-events: none;
  /* `width` is animated here on purpose. The element is out of flow, so a
     width change dirties layout for exactly one node with no siblings to
     reflow — and it is the only way to keep true round caps at every length,
     which scaleX cannot do (it turns the caps into ellipses). The 60fps-
     critical part, the fill advance, stays a pure compositor transform. */
  will-change: width, transform, opacity;
  transition:
    opacity var(--d-base) var(--e-out),
    background-color var(--d-base) var(--e-out),
    box-shadow var(--d-base) var(--e-out);
}

.zone[data-state="busy"] .pill,
.zone[data-state="done"] .pill,
.zone[data-state="error"] .pill {
  opacity: 1;
  pointer-events: auto;
}

.pill__fill {
  position: absolute;
  inset: 0;
  background: var(--g-dawn);
  /* Pinned to the track, not the fill: growing reveals more of the sweep, so
     the colour itself is the progress readout. It also means the later
     contraction crops the gradient instead of squeezing it. */
  background-size: var(--pill-w) 100%;
  background-position: left center;
  background-repeat: no-repeat;
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;
}

/* "Working" signal that survives a network stall, when progress cannot move.
   It is a sibling of the fill, not a child: as a child the fill's scaleX would
   squash the highlight along with it. Instead the same rAF that advances the
   fill clips the sheen to match, so it sweeps only across the filled part at
   its true width. */
.pill__sheen {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.16) 50%, transparent 100%);
  background-size: 120px 100%;
  background-repeat: no-repeat;
  clip-path: inset(0 100% 0 0);
  opacity: 0;
}
.zone[data-state="busy"] .pill__sheen {
  opacity: 1;
  animation: sheen 1400ms cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes sheen {
  0%        { background-position: -140px center; }
  70%, 100% { background-position: var(--pill-w) center; }
}

/* -- the result, condensed out of the bar ---------------------------------- */

.pill__result {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 2px;
  opacity: 0;
  white-space: nowrap;
}
.pill__prefix {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-sm);
  color: var(--c-text-faint);
  opacity: 0;
  transform: translateX(-6px);
}
.pill__code {
  font-family: var(--font-mono);
  font-size: var(--fs-link);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--c-ink);
  opacity: 0;
  filter: blur(6px);
  transform: translateY(5px) scale(0.97);
}
/* A short, fixed label. The actual reason goes in the line below the pill,
   where it can wrap freely — the pill is a fixed 56px with overflow hidden,
   so a long message inside it clips the text and the retry button both. */
.pill__error {
  position: relative;
  z-index: 2;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--c-danger);
  opacity: 0;
  white-space: nowrap;
}

.copy-btn {
  position: relative;
  z-index: 2;
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-full);
  background: var(--c-surface-max);
  color: var(--c-lilac);
  opacity: 0;
  transform: scale(0.7);
}
/* Visually 32px, but a 44px target. Never inflate the visual size for this. */
.copy-btn::before {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: inherit;
}
.copy-btn svg { width: 16px; height: 16px; }
.copy-btn .icon-check { display: none; }
.copy-btn.is-copied { color: var(--c-mint); }
.copy-btn.is-copied .icon-copy { display: none; }
.copy-btn.is-copied .icon-check { display: block; }

.zone__helper {
  position: absolute;
  left: 50%;
  top: calc(50% + 52px);
  transform: translateX(-50%);
  font-size: var(--fs-2xs);
  color: var(--c-mint);
  opacity: 0;
  white-space: nowrap;
  transition: opacity var(--d-base) var(--e-out);
}
.zone__helper--manual { color: var(--c-text-muted); }
.zone__helper--error {
  color: var(--c-danger);
  white-space: normal;
  max-width: min(88%, 420px);
  text-wrap: balance;
  line-height: 1.5;
}
.zone[data-state="done"] .zone__helper,
.zone[data-state="error"] .zone__helper { opacity: 1; }

.zone__filename {
  position: absolute;
  left: 50%;
  top: calc(50% + 52px);
  transform: translateX(-50%);
  max-width: 80%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-2xs);
  color: var(--c-text-faint);
  opacity: 0;
  transition: opacity var(--d-base) var(--e-out);
}

/* -- error ----------------------------------------------------------------- */

.zone[data-state="error"] .pill {
  background: var(--t-danger-10);
  box-shadow: inset 0 0 0 1px var(--c-danger);
}
.zone.is-error {
  border-color: color-mix(in srgb, var(--c-danger) 40%, transparent);
  transition: border-color 600ms var(--e-out);
}
@keyframes shake {
  0%   { transform: translate(-50%, -50%) translateX(0); }
  20%  { transform: translate(-50%, -50%) translateX(-5px); }
  40%  { transform: translate(-50%, -50%) translateX(4px); }
  60%  { transform: translate(-50%, -50%) translateX(-3px); }
  80%  { transform: translate(-50%, -50%) translateX(2px); }
  100% { transform: translate(-50%, -50%) translateX(0); }
}

.retry-btn {
  position: relative;
  z-index: 2;
  flex: none;
  padding: 6px var(--s-3);
  border-radius: var(--r-full);
  background: var(--c-surface-max);
  color: var(--c-text);
  font-size: var(--fs-xs);
  opacity: 0;
}

/* -- session history ------------------------------------------------------- */

.history {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-5);
  min-height: 28px;
}
.history:empty { display: none; }
.history__chip {
  height: 28px;
  padding: 0 var(--s-3);
  border-radius: var(--r-full);
  background: var(--c-surface-hi);
  color: var(--c-text-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  line-height: 28px;
  transition: color var(--d-fast) var(--e-out), opacity var(--d-fast) var(--e-out);
}
.history__chip:hover { color: var(--c-text); }

/* --------------------------------------------------------------------------
   8. Subpage prose
   -------------------------------------------------------------------------- */

.prose-page {
  width: 100%;
  max-width: var(--w-prose);
  margin-inline: auto;
  padding: var(--s-10) var(--gutter) var(--s-12);
}
.page-head { padding-bottom: var(--s-6); border-bottom: 1px solid var(--c-hairline); }
.page-head h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-2xl);
  letter-spacing: -0.02em;
  line-height: 1.2;
}
.page-head .lede {
  margin-top: var(--s-3);
  font-size: var(--fs-md);
  letter-spacing: -0.005em;
  color: var(--c-text-muted);
}

.prose { padding-top: var(--s-7); }
.prose h2 {
  margin-top: var(--s-9);
  margin-bottom: var(--s-3);
  font-size: var(--fs-xl);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.3;
}
.prose p, .prose ul { margin-bottom: var(--s-5); }
.prose ul { padding-left: var(--s-5); list-style: disc; }
.prose ul::marker, .prose li::marker { color: var(--c-lilac); }
.prose li { margin-bottom: var(--s-3); padding-left: var(--s-1); }
.prose strong { font-weight: 600; color: var(--c-text); }
.prose a {
  color: var(--c-lilac);
  text-decoration-line: underline;
  text-decoration-color: color-mix(in srgb, var(--c-lilac) 35%, transparent);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--d-fast) var(--e-out);
}
.prose a:hover { text-decoration-color: var(--c-lilac); }
.prose code {
  padding: 2px 6px;
  border-radius: var(--r-xs);
  background: var(--c-surface-hi);
  color: var(--c-pink);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}
.prose .meta { font-size: var(--fs-2xs); color: var(--c-text-faint); }

/* --------------------------------------------------------------------------
   9. Reflow
   -------------------------------------------------------------------------- */

@media (max-width: 719px) {
  .zone { height: 260px; }
  .stage__mark { margin-bottom: var(--s-7); }
  .stage { padding-bottom: var(--s-7); }   /* less optical lift on a phone */
}

@media (max-width: 479px) {
  .zone { height: 220px; }          /* the whole surface is the button */
  .zone__sub { display: none; }
  .pill { padding: 0 var(--s-4); }
  :root { --fs-link: 1.75rem; }
}

/* Landscape phones: let the page scroll rather than crushing the stage. */
@media (max-height: 600px) {
  .stage { min-height: auto; padding-block: var(--s-8) var(--s-8); }
  .stage__mark { height: 32px; margin-bottom: var(--s-5); }
  .zone { height: 180px; }
}

/* --------------------------------------------------------------------------
   10. Reduced motion — a quieter site, not a dead one.

   Vestibular triggers are movement through space and scale change. Colour and
   opacity are safe and carry most of the meaning, so they are kept.
   -------------------------------------------------------------------------- */

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

  /* Restore the non-vestibular channels that carry meaning. */
  .zone, .pill, .copy-btn, .history__chip, .site-nav a, .footer-nav a, .prose a {
    transition-property: background-color, border-color, color, opacity, box-shadow !important;
    transition-duration: 140ms !important;
    transition-timing-function: var(--e-out) !important;
  }

  /* A progress bar that doesn't move isn't a progress bar. It still fills —
     just stepped and linear, with none of the liquid feel. */
  .pill__fill { transition: transform 140ms linear !important; }

  body.is-dragging .zone { transform: none; }
  .zone:hover { transform: none; }
  body.is-dragging .mark__ear--l,
  body.is-dragging .mark__ear--r { transform: none; }
  .zone__arrow { animation: none; }
  .zone[data-state="busy"] .pill__sheen { animation: none; opacity: 0; }
  .mark__pupil { transition: none; }
}
