/* Inter, self-hosted.

   Chosen over the system font for character, and served from our own origin
   rather than from Google's CDN. Analytics already reaches a third party, but
   that is one the visitor can decline; a webfont is not declinable, so putting
   it on someone else's server would mean every visitor's IP reaching Google
   whether they consented or not, on a site whose whole argument is about not
   handing your data around.

   Latin only, three weights, 142KB total. The latin-ext faces were dropped:
   they cover accented characters this site never renders and were the larger
   half of the payload. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/inter-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/inter-500-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/inter-600-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================
   MacGaze · "Instrument"

   The product turns a face into a measurement, so the site is built like a
   measuring device. That is not decoration: this page has to say "not as
   secure as Face ID, it stores your password, a video might fool it", and in
   a glossy aesthetic those read as apologies. Stated as instrument
   specifications, candour becomes the brand instead of the liability.

   Two grounds alternate as CHAPTERS, not as a theme: pale panel to explain,
   deep night for the moment of recognition. Neutrals carry a slight green
   bias throughout so they read as chosen rather than defaulted.

   All type is system type. The only external measurement request is the
   consent-controlled Google tag described in the Privacy Policy.
   ============================================================ */

:root {
  /* Panel: the instrument face */
  --paper: #F0F2EC;
  --paper-2: #E6E9E1;
  --card: #FBFCF9;
  --ink: #12150F;
  --ink-2: #383E37;
  --ink-3: #5A6158;
  --rule: #D3D7CC;
  --rule-2: #C0C5B8;
  --signal: #1B7F38;
  --signal-quiet: rgba(27, 127, 56, 0.10);
  --amber: #8A5A08;
  --amber-quiet: rgba(138, 90, 8, 0.10);

  /* Night: the moment */
  --night: #0A0C0B;
  --night-2: #141816;
  --night-3: #1D2220;
  --night-ink: #E6EAE5;
  --night-ink-2: #ADB5AC;
  --night-rule: #262B27;
  --glow: #30D158;

  --sans: "Inter", -apple-system, BlinkMacSystemFont, system-ui,
          "Segoe UI", sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  --serif: ui-serif, "New York", "Iowan Old Style", Georgia, serif;

  /* Built-in easings are too weak to read as deliberate. These are the
     three the whole page uses, and nothing rolls its own. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-move: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.5, 1);

  --measure: 33rem;
  --gutter: 1.75rem;
  --page: 74rem;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 18px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

/* ---------- structure ---------- */

/* Tightened from 9.5rem. The page was running to seven thousand pixels, and
   generous is not the same as empty: past a point the gaps stop reading as
   composure and start reading as unfinished. */
.chapter { padding: clamp(3.75rem, 8vw, 7rem) 0; position: relative; }
.chapter--night { background: var(--night); color: var(--night-ink); }
.chapter--tight { padding-top: clamp(3rem, 6vw, 5rem); }

/* The opening screen has to be the only thing on it. Letting the next
   chapter's pale ground peek in at the bottom tells the visitor the page is
   a list of sections before they have read a word of the first one. svh
   rather than vh so mobile browser chrome cannot push it into an overflow. */
.chapter--hero {
  /* The sticky nav sits in the flow above this, so a flat 100svh overshoots by
     exactly the nav's height and pushes the scroll cue off the bottom. --nav-h
     is measured in JS, and the fallback keeps this sane before it lands. */
  min-height: calc(100vh - var(--nav-h, 3.6rem));
  min-height: calc(100svh - var(--nav-h, 3.6rem));
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: clamp(4.5rem, 9vw, 7rem);
  padding-bottom: clamp(3.5rem, 7vw, 6rem);
}

/* A quiet mark that there is more below, since a full-height first screen
   removes the usual cue of a section edge poking up. */

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

.stack { display: flex; flex-direction: column; }
.stack > * { margin: 0; }

.gap-xs { gap: 0.5rem; }
.gap-s  { gap: 0.9rem; }
.gap-m  { gap: 1.6rem; }
.gap-l  { gap: 2.6rem; }
.gap-xl { gap: 4rem; }

.row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.85rem; }

/* ---------- analytics consent ---------- */

.consent {
  position: fixed;
  z-index: 20;
  left: 1rem;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.2rem;
  max-width: 56rem;
  margin: 0 auto;
  padding: 1rem 1.1rem;
  border: 1px solid rgba(48, 209, 88, 0.35);
  border-radius: 1rem;
  background: rgba(20, 24, 22, 0.96);
  color: var(--night-ink);
  box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.24);
  font-size: 0.82rem;
  line-height: 1.35;
}

/* The author-level flex display must not override the hidden attribute. */
.consent[hidden] { display: none; }

.consent-copy {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.7rem;
}

.consent-copy span { color: var(--night-ink-2); }
.consent-copy a { color: var(--glow); }
.consent-actions { display: flex; flex-shrink: 0; gap: 0.5rem; }
.consent-actions .btn { padding: 0.55rem 0.8rem; font-size: 0.75rem; }
.consent-actions .btn--ghost {
  color: var(--night-ink);
  border-color: rgba(230, 234, 229, 0.55);
}
.consent-actions .btn--ghost:hover {
  background: rgba(230, 234, 229, 0.12);
  border-color: var(--night-ink);
}

@media (max-width: 42rem) {
  .consent { align-items: stretch; flex-direction: column; }
  .consent-actions { display: grid; grid-template-columns: 1fr 1fr; }
}

/* ---------- type ---------- */

h1, h2, h3 { margin: 0; font-weight: 600; text-wrap: balance; }

/* Wispr-scale confidence, delivered by a grotesque rather than a serif. */
.display {
  /* 5.5rem, not 6. Inter sets wider than SF Pro at the same size, and at 6rem
     "Open the lid." needed 538px in a 537px column: one pixel over, which is
     the difference between two lines and three. Left some room this time. */
  font-size: clamp(2.7rem, 7vw, 5.5rem);
  line-height: 0.94;
  letter-spacing: -0.04em;
}

.display--sm {
  font-size: clamp(2.1rem, 5vw, 3.9rem);
  line-height: 1.0;
  letter-spacing: -0.032em;
}

h3 { font-size: 1.09rem; line-height: 1.35; letter-spacing: -0.012em; }

p { margin: 0; max-width: var(--measure); }

/* The instrument's voice. Promoted well beyond caption duty. This is where
   most of the page's personality lives. */
.label {
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.chapter--night .label { color: var(--night-ink-2); }

.lede {
  font-size: clamp(1.1rem, 1.9vw, 1.32rem);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 30rem;
}
.chapter--night .lede { color: var(--night-ink-2); }

.small { font-size: 1.02rem; line-height: 1.58; color: var(--ink-2); }
.chapter--night .small { color: var(--night-ink-2); }
.tiny { font-size: 0.92rem; color: var(--ink-2); line-height: 1.55; }
.chapter--night .tiny { color: var(--night-ink-2); }

.aside {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.3rem, 2.6vw, 1.85rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
  max-width: 26rem;
}

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.sig { color: var(--signal); }
.chapter--night .sig { color: var(--glow); }

/* ---------- the notch ---------- */

/* Pure black against a lighter ground: a notch only reads as a notch when the
   screen around it is lighter than it is, exactly as on real hardware. */
/* The lid is the only piece of hardware on the page, so it has to read as
   hardware: a screen with something on it, a bezel catching light, and a
   shadow underneath. A flat grey rectangle reads as a placeholder. */
.lid-stage {
  /* Larger than before because the corrected notch ratio is small by nature:
     a real Mac's island is a modest thing against a whole screen. Scaling the
     lid up keeps it legible without lying about the proportion. */
  width: min(100%, 34rem);
  /* Closer perspective makes the same tilt angle read as real depth rather
     than a flat swivel. Paired with the stronger tilt angle in app.js. */
  perspective: 800px;
  perspective-origin: 50% 40%;
}

.lid {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10.5;
  background:
    radial-gradient(120% 90% at 50% -10%, rgba(48, 209, 88, 0.10), transparent 58%),
    radial-gradient(90% 70% at 22% 108%, rgba(80, 120, 100, 0.13), transparent 62%),
    linear-gradient(168deg, #1C2220 0%, #101413 52%, #0B0E0D 100%);
  border: 1px solid #2E3531;
  border-radius: 16px 16px 5px 5px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    inset 0 0 40px rgba(0, 0, 0, 0.5),
    0 30px 60px -30px rgba(0, 0, 0, 0.9);
  /* Three rotations compose here: the cursor tilt, and the scroll-driven turn.
     Kept as separate custom properties so the pointer handler and the scroll
     handler never overwrite each other's work. */
  transform: rotateX(calc(var(--tilt-x, 0deg) + var(--spin-x, 0deg)))
             rotateY(calc(var(--tilt-y, 0deg) + var(--spin-y, 0deg)));
  transform-style: preserve-3d;
  transition: transform 0.5s var(--ease-out), box-shadow 0.6s var(--ease-out);
  will-change: transform;
}

/* Following the cursor exactly feels mechanical, so the tilt is smoothed in
   JS and the transition here only covers the return to rest. */
.lid[data-tracking="true"] { transition: box-shadow 0.6s var(--ease-out); }

/* Recognition spills light onto the screen below the notch, the way a real
   display does. This is what sells the moment more than the tick alone. */
.lid::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(70% 46% at 50% 4%,
              rgba(48, 209, 88, 0.30), rgba(48, 209, 88, 0.07) 46%, transparent 72%);
  opacity: 0;
  transition: opacity 0.7s var(--ease-out);
}
.lid[data-glow="true"]::after { opacity: 1; }

.notch {
  /* Straight off the hardware. A 14 inch MacBook Pro is 1512x982pt with a
     180x37pt notch, so 11.9% by 3.77%. Every previous value here was a guess
     that came out too tall. */
  width: 11.9%;
  min-width: 44px;
  height: 3.77%;
  min-height: 11px;
  background: #000;
  position: relative;
  border-radius: 0 0 8px 8px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-bottom: 0;
  /* The Dynamic Island curve: it overshoots very slightly on the way open,
     which is what makes a piece of hardware feel like it stretched rather
     than resized. */
  transition: width 0.58s var(--ease-spring),
              height 0.52s var(--ease-spring),
              border-radius 0.5s var(--ease-out),
              box-shadow 0.5s var(--ease-out);
  box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.7);
  z-index: 2;
}

.notch[data-open="true"] {
  /* Opening stretches it in both directions, which is the whole appeal of the
     Dynamic Island: the hardware appears to give way rather than a panel
     appearing over it.

     Sized from what the real thing actually does. On a 14 inch MacBook the
     notch is about 180x37pt on a 1512x982pt screen, and the app expands it to
     250x124, so roughly 17% of the width and 13% of the height. The previous
     47% by 62% was a curtain coming down over half the display, which is why
     it read as the app taking over the screen rather than the hardware making
     a little room. The status text moved out onto the screen below for the
     same reason: the real island holds the glyph and nothing else. */
  /* The app expands to 250x124pt on that same 1512x982 screen, so 16.5% by
     12.63%. Matching the app exactly rather than matching an iPhone: this is a
     Mac, and the panel here should be the one the Mac actually draws. */
  width: 16.5%;
  min-width: 62px;
  height: 12.63%;
  /* Low enough that the percentage governs at desktop sizes and only takes
     over on genuinely small screens, where 12.63% would be unreadable. */
  min-height: 38px;
  border-radius: 0 0 16px 16px;
  box-shadow: 0 14px 34px -14px rgba(0, 0, 0, 0.85);
}

/* Bottom aligned, which is what the app does, and it is not a style choice.
   The top of this panel is where the physical notch actually is: a real Mac
   has camera housing there, not screen. Anything drawn in that band would be
   hidden behind the hardware.

   Positioned rather than padded, because a percentage padding resolves against
   the containing block's WIDTH even when it is being used to place something
   vertically. This panel is wider than it is tall, so padding-bottom: 12.9%
   silently came out a third too large. `bottom` and `height` percentages both
   resolve against height, which is what was wanted. */
.notch-body {
  position: absolute;
  inset: 0;
  gap: 0;
  opacity: 0;
  transform: scale(0.94);
  transition: opacity 0.3s ease 0.12s, transform 0.4s cubic-bezier(0.34,1.4,0.5,1) 0.12s;
}
.notch[data-open="true"] .notch-body { opacity: 1; transform: scale(1); }

/* ---------- the glyph, and the sequence it plays ----------

   One element, five phases, and the same choreography the app runs. See
   docs/design/animation-spec.md.

     idle      nothing
     searching the Finder face, eyes glancing, light sweeping down it
     orb       the face becomes a ring, and the ring flips through a sphere
     collapse  the sphere folds back into a single circle
     tick      the tick draws inside it

   The unlock is deliberately not "the face fades and a tick appears". That was
   the first attempt and it threw away the most recognisable half of what an
   iPhone actually does, which is the flip. */

.face {
  /* The app draws a 62pt glyph in a 124pt panel, so half the panel's height,
     sitting 16pt off the bottom, which is 12.9% of that height. */
  position: absolute;
  left: 50%;
  bottom: 12.9%;
  /* Centred with `translate`, not `transform`. The refusal shake animates
     `transform`, and a keyframe replaces the whole property rather than
     adding to it, so centring the glyph with transform meant every shake
     threw it half its own width sideways. `translate` is a separate
     property, so the two cannot collide. */
  translate: -50% 0;
  height: 50%;
  aspect-ratio: 1;
}
.glyph { width: 100%; height: 100%; overflow: visible; display: block; }
.glyph path, .glyph circle, .glyph ellipse {
  fill: none;
  stroke: var(--glow);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.glyph {
  shape-rendering: geometricPrecision;
  text-rendering: geometricPrecision;
}

/* ---- the face ---- */

.g-dim { opacity: 0.26; }
.g-dim path, .g-lit path { stroke-width: 5.2; }

/* Only light moves during the search. Geometry that rotates demands the eye
   follow it; light travelling over a still object can be ignored, which is
   what makes it liveable on the twentieth viewing. */
.g-lit {
  -webkit-mask-image: linear-gradient(transparent, #000 40%, #000 60%, transparent);
  mask-image: linear-gradient(transparent, #000 40%, #000 60%, transparent);
  -webkit-mask-size: 100% 200%;
  mask-size: 100% 200%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  opacity: 0;
}
[data-phase="searching"] .g-lit { opacity: 1; animation: sweep 1.5s ease-in-out infinite; }
@keyframes sweep {
  0%   { -webkit-mask-position: 0 -100%; mask-position: 0 -100%; }
  100% { -webkit-mask-position: 0 100%;  mask-position: 0 100%; }
}

/* The eyes glance left, right, then settle. Nothing else on the face moves. */
[data-phase="searching"] .g-eyes {
  animation: glance 1.5s var(--ease-move) infinite;
  transform-origin: 50px 50px;
}
@keyframes glance {
  0%   { transform: translateX(0); }
  22%  { transform: translateX(-3.2px); }
  46%  { transform: translateX(3.2px); }
  72%  { transform: translateX(-1.4px); }
  100% { transform: translateX(0); }
}

/* Recognition: the features collapse inward and the brackets rotate away as
   the tick arrives, so the face resolves into the same yes-state used by the
   app rather than being swapped for an unrelated graphic. */
.g-dim, .g-lit { transition: opacity 0.2s ease, transform 0.3s var(--ease-move); transform-origin: 50px 50px; }
[data-phase="tick"] .g-dim, [data-phase="tick"] .g-lit {
  opacity: 0;
  transform: rotate(45deg) scale(0.72);
}

/* ---- the tick ----

   The answer, drawn once the frame has stepped aside. Same shape, same
   stroke, as the rest of the glyph: one bracket frame that resolves from a
   face to a tick, not a second animation handed off to. */
.g-tick {
  fill: none;
  stroke: var(--glow);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 5.2;
  stroke-dasharray: 60;
  stroke-dashoffset: 60;
  opacity: 0;
}
[data-phase="tick"] .g-tick {
  opacity: 1;
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.42s var(--ease-out) 0.18s;
}

/* ---- refusal ----
   No ring, no flip, no tick. The tick means yes, so it never appears here. */
[data-phase="refused"] .g-dim,
[data-phase="refused"] .g-lit { opacity: 1; transform: none; }
[data-phase="refused"] .g-dim path,
[data-phase="refused"] .g-lit path { stroke: #8C948C; }
[data-phase="refused"] .g-lit { animation: none; }

/* The status word sits on the screen below the island, which is where the
   device puts it. Keeping it inside meant the notch had to be big enough to
   contain a sentence, which is what forced the old oversized geometry. */
.lid-word {
  margin-top: 0.7rem;
  max-width: 100%;
  text-align: center;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: rgba(230, 234, 229, 0.72);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.3s ease, transform 0.4s var(--ease-out);
  pointer-events: none;
}
.lid[data-open="true"] .lid-word {
  opacity: 1;
  transform: none;
  transition-delay: 0.16s;
}

.shake { animation: shake 0.42s ease; }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  22% { transform: translateX(-6px); }
  46% { transform: translateX(6px); }
  70% { transform: translateX(-4px); }
  88% { transform: translateX(3px); }
}

@media (prefers-reduced-motion: reduce) {
  [data-phase="searching"] .g-lit,
  [data-phase="searching"] .g-eyes { display: none; }
  .g-tick { transition: none; }
}

/* ---------- controls ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: inherit;
  font-size: 0.97rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  padding: 0.82rem 1.45rem;
  border-radius: 11px;
  border: 1.5px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.16s var(--ease-out), background 0.18s ease,
              border-color 0.18s ease, box-shadow 0.22s var(--ease-out);
}
/* A button that does not move when pressed does not feel like it heard you. */
.btn:active { transform: scale(0.972); }

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover { box-shadow: 0 6px 20px -8px rgba(27, 127, 56, 0.55); }
  .btn--ghost:hover { border-color: var(--ink-3); }
}

.btn--primary { background: var(--signal); color: #F4FBF5; }
.btn--primary:hover { background: #17692F; }
.chapter--night .btn--primary { background: var(--glow); color: #04150A; }
.chapter--night .btn--primary:hover { background: #4ADC6C; }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--rule-2); }
.btn--ghost:hover { background: var(--paper-2); }
.chapter--night .btn--ghost { color: var(--night-ink); border-color: var(--night-rule); }
.chapter--night .btn--ghost:hover { background: var(--night-2); }

/* Segmented control, styled like a physical instrument switch.

   The selected state is a single thumb that TRAVELS between the two options
   rather than one background switching off while another switches on. A
   blinking segmented control tells you the state changed; a sliding one shows
   you which way it went, and the eye follows it without being asked. Its
   geometry is measured in JS and handed back as --thumb-x / --thumb-w, so the
   two labels can be different lengths and the thumb still fits exactly. */
.switch {
  position: relative;
  display: inline-flex;
  /* Inside a column flex parent, inline-flex still gets stretched. The control
     has to hug its own labels or the thumb reads as a bar with text in it. */
  align-self: flex-start;
  max-width: 100%;
  padding: 4px;
  gap: 4px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 13px;
  isolation: isolate;
}

.switch-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: var(--thumb-w, 0px);
  border-radius: 9px;
  background: var(--card);
  box-shadow: 0 1px 2px rgba(18, 21, 15, 0.10),
              0 4px 12px -4px rgba(18, 21, 15, 0.16);
  transform: translateX(var(--thumb-x, 4px));
  transition: transform 0.42s var(--ease-spring), width 0.42s var(--ease-spring);
  z-index: -1;
  opacity: 0;
}
.switch[data-ready="true"] .switch-thumb { opacity: 1; }

.switch button {
  position: relative;
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.62rem 1.05rem;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  transition: color 0.26s ease, transform 0.14s var(--ease-out);
}
.switch button:active { transform: scale(0.965); }
.switch button[aria-pressed="true"] { color: var(--ink); }

/* No JS: fall back to the flat two-state control rather than no state at all. */
.no-js .switch button[aria-pressed="true"],
.switch:not([data-ready="true"]) button[aria-pressed="true"] {
  background: var(--card);
  box-shadow: 0 1px 2px rgba(18, 21, 15, 0.09);
}

/* A hard text swap under the demo reads as a bug. Blur bridges the gap so the
   eye sees one sentence becoming another rather than two sentences trading
   places. */
.morph { transition: opacity 0.2s ease, filter 0.2s ease; }
.morph[data-swapping="true"] { opacity: 0; filter: blur(3px); }

/* ---------- panels + cards ---------- */

.card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 16px;
  padding: 1.6rem 1.7rem;
  transition: transform 0.34s var(--ease-out), box-shadow 0.34s var(--ease-out),
              border-color 0.34s ease;
}
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: translateY(-3px);
    border-color: var(--rule-2);
    box-shadow: 0 18px 40px -28px rgba(18, 21, 15, 0.4);
  }
}
.chapter--night .card {
  background: var(--night-2);
  border-color: var(--night-rule);
}

.refund-promise {
  display: grid;
  grid-template-columns: minmax(9rem, 0.35fr) 1fr;
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: baseline;
  padding: 1.1rem 1.25rem;
  border: 1px solid color-mix(in srgb, var(--signal) 30%, var(--rule));
  border-radius: 14px;
  background: color-mix(in srgb, var(--signal) 6%, var(--card));
}
.refund-promise p { margin: 0; max-width: 48rem; }
.refund-promise a { color: var(--signal); text-underline-offset: 0.16em; }
@media (max-width: 40rem) {
  .refund-promise { grid-template-columns: 1fr; gap: 0.55rem; }
}

.grid { display: grid; gap: 1rem; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }

/* Section intros. A label, a heading and a lede stacked full width filled 41%
   of the measure and left a void down the right of every single section. That
   is the specific thing that reads as unbalanced rather than as minimal: not
   the amount of space, but all of it being on one side.

   Two columns, baseline aligned, so the heading and its lede sit as one line of
   thought across the width instead of a column with nothing beside it. */
.intro {
  display: grid;
  gap: clamp(1.2rem, 3vw, 3rem);
  grid-template-columns: 1fr;
}
@media (min-width: 56rem) {
  .intro {
    grid-template-columns: 1.3fr 1fr;
    align-items: end;
    gap: clamp(2rem, 5vw, 4.5rem);
  }
  /* Sits on the heading's baseline rather than floating above it. */
  .intro .lede { max-width: 25rem; padding-bottom: 0.3rem; }
}

.split {
  display: grid;
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
  grid-template-columns: 1fr;
}
@media (min-width: 62rem) {
  .split { grid-template-columns: 1.05fr 1fr; }
  .split--reverse > *:first-child { order: 2; }
}

/* ---------- spec sheet ---------- */

.spec { border-top: 1px solid var(--rule); }
.spec-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.35rem;
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 44rem) {
  .spec-row { grid-template-columns: 13rem 1fr; gap: 2rem; align-items: baseline; }
}
.spec-row dt {
  font-family: var(--mono);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.spec-row dd { margin: 0; max-width: var(--measure); }
/* Zoom on hover, done on the compositor.
   The old version animated padding-left, which is a layout property: every
   frame re-ran layout for the row and everything after it, which is exactly
   what made it judder. transform and opacity are the only two properties that
   skip layout and paint entirely, so the same movement becomes smooth. */
.spec-row {
  transition: transform 0.32s var(--ease-out),
              background-color 0.32s ease,
              box-shadow 0.32s var(--ease-out);
  transform-origin: left center;
  will-change: transform;
  border-radius: 10px;
}
@media (hover: hover) and (pointer: fine) {
  .spec-row:hover {
    background-color: rgba(27, 127, 56, 0.05);
    transform: scale(1.012);
    box-shadow: 0 10px 30px -22px rgba(18, 21, 15, 0.5);
  }
}
@media (prefers-reduced-motion: reduce) {
  .spec-row:hover { transform: none; }
}

/* ---------- readouts ---------- */

.readout {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.readout b { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.02em; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--mono);
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 0.34rem 0.66rem;
  border-radius: 6px;
  background: var(--signal-quiet);
  color: var(--signal);
}
.pill--amber { background: var(--amber-quiet); color: var(--amber); }
.chapter--night .pill { background: rgba(48,209,88,0.14); color: var(--glow); }

/* ---------- the ring ---------- */

.ring-stage {
  position: relative;
  width: min(100%, 21rem);
  aspect-ratio: 1;
  margin-inline: auto;
  display: grid;
  place-items: center;
  touch-action: none;
}

.ring-eye {
  width: 56%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  display: grid;
  place-items: center;
}

.ring-tick {
  position: absolute;
  width: 3px;
  border-radius: 2px;
  background: var(--rule-2);
  transform-origin: 50% 50%;
  transition: background 0.18s ease;
}
.ring-tick[data-lit="true"] { background: var(--signal); }
/* Pointer tracking sets height every move, so it must not be animated there.
   Only the self-filling pass (touch, and the nudge) gets easing. */
.ring-tick[data-eased="true"] {
  transition: background 0.18s ease, height 0.34s var(--ease-spring),
              transform 0.34s var(--ease-spring), margin-top 0.34s var(--ease-spring);
}
.ring-stage {
  cursor: crosshair;
  /* pan-y, not none: a circular sweep is mostly horizontal, so this leaves
     the page scrollable with a thumb over the ring while still delivering
     sideways drags to us. touch-action: none would trap the scroll. */
  touch-action: pan-y;
}
/* The face in the middle is the reset, so it has to look pressable rather
   than inherit the sweep cursor. */
.ring-eye { cursor: pointer; }
.ring-eye {
  transition: transform 0.5s var(--ease-out), box-shadow 0.5s var(--ease-out);
}
.ring-stage[data-full="true"] .ring-eye {
  transform: scale(1.045);
  box-shadow: 0 0 0 1px var(--signal), 0 10px 30px -18px rgba(27, 127, 56, 0.6);
}

/* ---------- privacy diagram ---------- */

.diagram {
  position: relative;
  border: 1.5px solid var(--ink);
  border-radius: 18px;
  padding: 1.5rem;
  background: var(--card);
}
.diagram-label {
  position: absolute;
  top: -0.7rem;
  left: 1.2rem;
  padding: 0 0.5rem;
  background: var(--paper);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.chip-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.diagram { transition: border-color 0.4s ease, box-shadow 0.4s var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .diagram:hover { box-shadow: 0 20px 46px -34px rgba(18, 21, 15, 0.45); }
  .chip { transition: transform 0.24s var(--ease-out), border-color 0.24s ease; }
  .chip:hover { transform: translateY(-2px); border-color: var(--rule-2); }
}
.chip {
  font-family: var(--mono);
  font-size: 0.79rem;
  padding: 0.44rem 0.7rem;
  border-radius: 8px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  color: var(--ink-2);
}

/* ---------- timeline ---------- */

.tl { display: flex; flex-direction: column; }
.tl-item {
  display: grid;
  grid-template-columns: 4.6rem 1fr;
  gap: 1.1rem;
  padding: 0.85rem 0;
  border-top: 1px solid var(--night-rule);
  align-items: baseline;
}
.tl-item:first-child { border-top: 0; }

/* The section is about a sequence, so it arrives as a sequence. A green mark
   travels down the left edge as each row lands. */
.tl-item {
  opacity: 0;
  transform: translateY(10px);
}
.tl[data-in="true"] .tl-item {
  opacity: 1;
  transform: none;
  transition: opacity 0.42s ease, transform 0.52s var(--ease-out);
}
.tl[data-in="true"] .tl-item:nth-child(1) { transition-delay: 0.00s; }
.tl[data-in="true"] .tl-item:nth-child(2) { transition-delay: 0.11s; }
.tl[data-in="true"] .tl-item:nth-child(3) { transition-delay: 0.22s; }
.tl[data-in="true"] .tl-item:nth-child(4) { transition-delay: 0.33s; }
.tl[data-in="true"] .tl-item:nth-child(5) { transition-delay: 0.44s; }
.tl[data-in="true"] .tl-item:nth-child(6) { transition-delay: 0.55s; }
.tl[data-in="true"] .tl-item:nth-child(7) { transition-delay: 0.66s; }
@media (prefers-reduced-motion: reduce) {
  .tl-item { opacity: 1; transform: none; }
}
.no-js .tl-item { opacity: 1; transform: none; }

/* Same asymmetry as the reveals: cascade in, leave together. */
.tl-item { transition: opacity 0.22s ease, transform 0.26s var(--ease-out); }

/* The row you are reading brightens. Cheap, and it makes a static list feel
   like something you are moving through. */
@media (hover: hover) and (pointer: fine) {
  .tl-item { transition: opacity 0.42s ease, transform 0.52s var(--ease-out), background 0.2s ease; }
  .tl[data-in="true"] .tl-item:hover { background: rgba(48, 209, 88, 0.045); }
}
.tl-t {
  font-family: var(--mono);
  font-size: 0.84rem;
  font-variant-numeric: tabular-nums;
  color: var(--glow);
}

/* ---------- nav + footer ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(10, 12, 11, 0.82);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s ease, background 0.3s ease;
}
.nav[data-scrolled="true"] {
  border-bottom-color: var(--night-rule);
  background: rgba(10, 12, 11, 0.92);
}
.nav-in {
  transition: padding 0.3s var(--ease-out);
}
.nav[data-scrolled="true"] .nav-in { padding-block: 0.62rem; }

/* A single hairline showing how far down the page you are. The product is a
   measuring instrument, so the page gets a gauge. */
.gauge {
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 4px;
  width: 100%;
  transform: scaleX(var(--progress, 0));
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--signal), var(--glow));
  opacity: 0;
  transition: opacity 0.3s ease;
}
.nav[data-scrolled="true"] .gauge { opacity: 1; }

.nav-in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem var(--gutter);
  max-width: var(--page);
  margin: 0 auto;
  flex-wrap: wrap;
}
.brand svg { transition: transform 0.5s var(--ease-spring); }
@media (hover: hover) and (pointer: fine) {
  .brand:hover svg { transform: scale(1.14); }
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--night-ink);
  text-decoration: none;
}
.nav-links { display: flex; gap: 1.15rem; flex-wrap: wrap; align-items: center; }
.nav-links a {
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--night-ink-2);
  text-decoration: none;
  transition: color 0.16s ease;
}
.nav-links a:hover { color: var(--night-ink); }

a { color: var(--signal); }
.chapter--night a { color: var(--glow); }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: 4px;
}
.chapter--night :focus-visible, .nav :focus-visible { outline-color: var(--glow); }

.foot {
  background: var(--night);
  color: var(--night-ink-2);
  padding: 3.5rem 0 4rem;
  font-size: 0.88rem;
}
.foot a { color: var(--night-ink); text-decoration: none; }
.foot a:hover { text-decoration: underline; }

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

/* Reveals run in both directions. Scrolling back up puts things away again,
   which is what makes the page feel like one continuous thing rather than a
   set of tricks that each fire once. The exit is deliberately about a third
   of the entrance: arriving is the moment worth drawing out, leaving is not. */
.reveal {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.26s ease, transform 0.3s var(--ease-out);
}
.reveal[data-in="true"] {
  opacity: 1;
  transform: none;
  transition: opacity 0.72s ease var(--delay, 0s),
              transform 0.82s var(--ease-out) var(--delay, 0s);
}

/* The travelling Mac uses the demo slot as a physical landing point. Keep
   that slot’s parent in one place while the flight is active, so a late
   reveal cannot move the target underneath the Mac. The section still fades
   in, and the normal lift remains on phones and reduced-motion pages. */
html.flight .split.reveal {
  transform: none;
  transition: opacity 0.72s ease var(--delay, 0s);
}

/* Rows inside a revealed block arrive one after another. Everything landing
   at once reads as a page load; a short cascade reads as a page arriving. */
.stagger > * {
  opacity: 0;
  transform: translateY(12px);
  /* No per-child delay on the way out, or putting a six-row block away takes
     longer than reading it. */
  transition: opacity 0.22s ease, transform 0.26s var(--ease-out);
}
.stagger[data-in="true"] > * {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s ease, transform 0.6s var(--ease-out);
}
.stagger[data-in="true"] > *:nth-child(1)  { transition-delay: 0.02s; }
.stagger[data-in="true"] > *:nth-child(2)  { transition-delay: 0.07s; }
.stagger[data-in="true"] > *:nth-child(3)  { transition-delay: 0.12s; }
.stagger[data-in="true"] > *:nth-child(4)  { transition-delay: 0.17s; }
.stagger[data-in="true"] > *:nth-child(5)  { transition-delay: 0.22s; }
.stagger[data-in="true"] > *:nth-child(6)  { transition-delay: 0.27s; }
.stagger[data-in="true"] > *:nth-child(7)  { transition-delay: 0.32s; }
.stagger[data-in="true"] > *:nth-child(8)  { transition-delay: 0.37s; }
@media (prefers-reduced-motion: reduce) {
  .stagger > * { opacity: 1; transform: none; }
}
.no-js .stagger > * { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
}
.no-js .reveal { opacity: 1; transform: none; }

/* ---------- tables + long-form legal ---------- */

.tablewrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 0.93rem; }
th, td { text-align: left; padding: 0.62rem 0.8rem; border-bottom: 1px solid var(--rule); }
th {
  font-family: var(--mono); font-weight: 500; font-size: 0.66rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
}
tr:last-child td { border-bottom: none; }

/* Legal pages are a reading column, and it should sit in the middle of the
   page rather than being pinned to the left of a container three times its
   width. It was filling 45% of the measure with 628px of nothing beside it,
   which is why the line breaks looked arbitrary: the text was wrapping at 33rem
   for legibility while the eye could see there was clearly room for more.

   The column is centred and widened to 42rem. Still a comfortable line length,
   about 85 characters, but it no longer looks like a mistake. */
.doc {
  padding: 3.5rem 0 2rem;
  max-width: 42rem;
  margin-inline: auto;
}
.doc h1 {
  font-size: clamp(2.2rem, 5vw, 3.2rem);
  letter-spacing: -0.035em;
  line-height: 1.04;
  margin-bottom: 0.2rem;
}
.doc h2 {
  font-size: 1.45rem;
  letter-spacing: -0.022em;
  margin-top: 3rem;
  line-height: 1.25;
}
.doc h3 { margin-top: 1.9rem; font-size: 1.12rem; }
.doc p, .doc li { max-width: none; }
.doc { line-height: 1.68; }

/* Tables get the full width back, since a table is not a reading column and
   squeezing three columns into a paragraph measure is what made them cramped. */
.doc .tablewrap {
  margin-inline: calc(50% - 50vw);
  padding-inline: max(var(--gutter), calc(50vw - var(--page) / 2));
  max-width: 100vw;
}
@media (max-width: 46rem) {
  .doc .tablewrap { margin-inline: 0; padding-inline: 0; }
}
.doc .tiny, .doc td, .doc th { color: var(--ink-2); }
.doc > p, .doc > ul, .doc > div { margin-top: 0.9rem; }
.doc ul { padding-left: 1.1rem; display: flex; flex-direction: column; gap: 0.45rem; }
.doc li::marker { color: var(--ink-3); }
.doc code {
  font-family: var(--mono); font-size: 0.88em;
  background: var(--paper-2); padding: 0.1em 0.35em; border-radius: 4px;
}
.doc blockquote {
  margin: 1.5rem 0;
  padding: 1.1rem 1.3rem;
  border-left: 2px solid var(--amber);
  background: var(--card);
  border-radius: 0 10px 10px 0;
  color: var(--ink-2);
  font-size: 0.94rem;
}
.doc blockquote p { max-width: none; }
.doc strong { color: var(--ink); }


/* ---------- the sign-off lockup ---------- */

/* Wispr Flow ends on an enormous wordmark whose W animates as sound waves,
   which works because the mark IS the product. Ours does the same trick with
   the thing MacGaze actually does: the glyph searches, then recognises. It
   plays once when it scrolls into view, which on this page means at the very
   end, as a full stop rather than another thing competing for attention.

   Sized in vw so it spans the measure at any width. That is deliberate: a
   fixed-size mark in a wide container is what leaves the big empty side
   panels this page is meant to avoid. */
.lockup {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.6rem, 1.6vw, 1.4rem);
  padding: clamp(1.5rem, 4vw, 3.5rem) 0 clamp(2rem, 5vw, 4rem);
}

.lockup-word {
  font-size: clamp(3rem, 15.5vw, 13rem);
  font-weight: 600;
  letter-spacing: -0.055em;
  line-height: 0.8;
  color: var(--night-ink);
  /* Optical alignment: the cap height of the word sits slightly above the
     glyph's box, so nudge the word rather than the glyph, which has to stay
     square for the animation to read as a face. */
  transform: translateY(-0.03em);
}

.lockup-glyph {
  width: clamp(3rem, 15vw, 12.5rem);
  height: auto;
  flex: none;
  overflow: visible;
  fill: none;
  stroke: var(--glow);
  stroke-width: 5.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Brackets draw themselves on, corner by corner. 150 comfortably exceeds the
   real path length, so the dash fully covers it without measuring each one. */
.lg-brackets path {
  stroke-dasharray: 150;
  stroke-dashoffset: 150;
}
.lockup[data-phase="searching"] .lg-brackets path,
.lockup[data-phase="found"] .lg-brackets path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.55s var(--ease-out);
}
.lockup[data-phase="searching"] .lg-brackets path:nth-child(2) { transition-delay: 0.07s; }
.lockup[data-phase="searching"] .lg-brackets path:nth-child(3) { transition-delay: 0.14s; }
.lockup[data-phase="searching"] .lg-brackets path:nth-child(4) { transition-delay: 0.21s; }

/* The face itself fades up once the frame exists to hold it. */
.lg-face { opacity: 0; transition: opacity 0.3s ease; }
.lockup[data-phase="searching"] .lg-face { opacity: 1; transition-delay: 0.42s; }
.lockup[data-phase="found"] .lg-face { opacity: 0; transition-duration: 0.22s; }

/* The searching beat: eyes glance left, then right, then settle. This is the
   "looking around" from the second reference, reduced to the one movement
   that reads at a glance. Only the eyes move; a whole face sliding about
   looks like a sticker being dragged rather than something looking. */
.lockup[data-phase="searching"] .lg-eyes {
  animation: lg-glance 1.5s var(--ease-move) 0.5s both;
}
@keyframes lg-glance {
  0%   { transform: translateX(0); }
  22%  { transform: translateX(-3.2px); }
  46%  { transform: translateX(3.2px); }
  72%  { transform: translateX(-1.4px); }
  100% { transform: translateX(0); }
}

/* Recognition: the face steps aside and the tick takes its place, one glyph
   at a time, exactly as the app behaves. */
.lg-tick {
  stroke-dasharray: 80;
  stroke-dashoffset: 80;
}
.lockup[data-phase="found"] .lg-tick {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.42s var(--ease-out) 0.18s;
}

/* The whole mark gives one small nod of acknowledgement on success. */
.lockup[data-phase="found"] .lockup-glyph {
  animation: lg-settle 0.5s var(--ease-spring) 0.18s;
}
@keyframes lg-settle {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.045); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .lockup[data-phase="searching"] .lg-eyes,
  .lockup[data-phase="found"] .lockup-glyph { animation: none; }
}

/* Resting state, reached through black rather than by crossfading.

   Macario was specific and he is right: a direct crossfade has both marks
   faintly visible at once, which looks like a printing error. Out to nothing,
   a beat of nothing, then in. The pause is what makes it read as two separate
   statements rather than one smearing into the other. */
.lockup[data-phase="fading"] .lg-tick { opacity: 0; transition: opacity 0.3s ease; }
.lockup[data-phase="fading"] .lg-face { opacity: 0; transition: opacity 0.3s ease; }
.lockup[data-phase="fading"] .lg-brackets path { stroke-dashoffset: 0; }

.lockup[data-phase="settled"] .lg-brackets path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.4s var(--ease-out);
}
.lockup[data-phase="settled"] .lg-face {
  opacity: 1;
  transition: opacity 0.38s ease 0.08s;
}
.lockup[data-phase="settled"] .lg-brackets {
  opacity: 1;
  transition: opacity 0.42s ease 0.04s;
}
.lockup[data-phase="settled"] .lg-tick { opacity: 0; }

/* The tick fades rather than un-draws: un-drawing reads as it being rubbed
   out, and nothing here is being undone. */
.lg-tick { opacity: 1; }

/* No JS: show the resolved mark rather than an invisible one. */
.no-js .lg-brackets path { stroke-dashoffset: 0; }
.no-js .lg-face { opacity: 1; }


/* ============================================================
   Phone and desktop are two different products

   Not two stylesheets, but a real boundary, drawn once and honoured:

     Phone      No cursor exists, so nothing may depend on one. Nothing
                hover-only, nothing that has to be pointed at to be
                discovered. Motion is limited to what arrives on scroll,
                because a thumb is already busy scrolling.
     Desktop    There is a cursor and a mouse wheel, so the page can afford
                to answer both. The lid turns as you scroll and leans toward
                the pointer.

   The switch is `hover: hover` and `pointer: fine` rather than a width, since
   width does not tell you whether there is a cursor: a small window on a Mac
   still has one, and a large tablet does not.
   ============================================================ */

/* ---- phone ---- */

@media (hover: none), (pointer: coarse) {
  /* A tap target that only reveals itself on hover is invisible here, so the
     lid says plainly that it can be tapped. */
  .lid {
    cursor: default;
    /* There is no pointer tilt on touch devices. Leaving a zero-degree 3D
       transform and a promoted layer in place makes small vector strokes in
       the notch look soft on high-density phone screens. Keep the close-up in
       the normal paint path when no cursor can use the tilt. */
    transform: none;
    transform-style: flat;
    will-change: auto;
  }

  /* Slightly larger, since it is being read at arm's length on a small screen
     rather than at desk distance. */
  .lid-word { font-size: 0.68rem; }

  /* Nothing on a phone should wait to be pointed at. */
  .spec-row:hover, .card:hover, .chip:hover { transform: none; box-shadow: none; }
}

/* ---- desktop ---- */

@media (hover: hover) and (pointer: fine) {
  /* The hero MacBook turns as the page scrolls, which is the one piece of
     scroll-driven motion on the page. It is here rather than everywhere
     because the lid is the product: turning it shows the hardware the app
     runs on, and turning anything else would just be movement.

     --spin-x and --spin-y are written by the scroll handler in app.js. */
  .lid-stage { perspective: 1400px; }
}

@media (prefers-reduced-motion: reduce) {
  .lid { --spin-x: 0deg !important; --spin-y: 0deg !important; }
}


/* ---------- the demo is a close-up ----------

   The hero lid is drawn at true scale, which means its notch is small: a real
   island is a modest thing against a whole screen, and showing that honestly
   was the point of correcting the ratio. But at that scale the glyph lands at
   about 21px, and nobody can see an animation at 21px.

   So the two surfaces answer two different questions. The hero says "this is
   how much of your screen it uses", and has to stay in proportion to be worth
   anything. The demo says "this is what it does", and is a close-up, cropped to
   the top of a screen the way a product shot would be. The panel keeps the real
   250:124 and 180:37 aspect ratios; only the magnification changes. */

.lid--closeup {
  aspect-ratio: 16 / 6;
  border-radius: 16px 16px 4px 4px;
}
.lid--closeup .notch {
  width: 152px;
  height: 31px;
  min-width: 0;
  min-height: 0;
}
.lid--closeup .notch[data-open="true"] {
  width: 210px;
  height: 104px;
  min-width: 0;
  min-height: 0;
  border-radius: 0 0 26px 26px;
}
.lid--closeup .lid-word { font-size: 0.66rem; margin-top: 0.9rem; }

@media (max-width: 34rem) {
  .lid--closeup .notch { width: 122px; height: 25px; }
  .lid--closeup .notch[data-open="true"] { width: 172px; height: 85px; }
}


/* ---------- click the face, it spins ----------

   Every Finder face on the page is clickable and gives a small spin back. Not
   a feature, just the kind of thing that rewards someone poking at it. The
   whole mark turns once, quickly, and lands where it started.

   On the mark itself rather than on a wrapper so it works wherever the glyph
   appears: the nav, the footer lockup, the ring's centre, the notch. */
.spinnable { cursor: pointer; }
.spinnable > svg, svg.spinnable {
  transform-origin: 50% 50%;
  will-change: transform;
}
.spinnable[data-spin="true"] > svg,
svg.spinnable[data-spin="true"] {
  animation: face-spin 0.72s cubic-bezier(0.34, 1.28, 0.42, 1);
}
@keyframes face-spin {
  0%   { transform: rotate(0deg) scale(1); }
  35%  { transform: rotate(150deg) scale(0.9); }
  100% { transform: rotate(360deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .spinnable[data-spin="true"] > svg,
  svg.spinnable[data-spin="true"] { animation: none; }
}


/* ---------- click the face, it flips ----------

   A coin flip about the vertical axis: the face turns edge-on and comes back,
   rather than spinning round in the plane of the page. rotateY, not rotate.

   Smoothness comes from three things, and the first attempt had none of them:
   the glyph is promoted to its own compositor layer with translateZ, nothing
   but the rotation animates, and there is no scale to change the size the
   layer has to be rasterised at mid-turn. An SVG this size re-rasterised from
   its paths every frame is exactly the stutter that showed up half way
   through the turn. */
.spinnable { cursor: pointer; }

.spinnable > svg,
svg.spinnable {
  transform-origin: 50% 50%;
  transform: translateZ(0);
  backface-visibility: visible;
}

.spinnable[data-spin="true"] > svg,
svg.spinnable[data-spin="true"] {
  animation: face-flip 0.7s cubic-bezier(0.45, 0, 0.25, 1);
  will-change: transform;
}

@keyframes face-flip {
  from { transform: perspective(600px) translateZ(0) rotateY(0deg); }
  to   { transform: perspective(600px) translateZ(0) rotateY(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .spinnable[data-spin="true"] > svg,
  svg.spinnable[data-spin="true"] { animation: none; }
}

/* The trial badge in the hero. Bigger and brighter than a normal pill because
   it is the offer, not a footnote: it was previously stated in 13px grey under
   the buttons, where small print goes, so the page read as wanting money up
   front. */
.pill--lead {
  align-self: flex-start;
  font-size: 0.82rem;
  padding: 0.42rem 0.85rem;
  background: rgba(48, 209, 88, 0.16);
  color: var(--glow);
  border: 1px solid rgba(48, 209, 88, 0.3);
  letter-spacing: 0.06em;
}


/* The thank-you page's opening. One column, centred: this page has a single
   thing to say, and a product shot beside it would invite comparison instead
   of reading. */
.thanks-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1rem;
}
.thanks-hero .lockup { padding: 0 0 1.5rem; }
.thanks-hero .lockup-word { font-size: clamp(2.4rem, 9vw, 6rem); }
.thanks-hero .lockup-glyph { width: clamp(2.4rem, 8.6vw, 5.6rem); }

/* ============================================================
   The Mac on the page: a real screen, and the journey it makes
   ============================================================ */

/* The screen keeps its own 16/10.5 shape anchored to the top of the lid, so
   shortening the lid CROPS the screen rather than squashing it. That is what
   lets one element be a whole MacBook in the hero and a close-up of the notch
   once docked: --zoom pushes into the screen, the lid's height crops the frame
   around it. */
.screen-clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 16px 16px 5px 5px;
}

.screen {
  position: absolute;
  inset: 0;
  aspect-ratio: 16 / 10.5;
  /* Sits inside the lid's 1px bezel in the hero, where that highlight sells a
     piece of hardware, and creeps over it as the view pushes in, where the
     same 1px reads as a stray bright line across a close-up. This used to be
     done by animating top/left/width directly, which sits inside a
     container-type: inline-size box, so every scroll frame forced a full
     relayout of everything sized in cqw underneath it, and a fast scroll
     reversal showed that relayout as a squashed, jiggling frame. Folding the
     same growth into the transform keeps it compositor-only: no layout, no
     jiggle, at a size where the difference is not visible anyway. */
  transform: scale(calc(var(--zoom, 1) * (1 + var(--scrim, 0) * 0.006)));
  transform-origin: 50% 0;
  /* Everything on screen is sized in cqw, so the menu bar, clock and dock
     icons stay in proportion at any lid size. */
  container-type: inline-size;
  pointer-events: none;
}

/* A soft generated gradient standing in for a wallpaper, not a photograph,
   and not a copy of any real macOS desktop picture. A few overlapping radial
   washes in the same green the rest of the page already uses, so the screen
   looks considered rather than borrowed. */
.screen-wall {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  background:
    radial-gradient(120% 90% at 15% -10%, rgba(48, 209, 88, 0.35), transparent 55%),
    radial-gradient(100% 80% at 90% 15%, rgba(60, 140, 220, 0.30), transparent 60%),
    radial-gradient(110% 90% at 50% 115%, rgba(20, 60, 90, 0.55), transparent 65%),
    linear-gradient(165deg, #0E2A22 0%, #0A1B22 55%, #060F14 100%);
  /* macOS dims the wallpaper behind the lock screen and lifts it the moment
     you are let in, which is the clearest possible signal something changed. */
  filter: brightness(0.72) saturate(1.05);
  transition: filter 0.85s var(--ease-out);
}
.lid[data-state="desktop"] .screen-wall { filter: none; }

.screen-menubar {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4.2%;
  display: flex;
  align-items: center;
  gap: 1.5cqw;
  padding: 0 1.8cqw;
  background: rgba(16, 22, 20, 0.22);
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
  color: rgba(255, 255, 255, 0.94);
  font-size: 1.15cqw;
  font-weight: 500;
  z-index: 1;
}
.mb-apple { width: 1.5cqw; height: 1.5cqw; flex: none; color: currentColor; }
.mb-spacer { flex: 1; }
.mb-clock { font-variant-numeric: tabular-nums; }
/* A locked Mac has no app menus, so only the clock is there. */
.lid[data-state="locked"] .mb-apple,
.lid[data-state="locked"] .mb-title { opacity: 0; }
.mb-apple, .mb-title { transition: opacity 0.4s var(--ease-out) 0.15s; }

.screen-lock {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 8.5% 0 6.5%;
  color: #fff;
  text-align: center;
  transition: opacity 0.45s var(--ease-out), transform 0.8s var(--ease-out);
}
.lid[data-state="desktop"] .screen-lock { opacity: 0; transform: scale(1.06); }
.lock-clock { display: flex; flex-direction: column; align-items: center; gap: 0.2cqw; }
.lock-date {
  font-size: 2cqw;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 0.2cqw 1cqw rgba(0, 0, 0, 0.35);
}
.lock-time {
  font-size: 10.5cqw;
  font-weight: 600;
  line-height: 0.95;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0.3cqw 1.6cqw rgba(0, 0, 0, 0.35);
}
.lock-account { display: flex; flex-direction: column; align-items: center; gap: 0.9cqw; }
/* The account picture is the MacGaze mark: on this Mac the face is the login. */
.lock-avatar {
  width: 7.4cqw;
  height: 7.4cqw;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, 0.16);
  border: 0.16cqw solid rgba(255, 255, 255, 0.4);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.lock-avatar svg {
  width: 58%; height: 58%;
  fill: none;
  stroke: var(--glow);
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.lock-name { font-size: 2.1cqw; font-weight: 600; letter-spacing: -0.01em; }
.lock-hint {
  font-size: 1.55cqw;
  padding: 0.55cqw 1.6cqw;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.18);
  border: 0.1cqw solid rgba(255, 255, 255, 0.22);
  color: rgba(255, 255, 255, 0.86);
}

.screen-dock {
  position: absolute;
  left: 50%;
  bottom: 2.4%;
  display: flex;
  align-items: flex-end;
  gap: 1cqw;
  padding: 0.8cqw;
  border-radius: 2.4cqw;
  background: rgba(238, 245, 242, 0.2);
  border: 0.1cqw solid rgba(255, 255, 255, 0.3);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  backdrop-filter: blur(16px) saturate(180%);
  opacity: 0;
  transform: translateX(-50%) translateY(24%);
  transition: opacity 0.5s var(--ease-out) 0.1s,
              transform 0.75s var(--ease-spring) 0.1s;
}
.lid[data-state="desktop"] .screen-dock { opacity: 1; transform: translateX(-50%); }
.dock-app {
  width: 5.4cqw;
  aspect-ratio: 1;
  border-radius: 1.25cqw;
  overflow: hidden;
  display: block;
  box-shadow: 0 0.35cqw 0.9cqw rgba(0, 0, 0, 0.3);
}
.dock-app svg { width: 100%; height: 100%; display: block; }
.dock-app--gap {
  margin-left: 1.7cqw;
  box-shadow: -1.05cqw 0 0 -0.98cqw rgba(255, 255, 255, 0.45),
              0 0.35cqw 0.9cqw rgba(0, 0, 0, 0.3);
}

.notch-stack {
  position: absolute;
  top: 0; left: 0; right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 3;
}

.screen-scrim {
  position: absolute;
  /* Bled two pixels past the screen on every side. At the docked zoom the
     screen's edges land on fractional pixels, and the scrim rounding one way
     while the wallpaper rounds the other left a bright row of sky across the
     top of the panel. The clip hides the overhang, so this costs nothing. */
  inset: -2px;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to bottom,
              rgba(8, 10, 9, 1), rgba(13, 17, 15, 0.94));
  opacity: var(--scrim, 0);
}

/* The recognition glow belongs inside the screen and under the notch. A notch
   is a hole in the hardware, so display light passes behind it; painted over
   the top it washes the notch green and buries the glyph. */
.lid:has(.screen)::after { display: none; }
.screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(70% 46% at 50% 4%,
              rgba(48, 209, 88, 0.30), rgba(48, 209, 88, 0.07) 46%, transparent 72%);
  opacity: 0;
  transition: opacity 0.7s var(--ease-out);
}
.lid[data-glow="true"] .screen::after { opacity: 1; }

/* ---- the flight: lift out, hold still, then dock ---- */

.demo-slot { display: none; }

/* Deliberately narrower than the hero's stage: the machine ends up smaller
   than it started, receding into the page while the view pushes into its
   notch. */
.flight .demo-slot {
  display: block;
  width: min(100%, 30rem);
  aspect-ratio: 16 / 6;
  margin-inline: auto;
  /* Clear of the caption underneath: the docked lid is a hair taller than
     the slot reserving its space, and its shadow taller again. */
  margin-bottom: 0.8rem;
}
.flight #demoStage { display: none; }

/* The hero lid leaves the flow, so its stage holds the space open. The
   perspective has to go with it: an ancestor with a perspective becomes the
   containing block for a fixed child, which would offset the lid from the
   stage instead of from the viewport. The flying lid carries its own
   perspective inside its own transform. */
.flight #heroStage {
  aspect-ratio: 16 / 10.5;
  perspective: none;
}

.lid[data-flying="true"] {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 6;
  /* Keep one layout size for the whole journey. The scroll handler scales the
     compositor layer instead of changing width and height every frame, which
     prevents the screen contents from being relaid out and rasterised while
     the visitor reverses direction. */
  width: var(--flight-base-w, 34rem);
  height: var(--flight-base-h, 22.3rem);
  aspect-ratio: auto;
  margin: 0;
  /* One uniform scale preserves the Mac's proportions. The destination is a
     shorter close-up, so clip-path crops the lower screen as it docks. */
  transform: translate3d(var(--fly-x, 0px), var(--fly-y, 0px), 0)
             scale(var(--fly-scale, 1));
  clip-path: inset(0 0 var(--fly-crop, 0%) 0 round 16px 16px 4px 4px);
  transform-origin: 0 0;
  transition: box-shadow 0.6s var(--ease-out);
  will-change: transform, clip-path;
}

@media (prefers-reduced-motion: reduce) {
  .lid[data-flying="true"] {
    position: static;
    width: auto;
    height: auto;
    transform: none;
    clip-path: none;
  }
}

/* ============================================================
   The pinned claim, lit word by word

   The section is tall; its inner block is sticky. CSS does the pinning, so
   it holds with JavaScript off, and nothing is ever taken out of the
   visitor's hands: the wheel and the thumb keep working exactly as they
   expect. Only the lighting needs the engine.
   ============================================================ */

.scrub .chapter--pin { height: 260vh; padding: 0; }
.scrub .pin-sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  align-items: center;
}

.scrub-words {
  font-size: clamp(1.7rem, 3.9vw, 3rem);
  line-height: 1.26;
  letter-spacing: -0.025em;
  font-weight: 650;
  max-width: 48rem;
  text-wrap: pretty;
}

/* Each word carries a 0..1 --lit and a 0..1 --hot, both set per frame by the
   engine and already eased there.

   --lit is how far the word has been read: it inks up, settles the last
   fraction of a line upward, and comes into focus. inline-block keeps every
   word a stable box, so nothing reflows and the paragraph never rewraps
   under the reader.

   --hot is the green. It peaks at the moment a word is being lit and falls
   away again, so the brand colour travels through the sentence as a leading
   edge rather than sitting on it. Green means "this is being read right
   now", which is the same thing green means everywhere else on the page. */
.scrub .scrub-words .w {
  display: inline-block;
  opacity: calc(0.14 + 0.86 * var(--lit, 0));
  transform: translateY(calc((1 - var(--lit, 0)) * 0.12em));
  filter: blur(calc((1 - var(--lit, 0)) * 1.2px));
  color: color-mix(in oklab, var(--ink), var(--signal) calc(var(--hot, 0) * 100%));
}

/* The same green, as a measure of how far through the claim you are. */
.pin-rail {
  width: min(320px, 70%);
  height: 3px;
  border-radius: 99px;
  background: var(--rule);
  overflow: hidden;
  margin-top: 0.8rem;
}
.pin-rail span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--signal), var(--glow));
  transform: scaleX(var(--rail, 0));
  transform-origin: 0 50%;
  will-change: transform;
}
.no-js .pin-rail, html:not(.scrub) .pin-rail { display: none; }

@media (max-width: 61.99rem) {
  /* Shorter on a phone: one idea is a long way to push with a thumb, and a
     pinned section that outstays its welcome reads as the page having
     stopped responding. */
  .scrub .chapter--pin { height: 200vh; }
}

@media (prefers-reduced-motion: reduce) {
  /* No pin, no lighting: an ordinary section with the claim fully inked. */
  .scrub .chapter--pin { height: auto; padding: clamp(3.75rem, 8vw, 7rem) 0; }
  .scrub .pin-sticky { position: static; height: auto; }
  .scrub .scrub-words .w {
    --lit: 1; --hot: 0;
    transform: none;
    filter: none;
  }
}

/* ============================================================
   The sign-off mark

   Same drawn glyph as the notch, at twelve times the size: a bracket frame
   that fades its face away and strokes a tick on. One shape, one
   implementation, so the instances cannot drift.
   ============================================================ */

.lockup-glyph {
  position: relative;
  width: clamp(3rem, 15vw, 12.5rem);
  aspect-ratio: 1;
  height: auto;
  flex: none;
}

/* The drawn mark carries the stroke styling that used to sit on the element
   the SVG itself was. */
.lockup-mark {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  fill: none;
  stroke: var(--glow);
  stroke-width: 5.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The brackets step aside into the tick, fading rather than swinging: a
   rotate-and-shrink read as the mark swinging back into place on return,
   which nothing here is meant to do. */
.lockup[data-phase="found"] .lg-brackets,
.lockup[data-phase="fading"] .lg-brackets {
  opacity: 0;
  transition: opacity 0.2s ease;
}

/* ============================================================
   One unlock, scrubbed by the scroll

   Scroll jacking done the honest way: the section is tall, the screen inside
   it is sticky, and JavaScript maps scroll position to progress. Nothing is
   ever taken out of the visitor's hands, so the wheel and the thumb keep
   behaving exactly as they expect. The pin is CSS, so it holds with
   JavaScript off; only the choreography needs the engine.
   ============================================================ */

.scrub .chapter--how { height: 340vh; padding: 0; }

.scrub .how-sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  align-items: center;
}

.how-grid {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
  width: 100%;
}

.how-copy { min-width: 0; }

.how-steps {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  margin-top: 1.7rem;
  min-height: 15.5rem;
}

.how-num {
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--signal);
}

.how-title {
  font-size: clamp(1.9rem, 3.4vw, 2.9rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.02;
  margin: 0.4rem 0 0.55rem;
}

.how-step p { max-width: 26rem; }

.how-rail {
  width: min(320px, 70%);
  height: 3px;
  border-radius: 99px;
  background: var(--rule);
  overflow: hidden;
  margin-top: 2.2rem;
}
.how-rail span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--signal), var(--glow));
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}

/* The choreographed states exist only while the engine is running: with
   JavaScript off the three steps are simply a stacked list. */
.scrub .how-steps { display: grid; }
.scrub .how-step {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transform: translateX(56px);
  will-change: transform, opacity;
}
.scrub .how-visual { will-change: transform; }

@media (max-width: 61.99rem) {
  /* Shorter on a phone. Three steps at 340vh is a long way to push with a
     thumb for one idea. */
  .scrub .chapter--how { height: 240vh; }
  .how-grid { grid-template-columns: 1fr; gap: 2rem; align-items: start; }
  .scrub .how-sticky { padding-top: 4.5rem; }
  .how-steps { min-height: 13rem; }
  .how-title { font-size: clamp(1.7rem, 6.5vw, 2.2rem); }
  .how-step p { font-size: 0.95rem; }
  .how-rail { margin-top: 1.6rem; }
}

@media (prefers-reduced-motion: reduce) {
  /* No engine, no pin: an ordinary section with the two steps stacked and
     the notch resting open on the tick. */
  .scrub .chapter--how { height: auto; padding: clamp(3.75rem, 8vw, 7rem) 0; }
  .scrub .how-sticky { position: static; height: auto; }
  .scrub .how-steps { display: flex; }
  .scrub .how-step {
    grid-area: auto;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
}

/* The caption sits under the lid rather than against its bottom edge: the
   lid casts a shadow, and text starting inside it reads as part of the
   screen. */
.how-visual > p { margin-top: 1.1rem; }

/* The steps slide in from the right, and their off-screen resting position
   is still part of the page's width even while they are invisible, which on
   a phone pushed the whole document sideways. Clipping horizontally is both
   the fix and the correct look: a step should appear to slide in from the
   edge of its own column rather than from somewhere off the page.

   overflow-x: clip rather than hidden, so this never becomes a scroll
   container and vertical overflow still behaves normally. */
.scrub .how-steps {
  overflow-x: clip;
}
@media (max-width: 61.99rem) {
  /* Less travel on a narrow column, where 56px is a long way across. */
  .scrub .how-step { transform: translateX(32px); }
}

/* ============================================================
   What's New motion film

   The latest release gets one considered frame. The film carries the
   movement, while the surrounding layout stays quiet and easy to scan.
   ============================================================ */

.motion-chapter {
  overflow: clip;
  scroll-margin-top: var(--nav-h, 4rem);
  background:
    radial-gradient(60rem 32rem at 76% 16%, rgba(48, 209, 88, 0.12), transparent 62%),
    radial-gradient(42rem 26rem at 8% 86%, rgba(27, 127, 56, 0.08), transparent 68%),
    var(--night);
}

.motion-intro {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(18rem, 0.72fr);
  align-items: end;
  gap: clamp(2rem, 6vw, 6rem);
}

.motion-gallery {
  display: grid;
  gap: 1rem;
}

.motion-card {
  min-width: 0;
  padding: clamp(0.75rem, 1.4vw, 1rem);
  border: 1px solid rgba(230, 234, 229, 0.14);
  border-radius: 1.4rem;
  background: linear-gradient(145deg, rgba(29, 34, 32, 0.94), rgba(14, 17, 15, 0.96));
  box-shadow: 0 1.4rem 4rem rgba(0, 0, 0, 0.22);
  transition: border-color 0.45s var(--ease-out),
              transform 0.55s var(--ease-out),
              box-shadow 0.55s var(--ease-out);
}

.motion-card:hover,
.motion-card:focus-within {
  transform: translateY(-4px);
  border-color: rgba(48, 209, 88, 0.38);
  box-shadow: 0 1.8rem 5rem rgba(0, 0, 0, 0.3);
}

.motion-feature {
  display: grid;
  grid-template-columns: minmax(14rem, 0.64fr) minmax(0, 1.36fr);
  align-items: center;
  gap: clamp(1.1rem, 3vw, 2.5rem);
  padding: clamp(1rem, 2vw, 1.4rem);
}

.motion-copy {
  padding: clamp(0.7rem, 2vw, 1.25rem);
}

.motion-copy h3 {
  color: var(--night-ink);
  font-size: clamp(1.35rem, 2.5vw, 2.1rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
}

.motion-copy p {
  color: var(--night-ink-2);
  font-size: 0.95rem;
  line-height: 1.55;
}

.motion-index {
  color: var(--glow);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.12em;
}

.motion-link {
  width: fit-content;
  color: var(--night-ink);
  font-size: 0.86rem;
  font-weight: 550;
  text-decoration-color: rgba(48, 209, 88, 0.5);
  text-underline-offset: 0.3em;
}

.motion-link span {
  display: inline-block;
  color: var(--glow);
  transition: transform 0.35s var(--ease-out);
}

.motion-link:hover span { transform: translateX(0.28rem); }

.motion-frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  border: 1px solid rgba(230, 234, 229, 0.12);
  border-radius: 0.9rem;
  background: #080a09;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.025);
}

.motion-frame::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 3rem rgba(0, 0, 0, 0.16);
}

.motion-film {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #080a09;
}

.motion-note {
  align-self: center;
  max-width: none;
  color: var(--night-ink-2);
  text-align: center;
}

@media (max-width: 61.99rem) {
  .motion-intro,
  .motion-feature {
    grid-template-columns: 1fr;
  }

  .motion-card { border-radius: 1.05rem; }
  .motion-copy { padding: 0.75rem 0.35rem 0.35rem; }
  .motion-feature .motion-copy { order: 2; }
  .motion-feature .motion-frame { order: 1; }
}

@media (max-width: 42rem) {
  .nav-links a[href="#whats-new"] { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .motion-film { scroll-behavior: auto; }
  .motion-card,
  .motion-link span { transition: none; }
  .motion-card:hover,
  .motion-card:focus-within { transform: none; }
}

/* ============================================================
   Setup tutorial

   The installation film is the quiet answer to the first-launch question.
   It gets a little more room than the release film, while the three short
   labels keep the section readable for someone who prefers not to watch. */

.setup-chapter {
  border-top: 1px solid rgba(230, 234, 229, 0.1);
}

.setup-guide {
  display: grid;
  grid-template-columns: minmax(0, 1.42fr) minmax(15rem, 0.58fr);
  align-items: center;
  gap: clamp(1.5rem, 4vw, 4.5rem);
}

.setup-frame {
  border-radius: 1.2rem;
  box-shadow: 0 2rem 5rem rgba(0, 0, 0, 0.28);
}

.setup-steps {
  display: grid;
  gap: 0;
}

.setup-step {
  display: grid;
  grid-template-columns: 2.3rem minmax(0, 1fr);
  gap: 0.9rem;
  padding: 1.1rem 0;
  border-top: 1px solid rgba(230, 234, 229, 0.16);
}

.setup-step:last-child {
  border-bottom: 1px solid rgba(230, 234, 229, 0.16);
}

.setup-step-no {
  padding-top: 0.16rem;
  color: var(--glow);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
}

.setup-step h3 {
  color: var(--night-ink);
  font-size: 1.15rem;
  line-height: 1.1;
}

.setup-step p {
  color: var(--night-ink-2);
  font-size: 0.9rem;
  line-height: 1.5;
}

.setup-text-link {
  align-self: center;
}

@media (max-width: 61.99rem) {
  .setup-guide {
    grid-template-columns: 1fr;
  }

  .setup-steps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1rem;
  }

  .setup-step,
  .setup-step:last-child {
    display: block;
    padding: 0.9rem 0.9rem 0.9rem 0;
    border-top: 1px solid rgba(230, 234, 229, 0.16);
    border-bottom: 0;
  }

  .setup-step-no {
    display: block;
    margin-bottom: 0.65rem;
  }
}

@media (max-width: 42rem) {
  .setup-steps {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .setup-step,
  .setup-step:last-child {
    display: grid;
    padding: 1rem 0;
  }

  .setup-step-no {
    margin-bottom: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .setup-frame { box-shadow: none; }
}
