/* =========================================================================
   VILO — THE SITE

   The app's design system, brought outdoors. The token block below is lifted
   from src/renderer/src/styles/tokens.css wherever it still applies — the
   ramp, the light, the shadows, the curve — because a site that invents its
   own greys is a site for a different product.

   Two rules carry over unchanged:

   1. There is no colour. Brightness is the only axis, and it means
      proximity — of a deadline, or of your attention. Near-white is reserved.
   2. Everything that appears, rises: up, in, and into focus. The blur is not
      decoration; it is what makes motion read as arrival.

   And one rule is this site's own, because a page is not a window: **there is
   one background.** No bands, no tinted sections, no cards. A landing page
   that fences every idea inside its own grey rectangle is a landing page made
   of boxes, and boxes are what make a page look assembled rather than
   designed. Sections are separated by space, and by nothing else. The only
   things allowed to look like objects are the ones that are objects: the
   floating header, the buttons, and the application itself.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Tokens
   ------------------------------------------------------------------------- */

:root {
  /* --- Neutral ramp. Very slightly cool, as in the app. ------------------ */
  --g-1000: #08080a;
  --g-950: #0d0d10;
  --g-900: #121215;
  --g-850: #17171b;
  --g-800: #1c1c21;
  --g-750: #222228;
  --g-700: #2a2a31;
  --g-650: #33333b;
  --g-600: #3f3f48;
  --g-500: #55555f;
  --g-400: #74747f;
  --g-300: #9a9aa4;
  --g-200: #c2c2ca;
  --g-100: #e2e2e7;
  --g-050: #f2f2f5;

  /* --- Text. Four levels, and that is the entire hierarchy. --------------
     The bottom two are a step brighter than the app's. This is the one place
     the site knowingly diverges, and the reason is the medium: the app's
     #74757f lands at 4.24:1 on this background and #4d4e57 at 2.6:1, both
     short of what a public page has to clear. */
  --text: #ecedf0;
  --text-2: #a6a7b0;
  --text-3: #7d7e88;
  --text-4: #5f606a;
  --hot: #ffffff;

  /* --- Status. The only colours, and only ever for state. --------------- */
  --ok: #5fd39a;
  --ok-soft: rgb(95 211 154 / 13%);
  --ok-line: rgb(95 211 154 / 34%);

  /* --- Lines. White at low alpha, never a solid grey. -------------------- */
  --line: rgb(255 255 255 / 6%);
  --line-2: rgb(255 255 255 / 11%);
  --line-3: rgb(255 255 255 / 18%);
  --line-4: rgb(255 255 255 / 28%);

  /* --- Surfaces --------------------------------------------------------- */
  --page-bg: var(--g-950);
  --surface: rgb(255 255 255 / 3.5%);
  --surface-2: rgb(255 255 255 / 5.5%);
  --surface-3: rgb(255 255 255 / 8%);
  --elevated: #16161a;

  /* --- Glass. The header, and nothing else on the home page. ------------- */
  --glass-blur: saturate(180%) blur(22px);
  --glass-edge:
    inset 0 1px 0 rgb(255 255 255 / 11%),
    inset 0 -1px 0 rgb(255 255 255 / 3.5%);

  /* --- The light -------------------------------------------------------- */
  --lit: inset 0 1px 0 rgb(255 255 255 / 7%);
  --lit-strong: inset 0 1px 0 rgb(255 255 255 / 14%);
  --sunk: inset 0 1px 2px rgb(0 0 0 / 40%);

  /* --- Shadows. Tight and dark; a soft grey shadow on dark UI is mud. ---- */
  --shadow-1: 0 1px 2px rgb(0 0 0 / 40%);
  --shadow-2: 0 4px 16px -4px rgb(0 0 0 / 55%);
  --shadow-3: 0 16px 48px -12px rgb(0 0 0 / 70%);
  --shadow-4: 0 32px 80px -16px rgb(0 0 0 / 80%);
  --halo: 0 0 40px -8px rgb(255 255 255 / 14%);

  /* --- Radii. Generous, because the mark is drawn with round joins. ------ */
  --r-xs: 6px;
  --r-sm: 9px;
  --r-md: 13px;
  --r-lg: 18px;
  --r-xl: 24px;
  --r-2xl: 32px;
  --r-full: 999px;

  /* --- Type ------------------------------------------------------------- */
  --font-display: 'Archivo', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, -apple-system, sans-serif;

  /* Smaller than they were. The first screen is meant to be read in one
     glance and then scrolled past, not to fill a wall. */
  --fs-hero: clamp(31px, 4.1vw, 55px);
  --fs-display: clamp(25px, 2.9vw, 36px);
  --fs-h2: clamp(22px, 2.9vw, 32px);
  --fs-h3: 16px;
  --fs-lead: clamp(15px, 1.2vw, 17px);
  --fs-body: 15px;
  --fs-sm: 14px;
  --fs-xs: 12.5px;
  --fs-label: 11px;

  /* --- Space. 4px base, as in the app. ---------------------------------- */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;
  --s-32: 128px;

  /* --- Motion. One curve does almost all the work. ---------------------- */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --t-1: 120ms;
  --t-2: 200ms;
  --t-3: 320ms;
  --t-4: 520ms;
  --t-5: 800ms;

  /* --- Layout ----------------------------------------------------------- */
  --nav-h: 72px;
  --page: 1240px;
  --prose: 720px;
  --gutter: clamp(20px, 5vw, 40px);
}

/* -------------------------------------------------------------------------
   Reset and base
   ------------------------------------------------------------------------- */

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

* {
  margin: 0;
  padding: 0;
}

html {
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  scroll-behavior: smooth;
  /* The header floats, so anchors have to stop below it. */
  scroll-padding-top: calc(var(--nav-h) + 32px);
  background: var(--page-bg);
}

body {
  min-height: 100%;
  background: var(--page-bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.62;
  letter-spacing: -0.006em;
  overflow-x: hidden;
}

img,
svg,
canvas,
video {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

::selection {
  background: rgb(255 255 255 / 16%);
}

:focus-visible {
  outline: 2px solid var(--line-4);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

/* Available to a screen reader, and to nothing else. Used by the rotating
   line, which is otherwise announced one letter at a time. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: fixed;
  top: -100px;
  left: var(--gutter);
  z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: var(--elevated);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  transition: top var(--t-2) var(--ease);
}

.skip:focus {
  top: var(--s-4);
}

/* -------------------------------------------------------------------------
   Layout primitives
   ------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  position: relative;
  padding-block: clamp(64px, 9vw, 112px);
}

/* The only divider on the site. One hairline, and only where a change of
   subject genuinely needs marking. */
.rule {
  border-top: 1px solid var(--line);
}

.head {
  max-width: 640px;
  margin-bottom: clamp(32px, 4vw, 48px);
}

.head .display {
  margin-bottom: var(--s-4);
}

/* -------------------------------------------------------------------------
   Type
   ------------------------------------------------------------------------- */

.display {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 750;
  font-variation-settings: 'wdth' 94;
  line-height: 1.08;
  letter-spacing: -0.028em;
  text-wrap: balance;
}

.display.xl {
  font-size: var(--fs-hero);
  font-weight: 800;
  letter-spacing: -0.038em;
  font-variation-settings: 'wdth' 90;
}

h3,
.h3 {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 700;
  font-variation-settings: 'wdth' 96;
  letter-spacing: -0.014em;
  line-height: 1.3;
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.58;
  color: var(--text-2);
  text-wrap: pretty;
}

/* A small label above a heading. It had a dot in front of it once; the dot
   said nothing, and a decoration that says nothing is the thing that makes a
   page look generated. */
.eyebrow {
  display: block;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: var(--s-4);
}

.muted {
  color: var(--text-3);
}

.small {
  font-size: var(--fs-sm);
}

.tiny {
  font-size: var(--fs-xs);
}

/* Numbers only ever appear in Inter with tabular figures — the same call the
   app makes, and the reason there is no monospace bundled here either. */
.num {
  font-variant-numeric: tabular-nums;
}

/* There is no monospace face on this site, for the same reason the app
   dropped its own: the only things ever set in one are key names and file
   paths, and at these sizes a mono face reads as a pixel font next to Inter.
   A key gets a border and tighter tracking instead. */
code {
  font-family: inherit;
  font-size: 0.92em;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 1px 6px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-xs);
  background: var(--surface);
  color: var(--text-2);
  white-space: nowrap;
}

.link {
  color: var(--text-2);
  border-bottom: 1px solid var(--line-3);
  transition:
    color var(--t-2) var(--ease),
    border-color var(--t-2) var(--ease);
}

.link:hover {
  color: var(--text);
  border-color: var(--line-4);
}

/* -------------------------------------------------------------------------
   The line that turns over

   Both lines share one grid cell, so the incoming sentence condenses out of
   blur exactly where the outgoing one is dissolving. `--d` is per letter and
   is written by site.js: distance from the middle of the line, in
   milliseconds, so the ends move last on the way in and first on the way out.
   ------------------------------------------------------------------------- */

.swap {
  display: grid;
  place-items: center;
  text-align: center;
}

.swap-line {
  grid-area: 1 / 1;
}

/* A word is one unbreakable block. Without it the per-letter inline-blocks
   give the browser a break opportunity between every pair of letters, and the
   heading wraps in the middle of a word. */
.swap-word {
  display: inline-block;
  white-space: pre;
}

.swap-letter {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  transform: translateY(16px) scale(0.94);
  filter: blur(12px);
}

.swap-line.is-in .swap-letter {
  opacity: 1;
  transform: none;
  filter: blur(0);
  transition:
    opacity 620ms var(--ease) var(--d, 0ms),
    transform 620ms var(--ease) var(--d, 0ms),
    filter 620ms var(--ease) var(--d, 0ms);
}

.swap-line.is-out .swap-letter {
  opacity: 0;
  transform: translateY(-14px) scale(1.05);
  filter: blur(12px);
  transition:
    opacity 620ms var(--ease) var(--d, 0ms),
    transform 620ms var(--ease) var(--d, 0ms),
    filter 620ms var(--ease) var(--d, 0ms);
}

/* -------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------- */

.btn {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  height: 40px;
  padding-inline: var(--s-5);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--surface-2);
  box-shadow: var(--lit), var(--shadow-1);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: 550;
  letter-spacing: -0.008em;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
  transition:
    transform var(--t-2) var(--ease),
    border-color var(--t-2) var(--ease),
    background var(--t-2) var(--ease),
    box-shadow var(--t-3) var(--ease);
}

.btn::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: radial-gradient(
    160px circle at var(--px, 50%) var(--py, 50%),
    rgb(255 255 255 / 16%),
    transparent 62%
  );
  opacity: 0;
  transition: opacity var(--t-3) var(--ease);
}

.btn:hover {
  border-color: var(--line-3);
  background: var(--surface-3);
  transform: translateY(-1px);
  box-shadow: var(--lit), var(--shadow-2);
}

.btn:hover::after {
  opacity: 1;
}

.btn:active {
  transform: translateY(0);
  transition-duration: var(--t-1);
}

.btn svg {
  width: 15px;
  height: 15px;
  flex: none;
}

/* The size, on the button. A download button that does not say how big is a
   download button people hesitate over. */
.btn small {
  font-weight: 450;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
}

/* The one filled control on the page. White, because white is what the app
   reserves for "this is the thing to do". */
.btn.primary {
  background: linear-gradient(180deg, var(--g-050), var(--g-200));
  border-color: transparent;
  color: var(--g-950);
  box-shadow: var(--lit-strong), var(--shadow-2), var(--halo);
}

.btn.primary::after {
  background: radial-gradient(
    160px circle at var(--px, 50%) var(--py, 50%),
    rgb(255 255 255 / 55%),
    transparent 60%
  );
}

.btn.primary:hover {
  background: linear-gradient(180deg, #ffffff, var(--g-100));
  box-shadow: var(--lit-strong), var(--shadow-3), 0 0 56px -8px rgb(255 255 255 / 22%);
}

.btn.quiet {
  background: transparent;
  border-color: var(--line);
  box-shadow: none;
  color: var(--text-2);
}

.btn.quiet:hover {
  background: var(--surface);
  border-color: var(--line-2);
  color: var(--text);
  box-shadow: none;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

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

   Full width and weightless at the top of the page; once the hero is behind
   it, it contracts into a floating pill with rounded sides. Contracting is
   the point — it is the same object the whole time, which is why the width,
   the radius and the glass are all transitioned rather than swapped.
   ------------------------------------------------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 90;
  padding: var(--s-4) var(--gutter) 0;
  pointer-events: none;
}

.nav-shell {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--s-4);
  width: 100%;
  max-width: var(--page);
  height: 52px;
  margin-inline: auto;
  padding: 0 var(--s-2) 0 var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--r-full);
  background: transparent;
  transition:
    max-width var(--t-5) var(--ease),
    height var(--t-4) var(--ease),
    background var(--t-4) var(--ease),
    border-color var(--t-4) var(--ease),
    box-shadow var(--t-4) var(--ease),
    backdrop-filter var(--t-4) var(--ease);
}

.nav.is-stuck .nav-shell {
  max-width: 700px;
  height: 48px;
  background: rgb(16 16 20 / 62%);
  border-color: var(--line-2);
  box-shadow: var(--glass-edge), var(--shadow-3);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  font-variation-settings: 'wdth' 96;
  letter-spacing: -0.02em;
  margin-right: auto;
}

.nav-brand img {
  width: 24px;
  height: 24px;
  border-radius: 6px;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

.nav-links a {
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-3);
  transition:
    color var(--t-2) var(--ease),
    background var(--t-2) var(--ease);
}

.nav-links a:hover,
.nav-links a[aria-current='page'] {
  color: var(--text);
  background: var(--surface-2);
}

.nav-burger {
  display: none;
  width: 38px;
  height: 38px;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-full);
  color: var(--text-2);
}

.nav-burger:hover {
  background: var(--surface-2);
  color: var(--text);
}

.nav-burger svg {
  width: 19px;
  height: 19px;
}

/* The small-screen sheet. It drops out of the pill rather than covering the
   page, so the header never stops being where you are. */
.nav-drawer {
  pointer-events: auto;
  display: none;
  flex-direction: column;
  gap: var(--s-1);
  width: 100%;
  max-width: var(--page);
  margin: var(--s-2) auto 0;
  padding: var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  background: rgb(16 16 20 / 78%);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-edge), var(--shadow-3);
  opacity: 0;
  transform: translateY(-8px);
  transition:
    opacity var(--t-3) var(--ease),
    transform var(--t-3) var(--ease);
}

.nav-drawer a {
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.nav-drawer a:hover {
  background: var(--surface-2);
  color: var(--text);
}

.nav.is-open .nav-drawer {
  opacity: 1;
  transform: none;
}

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

   Small on purpose. Everything above the fold is the icon, one sentence, one
   line of explanation and three buttons — which is all a first screen has to
   do. The glow behind it is the only thing on the page that is not flat, and
   it is light rather than a surface: no edge, no corner, nothing to read as
   a box.
   ------------------------------------------------------------------------- */

.hero {
  position: relative;
  /* One screen, and only this. Whatever comes next starts below the fold on
     purpose — the first thing the site says should not have to share the
     view with the second. */
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding-block: clamp(112px, 13vw, 152px) clamp(56px, 7vw, 88px);
  text-align: center;
  overflow: hidden;
}

/* A drifting field of light with a brighter one under the pointer, drawn
   frame by frame in site.js. It is the only "image" on the page. */
.hero-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  opacity: 0;
  transition: opacity 1.2s var(--ease);

  /* Two masks, multiplied. The ellipse keeps the cloud away from the left and
     right edges so it reads as a light source rather than as wallpaper; the
     linear one clears the top, because the header floats there and text over
     a moving dot field is text you have to work to read. */
  mask-image:
    radial-gradient(84% 64% at 50% 46%, #000 10%, transparent 76%),
    linear-gradient(180deg, transparent 2%, #000 18%, #000 82%, transparent 100%);
  mask-composite: intersect;
  -webkit-mask-image:
    radial-gradient(84% 64% at 50% 46%, #000 10%, transparent 76%),
    linear-gradient(180deg, transparent 2%, #000 18%, #000 82%, transparent 100%);
  -webkit-mask-composite: source-in;
}

/* Bright enough to see it move. Below about a quarter the light following
   the pointer stops registering at all, which is the whole reason it is a
   canvas and not an image. */
.hero-canvas.is-ready {
  opacity: 0.48;
}

.hero-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* The icon, and it is the first thing on the page for a reason: it is what
   you will be looking for in the dock afterwards. It does not react to the
   pointer — see the note at the top of site.js. */
.hero-icon {
  position: relative;
  width: clamp(88px, 9vw, 118px);
  margin-bottom: var(--s-8);
  isolation: isolate;
}

.hero-icon img {
  width: 100%;
  height: auto;
}

/*
 * One slow pass of light across the glass, every eleven seconds.
 *
 * The band is masked by the icon artwork itself, so it can only ever paint
 * where the tile is — no rounded-corner guesswork, and nothing spilling into
 * the transparent margin the render leaves around it. That also decides how
 * it moves: the element has to stay still for a static mask to line up, so
 * the sweep is a travelling `background-position` rather than a transform.
 */
.hero-icon::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    104deg,
    transparent 40%,
    rgb(255 255 255 / 30%) 50%,
    transparent 60%
  );
  background-size: 260% 100%;
  background-position: 190% 0;
  mask-image: url('assets/icon-glass.webp');
  mask-size: 100% 100%;
  -webkit-mask-image: url('assets/icon-glass.webp');
  -webkit-mask-size: 100% 100%;
  mix-blend-mode: overlay;
  animation: sheen 11s var(--ease-in-out) 1.6s infinite;
  pointer-events: none;
}

@keyframes sheen {
  0% {
    background-position: 190% 0;
  }
  26%,
  100% {
    background-position: -90% 0;
  }
}

.hero h1 {
  max-width: 18ch;
  margin-bottom: var(--s-4);
}

.hero .lead {
  max-width: 52ch;
  margin-bottom: var(--s-6);
}

.hero .actions {
  justify-content: center;
  margin-bottom: var(--s-5);
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--s-2) var(--s-3);
  font-size: var(--fs-xs);
  color: var(--text-4);
}

.hero-meta span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
}

.hero-meta span + span::before {
  content: '';
  width: 3px;
  height: 3px;
  border-radius: var(--r-full);
  background: currentColor;
  opacity: 0.6;
}

/* -------------------------------------------------------------------------
   Features

   One per row, alternating which side the artwork sits on, in the order the
   application puts them in its own sidebar. The artwork has no frame, no
   background and no crop: it sits directly on the page, because a picture
   inside a tinted rectangle is a picture with a visible edge, and an edge is
   the thing that says "this is a screenshot" rather than "this is the app".

   Everything inside a piece of artwork is sized in `cqw` against its own
   column, so the same markup is a 620px illustration beside a paragraph here
   and a full-width one on a phone.
   ------------------------------------------------------------------------- */

.feature {
  padding-block: clamp(48px, 7vw, 88px);
}

/* The four features still waiting on artwork. They sit directly under the
   two that have it, so they take the feature rhythm rather than a section's. */
.feature-rest {
  padding-block: clamp(16px, 2vw, 32px) clamp(48px, 7vw, 88px);
}

.feature-rest .head {
  margin-bottom: var(--s-8);
}

.feature-row {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 72px);
  align-items: center;
}

/* Every other one turns around. The artwork stays first in the source so the
   reading order is always picture-then-explanation. */
.feature.flip .feature-media {
  order: 2;
}

.feature-media {
  container-type: inline-size;
  position: relative;
}

/* The light an object of this size would throw. No edges, because a radial
   gradient that reaches transparent does not have one. */
.feature-media::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 118%;
  aspect-ratio: 1.35;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgb(255 255 255 / 7%), transparent 72%);
  pointer-events: none;
}

.feature-kicker {
  display: block;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: var(--s-3);
}

.feature-text h3 {
  font-family: var(--font-display);
  font-size: clamp(22px, 2.4vw, 30px);
  font-weight: 750;
  font-variation-settings: 'wdth' 94;
  letter-spacing: -0.026em;
  line-height: 1.1;
  margin-bottom: var(--s-4);
}

.feature-text p {
  color: var(--text-2);
  text-wrap: pretty;
  margin-bottom: var(--s-4);
}

.feature-text p:last-child {
  margin-bottom: 0;
}

.feature-text .tiny {
  color: var(--text-4);
}

/* The artwork arrives a little later than the words beside it, and from
   slightly further away — it is the heavier object of the two. */
.lift {
  opacity: 0;
  transform: translateY(18px) scale(0.985);
  filter: blur(4px);
  transition:
    opacity 480ms var(--ease),
    transform 560ms var(--ease),
    filter 480ms var(--ease);
  transition-delay: var(--delay, 0ms);
}

.lift.is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* -------------------------------------------------------------------------
   The chat window

   The application rebuilt as markup — the sidebar, the header, the empty
   state, the composer — turned slightly so it reads as an object sitting in
   the page rather than a rectangle pasted onto it.
   ------------------------------------------------------------------------- */

/*
 * The application, sized in design pixels.
 *
 * `--u` is one pixel of the 1180-wide mock, expressed as a fraction of its
 * column — so every number below is the number the interface actually uses,
 * and the whole thing scales with the layout instead of being re-laid-out at
 * each size. It runs a little past the column on both sides, which is what
 * stops it reading as a rectangle dropped into a slot.
 *
 * It is not a `transform: scale()`, and the reason is a trap worth writing
 * down: `scale()` takes a number, `calc(112cqw / 1180)` produces a *length*,
 * and a length there makes the entire transform invalid — so the mock renders
 * at its full 1180px and hangs off the side with no warning anywhere. Laying
 * it out at the right size avoids the question.
 */
.shot-app {
  --u: calc(112cqw / 1180);
  width: 112cqw;
  margin-left: -6cqw;
}

.app {
  display: flex;
  height: calc(700 * var(--u));
  border: 1px solid var(--line-2);
  border-radius: calc(14 * var(--u));
  overflow: hidden;
  background: rgb(13 13 16 / 82%);
  box-shadow: var(--glass-edge), var(--shadow-4);
  /* A real turn rather than a lean.
     
     The old one used a 2600px perspective, which is so far away that the near
     and far edges come out almost the same width and the result reads as a
     squashed rectangle instead of a panel with a side facing you. Bringing
     the vanishing point in to 1200px is what makes the left edge visibly
     nearer than the right, and the origin sits off centre so the panel turns
     about its own far corner. */
  transform-origin: 76% 50%;
  transform: perspective(1200px) rotateY(-19deg) rotateX(5deg) rotateZ(-1deg);
  font-size: calc(14 * var(--u));
  color: var(--text);
}

/* --- sidebar -------------------------------------------------------------- */

.app-side {
  display: flex;
  flex-direction: column;
  width: calc(226 * var(--u));
  flex: none;
  padding: calc(18 * var(--u)) calc(12 * var(--u));
  border-right: 1px solid var(--line);
  background: rgb(255 255 255 / 1.5%);
}

.app-brand {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--u));
  padding: calc(6 * var(--u)) calc(8 * var(--u)) calc(22 * var(--u));
  font-family: var(--font-display);
  font-size: calc(19 * var(--u));
  font-weight: 700;
  letter-spacing: -0.02em;
}

.app-brand svg {
  width: calc(20 * var(--u));
  height: calc(20 * var(--u));
  fill: var(--text);
}

.app-nav {
  display: flex;
  flex-direction: column;
  gap: calc(2 * var(--u));
}

.app-nav a {
  display: flex;
  align-items: center;
  gap: calc(11 * var(--u));
  padding: calc(8 * var(--u)) calc(10 * var(--u));
  border-radius: calc(9 * var(--u));
  font-size: calc(13.5 * var(--u));
  color: var(--text-3);
}

.app-nav a.is-on {
  background: var(--surface-2);
  box-shadow: var(--lit);
  color: var(--text);
}

.app-nav svg {
  width: calc(15 * var(--u));
  height: calc(15 * var(--u));
  flex: none;
}

.app-search {
  display: flex;
  align-items: center;
  gap: calc(9 * var(--u));
  margin-top: auto;
  padding: calc(9 * var(--u)) calc(10 * var(--u));
  border: 1px solid var(--line);
  border-radius: calc(9 * var(--u));
  font-size: calc(12.5 * var(--u));
  color: var(--text-4);
}

.app-search svg {
  width: calc(14 * var(--u));
  height: calc(14 * var(--u));
}

.app-search kbd {
  margin-left: auto;
  padding: calc(1 * var(--u)) calc(5 * var(--u));
  border: 1px solid var(--line-2);
  border-radius: calc(5 * var(--u));
  font: inherit;
  font-size: calc(11 * var(--u));
}

/* --- main ---------------------------------------------------------------- */

.app-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  padding: calc(20 * var(--u)) calc(26 * var(--u)) calc(22 * var(--u));
}

.app-top {
  display: flex;
  align-items: flex-start;
  gap: calc(16 * var(--u));
}

.app-top h4 {
  font-family: var(--font-display);
  font-size: calc(25 * var(--u));
  font-weight: 750;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

.app-top p {
  font-size: calc(12.5 * var(--u));
  color: var(--text-4);
}

.app-hist {
  display: inline-flex;
  align-items: center;
  gap: calc(7 * var(--u));
  margin-left: auto;
  padding: calc(6 * var(--u)) calc(12 * var(--u));
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface);
  font-size: calc(12 * var(--u));
  color: var(--text-2);
}

.app-hist svg {
  width: calc(13 * var(--u));
  height: calc(13 * var(--u));
}

.app-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  text-align: center;
  padding-inline: calc(40 * var(--u));
}

.app-empty h5 {
  font-family: var(--font-display);
  font-size: calc(26 * var(--u));
  font-weight: 750;
  letter-spacing: -0.025em;
  margin-bottom: calc(10 * var(--u));
}

.app-empty p {
  max-width: 46ch;
  font-size: calc(13 * var(--u));
  line-height: 1.55;
  color: var(--text-3);
  margin-bottom: calc(20 * var(--u));
}

.app-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: calc(8 * var(--u));
  max-width: calc(480 * var(--u));
}

.app-chip {
  padding: calc(7 * var(--u)) calc(14 * var(--u));
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--surface);
  box-shadow: var(--lit);
  font-size: calc(12.5 * var(--u));
  color: var(--text-2);
}

.app-composer {
  border: 1px solid var(--line-2);
  border-radius: calc(13 * var(--u));
  background: var(--surface);
  box-shadow: var(--lit);
  padding: calc(14 * var(--u)) calc(14 * var(--u)) calc(10 * var(--u));
}

.app-composer > p {
  font-size: calc(13.5 * var(--u));
  color: var(--text-4);
  margin-bottom: calc(16 * var(--u));
}

.app-composer-row {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--u));
  font-size: calc(12 * var(--u));
  color: var(--text-4);
}

.app-composer-row svg {
  width: calc(14 * var(--u));
  height: calc(14 * var(--u));
}

.app-send {
  display: grid;
  place-items: center;
  width: calc(28 * var(--u));
  height: calc(28 * var(--u));
  margin-left: auto;
  border-radius: calc(8 * var(--u));
  background: var(--surface-3);
  box-shadow: var(--lit);
  color: var(--text-2);
}

/* -------------------------------------------------------------------------
   The voice screen

   The ring is the application's own fragment shader, compiled on the page —
   the same ordered-dithered band, the same three harmonics, the same
   travelling arc. The mark inside is the app's assistant, running the same
   springs and saccades. Nothing here is a picture of the product.

   Laid out on a 720-wide design, the same way the chat window is laid out on
   an 1180-wide one, so both keep their real proportions in whatever column
   they are handed.
   ------------------------------------------------------------------------- */

.shot-voice {
  --v: calc(100cqw / 720);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(30 * var(--v));
  padding-block: calc(16 * var(--v));
}

.shot-orb {
  position: relative;
  width: calc(340 * var(--v));
  aspect-ratio: 1;
}

.orb-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 900ms var(--ease);
}

.orb-canvas.is-ready {
  opacity: 1;
}

.shot-orb .assistant-logo {
  position: absolute;
  inset: 27.5%;
  width: 45%;
  height: 45%;
}

/* --- the dock ------------------------------------------------------------- */

.shot-dock {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(14 * var(--v));
}

.dock-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(12 * var(--v));
}

.dock-hint {
  display: inline-flex;
  align-items: center;
  gap: calc(7 * var(--v));
  font-size: calc(14 * var(--v));
  color: var(--text-3);
}

.dock-hint svg {
  width: calc(14 * var(--v));
  height: calc(14 * var(--v));
}

.dock-hint kbd {
  padding: calc(2 * var(--v)) calc(6 * var(--v));
  border: 1px solid var(--line-2);
  border-radius: calc(5 * var(--v));
  background: var(--surface-2);
  font: inherit;
  font-size: calc(12 * var(--v));
  color: var(--text-2);
}

.dock-btn {
  display: inline-flex;
  align-items: center;
  gap: calc(7 * var(--v));
  padding: calc(9 * var(--v)) calc(15 * var(--v));
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--surface-2);
  box-shadow: var(--lit), var(--shadow-1);
  font-size: calc(14 * var(--v));
  font-weight: 550;
  color: var(--text);
}

.dock-btn svg {
  width: calc(15 * var(--v));
  height: calc(15 * var(--v));
}

.dock-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: calc(9 * var(--v));
}

.dock-chip {
  padding: calc(8 * var(--v)) calc(16 * var(--v));
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface);
  box-shadow: var(--lit);
  font-size: calc(13 * var(--v));
  color: var(--text-2);
}

/* -------------------------------------------------------------------------
   Plain rows

   What used to be six cards in a grid. The information was fine; the
   rectangles around it were the problem. A hairline and a column of space do
   the same separating job and leave the page looking like a page.
   ------------------------------------------------------------------------- */

/* Two columns, fixed. `auto-fit` gave three at this width and left the
   fourth item stranded on a row of its own. */
.rows {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(32px, 5vw, 72px);
}

.row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: var(--s-5);
  align-items: baseline;
  padding-block: var(--s-5);
  border-top: 1px solid var(--line);
}

.row dt {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  font-variation-settings: 'wdth' 96;
  letter-spacing: -0.014em;
}

.row dd {
  font-size: var(--fs-sm);
  color: var(--text-3);
  text-wrap: pretty;
}

/* -------------------------------------------------------------------------
   Google

   The section Google's verification asks for. It used to be six identical
   rows of scope-string and paragraph, which is the shape of the source file
   and not the shape of the question anyone is actually asking — which is
   "what of mine does it touch". So it is grouped by service now, each with
   what it reads, what it writes, and the exact permissions underneath in
   small type for the people who do want the strings.
   ------------------------------------------------------------------------- */

.services {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(28px, 4vw, 56px);
  padding-top: var(--s-8);
  border-top: 1px solid var(--line);
}

.service h3 {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}

.service h3 svg {
  width: 17px;
  height: 17px;
  color: var(--text-3);
  flex: none;
}

.service > p {
  font-size: var(--fs-sm);
  color: var(--text-3);
  margin-bottom: var(--s-5);
  text-wrap: pretty;
}

.service ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.service li {
  font-size: var(--fs-xs);
  color: var(--text-4);
  line-height: 1.9;
}

.service li code {
  border: 0;
  background: none;
  padding: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-3);
  white-space: normal;
  word-break: break-word;
}

.tag {
  display: inline-block;
  margin-left: var(--s-2);
  padding: 1px var(--s-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xs);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-4);
  white-space: nowrap;
}

.tag.read {
  border-color: var(--ok-line);
  background: var(--ok-soft);
  color: var(--ok);
}

/* Three promises, under a hairline. They are the answer to the question the
   permissions above raise, and they belong on the same screen as it. */
.promises {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--s-6) clamp(28px, 4vw, 56px);
  margin-top: clamp(40px, 5vw, 64px);
  padding-top: var(--s-8);
  border-top: 1px solid var(--line);
}

.promise strong {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  font-variation-settings: 'wdth' 96;
  letter-spacing: -0.014em;
  margin-bottom: var(--s-2);
}

.promise span {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-3);
  text-wrap: pretty;
}

/* -------------------------------------------------------------------------
   Providers

   Vilo ships without a model, so this is the list of what you can point it
   at. Names in a grid with one line each — the shape of a reference, which is
   what it is.
   ------------------------------------------------------------------------- */

.providers {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 clamp(28px, 4vw, 56px);
}

@media (max-width: 900px) {
  .providers {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .providers {
    grid-template-columns: minmax(0, 1fr);
  }
}

.provider {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: var(--s-4);
  align-items: start;
  padding-block: var(--s-5);
  border-top: 1px solid var(--line);
}

.provider svg {
  width: 20px;
  height: 20px;
  margin-top: 1px;
  color: var(--text-3);
}

.provider strong {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  font-variation-settings: 'wdth' 96;
  letter-spacing: -0.014em;
  margin-bottom: 2px;
}

.provider span {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-3);
  text-wrap: pretty;
}

.provider em {
  font-style: normal;
  color: var(--text-4);
}

/* -------------------------------------------------------------------------
   Footer
   ------------------------------------------------------------------------- */

.footer {
  position: relative;
  padding-top: clamp(56px, 7vw, 88px);
  border-top: 1px solid var(--line);
  overflow: hidden;
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-10);
  justify-content: space-between;
  padding-bottom: clamp(40px, 5vw, 64px);
}

.footer-say {
  flex: 1 1 300px;
  max-width: 34ch;
}

.footer-say .display {
  margin-bottom: var(--s-4);
}

.footer-cols {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-10);
}

.footer-col h4 {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: var(--s-4);
}

.footer-col ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.footer-col a {
  font-size: var(--fs-sm);
  color: var(--text-3);
  transition: color var(--t-2) var(--ease);
}

.footer-col a:hover {
  color: var(--text);
}

.footer-base {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-6);
  padding-block: var(--s-6) var(--s-8);
  border-top: 1px solid var(--line);
  font-size: var(--fs-xs);
  color: var(--text-4);
}

/* -------------------------------------------------------------------------
   The mark

   Ported from the app. The sprite is a cupboard: every live copy on the page
   is a <use> pointing into it, because external <use> references were never
   implemented outside Electron's own document. See scripts/inline-mark.mjs.
   ------------------------------------------------------------------------- */

.vilo-sprite {
  display: none;
}

.assistant-logo {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  overflow: visible;
}

.assistant-body,
.assistant-head,
.assistant-gaze,
.assistant-blink {
  transform-box: view-box;
}

.assistant-body {
  transform-origin: 627px 627px;
  transform: scale(var(--a-scale, 1));
}

.assistant-head {
  transform-origin: 627px 627px;
  transform: translate(var(--a-head-x, 0), var(--a-head-y, 0)) rotate(var(--a-head-r, 0deg));
}

.assistant-gaze {
  transform: translate(var(--a-eye-x, 0), var(--a-eye-y, 0));
}

/* Squashing to the centre of the eye is not how an eyelid works, but at this
   scale, on a mark with no lids drawn, it is what reads as a blink. */
.assistant-blink {
  transform-origin: 627px 712px;
  transform: scaleY(var(--a-lid, 1));
}

/* -------------------------------------------------------------------------
   Document pages — /privacy, /terms, /support

   A single prose column with the contents pinned beside it. Nothing here is
   styled to look designed: these are pages people arrive at with a question,
   and the only job is that they can find the paragraph that answers it.
   ------------------------------------------------------------------------- */

.doc-head {
  padding-block: clamp(120px, 15vw, 168px) clamp(32px, 4vw, 48px);
  border-bottom: 1px solid var(--line);
}

.doc-head .display {
  margin-bottom: var(--s-4);
}

.doc-layout {
  display: grid;
  gap: clamp(40px, 6vw, 80px);
  grid-template-columns: 200px minmax(0, 1fr);
  align-items: start;
  padding-block: clamp(48px, 6vw, 72px) clamp(72px, 9vw, 112px);
}

.toc {
  position: sticky;
  top: calc(var(--nav-h) + 24px);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

.toc h4 {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: var(--s-3);
}

.toc a {
  padding: var(--s-2) var(--s-3);
  border-left: 1px solid var(--line-2);
  font-size: var(--fs-xs);
  color: var(--text-3);
  transition:
    color var(--t-2) var(--ease),
    border-color var(--t-2) var(--ease);
}

.toc a:hover,
.toc a.is-here {
  color: var(--text);
  border-color: var(--line-4);
}

.doc {
  max-width: var(--prose);
  font-size: 15.5px;
  color: var(--text-2);
}

.doc h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 750;
  font-variation-settings: 'wdth' 94;
  letter-spacing: -0.024em;
  line-height: 1.15;
  color: var(--text);
  margin-top: clamp(48px, 6vw, 72px);
  margin-bottom: var(--s-5);
  scroll-margin-top: calc(var(--nav-h) + 32px);
}

.doc h2:first-child {
  margin-top: 0;
}

.doc h3 {
  color: var(--text);
  margin-top: var(--s-8);
  margin-bottom: var(--s-3);
}

.doc p,
.doc ul,
.doc ol {
  margin-bottom: var(--s-4);
  text-wrap: pretty;
}

.doc ul,
.doc ol {
  padding-left: var(--s-5);
}

.doc li {
  margin-bottom: var(--s-2);
}

.doc li::marker {
  color: var(--text-4);
}

.doc strong {
  color: var(--text);
  font-weight: 600;
}

.doc a {
  color: var(--text);
  border-bottom: 1px solid var(--line-3);
  transition: border-color var(--t-2) var(--ease);
}

.doc a:hover {
  border-color: var(--line-4);
}

/* The global `code` refuses to wrap, which is right for a key name and wrong
   for `classroom.student-submissions.me.readonly` and for a Windows path. */
.doc code {
  white-space: normal;
  word-break: break-word;
}

/* A note is a paragraph that has been pulled out because someone will need it
   in a hurry — the address to write to, the folder to delete. */
.doc .note {
  padding: var(--s-5) var(--s-6);
  margin-bottom: var(--s-5);
  border: 1px solid var(--line-2);
  border-left: 2px solid var(--line-4);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: var(--fs-sm);
}

.doc .note :last-child {
  margin-bottom: 0;
}

.doc .updated {
  font-size: var(--fs-xs);
  color: var(--text-4);
}

/* Steps: the two procedures Google asks to be written down — disconnecting,
   and deleting. Numbered by CSS so the markup stays an <ol>. */
.doc ol.steps {
  list-style: none;
  padding-left: 0;
  counter-reset: step;
}

/* Direct children only. A nested list inside a step is an ordinary list, and
   without the child combinator it inherits the numbered disc — which both
   mislabels the sub-items and makes the outer list count them. */
.doc ol.steps > li {
  position: relative;
  padding-left: var(--s-10);
  margin-bottom: var(--s-4);
  counter-increment: step;
}

.doc ol.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: -1px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--surface-2);
  box-shadow: var(--lit);
  font-size: var(--fs-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

/* -------------------------------------------------------------------------
   Headings, a word at a time

   Coarser than the hero line, and quicker: 34ms between words and 420ms each,
   so a six-word heading is finished in under six hundred. Any slower and you
   are watching it write itself instead of reading it.
   ------------------------------------------------------------------------- */

.word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.3em);
  filter: blur(5px);
  transition:
    opacity 420ms var(--ease),
    transform 460ms var(--ease),
    filter 420ms var(--ease);
  transition-delay: calc(var(--i, 0) * 34ms + var(--delay, 0ms));
}

.words.is-in .word {
  opacity: 1;
  transform: none;
  filter: none;
}

/* -------------------------------------------------------------------------
   The hero, arriving

   Everything in the first screen comes in on load rather than on scroll,
   because there is no scroll yet. Staged from the icon downwards, one beat
   apart, so the eye is led from the mark to the sentence to the buttons.
   ------------------------------------------------------------------------- */

@keyframes hero-in {
  from {
    opacity: 0;
    transform: translateY(14px);
    filter: blur(5px);
  }
}

.hero-inner > * {
  animation: hero-in 620ms var(--ease) both;
}

.hero-inner > .hero-icon {
  animation-delay: 60ms;
}

/* The line handles itself, letter by letter. It only needs to be visible. */
.hero-inner > h1 {
  animation: none;
}

.hero-inner > .lead {
  animation-delay: 300ms;
}

.hero-inner > .actions {
  animation-delay: 400ms;
}

.hero-inner > .hero-meta {
  animation-delay: 480ms;
}

/* -------------------------------------------------------------------------
   Reveals

   An element arrives from below, out of blur, once — granted on scroll and
   never taken back. Re-hiding things that scroll out is a page that flickers
   at anyone who scrolls up.
   ------------------------------------------------------------------------- */

/*
 * Short, and barely blurred.
 *
 * These used to run for 800ms out of a 6px blur, and at that length a page of
 * them reads as drifting rather than arriving — every block is still settling
 * while you are already reading the next one. Half the duration and half the
 * travel lands each one before your eye gets there, which is what makes a
 * scroll feel crisp instead of soft.
 */
.rise {
  opacity: 0;
  transform: translateY(10px);
  filter: blur(3px);
  transition:
    opacity 380ms var(--ease),
    transform 440ms var(--ease),
    filter 380ms var(--ease);
  transition-delay: var(--delay, 0ms);
}

.rise.is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* -------------------------------------------------------------------------
   Narrower screens
   ------------------------------------------------------------------------- */

@media (max-width: 900px) {
  .doc-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .toc {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--s-2);
  }

  .toc h4 {
    width: 100%;
    margin-bottom: 0;
  }

  .toc a {
    border: 1px solid var(--line-2);
    border-radius: var(--r-full);
    padding: var(--s-2) var(--s-4);
  }

  .scope {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-2);
  }
}

@media (max-width: 760px) {
  :root {
    --nav-h: 64px;
  }

  .nav-links {
    display: none;
  }

  .nav-burger {
    display: inline-flex;
  }

  .nav-drawer {
    display: flex;
  }

  /* The pill has nothing to contract from on a phone — it is already the
     width of the screen — so it simply becomes glass. */
  .nav.is-stuck .nav-shell {
    max-width: 100%;
  }

  .hero .actions {
    width: 100%;
    flex-direction: column;
  }

  .hero .actions .btn {
    width: 100%;
  }

  /* One column, and the artwork goes first in both. */
  .feature-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .feature.flip .feature-media {
    order: 0;
  }

  /* A 1180px application on a phone is unreadable however it is turned, so it
     gives up the turn — which is what costs the most legibility — and takes
     the whole width. */
  .shot-app {
    --u: calc(118cqw / 1180);
    width: 118cqw;
    margin-left: -9cqw;
  }

  .app {
    transform: none;
  }

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

  .row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-1);
  }
}

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

   Everything still arrives; it just arrives already here. The ring draws one
   frame at rest and the line stops turning over.
   ------------------------------------------------------------------------- */

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .rise,
  .lift,
  .word,
  .swap-letter {
    opacity: 1;
    transform: none;
    filter: none;
  }

  .hero-inner > * {
    animation: none;
  }
}
