/* DesignLoop site — the DesignLoop editorial system.
   Cool porcelain, ink, a high-contrast serif for display type, and exactly one
   ultramarine voice. Hierarchy comes from the type contrast (serif display
   against a quiet grotesque) and hairlines, never from cards or borders —
   the page argues against the generated-UI look, so it can't wear it.

   The values below mirror extension/src/ui/tokens.css — the product is the
   source of truth for the palette, and the site must not drift from it. */

/* The display face, self-hosted: the site serves the same Fraunces the product
   bundles, so the CSP still needs no third-party origin. */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/fraunces-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/fraunces-var-italic.woff2') format('woff2');
}

:root {
  --bg: #e9ebf2;          /* cool porcelain */
  --bg-2: #e0e3ec;        /* a stop deeper — footer, wells */
  --panel: #fcfcfe;
  --text: #17181f;        /* ink */
  --dim: rgba(23, 24, 31, 0.68);
  --dim-2: rgba(23, 24, 31, 0.38);
  --line: rgba(23, 24, 31, 0.14);
  --brand: #4353ff;       /* ultramarine — the one accent */
  --brand-deep: color-mix(in srgb, var(--brand) 86%, var(--text));
  --accent: #4353ff;      /* ultramarine — cool counterweight, used once */

  --brand-gradient: linear-gradient(135deg, #2bb3de, #4353ff 55%, #8a55f0);

  /* The two halves of the mark, sampled from icon.png. */

  /* The aura — the identity's actual signature: violet and ultramarine
     bleeding into each other, cyan rising from below. Lifted verbatim from
     the product tokens; the hero is a printed field, not a beige page. */
  --aura:
    radial-gradient(120% 130% at 82% -18%, rgba(138, 85, 240, 0.92) 0%, rgba(67, 83, 255, 0.55) 42%, transparent 68%),
    radial-gradient(110% 120% at 12% 118%, rgba(43, 179, 222, 0.85) 0%, transparent 55%),
    linear-gradient(118deg, #2bb3de 0%, #4353ff 34%, #6154f8 72%, #8a55f0 100%);
  --on-aura: #f8faff;
  --on-aura-dim: rgba(248, 250, 255, 0.82);

  /* The pixel — the dither's single grain, promoted to a token. Everything that
     carries the motif away from the hero (rules, shot frames, the eyebrow's
     bullet) is built from these two. */
  --px: rgba(23, 24, 31, 0.20);
  --px-faint: rgba(23, 24, 31, 0.06);

  --r-pill: 999px;
  --r-panel: 18px;

  /* Motion. The built-in CSS easings are too weak to read as decided, so the
     page carries strong curves instead: --ease-out for anything entering,
     exiting or being pressed, --ease-in-out for anything that moves across the
     screen. Hover and colour changes keep the plain `ease` keyword — a colour
     crossfade wants a symmetric curve, not a punch. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  --font-sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif;
  --font-serif: 'Fraunces', 'Iowan Old Style', 'Palatino', Georgia, 'Times New Roman', serif;
  --font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, monospace;

  /* Film grain — the same turbulence tile the product uses, felt not seen. */
  --noise: 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.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  /* The dither tile. Opaque grayscale turbulence centred on mid-grey, applied
     with `overlay`: grains above 0.5 lighten, grains below darken, and a flat
     mid-grey is a no-op. That last property is the whole point — the tile can
     lie across the gradient AND the bare paper without greying the paper,
     which a black-alpha tile cannot do (it just dims whatever it covers). */
  --grain: 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.7' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.33 0.33 0.33 0 0 0.33 0.33 0.33 0 0 0.33 0.33 0.33 0 0 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  color-scheme: light;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Fraunces carries an optical-size axis: let the display sizes pick up the
     high-contrast cut and the small ones stay sturdy. */
  font-optical-sizing: auto;
}

/* The paper is printed stock, not a flat hex: one fixed grain sheet over
   everything, at an opacity you feel rather than see. */
body::after {
  content: '';
  position: fixed; inset: 0; z-index: 999;
  pointer-events: none;
  background-image: var(--noise);
  background-size: 160px 160px;
  opacity: 0.05;
  mix-blend-mode: multiply;
}

::selection { background: rgba(67, 83, 255, 0.18); }

.wrap {
  max-width: 880px;
  margin: 0 auto;
  padding-inline: 24px;
}

/* ── Nav ── */
.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 24px;
  background: rgba(238, 233, 221, 0.8);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--line);
}

/* The mark at nav scale. It's the real logo now, not a gradient chip standing in
   for one — the sub-pages wear the same face as the hero. No radius: the mark is
   its own silhouette, not a tile, and rounding the box clips the circle. */
.dot {
  /* 24px, not 17px, to match .mark in the hero nav. The two call sites draw the
     same logo but from different viewBoxes: the hero's is a tight 32-unit grid,
     this one a 100-unit master whose artwork only spans 64 of those units. Equal
     box sizes are the only way the mark reads the same size on both pages. */
  width: 24px;
  height: 24px;
  flex: none;
  display: block;
}

.nav strong {
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
  letter-spacing: -0.01em;
}

.nav .spacer { flex: 1; }

.nav a {
  color: var(--dim);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  padding: 6px 10px;
  border-radius: var(--r-pill);
  /* The press has to be transitioned too, or the scale snaps back on release. */
  transition: color 0.16s ease, background-color 0.16s ease,
    transform 0.16s var(--ease-out);
}

/* Hover is gated to real pointers. A tap on a touch screen fires :hover and the
   link stays lit after the finger is gone — on a landing page, where most of the
   traffic is a phone, that is the common case, not the edge case. */
@media (hover: hover) and (pointer: fine) {
  .nav a:hover { color: var(--text); background: rgba(23, 24, 31, 0.05); }
}

.nav a:active { transform: scale(0.97); }

/* ── Hero ──
   The identity's cover slide: a full-bleed aura field, dithered by the grain,
   with the display type set white and left-aligned over it. Centered type on
   beige was the generic tell — the brand is a printed poster, so the page
   opens with one. */
.hero {
  padding: 12px 12px 0;
}

.aura-panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--r-panel);
  background: var(--panel);
  color: var(--text);
  min-height: min(88vh, 820px);
  padding: 28px clamp(24px, 4vw, 48px) 56px;
  display: flex;
  flex-direction: column;
  gap: 48px;
  text-align: center;
}

/* The type block sits optically centred in the panel, with the nav pinned above
   it — not spread edge to edge. */
.aura-panel .hero-body { margin-block: auto; }

/* The bloom. Colour is pressed out to the edges and dissolved inward by the
   dither, so the middle stays paper and the type is read on paper — the way
   the identity actually composes. A saturated field with white type on it is
   the poster; the page is the other half of the system. */
.bloom {
  position: absolute;
  inset: -6%;
  z-index: -1;
  pointer-events: none;
  filter: url('#dither');
  /* Peaks stay BELOW full opacity on purpose. At alpha 1 the threshold is met
     almost everywhere and the core goes flat solid — a smooth blob with a
     dotted rim, which is the thing that kept reading as generic. Held around
     0.8, the dither bites all the way into the core and the whole field stays
     screened. Density is the picture.
     The top edge is also left deliberately clear: the nav sits there, and type
     on a dithered field is unreadable. */
  background-image:
    radial-gradient(44% 56% at 0% 48%, rgba(67, 83, 255, 0.84) 0%, rgba(67, 83, 255, 0.6) 40%, transparent 74%),
    radial-gradient(38% 48% at 10% 96%, rgba(43, 179, 222, 0.8) 0%, transparent 72%),
    radial-gradient(42% 54% at 100% 58%, rgba(67, 83, 255, 0.82) 0%, rgba(67, 83, 255, 0.55) 42%, transparent 76%),
    radial-gradient(34% 44% at 92% 100%, rgba(138, 85, 240, 0.78) 0%, transparent 72%),
    radial-gradient(30% 38% at 44% 108%, rgba(138, 85, 240, 0.6) 0%, transparent 70%),
    radial-gradient(26% 34% at 70% 100%, rgba(43, 179, 222, 0.55) 0%, transparent 70%),
    radial-gradient(24% 30% at 2% 4%, rgba(43, 179, 222, 0.45) 0%, transparent 72%);
}

/* The nav, inside the panel. */
.panel-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.brand strong {
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
  letter-spacing: -0.01em;
}

.links { display: flex; gap: 4px; }

.links a {
  color: var(--dim);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  padding: 6px 10px;
  border-radius: var(--r-pill);
  transition: color 0.16s ease, background-color 0.16s ease,
    transform 0.16s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .links a:hover { color: var(--text); background: rgba(23, 24, 31, 0.05); }
}

.links a:active { transform: scale(0.97); }

/* The ornament — the mark bloomed and screened. It floats behind the type at
   the section's edge, the way the identity scatters its own form across a
   plate rather than boxing it. */
.has-ornament { position: relative; }

.ornament {
  position: absolute;
  top: 8px;
  right: -56px;
  width: 340px;
  height: 340px;
  z-index: -1;
  pointer-events: none;
}

@media (max-width: 900px) {
  .ornament { right: -80px; width: 200px; height: 200px; opacity: 0.7; }
}

/* Corner metadata — the deck's register: small, mono, letterspaced, quiet. */
.meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim-2);
}

.meta-mark {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
  color: var(--text);
}

/* The mark. */
.mark {
  width: 24px;
  height: 24px;
  flex: none;
}

.hero-body { max-width: 900px; margin: 0 auto; }

@media (max-width: 720px) {
  .meta-mid { display: none; }
  .aura-panel { min-height: 0; padding-block: 24px 28px; gap: 40px; }
  .links a { padding: 6px 6px; }
}

/* Display sizes take the high-contrast optical cut and a lighter weight —
   the same trade the product makes at opsz 72. */
h1 {
  font-family: var(--font-serif);
  font-size: clamp(40px, 5.4vw, 68px);
  line-height: 1.05;
  letter-spacing: -0.015em;
  font-weight: 480;
  font-variation-settings: 'opsz' 72;
  margin: 0 0 24px;
  text-wrap: balance;
}

/* The claim in ink, the verdict in the brand's voice — exactly the two-line
   move the identity uses for its own pull-lines. */
.aura-panel h1 {
  font-size: clamp(36px, 5.6vw, 72px);
  margin: 0 0 20px;
}

h1 .grad {
  font-style: italic;
  color: var(--brand);
}

.lede {
  font-size: clamp(18px, 2vw, 21px);
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--dim);
  max-width: 620px;
  margin: 0 0 40px;
  font-weight: 400;
  text-wrap: pretty;
}

.aura-panel .lede { margin-inline: auto; }

.cta {
  display: flex;
  gap: 24px;
  align-items: center;
  flex-wrap: wrap;
}

.aura-panel .cta { justify-content: center; }

.btn {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  font-size: 16px;
  /* Colour on `ease`, the press on the strong ease-out — two different kinds of
     motion, so two different curves. */
  transition: background-color 0.16s ease, color 0.16s ease,
    transform 0.16s var(--ease-out);
}

.btn.primary {
  background: var(--brand);
  color: #f8faff;
  padding: 13px 26px;
  border-radius: var(--r-pill);
  font-weight: 500;
}

/* Hover deepens toward ink; the press is where the motion lives. */
@media (hover: hover) and (pointer: fine) {
  .btn.primary:hover { background: var(--brand-deep); }
}

.btn.primary:active { transform: scale(0.97); }

.dither { filter: url('#dither'); }

.btn.quiet {
  color: var(--text);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: var(--dim-2);
  text-underline-offset: 3px;
  transition: text-decoration-color 0.16s ease,
    transform 0.16s var(--ease-out);
}
/* nbsp — a plain space in `content` collapses against the chevron. */
.btn.quiet::after { content: '\00a0›'; }

@media (hover: hover) and (pointer: fine) {
  .btn.quiet:hover { text-decoration-color: currentColor; }
}

/* The secondary CTA is still a CTA: it presses like one. */
.btn.quiet:active { transform: scale(0.97); }

/* ── Sections ── */
section {
  padding-block: 88px;
  scroll-margin-top: 64px;
}

/* Sections are separated by a pixel run, not a hairline — same grain as the
   hero, at a whisper. */
section + section {
  border-top: 0;
  background-image:
    conic-gradient(from 90deg at 2px 2px, var(--px) 25%, transparent 0);
  background-size: 4px 4px;
  background-repeat: repeat-x;
  background-position: top;
}

h2 {
  font-family: var(--font-serif);
  font-size: clamp(30px, 3.6vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0;
  font-weight: 480;
  font-variation-settings: 'opsz' 48;
  text-wrap: balance;
}

/* The deck labels every plate before it speaks. Same register here: mono,
   letterspaced, quiet — it frames the heading instead of competing with it. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim-2);
  margin: 0 0 14px;
}

/* Not a rule — four pixels. The mark's own grain, at label scale, in the
   brand's one voice. */
.eyebrow::before {
  content: '';
  display: inline-block;
  width: 21px;
  height: 3px;
  margin-right: 10px;
  vertical-align: middle;
  background-image: linear-gradient(90deg, var(--brand) 3px, transparent 0);
  background-size: 6px 3px;
}

.lead-para {
  color: var(--dim);
  margin: 20px 0 0;
  max-width: 600px;
  font-size: 19px;
  line-height: 1.55;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}


code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  color: var(--text);
  background: var(--bg-2);
  padding: 1px 6px;
  border-radius: 5px;
}

.mt-lg { margin-top: 32px; }

a.inline {
  color: var(--text);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: var(--dim-2);
  text-underline-offset: 3px;
  transition: text-decoration-color 0.16s ease;
}

@media (hover: hover) and (pointer: fine) {
  a.inline:hover { text-decoration-color: currentColor; }
}

/* ── Footer ──
   The aura returns as landscape: a dithered range rising off the bottom of the
   page, masked so it dissolves into the paper rather than ending on an edge.
   The hero states the brand; the footer lets it set. */
footer {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--bg-2);
  border-top: 1px solid var(--line);
  /* The extra bottom padding buys clearance between the last mono line of the
     colophon and the point where the crests start to bite. */
  padding-block: 64px 440px;
  color: var(--dim);
  font-size: 13px;
}

/* The ridges: overlapping ellipses pushed below the fold so only their crowns
   show, each one a different note of the spectrum. */
footer::before {
  content: '';
  position: absolute;
  z-index: -2;
  left: -6%;
  right: -6%;
  bottom: 0;
  height: 560px;
  /* Ridges, front to back. Each is an ellipse pushed below the fold and painted
     nearly to its own edge, so it keeps a crest line — soft shoulders alone
     wash into each other and you get a sunset, not a range. Geometry sets where
     each crest breaks; the near ones burn cyan and the far ones cool to
     ultramarine, which is the depth cue. */
  /* Every crest carries a long alpha feather of its own. The filter reads that
     alpha, so the ridges don't fade out — they disintegrate into pixels. The
     fade must live in the SOURCE, not in a mask: a mask applies after the
     filter and would just make the dots translucent. */
  /* Held well back. At near-full alpha the range read as a paint spill fighting
     the colophon above it — the crests have to stay a HORIZON, something the
     page settles into, not the loudest thing on it. Peaks now top out around
     0.5, so the dither also bites deeper (it thresholds on alpha) and the whole
     band stays screened rather than going flat. */
  background-image:
    radial-gradient(150% 52% at 12% 122%, rgba(67, 83, 255, 0.52) 0 62%, rgba(67, 83, 255, 0.3) 84%, transparent 100%),
    radial-gradient(130% 58% at 40% 112%, rgba(138, 85, 240, 0.46) 0 64%, rgba(138, 85, 240, 0.24) 86%, transparent 100%),
    radial-gradient(140% 64% at 76% 103%, rgba(67, 83, 255, 0.44) 0 66%, rgba(67, 83, 255, 0.22) 88%, transparent 100%),
    radial-gradient(70% 46% at 62% 80%, rgba(43, 179, 222, 0.28) 0%, transparent 74%);
  filter: url('#dither');
}


/* ── Prose (privacy page) ── */
.prose { padding-block: 72px 88px; }
.prose h1 { font-size: clamp(34px, 4.2vw, 52px); text-align: left; margin: 0 0 10px; max-width: none; }
.prose h2 { font-size: 28px; margin: 56px 0 14px; }
.prose h3 {
  font-family: var(--font-serif);
  font-size: 20px;
  margin: 32px 0 10px;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
}
.prose p, .prose li { color: var(--dim); }
.prose li { margin-bottom: 8px; }
.prose strong { color: var(--text); font-weight: 600; }
.prose .updated { color: var(--dim-2); font-size: 15px; margin-bottom: 44px; }

.prose table {
  width: 100%;
  border-collapse: collapse;
  margin: 24px 0;
  font-size: 15px;
  display: block;
  overflow-x: auto;
}

.prose th, .prose td {
  text-align: left;
  padding: 13px 16px 13px 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

.prose th { color: var(--text); font-weight: 600; white-space: nowrap; }
.prose td { color: var(--dim); }

/* The pull-quote: the page's thesis, set as print would set it — serif italic
   behind an ultramarine rule, not a tinted card. */
.callout {
  margin: 40px 0;
  padding-left: 24px;
  border-left: 2px solid var(--brand);
}

.callout p {
  margin: 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 480;
  font-variation-settings: 'opsz' 48;
  color: var(--text);
  font-size: clamp(21px, 2.4vw, 26px);
  line-height: 1.4;
  letter-spacing: -0.005em;
  max-width: 560px;
  text-wrap: pretty;
}

/* A display line is already emphatic: a 600-weight <strong> inside it lands as
   a patch, not a stress. Lift it by one step, not five. */
.callout strong { font-weight: 560; }

/* ── The tour ──
   One block per surface: where you are, what it does, and the clip. Ruled off
   from each other rather than boxed — the page spends a whole section mocking
   the card grid, so it can't then be one. */
.tour { margin: 44px 0 0; }

.tour > article {
  padding-block: 44px;
  border-top: 1px solid var(--line);
}

.tour > article:last-child { padding-bottom: 8px; }

/* The locator: which screen of the app you're looking at. Same mono register as
   the eyebrow, one rung quieter, with a pixel for a bullet. */
.tour-where {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim-2);
  margin: 0 0 10px;
}

.tour-where::before {
  content: '';
  display: inline-block;
  width: 3px;
  height: 3px;
  margin-right: 8px;
  vertical-align: middle;
  background: var(--brand);
}

.tour h3 {
  font-family: var(--font-serif);
  margin: 0 0 10px;
  font-size: clamp(23px, 2.6vw, 28px);
  line-height: 1.2;
  letter-spacing: -0.005em;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
  text-wrap: balance;
}

.tour p:not(.tour-where) {
  margin: 0;
  color: var(--dim);
  max-width: 620px;
  line-height: 1.55;
}

.tour strong { color: var(--text); font-weight: 600; }
.tour em { color: var(--text); font-style: italic; }

/* ── Pixels ──
   The dither is the identity, so it shouldn't only happen at the hero and the
   footer. These are the small carriers: a pixel rule between sections, a pixel
   bullet on the eyebrow, and the empty shot frames below, which are literally
   drawn out of the same grid. Same grain, quieter voice.

   The grid is a hard 4px checker built from a repeating conic gradient — no
   image, no filter, so it costs nothing and stays crisp at any zoom. */
.px-rule {
  height: 6px;
  border: 0;
  margin: 0;
  background-image:
    conic-gradient(from 90deg at 3px 3px, var(--px) 25%, transparent 0);
  background-size: 6px 6px;
  /* Fades to nothing at both ends, so the rule dissolves instead of stopping. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
}

/* ── Screenshot frames ──
   Each one holds a short silent clip — a synthetic recording of the surface it
   describes, rendered with Remotion (see motion/). They autoplay, loop, and
   carry no audio, so they behave like animated screenshots rather than video:
   no controls, nothing to press, nothing that starts talking at you.

   The figure is a bare container; .shot-frame is the plate that carries the
   radius, the hairline and the shadow. They're separate so the caption can sit
   BELOW the plate instead of being clipped by its overflow. */
.shot {
  position: relative;
  margin: 28px 0 0;
}

.shot-frame {
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  background: var(--panel);
  box-shadow:
    0 0 0 1px var(--line),
    0 22px 50px -28px rgba(23, 24, 31, 0.28);
}

.shot img,
.shot video {
  display: block;
  width: 100%;
  height: auto;
}

/* The empty state: the pixel field, a dashed pixel edge, and the caption of
   what belongs here. */
.shot.empty {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  padding: 24px;
  text-align: center;
  background-color: var(--panel);
  background-image:
    conic-gradient(from 90deg at 4px 4px, var(--px-faint) 25%, transparent 0);
  background-size: 8px 8px;
}

.shot.empty::before {
  content: '';
  position: absolute;
  inset: 10px;
  border-radius: 8px;
  border: 1px dashed var(--dim-2);
  opacity: 0.5;
  pointer-events: none;
}

.shot-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim-2);
  margin: 0 0 6px;
}

.shot-note {
  font-family: var(--font-serif);
  font-size: 19px;
  font-weight: 480;
  font-variation-settings: 'opsz' 28;
  color: var(--text);
  margin: 0;
  max-width: 32ch;
  text-wrap: balance;
}

/* A caption under a filled frame. */
.shot-cap {
  margin: 12px 0 0;
  font-size: 14px;
  color: var(--dim-2);
}

/* ── The demo stage ──
   The one thing on the page allowed to break the 880px measure. It runs the
   whole loop in a single take, and it earns the width: at the body's measure
   the browser chrome, the Figma layers panel and the inspector dock all shrink
   to illegible. Sitting between the hero and the argument, it also does the
   work a hero video usually does without stealing the headline's space. */
.demo {
  padding-block: 64px 96px;
}

.demo-wrap {
  max-width: 1180px;
  margin: 0 auto;
  padding-inline: 24px;
}

.demo-shot {
  margin-top: 18px;
}

.demo-shot .shot-frame {
  aspect-ratio: 16 / 9;
  border-radius: 18px;
  /* A deeper lift than the inline shots — this one is the stage, not a figure
     in the margin. */
  box-shadow:
    0 0 0 1px var(--line),
    0 40px 90px -40px rgba(23, 24, 31, 0.42);
}

.demo-shot .shot-cap {
  max-width: 62ch;
}

/* Aspect ratios that match the surface being shown — a side panel is tall and
   narrow, a Figma window is wide, the in-page overlay is a browser. Declared on
   the plate as well as the media so the page doesn't jump while a clip loads. */
.shot.panel { max-width: 460px; }
.shot.panel .shot-frame { aspect-ratio: 4 / 5; }
.shot.wide .shot-frame { aspect-ratio: 16 / 9; }
.shot.empty.panel { aspect-ratio: 4 / 5; }
.shot.empty.wide { aspect-ratio: 16 / 9; }

/* Each <video> carries a poster taken from a late frame, so before it plays —
   and for anyone who has asked for reduced motion, where it never plays at all
   — the frame still shows the finished result rather than an empty setup shot.
   Playback is driven by an IntersectionObserver in index.html: a clip is only
   fetched and only runs while it's actually on screen. Twelve autoplaying
   videos would otherwise cost several megabytes nobody scrolled to. */
.shot-frame video {
  background: var(--panel);
}

/* ── Focus ── */
a:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}
.btn.primary:focus-visible { border-radius: var(--r-pill); }

/* ── Entrance ── */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  animation: rise 0.7s var(--ease-out) forwards;
}

.reveal.d1 { animation-delay: 0.08s; }
.reveal.d2 { animation-delay: 0.16s; }

@keyframes rise {
  to { opacity: 1; transform: none; }
}

@keyframes fade-in {
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Reduced is fewer and gentler, not zero. The reveal keeps its fade — an
     opacity change carries no vestibular motion and it's what tells you the
     element arrived — and loses only the travel. Press feedback also stays: a
     3% scale under the finger is feedback, not movement across the screen, and
     killing it makes every control on the page feel dead. */
  .reveal {
    transform: none;
    animation: fade-in 0.4s ease forwards;
  }
}

/* ═══ Plate 01 — the problem ═══ */

.ornament-why {
  top: -28px;
  right: -64px;
  width: 300px;
  height: 300px;
}

/* The standfirst: the opening beat in the display face, ink not grey, so the
   plate speaks before it explains. */
.standfirst {
  font-family: var(--font-serif);
  font-size: clamp(21px, 2.2vw, 25px);
  line-height: 1.4;
  letter-spacing: -0.005em;
  font-weight: 480;
  font-variation-settings: 'opsz' 28;
  color: var(--text);
  max-width: 560px;
  margin: 24px 0 0;
  text-wrap: pretty;
}

/* One drop cap, in the one voice the page has. Display size, so the lighter
   weight and the high-contrast cut — same trade as h1. */
.standfirst::first-letter {
  float: left;
  font-family: var(--font-serif);
  font-size: 3.1em;
  line-height: 0.84;
  font-weight: 480;
  font-variation-settings: 'opsz' 72;
  color: var(--brand);
  padding: 2px 10px 0 0;
}

/* ── Fig. 1: the specimen strip ──
   The joke, drawn. Four flat cold greys — the palette every generated app
   actually ships — against one colour that had to be decided. Swatches, not
   cards: no border, no radius, no elevation. */
.specimen { margin: 40px 0 0; }

.tints {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  max-width: 560px;
}

.sw { display: block; }

.tint {
  display: block;
  height: clamp(56px, 9vw, 92px);
}

.t1 { background: #f9fafb; }
.t2 { background: #e5e7eb; }
.t3 { background: #9ca3af; }
.t4 { background: #111827; }

/* The fifth swatch is a printed tint ladder — 100/70/50/30 — pushed through the
   halftone screen. The filter thresholds source alpha against the dot lattice,
   so each step's dot radius IS its tint: the 100 stays solid, the 30 breaks into
   sparse dots. A colour that was chosen, printed the way this brand prints. */
.tone {
  background-image: linear-gradient(
    to bottom,
    rgba(67, 83, 255, 1) 0 25%,
    rgba(67, 83, 255, 0.74) 25% 50%,
    rgba(67, 83, 255, 0.5) 50% 75%,
    rgba(67, 83, 255, 0.28) 75% 100%
  );
  filter: url('#halftone');
}

.hex {
  display: block;
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim-2);
}

.hex.on { color: var(--brand); }

.specimen figcaption {
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--dim-2);
  max-width: 560px;
}

@media (max-width: 900px) {
  .ornament-why { right: -72px; width: 200px; height: 200px; opacity: 0.7; }
}

@media (max-width: 480px) {
  .tints { gap: 8px; }
  .hex { font-size: 9px; letter-spacing: 0.04em; }
}

/* ═══ Plate 02 — the loop ═══
   The copy claims a LOOP, so the rail draws one: a hairline circle per step with
   a halftone arc filling one more quadrant each time, threaded top to bottom by
   a single strand. The dots ARE the progression — #halftone thresholds against
   source alpha, so raising stroke-opacity across the four steps makes each cell's
   dot grow. Step 01 is a scattering; step 04 is nearly solid. Colour walks
   cyan → violet → ultramarine and returns to cyan on 04, whose ring
   is closed: you are where you started. */
.steps {
  list-style: none;
  margin: 32px 0 0;
  padding: 0;
}

.steps li {
  position: relative;
  display: grid;
  grid-template-columns: 76px 1fr;
  gap: 0 24px;
  padding-block: 32px;
  border-top: 1px solid var(--line);
}

/* The strand: one hairline down the rail, passing BEHIND each dial, so the four
   rings read as beads on a thread rather than four bullets. */
.steps li::before {
  content: '';
  position: absolute;
  left: 38px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line);
}

/* On the last step the thread stops dead inside the closed ring. */
.steps li:last-child::before { bottom: auto; height: 70px; }

.steps .dial {
  position: relative;
  grid-row: span 2;
  width: 76px;
  height: 76px;
  /* Paper disc: cuts the strand so it enters and exits the ring instead of
     striping it. Same colour as the page — invisible. */
  background: var(--bg);
  border-radius: 50%;
}

.steps .dial svg {
  display: block;
  width: 100%;
  height: 100%;
  /* Start at twelve o'clock so quadrants fill clockwise. */
  transform: rotate(-90deg);
}

.steps .track {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.4;
}

/* pathLength=4 normalises the circumference to four units, so the dasharray is
   written in literal quadrants — no circumference arithmetic. */
.steps .arc {
  fill: none;
  stroke: var(--step);
  stroke-opacity: var(--ink);
  stroke-width: 17;
  stroke-dasharray: var(--fill);
}

.steps b {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 19px;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: var(--step);
}

/* One quadrant per move; alpha climbs so the screen thickens as you go. */
.steps li:nth-child(1) { --step: #2bb3de; --fill: 1 3; --ink: 0.62; }
.steps li:nth-child(2) { --step: #8a55f0; --fill: 2 2; --ink: 0.74; }
.steps li:nth-child(3) { --step: #4353ff; --fill: 3 1; --ink: 0.86; }
.steps li:nth-child(4) { --step: #2bb3de; --fill: 4 0; --ink: 1; }

.steps h3 {
  font-family: var(--font-serif);
  grid-column: 2;
  margin: 0 0 8px;
  padding-top: 2px;
  font-size: 23px;
  letter-spacing: -0.005em;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
  line-height: 1.25;
}

.steps p {
  grid-column: 2;
  margin: 0;
  color: var(--dim);
  max-width: 620px;
  line-height: 1.55;
}

.steps em { color: var(--text); font-style: italic; }

/* The shot belongs to the step's text column — the dial owns the rail. */
.steps .shot { grid-column: 2; }

/* The coda closes the list and says out loud what the returning cyan does. */
.loop-coda {
  margin: 0;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  max-width: 620px;
  color: var(--dim-2);
  font-size: 15px;
  line-height: 1.6;
}

.loop-coda span {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--brand);
  margin-right: 10px;
}

@media (max-width: 600px) {
  /* One column: the strand has no rail to run down, so it goes. */
  .steps li { grid-template-columns: 1fr; gap: 12px; }
  .steps li::before { display: none; }
  .steps .dial { grid-row: auto; width: 56px; height: 56px; background: none; }
  .steps b { font-size: 15px; }
  .steps h3, .steps p, .steps .shot { grid-column: 1; }
  .steps h3 { padding-top: 0; }
}

/* ═══ Plate 03 — the spec sheet ═══
   Technical documentation, not a card grid: a mono key on the rail, a serif
   answer beside it. Each row takes one note of the spectrum, which tints its
   halftone figure AND a short crest on the hairline above it — so the rule
   stops being uniform grey ruling and starts keeping time. The glyph is an
   argument, not an icon: one squircle = one device, two = a conversation, the
   bloom = layers, a hollow ring = the plugin you don't have. */
.specs {
  margin: 36px 0 0;
  font-size: 16px;
  line-height: 1.55;
}

.specs > div {
  position: relative;
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 0 32px;
  padding-block: 34px;
  border-top: 1px solid var(--line);
}

.specs > div:last-child { border-bottom: 1px solid var(--line); }

/* The crest: 40px of the rule repainted in the row's own colour. */
.specs > div::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  width: 40px;
  height: 1px;
  background: var(--spec);
}

.specs > div:nth-child(1) { --spec: #2bb3de; }
.specs > div:nth-child(2) { --spec: #4353ff; }
.specs > div:nth-child(3) { --spec: #8a55f0; }
.specs > div:nth-child(4) { --spec: #56cdea; }

.specs dt {
  display: flex;
  flex-direction: column;
  gap: 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim-2);
}

.spec-glyph {
  width: 58px;
  height: 58px;
  margin-top: 2px;
}

.specs dd { margin: 0; max-width: 620px; }

.specs h3 {
  margin: 0 0 10px;
  font-family: var(--font-serif);
  font-size: 25px;
  font-weight: 560;
  font-variation-settings: 'opsz' 36;
  letter-spacing: -0.015em;
}

.specs p {
  margin: 0;
  color: var(--dim);
  text-wrap: pretty;
}

@media (max-width: 600px) {
  .specs > div { grid-template-columns: 1fr; gap: 14px; }
  .specs dt { flex-direction: row; align-items: center; gap: 12px; }
  .spec-glyph { width: 42px; height: 42px; margin-top: 0; }
}

/* ═══ The colophon ═══
   The footer signs the page the way a printed plate does: the mark, the wordmark,
   and the specification as a mono table. It sits on clean paper ABOVE the range —
   the dither is a horizon you read against, never a bed you set type on. */
.colophon {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: start;
}

.colophon-brand { max-width: 320px; }

/* The mark at signing size — the page closes on the thing it opened with. */
.mark-lg { width: 40px; height: 40px; }

.wordmark {
  font-family: var(--font-serif);
  font-size: 28px;
  font-weight: 560;
  font-variation-settings: 'opsz' 28;
  letter-spacing: -0.015em;
  color: var(--text);
  margin: 14px 0 0;
  line-height: 1.1;
}

/* The mark's own meaning, said once, in the pull-quote's voice. */
.colophon-line {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 480;
  font-variation-settings: 'opsz' 28;
  font-size: 17px;
  line-height: 1.4;
  color: var(--dim);
  margin: 10px 0 0;
  max-width: 300px;
  text-wrap: pretty;
}

/* A mono key against a serif value, played quietly — the same instrument as the
   rest of the page, which is what makes it feel like one document. */
.colophon-meta { margin: 0; }

.colophon-meta > div {
  display: grid;
  grid-template-columns: 116px 1fr;
  gap: 0 20px;
  padding-block: 12px;
  border-top: 1px solid var(--line);
}

.colophon-meta > div:last-child { border-bottom: 1px solid var(--line); }

.colophon-meta dt {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim-2);
  line-height: 1.7;
}

.colophon-meta dd {
  margin: 0;
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  text-wrap: pretty;
}

.colophon-foot {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-top: 40px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim-2);
}

.colophon-foot a {
  color: var(--text);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.16s ease, transform 0.16s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .colophon-foot a:hover { color: var(--brand); }
}

.colophon-foot a:active { transform: scale(0.97); }

.colophon-rule {
  flex: 1;
  height: 1px;
  background: var(--line);
}

@media (max-width: 720px) {
  .colophon { grid-template-columns: 1fr; gap: 36px; }
  .colophon-foot > span:last-child { display: none; }
}
