/* ============================================================
   Offloop — landing page
   Light, open, airy. Italic display serif over a quiet text serif.
   ============================================================ */

/* ---------- Fonts (self-hosted, latin subsets) ---------- */

@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url('/fonts/playfair-display-italic-latin.woff2') format('woff2');
}
/* LT Superior Serif — LyonsType, SIL Open Font License 1.1.
   The page's text face (brief v2): body 400, subheads/FAQ 500,
   buttons/labels/nav 600. Upright only — italic stays Playfair's.
   Weights 700/800 exist in the family but nothing uses them, so
   they aren't shipped. */
@font-face {
  font-family: 'LT Superior Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/lt-superior-serif-regular.woff2') format('woff2');
}
@font-face {
  font-family: 'LT Superior Serif';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/lt-superior-serif-medium.woff2') format('woff2');
}
@font-face {
  font-family: 'LT Superior Serif';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/lt-superior-serif-semibold.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-400-latin.woff2') format('woff2');
}
/* Bold cut of JetBrains Mono for the wordmark only, so it is given its
   own family name: this file contains just O, f, l, o and p — the seven
   letters of "Offloop" — which is why 700 weight costs 864 bytes rather
   than the ~22KB a full latin cut would. Instanced at wght 700 from the
   variable JetBrains Mono (SIL OFL 1.1). Nothing else on the page sets
   bold mono; if anything ever does, it wants the full face, not this. */
@font-face {
  font-family: 'JetBrains Mono Wordmark';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-wordmark-700.woff2') format('woff2');
}

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

:root {
  --sky: #DCEBF7;
  --haze: #F4F8FB;
  --horizon: #8FB8D4;
  --depth: #1B3A4B;
  /* brief specifies #5A7386, darkened a step so body text clears
     AA (4.5:1) over --sky, not just over --haze */
  --slate: #52697B;
  --meadow: #4A7C4E;
  /* Text-safe green. --meadow is an accent for strokes and rules, which
     only owe 3:1; as 15px bold text on the glass nav it measures
     3.2–4.1:1 depending on the sky compositing through, under AA. This
     is the lightest green that clears 4.5:1 against the darkest of
     those. Same reasoning that darkened --slate. */
  --meadow-ink: #365C3B;

  --font-display: 'Playfair Display', Georgia, serif;
  --font-display-2: 'LT Superior Serif', Georgia, serif;
  --font-body: 'LT Superior Serif', Georgia, 'Times New Roman', serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --container: 68rem;
  --narrow: 44rem;

  /* vertical footprint of the fixed nav, so hero copy can clear it
     explicitly instead of relying on a padding value that happens
     to be large enough */
  --nav-space: 5rem;
}

/* ---------- Reset-ish ---------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: 5.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--depth);
  background: linear-gradient(180deg,
    var(--sky) 0%,
    var(--sky) 38%,
    #E8F2F9 58%,
    var(--haze) 80%,
    var(--haze) 100%);
  /* clip, not hidden — hidden would make <body> its own scroll
     container and break the fixed nav / scroll-driven cloud layer */
  overflow-x: clip;
}

h1, h2, h3, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
img, video { max-width: 100%; display: block; }

a { color: var(--meadow); text-decoration-thickness: 1px; text-underline-offset: 3px; }

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

::selection { background: rgba(74, 124, 78, 0.22); }

.container {
  width: min(100% - 3rem, var(--container));
  margin-inline: auto;
}
.container.narrow { max-width: var(--narrow); }

/* ---------- Skip link ---------- */

.skip-link {
  position: absolute;
  top: 0.75rem; left: 0.75rem;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: #fff;
  color: var(--depth);
  border-radius: 999px;
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
  box-shadow: 0 8px 32px rgba(27, 58, 75, 0.18);
}
.skip-link:focus-visible { transform: none; }

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

/* The ornate italic is reserved for the H1 — one editorial
   moment per page. */
.hero-title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 450;
  letter-spacing: -0.005em;
  line-height: 1.1;
  text-wrap: balance;
}

/* Section headings use the upright secondary serif: still serif,
   so the page stays cohesive, but calmer than the italic. */
.display {
  font-family: var(--font-display-2);
  font-style: normal;
  font-weight: 500;
  letter-spacing: -0.012em;
  line-height: 1.14;
  text-wrap: balance;
  font-size: clamp(2rem, 3.5vw, 3rem);
  margin-bottom: 1.1rem;
}

/* ---------- Glass buttons ---------- */

.btn {
  display: inline-block;
  padding: 0.85rem 1.7rem;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  color: var(--depth);
  background: rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.35);
  box-shadow:
    0 8px 32px rgba(27, 58, 75, 0.12),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
  border-radius: 999px;
  transition: background 200ms ease, transform 200ms ease, box-shadow 200ms ease;
}
.btn:hover {
  background: rgba(255, 255, 255, 0.28);
  transform: translateY(-1px);
  box-shadow:
    0 12px 36px rgba(27, 58, 75, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.55);
}
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 2px solid var(--meadow); outline-offset: 3px; }

.btn-primary {
  background: rgba(255, 255, 255, 0.42);
}
.btn-primary:hover { background: rgba(255, 255, 255, 0.56); }

.btn-ghost {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.28);
  font-weight: 400;
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.22); }

@supports not (backdrop-filter: blur(1px)) {
  .btn, .btn-primary { background: rgba(255, 255, 255, 0.85); }
  .btn:hover, .btn-primary:hover { background: rgba(255, 255, 255, 0.95); }
  .btn-ghost { background: rgba(255, 255, 255, 0.7); }
}

/* ---------- Nav ---------- */

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  padding: 0.9rem 0;
}

.nav-inner {
  /* hugs its contents so the bar floats as a centred island
     rather than spanning the page */
  width: fit-content;
  max-width: calc(100% - 2rem);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 2rem;
  padding: 0.55rem 0.65rem 0.55rem 1.1rem;
  background: rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.38);
  box-shadow:
    0 8px 32px rgba(27, 58, 75, 0.10),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
  /* pill bar; the right padding keeps the inner CTA's curve
     concentric with the bar's own */
  border-radius: 999px;
}

@supports not (backdrop-filter: blur(1px)) {
  .nav-inner { background: rgba(255, 255, 255, 0.88); }
}

/* Wordmark. In LT Superior the name was indistinguishable from the nav
   links beside it — it read as a fourth menu item. JetBrains Mono is
   the page's third voice (app chips, the fn keycap), so setting the
   product's own name in the developer's typeface separates the brand
   from the navigation without importing a fourth face, and gives the
   page a clean split: Playfair italic is the hero's voice, LT Superior
   is for reading, mono is the machine. */
.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.44rem;
  color: var(--depth);
  text-decoration: none;
}
.brand-word {
  /* 700, not the 400 the rest of the mono runs at — at 400 the name read
     as a code sample rather than a logotype. 800 was tried and closes up
     the counters of "oo" at this size. */
  font-family: 'JetBrains Mono Wordmark', var(--font-mono);
  font-weight: 700;
  /* mono's tall x-height sets optically larger than the serif at the
     same value, so this sits a shade under the 0.95rem nav links */
  font-size: 0.94rem;
  /* mono is generously spaced by nature; pull it into one shape */
  letter-spacing: -0.02em;
}
/* The TLD is part of the logotype, not a second word — meadow ties it
   to the mark and keeps "offloop" the thing the eye lands on. */
.brand-tld { color: var(--meadow-ink); }

/* Three woven rings, Borromean: each passes under one neighbour and over
   the other, so no two are linked yet none can be pulled free. The closed
   rings still rhyme with the privacy section's closed loop, and the weave
   says the thing the page is about — speech, cursor and app held together
   without any of them leaving the machine. */
.nav-mark {
  /* viewBox is cropped to the artwork, so height sets the rings' actual
     drawn height rather than the height of a mostly-empty square box.
     Taller than the 1.02rem the old two-loop mark ran at: that one was
     half again as wide as it was tall, and a near-square mark at the same
     height carries visibly less weight beside a 700 wordmark. */
  height: 1.34rem;
  width: auto;
  flex: none;
  fill: none;
  stroke: var(--meadow);
  /* heavier than the page's 1.5px icon stroke: the mark is paired with
     a 700 wordmark here, not with body text, and at 1.5 it read thin
     beside it */
  stroke-width: 1.8;
  stroke-linecap: round;
}

.nav-brand:hover .nav-mark { stroke: var(--depth); }
.nav-mark { transition: stroke 200ms ease; }

.nav-links {
  display: flex;
  gap: 1.6rem;
}
.nav-links a {
  color: var(--depth);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 600;
}
.nav-links a:hover { color: var(--meadow); }

.btn-nav { padding: 0.55rem 1.15rem; font-size: 0.95rem; white-space: nowrap; }

@media (max-width: 640px) {
  .nav-links { display: none; }
  /* tighter so brand + CTA still fit inside the pill at 360px */
  .nav-inner { gap: 0.75rem; padding-left: 0.9rem; }
  .btn-nav { padding: 0.55rem 0.95rem; }
}

/* Phones: the domain form is 44px wider than the bare name and
   overruns the pill at 360px, so the whole lockup steps down rather
   than dropping ".work" — a wordmark that loses half of itself on the
   screen most people see it on is not a wordmark.

   These live down here, below .nav-mark and .btn-nav, for the same
   reason the hero's short-viewport query does: both of those set the
   properties involved from further down the file, and from above them
   these overrides lose on source order and silently do nothing. */
@media (max-width: 480px) {
  .nav-brand { gap: 0.36rem; }
  .nav-mark { height: 1.21rem; }
  .brand-word { font-size: 0.84rem; }
}
/* At 360px the bar's natural width still landed past its own
   max-width, so the flex row was quietly eating its own padding. The
   CTA is the widest thing in the pill, so most of the room comes from
   its side padding rather than from shrinking the name further. */
@media (max-width: 400px) {
  .nav-mark { height: 1.13rem; }
  .brand-word { font-size: 0.8rem; }
  .btn-nav { padding: 0.55rem 0.8rem; }
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

.hero-media {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* dissolve the footage into the page background */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 78%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 78%, transparent 100%);
}

.hero-poster img,
.hero-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}

.hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.52) 0%,
    rgba(255, 255, 255, 0.38) 26%,
    rgba(255, 255, 255, 0.14) 44%,
    rgba(255, 255, 255, 0) 56%);
}

.hero-content {
  position: relative;
  z-index: 1;
  width: min(100% - 3rem, var(--container));
  margin-inline: auto;
  /* The horizon in the footage sits at ~56% of the hero height, and it
     moves with the viewport while the copy block stays about the same
     height. So park the copy a short way above the horizon and let the
     leftover fall to the top gap, rather than splitting the slack
     evenly: 56vh locates the horizon, and the 435px subtracts the copy
     block (~395px, measured on the tall viewports where this term is
     the one that wins — it grew by the eyebrow chip) plus the gap we
     want kept below it (~40px).
     The sub holds one line above ~780px wide and wraps to two below,
     so the floor below has to survive the two-line case.
     The floor's own gap tapers with the viewport rather than sitting at
     a flat vh, because short screens have no slack to redistribute: the
     horizon is high, and the block plus the nav already fills most of
     what is above it. */
  padding-top: clamp(
    calc(var(--nav-space) + clamp(1.5rem, 16vh - 72px, 4.5rem)),
    calc(56vh - 460px),
    15rem
  );
  text-align: center;
}

/* Eyebrow chip. Takes the app-name pills' glass numbers (.30 / 12px /
   .45) rather than inventing a rung — it is the same class of object:
   a small tag, not a surface holding content. Set in the mono, which on
   this page is the machine's voice, so it reads as a label on the
   product rather than as another line of the pitch. */
.hero-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: -0.01em;
  color: var(--depth);
  padding: 0.42rem 0.95rem;
  /* the gap under the chip gives way first on short screens */
  margin: 0 0 clamp(0.5rem, 2vh, 1.1rem);
  background: rgba(255, 255, 255, 0.30);
  backdrop-filter: blur(12px) saturate(150%);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.45);
  box-shadow:
    0 4px 16px rgba(27, 58, 75, 0.07),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
  border-radius: 999px;
  text-wrap: balance;
}

@supports not (backdrop-filter: blur(1px)) {
  .hero-chip { background: rgba(255, 255, 255, 0.82); }
}

/* Live indicator. Deliberately a dot that fades in place rather than a
   dot with an expanding ring: a ring radiating outward reads as
   broadcasting, which is the one thing this product is claiming not to
   do — the same reason the privacy section's animation catches its dots
   rather than emitting them. Static and fully opaque under reduced
   motion, since the default state carries no motion. */
.chip-dot {
  flex: none;
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--meadow);
}

@media (prefers-reduced-motion: no-preference) {
  .chip-dot { animation: chip-blink 2.4s ease-in-out infinite; }
}

@keyframes chip-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

.hero-title {
  /* sized off width, but capped by height as well — on a wide, short
     window a 7vw headline would otherwise push the copy onto the
     horizon. The min() keeps that continuous instead of stepping at a
     breakpoint. */
  font-size: min(clamp(3rem, 7vw, 5.5rem), 8vh);
  line-height: 1.08;
}
.hero-line1, .hero-line2 { display: block; }

.hero-sub {
  /* Deliberately narrower than the line needs (it sets ~40em on one
     line), so it breaks to two and fills some of the space the dropped
     horizon opened up. `balance` picks the break, so both lines land
     near the headline's width rather than one long line and a runt. */
  max-width: 30em;
  margin: 1.1rem auto 0;
  font-size: 1.125rem;
  color: var(--depth);
  /* keeps the short sub from stranding a word on its own line */
  text-wrap: balance;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.9rem;
  margin-top: 1.6rem;
}

.microcopy {
  margin-top: 1rem;
  font-size: 0.875rem;
  color: var(--slate);
}
/* the hardware requirement, carried at full contrast so it
   reads as a constraint rather than fine print */
.microcopy strong {
  font-weight: 600;
  color: var(--depth);
}
/* Short viewports: the headline is already capped by vh above, so
   mainly the gaps between the hero's parts need tightening here.

   Two things about where this block sits. It is gated at 640px, not
   900px — the desktop hero layout runs all the way down to 640px, and
   a 640–900px window that is also short is the tightest case there is;
   the old 900px gate left a cliff just below it where the headline
   snapped back to its tall cap and the copy crossed the horizon. And
   it has to come *after* .hero-sub and .hero-actions: those set margin
   with the shorthand, so from higher up the file this query's
   margin-top longhands lost to source order and quietly did nothing. */
@media (max-height: 960px) and (min-width: 641px) {
  .hero-chip { font-size: 0.72rem; padding: 0.3rem 0.8rem; margin-bottom: 0.5rem; }
  .hero-title { font-size: min(clamp(3rem, 7vw, 5.5rem), 6.4vh); }
  .hero-sub { margin-top: 0.7rem; font-size: 1.0625rem; line-height: 1.45; }
  .hero-actions { margin-top: 0.9rem; }
}
/* Under ~760px tall the block cannot carry the chip and still keep the
   buttons off the grass — even tightened it costs ~40px, and at 1280×720
   the whole hero has only 43px between the nav and the horizon to spend.
   The chip is the least load-bearing thing in it, so it is what goes.
   Width-gated so the phone layout, which has no horizon to clear, keeps
   it. */
@media (max-height: 760px) and (min-width: 641px) {
  .hero-chip { display: none; }
}

/* entrance: gentle fade-and-rise */
@media (prefers-reduced-motion: no-preference) {
  .hero-chip, .hero-title, .hero-sub, .hero-actions {
    animation: rise 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }
  .hero-title { animation-delay: 0.08s; }
  .hero-sub { animation-delay: 0.2s; }
  .hero-actions { animation-delay: 0.32s; }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Scroll reveals ---------- */

@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.7s ease var(--reveal-delay, 0s),
                transform 0.7s ease var(--reveal-delay, 0s);
  }
  .js .reveal.in { opacity: 1; transform: none; }
}

/* ---------- Sections ---------- */

section {
  padding: clamp(4.5rem, 9vh, 7.5rem) 0;
  text-align: center;
}

.section-body {
  color: var(--slate);
  max-width: 36em;
  margin-inline: auto;
  font-size: 1.0625rem;
}

.wash-section { position: relative; }

/* soft colour washes so backdrop-filter glass has something to blur */
.wash {
  position: absolute;
  inset: -8rem 0;
  pointer-events: none;
  z-index: -1;
}
.wash-sky {
  background:
    radial-gradient(42rem 26rem at 18% 40%, rgba(143, 184, 212, 0.20), transparent 70%),
    radial-gradient(36rem 22rem at 82% 65%, rgba(255, 255, 255, 0.5), transparent 70%);
}
.wash-meadow {
  background:
    radial-gradient(40rem 24rem at 78% 35%, rgba(74, 124, 78, 0.08), transparent 70%),
    radial-gradient(38rem 24rem at 22% 70%, rgba(143, 184, 212, 0.16), transparent 70%);
}

/* ---------- Three qualities ---------- */

/* full section padding here — the hero's masked fade needs clear
   air before the first content block */
.qualities { padding-top: clamp(4.5rem, 10vh, 8rem); }

.qualities-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
}

.quality-label {
  font-family: var(--font-body);
  font-style: normal;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--depth);
  padding-top: 0.9rem;
  margin-bottom: 0.55rem;
  border-top: 2px solid var(--meadow);
  max-width: 100%;
}

.quality p { color: var(--slate); }

@media (max-width: 720px) {
  .qualities-grid { grid-template-columns: 1fr; gap: 2.25rem; }
}

/* ---------- Privacy ---------- */

.privacy-points {
  margin-top: 2rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 2.5rem;
  color: var(--depth);
}
.privacy-points li {
  padding-left: 1rem;
  position: relative;
}
.privacy-points li::before {
  content: '';
  position: absolute;
  left: 0; top: 0.62em;
  width: 0.35rem; height: 0.35rem;
  border-radius: 50%;
  background: var(--meadow);
}

/* ---------- Works wherever ---------- */

.app-row {
  margin-top: 2.25rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.7rem;
}

.app-row li {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  color: var(--depth);
  padding: 0.5rem 1rem;
  background: rgba(255, 255, 255, 0.30);
  backdrop-filter: blur(12px) saturate(150%);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.45);
  box-shadow:
    0 4px 16px rgba(27, 58, 75, 0.07),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
  border-radius: 999px;
}

@supports not (backdrop-filter: blur(1px)) {
  .app-row li { background: rgba(255, 255, 255, 0.8); }
}

/* official marks, rendered monochrome so the row stays one system */
.chip-logo {
  flex: none;
  width: 1rem;
  height: 1rem;
  fill: currentColor;
}

/* ---------- FAQ ---------- */

.faq .display { margin-bottom: 2rem; }

.faq-list {
  border-top: 1px solid var(--horizon);
}

.faq-item {
  border-bottom: 1px solid var(--horizon);
}

.faq-item h3 { font-size: 1.0625rem; font-weight: 400; }

.faq-question {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.15rem 2.75rem;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 500;
  text-align: center;
  color: var(--depth);
  background: none;
  border: 0;
  cursor: pointer;
}
.faq-question:hover { color: var(--meadow); }

/* pinned right so the label stays optically centred regardless of
   its length; the plus rotates 45° into a cross on open */
.faq-plus {
  position: absolute;
  right: 0.5rem;
  top: 50%;
  width: 1rem; height: 1rem;
  color: var(--slate);
  transform: translateY(-50%);
  transition: transform 200ms ease;
}
.faq-question[aria-expanded="true"] .faq-plus {
  transform: translateY(-50%) rotate(45deg);
}

.faq-answer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease;
}
.faq-answer > div {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s 300ms;
}
.faq-answer.open {
  grid-template-rows: 1fr;
}
.faq-answer.open > div {
  visibility: visible;
  transition: visibility 0s;
}
.faq-answer p {
  padding: 0 1.5rem 1.25rem;
  margin-inline: auto;
  color: var(--slate);
  max-width: 38em;
}

@media (prefers-reduced-motion: reduce) {
  .faq-answer, .faq-answer > div, .faq-plus { transition: none; }
}

/* ---------- Section icons (hand-drawn SVG set) ----------
   Shared system: 1.5px stroke, rounded caps/joins, currentColor.
   Meadow is reserved for the privacy loop and pricing checks so it
   stays an accent. */

.q-icon {
  width: 1.75rem; height: 1.75rem;
  margin: 0 auto 0.9rem;
  display: block;
  color: var(--slate);
}

.how-glyphs {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  margin: 0.4rem 0 1.2rem;
  color: var(--slate);
}
.how-glyph { width: 1.75rem; height: 1.75rem; }
.how-glyph-sep {
  width: 0.25rem; height: 0.25rem;
  border-radius: 50%;
  background: var(--horizon);
}

/* waveform bars idle at full height; the pulse only runs with
   motion allowed, once the section has scrolled in */
.wave-bar {
  transform-box: fill-box;
  transform-origin: center;
}
@media (prefers-reduced-motion: no-preference) {
  .js .reveal.in .wave-bar { animation: wave-pulse 1.8s ease-in-out infinite; }
  .js .reveal.in .wave-bar:nth-child(1) { animation-delay: 0s; }
  .js .reveal.in .wave-bar:nth-child(2) { animation-delay: 0.15s; }
  .js .reveal.in .wave-bar:nth-child(3) { animation-delay: 0.3s; }
  .js .reveal.in .wave-bar:nth-child(4) { animation-delay: 0.45s; }
  .js .reveal.in .wave-bar:nth-child(5) { animation-delay: 0.6s; }
}
@keyframes wave-pulse {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(0.55); }
}

/* ---------- Privacy loop diagram ---------- */

.privacy-loop {
  width: 10.5rem;
  margin: 0.4rem auto 1.4rem;
  display: block;
  color: var(--slate);
  overflow: visible;
}

/* Dots and boundary flashes are motion-only: invisible by default, so
   the static end state is just the Mac inside a closed loop. That is
   what no-JS and reduced-motion readers get, and it still says the
   whole thing. */
.loop-dot,
.loop-flash { opacity: 0; }

/* the boundary draws itself closed on scroll-in, then data starts
   testing it — each dot runs at the wall and the wall catches it */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal .loop-ring {
    stroke-dasharray: 304;
    stroke-dashoffset: 304;
  }
  .js .reveal.in .loop-ring {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 600ms ease-out 200ms;
  }

  /* one escape attempt every 1.6s, cycling round the four sides */
  .js .reveal.in .loop-dot,
  .js .reveal.in .loop-flash {
    animation-duration: 6.4s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
  }
  .js .reveal.in .loop-dot--r,
  .js .reveal.in .loop-flash--r { animation-delay: 1s; }
  .js .reveal.in .loop-dot--t,
  .js .reveal.in .loop-flash--t { animation-delay: 2.6s; }
  .js .reveal.in .loop-dot--l,
  .js .reveal.in .loop-flash--l { animation-delay: 4.2s; }
  .js .reveal.in .loop-dot--b,
  .js .reveal.in .loop-flash--b { animation-delay: 5.8s; }

  .js .reveal.in .loop-dot--r { animation-name: loop-escape-r; }
  .js .reveal.in .loop-dot--l { animation-name: loop-escape-l; }
  .js .reveal.in .loop-dot--t { animation-name: loop-escape-t; }
  .js .reveal.in .loop-dot--b { animation-name: loop-escape-b; }
  .js .reveal.in .loop-flash { animation-name: loop-catch; }
}

/* horizontal runs are longer than vertical ones because the boundary
   genuinely is further away on that axis */
@keyframes loop-escape-r {
  0%, 100% { transform: translate(0, 0); opacity: 0; }
  6%       { opacity: 0.9; }
  34%      { transform: translate(26px, 0); opacity: 0.9; }
  41%      { transform: translate(23px, 0); opacity: 0.45; }
  70%, 99% { transform: translate(0, 0); opacity: 0; }
}
@keyframes loop-escape-l {
  0%, 100% { transform: translate(0, 0); opacity: 0; }
  6%       { opacity: 0.9; }
  34%      { transform: translate(-26px, 0); opacity: 0.9; }
  41%      { transform: translate(-23px, 0); opacity: 0.45; }
  70%, 99% { transform: translate(0, 0); opacity: 0; }
}
@keyframes loop-escape-t {
  0%, 100% { transform: translate(0, 0); opacity: 0; }
  6%       { opacity: 0.9; }
  34%      { transform: translate(0, -11px); opacity: 0.9; }
  41%      { transform: translate(0, -8px); opacity: 0.45; }
  70%, 99% { transform: translate(0, 0); opacity: 0; }
}
@keyframes loop-escape-b {
  0%, 100% { transform: translate(0, 0); opacity: 0; }
  6%       { opacity: 0.9; }
  34%      { transform: translate(0, 11px); opacity: 0.9; }
  41%      { transform: translate(0, 8px); opacity: 0.45; }
  70%, 99% { transform: translate(0, 0); opacity: 0; }
}
/* peaks a hair after the dot lands, so the wall reads as reacting */
@keyframes loop-catch {
  0%, 30%   { opacity: 0; }
  36%       { opacity: 1; }
  52%, 100% { opacity: 0; }
}

/* ---------- App chip stagger ---------- */

@media (prefers-reduced-motion: no-preference) {
  .js .reveal .app-row li {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.45s ease, transform 0.45s ease;
  }
  .js .reveal.in .app-row li { opacity: 1; transform: none; }
  .js .reveal.in .app-row li:nth-child(2) { transition-delay: 0.06s; }
  .js .reveal.in .app-row li:nth-child(3) { transition-delay: 0.12s; }
  .js .reveal.in .app-row li:nth-child(4) { transition-delay: 0.18s; }
  .js .reveal.in .app-row li:nth-child(5) { transition-delay: 0.24s; }
  .js .reveal.in .app-row li:nth-child(6) { transition-delay: 0.3s; }
  .js .reveal.in .app-row li:nth-child(7) { transition-delay: 0.36s; }
  .js .reveal.in .app-row li:nth-child(8) { transition-delay: 0.42s; }
  .js .reveal.in .app-row li:nth-child(9) { transition-delay: 0.48s; }
}

/* ---------- Pricing ---------- */

.pricing-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  margin-top: 1rem;
  text-align: left;
}

.price-card {
  display: flex;
  flex-direction: column;
  padding: 1.75rem 1.6rem 1.6rem;
  background: rgba(255, 255, 255, 0.32);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  border: 1.5px solid rgba(255, 255, 255, 0.45);
  border-radius: 22px;
  box-shadow:
    0 8px 32px rgba(27, 58, 75, 0.08),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
@supports not (backdrop-filter: blur(1px)) {
  .price-card { background: rgba(255, 255, 255, 0.85); }
}

.price-card--pro {
  position: relative;
  border-color: var(--meadow);
  box-shadow:
    0 14px 44px rgba(27, 58, 75, 0.14),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
}

.price-flag {
  position: absolute;
  top: -0.7rem;
  left: 50%;
  transform: translateX(-50%);
  padding: 0.2rem 0.8rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #fff;
  background: var(--meadow);
  border-radius: 999px;
  white-space: nowrap;
}

.price-name {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--slate);
}

.price-amount {
  font-family: var(--font-display-2);
  font-size: 2.4rem;
  font-weight: 500;
  line-height: 1.15;
  margin: 0.2rem 0 1rem;
}
.price-per {
  font-size: 1rem;
  font-weight: 400;
  color: var(--slate);
}

.price-features {
  flex: 1;
  margin-bottom: 1.5rem;
  display: grid;
  gap: 0.55rem;
  font-size: 0.9375rem;
}
.price-features li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.price-lead { color: var(--slate); }
.check {
  flex: none;
  width: 1rem; height: 1rem;
  color: var(--meadow);
}

.price-card .btn { text-align: center; }

/* the cards land together; the Pro emphasis arrives a beat later so
   the eye registers "two options" before "here's the pick" */
@media (prefers-reduced-motion: no-preference) {
  .js .pricing-reveal .price-card--pro {
    border-color: rgba(255, 255, 255, 0.45);
    box-shadow:
      0 8px 32px rgba(27, 58, 75, 0.08),
      inset 0 1px 0 rgba(255, 255, 255, 0.5);
    transition: border-color 0.45s ease 0.4s, box-shadow 0.45s ease 0.4s;
  }
  .js .pricing-reveal .price-flag { opacity: 0; transition: opacity 0.45s ease 0.4s; }
  .js .pricing-reveal.in .price-card--pro {
    border-color: var(--meadow);
    box-shadow:
      0 14px 44px rgba(27, 58, 75, 0.14),
      inset 0 1px 0 rgba(255, 255, 255, 0.5);
  }
  .js .pricing-reveal.in .price-flag { opacity: 1; }
}

@media (max-width: 720px) {
  .pricing-grid { grid-template-columns: 1fr; gap: 1.75rem; }
}

/* ---------- Final CTA ---------- */

.cta-actions { margin-top: 2rem; }
.cta .microcopy { margin-top: 1.1rem; }

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

.footer {
  padding: 3.5rem 0 2.5rem;
  border-top: 1px solid var(--horizon);
  background: var(--haze);
  text-align: center;
}

.footer-tagline {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.25rem;
  color: var(--depth);
}

.footer-links {
  margin-top: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem 1.75rem;
}
.footer-links a {
  color: var(--slate);
  text-decoration: none;
  font-size: 0.9rem;
}
.footer-links a:hover { color: var(--meadow); text-decoration: underline; }

.footer-note {
  margin-top: 1.75rem;
  font-size: 0.8rem;
  color: var(--slate);
}

/* ---------- WebGL cloud layer ---------- */

.cloud-canvas {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
}

/* ---------- Small screens ---------- */

/* Small screens: full-bleed footage would put copy on top of the
   desk and grass. Keep the copy on the sky gradient and show the
   poster as a blended band beneath it instead. */
@media (max-width: 640px) {
  .hero { min-height: 100svh; }
  .hero-content {
    order: 1;
    /* same clearance rule as desktop; the bar is still 64px tall here,
       so a flat 6rem left only ~18px under it */
    padding-top: calc(var(--nav-space) + 2.5rem);
  }
  .hero-media {
    position: relative;
    inset: auto;
    order: 2;
    height: 46svh;
    margin-top: 1.25rem;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 82%, transparent 100%);
    mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 82%, transparent 100%);
  }
  .hero-poster img,
  .hero-media video { object-position: 50% 72%; }
  .hero-scrim { display: none; }
  .hero-actions .btn { width: 100%; text-align: center; }
  /* a size down is what keeps it to one row at phone widths — it stays
     a pill because it stays one row */
  .hero-chip {
    font-size: 0.72rem;
    padding: 0.45rem 0.85rem;
    max-width: 20rem;
  }
}

@media (max-width: 400px) {
  .hero-title { font-size: 2.6rem; }
}
