/* =====================================================================
   thilo.ai public website — landing-page styles (index.html only).

   Loaded AFTER /website.css and consumes the same custom properties —
   the literal values of the design-system tokens (see the header of
   website.css for the rule). No colour in this file that is not one of
   those tokens; --accent-dot stays the single fixed accent.

   Motion: every transition/animation is wrapped in
   `prefers-reduced-motion: no-preference`, and the reveal-on-scroll
   classes degrade to "just visible" without JavaScript.
   ===================================================================== */

/* ------------------------------------------------------------- layout */

body.site {
  min-height: 100svh;
  overflow-x: hidden;
}

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

section { position: relative; }

.section {
  padding: 96px 0;
}

.section-head {
  max-width: 640px;
  margin-bottom: 48px;
}

.section-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 14px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.section-eyebrow::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-dot);
}

.section-title {
  margin: 0 0 14px;
  font-size: clamp(26px, 4vw, 38px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.15;
}

.section-lede {
  margin: 0;
  font-size: 16px;
  line-height: 1.7;
  color: var(--muted-foreground);
}

/* ------------------------------------------------------------- header */

/* A FLOATING GLASS PILL, 24px down from the top of the viewport
   (TCKHXDEHPC) — not a bar across the page. `.site-header` itself is only the
   fixed, transparent, full-width row that centres it, and it is
   `pointer-events: none` so the 24px of hero either side of the pill stays
   the hero: a click there must reach the band, not an invisible header.
   `.site-header-inner` is the pill, and takes the pointer events back.

   It is FIXED rather than sticky, so it is out of the flow entirely and the
   hero band starts at y=0 with the pill floating on it. Two things follow
   from that and are not optional: every anchored section carries
   `scroll-margin-top` (§ anchors below), because the browser would otherwise
   scroll a heading to y=0 and leave it under the pill; and nothing may assume
   the header reserves space at the top of the document.

   The header carries its OWN `data-tone`, which `initHeaderTone` (site.js)
   keeps equal to the tone of the section crossing the pill's bottom edge
   (24 + 56 = 80px). It has to: the pill is glass, so a dark pill held over a
   light section is a grey lozenge on it — and the reverse is worse. The
   attribute starts at "dark" in the HTML, which is the hero's tone, so the
   first paint is right before any script has run. */
.site-header {
  position: fixed;
  top: 24px;
  left: 0;
  right: 0;
  z-index: 50;
  display: flex;
  justify-content: center;
  background: transparent;
  pointer-events: none;
}

/* The pill. `width: max-content` so it is exactly as wide as what is in it —
   a pill stretched to a container is a bar with round ends. No
   `overflow: hidden` (which is what clips the glass edge on the reference
   design): the folded nav below 1240px is an absolutely positioned card that
   hangs OUT of the pill, and hiding the overflow would cut it off. Nothing
   inside reaches the corners, so the radius clips nothing visually anyway.

   A GRID of two columns — the brand, and `.site-header-body` (the nav and
   the tools) — rather than a flex row, because the second column is what
   the collapse (§ header collapse, below) animates from `1fr` to `0fr`. A
   `max-content` grid sizes an fr column to its content when the flex factor
   is 1 or more, and to that FRACTION of its content below 1, so the column
   shrinks smoothly instead of snapping; `--pill-pad` is the side the brand
   is on, and the collapsed pill takes it on both sides. */
.site-header-inner {
  --pill-pad: 20px;
  position: relative;
  pointer-events: auto;
  width: max-content;
  max-width: calc(100vw - 40px);
  height: 56px;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 0 12px 0 var(--pill-pad);
  border: 1px solid var(--glass-border);
  border-radius: 9999px;
  box-shadow: var(--glass-shadow);
}

/* The second column. `min-width: 0` so the grid may size it below its
   content — which is what lets the `0fr` track reach zero, and what lets the
   tools go on shrinking on a narrow phone the way the flex row let them —
   and `overflow: clip` so a column narrower than its content clips instead
   of spilling over the brand. `clip`, not `hidden`, with a margin: `hidden`
   would cut the focus rings off the buttons at the column's edge. The folded
   nav below 1240px is absolutely positioned against the PILL, an ancestor of
   this box, so the clip does not reach it and the card still hangs out. */
.site-header-body {
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
  overflow: clip;
  overflow-clip-margin: 6px;
}

/* THE GLASS IS A PSEUDO-ELEMENT, and it has to be. `backdrop-filter` on the
   pill itself would make the pill a BACKDROP ROOT: a descendant's own
   `backdrop-filter` then sees only what is painted inside that root — nothing
   — and the folded nav card, which lives inside the pill, comes out a flat
   translucent sheet with the hero's headline reading straight through its
   links. Caught at 390px (TCKHXDEHPC). Painting the ground and the blur on
   `::before` instead leaves the pill itself unfiltered, so the card is a
   sibling of the blur rather than a child of it and gets its own.

   `inset: -1px` so the glass reaches under the 1px border, `border-radius:
   inherit` so it is the same lozenge, `z-index: -1` to sit behind the row —
   the pill sets no z-index of its own, so this stays inside `.site-header`'s
   stacking context and nothing else on the page is affected. */
.site-header-inner::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(16px) saturate(200%);
  backdrop-filter: blur(16px) saturate(200%);
}

/* The tone changes on a scroll boundary, so the glass crossfades rather than
   snapping — .4s on the designer's enter curve, long enough to read as the
   ground changing under the pill. The links, the buttons and the select
   inside it inherit their colours from the same tokens and come along. */
@media (prefers-reduced-motion: no-preference) {
  .site-header-inner,
  .site-header-inner::before,
  .site-header .site-nav {
    transition:
      background-color .4s cubic-bezier(.16, 1, .3, 1),
      border-color .4s cubic-bezier(.16, 1, .3, 1),
      box-shadow .4s cubic-bezier(.16, 1, .3, 1);
  }

  /* The collapse (§ header collapse): the body column, the gap and the
     padding close on the same curve and the same .4s as the tone crossfade,
     so the pill reads as one object changing, not two. */
  .site-header-inner {
    transition:
      background-color .4s cubic-bezier(.16, 1, .3, 1),
      border-color .4s cubic-bezier(.16, 1, .3, 1),
      box-shadow .4s cubic-bezier(.16, 1, .3, 1),
      grid-template-columns .4s cubic-bezier(.16, 1, .3, 1),
      gap .4s cubic-bezier(.16, 1, .3, 1),
      padding .4s cubic-bezier(.16, 1, .3, 1);
  }

  .site-header-body { transition: opacity .25s ease; }

  .site-header .play-btn,
  .site-header .lang-picker,
  .site-header .nav-toggle,
  .site-header .btn {
    transition: background-color .25s ease, border-color .25s ease, color .25s ease;
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}

.brand .mark { width: 28px; height: 28px; }

.brand .dot { color: var(--accent-dot); }

/* No `margin: 0 auto` any more: the pill is `max-content`, so there is no
   free space for auto margins to eat and the nav sits where the flex row puts
   it. The 2px gap is the floor — eight links, four tools and a session button
   have to make one row of this pill down to the fold (1240px, below). */
.site-nav {
  display: flex;
  align-items: center;
  gap: 2px;
}

/* Each link is its own small pill inside the big one. The colours are mixed
   off the tone's --foreground rather than taken from --muted-foreground /
   --muted: on glass, an opaque hover ground is a patch stuck to the pill, and
   these two alphas are what reads as the glass itself lighting up. */
.site-nav a {
  padding: 8px 12px;
  border-radius: 9999px;
  font-size: 14px;
  font-weight: 500;
  color: color-mix(in oklab, var(--foreground) 80%, transparent);
  text-decoration: none;
  white-space: nowrap;
}

.site-nav a:hover {
  color: var(--foreground);
  background: color-mix(in oklab, var(--foreground) 6%, transparent);
}

/* No `margin-left: auto` either, for the same reason. */
.header-tools {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* ---------------------------------------------------- header collapse */

/* Scrolled DOWN, the pill closes around the mark (TCKKP3T3DY, after
   wingops.ai): `initHeaderCollapse` (site.js) puts `is-collapsed` on the
   header once the reader is past COLLAPSE_AT and moving down, and takes it
   off on the first scroll up or at the top of the page. In that state the
   body column goes to `0fr`, the gap to 0 and the padding to the brand's own
   on both sides, so what is left is a small glass lozenge with the logo in
   it — the default, expanded state is untouched.

   The class is the JS's verdict; two things override it without a scroll,
   so the nav is never out of reach: the pointer resting on the pill (only
   where hovering is real — on a touch screen `:hover` sticks to the last
   tap, and a tap on the mark is a link home), and keyboard focus landing in
   it. `:has(:focus-visible)` rather than `:focus-within`, on purpose: the
   menu button keeps focus after a tap on Android, and `:focus-within` would
   then hold a collapsed pill open until the next tap anywhere.

   The body keeps `visibility` and stays in the tab order: a Tab into the
   first link is exactly what opens the pill for a keyboard reader. It just
   cannot be clicked while it is invisible. */
.site-header.is-collapsed .site-header-inner:not(:has(:focus-visible)) {
  grid-template-columns: auto 0fr;
  gap: 0;
  padding: 0 var(--pill-pad);
}

.site-header.is-collapsed .site-header-inner:not(:has(:focus-visible)) .site-header-body {
  opacity: 0;
  pointer-events: none;
}

@media (hover: hover) {
  .site-header.is-collapsed .site-header-inner:hover {
    grid-template-columns: auto 1fr;
    gap: 16px;
    padding: 0 12px 0 var(--pill-pad);
  }

  .site-header.is-collapsed .site-header-inner:hover .site-header-body {
    opacity: 1;
    pointer-events: auto;
  }
}

/* Everything in the pill is a pill. The controls keep their own metrics and
   their own tokens (website.css § buttons) — only the corner follows the
   shape they sit in. */
.site-header .btn,
.site-header .play-btn,
.site-header .lang-picker,
.site-header .nav-toggle {
  border-radius: 9999px;
}

/* Voice-over play button: same metrics as the other header tools. The
   label (Play/Stop) and tooltip are localized by voiceover.js. */
.play-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 0.6);
  background: var(--background);
  color: var(--foreground);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
}

.play-btn:hover { background: var(--muted); }

.play-btn svg { width: 12px; height: 12px; flex: none; }

/* One of the two glyphs shows, driven by the playing state. */
.play-btn .ic-stop { display: none; }
.play-btn.playing .ic-play { display: none; }
.play-btn.playing .ic-stop { display: block; }

.play-btn.playing {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-foreground);
}

/* Language select: same metrics as the app's small controls. */
.lang-picker {
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 0.6);
  background: var(--background);
  color: var(--foreground);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
}


/* The header is at capacity (TCKRXH3QHM): eight section links, four tools
   and a session button do not fit under ~1240px, and a flex row that runs
   out of room shrinks the buttons until "Sign in" wraps onto two lines.
   So below that width the section nav folds behind a menu button and hangs
   under the pill as a card; the tools stay in the row. The old rule simply
   hid the nav below 860px — a phone had no way to reach a section at all.

   1240 and not the 1100 it was: the pill is `max-content`, so the fold has
   to happen at the width the WIDEST state actually needs rather than at a
   round number the flex row could quietly squeeze under. Measured on the
   page, in every language, in both session states (TCKHXDEHPC): German
   signed in — "So funktioniert es" in the nav, "Mitglieder" plus the
   sign-out icon in the tools — wants 1174px of pill, and the pill keeps a
   20px gutter each side, so it needs 1214px of viewport; every other
   language needs less. The number here is 1240 rather than that measurement:
   the extra ~25px is headroom for a visitor whose browser never got Geist
   off Google Fonts and is laying the row out in the fallback stack, which is
   wider. Re-measure when a nav label or a tool changes, and raise it; do not
   shorten a label to keep it. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 0.6);
  background: var(--background);
  color: var(--foreground);
  cursor: pointer;
}

.nav-toggle:hover { background: var(--muted); }

.nav-toggle svg { width: 16px; height: 16px; }

/* One of the two glyphs shows, driven by the open state. */
.nav-toggle .ic-close { display: none; }
.site-header.nav-open .nav-toggle .ic-menu { display: none; }
.site-header.nav-open .nav-toggle .ic-close { display: block; }

/* Folded, the nav is a SECOND GLASS CARD hanging under the pill — same
   --glass-* tokens, a card radius instead of a pill one, 8px of hero showing
   between the two. Not a bar across the viewport: the pill is a floating
   object now, and a full-width sheet dropping out of it reads as the header
   un-floating. It is positioned against `.site-header-inner`, so it is
   exactly as wide as the pill it hangs from. */
@media (max-width: 1240px) {
  .nav-toggle { display: inline-flex; }

  .site-nav {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    min-width: 220px;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    margin: 0;
    padding: 8px;
    border: 1px solid var(--glass-border);
    border-radius: 20px;
    background: var(--glass-bg);
    box-shadow: var(--glass-shadow);
    -webkit-backdrop-filter: blur(16px) saturate(200%);
    backdrop-filter: blur(16px) saturate(200%);
  }

  .site-header.nav-open .site-nav { display: flex; }

  .site-nav a { padding: 10px 14px; font-size: 15px; border-radius: 12px; }
}


/* Narrow phones: the mark without the wordmark, icon-only Play, tighter
   gaps and padding — so the row (tools + session button + menu button)
   still fits a 360px screen in both session states. Below 400px the pill
   stops being `max-content` and takes the width it is given, minus a 16px
   gutter each side: there is nothing left to shrink, and a pill wider than
   the screen is worse than a pill as wide as the screen. */
@media (max-width: 480px) {
  .site-header-inner { --pill-pad: 16px; gap: 12px; padding: 0 8px 0 var(--pill-pad); }
  .site-header-body { gap: 12px; }
  .brand > span { display: none; }
  .header-tools { gap: 6px; }
  .header-tools .btn { padding: 0 12px; }
  .lang-picker { padding: 0 4px; }
  .play-btn { padding: 0 10px; }
  .play-btn .vo-label { display: none; }
}

/* The hover re-open (§ header collapse) restores the tighter metrics here. */
@media (hover: hover) and (max-width: 480px) {
  .site-header.is-collapsed .site-header-inner:hover { gap: 12px; padding: 0 8px 0 var(--pill-pad); }
}

@media (max-width: 400px) {
  /* Only the clamp moves, from a 20px gutter to 16: the pill stays
     `max-content`, so the collapse (§ header collapse) can still close it
     around the mark. Content wider than the screen shrinks inside the body
     column and is clipped as a last resort — a pill wider than the screen is
     worse than a pill as wide as the screen. */
  .site-header-inner { max-width: calc(100vw - 32px); }
}


/* ------------------------------------------------------------ anchors */

/* The header is OUT of the flow (§ header), so `#what` scrolled to the top of
   the viewport lands with its heading under the pill. Every anchored section
   keeps the pill's own bottom edge plus a breath clear: 24 (offset) + 56
   (pill) + 16. It is on `section[id]` rather than on `html { scroll-padding }`
   so that only the click targets pay for it — the reveal observers and the
   tone line measure the real geometry. */
section[id] { scroll-margin-top: 96px; }


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

/* The band the page opens on (TCKK73JEQ6): full-bleed, always dark, with the
   terrain animation in a canvas behind the headline.

   The band is the FIRST SCREEN, at every width (TCKUMCDUJW) — exactly one
   viewport tall, not a fixed 900px that leaves 64px of the next section
   showing under it on one monitor and buries the CTA on another. It reads as
   the screen only if it IS the screen; the header is `position: fixed` and out
   of the flow (§ header, TCKHXDEHPC), so `100svh` here is the whole of it with
   nothing above to subtract.

   `100svh` rather than `100vh`: on a phone `vh` is the tall viewport, the one
   the browser's own chrome is retracted out of, so the buttons would sit under
   the address bar until the visitor scrolls — and an animation whose CTA is
   under the fold is an animation nobody scrolls past.

   `min-height` is for the low landscape phone (844x390 less chrome): below
   560px the headline, the sub and the two buttons stop fitting, and a band
   that scrolls a little is better than one that clips its own buttons. */
.hero-band {
  position: relative;
  height: 100svh;
  min-height: 560px;
  overflow: hidden;
  isolation: isolate;
  background: var(--hero-ground);
}

/* The canvas is the band. It is sized in device pixels by the engine and
   stretched over the box by CSS, which is what lets the ratio be capped. */
.hero-band canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* Legibility, not decoration: a crest passing under the headline is bright
   enough to eat a letter. A radial wash of the band's OWN ground darkens the
   middle without tinting it, so the dots read as further away rather than as
   being behind a panel. */
.hero-pool {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(46% 44% at 50% 50%,
    color-mix(in srgb, var(--hero-ground) 74%, transparent) 0%, transparent 70%);
  pointer-events: none;
}

/* The bottom edge. Without it the band ends on whatever dots happen to be at
   that row. It fades to `--background` — NOT to `--hero-ground` — and that is
   the dark tone's ground, resolved by the band's own `data-tone="dark"`: the
   section under the band (`#what`) is dark too (TCK7FN72UU), and two near-
   blacks that differ (the artwork's navy #04060f, the palette's neutral
   oklch(0.145)) meeting on a hard line read as a rendering step, not as a
   design. Settling the dots into the next section's ground instead makes the
   band and the product shot one continuous opening. It is the only place the
   artwork touches a token, and the gradient is what makes that legitimate —
   the ground under the dots is still --hero-ground. Nothing is needed at the
   top — the header is there. */
.hero-fade-b {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  height: 160px;
  background: linear-gradient(to top, var(--background), transparent);
  pointer-events: none;
}

/* Both are ?devel switches, so both can be turned off from the panel — that
   is how the artwork is looked at on its own. */
.hero-band--nopool .hero-pool { display: none; }
.hero-band--nocopy .hero-copy { visibility: hidden; }

/* The copy over the band. `.on-dark-ground` (website.css) is what makes the
   buttons the dark tone's pair — a white primary and an outlined secondary —
   whatever tone the section carries, without a second set of button rules:
   the band is always dark, so everything on it is. `pointer-events: none` on the block and
   `auto` back on the links keeps the band itself clickable everywhere except
   the two buttons. */
.hero-copy {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  color: var(--foreground);
  pointer-events: none;
}

.hero-copy a { pointer-events: auto; }

/* The copy is centred over the WHOLE band, and the pill floats over its top —
   so on a short band (the 560px floor, a landscape phone) a centred headline
   walks straight under it. 96px is the pill's own bottom edge plus a breath,
   the same number `section[id]` scrolls to; it is symmetric so that the block
   stays optically centred on a tall band and only starts doing work once the
   copy is tall enough to reach the pill. `padding-block` rather than the
   shorthand: `.wrap`'s own 24px side gutter has to survive. */
.hero-copy .wrap { padding-block: 96px; }

/* Under the 560px floor the band is TALLER than the screen, and centring the
   copy in the band then spends the bottom third of it on ground the visitor
   has to scroll past to reach the buttons. So on a screen this short the copy
   stops being centred and hangs off the top instead, one pill's height down —
   which is the whole point of the floor: the headline, the sub and both
   buttons on the screen at once on a landscape phone. */
@media (max-height: 560px) {
  .hero-copy { justify-content: flex-start; }
  .hero-copy .wrap { padding-block: 92px 24px; }
}

.hero-title {
  margin: 0 auto;
  max-width: 780px;
  font-size: clamp(34px, 6vw, 60px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.08;
}

.hero-title .dot { color: var(--accent-dot); }

.hero-sub {
  margin: 20px auto 0;
  max-width: 640px;
  font-size: 17px;
  line-height: 1.7;
  color: var(--muted-foreground);
}

.hero-actions {
  margin-top: 32px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

.btn-lg { height: 44px; padding: 0 24px; font-size: 15px; }

/* The scroll hint (TCKUMCDUJW). Once the band is the whole first screen, the
   page gives a visitor no evidence that it continues — the old 900px band
   showed the top of `#what` and did that job by accident. One chevron on the
   bottom edge is the smallest thing that says so, and it is an `<a href="#what">`
   so that clicking it does what it looks like it should.

   It sits ON `.hero-fade-b`, which is why the colour is the muted foreground
   rather than the full one: a hint, not a third call to action. It inherits
   the dark tone's tokens from the section's own `data-tone="dark"`. */
.hero-scroll {
  position: absolute;
  left: 50%;
  bottom: 24px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-left: -20px;
  border-radius: 50%;
  color: var(--muted-foreground);
  transition: color 0.2s ease;
  animation: hero-scroll-bob 2.6s ease-in-out infinite;
}

.hero-scroll svg { width: 24px; height: 24px; }

.hero-scroll:hover,
.hero-scroll:focus-visible { color: var(--foreground); }

@keyframes hero-scroll-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(6px); }
}

/* Reduced motion keeps the chevron — it is information, not decoration — and
   drops the bob, which is the part that moves. */
@media (prefers-reduced-motion: reduce) {
  .hero-scroll { animation: none; }
}

/* On a short band every pixel between the buttons and the bottom edge is
   spoken for, and a chevron under them is one more thing to clear. The hint
   is the first thing to go: the fold is much less of a surprise on a screen
   this short anyway. */
@media (max-height: 640px) {
  .hero-scroll { display: none; }
}

/* Product shot in a browser-chrome frame. It used to sit inside the hero;
   since the band took that place it is the first thing in `#what` (see
   index.html), which is why the top margin is smaller than the section's own
   rhythm — it belongs to the band above it.

   `#what` is dark (TCK7FN72UU), so the frame sits on near-black: the border,
   the bar and the url pill are tokens and come out right by themselves, but
   the LIFT is `--shot-shadow` (website.css, declared per tone) — the black
   alpha that separates a card from a white page is nothing on this ground,
   and the dark tone's value is denser and adds the tone's own hairline. */
.hero-shot {
  margin: 0 auto 64px;
  max-width: 960px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.4);
  background: var(--card);
  box-shadow: var(--shot-shadow);
  overflow: hidden;
}

.hero-shot .frame-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}

.frame-bar i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--muted);
  border: 1px solid var(--border);
}

.frame-bar .frame-url {
  margin: 0 auto;
  padding: 3px 14px;
  border-radius: 999px;
  background: var(--muted);
  font-size: 11px;
  color: var(--muted-foreground);
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

.hero-shot img { display: block; width: 100%; height: auto; }

/* ------------------------------------------------------- showcase film */

/* `#showcase` and `#what` are one block of ground split into two sections
   (index.html says why: the voice-over tour needs the figure to BE a section).
   The padding is split with them, so the rhythm is what it was when the figure
   was the first child of `#what`: top from this one, bottom from that one, and
   the 64px under the figure unchanged. */
.section-showcase { padding-bottom: 0; }
.section-what { padding-top: 0; }

/* The film replaces the <img> inside the same frame, so everything above —
   border, radius, shadow, the url pill — is untouched and the page does not
   move when `site.js` upgrades the figure. */
.hero-shot { position: relative; }

.hero-shot video {
  display: block;
  width: 100%;
  height: auto;
  /* The still it replaces is 16:10 and so is the desktop film; declaring it
     means the frame keeps its height between the poster and the first decoded
     frame, on a connection where that is a visible gap. */
  aspect-ratio: 16 / 10;
  background: var(--muted);
}

/* Under `prefers-reduced-motion` and on a metered connection the figure keeps
   the still and offers a real Play button instead (the film is then loaded
   only if the visitor asks for it). */
.hero-shot .shot-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  color: var(--foreground);
}

.hero-shot .shot-play span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shot-shadow);
}

.hero-shot .shot-play svg { width: 24px; height: 24px; margin-left: 3px; }

/* The two controls, bottom right of the frame (fullscreen and sound). Always
   visible on a touch device — there is no hover to reveal them with — and on a
   pointer device on hover and on keyboard focus, because a button nobody can
   find is not a button. */
.shot-tools {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: flex;
  gap: 8px;
  opacity: 0;
  transition: opacity 160ms ease;
}

.hero-shot:hover .shot-tools,
.hero-shot:focus-within .shot-tools { opacity: 1; }

@media (hover: none) {
  .shot-tools { opacity: 1; }
}

.shot-tool {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--foreground);
  cursor: pointer;
}

.shot-tool:hover { background: var(--muted); }
.shot-tool svg { width: 18px; height: 18px; }
.shot-tool.is-on { border-color: var(--ring); }

/* In fullscreen the figure IS the viewport: centre the film in it rather than
   letting it stretch to a shape it was not shot in. `:fullscreen` on the
   figure, not on the video, is what keeps the frame and the two buttons. */
.hero-shot:fullscreen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: none;
  border: 0;
  border-radius: 0;
  background: #000;
}

.hero-shot:fullscreen video { max-height: 100vh; object-fit: contain; }
.hero-shot:fullscreen .frame-bar { display: none; }

/* The mobile film is a phone, so it gets the phone. The device frame is the
   one `#mobile` already draws (`.phone-*` below); this is only the wrapper
   that centres it and takes the browser chrome away, because a phone inside a
   browser window is a picture of nothing. */
.hero-shot.is-phone {
  max-width: 320px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
}

.hero-shot.is-phone .frame-bar { display: none; }
.hero-shot.is-phone .phone-body { --pw: 300px; }

/* The radius is on the VIDEO, not only on `.phone-screen`'s `overflow`:
   a browser does not reliably clip a video element by an ancestor's rounded
   corner, and a square-cornered picture inside a rounded phone is the one
   detail that gives the frame away. */
.hero-shot.is-phone video {
  aspect-ratio: 720 / 1560;
  border-radius: calc(var(--pw) * 0.126);
}

/* Inside the screen, not over the bezel — the two buttons belong to the film,
   and a control drawn on the titanium band reads as part of the phone. */
.hero-shot.is-phone .shot-tools { right: 22px; bottom: 30px; }

@media (max-width: 400px) {
  .hero-shot.is-phone { max-width: 280px; }
  .hero-shot.is-phone .phone-body { --pw: 260px; }
  .hero-shot.is-phone .shot-tools { right: 18px; bottom: 26px; }
}

/* ----------------------------------------------------------- features */

.feature-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

@media (max-width: 960px) { .feature-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .feature-grid { grid-template-columns: 1fr; } }

.feature-card {
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
}

@media (prefers-reduced-motion: no-preference) {
  .feature-card { transition: transform 200ms ease, box-shadow 200ms ease; }
  .feature-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--lift-shadow);   /* per tone — website.css */
  }
}

.feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-bottom: 16px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 0.8);
  background: var(--muted);
}

.feature-icon svg { width: 20px; height: 20px; }

.feature-card h3 {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.feature-card p {
  margin: 0;
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted-foreground);
}

/* -------------------------------------------------------------- steps */

.steps {
  counter-reset: step;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

@media (max-width: 960px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .steps { grid-template-columns: 1fr; } }

.step {
  counter-increment: step;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
}

.step::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  margin-bottom: 14px;
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  /* Accent TEXT, so --accent-ink and not --accent-dot: the mark's yellow
     on the light card is 1.4:1 and these numbers were unreadable
     (TCKRWWUJQY). See website.css. */
  color: var(--accent-ink);
}

.step h3 { margin: 0 0 8px; font-size: 15px; font-weight: 600; }

.step p { margin: 0; font-size: 14px; line-height: 1.65; color: var(--muted-foreground); }

/* ------------------------------------------------------------ gallery */

.gallery-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 20px;
}

.gallery-tab {
  height: 34px;
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--background);
  color: var(--muted-foreground);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}

.gallery-tab[aria-selected="true"] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-foreground);
}

@media (prefers-reduced-motion: no-preference) {
  .gallery-tab { transition: background 150ms ease, color 150ms ease; }
}

.gallery-stage {
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.4);
  background: var(--card);
  overflow: hidden;
  box-shadow: var(--shot-shadow);      /* per tone — website.css */
}

.gallery-stage img {
  display: block;
  width: 100%;
  height: auto;
}

@media (prefers-reduced-motion: no-preference) {
  .gallery-stage img.swap { animation: gallery-in 260ms ease; }
  @keyframes gallery-in {
    from { opacity: 0; transform: scale(0.992); }
    to   { opacity: 1; transform: scale(1); }
  }
}

.gallery-caption {
  margin: 14px 4px 0;
  font-size: 13px;
  color: var(--muted-foreground);
}

/* -------------------------------------------------------------- split */

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: center;
}

@media (max-width: 860px) { .split { grid-template-columns: 1fr; gap: 40px; } }

.split .section-head { margin-bottom: 24px; }

.check-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.check-list li {
  position: relative;
  padding: 6px 0 6px 30px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--muted-foreground);
}

.check-list li strong { color: var(--foreground); font-weight: 600; }

.check-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 11px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--muted);
  border: 1px solid var(--border);
}

.check-list li::after {
  content: "";
  position: absolute;
  left: 4.5px;
  top: 14.5px;
  width: 7px;
  height: 4.5px;
  border-left: 1.6px solid var(--foreground);
  border-bottom: 1.6px solid var(--foreground);
  transform: rotate(-45deg);
}

/* -------------------------------------------------------- thilo vault */

/* The vault facts are a full-width check-list under the three cards —
   two columns so five short items do not run the width of the page. */
.vault-facts {
  margin-top: 32px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2px 48px;
}

@media (max-width: 860px) { .vault-facts { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------- pricing */

/* Four plan cards over a limits table. The cards break 4 → 2 → 1 on the
   same 960/620 px steps as `.feature-grid`; the TABLE cannot break, so it
   scrolls sideways inside its wrapper instead of wrapping every row label
   onto four lines. */
.plan-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

@media (max-width: 960px) { .plan-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .plan-grid { grid-template-columns: 1fr; } }

.plan-card {
  display: flex;
  flex-direction: column;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
}

/* The plan we point at — same border weight, accent colour, no new tokens. */
.plan-card.is-featured { border-color: var(--accent-dot); }

.plan-card h3 {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.plan-note {
  margin: 0 0 20px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted-foreground);
}

/* Pushed to the bottom edge so the four buttons line up across cards
   whose notes are different lengths. */
.plan-cta {
  margin-top: auto;
  align-self: flex-start;
}

.plan-tokens {
  margin: 32px 0 16px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--foreground);
}

/* Only where the table is actually wider than the viewport — above that it
   fits and the line would be a lie. */
.plan-scroll-hint { display: none; }

@media (max-width: 780px) {
  .plan-scroll-hint {
    display: block;
    margin: 0 0 8px;
    font-size: 13px;
    color: var(--muted-foreground);
  }
}

.plan-table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
}

.plan-table {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  font-size: 14px;
}

.plan-table th,
.plan-table td {
  padding: 12px 16px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}

.plan-table tbody tr:last-child th,
.plan-table tbody tr:last-child td { border-bottom: 0; }

.plan-table thead th {
  font-size: 13px;
  font-weight: 600;
  color: var(--foreground);
  background: var(--muted);
  white-space: nowrap;
}

/* Fixed geometry: the row label takes a third, the four plans split the
   rest evenly. Left to `auto`, the columns size themselves from the longest
   cell and bunch against the label — which reads as four plans of unequal
   importance. */
.plan-table { table-layout: fixed; }

.plan-table tbody th {
  font-weight: 400;
  color: var(--foreground);
}

.plan-table th:first-child { width: 28%; }
.plan-table th:not(:first-child) { width: 18%; }

.plan-table tbody th strong { font-weight: 600; }

.plan-table td { color: var(--muted-foreground); }

.plan-hint { color: var(--muted-foreground); }

/* Same circle-and-tick as `.check-list`, drawn on a span so the cell can
   also carry the localized word for a screen reader. */
.plan-yes,
.plan-no {
  position: relative;
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--muted);
  border: 1px solid var(--border);
}

.plan-yes::after {
  content: "";
  position: absolute;
  left: 3.5px;
  top: 3.8px;
  width: 7px;
  height: 4.5px;
  border-left: 1.6px solid var(--foreground);
  border-bottom: 1.6px solid var(--foreground);
  transform: rotate(-45deg);
}

.plan-no::after {
  content: "";
  position: absolute;
  left: 3.5px;
  top: 6.5px;
  width: 7px;
  border-top: 1.6px solid var(--muted-foreground);
}

/* Read aloud, never drawn — the tick glyph alone says nothing. */
.plan-vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.plan-foot {
  margin: 20px 0 0;
  max-width: 760px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted-foreground);
}

.plan-foot a { color: var(--foreground); text-decoration: underline; text-underline-offset: 3px; }

.plan-foot-small { margin-top: 8px; font-size: 13px; }

/* ------------------------------------------------------------- phones */

/* The mobile section is a coverflow deck: one phone centred, the other two
   standing behind it, stepped by a click, a dot, an arrow key or a swipe
   (site.js `initPhoneDeck`). Every dimension below is a fraction of `--pw`,
   the width of the centred phone, so a breakpoint resizes the whole device
   — band, bezel, island, buttons, status bar — by changing one number.

   DEVICE COLOURS — the second deliberate exception in this file, the video
   player's chrome being the other. A phone is a physical object, not a
   themed surface: its titanium band is the same metal on a white page and
   on a black one, the Dynamic Island is black in both, and the strip the
   status bar sits on has to be the SCREENSHOT'S own top-edge colour
   (#ffffff, sampled from the /shots/mobile-*-light.webp this section shows)
   or the seam between the drawn status bar and the picture shows. Semantic
   tokens would flip all three with the section's tone, which is exactly
   wrong. They are kept as named variables so nothing else reaches for a
   literal. */

.phone-deck {
  --pw: 288px;                     /* width of the centred phone */
  --phone-spread: 66%;             /* neighbour offset, in phone widths */
  margin: 0;
}

.phone-stage {
  display: flex;
  justify-content: center;
  gap: 20px;
}

/* No JavaScript: the three phones simply stand side by side, small enough
   to fit the wrap. `.is-ready` (stamped by site.js) is what turns the row
   into the coverflow. */
.phone-stage:not(.is-ready) { --pw: 190px; }

.phone-stage.is-ready {
  display: grid;
  justify-items: center;
  gap: 0;
}

.phone {
  width: var(--pw);
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;                   /* the drawn status bar is in the page's font */
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Every phone occupies the same grid cell; only the transform tells them
   apart, so the stage is exactly as tall as one device. */
.phone-stage.is-ready > .phone {
  grid-area: 1 / 1;
  transform: translateX(calc(var(--pos, 0) * var(--phone-spread))) scale(var(--phone-scale, 1));
}

.phone.is-active { --phone-scale: 1; z-index: 3; }
.phone.is-side   { --phone-scale: 0.8; z-index: 2; opacity: 0.62; }
.phone.is-far    { --phone-scale: 0.7; z-index: 1; opacity: 0; pointer-events: none; }

.phone:focus { outline: none; }

.phone:focus-visible .phone-body {
  outline: 2px solid var(--ring);
  outline-offset: 6px;
}

/* --------------------------------------------------- the device itself */

.phone-body {
  --dev-band: linear-gradient(148deg, #b7bbc1 0%, #6a6e75 14%, #e8eaee 30%, #7e828a 46%, #4a4e55 66%, #a9aeb5 84%, #5c6067 100%);
  --dev-key: linear-gradient(180deg, #9aa0a7, #5b6067);

  position: relative;
  display: block;
  padding: calc(var(--pw) * 0.011);
  border-radius: calc(var(--pw) * 0.158);
  background: var(--dev-band);
  box-shadow:
    inset 0 0 0 1px rgb(255 255 255 / 22%),
    0 calc(var(--pw) * 0.09) calc(var(--pw) * 0.16) calc(var(--pw) * -0.06) rgb(0 0 0 / 45%);
}

/* Left: the Action button, short, above the volume pair. */
.phone-body::before {
  content: "";
  position: absolute;
  left: calc(var(--pw) * -0.010);
  top: calc(var(--pw) * 0.30);
  width: calc(var(--pw) * 0.011);
  height: calc(var(--pw) * 0.055);
  border-radius: calc(var(--pw) * 0.006) 0 0 calc(var(--pw) * 0.006);
  background: var(--dev-key);
}

/* Right: the side (power) button, the long one. */
.phone-body::after {
  content: "";
  position: absolute;
  right: calc(var(--pw) * -0.010);
  top: calc(var(--pw) * 0.44);
  width: calc(var(--pw) * 0.011);
  height: calc(var(--pw) * 0.155);
  border-radius: 0 calc(var(--pw) * 0.006) calc(var(--pw) * 0.006) 0;
  background: var(--dev-key);
}

/* Left: volume up, and volume down as its shadow one button lower. */
.phone-keys::before {
  content: "";
  position: absolute;
  left: calc(var(--pw) * -0.010);
  top: calc(var(--pw) * 0.41);
  width: calc(var(--pw) * 0.011);
  height: calc(var(--pw) * 0.095);
  border-radius: calc(var(--pw) * 0.006) 0 0 calc(var(--pw) * 0.006);
  background: var(--dev-key);
  box-shadow: 0 calc(var(--pw) * 0.115) 0 #6b7076;
}

/* The black glass edge between the band and the picture. */
.phone-bezel {
  display: block;
  padding: calc(var(--pw) * 0.021);
  border-radius: calc(var(--pw) * 0.147);
  background: #000;
}

.phone-screen {
  /* The shots' own top-edge colour. #mobile carries data-tone="light"
     (TCK7FN72UU) and therefore shows the `-light.webp` shots, so these are
     the light pair, fixed — a phone is a physical object, not a themed
     surface (doc 60 § The status bar is drawn). The dark pair, for when the
     section is flipped back, is #171717 / #f5f5f5: move them with the tone,
     and re-sample the files if the app's header changes. */
  --screen-bg: #ffffff;
  --screen-ink: #171717;

  position: relative;
  display: block;
  overflow: hidden;
  border-radius: calc(var(--pw) * 0.126);
  background: var(--screen-bg);
}

.phone-screen img {
  display: block;
  width: 100%;
  height: auto;
}

/* ------------------------------------------------------- status bar */

/* Drawn, not photographed: the screenshots start at the app's own header.
   The strip is the screenshot's top-edge colour, so the two meet without
   a seam, and the proportions are the device's (54pt of 393pt wide). */
.phone-status {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: calc(var(--pw) * 0.125);
  padding: 0 calc(var(--pw) * 0.075) calc(var(--pw) * 0.006);
  color: var(--screen-ink);
  font-size: calc(var(--pw) * 0.043);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.phone-island {
  position: absolute;
  left: 50%;
  top: calc(var(--pw) * 0.028);
  transform: translateX(-50%);
  width: calc(var(--pw) * 0.318);
  height: calc(var(--pw) * 0.092);
  border-radius: 999px;
  background: #000;
}

/* The camera, just visible in the pill. */
.phone-island::after {
  content: "";
  position: absolute;
  right: calc(var(--pw) * 0.022);
  top: 50%;
  width: calc(var(--pw) * 0.038);
  height: calc(var(--pw) * 0.038);
  transform: translateY(-50%);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #23252b, #0a0a0c 70%);
}

.phone-signal {
  display: flex;
  align-items: center;
  gap: calc(var(--pw) * 0.017);
}

.phone-signal svg { height: calc(var(--pw) * 0.038); width: auto; display: block; }

/* iOS draws the home indicator over the content, and so do we. */
.phone-home {
  position: absolute;
  left: 50%;
  bottom: calc(var(--pw) * 0.020);
  transform: translateX(-50%);
  width: calc(var(--pw) * 0.356);
  height: calc(var(--pw) * 0.0135);
  border-radius: 999px;
  background: var(--screen-ink);
  opacity: 0.55;
}

/* ------------------------------------------------- caption and dots */

.phone-caption {
  margin: 28px auto 0;
  max-width: 560px;
  text-align: center;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--muted-foreground);
}

.phone-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 16px;
}

.phone-dots:empty { display: none; }

.phone-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--border);
  cursor: pointer;
}

.phone-dot:hover { background: var(--muted-foreground); }

.phone-dot.is-active { width: 22px; background: var(--foreground); }

.phone-dot:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }

/* The four facts sit under the deck, two columns so they do not run the
   width of the page — the same shape as the vault facts, and aligned with
   the section head rather than with the (centred) deck. */
.phone-facts {
  margin-top: 44px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2px 48px;
}

/* Where the section talks about the app, it says how to get it
   (TCKEC5UWXX). The note next to the button is not decoration: the link
   goes into the members area, and a person clicking a "Get the app"
   button deserves to know that before the login appears. */
.phone-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin: 36px 0 0;
}

.phone-cta-note {
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted-foreground);
}

@media (prefers-reduced-motion: no-preference) {
  .phone-stage.is-ready > .phone {
    transition: transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 400ms ease;
  }
  .phone-dot { transition: background 200ms ease, width 200ms ease; }
}

@media (max-width: 1000px) { .phone-deck { --pw: 250px; } }

@media (max-width: 860px) {
  .phone-facts { grid-template-columns: 1fr; }
}

/* Below this the neighbours run off the page — deliberately: `body.site`
   clips the overflow, so the deck reads as a strip that continues. */
@media (max-width: 700px) {
  .phone-deck { --pw: 216px; --phone-spread: 72%; }
  .phone-stage:not(.is-ready) { --pw: 140px; }
  .phone-stage:not(.is-ready) { gap: 12px; }
}

@media (max-width: 460px) {
  .phone-deck { --pw: 190px; }
}

/* ------------------------------------------------------------- videos */

/* A scroll-snapped track, not a grid: `--vc-per` cards fit in the visible
   width, and it is deliberately fractional (3.5) so the cut card on the
   right is the affordance that says the list continues. Paging is by the
   dots under the track — see `carousel` in site.js. */
.video-carousel { --vc-gap: 16px; --vc-per: 3.5; }

.video-track {
  display: flex;
  gap: var(--vc-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* Room for the card's focus ring and shadow, clawed back on the sides so
     the first card still lines up with the section title. */
  padding: 4px 0 14px;
  scrollbar-width: none;
}

.video-track::-webkit-scrollbar { display: none; }

.video-track:focus-visible { outline: 2px solid var(--ring); outline-offset: 4px; border-radius: var(--radius); }

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

.video-card {
  flex: 0 0 calc((100% - (var(--vc-per) - 1) * var(--vc-gap)) / var(--vc-per));
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  overflow: hidden;
}

@media (max-width: 1100px) { .video-carousel { --vc-per: 2.5; } }
@media (max-width: 760px)  { .video-carousel { --vc-per: 1.25; } }

.video-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  background:
    radial-gradient(220px 120px at 50% 45%,
      color-mix(in oklab, var(--accent-dot) 10%, transparent), transparent 75%),
    var(--muted);
}

/* The live card's thumb is a <button>: it plays. */
button.video-thumb { cursor: pointer; font: inherit; color: inherit; }

button.video-thumb:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }

.video-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Dimmed so the play button and the badge stay readable on any frame. */
  filter: brightness(0.72);
  transition: filter 200ms ease, transform 400ms ease;
}

button.video-thumb:hover .video-poster { filter: brightness(0.85); transform: scale(1.02); }

.video-thumb .play {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--background);
  color: var(--foreground);
  transition: transform 200ms ease;
}

button.video-thumb:hover .play { transform: scale(1.08); }

.video-thumb .play svg { width: 18px; height: 18px; margin-left: 2px; }

.video-thumb .soon {
  position: absolute;
  top: 12px;
  right: 12px;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--background);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.video-thumb .soon.is-now {
  border-color: transparent;
  background: var(--accent-dot);
  color: #1a1a1a;                 /* --accent-dot is a fixed yellow, in both themes */
  font-weight: 600;
}

.video-card h3 { margin: 16px 20px 4px; font-size: 15px; font-weight: 600; }

.video-card p { margin: 0 20px 18px; font-size: 13.5px; line-height: 1.6; color: var(--muted-foreground); }

/* Dots. One per scrollable page, count computed in JS from the track's own
   scrollWidth so it stays right when --vc-per changes at a breakpoint. */
.video-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 8px;
}

.video-dots:empty { display: none; }

.video-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--border);
  cursor: pointer;
  transition: background 200ms ease, width 200ms ease;
}

.video-dot:hover { background: var(--muted-foreground); }

.video-dot.is-active { width: 22px; background: var(--foreground); }

.video-dot:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }

/* ------------------------------------------------------- video player */

/* Opened by a click on a real video card. The overlay element itself is what
   goes fullscreen (not the iframe), so the title bar and the close button
   come along and Escape leaves fullscreen without killing playback.

   DELIBERATELY NOT THEMED, and the only place on this site that is. The
   surface behind the chrome is the video: it is black in both themes, so a
   semantic token would flip this bar to near-white in light mode and put
   white text on it. The three values below are the whole exception, kept as
   named variables so nothing else here reaches for a literal. */
.video-player {
  --vp-chrome: #fff;
  --vp-surface: #000;
  --vp-btn: rgba(255, 255, 255, 0.08);
  --vp-btn-hover: rgba(255, 255, 255, 0.18);
  --vp-btn-border: rgba(255, 255, 255, 0.2);
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: color-mix(in oklab, var(--vp-surface) 82%, transparent);
}

.video-player[hidden] { display: none; }

.video-player-backdrop { position: absolute; inset: 0; }

.video-player-box {
  position: relative;
  width: min(1200px, 100%);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.video-player-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--vp-chrome);
}

.video-player-title {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.video-player-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--vp-btn-border);
  background: var(--vp-btn);
  color: var(--vp-chrome);
  cursor: pointer;
}

.video-player-btn:hover { background: var(--vp-btn-hover); }

.video-player-btn svg { width: 16px; height: 16px; }

.video-player-frame {
  aspect-ratio: 16 / 9;
  width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--vp-surface);
}

.video-player-frame iframe { display: block; width: 100%; height: 100%; border: 0; }

/* Fullscreen: the box fills the screen and the frame takes what the bar
   leaves, letterboxed by the iframe itself. */
.video-player:fullscreen { padding: 0; background: var(--vp-surface); }
.video-player:fullscreen .video-player-box { width: 100%; height: 100%; gap: 0; }
.video-player:fullscreen .video-player-bar { padding: 10px 16px; }
.video-player:fullscreen .video-player-frame { flex: 1; aspect-ratio: auto; border-radius: 0; min-height: 0; }

/* ------------------------------------------------------------ contact */

.contact-card {
  max-width: 560px;
  padding: 32px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.4);
  background: var(--card);
}

.contact-card textarea {
  display: block;
  width: 100%;
  min-height: 120px;
  padding: 10px 12px;
  border: 1px solid var(--input);
  border-radius: calc(var(--radius) * 0.8);
  background: var(--background);
  color: var(--foreground);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.6;
  resize: vertical;
}

.contact-card textarea:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.contact-card textarea[aria-invalid="true"] { border-color: var(--destructive); }

/* ------------------------------------------------------------ captcha */

/* The anti-robot check: one bordered row, a checkbox-looking button that
   solves a signed proof-of-work challenge in the page and then shows the
   tick. Also used on /request/. */
.captcha {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 4px 0 18px;
  padding: 12px 14px;
  border: 1px solid var(--input);
  border-radius: calc(var(--radius) * 0.8);
  background: var(--background);
}

.captcha-box {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 2px solid var(--muted-foreground);
  border-radius: 6px;
  background: var(--background);
  color: var(--background);
  cursor: pointer;
}

.captcha[data-state="working"] .captcha-box {
  border-color: var(--ring);
  cursor: progress;
}

.captcha[data-state="done"] .captcha-box {
  border-color: var(--accent-dot);
  background: var(--accent-dot);
  color: oklch(0.145 0 0);
  cursor: default;
}

.captcha[data-state="error"] .captcha-box { border-color: var(--destructive); }

.captcha-box svg { width: 14px; height: 14px; display: none; }

.captcha[data-state="done"] .captcha-box svg { display: block; }

@media (prefers-reduced-motion: no-preference) {
  .captcha[data-state="working"] .captcha-box {
    animation: captcha-pulse 1s ease-in-out infinite;
  }
  @keyframes captcha-pulse {
    50% { border-color: var(--foreground); }
  }
}

.captcha-label { font-size: 14px; color: var(--foreground); }

.captcha-note { margin-left: auto; font-size: 11px; color: var(--muted-foreground); white-space: nowrap; }

/* ---------------------------------------------------------- final CTA */

.cta-section {
  padding: 110px 0;
  text-align: center;
}

.cta-section .hero-actions { margin-top: 28px; }

.cta-card {
  position: relative;
  padding: 72px 32px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 2);
  background: var(--card);
  overflow: hidden;
}

.cta-card::before {
  content: "";
  position: absolute;
  inset: auto -20% -60%;
  height: 120%;
  background:
    radial-gradient(500px 260px at 50% 100%,
      color-mix(in oklab, var(--accent-dot) 12%, transparent), transparent 70%);
  pointer-events: none;
}

.cta-card > * { position: relative; }

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

.site-footer {
  border-top: 1px solid var(--border);
  padding: 48px 0 40px;
}

.footer-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  align-items: flex-start;
  justify-content: space-between;
}

.footer-brand { max-width: 300px; }

.footer-brand p { margin: 12px 0 0; font-size: 13px; line-height: 1.6; color: var(--muted-foreground); }

.footer-col h4 {
  margin: 0 0 12px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.footer-col ul { margin: 0; padding: 0; list-style: none; }

.footer-col li { margin: 0 0 8px; }

.footer-col a { font-size: 14px; color: var(--foreground); text-decoration: none; }

.footer-col a:hover { text-decoration: underline; text-underline-offset: 3px; }

.footer-bottom {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  color: var(--muted-foreground);
}

.footer-bottom a { color: inherit; }

/* ------------------------------------------------------------- reveal */

/* Scroll-in reveals. The base class alone changes nothing — only
   `html.js` (stamped by site.js) hides elements first, so without
   JavaScript everything is simply visible. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 600ms ease, transform 600ms ease;
  }
  html.js .reveal.is-visible {
    opacity: 1;
    transform: none;
  }
  html.js .reveal-stagger > * {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 500ms ease, transform 500ms ease;
  }
  html.js .reveal-stagger.is-visible > * {
    opacity: 1;
    transform: none;
  }
  html.js .reveal-stagger.is-visible > :nth-child(2) { transition-delay: 70ms; }
  html.js .reveal-stagger.is-visible > :nth-child(3) { transition-delay: 140ms; }
  html.js .reveal-stagger.is-visible > :nth-child(4) { transition-delay: 210ms; }
  html.js .reveal-stagger.is-visible > :nth-child(5) { transition-delay: 280ms; }
  html.js .reveal-stagger.is-visible > :nth-child(6) { transition-delay: 350ms; }
}

html { scroll-behavior: smooth; }

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