/* ADAPT — site styles.
   Colours are lifted from the app's own tokens (src/theme/tokens.ts) so the
   site and the app read as one thing. If a colour changes there, change it
   here; nothing else on this site should carry a hex value. */

/* Dark is the default, as in the app. Light arrives two ways: the visitor's
   system preference, or an explicit choice stored in localStorage. The
   explicit choice has to win in both directions, which is why the same tokens
   are written twice — once behind the media query (system preference, unless
   the visitor has chosen dark) and once behind the attribute (their choice). */
:root {
  --bg: #0d0e10;
  --surface: #17181b;
  --raised: #222327;
  --border: #292b30;
  --text: #f4f5f6;
  --muted: #a2a6ac;
  /* Two different jobs, as in the app's tokens. `--accent-fill` paints a
     control and never changes between themes, so `--accent-ink` — the text
     that sits on it — never changes either. `--accent` is the text weight of
     the same colour, which does have to darken on a pale background or it
     washes out. */
  --accent-fill: #4ade80;
  --accent-ink: #0d0e10;
  --accent: #4ade80;
  --accent-2: #38bdf8;
  --warn: #fbbf24;
  --tint: #13221a;
  --glow: rgba(74, 222, 128, 0.13);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.22);
  --measure: 34rem;
  --page: 68rem;
  --ease: cubic-bezier(0.23, 1, 0.32, 1);
  color-scheme: dark;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #fafbfc;
    --surface: #ffffff;
    --raised: #ffffff;
    --border: #e3e8ee;
    --text: #14181d;
    --muted: #5b6672;
    --accent: #15803d;
    --accent-2: #0369a1;
    --warn: #a15c07;
    --tint: #ebfaf1;
    --glow: rgba(74, 222, 128, 0.16);
    --shadow: 0 1px 2px rgba(58, 70, 84, 0.06), 0 8px 24px rgba(58, 70, 84, 0.08);
    color-scheme: light;
  }
}

:root[data-theme="light"] {
  --bg: #fafbfc;
  --surface: #ffffff;
  --raised: #ffffff;
  --border: #e3e8ee;
  --text: #14181d;
  --muted: #5b6672;
  --accent: #15803d;
  --accent-2: #0369a1;
  --warn: #a15c07;
  --tint: #ebfaf1;
  --glow: rgba(74, 222, 128, 0.16);
  --shadow: 0 1px 2px rgba(58, 70, 84, 0.06), 0 8px 24px rgba(58, 70, 84, 0.08);
  color-scheme: light;
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 1rem/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-feature-settings: "kern";
  text-rendering: optimizeLegibility;
  /* Only the colours cross-fade when the theme changes — laying out the whole
     page again would make the switch feel heavy. */
  transition: background-color 240ms var(--ease), color 240ms var(--ease);
}

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

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

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

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

   Everything that moves is opacity and transform only, so nothing here can
   cause a layout pass. `.reveal` is the one rule the whole page leans on: hide,
   then release when it scrolls into view.

   The `.js` guard matters. Without JavaScript there is nothing to add `.in`,
   so the starting state must never be applied — otherwise a visitor with JS
   off gets a blank page instead of a slightly less animated one. */

.js .reveal { opacity: 0; transform: translateY(14px); }
.reveal {
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
}
.reveal.in { opacity: 1; transform: none; }

/* Children of a revealed group come in one after another rather than all at
   once — the eye reads a sequence as intent and a simultaneous jump as a
   glitch. */
.stagger > * { transition-delay: calc(var(--i, 0) * 70ms); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* Nothing moves and nothing fades, but everything is present and correct. */
  .js .reveal, .reveal { opacity: 1; transform: none; transition: none; }
  body, .card, .btn, .theme-toggle, .review { transition: none; }
  .device-screen img, .device-screen video { transition: none; }
  .js .word > span { transform: none; transition: none; }
  .scroll-cue { animation: none; }
  .life-steps .step { opacity: 1 !important; }
  .js .hero .mark .bar { transform: none; animation: none; }
}

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

.site-head {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(150%) blur(12px);
  border-bottom: 1px solid var(--border);
}
.site-head .wrap {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4rem;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-size: 0.8125rem;
  color: var(--text);
  text-decoration: none;
}
.brand img { width: 1.125rem; height: 1rem; display: block; }
.site-head nav { margin-left: auto; display: flex; gap: 1.25rem; flex-wrap: wrap; align-items: center; }
.site-head nav a { color: var(--muted); text-decoration: none; font-size: 0.9375rem; }
.site-head nav a:hover, .site-head nav a[aria-current="page"] { color: var(--text); }

/* The theme switch. Two icons in one button; CSS decides which is showing, so
   there is no JavaScript involved in drawing it. */
.theme-toggle {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.75rem;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
  transition: color 180ms var(--ease), border-color 180ms var(--ease), transform 180ms var(--ease);
}
.theme-toggle:hover { color: var(--text); border-color: var(--muted); }
.theme-toggle:active { transform: scale(0.94); }
.theme-toggle svg { width: 1.0625rem; height: 1.0625rem; display: block; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: none; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: block; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .theme-toggle .icon-sun { display: none; }
  :root:not([data-theme="dark"]) .theme-toggle .icon-moon { display: block; }
}

@media (max-width: 46rem) {
  .site-head .wrap { flex-wrap: wrap; padding-top: 0.75rem; padding-bottom: 0.75rem; min-height: 0; }
  /* Two rows, not three: the wordmark and the switch share the first, and the
     links take the second on their own. */
  .brand { order: 0; }
  .theme-toggle { order: 1; margin-left: auto; }
  .site-head nav { order: 2; margin-left: 0; width: 100%; gap: 1rem; }
}

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

.hero {
  position: relative;
  padding: clamp(3.5rem, 10vw, 7rem) 0 clamp(2.5rem, 6vw, 4.5rem);
  overflow: hidden;
}
.hero::before {
  content: "";
  position: absolute;
  inset: -12rem 0 auto -20%;
  height: 34rem;
  width: 140%;
  background: radial-gradient(closest-side, var(--glow), transparent 70%);
  pointer-events: none;
}
.hero > .wrap { position: relative; }
.hero .mark { width: clamp(3.25rem, 8vw, 4.5rem); height: auto; display: block; }

.hero h1 {
  font-size: clamp(2.75rem, 8vw, 4.75rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
  margin: 1.75rem 0 0;
  font-weight: 800;
}
.hero .tagline {
  font-size: clamp(1.25rem, 3.2vw, 1.75rem);
  color: var(--accent);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0.5rem 0 0;
}
.hero .lede {
  font-size: clamp(1.0625rem, 2.2vw, 1.25rem);
  color: var(--muted);
  max-width: var(--measure);
  margin: 1.5rem 0 0;
}

/* ---------- the mark, animated ----------

   Five bars, the fourth lifted and split. It rests in the shape the logo
   actually is — a mark that spends most of its time in the wrong shape is a
   worse mark — and every few seconds the fourth bar drops back into line with
   the others before lifting away again. That is the whole idea of the app in
   one gesture, so it does not need to be loud.

   Everything is transform-based. SVG geometry attributes (`y`, `height`) are
   animatable as CSS in some browsers and not others; transforms are animatable
   in all of them, and they run on the compositor. */

.hero .mark { overflow: visible; }
/* One origin for everything: the baseline the bars stand on. The rise needs it,
   and expressing the rejoin for the same origin costs one number. */
.hero .mark .bar {
  transform-box: fill-box;
  transform-origin: center bottom;
}

/* On arrival each bar grows up out of the baseline, left to right. */
.js .hero .mark .bar { transform: scaleY(0); }
.js .hero .mark.in .bar {
  animation:
    bar-rise 620ms cubic-bezier(0.16, 1, 0.3, 1) both,
    /* The loop starts after every bar has finished arriving. */
    bar-settle 7600ms 1.6s infinite;
  animation-delay: calc(var(--b) * 90ms), 1.6s;
}
@keyframes bar-rise {
  from { transform: scaleY(0); }
  to { transform: scaleY(1); }
}
/* The four bars that never move still need a keyframe, or the shared animation
   would reset their transform-origin mid-loop. */
@keyframes bar-settle {
  0%, 100% { transform: none; }
}

/* The lifted piece stretches to the common height — 1027/786 — and drops until
   its foot meets the others' baseline, then returns. */
.js .hero .mark.in .bar-lifted {
  animation:
    bar-rise 620ms cubic-bezier(0.16, 1, 0.3, 1) both,
    bar-join 7600ms 1.6s infinite;
}
@keyframes bar-join {
  0%, 58% { transform: none; }
  70%, 78% { transform: translateY(424px) scaleY(1.3066); }
  90%, 100% { transform: none; }
}

/* Its tail shrinks into the baseline while the bar above is rejoining. */
.js .hero .mark.in .bar-tail {
  animation:
    bar-rise 620ms cubic-bezier(0.16, 1, 0.3, 1) both,
    bar-tuck 7600ms 1.6s infinite;
}
@keyframes bar-tuck {
  0%, 58% { transform: none; }
  70%, 78% { transform: scaleY(0); }
  90%, 100% { transform: none; }
}

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

.actions { display: flex; flex-wrap: wrap; gap: 0.875rem; margin-top: 2rem; align-items: center; }
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: 0 1.5rem;
  border-radius: 0.875rem;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  font-size: 1rem;
  font-family: inherit;
  transition: transform 180ms var(--ease), filter 180ms var(--ease),
    border-color 180ms var(--ease), background-color 180ms var(--ease);
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0) scale(0.98); }
.btn-primary { background: var(--accent-fill); color: var(--accent-ink); }
.btn-primary:hover { filter: brightness(1.06); }
.btn-secondary { background: var(--surface); border-color: var(--border); color: var(--text); }
.btn-secondary:hover { border-color: var(--muted); }
.btn[disabled] { opacity: 0.55; pointer-events: none; }
.note { color: var(--muted); font-size: 0.9375rem; margin: 0.875rem 0 0; }

/* ---------- sections ---------- */

section { padding: clamp(3rem, 7vw, 5rem) 0; }
section + section { border-top: 1px solid var(--border); }

h2 {
  font-size: clamp(1.75rem, 4vw, 2.375rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 0.75rem;
  font-weight: 700;
}
h3 { font-size: 1.125rem; margin: 0 0 0.375rem; font-weight: 650; letter-spacing: -0.01em; }
.section-lede { color: var(--muted); max-width: var(--measure); margin: 0 0 2.5rem; font-size: 1.0625rem; }

.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 0.75rem;
}

/* ---------- cards ---------- */

.cards { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
/* Four cards wrap to 3 + 1 orphan on a wide screen. Two rows of two is the
   same information without the ragged edge. */
@media (min-width: 52rem) {
  .cards-4 { grid-template-columns: repeat(2, 1fr); }
}
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 1rem;
  padding: 1.5rem;
  transition: transform 260ms var(--ease), border-color 260ms var(--ease),
    box-shadow 260ms var(--ease);
}
.card:hover { transform: translateY(-3px); border-color: var(--muted); box-shadow: var(--shadow); }
.card p { margin: 0; color: var(--muted); }
.card .when {
  display: block;
  font-weight: 650;
  color: var(--accent);
  font-size: 0.9375rem;
  margin-bottom: 0.5rem;
}

/* A feature column: a heading and a plain list, no decoration. */
.columns { display: grid; gap: 2.5rem 3rem; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
.columns ul { margin: 0.75rem 0 0; padding-left: 1.125rem; }
.columns li { margin-bottom: 0.625rem; color: var(--muted); }
.columns li strong { color: var(--text); font-weight: 600; }

/* The "won't do" list reads as a set of promises, so it gets its own shape. */
.promises { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; max-width: var(--measure); }
.promises li { position: relative; padding-left: 2rem; color: var(--muted); }
.promises li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 1.125rem;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
}
.promises li strong { color: var(--text); font-weight: 600; }

.callout {
  background: var(--tint);
  border: 1px solid var(--border);
  border-radius: 1rem;
  padding: 1.5rem;
  max-width: var(--measure);
}
.callout p:first-child { margin-top: 0; }
.callout p:last-child { margin-bottom: 0; }

/* ---------- about the developer ---------- */

.about { display: grid; gap: 2rem 3rem; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 54rem) {
  .about { grid-template-columns: 15rem 1fr; }
}
.about-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 1rem;
  padding: 1.5rem;
}
.about-card .initials {
  width: 4rem;
  height: 4rem;
  border-radius: 1.25rem;
  display: grid;
  place-items: center;
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-weight: 800;
  font-size: 1.375rem;
  letter-spacing: -0.02em;
  margin-bottom: 1rem;
}
.about-card dl { margin: 0.75rem 0 0; display: grid; gap: 0.5rem; font-size: 0.9375rem; }
.about-card dt { color: var(--muted); font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.08em; }
.about-card dd { margin: 0 0 0.5rem; color: var(--text); }
.about p { color: var(--muted); max-width: var(--measure); }

blockquote.pull {
  margin: 2rem 0;
  padding: 0 0 0 1.25rem;
  border-left: 3px solid var(--accent);
  font-size: 1.25rem;
  line-height: 1.45;
  letter-spacing: -0.015em;
  color: var(--text);
  max-width: var(--measure);
}
blockquote.pull footer { margin-top: 0.5rem; font-size: 0.9375rem; color: var(--muted); }

/* ---------- reviews ---------- */

.reviews { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
.review {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 1rem;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  transition: transform 260ms var(--ease), border-color 260ms var(--ease);
}
.review:hover { transform: translateY(-3px); border-color: var(--muted); }
.review .stars { display: flex; gap: 0.1875rem; color: var(--warn); }
.review .stars svg { width: 0.9375rem; height: 0.9375rem; }
.review .stars .off { opacity: 0.25; }
.review blockquote { margin: 0; color: var(--muted); }
.review .who { font-size: 0.9375rem; color: var(--text); font-weight: 600; margin: 0; }
.review .who span { display: block; font-weight: 400; color: var(--muted); font-size: 0.875rem; }

/* ---------- feedback form ---------- */

.form { display: grid; gap: 1rem; max-width: var(--measure); }
.field { display: grid; gap: 0.375rem; }
.field label { font-size: 0.875rem; color: var(--muted); font-weight: 600; }
.field input, .field select, .field textarea {
  width: 100%;
  min-height: 3rem;
  padding: 0.75rem 1rem;
  border-radius: 0.875rem;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  transition: border-color 180ms var(--ease);
}
.field textarea { min-height: 8rem; resize: vertical; line-height: 1.55; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); }
.field .hint { font-size: 0.8125rem; color: var(--muted); }
.form-status { font-size: 0.9375rem; margin: 0; min-height: 1.5rem; }
.form-status[data-tone="error"] { color: var(--warn); }
.form-status[data-tone="ok"] { color: var(--accent); }

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

   A CSS frame around a real screenshot. The bezel keeps its own dark colour in
   both themes on purpose: a phone is a physical object sitting on the page,
   not a surface that belongs to the page's palette. */

.device {
  position: relative;
  width: 100%;
  max-width: 19rem;
  aspect-ratio: 390 / 844;
  margin: 0 auto;
  padding: 0.5rem;
  border-radius: 2.9rem;
  background: linear-gradient(160deg, #2a2c31, #101114 40%, #17181b);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.09),
    0 30px 60px -20px rgba(0, 0, 0, 0.55),
    0 8px 20px -8px rgba(0, 0, 0, 0.4);
}
.device-screen {
  position: relative;
  height: 100%;
  border-radius: 2.45rem;
  overflow: hidden;
  /* The app's own top wash, so the seam between the status bar and the
     screenshot underneath it is invisible. */
  background: #111a14;
}
/* 54 of the screen's 844 points, which is exactly what the capture script
   leaves out of the screenshot. The app is not covered up: it was laid out to
   fit underneath. */
.device-status {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 6.4%;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.4rem 0 1.6rem;
  color: #f4f5f6;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  pointer-events: none;
}
.device-status svg { height: 0.68rem; width: auto; display: block; }
.device-status .glyphs { display: flex; align-items: center; gap: 0.3rem; }

.device-screen img,
.device-screen video {
  position: absolute;
  top: 6.4%;
  left: 0;
  width: 100%;
  height: 93.6%;
  object-fit: cover;
  object-position: top;
  opacity: 0;
  transform: translateY(10px);
  /* Leaving: quick and unaccompanied. Two text-heavy screenshots cross-fading
     through each other are both readable at once, which looks like a fault —
     so the old one is gone before the new one arrives. */
  transition: opacity 220ms var(--ease), transform 0ms 220ms;
}
.device-screen .on {
  opacity: 1;
  transform: none;
  transition: opacity 460ms var(--ease) 200ms, transform 620ms var(--ease) 200ms;
}
/* The island. Small, dark, and not trying to be photoreal — it only has to say
   "phone" from across the page. */
.device::after {
  content: "";
  position: absolute;
  top: 1.15rem;
  left: 50%;
  transform: translateX(-50%);
  width: 4.5rem;
  height: 1.15rem;
  border-radius: 1rem;
  background: #08090b;
  z-index: 2;
}

/* One phone, not a cluster. Overlapping frames work when the screenshots are
   bright; these are near-black, so a second dimmed phone reads as a smudge
   rather than as depth. A wash of accent behind it does the same job. */
.device-stack { position: relative; }
.device-stack::before {
  content: "";
  position: absolute;
  inset: 8% -12% 4%;
  background: radial-gradient(closest-side, var(--glow), transparent 72%);
  filter: blur(6px);
  pointer-events: none;
}
.device-stack .device { position: relative; z-index: 1; }
@media (min-width: 56rem) {
  .device-stack .device { transform: rotate(2.5deg); }
}

.device-caption {
  text-align: center;
  color: var(--muted);
  font-size: 0.9375rem;
  margin: 1.25rem 0 0;
  min-height: 1.5rem;
  transition: opacity 300ms var(--ease);
}
.device-dots { display: flex; gap: 0.4375rem; justify-content: center; margin-top: 0.875rem; }
.device-dots button {
  appearance: none;
  border: 0;
  padding: 0;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--border);
  cursor: pointer;
  transition: background-color 260ms var(--ease), transform 260ms var(--ease);
}
.device-dots button:hover { background: var(--muted); }
.device-dots button[aria-current="true"] { background: var(--accent); transform: scale(1.3); }

/* ---------- hero layout with the phone ---------- */

.hero-grid { display: grid; gap: 3rem; align-items: center; }
@media (min-width: 56rem) {
  .hero-grid { grid-template-columns: 1.15fr 0.85fr; gap: 4rem; }
}

/* ---------- showreel ---------- */

/* The closing panel. Its edges fade into the page background rather than
   butting up against it, which is the difference between a video that belongs
   to the page and a video that was pasted into the middle of it. */
.showreel { padding: 0; position: relative; border-top: 1px solid var(--border); }
.showreel::before,
.showreel::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 5rem;
  z-index: 3;
  pointer-events: none;
}
.showreel::before { top: 0; background: linear-gradient(to bottom, var(--bg), transparent); }
.showreel::after { bottom: 0; background: linear-gradient(to top, var(--bg), transparent); }
.showreel .frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #0b0c0e;
  overflow: hidden;
}
@media (max-width: 46rem) {
  .showreel .frame { aspect-ratio: 4 / 5; }
}
.showreel video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Stock footage is graded for its own purposes. A touch down in saturation
     and brightness settles it into the page instead of shouting over it. */
  filter: saturate(0.92) contrast(1.04) brightness(0.88);
}
/* The header is sticky and transparent, so it sits on top of this band. Enough
   shade at the top to keep the nav readable whatever the footage is doing. */
.showreel .frame::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 30%;
  background: linear-gradient(to bottom, rgba(6, 8, 10, 0.7), transparent);
  z-index: 1;
  pointer-events: none;
}
.showreel .overlay { z-index: 2; }
/* The footage is backlit and very bright, so the scrim has to do real work —
   a light wash would leave white text on white sky. It reaches higher and
   darkens harder than it looks like it needs to. */
.showreel .overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: 0 0 clamp(2.5rem, 6vw, 4.5rem);
  background: linear-gradient(
    to top,
    rgba(6, 8, 10, 0.94) 0%,
    rgba(6, 8, 10, 0.75) 32%,
    rgba(6, 8, 10, 0.35) 62%,
    transparent 100%
  );
  padding-top: 8rem;
  color: #f4f5f6;
  pointer-events: none;
}
.showreel .overlay h2 { margin: 0 0 0.5rem; color: #f4f5f6; }
.showreel .overlay p { margin: 0 0 1.75rem; color: #dde1e6; max-width: var(--measure); }
/* The overlay ignores the pointer so the video can be clicked through to; the
   one thing on it that has to be clickable says so itself. */
.showreel .overlay .btn { pointer-events: auto; }

/* ---------- headline, revealed a word at a time ----------
   Each word rides up out of its own clipped box. It is the one piece of motion
   on the page that is purely for effect, so it happens once, on the heading
   people came to read. */

.js .word { display: inline-block; overflow: hidden; vertical-align: bottom; }
.js .word > span {
  display: inline-block;
  transform: translateY(105%);
  transition: transform 760ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--w) * 65ms);
}
.js .in .word > span, .js .word.in > span { transform: none; }
/* The words carry the entrance, so the block they sit in must not slide too. */
.js .reveal.word-reveal { opacity: 1; transform: none; }

/* ---------- scroll cue ---------- */

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  color: var(--muted);
  opacity: 0.75;
  transition: opacity 400ms var(--ease);
  pointer-events: none;
  animation: nudge 2.4s ease-in-out infinite;
}
.scroll-cue.gone { opacity: 0; }
.scroll-cue svg { width: 1.5rem; height: 1.5rem; display: block; }
@keyframes nudge {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 7px); }
}
@media (max-width: 56rem) { .scroll-cue { display: none; } }

/* ---------- "how it adapts": the phone stays, the reasons scroll past ----------

   On a wide screen this is one continuous scene rather than four cards: the
   phone is pinned and changes screen as each reason arrives. Below that width
   there is no room to pin anything, so it falls back to the cards. */

.life-grid { display: grid; gap: 2.5rem; }
.life-phone { display: none; }

@media (min-width: 62rem) {
  .life-grid { grid-template-columns: 1fr 20rem; gap: 5rem; align-items: start; }
  .life-phone {
    display: block;
    position: sticky;
    /* Clear of the sticky header, and roughly centred in what is left. */
    top: calc(50vh - 22rem);
  }
  /* The cards grid is the narrow-screen fallback. Once the phone is pinned
     the reasons have to be a single column, or two of them sit at the same
     height and both claim the phone at once. */
  .life-steps { display: block; }
  .life-steps .step {
    min-height: 62vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    border: 0;
    background: none;
    padding: 0;
    max-width: 30rem;
  }
  /* Everything dims except the reason being read. */
  .life-steps .step {
    opacity: 0.35;
    transition: opacity 500ms var(--ease);
  }
  .life-steps .step.active { opacity: 1; }
  .life-steps .step:hover { transform: none; box-shadow: none; }
  .life-steps .step .when { font-size: 1.5rem; letter-spacing: -0.02em; margin-bottom: 0.75rem; }
  .life-steps .step p { font-size: 1.0625rem; }
}

/* ---------- long-form pages (privacy, support) ---------- */

.doc { padding: clamp(2.5rem, 6vw, 4rem) 0 5rem; }
.doc .wrap { max-width: 46rem; }
.doc h1 { font-size: clamp(2rem, 5vw, 2.75rem); letter-spacing: -0.025em; margin: 0 0 0.5rem; }
.doc .updated { color: var(--muted); margin: 0 0 2.5rem; font-size: 0.9375rem; }
.doc h2 { font-size: 1.5rem; margin: 2.75rem 0 0.75rem; }
.doc h3 { font-size: 1.125rem; margin: 1.75rem 0 0.5rem; color: var(--accent); }
.doc p, .doc li { color: var(--muted); }
.doc strong { color: var(--text); font-weight: 600; }
.doc ul { padding-left: 1.25rem; }
.doc li { margin-bottom: 0.5rem; }
.doc hr { border: 0; border-top: 1px solid var(--border); margin: 2.5rem 0; }
.doc table { width: 100%; border-collapse: collapse; margin: 1.25rem 0; font-size: 0.9375rem; }
.doc th, .doc td { text-align: left; padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.doc th { color: var(--text); font-weight: 650; }
.doc td { color: var(--muted); }

/* A page that is one short message, centred (e.g. email confirmed). */
.centre { min-height: 70vh; display: grid; place-items: center; text-align: center; padding: 3rem 0; }
.centre .wrap { max-width: 30rem; }
.centre img.mark { width: 3rem; margin: 0 auto 1.75rem; display: block; }

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

.site-foot { border-top: 1px solid var(--border); padding: 2.5rem 0 3.5rem; color: var(--muted); font-size: 0.9375rem; }
.site-foot .wrap { display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; align-items: center; }
.site-foot nav { display: flex; gap: 1.25rem; flex-wrap: wrap; margin-left: auto; }
.site-foot a { color: var(--muted); text-decoration: none; }
.site-foot a:hover { color: var(--text); text-decoration: underline; }
