/* sous landing page - "frosted glass cathedral at midnight".
 *
 * Implements the AuthKit style reference (see site/README.md → Design system).
 * Rules that matter and are easy to break later:
 *
 *   - Borders are never solid strokes. Every edge is a 1px *inset* box-shadow at
 *     rgba(186,215,247,0.12). That is the system's border language.
 *   - Elevation is inset frost highlight + soft inner glow + a cool dark halo.
 *     No conventional drop shadows.
 *   - Radius families never mix: buttons 999px, cards/modals 16px, badges and
 *     inputs 6px, icon tiles 9999px.
 *   - There is no chromatic accent. The page is ice on near-black, and the one
 *     committing action wears the ice gradient at full strength rather than a
 *     colour of its own. Capture green is the single documented exception, and
 *     it means one thing: something is being recorded.
 */

/* Funnel Display, self-hosted. No CDN: the app has a strict CSP and the whole
   product promise is that it does not phone home. */
@font-face {
  font-family: 'Funnel Display';
  src: url('fonts/funnel-display-latin-300-normal.woff2') format('woff2');
  font-weight: 300; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Funnel Display';
  src: url('fonts/funnel-display-latin-400-normal.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Funnel Display';
  src: url('fonts/funnel-display-latin-500-normal.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Funnel Display';
  src: url('fonts/funnel-display-latin-600-normal.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Funnel Display';
  src: url('fonts/funnel-display-latin-700-normal.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* ---- colours ---------------------------------------------------------- */
  --color-midnight-canvas: #05060f;
  --color-steel-plate: #2f343e;
  --color-fog-veil: #9da7ba;
  --color-moon-mist: #c7d3ea;
  --color-frost-glow: #d1e4fa;
  --color-ice-highlight: #d8ecf8;
  /* The gradient's far stop, named so the SVG mark can reference the same two
     colours the CSS gradient is built from instead of keeping a copy. */
  --color-glacier: #98c0ef;
  --gradient-ice-highlight: linear-gradient(0deg, var(--color-ice-highlight) 0%, var(--color-glacier) 100%);
  --color-pure-white: #ffffff;
  /* The documented chromatic exception: capture green, shared with the
     desktop app so "listening" means the same colour in both places. Without it
     here the state dot resolves to nothing and the pill never lights up. */
  --color-live: #34d399;
  --color-blueprint-blue: #b6d9fc;
  --color-gridline-blue: #3f4959;
  --color-glass-edge: rgba(186, 215, 247, 0.12);
  --color-luminous-fill: rgba(199, 211, 234, 0.12);

  /* ---- surfaces --------------------------------------------------------- */
  --surface-frosted-glass: rgba(186, 214, 247, 0.03);
  --surface-deep-glass: rgba(5, 6, 15, 0.97);
  --surface-frost-wash: rgba(186, 214, 247, 0.06);
  --surface-frost-wash-hover: rgba(186, 214, 247, 0.12);

  /* ---- typography ------------------------------------------------------- */
  /* Substitutes are named ahead of the system stack, so self-hosting the real
     faces later is a @font-face block and nothing else. */
  --font-untitled-sans: 'Funnel Display', 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-aeonikpro: 'Funnel Display', 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-dotdigital: 'dotDigital', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --text-caption: 12px;   --leading-caption: 1.33;
  --text-body-sm: 14px;   --leading-body-sm: 1.43;
  --text-body: 16px;      --leading-body: 1.5;      --tracking-body: -0.16px;
  --text-subheading: 18px; --leading-subheading: 1.33;
  --text-heading-sm: 24px; --leading-heading-sm: 1.17; --tracking-heading-sm: -0.24px;
  --text-heading: 28px;   --leading-heading: 1.14;
  --text-heading-lg: 44px; --leading-heading-lg: 1.16;
  --text-display: 48px;   --leading-display: 1.17;

  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;

  /* ---- spacing ---------------------------------------------------------- */
  --spacing-4: 4px;    --spacing-8: 8px;    --spacing-12: 12px;
  --spacing-16: 16px;  --spacing-20: 20px;  --spacing-24: 24px;
  --spacing-32: 32px;  --spacing-36: 36px;  --spacing-40: 40px;
  --spacing-48: 48px;  --spacing-56: 56px;  --spacing-100: 100px;
  --spacing-120: 120px; --spacing-200: 200px;

  /* The band the product illustration occupies. The dock reserves exactly this
     much whatever state the product is in, so collapsing cannot change the
     document height - which would otherwise move the scroll position, un-stick
     the bar, and leave it oscillating between the two states. */
  --product-h: 460px;
  /* The step up the scale from the 40 inside the hero, so the dock reads as a
     different object rather than as the hero's next line. */
  --dock-gap: 56px;
  /* Height of the collapsed bar plus its sticky offset - what the walkthrough
     view has to clear when both are pinned. */
  --toolbar-clear: 96px;

  /* How far the eyebrow's frame runs past its own corners. */
  --eyebrow-overshoot: 9px;

  /* The mark's size once it has left the toolbar and is touring the features. */

  /* The height of whatever the collapsed toolbar is carrying. Set by the tallest
     thing any payload holds - a kbd (20px) plus the chip's 4px padding - and
     applied to every payload so the bar cannot change height when they swap. */
  --chip-h: 28px;

  /* ---- motion ----------------------------------------------------------- */
  /* One scale for the whole page. Surface motion (collapse, slide, position)
     uses --ease-smooth-out; anything that swaps in place uses --ease-in-out. */
  --ease-smooth-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: ease-in-out;
  --duration-stagger: 40ms;
  --duration-quick: 150ms;
  --duration-fast: 250ms;
  --duration-medium: 350ms;
  --duration-slow: 400ms;
  --duration-very-slow: 500ms;
  --blur-medium: 3px;
  --distance-medium: 12px;

  --page-max-width: 1200px;
  --section-gap: 120px;
  --card-padding: 24px;
  --element-gap: 16px;

  /* ---- radii ------------------------------------------------------------ */
  --radius-cards: 16px;
  --radius-badges: 6px;
  --radius-inputs: 6px;
  --radius-modals: 16px;
  --radius-buttons: 999px;
  --radius-iconcontainers: 9999px;

  /* ---- shadows ---------------------------------------------------------- */
  --shadow-sm: rgba(186, 207, 247, 0.32) 0px 0px 6px 0px;
  --shadow-md: rgba(238, 186, 247, 0.24) 0px 0px 12px 0px;
  --shadow-subtle: rgba(186, 215, 247, 0.12) 0px 0px 0px 1px inset;
  --shadow-subtle-3: rgba(186, 214, 247, 0.06) 0px 0px 0px 1px inset;
  --shadow-subtle-9: rgba(186, 214, 247, 0.24) 0px 0px 0px 1px inset;

  --elevation-feature-card:
    inset 0 1px 1px rgba(199, 211, 234, 0.12),
    inset 0 24px 48px rgba(199, 211, 234, 0.05),
    0 24px 32px rgba(6, 6, 14, 0.7);
  --elevation-modal:
    inset 0 1px 1px rgba(216, 236, 248, 0.2),
    inset 0 24px 48px rgba(168, 216, 245, 0.06),
    0 16px 32px rgba(0, 0, 0, 0.3);
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* Scrollbar hidden by request. The page still scrolls by wheel, trackpad,
     keyboard and touch - only the visual track is gone. */
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* legacy Edge */
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
  width: 0; height: 0; display: none;
}
body { scrollbar-width: none; -ms-overflow-style: none; }

body {
  margin: 0;
  background: var(--color-midnight-canvas);
  color: var(--color-moon-mist);
  font-family: var(--font-untitled-sans);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  letter-spacing: var(--tracking-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0; font-family: var(--font-aeonikpro); font-weight: var(--font-weight-medium); color: var(--color-ice-highlight); }
p { margin: 0; }
a { color: inherit; }

.hidden { display: none !important; }
/* A `display` on the element itself outranks the [hidden] attribute's UA rule,
   which is how the wizard's Back and Submit buttons - both .btn, both
   inline-flex - stayed on screen while marked hidden. */
[hidden] { display: none !important; }

/* Present for screen readers, absent for everyone else. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: var(--spacing-12) var(--spacing-16);
  border-radius: var(--radius-buttons);
  background: var(--color-midnight-canvas); color: var(--color-frost-glow);
  box-shadow: var(--shadow-subtle); text-decoration: none;
}
.skip-link:focus { left: var(--spacing-16); top: var(--spacing-16); }

:focus-visible {
  outline: 1px solid rgba(186, 215, 247, 0.5);
  outline-offset: 2px;
}

/* ============================ brand ============================ */
.brand-word {
  color: var(--color-frost-glow);
  font-family: var(--font-untitled-sans);
  font-size: var(--text-body); font-weight: var(--font-weight-medium);
}

/* ============================ shared ============================ */
main { position: relative; z-index: 1; }

.section {
  max-width: var(--page-max-width); margin: 0 auto;
  padding: 0 var(--spacing-24);
  margin-top: var(--section-gap);
}

/* Eyebrow: tracked caps inside a drawn frame whose rules overshoot the corners,
   so the box reads as registration marks rather than a boxed label. Two crossed
   pseudo-elements do it: one carries the horizontals and overhangs sideways, the
   other carries the verticals and overhangs vertically. */
.eyebrow {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  align-self: center;
  padding: var(--spacing-8) var(--spacing-16);
  font-family: var(--font-dotdigital);
  font-size: 11.5px; font-weight: var(--font-weight-regular);
  line-height: 1.2; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--color-moon-mist);
  font-feature-settings: 'tnum' on;
}
.eyebrow::before, .eyebrow::after { content: ''; position: absolute; pointer-events: none; }
/* Horizontals, overhanging left and right. */
.eyebrow::before {
  inset: 0 calc(var(--eyebrow-overshoot) * -1);
  border-top: 1px solid var(--color-glass-edge);
  border-bottom: 1px solid var(--color-glass-edge);
}
/* Verticals, overhanging top and bottom. */
.eyebrow::after {
  inset: calc(var(--eyebrow-overshoot) * -1) 0;
  border-left: 1px solid var(--color-glass-edge);
  border-right: 1px solid var(--color-glass-edge);
}

.section-title {
  margin-top: var(--spacing-24);
  font-size: var(--text-heading-lg); line-height: var(--leading-heading-lg);
  text-align: center;
  background: var(--gradient-ice-highlight);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.section-sub {
  max-width: 640px; margin: var(--spacing-20) auto 0;
  text-align: center;
  color: var(--color-moon-mist);
  font-size: var(--text-body); line-height: var(--leading-body);
}

/* ---- buttons ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-8);
  padding: var(--spacing-12) var(--spacing-24);
  border: 0; border-radius: var(--radius-buttons);
  font-family: var(--font-untitled-sans);
  font-size: var(--text-body-sm); font-weight: var(--font-weight-medium);
  line-height: var(--leading-body-sm);
  text-decoration: none; cursor: pointer;
  transition: background 160ms ease, box-shadow 160ms ease;
}
.btn-primary {
  background: var(--surface-frost-wash); color: var(--color-pure-white);
  box-shadow: var(--shadow-subtle);
}
.btn-lg { padding: var(--spacing-16) var(--spacing-32); font-size: var(--text-body); }
.btn-ic { display: inline-flex; }
.btn-ic svg { width: 16px; height: 16px; }
.btn-primary:hover { background: var(--surface-frost-wash-hover); }
.btn-quiet {
  background: transparent; color: var(--color-frost-glow);
  box-shadow: var(--shadow-subtle);
}
.btn-quiet:hover { background: var(--surface-frost-wash); }

/* The one committing action on the page, and the brightest object on it.
 *
 * Ice rather than a chromatic accent: the mark, the wordmark and every heading
 * are drawn from this same gradient, so the submit reads as the page's own
 * brand at full strength instead of introducing a colour that appears nowhere
 * else. On near-black it is also the highest contrast this palette can reach.
 * 6px radius so it belongs to the input family it sits under, not the pills. */
.btn-ice {
  background: var(--gradient-ice-highlight); color: var(--color-midnight-canvas);
  border-radius: var(--radius-inputs);
  padding: var(--spacing-12) var(--spacing-24);
  font-weight: var(--font-weight-semibold);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16) inset;
  transition: filter 160ms ease;
}
.btn-ice:hover { filter: brightness(1.08); }
.btn-ice[disabled] { opacity: 0.6; cursor: default; filter: none; }

/* ---- glass card ---- */
.glass-card {
  border-radius: var(--radius-cards);
  background: var(--surface-frosted-glass);
  padding: var(--card-padding);
  box-shadow: var(--elevation-feature-card);
}

kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 var(--spacing-4);
  border-radius: var(--radius-badges);
  background: var(--color-luminous-fill);
  box-shadow: var(--shadow-subtle);
  color: var(--color-frost-glow);
  font-family: var(--font-untitled-sans);
  font-size: var(--text-caption); font-weight: var(--font-weight-medium);
  line-height: 1; letter-spacing: 0;
}

/* ============================ hero ============================ */
/* Hero copy and the illustration below it together fill the viewport, so the
   four elements read as one centred group. --product-h reserves the band the
   illustration occupies. */
.stage { position: relative; z-index: 1; }
/* Anchored to the bottom of its band, not centred in it.
 *
 * Centred, the space between the lede and the dock was half of whatever was
 * left over: 71px at 1440x900 against an internal rhythm of 32/40/40, and
 * larger on a taller display. Anchoring makes that gap exactly --dock-gap at
 * every height and sends the leftover to the top, where it reads as breathing
 * room under the edge of the window. The cap stops the top slack running away
 * on a very tall display. */
.hero {
  min-height: min(calc(100vh - var(--product-h) - var(--dock-gap)), 620px);
  max-width: var(--page-max-width); margin: 0 auto;
  padding: var(--spacing-56) var(--spacing-24) 0;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
  text-align: center;
}
/* No shaft, no bloom: the mark stands on its own and earns attention by moving
   rather than by being lit. */
.hero-mark {
  width: 46px; height: 46px;
  margin-bottom: var(--spacing-32);
}

/* The rhythm down the hero, in optical gaps rather than declared ones: 32 to the
   eyebrow, 40 to the wordmark, 40 to the lede, then --dock-gap to the product.
   The eyebrow labels the wordmark, so it sits closer to it than the mark does to
   either. */
.hero-eyebrow { margin-bottom: var(--spacing-40); }

/* ---- the wordmark, which decodes and then fills ---- */
/* Two layers of the same word in one box. The outline carries the scramble, so
   the letters are legible as shapes while they are still nonsense; the gradient
   fill then wipes across from the left and eats the outline as it goes. One
   shared clip edge does both halves, which is why it reads as filling in rather
   than as two things cross-fading. */
.wordmark {
  position: relative;
  display: inline-block; min-width: 4ch; text-align: center;
  font-family: var(--font-aeonikpro);
  font-weight: var(--font-weight-medium);
  font-size: clamp(88px, 16vw, 180px);
  /* Optically trimmed, not 1. At 180px a line-height of 1 leaves ~36px of
     leading above the caps and ~24px below, so the spacing you *set* around the
     wordmark and the spacing you *see* differ by a third - and the headline
     reads as sitting low in a box of its own. 0.82 hugs the glyphs, which makes
     the margins on either side mean what they say. */
  line-height: 0.82; letter-spacing: -0.03em;
  /* Tabular figures and a reserved width stop the scrambling glyphs from
     resizing the headline on every frame - the reveal has to be still. */
  font-variant-numeric: tabular-nums;
}
.wordmark-fill, .wordmark-line {
  display: block;
  transition: clip-path var(--duration-very-slow) var(--ease-smooth-out);
}
.wordmark-fill {
  background: var(--gradient-ice-highlight);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  clip-path: inset(0 0 0 0);
}
/* Sits exactly on top of the fill. -webkit-text-fill-color, not color: the
   stroke is only visible when the glyph body is knocked out. */
.wordmark-line {
  position: absolute; inset: 0;
  -webkit-text-fill-color: transparent; color: transparent;
  -webkit-text-stroke: 1px rgba(216, 236, 248, 0.5);
  clip-path: inset(0 0 0 100%);
}
.wordmark.is-decoding .wordmark-fill { clip-path: inset(0 100% 0 0); }
.wordmark.is-decoding .wordmark-line { clip-path: inset(0 0 0 0); }
.wordmark-halo { position: relative; display: inline-block; }
/* Re-tuned with the trimmed line box: the halo is a percentage of a box that is
   now most glyph, so the old 15/25 inset would have sat it below the letters. */
.wordmark-halo::after {
  content: ''; position: absolute; inset: -6% 0 -2%;
  background: radial-gradient(ellipse at center, rgba(186, 207, 247, 0.22), transparent 70%);
  filter: blur(28px); z-index: -1;
}

.hero-lede {
  max-width: 620px; margin: var(--spacing-40) auto 0;
  color: var(--color-moon-mist);
  font-size: var(--text-subheading); line-height: var(--leading-subheading);
}

/* ---- the product illustration, which is also the navigation ---- */
/* Its parent is .stage, which spans the whole page, so once it sticks it stays
   stuck - a dock inside .hero would come unstuck the moment the hero ended. */
.product-dock {
  position: sticky; top: 14px; z-index: 40;
  height: var(--product-h);
  display: flex; justify-content: center; align-items: flex-start;
  margin: var(--dock-gap) auto 0;
  padding: 0 var(--spacing-24);
  /* The reserved band is mostly empty once collapsed; only the bar itself
     should ever intercept the pointer. */
  pointer-events: none;
}
.product { pointer-events: auto; }

/* Every dimension that changes is transitioned on the same curve and duration,
   so the collapse reads as one movement rather than several. */
.product {
  width: min(620px, 100%);
  text-align: left;
  border-radius: var(--radius-modals);
  background: var(--surface-deep-glass);
  box-shadow: var(--elevation-modal);
  overflow: hidden;
  transition:
    width 520ms var(--ease-smooth-out),
    border-radius 520ms var(--ease-smooth-out),
    transform var(--duration-medium) var(--ease-smooth-out),
    box-shadow var(--duration-medium) ease;
}
.product:focus-visible { outline: 1px solid rgba(186, 215, 247, 0.5); outline-offset: 3px; }

.product-bar {
  display: flex; align-items: center; gap: var(--spacing-12);
  padding: var(--spacing-16) var(--spacing-20);
  box-shadow: inset 0 -1px 0 var(--color-glass-edge);
  transition: padding 520ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 300ms ease;
}
/* The bar's mark is hidden until the hero's has scrolled away, and hidden again
   while it is off touring the tiles. Its 20px box stays put through both - the
   slot is small enough to read as the bar's own lead-in, and collapsing it would
   mean animating width and a negative margin, which relayouts the bar on every
   frame *while the page is scrolling*. Transform and opacity only. */
.pb-mark {
  width: 20px; height: 20px; flex: none;
  opacity: 0; transform: scale(0.4);
  transition:
    opacity var(--duration-quick) var(--ease-in-out),
    transform var(--duration-medium) var(--ease-smooth-out);
}
/* Arrives with a puff: overshoot, a ring that expands out of it and dies. A fade
   would read as something still loading. */
.product.mark-here:not(.mark-away) .pb-mark {
  opacity: 1; transform: none;
  animation: mark-poof var(--duration-very-slow) var(--ease-smooth-out);
}
.product.mark-here:not(.mark-away) .pb-mark::after {
  content: ''; position: absolute; inset: -6px;
  border-radius: var(--radius-iconcontainers);
  box-shadow: 0 0 0 1px var(--color-glass-edge);
  animation: mark-puff var(--duration-very-slow) var(--ease-smooth-out) forwards;
  pointer-events: none;
}
.pb-mark { position: relative; }

/* The word sits over the empty slot whenever the mark is not in it. */
.pb-word {
  color: var(--color-frost-glow);
  font-size: var(--text-body-sm); font-weight: var(--font-weight-medium);
  transform: translateX(calc((20px + var(--spacing-12)) * -1));
  transition: transform var(--duration-medium) var(--ease-smooth-out);
}
.product.mark-here:not(.mark-away) .pb-word { transform: none; }

/* Three payloads share the right-hand slot and exactly one is ever shown.
 *
 * Expanded, the bar points down the page: there is nothing worth asking of
 * somebody who has not seen the product yet, and the dock is the product. Once
 * it has collapsed - which only happens after the hero has been read - the slot
 * carries the invitation, and lends itself to the function chip while a feature
 * tile is under the pointer.
 *
 * All three are built to the same metrics, so the bar cannot change height when
 * they swap. */
.pb-hint, .pb-func { display: none; }
.product:not(.as-toolbar) .pb-hint { display: inline-flex; }
.product:not(.as-toolbar) .pb-cta { display: none; }
.product.as-toolbar.showing-func .pb-func { display: inline-flex; }
.product.as-toolbar.showing-func .pb-cta { display: none; }

/* A caption you can press. Quiet - it is a direction, not an offer. */
.pb-hint {
  margin-left: auto;
  align-items: center; gap: var(--spacing-8);
  min-height: var(--chip-h);
  padding: var(--spacing-4) var(--spacing-12);
  border: 0; border-radius: var(--radius-badges);
  background: none; cursor: pointer;
  color: var(--color-fog-veil);
  font-family: var(--font-untitled-sans);
  font-size: var(--text-caption); line-height: var(--leading-caption);
  white-space: nowrap;
  transition: color 160ms var(--ease-in-out), background 160ms var(--ease-in-out);
}
.pb-hint:hover { background: var(--surface-frost-wash); color: var(--color-frost-glow); }
.pb-hint-ic { display: inline-flex; }
.pb-hint-ic svg { width: 13px; height: 13px; }
/* Drifts down and settles, once every few seconds - the smallest possible
   nudge, and the only thing on the bar that moves on its own. */
.pb-hint-ic { animation: nudge 2.8s var(--ease-in-out) infinite; }
@keyframes nudge {
  0%, 62%, 100% { transform: none; }
  76%           { transform: translateY(3px); }
}
.pb-func {
  margin-left: auto;
  align-items: center; gap: var(--spacing-12);
  min-height: var(--chip-h);
  padding: var(--spacing-4) var(--spacing-12);
  border-radius: var(--radius-badges);
  background: var(--color-luminous-fill);
  box-shadow: var(--shadow-subtle);
  white-space: nowrap;
}
/* A little larger than 15px: the shape is being tweened rather than cut, and
   below about 17px the tween has no room to be read as one. */
.pb-func-ic { display: inline-flex; }
.pb-func-ic svg { width: 17px; height: 17px; color: var(--color-blueprint-blue); }
.pb-func-name {
  min-width: 7ch;
  color: var(--color-frost-glow);
  font-size: var(--text-caption); font-weight: var(--font-weight-medium);
}
/* The chord has no letters to decode, so it swaps under a blur instead. */
.pb-func-keys {
  display: inline-flex; gap: 3px;
  transition: opacity var(--duration-quick) var(--ease-in-out), filter var(--duration-quick) var(--ease-in-out);
}
.pb-func-keys.is-swapping { opacity: 0; filter: blur(2px); }

/* Built to the same metrics as the function chip above it, because it takes the
   same slot: .btn's own padding and 14px type made it 12px taller than the chip,
   and the bar grew by that much the moment the bento handed over. The payloads
   have to be interchangeable or the toolbar changes height mid-scroll. */
.pb-cta {
  margin-left: auto;
  min-height: var(--chip-h);
  padding: var(--spacing-4) var(--spacing-12);
  border-radius: var(--radius-badges);
  background: var(--color-luminous-fill);
  box-shadow: var(--shadow-subtle);
  color: var(--color-frost-glow);
  font-size: var(--text-caption); line-height: var(--leading-caption);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}
.pb-cta:hover { background: var(--surface-frost-wash-hover); color: var(--color-pure-white); }
.pb-cta-ic { display: inline-flex; }
.pb-cta-ic svg { width: 13px; height: 13px; color: var(--color-blueprint-blue); }

.product-body {
  padding: var(--spacing-40) var(--spacing-32);
  transition:
    max-height 520ms var(--ease-smooth-out),
    opacity 300ms ease,
    padding 520ms var(--ease-smooth-out);
  max-height: 400px; opacity: 1;
}
.product-title {
  font-family: var(--font-aeonikpro); font-size: var(--text-heading-sm);
  line-height: var(--leading-heading-sm); text-align: center;
  background: var(--gradient-ice-highlight);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.product-keys {
  list-style: none; margin: var(--spacing-32) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--spacing-12);
}
.product-keys li {
  display: flex; align-items: center; gap: var(--spacing-16);
  color: var(--color-moon-mist);
  font-size: var(--text-body-sm); line-height: var(--leading-body-sm);
}
.combo { display: inline-flex; gap: 3px; flex: none; min-width: 120px; }

/* Collapsed: a bare toolbar carrying the CTA, which is the navigation. */
.product.as-toolbar { width: min(460px, 100%); border-radius: 14px; }
.product.as-toolbar .product-bar { padding: var(--spacing-8) var(--spacing-8) var(--spacing-8) var(--spacing-16); box-shadow: none; }
.product.as-toolbar .product-body { max-height: 0; opacity: 0; padding-top: 0; padding-bottom: 0; }

/* ============================ the bento ============================ */
/* Five tiles, each carrying a working miniature of the thing it describes
   rather than a picture of one. The mark tours them as the section scrolls past
   and the tile it is on lights up, so the grid is read in an order rather than
   all at once - which is what the pinned walkthrough used to do, kept. */
.walk {
  position: relative;
  max-width: var(--page-max-width); margin: 0 auto;
  padding: 0 var(--spacing-24);
}
.bento {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--spacing-16);
}

.tile {
  position: relative;
  grid-column: span 2;
  display: flex; flex-direction: column;
  padding: var(--spacing-24);
  border-radius: var(--radius-cards);
  background: var(--surface-frosted-glass);
  box-shadow: var(--shadow-subtle);
  overflow: hidden;
  transition: box-shadow var(--duration-slow) var(--ease-in-out),
              background var(--duration-slow) var(--ease-in-out);
}
/* Two shapes, so the grid is a bento rather than a row of equal boxes. */
.tile-wide { grid-column: span 4; }
.tile-tall { grid-column: span 2; grid-row: span 2; }

/* The tile the mark has arrived on, and any tile under the pointer. Hover is
   the same treatment because the mark's stop *is* a kind of attention - two
   different highlights for "look here" would read as two different meanings. */
.tile.is-lit, .tile:hover {
  background: var(--surface-frost-wash);
  box-shadow: var(--shadow-subtle-9);
}
/* A hairline that runs along the top edge of the lit tile, brightest where the
   mark is parked. */
.tile::after {
  content: ''; position: absolute; inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--color-blueprint-blue), transparent);
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-in-out);
}
.tile.is-lit::after { opacity: 0.5; }

.tile-art {
  flex: 1;
  display: grid; place-items: center;
  min-height: 168px;
  margin-bottom: var(--spacing-24);
}
.tile-tall .tile-art { min-height: 300px; }

.tile-title {
  font-family: var(--font-aeonikpro);
  font-size: var(--text-subheading); line-height: var(--leading-subheading);
  letter-spacing: -0.01em;
  color: var(--color-ice-highlight);
}
.tile-copy {
  margin-top: var(--spacing-8);
  color: var(--color-fog-veil);
  font-size: var(--text-body-sm); line-height: var(--leading-body-sm);
}

/* ---- the miniatures ---- */
/* Built from the same tokens as the real UI rather than drawn as pictures, so
   they cannot drift from what the product actually looks like. */
.mini {
  width: 100%; max-width: 320px;
  padding: var(--spacing-16);
  border-radius: var(--radius-cards);
  background: var(--surface-deep-glass);
  box-shadow: var(--elevation-modal);
  font-size: var(--text-caption); line-height: 1.45;
}
.mini-bar {
  display: flex; align-items: center; gap: var(--spacing-8);
  padding-bottom: var(--spacing-12);
  box-shadow: inset 0 -1px 0 var(--color-glass-edge);
  color: var(--color-frost-glow);
}
.mini-dot {
  width: 7px; height: 7px; border-radius: var(--radius-iconcontainers);
  background: var(--color-live);
  box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.18);
}
.mini-keys { margin-left: auto; display: inline-flex; gap: 3px; }
.mini-keys kbd { height: 18px; min-width: 18px; font-size: 10px; }
.mini-q { margin-top: var(--spacing-12); color: var(--color-fog-veil); }
.mini-a {
  margin-top: var(--spacing-8);
  padding: var(--spacing-12);
  border-radius: var(--radius-badges);
  background: var(--color-luminous-fill);
  color: var(--color-frost-glow);
}

.mini-shot {
  position: relative; overflow: hidden;
  height: 96px; border-radius: var(--radius-badges);
  background:
    repeating-linear-gradient(115deg, rgba(199,211,234,0.05) 0 2px, transparent 2px 9px),
    rgba(199, 211, 234, 0.03);
  box-shadow: var(--shadow-subtle);
}
/* The shutter. Hidden until the tile is pointed at, then one pass down the
   frame - the way a sensor is read, not a loop. */
.mini-shot::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    transparent 0%,
    rgba(216, 236, 248, 0.14) 44%,
    rgba(216, 236, 248, 0.38) 50%,
    rgba(216, 236, 248, 0.14) 56%,
    transparent 100%);
  opacity: 0; pointer-events: none;
}
/* The capture marquee, drawn the way a selection is: a dashed rectangle. */
.mini-crop {
  /* Centred on the frame it is cropping. It sat left of it, which read as an
     alignment mistake rather than as composition. */
  position: absolute; inset: 21% 18%;
  border: 1px dashed var(--color-blueprint-blue);
  border-radius: 2px;
  background: rgba(182, 217, 252, 0.06);
}
.mini-held {
  display: flex; width: fit-content; margin-left: auto; margin-right: auto;
  align-items: center; gap: var(--spacing-8);
  margin-top: var(--spacing-16);
  padding: var(--spacing-4) var(--spacing-12);
  border-radius: var(--radius-badges);
  background: var(--color-luminous-fill); box-shadow: var(--shadow-subtle);
  color: var(--color-frost-glow);
}
.mini-tick { display: inline-flex; }
.mini-tick svg { width: 13px; height: 13px; color: var(--color-live); }

.mini-search {
  display: flex; align-items: center; gap: var(--spacing-8);
  padding: var(--spacing-8) var(--spacing-12);
  border-radius: var(--radius-badges);
  background: rgba(199, 211, 234, 0.05); box-shadow: var(--shadow-subtle);
  color: var(--color-frost-glow);
}
.mini-search-ic { display: inline-flex; flex: none; }
.mini-search-ic svg { width: 13px; height: 13px; color: var(--color-fog-veil); }
/* Reserved for the full query, so typing it cannot widen the field it is being
   typed into. */
/* Plain inline, so the caret that follows it sits against the last letter.
   The width is reserved by .mini-typed around it. */
.mini-query { display: inline; min-width: 0; }

/* ---- selection ---- */
/* Selection is a colour change and nothing else. The background stays exactly
   as it was, so picking words out of a paragraph does not put a bar through the
   page: the glyphs simply go from moon mist to pure white and the reader can
   see what they have got.
 *
 * This replaced a decrypt-and-outline treatment that ran on every selection.
   It was the most technically interesting thing on the page and the wrong thing
   to do to somebody who was only trying to copy an email address, so the whole
   mechanism is gone rather than tuned. */
::selection { background: transparent; color: var(--color-pure-white); }
::-moz-selection { background: transparent; color: var(--color-pure-white); }

/* The caret, shared by both progressive reveals. Present only while something is
   being written, and it stops when the writing does - a caret that keeps
   blinking after the text has landed says the machine is still going. */
.is-writing::after {
  content: '';
  display: inline-block; vertical-align: text-bottom;
  width: 2px; height: 1em; margin-left: 1px;
  background: var(--color-blueprint-blue);
  animation: caret 900ms steps(2, start) infinite;
}
@keyframes caret { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* Results are the answer to a question that has been asked, so while the query
   is still being typed they are not results yet - they are skeletons.
 *
 * Skeletons and not hidden rows: the text stays in place and is merely made
 * transparent, with the shimmer painted behind it, so every block is exactly the
 * size of the content it stands in for. The card cannot change height at any
 * point in the sequence, which is what stops the bento reflowing under the
 * pointer. */
.mini-recall .mini-hit span,
.mini-recall .mini-hit em {
  border-radius: 3px;
  transition: color 160ms var(--ease-in-out);
}
.mini-recall.is-loading .mini-hit span,
.mini-recall.is-loading .mini-hit em {
  color: transparent;
  background-image: linear-gradient(
    90deg,
    rgba(199, 211, 234, 0.05) 0%,
    rgba(199, 211, 234, 0.14) 50%,
    rgba(199, 211, 234, 0.05) 100%);
  background-size: 200% 100%;
  animation: skeleton 1.2s linear infinite;
}
@keyframes skeleton {
  from { background-position: 120% 0; }
  to   { background-position: -20% 0; }
}
/* The real rows arrive once, on the way out of loading. */
.mini-recall:not(.is-loading) .mini-hit { animation: riseIn 280ms var(--ease-smooth-out); }
.mini-recall:not(.is-loading) .mini-hit:nth-of-type(2) { animation-delay: 80ms; }
.mini-hit {
  display: flex; flex-direction: column; gap: 2px;
  margin-top: var(--spacing-8);
  padding: var(--spacing-8) var(--spacing-12);
  border-radius: var(--radius-badges);
  box-shadow: var(--shadow-subtle-3);
}
.mini-hit span { color: var(--color-fog-veil); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; }
.mini-hit em { color: var(--color-moon-mist); font-style: normal; }

.mini-local { display: flex; flex-direction: column; align-items: center; gap: var(--spacing-16); }
.mini-shield { display: inline-flex; }
.mini-shield svg { width: 30px; height: 30px; color: var(--color-blueprint-blue); }
.mini-path {
  padding: var(--spacing-8) var(--spacing-12);
  border-radius: var(--radius-badges);
  background: rgba(199, 211, 234, 0.05); box-shadow: var(--shadow-subtle);
  color: var(--color-moon-mist);
  font-family: var(--font-dotdigital); font-size: 11px;
  /* Reserved for the longest form of the string, so the decryption cannot
     resize the chip it is running inside. */
  min-width: 23ch; text-align: center;
}

/* ---- the miniatures, woken by the pointer ---- */
/* Each card demonstrates its own feature rather than illustrating it: point at
   Hold and a capture happens, point at Recall and the results land. The text
   that re-letters is driven from main.js so it shares the wordmark's decryption;
   everything here is what a stylesheet can say on its own.
 *
 * All of it is one pass and none of it loops. A tile that keeps moving after
 * you have read it is a distraction from the four next to it. */
.tile:hover .mini-shot::after { animation: mini-shutter 620ms var(--ease-smooth-out); }
@keyframes mini-shutter {
  0%   { opacity: 0; transform: translateY(-100%); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(100%); }
}
/* The marquee opens onto its selection as the shutter passes.
 *
 * A transform, not the inset it looks like: the small rect and the final one
 * share a centre exactly (46%, 47%), so the whole move is a scale about that
 * centre - 40/64 across, 34/58 down. Animating `inset` would have described the
 * same two rectangles and made the browser lay the element out on every frame
 * of a scroll-adjacent hover, for a shape that is four numbers of arithmetic
 * away from being composited. */
.tile:hover .mini-crop { animation: mini-marquee 620ms var(--ease-smooth-out); }
@keyframes mini-marquee {
  from { transform: scale(0.625, 0.586); opacity: 0.35; }
  to   { transform: none; opacity: 1; }
}
/* The receipt, after both - you are told it was kept once it has been. */
.tile:hover .mini-held { animation: mini-receipt 380ms var(--ease-smooth-out) 460ms both; }
@keyframes mini-receipt {
  from { opacity: 0; transform: translateY(6px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}
/* Results arrive in the order they were ranked, not all at once. */
/* The recall tile's results are staged against the query being typed, so they
   are driven by `.mini-recall.is-done` above rather than by hover directly. */
/* The one card with nothing to demonstrate but the fact of staying put, so the
   shield simply acknowledges being looked at. */
.tile:hover .mini-shield { animation: settle 520ms var(--ease-smooth-out); }

/* ---- the mark in flight, and the line it leaves ---- */
/* Ambient texture, behind everything. */
.particles {
  position: fixed; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}

/* ==================== the invitation, inside the dock ==================== */
/* Unfolds out of the toolbar rather than opening as a dialog. It overlays the
   page instead of displacing it, which is only possible because the dock always
   reserves the same band whatever state the product is in - the panel grows into
   space that was already spoken for. */
/* The padding lives on the form inside, not here, so the unfold animates one
   property instead of three. A padded container cannot collapse to nothing —
   `max-height: 0` still leaves the padding standing, even under border-box - so
   the outer box is bare and the inner one carries the inset. */
.product-access {
  max-height: 0; opacity: 0; visibility: hidden;
  overflow: hidden;
  transition:
    max-height 420ms var(--ease-smooth-out),
    opacity 220ms var(--ease-in-out),
    visibility 0s linear 420ms;
}
.product.access-open .product-access {
  max-height: 200px; opacity: 1; visibility: visible;
  transition:
    max-height 420ms var(--ease-smooth-out),
    opacity 260ms var(--ease-in-out) 80ms,
    visibility 0s;
}
/* Tight below: the status line already reserves its own row, so the panel does
   not need to reserve one for it as well. */
.access { padding: var(--spacing-20) var(--spacing-20) var(--spacing-16); }
/* The invitation only opens under the collapsed bar - that is the only state its
   button appears in - so there is no longer a shortcut list to fold away. */

/* An address and the key to send it. Nothing else: this opens under a bar the
   visitor just pressed, so it needs no heading to say what it is. */
.access { display: flex; flex-wrap: wrap; gap: var(--spacing-12); }
.access input[type="email"] {
  flex: 1; min-width: 0;
}

.access input[type="email"] {
  width: 100%; min-width: 0;
  padding: var(--spacing-12) var(--spacing-16);
  border: 0; border-radius: var(--radius-inputs);
  background: rgba(199, 211, 234, 0.05);
  box-shadow: var(--shadow-subtle);
  color: var(--color-frost-glow);
  font-family: var(--font-untitled-sans);
  font-size: var(--text-body-sm); line-height: var(--leading-body-sm);
  outline: none;
  transition: box-shadow 160ms ease;
}
.access ::placeholder { color: rgba(199, 211, 234, 0.6); }
.access input:focus { box-shadow: var(--shadow-subtle-9); }
.access [aria-invalid="true"] { box-shadow: rgba(228, 109, 76, 0.5) 0 0 0 1px inset; }

/* Square to the input's height, so the pair reads as one control. */
.access-go {
  flex: none; width: 46px; height: 46px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--radius-inputs);
  background: var(--gradient-ice-highlight); color: var(--color-midnight-canvas);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16) inset;
  cursor: pointer;
  transition: filter 160ms ease, transform 160ms ease;
}
.access-go:hover { filter: brightness(1.08); }
.access-go:active { transform: scale(0.97); }
.access-go[disabled] { opacity: 0.6; cursor: default; filter: none; }
.access-go-ic { display: inline-flex; }
.access-go-ic svg { width: 18px; height: 18px; }

.btn-spinner { display: none; width: 15px; height: 15px; }
.access-go.busy .btn-spinner {
  display: block;
  border: 2px solid rgba(5, 6, 15, 0.25); border-top-color: var(--color-midnight-canvas);
  border-radius: var(--radius-iconcontainers);
  animation: spin 700ms linear infinite;
}
.access-go.busy .access-go-ic { display: none; }

/* Silent at rest, and its height is reserved either way, so a complaint cannot
   resize a panel that has been unfolded out of a toolbar. */
.access-note {
  flex: 1 0 100%;
  min-height: calc(var(--text-caption) * var(--leading-caption));
  color: var(--color-fog-veil);
  font-size: var(--text-caption); line-height: var(--leading-caption);
}
.access-note.error { color: #e9917a; }
.access-note.ok { color: var(--color-blueprint-blue); }
/* Answered: the controls go, the line stays. */
.product.access-done .access input[type="email"],
.product.access-done .access-go { display: none; }

.gotcha { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ============================ footer ============================ */
/* A registration rule, a patterned band carrying the mark, the statement, the
   one action, and the legal line. Same drawing language as the eyebrow's
   overshooting frame at the top of the page: hairlines that overrun, and marks
   that read as registration rather than decoration. */
.footer {
  position: relative; z-index: 1;
  /* The bento supplies the scroll the pin needs; this supplies the run-out after
     it, so the bar has somewhere to sit while it offers the CTA. Without it the
     last tile never hands over and the CTA is unreachable. */
  min-height: 30vh;
  margin-top: var(--spacing-120);
}

/* The rule across the top, with registration crosses along it. */
.footer-rule {
  display: flex; align-items: center; justify-content: space-between;
  height: 1px; margin-bottom: var(--spacing-56);
  background: linear-gradient(to right,
    transparent, var(--color-glass-edge) 12%, var(--color-glass-edge) 88%, transparent);
}
.reg { position: relative; width: 11px; height: 11px; flex: none; }
.reg::before, .reg::after {
  content: ''; position: absolute; background: var(--color-glass-edge);
}
.reg::before { left: 0; right: 0; top: 5px; height: 1px; }
.reg::after { top: 0; bottom: 0; left: 5px; width: 1px; }

/* The band. The mark, tiled at almost nothing behind the real one, fading out
   at both edges so the tiling never shows a seam. */
.footer-band {
  position: relative;
  display: grid; place-items: center;
  height: 210px;
  box-shadow: inset 0 -1px 0 var(--color-glass-edge);
  overflow: hidden;
}
.footer-band::before {
  content: ''; position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='%23ffffff'%3E%3Cpath%20fill-rule='evenodd'%20d='M7%204H9V6H15V4H17V2H19V6H17V8H19V10H23V16H21V12H20V18H17V20H19V22H15V18H9V22H5V20H7V18H4V12H3V16H1V10H5V8H7V6H5V2H7V4ZM9%2011V14H11V11H9ZM13%2011V14H15V11H13Z'/%3E%3C/svg%3E");
  /* Large and sparse. At a small tile size this reads as wallpaper rather than
     as texture, and the mark in the middle stops being the subject. */
  background-size: 104px 104px; background-repeat: repeat;
  opacity: 0.022;
  -webkit-mask-image: radial-gradient(ellipse 60% 70% at 50% 50%, #000, transparent);
  mask-image: radial-gradient(ellipse 60% 70% at 50% 50%, #000, transparent);
}
.footer-mark { position: relative; width: 72px; height: 72px; opacity: 0.55; }

/* The statement block, framed by two hairlines that stop short of the rules
   above and below - the frame is implied rather than closed. */
.footer-main {
  position: relative; overflow: hidden;
  max-width: var(--page-max-width); margin: 0 auto;
  padding: var(--spacing-56) var(--spacing-24);
  display: flex; flex-direction: column; align-items: center; gap: var(--spacing-32);
  text-align: center;
}
.footer-main::before, .footer-main::after {
  content: ''; position: absolute; top: var(--spacing-24); bottom: var(--spacing-24);
  width: 1px; background: var(--color-glass-edge);
}
.footer-main::before { left: max(var(--spacing-24), 4%); }
.footer-main::after { right: max(var(--spacing-24), 4%); }

.footer-brand { display: inline-flex; align-items: center; gap: var(--spacing-8); }
.footer-statement {
  max-width: 22ch;
  font-family: var(--font-aeonikpro);
  font-size: clamp(26px, 3.6vw, var(--text-heading-lg));
  line-height: 1.18; letter-spacing: -0.02em;
  color: var(--color-ice-highlight);
}

.footer-links { display: flex; flex-direction: column; align-items: center; gap: var(--spacing-8); }
.footer-label {
  font-family: var(--font-dotdigital);
  font-size: 11.5px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--color-fog-veil);
}
.footer-link {
  display: inline-flex; align-items: center; gap: var(--spacing-8);
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--color-frost-glow); text-decoration: none;
  /* An address is a string to copy, not a statement. At subheading size it
     competed with the line above it, which is the one doing the talking. */
  font-family: var(--font-untitled-sans); font-size: var(--text-body);
  word-break: break-word;
  transition: color 160ms ease;
}
.footer-link:hover { color: var(--color-pure-white); }
.footer-link-ic { display: inline-flex; }
.footer-link-ic svg { width: 15px; height: 15px; }
/* Reserved for the full address, so resolving it cannot resize the row it sits
   in - and set in the monospaced face so the scrambled state and the real one
   occupy exactly the same width. */
.footer-address {
  font-family: var(--font-dotdigital);
  font-size: var(--text-body-sm);
  letter-spacing: 0.01em;
}
/* Leans after the address, the way the envelope would go. */
.footer-link:hover .footer-link-ic { transform: translateX(2px); }
.footer-link-ic { transition: transform 160ms var(--ease-smooth-out); }

/* The guide, reachable from the bottom of the page. Not in the dock: the dock
   carries the one invitation this page is for, and a second link up there
   competes with it. */
.footer-legal-link {
  color: var(--color-moon-mist);
  text-decoration: none;
  margin-right: var(--spacing-12);
}
.footer-legal-link:hover { color: var(--color-ice-highlight); }

.footer-legal {
  padding: var(--spacing-24);
  box-shadow: inset 0 1px 0 var(--color-glass-edge);
  text-align: center;
  color: var(--color-fog-veil); font-size: var(--text-caption);
}

/* ============================ the mark ============================ */
/* Remix Icon game-2, filled with the ice highlight gradient and given a pulse.
 *
 * The eyes are holes punched by an SVG mask rather than shapes painted over the
 * body, so the mark keeps working on any backdrop - the toolbar's near-black
 * glass, the flat page, and the modal all show through them identically.
 *
 * It idles: a two-frame march, borrowed from the arcade sprites the glyph is
 * quoting, plus a blink every six seconds or so. Both are a fraction of a pixel
 * at this size. The point is that the logo is never quite still, not that anyone
 * catches it moving.
 */
.mark { display: inline-block; line-height: 0; }
.mark svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* Bottom stop then top: 0deg in CSS runs upward, and the SVG gradient is set up
   to match, so both read from the same two tokens. */
.mark-stop-0 { stop-color: var(--color-ice-highlight); }
.mark-stop-1 { stop-color: var(--color-glacier); }

.mark-body {
  transform-box: view-box; transform-origin: 50% 50%;
  animation: mark-march 1.4s linear infinite;
}
.mark-eye {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: mark-blink 6.2s var(--ease-in-out) infinite;
}
.mark-eye-r { animation-delay: 60ms; }

/* Hovering the illustration wakes it: the dot goes green, the state decrypts,
   and the mark blinks twice. Same event, three tells. */
.product:hover .mark-eye, .product:focus-visible .mark-eye {
  animation-name: mark-wake; animation-duration: 1s;
}

.brand-mark { width: 20px; height: 20px; }


/* ============================ animation ============================ */
/* Two frames, held flat and cut between, exactly like the sprites the glyph is
   quoting. An eased tween would read as floating; a hard cut reads as a step. */
@keyframes mark-march {
  0%, 49.99%  { transform: translate(-0.35px, 0); }
  50%, 100%   { transform: translate(0.35px, -0.5px); }
}
/* Nine tenths of the cycle is the eyes simply being open. */
@keyframes mark-blink {
  0%, 92%, 100% { transform: scaleY(1); }
  94.5%         { transform: scaleY(0.1); }
  97%           { transform: scaleY(1); }
}
/* The same blink, twice and up front: what the mark does when you touch it. */
@keyframes mark-wake {
  0%, 100%    { transform: scaleY(1); }
  8%          { transform: scaleY(0.1); }
  18%, 30%    { transform: scaleY(1); }
  40%         { transform: scaleY(0.1); }
  52%         { transform: scaleY(1); }
}
/* The bar's mark arriving: a little past full size, then settling. */
@keyframes mark-poof {
  0%   { opacity: 0; transform: scale(0.3); }
  55%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: scale(1); }
}
/* The ring it arrives in, expanding out and gone. */
@keyframes mark-puff {
  0%   { opacity: 0.9; transform: scale(0.4); }
  100% { opacity: 0; transform: scale(1.9); }
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes riseIn { from { opacity: 0; transform: translateY(12px) scale(0.99); } to { opacity: 1; transform: none; } }
@keyframes settle {
  from { opacity: 0; transform: rotate(-90deg) scale(0.8); }
  to { opacity: 1; transform: none; }
}

/* ============================ responsive ============================ */
/* More room for the illustration once the viewport can spare it. */
@media (min-width: 1100px) and (min-height: 820px) {
  :root { --product-h: 470px; }
  .product { width: min(700px, 100%); }
  .product-body { padding: var(--spacing-56) var(--spacing-48); max-height: 520px; }
  .product-keys { gap: var(--spacing-20); margin-top: var(--spacing-40); }
  .product-title { font-size: var(--text-heading); line-height: var(--leading-heading); }
  .product-keys li { font-size: var(--text-body); }
  .combo { min-width: 132px; }
}

/* Below this there is no empty half to fly into, so the copy goes back to one
   centred column and the mark tours the space above it instead - still
   alternating, so the route keeps its shape. */
@media (max-width: 900px) {
  :root { --section-gap: 100px; }
  /* Two across, and the wide tile spans both. Below this the bento stacks. */
  .bento { grid-template-columns: repeat(2, 1fr); }
  .tile, .tile-wide, .tile-tall { grid-column: span 2; grid-row: auto; }
  .tile-art, .tile-tall .tile-art { min-height: 150px; }
}

@media (max-width: 620px) {
  :root { --section-gap: 80px; }
  :root { --product-h: 380px; }
  .hero { padding-top: var(--spacing-40); }
  .section-title { font-size: var(--text-heading); line-height: var(--leading-heading); }
  /* Two questions stack rather than crowd: a select and a button side by
     side at this width leaves neither readable. */
  .access-row { flex-direction: column; }
  .access-go { width: 100%; }
  /* The band shrinks and the side rules pull in; everything else already
     stacks, which is what the reference does on mobile too. */
  .footer { margin-top: var(--spacing-56); }
  .footer-rule { margin-bottom: var(--spacing-32); }
  .footer-band { height: 150px; }
  .footer-band::before { background-size: 68px 68px; }
  .footer-mark { width: 52px; height: 52px; }
  .footer-main { padding: var(--spacing-40) var(--spacing-32); gap: var(--spacing-24); }
  .footer-main::before { left: var(--spacing-12); }
  .footer-main::after { right: var(--spacing-12); }
  .footer-statement { max-width: 16ch; }
  .product-dock { top: 10px; }
  :root { --toolbar-clear: 84px; --eyebrow-overshoot: 6px; }
  .walk { margin-top: var(--spacing-56); }
  .bento { grid-template-columns: 1fr; }
  .tile, .tile-wide, .tile-tall { grid-column: span 1; }
  .combo { min-width: 100px; }
  .combo { min-width: 96px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* Scroll-linked travel across the whole viewport is the one thing on this page
     that can genuinely make someone ill, so the mark stays in the bar and its
     route is never drawn. The copy still swaps - that part carries the meaning,
     and it swaps without movement. */
  .product.mark-here .pb-mark { opacity: 1; transform: none; }
}

/* ============================================================================
 * Promoted from the draft page. Everything below arrived through bento.html,
 * which was a full copy of this page used as a sandbox: the hero's spacing, the
 * hand-off in the dock's reserved band, the section header, the cursor
 * spotlight, the dock's lift, the glow button, and the two card fixes.
 *
 * MOTION. Every animation below follows design.md §4f: it has a job, it is
 * under 1.5s, and it stops for prefers-reduced-motion.
 * ==========================================================================*/

/* ======================= the hero's last gap ======================= */
/* The hero centres its content in a band sized to leave room for the dock. The
 * consequence is that the space between the lede and the dock is not a spacing
 * decision at all: it is half of whatever was left over, so it grows with the
 * viewport. Measured at 1440x900 it was 71px against an internal rhythm of
 * 32/40/40, and at a taller window it is larger still.
 *
 * Anchoring the content to the bottom of the band makes that gap exactly
 * --dock-gap at every height, and sends the leftover to the top, where it reads
 * as breathing room under the edge of the window rather than as a hole. The cap
 * stops the top slack running away on a very tall display.
 *
 * --dock-gap goes to 56: the next step up the scale from the 40 inside the
 * hero, so the dock reads as a different object rather than as the next line.
 */


/* ======================= section headers ======================= */
/* .eyebrow centres itself with align-self, so the header has to be a flex
   column for it to sit right. */
.sec-head {
  display: flex; flex-direction: column;
  max-width: var(--page-max-width); margin: 0 auto var(--spacing-48);
  padding: 0 var(--spacing-24);
}
/* The features section gains one. It sits outside .walk rather than inside it,
   because initScroll maps the mark's tour onto that section's own height and a
   header within it would start the tour while the heading was still on screen.
   The section's top margin moves up to the header with it.
 *
 * It fades in with the bar, like the hand-off above it. Until the dock has
 * collapsed there is nothing under the expanded panel but space, which is what
 * the run between the hero and the first tile should read as: room, not a
 * heading arriving early. */
.walk-head {
  margin-top: var(--section-gap);
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-in-out);
}
.is-docked .walk-head { opacity: 1; }


/* ======================= the hand-off ======================= */
/* The dock reserves the expanded panel's height for the life of the page, so
 * that collapsing cannot shorten the document, move the scroll position,
 * un-stick the bar and set it oscillating. Collapsed, the bar is 44px of a 460
 * reserve, and the rest of that band was empty.
 *
 * This fills it with the two things that are actually happening at that exact
 * scroll position: the shortcut list the panel just folded away, and the head
 * of the route the mark leaves along. It belongs to the band rather than to the
 * viewport, so it appears as the bar pins and scrolls off with the band.
 *
 * The negative margin is a fixed number and does not vary with the dock's
 * state, so it cannot reintroduce the oscillation the reserve prevents.
 */
.hand-off {
  position: relative; z-index: 1;
  margin: calc(var(--product-h) * -1 + 84px) auto 0;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--spacing-20);
  /* The reserved band sits over the hero at rest; nothing in here should ever
     be in the way of it. */
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-in-out);
}
/* Only once the bar has pinned. Before that the panel is open and saying the
   same three things thirty pixels above. */
.is-docked .hand-off { opacity: 1; }

/* The first leg of the mark's route, drawn as the line it is about to take. */
.ho-line {
  position: relative;
  width: 1px; height: 34px;
  background: linear-gradient(to bottom, var(--color-glass-edge), transparent);
}
.ho-token {
  position: absolute; left: -1px; top: 0;
  width: 3px; height: 3px;
  border-radius: var(--radius-iconcontainers);
  background: var(--color-blueprint-blue);
  opacity: 0;
}
.is-docked .ho-token { animation: ho-fall 1400ms var(--ease-in-out) infinite; }
@keyframes ho-fall {
  0%   { transform: translateY(0); opacity: 0; }
  25%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translateY(34px); opacity: 0; }
}

.ho-keys {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--spacing-8) var(--spacing-16);
}
.ho-key {
  display: inline-flex; align-items: center; gap: 5px;
  padding: var(--spacing-4) var(--spacing-12);
  border-radius: var(--radius-buttons);
  background: var(--surface-frosted-glass);
  box-shadow: var(--shadow-subtle);
  color: var(--color-fog-veil);
  font-size: var(--text-caption); line-height: var(--leading-caption);
  white-space: nowrap;
}
.ho-key kbd { height: 18px; min-width: 18px; font-size: 10px; }
.ho-key > span { margin-left: 3px; }
.ho-note {
  color: var(--color-fog-veil);
  font-family: var(--font-dotdigital);
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
}

/* ======================= cursor spotlight ======================= */
/* A soft light under the pointer, inside a card and nowhere else. It is over the
   content rather than behind it on purpose: the job is to illuminate what is
   being read, not to wash the card. .tile::after is already the lit hairline, so
   this takes ::before.
 *
 * --mx / --my are written by initSpotlight in main.js, which only runs on this
 * page and only while a card is actually hovered. */
.tile::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(
    200px circle at var(--mx, 50%) var(--my, 50%),
    rgba(186, 215, 247, 0.09),
    transparent 68%
  );
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-in-out);
  pointer-events: none;
}
.tile.is-spotlit::before { opacity: 1; }

/* ======================= the dock's lift ======================= */
/* Expanded only. Collapsed it is the navigation, pinned to the top of the
   viewport, and navigation that rises when you point at it is a toy.
 *
 * translateY and shadow, no scale. Scale was tried and dropped for two reasons:
 * it blurs the type inside a 700px panel for the length of the tween, and
 * syncReserve measures this element's height to size the reserved band, which a
 * scale would inflate by 2% at exactly the wrong moment. */

.product:not(.as-toolbar):hover,
.product:not(.as-toolbar):focus-visible {
  transform: translateY(-6px);
  box-shadow: var(--elevation-modal), 0 26px 46px rgba(4, 5, 12, 0.55);
}

/* ======================= the glow button ======================= */
/* Rebuilt against the reference rather than approximated from it. What was
 * missing the first time: the coloured drop shadow under the button, and the
 * geometry of the shine, which is a disc as wide as the button, lifted well
 * above it and rotating. The halo is not a ring of even thickness. The shine
 * layer is scaled 1.02/1.005 and rotated a third of a degree, so what escapes
 * around the face is uneven and moves, and that unevenness is the effect.
 *
 * One deliberate change from the reference: the radius is this system's
 * --radius-buttons rather than the reference's 11px. Radius families never mix
 * here, and an 11px button beside a 999px one is the kind of thing this design
 * system exists to prevent. Everything else is the reference's own, including
 * the 2s rotation, the 10/28 padding, the 124px floor, and the 32px spot under
 * a 20px blur.
 *
 * NOTHING ON THIS PAGE CURRENTLY WEARS IT. The referral section that did has
 * been removed, and the navigation's chip deliberately does not: at 28px inside
 * a bar pinned to the top of the screen for the length of the page, an animated
 * edge is a flicker in the corner of your eye. Kept, ready, for the next control
 * that commits to something. Add `glow-button` to the element and wrap its label
 * in `<span class="cta-face">`; initGlow does the rest.
 */
.glow-button {
  --btn-face: #05060f;
  --btn-shadow: rgba(120, 168, 232, 0.22);
  --btn-shine-l: rgba(216, 236, 248, 0.5);
  --btn-shine-r: rgba(152, 192, 239, 0.65);

  position: relative; z-index: 1;
  appearance: none; border: none; outline: none;
  margin: 0; padding: 0;
  background: none;
  border-radius: var(--radius-buttons);
  box-shadow: 0 8px 20px var(--btn-shadow);
  font-family: inherit;
  font-size: var(--text-body-sm); font-weight: var(--font-weight-medium);
  line-height: 24px;
  color: var(--color-ice-highlight);
  cursor: pointer;
}
.cta-gradient {
  position: absolute; inset: 0;
  border-radius: inherit;
  overflow: hidden;
  /* Safari will not clip a child to a rounded corner without a mask. */
  -webkit-mask-image: -webkit-radial-gradient(white, black);
  /* The reference's own numbers. Barely larger than the button, turned barely
     at all, and that is what makes the escaping light uneven. */
  transform: scaleY(1.02) scaleX(1.005) rotate(-0.35deg);
  pointer-events: none;
}
.cta-gradient::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0;
  padding-bottom: 100%;
  border-radius: 50%;
  background: linear-gradient(90deg, var(--btn-shine-l), var(--btn-shine-r));
  transform: scale(1.05) translateY(-44px) rotate(0deg) translateZ(0);
  animation: cta-spin 2s linear infinite;
}
@keyframes cta-spin {
  to { transform: scale(1.05) translateY(-44px) rotate(360deg) translateZ(0); }
}

.cta-face {
  position: relative; z-index: 1;
  display: block; width: fit-content; min-width: 124px;
  padding: 10px 28px;
  border-radius: inherit;
  background-color: var(--btn-face);
  overflow: hidden;
  -webkit-mask-image: -webkit-radial-gradient(white, black);
  text-align: center;
}
/* The spot. Follows the pointer through --pointer-x/y and shifts along the ice
   ramp with it, so the left of the button glows a different ice from the right.
   Both are set by initGlow. */
.cta-face::before {
  content: ''; position: absolute; left: -16px; top: -16px;
  width: 32px; height: 32px; border-radius: 50%;
  transform: translate(var(--pointer-x, 0px), var(--pointer-y, 0px)) translateZ(0);
  background-color: var(--cta-glow, transparent);
  opacity: var(--cta-glow-opacity, 0);
  transition: opacity var(--cta-glow-duration, 0.5s);
  filter: blur(20px);
  pointer-events: none;
}
.glow-button:hover { --cta-glow-opacity: 1; --cta-glow-duration: 0.25s; }

/* ======================= the hold card ======================= */
/* The card's copy is left-aligned and stays that way. Its illustration is not
   copy: a crop marquee sitting off to one side of the frame it is cropping, and
   a confirmation chip hard against the left edge, both read as alignment
   mistakes rather than as composition. */



/* ======================= the recall card's caret ======================= */
/* A search field that has been typed into still has a cursor in it, and it sits
   against the last letter rather than at the far end of the field.
 *
 * .mini-query reserves 26ch in the shared stylesheet so that typing cannot
 * widen the field it is being typed into. That reservation moves out to a
 * wrapper: the width is still held, and the caret now follows the text.
 *
 * At rest it blinks. While the query is being typed the shared .is-writing
 * caret takes over, which is the same 2px mark on the same 900ms clock, so the
 * hand-off is invisible and the card ends where it started. */
.mini-typed { display: inline-block; min-width: 26ch; }

.mini-caret {
  display: inline-block; vertical-align: text-bottom;
  width: 2px; height: 1em; margin-left: 1px;
  background: var(--color-blueprint-blue);
  animation: caret 900ms steps(2, start) infinite;
}
/* Two carets at once is one too many. */
.mini-query.is-writing + .mini-caret { display: none; }

/* ======================= the footer's gravity ======================= */
/* Marks fall from the pointer, bounce off the floor of the box, and go. Clipped
   to the box: the effect is a thing that happens in the footer, not a thing
   that happens on the page. Built in initGravity. */

.grav {
  position: absolute; z-index: 0;
  width: 46px; height: 46px;
  color: var(--color-glacier);
  pointer-events: none;
  opacity: 0.18;
}
/* Sized explicitly rather than left to the viewBox: an inline <svg> with no
   width or height is auto-sized, and what it resolves to depends on the
   parent's layout in ways that are not worth depending on. */
.grav svg { display: block; width: 100%; height: 100%; }
.footer-main > :not(.grav) { position: relative; z-index: 1; }

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

@media (max-width: 620px) {
  .sec-head { padding: 0 var(--spacing-16); margin-bottom: var(--spacing-32); }
  /* One chord a line at this width, and the caption goes: three rows of chips
     plus a line of small caps is more than the gap it is filling. */
  .ho-keys { flex-direction: column; align-items: center; }
  .ho-note { display: none; }
  .hand-off { margin-top: calc(var(--product-h) * -1 + 72px); gap: var(--spacing-16); }
  /* No pointer, no spotlight and no falling marks. Both are hover effects and
     neither has a touch equivalent worth inventing. */
  .tile::before { display: none; }
}

/* Everything in this file that moves, stops. The shared stylesheet already kills
   animation and transition globally under this query; these are the two states
   that also need standing down, because they are applied by script and would
   otherwise leave a lit spotlight or a lifted dock frozen in place. */
@media (prefers-reduced-motion: reduce) {
  .tile.is-spotlit::before { opacity: 0; }
  .product:not(.as-toolbar):hover,
  .product:not(.as-toolbar):focus-visible { transform: none; }
}
