/* Libot download site — one stylesheet, no build step.
 *
 * ── THE DESIGN ────────────────────────────────────────────────────────────
 * Modelled on an Apple product page, because that is the job this page does:
 * introduce a new thing, one feature at a time, and hand it over.
 *
 * Four rules, taken from how those pages actually behave:
 *
 *   1. LIGHT AND AIRY. White page, warm cream for alternating sections. Dark
 *      is still here and still good, but it is the OVERRIDE, not the default.
 *
 *   2. EVERY FEATURE GETS A MOMENT. The three things that matter — AR, routes,
 *      missions — each get a full-width section to themselves: a small eyebrow,
 *      one short headline, about forty words, and a large visual.
 *
 *      The three moments are NOT identical. One runs at page width, one runs
 *      wider than the text column, one is pulled in narrow. Three sections
 *      built to the same template in a row is the thing that makes a landing
 *      page read as generated, and varying the stage is the cheapest way out
 *      of it that does not fight rule 4.
 *
 *   3. WHITESPACE SEPARATES, NOT RULES. Apple pages have almost no hairlines —
 *      sections are told apart by a background change and a lot of vertical
 *      air. If you are reaching for a 1px border to divide something, reach
 *      for padding instead.
 *
 *   4. HEADLINES CARRY THE PAGE. Short, centred, and much larger than feels
 *      comfortable in a code editor. The copy under them stays under forty
 *      words so the air around it survives.
 *
 * ── SIZING: EVERYTHING FLUID, EVERYTHING IN REM ───────────────────────────
 * No font-size is declared in px. A visitor who has set their browser to 20px
 * because they cannot comfortably read 16 gets a page that grows with them —
 * a `clamp()` written in px ignores that setting completely and is the usual
 * way a site quietly fails WCAG 1.4.4.
 *
 * Type and the large spaces come off two fluid scales (--step-* and --sp-*),
 * each interpolating between a 360px and a 1280px viewport. Pick a step; do
 * not write a clamp() inline.
 *
 * ── BREAKPOINTS ───────────────────────────────────────────────────────────
 * Four, in rem, and no others:
 *
 *      36rem / 576px   phone -> large phone
 *      48rem / 768px   tablet
 *      64rem / 1024px  laptop
 *      80rem / 1280px  desk
 *
 * rem rather than px so the breakpoints move with the user's font size too.
 * Anything that depends on how much room a COMPONENT has — the diagrams, the
 * download card — uses a container query instead, because a component's width
 * and the viewport's width stopped being the same number the moment anything
 * was laid out in columns.
 *
 * ── COLOUR AND TYPE ───────────────────────────────────────────────────────
 * Libot's own. The --brand / --accent / text / surface tokens come from
 * libot-admin-1/src/index.css, which matches the app's ThemeContext.js, so the
 * three surfaces cannot drift apart.
 *
 * LIGHT IS THE DEFAULT. Add a colour to all three token blocks (`:root`, the
 * `prefers-color-scheme: dark` query, and `[data-theme='dark']`), never as a
 * hex in a rule further down. The one exception is the ONBOARDING SURFACE
 * (--ob-*): the cyan/yellow wash from the app's Welcome screen, fixed and
 * theme-independent exactly as in ThemeContext.js, used only by the phone.
 */

/* ── LIGHT (default) ──────────────────────────────────────────────────────── */
:root {
  --bg:               #FFFFFF;
  /* Warm cream rather than Apple's neutral grey — it is the mobile app's own
     capiz page colour, and it is what keeps this from looking like a Mac
     product page with a different logo dropped on it. */
  --bg-alt:           #F5F2EA;
  --card-bg:          #F5F2EA;   /* a card sitting on white */
  --card-bg-alt:      #FFFFFF;   /* a card sitting on cream */
  --card-bg-hover:    #EFEBE0;
  --border:           #E6E0D2;
  --divider:          #EDE8DB;

  --text-primary:     #1A2224;
  --text-secondary:   #4B5759;
  --text-muted:       #5E6C6A;

  --brand:            #0A6F78;
  --brand-soft:       rgba(10,111,120,0.10);
  --brand-wash:       rgba(10,111,120,0.06);

  --accent:           #F2CE1B;
  --accent-hover:     #E4C012;
  --accent-soft:      rgba(242,206,27,0.22);
  --on-accent:        #2C2810;

  /* A filled star is information — it is the rating — so it has to clear 4.5:1
     like any other text. The accent yellow measures 2.15:1 on white and 1.92:1
     on the cream, which is why this is a separate token and not --accent: this
     deeper amber is 5.78:1 and 5.16:1 on the two light surfaces. Dark mode has
     no such problem and uses the accent directly. */
  --star:             #8F5A0A;

  /* Cyan→yellow on a light page measures about 1.6:1, so both stops collapse
     to the brand teal (5.9:1 on white) and the same gradient rule renders as
     solid teal with no special case anywhere else in the file. */
  --grad-from:        #0A6F78;
  --grad-to:          #0A6F78;

  /* Deliberately faint. Apple's light pages are almost shadowless; depth comes
     from the surface colour changing, not from things floating. */
  --shadow-sm:        0 1px 3px rgba(26,34,36,0.04);
  --shadow-md:        0 6px 24px rgba(26,34,36,0.07);
  --shadow-lg:        0 18px 48px rgba(26,34,36,0.11);
  --shadow-accent:    0 4px 14px rgba(120,100,0,0.18);

  --header-bg:        rgba(255,255,255,0.82);
  /* Used when the visitor has asked for less transparency, and as the value
     the blur falls back to where backdrop-filter is unsupported. */
  --header-bg-solid:  #FFFFFF;

  color-scheme: light;
}

/* ── DARK (override) ──────────────────────────────────────────────────────── */
/* `:not([data-theme='light'])` so an explicit light choice still wins on a
   dark OS. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg:               #0E1416;
    --bg-alt:           #141D1F;
    --card-bg:          #141D1F;
    --card-bg-alt:      #1A2427;
    --card-bg-hover:    #1B2528;
    --border:           #253335;
    --divider:          #1E2A2C;

    --text-primary:     #EDF4F4;
    --text-secondary:   #AFC4C5;
    --text-muted:       #8DA3A4;

    --brand:            #56D6E2;
    --brand-soft:       rgba(86,214,226,0.12);
    --brand-wash:       rgba(86,214,226,0.07);

    --accent:           #F2CE1B;
    --accent-hover:     #FFDC3A;
    --accent-soft:      rgba(242,206,27,0.14);
    --on-accent:        #2C2810;

    --star:             #F2CE1B;   /* 12:1 on the dark page — no adjustment needed */

    /* On a dark page the app's own cyan→yellow onboarding wash is legible, so
       the emphasis here and the emphasis on the app's first screen are
       literally the same two colours. */
    --grad-from:        #6FE0EC;
    --grad-to:          #F2CE1B;

    --shadow-sm:        0 4px 14px rgba(0,0,0,0.30);
    --shadow-md:        0 10px 28px rgba(0,0,0,0.38);
    --shadow-lg:        0 18px 48px rgba(0,0,0,0.46);
    --shadow-accent:    0 8px 24px rgba(242,206,27,0.22);

    --header-bg:        rgba(14,20,22,0.80);
    --header-bg-solid:  #0E1416;

    color-scheme: dark;
  }
}

/* Explicit choice always wins over the OS preference. Duplicated rather than
   shared because a :is()/:where() list would lose to the media query above on
   specificity grounds in some browsers. */
:root[data-theme='dark'] {
  --bg:               #0E1416;
  --bg-alt:           #141D1F;
  --card-bg:          #141D1F;
  --card-bg-alt:      #1A2427;
  --card-bg-hover:    #1B2528;
  --border:           #253335;
  --divider:          #1E2A2C;

  --text-primary:     #EDF4F4;
  --text-secondary:   #AFC4C5;
  --text-muted:       #8DA3A4;

  --brand:            #56D6E2;
  --brand-soft:       rgba(86,214,226,0.12);
  --brand-wash:       rgba(86,214,226,0.07);

  --accent:           #F2CE1B;
  --accent-hover:     #FFDC3A;
  --accent-soft:      rgba(242,206,27,0.14);
  --on-accent:        #2C2810;

  --star:             #F2CE1B;

  --grad-from:        #6FE0EC;
  --grad-to:          #F2CE1B;

  --shadow-sm:        0 4px 14px rgba(0,0,0,0.30);
  --shadow-md:        0 10px 28px rgba(0,0,0,0.38);
  --shadow-lg:        0 18px 48px rgba(0,0,0,0.46);
  --shadow-accent:    0 8px 24px rgba(242,206,27,0.22);

  --header-bg:        rgba(14,20,22,0.80);
  --header-bg-solid:  #0E1416;

  color-scheme: dark;
}

/* ── Fixed tokens ─────────────────────────────────────────────────────────── */
:root {
  /* Onboarding surface — theme-independent, phone mockup only */
  --ob-wash-top:    #6FE0EC;
  --ob-wash-mid:    #E8E84A;
  --ob-wash-bottom: #7FE3EC;
  --ob-cta:         #F5E81C;
  --ob-on-cta:      #2C2810;
  --ob-on-photo:    #FFFFFF;

  /* ── FLUID TYPE SCALE ───────────────────────────────────────────────────
     Each step interpolates between a 360px and a 1280px viewport, written in
     rem so the whole scale still responds to the browser's font-size setting.
     The comment is the px range at a default 16px root. */
  --step--2: clamp(0.7188rem, 0.6943rem + 0.109vw, 0.7813rem);  /* 11.5 → 12.5 */
  --step--1: clamp(0.8438rem, 0.8071rem + 0.163vw, 0.9375rem);  /* 13.5 → 15   */
  --step-0:  clamp(1.0625rem, 1.038rem  + 0.109vw, 1.125rem);   /* 17   → 18   */
  --step-1:  clamp(1.125rem,  1.0516rem + 0.326vw, 1.3125rem);  /* 18   → 21   */
  --step-2:  clamp(1.25rem,   1.1522rem + 0.435vw, 1.5rem);     /* 20   → 24   */
  --step-3:  clamp(1.5rem,    1.3533rem + 0.652vw, 1.875rem);   /* 24   → 30   */
  --step-4:  clamp(1.75rem,   1.3098rem + 1.957vw, 2.875rem);   /* 28   → 46   */
  --step-5:  clamp(2.125rem,  1.3913rem + 3.261vw, 4rem);       /* 34   → 64   */
  --step-6:  clamp(2.5rem,    1.4239rem + 4.783vw, 5.25rem);    /* 40   → 84   */

  /* ── SPACE ──────────────────────────────────────────────────────────────
     The small end is a fixed 4-point scale, same shape as ThemeContext.js —
     a 4px gap should be 4px on every screen. Only the large steps, the ones
     doing the "airy" work, are fluid. */
  --sp-xs: 0.25rem;  --sp-sm: 0.5rem;  --sp-md: 0.75rem;
  --sp-lg: 1.125rem; --sp-xl: 1.5rem;

  --sp-2xl: clamp(1.75rem, 1.4565rem + 1.304vw, 2.5rem);   /* 28 → 40  */
  --sp-3xl: clamp(2.5rem,  1.913rem  + 2.609vw, 4rem);     /* 40 → 64  */
  --sp-4xl: clamp(3.5rem,  2.5217rem + 4.348vw, 6rem);     /* 56 → 96  */
  --sp-5xl: clamp(4.5rem,  2.837rem  + 7.391vw, 8.75rem);  /* 72 → 140 */

  --r-sm: 0.625rem; --r-md: 1rem;    --r-lg: 1.375rem;
  --r-xl: 1.875rem; --r-card: 1.375rem; --r-stage: 2.125rem;
  /* Apple's button radius is effectively a capsule. */
  --r-pill: 980px;

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-sans:    'Instrument Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, 'Cascadia Code', Consolas, monospace;

  /* The gutter grows a little with the viewport, and respects the rounded
     corners and camera cutout on a notched phone held in landscape. */
  --gutter: clamp(1.125rem, 0.85rem + 1.2vw, 2rem);
  --gutter-l: max(var(--gutter), env(safe-area-inset-left));
  --gutter-r: max(var(--gutter), env(safe-area-inset-right));

  --maxw:      70rem;    /* 1120px — the text column                       */
  --maxw-wide: 82.5rem;  /* 1320px — the one moment that runs wider        */

  /* Two numbers, not one. --bar-h is the top bar itself; --header-h is
     everything sticky, which on small screens is the bar PLUS the section
     rail underneath it. Anchor scrolling reads --header-h, so a heading you
     jump to can never land behind the chrome. Conflating the two made the bar
     grow to the height of both. */
  --bar-h: 3.75rem;
  --header-h: var(--bar-h);

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

/* The `hidden` attribute is how main.js keeps every optional section off the
   page until it has real data. It is only `display: none` in the UA
   stylesheet, so ANY author rule that sets display silently beats it and
   renders an empty component. Guarding it once here means a section can be
   given a layout without also having to remember a `[hidden]` override. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  /* The sticky header would otherwise cover the heading you jumped to. */
  scroll-padding-top: calc(var(--header-h) + var(--sp-xl));
  /* `clip`, not `hidden`. Both stop a stray horizontal scrollbar, but `hidden`
     turns the element into a scroll container, which silently kills
     `position: sticky` on anything inside it. This is a safety net, not a
     licence — if something overflows, fix the something. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.6;
}

/* ── Motion, transparency and contrast preferences ────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Someone who has turned transparency down at the OS level has usually done it
   because layered translucency is hard for them to read through, not because
   they dislike the look. Drop the blur and go opaque. */
@media (prefers-reduced-transparency: reduce) {
  .site-header {
    background: var(--header-bg-solid);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* Put the hairlines back for anyone asking for more contrast. Rule 3 above is
   a stylistic preference; their setting is a stated need, and it wins. */
@media (prefers-contrast: more) {
  .bento-item, .step, .review, .faq details, .stage, .download-card, .qr-panel {
    border: 1px solid var(--border);
  }
  .btn-ghost { border-color: currentColor; }
}

/* Windows high-contrast mode throws the palette away, so anything that was
   only distinguishable by background colour has to grow an edge. */
@media (forced-colors: active) {
  .bento-item, .step, .review, .faq details, .stage,
  .download-card, .qr-panel, .btn { border: 1px solid; }
  .dia-paper { fill: Canvas; }
}

img { max-width: 100%; display: block; }

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

/* Fraunces stays — it is the app's face, and WONK (the cocked alternates) is
   what stops it reading as Times. `opsz` is tracked to the rendered size
   rather than pinned at one value, because that axis decides how fine the
   serifs cut: 144 at hero size, 96 on a moment, 36 on a card heading. */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0;
  font-variation-settings: 'opsz' 48, 'SOFT' 0, 'WONK' 1;
}

p { margin: 0; }

code { font-family: var(--font-mono); font-size: 0.92em; }

/* No border-radius here. Setting one would square off the pill buttons the
   moment they take focus; `outline` already follows the element's own corners. */
:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0.625rem;
  z-index: 100;
  background: var(--bg);
  color: var(--text-primary);
  padding: 0.625rem 1rem;
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-md);
  font-weight: 600;
  text-decoration: none;
}
.skip-link:focus { left: 1rem; }

/* Gradient text for the emphasised words. The base rule is the fallback — a
   browser without background-clip:text keeps a readable solid colour instead
   of `transparent`, which would render the word invisible. */
.grad { color: var(--brand); }

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .grad {
    background-image: linear-gradient(100deg, var(--grad-from), var(--grad-to));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    padding-bottom: 0.06em; /* descenders get clipped by the tight text box */
  }
}

/* ── Layout ───────────────────────────────────────────────────────────────── */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-left: var(--gutter-l);
  padding-right: var(--gutter-r);
}
.wrap-narrow { max-width: 47.5rem; }  /* 760px */
.wrap-wide   { max-width: var(--maxw-wide); }

/* Generous by default. This is where "airy" actually comes from — not from
   anything clever, just from a lot of vertical padding and the discipline not
   to fill it. */
.section { padding-block: var(--sp-5xl); }

.section-alt { background: var(--bg-alt); }

/* ── Section headers ──────────────────────────────────────────────────────── */
/* Centred, always. The editorial split-column headers an earlier draft used
   are a magazine device; a product page centres its headlines and lets the
   whitespace do the work. */
.section-head {
  text-align: center;
  margin-bottom: var(--sp-3xl);
}

.kicker {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brand);
  margin-bottom: var(--sp-md);
}

.section-title {
  font-size: var(--step-4);
  font-variation-settings: 'opsz' 96, 'SOFT' 0, 'WONK' 1;
  letter-spacing: -0.028em;
  text-wrap: balance;
}

.lede {
  color: var(--text-secondary);
  font-size: var(--step-1);
  max-width: 54ch;
  margin-inline: auto;
  margin-top: var(--sp-lg);
  text-wrap: pretty;
}

/* ── Icons ────────────────────────────────────────────────────────────────── */
/* Every inline SVG is a 24-grid stroke icon, in the spirit of the Phosphor set
   the app and admin use. Stroke, not fill, so one rule covers all of them and
   they inherit colour from whatever they sit in. */
.ico {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── Buttons ──────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5625rem;
  /* 44px floor — iOS HIG says 44pt, Android 48dp; the lg variant clears both. */
  min-height: 2.75rem;
  padding: 0.75rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.005em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.15s ease,
              border-color 0.15s ease, box-shadow 0.15s ease;
}
.btn:active { transform: translateY(1px); }

.btn-accent {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-accent);
}
.btn-accent:hover { background: var(--accent-hover); }

/* The download arrow travels on hover. One small, specific motion, and it says
   which direction the button goes. Hover only — on a touchscreen this fires on
   tap, after the decision has already been made. */
@media (hover: hover) {
  .btn-accent .ico { transition: translate 0.18s cubic-bezier(0.2, 0.7, 0.3, 1); }
  .btn-accent:hover .ico { translate: 0 2px; }
}

.btn-ghost {
  background: transparent;
  color: var(--brand);
  border-color: var(--border);
}
.btn-ghost:hover { background: var(--card-bg-hover); border-color: var(--brand); }

.btn-lg  { min-height: 3.375rem; padding: 0.9375rem 1.875rem; font-size: 1.0625rem; }
.btn-sm  { min-height: 2.375rem; padding: 0.5rem 1.125rem;    font-size: 0.9062rem; }
.btn-xs  { min-height: 2.125rem; padding: 0.375rem 0.875rem;  font-size: 0.8438rem; }
.btn-block { width: 100%; }

/* Set by main.js when config.js has no apkUrl. The pointer-events kill stops a
   dead <a> from looking clickable; opacity keeps it legible while it waits. */
.btn.is-disabled {
  opacity: 0.5;
  pointer-events: none;
  box-shadow: none;
}

/* ── Header ───────────────────────────────────────────────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--header-bg-solid);
  border-bottom: 1px solid var(--divider);
  padding-top: env(safe-area-inset-top);
}

/* Only go translucent where the blur will actually render. Without the
   @supports guard, a browser that ignores backdrop-filter shows the page
   scrolling through a flat 82%-opaque white and the nav becomes unreadable. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header {
    background: var(--header-bg);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
  }
}

.header-inner {
  display: flex;
  align-items: center;
  gap: var(--sp-lg);
  min-height: var(--bar-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  text-decoration: none;
  color: var(--text-primary);
  /* Marks the logo as one unit so the mark and the word never break apart. */
  flex: none;
}
.brand-mark { border-radius: 9px; }
.brand-name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.3125rem;
  letter-spacing: -0.02em;
  font-variation-settings: 'opsz' 60, 'WONK' 1;
}

/* Apple's local nav: small, quiet, evenly spaced, sitting in the middle of the
   bar with the buy button at the end. */
.header-nav {
  display: flex;
  gap: clamp(var(--sp-lg), 3vw, var(--sp-2xl));
  margin-inline: auto;
}
.header-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;      /* the link is small; its hit area is not */
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.9062rem;
  font-weight: 500;
  transition: color 0.15s ease;
}
.header-nav a:hover { color: var(--text-primary); }
.header-nav a[aria-current] { color: var(--brand); }

/* No auto margin here while the nav is visible — the nav's own `margin-inline:
   auto` already eats the free space and leaves the actions against the right
   edge. A third auto margin would take a share of that space and pull the nav
   off centre. The mobile query below, where the nav is gone, adds it back. */
.header-actions { display: flex; align-items: center; gap: 0.625rem; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.icon-btn:hover { background: var(--card-bg-hover); color: var(--text-primary); }
.theme-icon { display: inline-flex; font-size: 1rem; line-height: 1; }

/* ── The section rail ─────────────────────────────────────────────────────
   Below the tablet breakpoint the inline nav will not fit, and the usual
   answer — a hamburger hiding six links behind a tap — is the wrong one for a
   page that is one long scroll with no destinations off it. A rail that
   scrolls sideways keeps every section one tap away AND shows where you
   currently are, which the hamburger cannot do while it is shut.

   main.js sets aria-current as you scroll. */
.section-rail {
  display: none;
  gap: var(--sp-sm);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  padding: 0 var(--gutter-r) 0.5rem var(--gutter-l);
  /* Fades the last chip rather than cutting it, so it reads as "keeps going"
     instead of "ends here". Prefixed for the older Android WebViews this page
     is squarely aimed at; where neither lands, the rail simply does not fade. */
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 2rem), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 2rem), transparent);
}
.section-rail::-webkit-scrollbar { display: none; }

.section-rail a {
  flex: none;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  min-height: 2.25rem;
  padding: 0 0.875rem;
  border-radius: var(--r-pill);
  background: var(--card-bg);
  color: var(--text-secondary);
  font-size: 0.8438rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.section-rail a[aria-current] {
  background: var(--brand-soft);
  color: var(--brand);
}

@media (max-width: 47.99rem) {
  .header-nav   { display: none; }
  .section-rail { display: flex; }
  /* With the nav gone there is nothing left to push the theme toggle and the
     download button over to the right. */
  .header-actions { margin-left: auto; }
  /* The rail is part of the sticky chrome, so anchor offsets have to clear it
     as well as the bar above it: 2.25rem chip + 0.5rem padding, rounded up. */
  :root { --header-h: calc(var(--bar-h) + 2.875rem); }
}

/* The download button is the whole point of the page — it does not get dropped
   on small screens, it gets shorter. main.js does not touch the label, so the
   text is swapped in CSS. */
@media (max-width: 26.25rem) {
  .header-cta { padding-inline: 0.875rem; }
  .header-cta::after { content: 'Get app'; }
  .header-cta-full { display: none; }
}

/* ══ HERO ═══════════════════════════════════════════════════════════════════
   The Apple product-page opening: eyebrow, one big headline, a short line of
   copy, the buttons, and then the product itself — large, centred, straight
   on, below the words rather than beside them. */
.hero {
  padding-block: var(--sp-4xl) 0;
  text-align: center;
  background: linear-gradient(180deg, var(--bg-alt), var(--bg) 78%);
}

.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5625rem;
  margin-bottom: var(--sp-lg);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.dot-live {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}

.hero-title {
  font-size: var(--step-6);
  font-weight: 900;
  line-height: 1.04;
  letter-spacing: -0.038em;
  /* opsz 144 is the top of Fraunces' optical range — at this size the serifs
     cut fine and the joins go taut. */
  font-variation-settings: 'opsz' 144, 'SOFT' 0, 'WONK' 1;
  max-width: 16ch;
  margin-inline: auto;
  text-wrap: balance;
}

.hero-sub {
  font-size: var(--step-1);
  color: var(--text-secondary);
  max-width: 46ch;
  margin: var(--sp-xl) auto 0;
  text-wrap: pretty;
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md);
  margin-top: var(--sp-2xl);
}

.hero-meta {
  margin-top: var(--sp-lg);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.05em;
  color: var(--text-muted);
  text-wrap: balance;
}

/* Social proof, centred under the buttons. Only rendered when the live API
   returns something true to say. */
.proof {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md);
  margin-top: var(--sp-xl);
}
.proof-avatars { display: flex; }
.proof-avatars img,
.proof-avatars span {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 2px solid var(--bg);
  object-fit: cover;
  margin-left: -0.5625rem;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 0.7813rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.proof-avatars :first-child { margin-left: 0; }

.stars { color: var(--star); font-size: 0.8125rem; letter-spacing: 2px; line-height: 1; }

.proof-text { font-size: var(--step--1); color: var(--text-muted); margin-top: 4px; text-align: left; }
.proof-text strong { color: var(--text-primary); }

/* The product shot. Apple runs these at 60-80% of the viewport and lets them
   bleed into the next section; the phone here is the nearest equivalent. */
.hero-stage {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-3xl);
}

/* A 9:19.5 frame is 693px tall at its widest. Held in landscape, a phone has
   about 360px of height, so the product shot becomes two full screens of
   scrolling before the first feature — and shrinking it to fit would leave a
   130px-wide mockup nobody can read anyway. It is decorative; everything it
   shows is in the copy above it. Drop it. */
@media (orientation: landscape) and (max-height: 34rem) {
  .hero-stage { display: none; }
}

/* ── Phone mockup ─────────────────────────────────────────────────────────── */
/* A recreation of the app's WelcomePage.js, not a screenshot: sharp at any
   size, theme-independent, and it costs one image the page already loads.
   Straight on, not rotated — a product shot at an angle is a styling flourish,
   and this page is trying to show the thing plainly. */
.phone {
  position: relative;
  width: min(20rem, 74vw);
  aspect-ratio: 9 / 19.5;
  padding: 0.6875rem;
  background: linear-gradient(160deg, #3A4446, #171E20 55%, #2B3435);
  border-radius: 3rem;
  box-shadow: var(--shadow-lg), 0 0 0 1px rgba(0,0,0,0.30);
}

/* The notch. A pill rather than a real cutout — cheaper and reads correctly at
   this size. */
.phone::before {
  content: '';
  position: absolute;
  top: 1.375rem;
  left: 50%;
  translate: -50% 0;
  width: 5rem;
  height: 1.375rem;
  background: #0D1214;
  border-radius: var(--r-pill);
  z-index: 3;
}

.phone-screen {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 2.375rem;
  background: var(--ob-wash-top);
  isolation: isolate;
}

.phone-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The duotone. Multiply keeps the church's masonry readable underneath instead
   of flooding it — a flat opaque gradient would hide the photograph entirely,
   and the point of the screen is that it IS a Bulacan photograph. */
.phone-wash {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    var(--ob-wash-top) 0%,
    var(--ob-wash-mid) 55%,
    var(--ob-wash-bottom) 100%
  );
  mix-blend-mode: multiply;
  opacity: 0.92;
}

/* White text over a light wash measures ~1.3:1 on its own. This band sits
   behind the headline only and lifts it past 7:1 without darkening the whole
   composition. Same trick, same stops as the app. */
.phone-band {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    transparent 18%,
    rgba(12,34,36,0.68) 34%,
    rgba(12,34,36,0.68) 74%,
    transparent 92%
  );
}

.phone-content {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* Padding and type scale with the frame, which is now viewport-dependent in
     two directions — a fixed 34px headline overflowed the screen as soon as
     the height cap kicked in. cqw is the frame's own width. */
  container-type: inline-size;
  padding: 15% 7% 8%;
}

.phone-headline {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 12.5cqw;
  line-height: 1.16;
  letter-spacing: -0.02em;
  color: var(--ob-on-photo);
  text-shadow: 0 2px 12px rgba(12,34,36,0.45);
  font-variation-settings: 'opsz' 144, 'WONK' 1;
}
.phone-headline .accent { color: var(--ob-cta); }

.phone-bottom { display: grid; gap: 5.5cqw; }

.phone-dots { display: flex; gap: 2.5cqw; justify-content: center; }
.phone-dots i { width: 2.5cqw; height: 2.5cqw; border-radius: 50%; background: #FFFFFF; }
.phone-dots i.on { background: var(--ob-cta); }

.phone-cta {
  height: 16cqw;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ob-cta);
  color: var(--ob-on-cta);
  border-radius: 5cqw;
  font-weight: 700;
  font-size: 5.5cqw;
  box-shadow: 0 6px 16px rgba(74,66,0,0.28);
}

/* ── Live stats strip ─────────────────────────────────────────────────────── */
/* Centred spec figures under the hero, the way a product page states what the
   thing contains. Real counts from LibotBackend; the whole section removes
   itself if the API is unreachable rather than showing zeros.

   auto-fit is right here and wrong in the bento below: these are four
   interchangeable figures of equal weight, so letting the browser decide how
   many fit per row costs nothing and removes a breakpoint. */
.stats { padding-block: var(--sp-4xl); }

/* 9.5rem, not 11: at 11rem two columns need 376px of track and gap, which a
   390px phone does not have once the gutters are paid for, so all four figures
   stacked into a single column and the strip became a screen and a half of
   scrolling. 9.5rem holds the 2 / 4 column split the design wants. */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(9.5rem, 100%), 1fr));
  gap: var(--sp-2xl) var(--sp-xl);
  text-align: center;
}

.stat-value {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--step-5);
  line-height: 1;
  color: var(--brand);
  font-variation-settings: 'opsz' 144, 'WONK' 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
}
.stat-label {
  font-size: var(--step--1);
  color: var(--text-muted);
  margin-top: var(--sp-sm);
  text-wrap: balance;
}

/* No opacity. Muted text at 80% composites to 3.6:1 on white, which fails AA
   at any size this label would ever be set in. */
.stats-note {
  margin-top: var(--sp-2xl);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ══ FEATURE MOMENTS ════════════════════════════════════════════════════════
   The centre of this design. Each of the three things that matter gets a
   full-width section to itself: eyebrow, one short headline, about forty words,
   and a large visual. No grid, no cards, no competing for attention.

   Sections alternate white / cream, which is the only separator they get — no
   rules, no dividers.

   The three are deliberately NOT the same width (see rule 2 at the top):
   moment 1 sits at page width, moment 2 runs wider than the text column, and
   moment 3 is pulled in narrow. */
.moment { padding-block: var(--sp-5xl); }

.moment-head { text-align: center; }

.moment-title {
  font-size: var(--step-5);
  font-weight: 900;
  line-height: 1.06;
  letter-spacing: -0.032em;
  font-variation-settings: 'opsz' 120, 'SOFT' 0, 'WONK' 1;
  max-width: 18ch;
  margin-inline: auto;
  text-wrap: balance;
}

.moment-copy {
  margin: var(--sp-xl) auto 0;
  max-width: 52ch;
  font-size: var(--step-1);
  color: var(--text-secondary);
  text-wrap: pretty;
}

/* The visual. A soft tinted panel holding a large inline SVG diagram.
   Deliberately DIAGRAMMATIC and not a mocked-up app screen: inventing a
   screenshot of a screen that may not look like that would be lying about what
   someone is downloading, which is the same reason the gallery section stays
   hidden until config.js lists real ones. */
.stage {
  /* The diagram's legibility depends on how wide THIS box is, not how wide the
     window is — the two stopped agreeing the moment the moments got different
     widths. Everything below queries the container. */
  container: stage / inline-size;

  margin-top: var(--sp-3xl);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xl);
  padding: var(--sp-2xl) var(--sp-xl);
  background:
    radial-gradient(90% 120% at 50% 0%, var(--brand-soft), transparent 70%),
    var(--card-bg);
  border-radius: var(--r-stage);
}
/* On a cream section the panel goes white, so it still reads as a surface
   sitting on the page rather than merging into it. */
.section-alt .stage {
  background:
    radial-gradient(90% 120% at 50% 0%, var(--brand-soft), transparent 70%),
    var(--card-bg-alt);
}

/* The three widths.

   The drawing has to grow with the panel, not just sit in the middle of a
   bigger one: capped at 760px inside the 1320px wide panel it left 224px of
   empty tint on each side, which reads as a mistake rather than as space. */
.moment--inset .stage { max-width: 48rem; margin-inline: auto; }

.stage svg {
  width: min(47.5rem, 100%);
  height: auto;
  overflow: visible;
}
.moment--wide .stage svg { width: min(60rem, 100%); }

/* One small set of classes drives every diagram: strokes inherit the brand,
   "paper" fills are the page colour. Means all three re-colour correctly in
   dark mode for free, and none of them carries a hardcoded hex.

   These are CLASSES rather than SVG presentation attributes on purpose —
   `stroke="var(--brand)"` as an attribute is not reliably supported, while a
   CSS rule setting the same property always is. */
.dia-line   { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.dia-soft   { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linecap: round; opacity: 0.35; }
.dia-faint  { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linecap: round; opacity: 0.13; }
.dia-dash   { fill: none; stroke: var(--brand); stroke-width: 2.5; stroke-linecap: round; stroke-dasharray: 1 9; }
.dia-fill   { fill: var(--brand); opacity: 0.10; }
.dia-solid  { fill: var(--brand); }
.dia-accent { fill: var(--accent); }
.dia-paper  { fill: var(--bg); }
/* A tick knocked out of a filled brand circle, so it has to be the page colour
   rather than a light grey — that is the only value that stays legible in both
   themes (white on teal, near-black on cyan). */
.dia-check  { fill: none; stroke: var(--bg); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
/* The one px font-size left in the file, and the only correct one: this is 13
   USER UNITS in a 560-unit viewBox, not 13 CSS pixels. It scales with the
   drawing. What it must never do is get so small that it stops being legible —
   see the container query below. */
.dia-label  {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  fill: var(--text-secondary);
}

/* ── Diagram callouts, and what replaces them ─────────────────────────────
   The labels live in a 560-unit viewBox that scales with the panel. Once the
   panel is narrower than the viewBox the whole drawing shrinks, and a 13-unit
   label stops being 13px: at 320px of panel it renders at about 7px, which is
   not small type, it is unreadable type.

   Below 32rem of panel the callouts are dropped and the same words are
   rendered underneath as real HTML, at a real font size. The drawing keeps its
   shape, the labels keep their meaning, and neither is squinted at. Screen
   readers were never affected either way — the <svg> carries an aria-label
   that says the whole thing in a sentence. */
.stage-legend { display: none; }

@container stage (max-width: 32rem) {
  .dia-callout { display: none; }

  .stage-legend {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-sm) var(--sp-md);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .stage-legend li {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.75rem;
    border-radius: var(--r-pill);
    background: var(--bg);
    color: var(--text-secondary);
    font-size: var(--step--1);
    font-weight: 600;
  }
  .section-alt .stage-legend li { background: var(--bg-alt); }
  .stage-legend li::before {
    content: '';
    width: 0.4375rem;
    height: 0.4375rem;
    border-radius: 50%;
    background: var(--brand);
  }
}

/* ══ BENTO ══════════════════════════════════════════════════════════════════
   Everything the app also does. The spans are CHOSEN — the two things people
   ask about most (badges, and what still works without AR) get the wide slots.
   This is not `auto-fit`; the browser does not get to decide how many equal
   boxes fit.

   The row of four equal rounded cards is the single most recognisable shape on
   a generated landing page, and an even 4/2/2/4 is only one step away from it.
   These are 4/2 and 2/4 on alternating rows and the second row is nudged down,
   so the grid reads as arranged rather than filled. */
.bento {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-lg);
}

.bento-item {
  padding: var(--sp-2xl);
  background: var(--card-bg);
  border-radius: var(--r-card);
  transition: background-color 0.2s ease, transform 0.2s ease;
}
.section-alt .bento-item { background: var(--card-bg-alt); }

@media (hover: hover) {
  .bento-item:hover { transform: translateY(-2px); background: var(--card-bg-hover); }
}

@media (min-width: 48rem) {
  .bento { grid-template-columns: repeat(6, 1fr); }
  .bento-item:nth-child(1) { grid-column: span 4; }
  .bento-item:nth-child(2) { grid-column: span 2; }
  .bento-item:nth-child(3) { grid-column: span 2; }
  .bento-item:nth-child(4) { grid-column: span 4; }
}

/* The stagger. Only once there is room for it to read as intentional rather
   than as a mistake. */
@media (min-width: 64rem) {
  .bento-item:nth-child(3),
  .bento-item:nth-child(4) { margin-top: calc(-1 * var(--sp-2xl)); }
  .bento { padding-bottom: var(--sp-2xl); }
}

.bento-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.875rem;
  height: 2.875rem;
  margin-bottom: var(--sp-lg);
  background: var(--brand-soft);
  color: var(--brand);
  border-radius: 0.875rem;
}
.bento-ico .ico { font-size: 1.4375rem; }

.bento-item h3 {
  font-size: var(--step-2);
  margin-bottom: var(--sp-sm);
  font-variation-settings: 'opsz' 48, 'WONK' 1;
}
.bento-item p { color: var(--text-secondary); font-size: var(--step--1); }

/* ── Gallery ──────────────────────────────────────────────────────────────── */
/* Hidden until config.js lists real screenshots. Three phones, the middle one
   raised, straight on. */
.gallery {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--sp-2xl);
  flex-wrap: wrap;
}
.gallery img {
  width: min(15.625rem, 70vw);
  height: auto;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
/* The lift only works while the three are on one line. Once they wrap it is
   just one picture sitting oddly. */
@media (min-width: 48rem) {
  .gallery img:nth-child(2) { translate: 0 -1.5rem; }
}

/* ── Reviews ──────────────────────────────────────────────────────────────── */
/* Real reviews pulled live from the app's database. These are reviews OF
   TOURIST SPOTS written by people using Libot — not reviews of the app — so
   every card names the spot it is about. That is content, not styling, and it
   does not change. */
.reviews {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  gap: var(--sp-lg);
}

.review {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
  margin: 0; /* <figure> carries a 40px default inline margin */
  padding: var(--sp-2xl);
  background: var(--card-bg);
  border-radius: var(--r-card);
}
.section-alt .review { background: var(--card-bg-alt); }

.review-text {
  flex: 1;
  margin: 0;
  color: var(--text-primary);
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.5;
  letter-spacing: -0.01em;
  font-variation-settings: 'opsz' 36, 'SOFT' 0, 'WONK' 1;
}

.review-foot { display: flex; align-items: center; gap: 0.6875rem; margin-top: var(--sp-sm); }
.review-avatar {
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  background: var(--brand-soft);
  color: var(--brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8438rem;
  font-weight: 700;
}
.review-name { font-size: var(--step--1); font-weight: 600; }
.review-spot { font-size: var(--step--1); color: var(--text-muted); }

/* ── Download card ────────────────────────────────────────────────────────── */
/* Container, not viewport: this card is the same shape whether it is sitting in
   a 1120px page or somewhere narrower, and it should collapse when IT runs out
   of room. */
.download-card {
  container: download / inline-size;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-2xl);
  align-items: start;
  padding: var(--sp-2xl);
  background: var(--card-bg);
  border-radius: var(--r-xl);
}
.section-alt .download-card { background: var(--card-bg-alt); }

@container download (min-width: 46rem) {
  .download-card {
    grid-template-columns: 1fr 18rem;
    gap: var(--sp-3xl);
  }
}

.download-main .section-head { text-align: left; margin-bottom: var(--sp-xl); }
.download-main .lede { margin-inline: 0; }

.download-note {
  margin-top: var(--sp-md);
  font-size: var(--step--1);
  color: var(--text-muted);
  text-align: center;
  text-wrap: pretty;
}

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(8rem, 45%), 1fr));
  gap: var(--sp-lg);
  margin: var(--sp-xl) 0 0;
  padding-top: var(--sp-xl);
  border-top: 1px solid var(--divider);
}
.facts dt {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.facts dd {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.checksum {
  margin-top: var(--sp-xl);
  padding: var(--sp-lg);
  background: var(--bg-alt);
  border-radius: var(--r-md);
}
.section-alt .checksum { background: var(--bg-alt); }
.checksum-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--step--1);
  font-weight: 600;
  margin-bottom: var(--sp-sm);
}
.checksum-label .ico { color: var(--brand); }
.checksum-row { display: flex; align-items: center; gap: var(--sp-sm); flex-wrap: wrap; }
.checksum-row code {
  flex: 1 1 14rem;
  min-width: 0;
  /* A 64-character hex string has no spaces to break at, and letting it force
     the card wider than the viewport is worse than wrapping mid-string. */
  overflow-wrap: anywhere;
  font-size: var(--step--2);
  color: var(--text-secondary);
}
.checksum-help { margin-top: var(--sp-sm); font-size: var(--step--1); color: var(--text-muted); }

/* ── QR panel ─────────────────────────────────────────────────────────────── */
.qr-panel {
  padding: var(--sp-xl);
  background: var(--bg-alt);
  border-radius: var(--r-lg);
  text-align: center;
}
.qr-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.875rem;
  /* Always white, never themed: a QR on a dark card fails to scan on most
     phone cameras, which expect dark-on-light. */
  background: #FFFFFF;
  border-radius: var(--r-md);
  margin-bottom: var(--sp-lg);
}
#qr img, #qr canvas, #qr svg { display: block; width: 100%; height: auto; }
.qr-label { font-weight: 700; font-size: 1rem; }
.qr-help  { font-size: var(--step--1); color: var(--text-muted); margin-top: 4px; }

/* On a phone the QR is pointless — you are already on the device you want the
   file on. Kept in the DOM, dropped from the layout. This one is a genuine
   VIEWPORT question ("what device is this?"), not a container question, so it
   stays a media query. */
@media (max-width: 47.99rem) {
  .qr-panel { display: none; }
}

/* ── Steps ────────────────────────────────────────────────────────────────── */
/* Three panels with large numerals. A row normally reads as a set of options
   rather than a sequence, which is why the numbers are big enough to be the
   first thing you see in each one. */
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--sp-lg);
}

.step {
  padding: var(--sp-2xl);
  background: var(--card-bg);
  border-radius: var(--r-card);
}
.section-alt .step { background: var(--card-bg-alt); }

.step-num {
  display: block;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--step-4);
  line-height: 1;
  color: var(--brand);
  opacity: 0.45;
  margin-bottom: var(--sp-lg);
  font-variation-settings: 'opsz' 96, 'WONK' 1;
}
.step h3 { font-size: var(--step-2); margin-bottom: var(--sp-sm); font-variation-settings: 'opsz' 48, 'WONK' 1; }
.step p  { color: var(--text-secondary); font-size: var(--step--1); }
.step strong { color: var(--text-primary); }
.step em { font-style: italic; }

.note {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  max-width: 62ch;
  margin: var(--sp-3xl) auto 0;
  padding: var(--sp-lg) var(--sp-xl);
  background: var(--brand-wash);
  border-radius: var(--r-md);
  font-size: var(--step--1);
  color: var(--text-secondary);
  text-wrap: pretty;
}
.note .ico { color: var(--brand); margin-top: 2px; }

/* ── Permissions ──────────────────────────────────────────────────────────── */
.perms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: var(--sp-2xl);
  text-align: center;
}
.perm .ico { font-size: 1.75rem; color: var(--brand); margin-inline: auto; }
.perm h3 { font-size: var(--step-1); margin: var(--sp-md) 0 6px; font-variation-settings: 'opsz' 36, 'WONK' 1; }
.perm p  { font-size: var(--step--1); color: var(--text-secondary); text-wrap: pretty; }

/* ── FAQ ──────────────────────────────────────────────────────────────────── */
.faq { display: grid; gap: var(--sp-sm); }

.faq details {
  background: var(--card-bg);
  border-radius: var(--r-md);
  overflow: hidden;
}
.section-alt .faq details { background: var(--card-bg-alt); }

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: var(--sp-lg) var(--sp-xl);
  font-weight: 600;
  font-size: var(--step-0);
  cursor: pointer;
  list-style: none;
  transition: color 0.15s ease;
}
.faq summary:hover { color: var(--brand); }
.faq summary::-webkit-details-marker { display: none; }

/* A plus that becomes a minus, drawn in CSS so it can animate. 135deg rather
   than 45: the vertical bar lands flat AND the mark has visibly travelled,
   which a 45deg turn hides by symmetry. */
.faq summary::after {
  content: '';
  flex: none;
  width: 0.8125rem;
  height: 0.8125rem;
  background:
    linear-gradient(currentColor, currentColor) center/0.8125rem 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center/1.5px 0.8125rem no-repeat;
  color: var(--text-muted);
  transition: rotate 0.25s cubic-bezier(0.2, 0.7, 0.3, 1), color 0.15s ease;
}
.faq summary:hover::after { color: var(--brand); }
.faq details[open] summary::after { rotate: 135deg; }

.faq details p {
  padding: 0 var(--sp-xl) var(--sp-lg);
  color: var(--text-secondary);
  font-size: var(--step--1);
  text-wrap: pretty;
}

/* ── Changelog ────────────────────────────────────────────────────────────── */
.changelog-wrap { margin-top: var(--sp-4xl); }
.changelog-title {
  font-size: var(--step-3);
  margin-bottom: var(--sp-xl);
  text-align: center;
  font-variation-settings: 'opsz' 48, 'WONK' 1;
}

.release {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-sm);
  padding: var(--sp-lg) 0;
}
.release + .release { border-top: 1px solid var(--divider); }

.release-head {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-md);
}
.release-ver {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.release-date { font-size: var(--step--1); color: var(--text-muted); }
.pill-latest {
  padding: 2px 9px;
  background: var(--accent-soft);
  color: var(--text-primary);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.release ul {
  margin: 0;
  padding-left: 1.125rem;
  color: var(--text-secondary);
  font-size: var(--step--1);
  list-style: none;
}
.release li { margin-bottom: 4px; position: relative; }
/* An en dash instead of a bullet — a release note is a line in a log. */
.release li::before {
  content: '–';
  position: absolute;
  left: -1.125rem;
  color: var(--text-muted);
}

@media (min-width: 40rem) {
  .release {
    grid-template-columns: 9.375rem 1fr;
    gap: var(--sp-xl);
  }
  .release-head { flex-direction: column; align-items: flex-start; gap: 5px; }
}

/* ── Closing CTA ──────────────────────────────────────────────────────────── */
.closer {
  text-align: center;
  padding-block: var(--sp-5xl);
  background: linear-gradient(180deg, var(--bg), var(--bg-alt));
}
.closer-icon {
  width: 4.75rem;
  height: 4.75rem;
  margin: 0 auto var(--sp-xl);
  border-radius: 1.25rem;
  box-shadow: var(--shadow-md);
}
.closer .lede { margin-bottom: var(--sp-2xl); }
.closer-fine {
  margin-top: var(--sp-lg);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  color: var(--text-muted);
  text-wrap: balance;
}

/* ── Footer ───────────────────────────────────────────────────────────────── */
.site-footer {
  background: var(--bg-alt);
  border-top: 1px solid var(--divider);
  padding-block: var(--sp-2xl);
  padding-bottom: max(var(--sp-2xl), env(safe-area-inset-bottom));
}

.footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-lg);
  flex-wrap: wrap;
}

.footer-brand { display: flex; align-items: center; gap: 0.75rem; }
.footer-brand strong { display: block; font-size: 1rem; }
.footer-brand span   { font-size: var(--step--1); color: var(--text-muted); }

.footer-links { display: flex; flex-wrap: wrap; gap: var(--sp-sm) var(--sp-lg); }
.footer-links a {
  display: inline-flex;
  align-items: center;
  /* These are the smallest tap targets on the page and they sit next to each
     other. 44px of height each, so a thumb cannot land between two of them. */
  min-height: 2.75rem;
  color: var(--text-secondary);
  text-decoration: none;
  font-size: var(--step--1);
  cursor: pointer;
}
.footer-links a:hover { color: var(--text-primary); }

.footer-legal {
  width: 100%;
  padding-top: var(--sp-lg);
  border-top: 1px solid var(--divider);
  font-size: var(--step--1);
  color: var(--text-muted);
}

/* ── Print ────────────────────────────────────────────────────────────────── */
/* Someone printing this is printing the install instructions to follow on a
   phone that is not the one they are reading on. Keep the steps, the facts and
   the checksum; drop everything that is a picture of the product. */
@media print {
  :root {
    --bg: #FFFFFF; --bg-alt: #FFFFFF;
    --card-bg: #FFFFFF; --card-bg-alt: #FFFFFF;
    --text-primary: #000000; --text-secondary: #1A1A1A; --text-muted: #3A3A3A;
    --brand: #0A6F78;
  }
  .site-header, .section-rail, .hero-stage, .stage, .gallery,
  .qr-panel, .closer, .skip-link, .cta-row, .proof { display: none !important; }

  body { font-size: 11pt; line-height: 1.45; }
  .section, .moment { padding-block: 1.5rem; }
  .section-title, .moment-title, .hero-title { font-size: 18pt; }

  /* An expander that is shut prints as a heading with nothing under it. */
  .faq details { display: block; }
  .faq details > p { display: block !important; }
  .faq summary::after { display: none; }

  .step, .review, .bento-item, .download-card { break-inside: avoid; border: 1px solid #CCC; }

  /* A link is not clickable on paper — say where it went. */
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 9pt; word-break: break-all; }
}
