/* Street Ridr: every page's stylesheet. The one-pager first, then, under
   "Pages", the legal pages and the 404, on the same tokens, nav and footer.
   Quiet surfaces and real devices. The page brings no colour of its own: the
   colour is in the screenshots, the hero's bikes and the brand green on a
   word. Cards on a warm ground, the way Apple and Flighty lay out a
   product. Light and dark from one set of tokens. */

/* The one font: Inter Variable 4.001 (OFL 1.1, font/LICENSE.txt), every
   weight from 100 to 900 and an optical-size axis the browser sets from the
   font size. Everything on the page is set in it, labels and figures
   included: the owner asked for Inter and nothing else (27 Sep 2026). */
@font-face{
  font-family:"Inter"; font-weight:100 900; font-style:normal; font-display:swap;
  src:url("font/InterVariable.woff2") format("woff2");
}

/* ---------- Tokens ---------- */
:root{
  color-scheme:light dark;
  --ground:#F5F5F2;
  --paper:#FFFFFF;
  --ink:#14161A;
  --ink-2:#565B61;
  --ink-3:#6A6E73;            /* quiet text, still AA: 4.7:1 on --ground, 5.1:1 on --paper */
  --brand:#6BDA39;
  --brand-lo:#1A9713;
  --accent:#2E7A1E;            /* the green as text, AA on --ground */
  /* The hero's city. home.js hands these to a canvas, which cannot read
     color-mix(), so they are plain colours: ink at a few percent for the
     blocks and the park, a little more for the park's trees and the pond,
     and for the stations. Bikes and e-bikes are the app's: system green, and
     its electric accent (Color+Utilities.swift). --city-scale sizes the
     streets and blocks. */
  --city-scale:1;
  --city-block:rgba(20,22,26,.055);
  --city-park:rgba(20,22,26,.028);
  --city-line:rgba(20,22,26,.16);
  --city-station:rgba(20,22,26,.3);
  --bike:#34C759;
  --ebike:#007A99;
  --onyx:#1B1D20;              /* the one dark card, behind the watch: light
                                  enough that a black band keeps its edge */
  --hair:color-mix(in oklab, var(--ink) 12%, transparent);
  --hair-2:color-mix(in oklab, var(--ink) 7%, transparent);
  --card-edge:none;            /* a feature card's edge: needed on the dark page only */
  --focus:#0B62D6;             /* the dark palette's is lighter: this one is 3.4:1 on its ground */
  --outline:color-mix(in oklab, var(--ink) 50%, transparent);   /* a control's edge: 3.4:1 on --ground and on --paper */

  --sans:"Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;

  --shell:1120px; --gutter:24px;
  --sec:clamp(96px, 11vw, 160px);
  --r:20px; --r-lg:30px; --pill:999px;

  --ease:cubic-bezier(.4,0,0,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --spring:cubic-bezier(.34,1.56,.64,1);
  --fast:120ms; --mid:260ms; --slow:700ms;
}
@media (min-width:900px){ :root{ --gutter:48px } }
/* Dark: the system's, unless the visitor picked light with the switch in the
   nav; or the one they picked (nav.js, "Theme", sets data-theme). CSS can't
   say "this media or that attribute" in one rule, so the palette is here
   twice: the two blocks must stay the same. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --ground:#0D0F11; --paper:#17191C;
    --ink:#F2F3F2; --ink-2:#A9AEB3; --ink-3:#848B92;
    --accent:#6BDA39;
    --onyx:var(--paper);
    --city-block:rgba(255,255,255,.045);
    --city-park:rgba(255,255,255,.022);
    --city-line:rgba(255,255,255,.14);
    --city-station:rgba(255,255,255,.26);
    --bike:#30D158;
    --ebike:#64D2FF;
    --hair:color-mix(in oklab, #fff 13%, transparent);
    --hair-2:color-mix(in oklab, #fff 7%, transparent);
    --card-edge:inset 0 0 0 1px rgb(255 255 255 / .06);
    --focus:#5AA9FF;
    --outline:color-mix(in oklab, var(--ink) 40%, transparent);   /* 3.5:1 */
  }
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --ground:#0D0F11; --paper:#17191C;
  --ink:#F2F3F2; --ink-2:#A9AEB3; --ink-3:#848B92;
  --accent:#6BDA39;
  --onyx:var(--paper);
  --city-block:rgba(255,255,255,.045);
  --city-park:rgba(255,255,255,.022);
  --city-line:rgba(255,255,255,.14);
  --city-station:rgba(255,255,255,.26);
  --bike:#30D158;
  --ebike:#64D2FF;
  --hair:color-mix(in oklab, #fff 13%, transparent);
  --hair-2:color-mix(in oklab, #fff 7%, transparent);
  --card-edge:inset 0 0 0 1px rgb(255 255 255 / .06);
  --focus:#5AA9FF;
  --outline:color-mix(in oklab, var(--ink) 40%, transparent);   /* 3.5:1 */
}
:root[data-theme="light"]{ color-scheme:light }

/* ---------- Base ---------- */
*{ box-sizing:border-box }
html{ scroll-behavior:smooth; -webkit-text-size-adjust:100% }
body{
  margin:0; background:var(--ground); color:var(--ink-2);
  font:16px/1.6 var(--sans); letter-spacing:-.005em;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; overflow-x:clip;
}
h1,h2,h3,h4{ color:var(--ink); margin:0; text-wrap:balance }
p{ margin:0; text-wrap:pretty }
a{ color:inherit }
img,svg{ display:block; max-width:100% }
figure{ margin:0 }
ul{ margin:0; padding:0; list-style:none }
::selection{ background:var(--brand); color:#0D0F11 }
:focus-visible{ outline:2px solid var(--focus); outline-offset:3px; border-radius:4px }
.skip{ position:absolute; left:16px; top:-48px; z-index:100; padding:10px 16px;
  background:var(--ink); color:var(--ground); border-radius:var(--pill); text-decoration:none }
.skip:focus{ top:16px }
.shell{ width:min(var(--shell), 100% - var(--gutter) * 2); margin-inline:auto }
.narrow{ max-width:760px }
/* For screen readers only: a heading the page no longer shows. */
.sr-only{ position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip-path:inset(50%);
  white-space:nowrap; border:0 }

/* ---------- Type ---------- */
.display{ font:760 clamp(46px, 7vw, 96px)/.98 var(--sans); letter-spacing:-.035em; color:var(--ink) }
.h2{ font:700 clamp(36px, 4.6vw, 60px)/1.02 var(--sans); letter-spacing:-.028em; color:var(--ink) }
.lede{ font-size:clamp(18px, 1.5vw, 21px); line-height:1.5; color:var(--ink-2) }
.body{ font-size:17px; line-height:1.6; max-width:46ch; color:var(--ink-2) }
.body a, .q-a a{ color:var(--ink); text-decoration-color:color-mix(in oklab, var(--accent) 60%, transparent);
  text-decoration-thickness:.06em; text-underline-offset:.14em }
.body a:hover, .q-a a:hover{ text-decoration-color:var(--accent) }
.g{ color:var(--accent) }
.num{ font-variant-numeric:tabular-nums }

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex; align-items:center; gap:10px; padding:12px 20px; border-radius:var(--pill);
  font:500 15px/1 var(--sans); letter-spacing:-.005em; text-decoration:none; border:1px solid transparent;
  cursor:pointer; transition:transform var(--mid) var(--ease), background-color var(--fast), border-color var(--fast), color var(--fast);
}
.btn:active{ transform:scale(.98) }
.btn-lg{ padding:16px 26px; font-size:16px }
.btn-primary{ background:var(--ink); color:var(--ground) }
.btn-primary:hover{ background:color-mix(in oklab, var(--ink) 86%, var(--ground)) }
/* An outlined button's edge is a control's, 3:1 on the ground or a card. */
.btn-ghost{ border-color:var(--outline); color:var(--ink) }
.btn-ghost:hover{ border-color:var(--ink) }
.btn-row{ display:flex; gap:14px; justify-content:center; flex-wrap:wrap }
/* Apple's own "Download on the App Store" badge, the black one, as Apple
   ships it (img/app-store/, fills written as attributes for the CSP). Apple's
   rules: at least 40px tall, clear space of a quarter of its height (the
   row's 14px gap), one badge in view at a time, below the main message, never
   modified or animated, and it links to the app's page. So the nav keeps a
   button of the site's own. */
.store{ display:inline-flex; flex:none; border-radius:9px }
.store img{ display:block; width:auto; height:50px }

/* ---------- Nav ----------
   A pane of glass above the page: capped at the content width, a hairline on
   every edge, translucent and blurred. */
.nav-container{ position:sticky; top:0; z-index:50; padding:14px 0; pointer-events:none }
.nav{
  width:min(var(--shell), 100% - var(--gutter) * 2); margin-inline:auto; pointer-events:auto;
  background:color-mix(in oklab, var(--ground) 74%, transparent);
  -webkit-backdrop-filter:blur(18px) saturate(1.3); backdrop-filter:blur(18px) saturate(1.3);
  border:1px solid var(--hair); border-radius:var(--pill);
  transition:background-color var(--fast) var(--ease);
}
.scrolled .nav{ background:color-mix(in oklab, var(--ground) 88%, transparent) }
.nav-in{ display:flex; align-items:center; gap:22px; padding:8px 8px 8px 18px }
.brand{ display:flex; align-items:center; gap:10px; text-decoration:none; color:var(--ink) }
.brand .mark{ width:26px; height:26px }
.wordmark{ font:640 16px/1 var(--sans); letter-spacing:-.02em }
.nav-links{ display:none; gap:4px; margin-left:auto }
.nav-links a{ text-decoration:none; color:var(--ink-2); font:500 14px/1 var(--sans); padding:10px 12px; border-radius:var(--pill);
  transition:color var(--fast), background-color var(--fast) }
.nav-links a:hover{ color:var(--ink); background:var(--hair-2) }
.nav-links a[aria-current="page"]{ color:var(--ink) }
.nav .btn{ flex:none; margin-left:auto; height:36px; padding:0 16px; font-size:14px; white-space:nowrap }
.brand{ flex:none }
@media (min-width:760px){ .nav-links{ display:flex } .nav .btn{ margin-left:0 } }
/* Where the bar is tight it tightens: just above 760px, where the links
   join it (the French ones, longer, a little closer together), and on the
   narrowest phones, where under 360px the brand is its mark alone (the link
   keeps the name), so the button keeps its words. */
@media (min-width:760px) and (max-width:899px){ .nav-in{ gap:16px } :lang(fr) .nav-links a{ padding-inline:10px } }
@media (max-width:399px){ .nav-in{ gap:8px; padding-left:14px } .nav .btn{ padding:0 14px } }
@media (max-width:359px){ .nav .wordmark{ display:none } }
/* The nav's controls are drawn 36px tall and answer a tap a few pixels
   round them, so each target is 44px, Apple's minimum. The bar's own
   padding holds the extra, and no two targets overlap. */
.nav .btn, .motion, .burger{ position:relative }
.nav .btn::after, .theme::after, .motion::after{ content:""; position:absolute; inset:-5px; border-radius:var(--pill) }
.burger::after{ content:""; position:absolute; inset:-4px; border-radius:50% }

/* The theme switch: light or dark over the system's (nav.js, "Theme"). A
   knob in ink, like the nav's button beside it, slides under the sun or
   the moon. It needs script, so without it the page follows the system and
   there is no switch. On a phone it is in the menu. */
.theme{ position:relative; display:none; flex:none; align-items:center; justify-content:space-between;
  width:68px; height:36px; padding:0 10px; border:1px solid var(--hair); border-radius:var(--pill);
  background:var(--hair-2); color:var(--ink-3); cursor:pointer; -webkit-tap-highlight-color:transparent;
  transition:border-color var(--fast) }
.js .theme{ display:inline-flex }
.theme:hover{ border-color:color-mix(in oklab, var(--ink) 28%, transparent) }
.theme::before{ content:""; position:absolute; top:3px; left:3px; width:28px; height:28px; border-radius:50%;
  background:var(--ink); transition:translate var(--mid) var(--ease-out), background-color var(--mid) var(--ease) }
.theme[aria-checked="true"]::before{ translate:32px 0 }
.theme svg{ position:relative; width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:1.8;
  stroke-linecap:round; stroke-linejoin:round; transition:color var(--mid) var(--ease) }
.theme[aria-checked="false"] .theme-sun, .theme[aria-checked="true"] .theme-moon{ color:var(--ground) }

/* The motion button: pauses the page's motion, or plays it again (nav.js,
   "Motion"). A round button as tall as the switch and dressed like it,
   close beside it: the two are how the visitor wants the page. Pause bars,
   or once pressed the play triangle, since its name says what pressing it
   will do. Like the switch it needs script; with reduced motion nothing
   moves anyway, so there is no button (see "Reduced motion"). On a phone it
   is in the menu. */
.motion{ display:none; flex:none; place-items:center; width:36px; height:36px; padding:0;
  border:1px solid var(--hair); border-radius:50%; background:var(--hair-2); color:var(--ink-2);
  cursor:pointer; -webkit-tap-highlight-color:transparent; transition:border-color var(--fast), color var(--fast) }
.js .motion{ display:grid }
.theme + .motion{ margin-left:-12px }
@media (min-width:760px) and (max-width:899px){ .theme + .motion{ margin-left:-8px } }   /* where the bar's gap is 16px: their targets just touch */
.motion:hover{ border-color:color-mix(in oklab, var(--ink) 28%, transparent); color:var(--ink) }
.motion svg{ grid-area:1 / 1; width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:1.8;
  stroke-linecap:round; stroke-linejoin:round }
.motion .motion-play, :root[data-motion="paused"] .motion .motion-pause{ display:none }
:root[data-motion="paused"] .motion .motion-play{ display:block }

/* The menu, on a phone: under 760px the links don't fit in the bar, so a
   burger opens them in a pane of the same glass hanging under it, with the
   theme switch and the motion button. A <details>, so it opens without
   script; nav.js closes it on a link, a tap outside, or Escape. While it
   is open the bar and the
   pane are solid paper, one object: inside the bar's own backdrop-filter
   the pane's blur would have nothing behind it to blur, and the page's
   headline showed through. */
.nav{ position:relative }
.nav-menu > summary{ list-style:none }
.nav-menu > summary::-webkit-details-marker{ display:none }
.burger{ display:grid; place-items:center; width:36px; height:36px; border-radius:50%; cursor:pointer;
  color:var(--ink); background:var(--hair-2); -webkit-tap-highlight-color:transparent;
  transition:background-color var(--fast) }
.burger:hover{ background:var(--hair) }
.burger span{ grid-area:1 / 1; width:16px; height:1.5px; border-radius:1px; background:currentColor;
  transition:translate var(--mid) var(--ease-out), rotate var(--mid) var(--ease-out) }
.burger span:first-child{ translate:0 -3.5px }
.burger span:last-child{ translate:0 3.5px }
.nav-menu[open] .burger span:first-child{ translate:0 0; rotate:45deg }
.nav-menu[open] .burger span:last-child{ translate:0 0; rotate:-45deg }
.nav:has(.nav-menu[open]){ background:var(--paper) }
.nav-panel{ position:absolute; top:calc(100% + 8px); left:-1px; right:-1px; padding:8px;
  border:1px solid var(--hair); border-radius:26px; background:var(--paper);
  transition:opacity var(--mid) var(--ease), translate var(--mid) var(--ease-out) }
@starting-style{ .nav-menu[open] .nav-panel{ opacity:0; translate:0 -8px } }
.nav-panel-links a{ display:block; padding:14px; border-radius:18px; text-decoration:none;
  font:600 20px/1.2 var(--sans); letter-spacing:-.015em; color:var(--ink) }
.nav-panel-links a:hover{ background:var(--hair-2) }
.nav-panel-theme, .nav-panel-motion{ display:none; align-items:center; justify-content:space-between; gap:16px;
  margin:6px 6px 0; padding:14px 8px 8px; border-top:1px solid var(--hair);
  font:500 15px/1 var(--sans); color:var(--ink-2) }
.js .nav-panel-theme, .js .nav-panel-motion{ display:flex }
.nav-panel-motion{ margin-top:0; padding-top:6px; border-top:0 }
@media (min-width:760px){ .nav-menu{ display:none } }
@media (max-width:759px){
  .nav-in{ gap:10px }
  .nav-in > .theme, .nav-in > .motion{ display:none }
}

/* ---------- Hero ----------
   One screen: the claim, the app on iPhone and Apple Watch, and what to do
   next with the figures, over a street grid that bikes ride. home.js draws
   the grid and the bikes on two canvases and writes --p (0 at the top, 1
   when the hero has scrolled away), on which the words fall back. The city
   stays put: it used to zoom as the hero scrolled away, and scaling two
   full-screen canvases every frame made the scroll stutter (taken off 26 Sep
   2026). The devices never move either: Apple's rules for product images
   forbid animating them.
   In .hero-in, in this order: the claim (.hero-claim), the devices
   (.hero-trio), and the buttons and figures (.hero-act). Stacked, that puts
   the app before the buttons on a phone; from 1180px the claim and the act
   stand one over the other on the left, the devices beside them.
   The city is absolute and carries no grid placement on purpose: an absolute
   grid child that has one is positioned against its grid area, below the top
   padding, not the hero. */
.hero{ position:relative; min-height:100vh; min-height:100svh; margin-top:-84px; padding:132px 0 88px;
  display:grid; place-items:center; overflow:clip; --p:0 }
/* The city is drawn by home.js on two canvases, the map and the bikes;
   without script the hero is plain ground. place-self:stretch on the city and
   the ground below: the hero centres its items, and an absolute box told to
   centre is shrunk to its content rather than stretched to its insets. */
.hero-city{ position:absolute; inset:0; place-self:stretch; pointer-events:none }
.hero-city canvas{ position:absolute; inset:0; width:100%; height:100% }
.city-bikes{ opacity:0; transition:opacity 1.4s var(--ease) .3s }
.drawn .city-bikes{ opacity:1 }
/* A fade at the foot of the hero into the page. */
.hero::after{ content:""; position:absolute; inset:0; place-self:stretch; pointer-events:none;
  background:linear-gradient(to bottom, transparent 82%, var(--ground)) }

.hero-in{ position:relative; z-index:2; width:min(var(--shell), 100% - var(--gutter) * 2);
  display:grid; justify-items:center; align-items:center; gap:56px }
/* The words rise in once (claim-in), the act a beat after the claim. As the
   hero scrolls away the claim falls back; stacked, the act stays put, since
   it is far enough down the hero that it would be half gone by the time it
   came into view. Beside the devices it falls back with the claim. */
.hero-claim, .hero-act{ position:relative; text-align:center; max-width:760px;
  animation:claim-in 600ms var(--ease-out) 60ms backwards }
.hero-act{ animation-delay:160ms }
.hero-claim{ opacity:calc(1 - var(--p) * 1.3); translate:0 calc(var(--p) * -70px) }
@keyframes claim-in{ from{ opacity:0; translate:0 28px } }
/* The ground under the words: the streets and the bikes fade out behind
   them, so no trail runs under a letter. It travels with them. Stacked, a
   soft ellipse behind each; beside the devices, a band (below). */
.hero-claim::before, .hero-act::before{ content:""; position:absolute; inset:-16% -12%; z-index:-1; pointer-events:none;
  background:radial-gradient(closest-side, var(--ground) 64%, transparent) }
.hero-claim .display{ margin-bottom:22px }
.hero-claim .lede{ max-width:32em; margin-inline:auto }
.hero-act .btn-row{ align-items:center }
/* The easter egg's note (home.js, "The hero's city"): under the stats, out of
   the flow, so it moves nothing when it comes. */
.hero-egg{ position:absolute; left:50%; top:calc(100% + 22px); translate:-50% 10px; opacity:0; pointer-events:none;
  margin:0; padding:10px 16px; white-space:nowrap; border-radius:var(--pill); border:1px solid var(--hair);
  background:color-mix(in oklab, var(--ground) 82%, transparent); -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px);
  font:600 12px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink);
  transition:opacity .4s var(--ease), translate .6s var(--ease-out) }
.hero-egg.on{ opacity:1; translate:-50% 0 }
.hero-egg::before{ content:""; display:inline-block; width:7px; height:7px; margin-right:10px; border-radius:50%;
  background:var(--brand); vertical-align:1px }
.hero-claim > .hero-egg{ top:calc(100% + 6px) }   /* stacked, in the gap above the devices, clear of them */

/* The devices beside the words, from where both fit at a size worth having:
   the claim and under it the act on the left, set flush left, and the
   devices on the right (the other way round until 27 Sep 2026, when the
   owner asked for it). The two rows stretch to the devices' height, the
   claim sitting at the foot of the first and the act at the head of the
   second, so the words stand together, centred on the devices. */
@media (min-width:1180px){
  .hero-in{ grid-template-columns:minmax(0, 1fr) auto; justify-items:start; gap:34px clamp(48px, 5.5vw, 96px) }
  .hero-claim{ grid-area:1 / 1; align-self:end }
  .hero-act{ grid-area:2 / 1; align-self:start }
  .hero-claim, .hero-act{ justify-self:stretch }   /* one width, so the ground behind them has one edge */
  .hero-act{ opacity:calc(1 - var(--p) * 1.3); translate:0 calc(var(--p) * -70px) }
  .hero-trio{ grid-area:1 / 2 / 3; justify-self:end }
  .hero-claim, .hero-act{ text-align:left }
  .hero-claim .display{ font-size:clamp(56px, 5vw, 84px) }
  .hero-claim .lede{ margin-inline:0 }
  .hero-act .btn-row, .hero-act .stats{ justify-content:flex-start }
  .hero-act .stat{ text-align:left; min-width:0 }
  .hero-act .stat-l{ justify-content:flex-start }
  .hero-egg{ left:0; translate:0 10px }
  .hero-egg.on{ translate:0 0 }
  /* Here the ground is a band from the page's left edge, solid behind the
     whole column of words and fading out over 64px past it, short of the
     devices. An ellipse centred on the words left the trails running
     behind the first and last letters of each line. The claim's reaches
     down through the gap to the act's, and only the outer ends fade. */
  .hero-claim::before, .hero-act::before{ left:-100vw; right:-64px;
    background:linear-gradient(to left, transparent, var(--ground) 64px) }
  .hero-claim::before{ top:-72px; bottom:-34px;
    -webkit-mask:linear-gradient(transparent, #000 72px); mask:linear-gradient(transparent, #000 72px) }
  .hero-act::before{ top:0; bottom:-72px;
    -webkit-mask:linear-gradient(to top, transparent, #000 72px); mask:linear-gradient(to top, transparent, #000 72px) }
}

/* The stat row: figures whose digits roll in (home.js) as the row comes into
   view. Each digit's window and each step of its roll are a line tall (lh,
   with em for browsers without it), so with the text spaced out the window
   grows with the line and shows no sliver of the digits above or below. */
.stats{ display:flex; gap:clamp(24px, 4vw, 56px); justify-content:center; margin-top:48px; padding-top:28px;
  border-top:1px solid var(--hair); flex-wrap:wrap }
.stat{ text-align:center; min-width:96px }
.stat-n{ font:650 clamp(28px, 3.2vw, 40px)/1 var(--sans); letter-spacing:-.03em; color:var(--ink);
  display:inline-flex; height:1em; height:1lh; overflow:hidden; font-variant-numeric:tabular-nums }
.stat-n .d{ display:inline-block; height:1em; height:1lh; overflow:hidden }
.stat-n .d span{ display:block; translate:0 -100%; transition:translate 700ms var(--ease-out) }
.stat-n .d span i{ display:block; height:1em; height:1lh; font-style:normal }
.stat-n .d.set span{ translate:0 calc(var(--v) * -1em); translate:0 calc(var(--v) * -1lh) }
.stat-l{ font:600 11px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink-3); margin-top:10px;
  display:flex; justify-content:center; align-items:center; gap:8px }

/* ---------- Sections ---------- */
.section{ padding:var(--sec) 0 0 }
.section-head{ text-align:center; max-width:720px; margin:0 auto 56px }
.section-head .h2{ margin:0 0 16px }
.section-head .lede{ max-width:34em; margin-inline:auto }

/* ---------- Reveal ---------- */
.js .reveal{ opacity:0; translate:0 22px; transition:opacity var(--slow) var(--ease), translate var(--slow) var(--ease-out) }
.js .reveal.in{ opacity:1; translate:0 0 }
.js .reveal[data-delay="1"]{ transition-delay:90ms } .js .reveal[data-delay="2"]{ transition-delay:180ms } .js .reveal[data-delay="3"]{ transition-delay:270ms }
/* Tabbed to before its reveal has fired (a FAQ row far down the page, say),
   it shows at once: focus is never on something invisible. */
.js .reveal:focus-within{ opacity:1; translate:0 0; transition:none }
/* A heading's words rise into place one after another as it is revealed
   (home.js wraps them). Each word is masked by its own box, so it comes up
   out of the line rather than drifting in over the words beside it: a
   clip-path, not overflow, because an inline-block that clips its overflow
   takes its baseline from its bottom edge and would lift off the line. The
   mask reaches a little past the box for the accents and descenders. */
.kin .w{ display:inline-block; clip-path:inset(-.25em -.12em -.28em -.12em) }
.kin .w > span{ display:inline-block; translate:0 118%; transition:translate 1100ms var(--ease-out) calc(var(--i) * 70ms + 120ms) }
.in .kin .w > span, .kin.in .w > span{ translate:0 0 }

/* ---------- Devices ----------
   Every device is one of Apple's own product bezels with a real screenshot
   composited in ahead of time (.claude/build_devices.swift). Apple's rules for
   product images: no shadow, no crop, no tilt, nothing over the screen, and
   true relative scale whenever two products share a view. So a device gets
   a width and nothing else.

   --w is each image's real width in millimetres (canvas pixels over the
   device's own pixel density: 460ppi for the iPhones, 326ppi for the
   watches), so a width of calc(var(--w) * var(--mm)) draws every device at
   one physical scale. */
.dev{ display:block; width:100%; height:auto }
/* The <picture> around a themed device takes no box of its own, so the <img>
   is the flex item and its width resolves against the card or the row. */
picture{ display:contents }
.dev-pm{ --w:81.17 }    /* iPhone 18 Pro Max, 1470px at 460ppi */
.dev-duo{ --w:86.91 }   /* iPhone Duo closed, 1574px at 460ppi */
.dev-w12{ --w:43.63 }   /* Apple Watch 46mm, 560px at 326ppi */
.dev-wu4{ --w:46.75 }   /* Apple Watch Ultra, 600px at 326ppi */

/* The devices together, at one scale. Apple also asks for at least 200px of
   height per device on screen, which sets --floor, the least --mm may be,
   from the shortest image in the row: 2.68px for the Ultra (74.8mm tall),
   2.92px for the 46mm watch (68.6mm). --span is the row's width in
   millimetres, the devices plus a 14mm gap between each: 139 for the iPhone
   and the 46mm watch, which fit side by side at the floor from 454px. Under
   that they stand one over the other, still at one scale, rather than run
   off the screen: the row used to scroll sideways there, with the watch cut
   off at the edge and nothing to say there was more. The hero's pair
   overlaps instead (below). (With the iPhone Duo, off the page since 26 Sep
   2026, the span was 243 for three.) */
.trio-wrap{ container-type:inline-size; margin-top:8px }
.trio{ --span:139; --mm:clamp(var(--floor, 2.68px), calc((100cqi - var(--gutter) * 2) / var(--span)), 3.9px);
  display:flex; align-items:flex-end; gap:calc(var(--mm) * 14); width:max-content; margin-inline:auto;
  padding:0 var(--gutter) 4px }
.trio li{ display:flex; flex-direction:column; align-items:center; gap:22px }
.trio .dev{ width:calc(var(--w) * var(--mm)) }
.dev-cap{ font:600 11px/1 var(--sans); letter-spacing:.08em; text-transform:uppercase; color:var(--ink-3) }
.app .trio{ --floor:2.92px }
@media (max-width:453px){
  .app .trio{ --mm:clamp(2.92px, calc((100cqi - var(--gutter) * 2) / 81.17), 3.2px);
    flex-direction:column; align-items:center; gap:48px }
}
/* The hero's pair stands together: the watch a little in front of the
   phone's lower corner, overlapping it by --lap and standing --drop lower,
   so it covers the phone's frame and the sheet's margin and none of the app
   (the owner's call, 27 Sep 2026; they stood 14mm apart before). Both are
   still at one scale. --span is the pair's width, 81.17 + 46.75 - --lap.
   It is not in a scroll row, so its scale comes from the viewport, and
   stops growing at 3.6px. Beside the claim it takes about a quarter of a
   pixel per pixel of width, and never more than lets the pair (the iPhone's
   165.7mm and the drop) stand inside the hero's height. Under 370px the
   pair would be wider than the screen at the floor, so the hero shows the
   iPhone alone. */
.hero-trio{ --lap:9; --drop:6; --span:118.92; --mm:clamp(2.68px, calc((100vw - var(--gutter) * 2) / var(--span)), 3.4px);
  gap:0; padding:0 0 calc(var(--drop) * var(--mm)) }
.hero-trio li + li{ margin:0 0 calc(var(--drop) * var(--mm) * -1) calc(var(--lap) * var(--mm) * -1) }
@media (min-width:1180px){ .hero-trio{ --mm:clamp(2.68px, min(.26vw, (100svh - 260px) / 172), 3.6px) } }
@media (max-width:369px){
  .hero-trio{ --mm:calc(min(64vw, 260px) / 81.17); padding:0 }
  .hero-trio li + li{ display:none }
}

/* ---------- Videos ----------
   A screen recording plays over a composite's screen, clipped by a mask of
   exactly the screen the bezel shows (img/devices/<device>-mask.png, from
   build_devices.swift), so the bezel is never covered. The composite
   underneath is the feature's screenshot, a moment of the same take
   (.claude/video/stills.py), and the video stays transparent until it is
   actually playing (home.js adds .on), so before it starts, with reduced
   motion, without script, or if it fails, the still is what shows.
   The box is where the screen sits in the composite: the percentages the
   build prints.

   The recordings are off for now (the owner's call, 27 Sep 2026), and the
   screenshots are the page: data-videos="off" on <html> hides every clip,
   its button and its beats, and home.js loads none. Take the attribute off
   to bring them back. */
.dev-live{ position:relative }
.dev-video{ position:absolute; opacity:0; transition:opacity .5s var(--ease); object-fit:fill; pointer-events:none;
  -webkit-mask-size:100% 100%; mask-size:100% 100%; -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat }
.dev-video.on{ opacity:1 }
.dev-live.pm .dev-video{ left:5.102%; top:2.199%; width:89.796%; height:95.56%;
  -webkit-mask-image:url("img/devices/pm-mask.png"); mask-image:url("img/devices/pm-mask.png") }
.dev-live.w12 .dev-video{ left:12.857%; top:21.818%; width:74.286%; height:56.364%;
  -webkit-mask-image:url("img/devices/w12-mask.png"); mask-image:url("img/devices/w12-mask.png") }
.dev-live.wu4 .dev-video{ left:14.833%; top:23.229%; width:70.333%; height:53.542%;
  -webkit-mask-image:url("img/devices/wu4-mask.png"); mask-image:url("img/devices/wu4-mask.png") }
.dev-live.duo .dev-video{ left:5.591%; top:3.645%; width:88.818%; height:92.671%;
  -webkit-mask-image:url("img/devices/duo-mask.png"); mask-image:url("img/devices/duo-mask.png") }
[data-videos="off"] .dev-video, [data-videos="off"] .shot-art video,
[data-videos="off"] .vid-toggle, [data-videos="off"] .beats{ display:none }
/* Pause and play, in the card's corner and clear of the device, for anyone
   who wants the motion to stop. On a phone the device fills the card's
   width, so the button sits under it instead, and the card makes room. */
.card-art{ position:relative }
.vid-toggle{ position:absolute; right:16px; bottom:16px; z-index:2; width:36px; height:36px; border-radius:50%; border:0; padding:0;
  display:grid; place-items:center; cursor:pointer; color:var(--ink); background:var(--hair-2);
  transition:background-color var(--fast) }
.vid-toggle:hover{ background:var(--hair) }
.vid-toggle[hidden]{ display:none }
.vid-toggle svg{ width:12px; height:12px; fill:currentColor }
.vid-toggle .i-play, .vid-toggle.paused .i-pause{ display:none }
.vid-toggle.paused .i-play{ display:inline }
/* Its rim is the clip's progress, drawn by home.js through --vp. */
.vid-toggle .rim{ position:absolute; inset:0; width:100%; height:100%; fill:none; rotate:-90deg }
.vid-toggle .rim circle{ stroke:currentColor; stroke-width:1.5; stroke-dasharray:106.8; stroke-dashoffset:calc(106.8 * (1 - var(--vp, 0)));
  opacity:.9; stroke-linecap:round }
.card.ink .vid-toggle{ color:#F2F3F2; background:rgb(255 255 255 / .1) }
.card.ink .vid-toggle:hover{ background:rgb(255 255 255 / .18) }

/* What it runs on, in the hero beside the figures (the owner moved it there,
   27 Sep 2026): small labels over two lines, a hairline between them and the
   figures while they share the row. On a phone they drop under the figures. */
.runs-on{ display:flex; flex-wrap:wrap; align-content:center; gap:12px 22px; margin:0;
  font:600 11px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink-3) }
.runs-on li{ display:flex; align-items:center; gap:8px }
.runs-on svg{ width:15px; height:15px; flex:none; stroke:currentColor; fill:none; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round }
.stats .runs-on{ flex:1 1 300px; max-width:400px; align-self:stretch; padding-left:clamp(24px, 3vw, 40px);
  border-left:1px solid var(--hair) }
@media (max-width:639px){
  .stats .runs-on{ flex-basis:100%; max-width:none; justify-content:center; padding:24px 0 0; border-left:0;
    border-top:1px solid var(--hair) }
}

/* ---------- Ticker: the 51 cities ---------- */
.ticker{ overflow:hidden; border-block:1px solid var(--hair); padding:14px 0; margin-top:var(--sec);
  -webkit-mask:linear-gradient(90deg, transparent, #000 8% 92%, transparent); mask:linear-gradient(90deg, transparent, #000 8% 92%, transparent) }
.ticker-row{ display:flex; width:max-content }
.js .ticker-row{ animation:ticker 110s linear infinite }   /* without script there is no pause button, so it holds still */
.ticker:hover .ticker-row, :root[data-motion="paused"] .ticker-row{ animation-play-state:paused }
.ticker li{ display:inline-flex; align-items:center; gap:14px; padding-right:14px;
  font:600 12px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink-2); white-space:nowrap }
.ticker li::after{ content:""; width:5px; height:5px; border-radius:50%; background:var(--brand); opacity:.8 }
@keyframes ticker{ to{ translate:-50% 0 } }

/* ---------- Cards ----------
   Flighty's layout: a wide card across the row, tall cards in pairs. Each
   card has a heading and its description under it (the owner, 27 Sep 2026:
   a bit different from Flighty, whose bold phrase runs into its sentence),
   and ends on a whole device standing at its foot. No borders, no shadows,
   no hover lift: the fill against the ground is the whole edge. */
.cards{ display:grid; gap:20px }
@media (min-width:820px){ .cards{ grid-template-columns:repeat(2, minmax(0, 1fr)) } .card.wide{ grid-column:1 / -1 } }
.card{ background:var(--paper); border-radius:var(--r-lg); padding:40px 40px 0; display:flex; flex-direction:column;
  overflow:hidden }
.card-copy{ max-width:30em; font-size:17px; line-height:1.55; color:var(--ink-2) }
.card-copy h3{ margin-bottom:12px; font:660 28px/1.1 var(--sans); letter-spacing:-.022em; color:var(--ink) }
.card-art{ margin-top:auto; padding:48px 0; display:flex; justify-content:center; align-items:flex-end }
.card .dev-pm{ width:min(66%, 250px) }
/* A device card beside a close-up stands as tall as the close-up, whose clip
   is as wide as the card and 4:5. So its iPhone grows with the card too,
   to the clip's height less the art's padding (an iPhone is 2.04 times as
   tall as it is wide: .61 of the card's width, which is its content box
   and 80px of padding), instead of leaving a gap of dark between the words
   and the device. */
@media (min-width:820px){
  .shot + .card:not(.wide), .card:not(.shot, .wide):has(+ .shot){ container-type:inline-size }
  .shot + .card:not(.wide) .dev-pm, .card:not(.shot, .wide):has(+ .shot) .dev-pm{ width:min(calc(61cqi + 49px), 340px) }
}
.card .dev-duo{ width:min(80%, 300px) }
.card .dev-w12, .card .dev-wu4{ width:min(58%, 236px) }
@media (min-width:820px){
  .card.wide{ flex-direction:row; align-items:center; gap:40px; padding:0 64px }
  .card.wide .card-copy{ flex:1 1 0; padding:64px 0; max-width:none; font-size:18px }
  .card.wide .card-copy h3{ margin-bottom:16px; font-size:36px }
  .card.wide .card-art{ flex:1 1 0; margin:0; padding:56px 0 }
  .card.wide .dev-pm{ width:min(100%, 244px) }
  .card.wide .dev-duo{ width:min(100%, 330px) }
  .card.wide .dev-w12, .card.wide .dev-wu4{ width:min(100%, 250px) }
}
/* A dark card: the feature cards all are (see "Atmospheres"), in both
   themes, and take light type. */
.card.ink{ background:var(--onyx) }
.card.ink, .card.ink .card-copy{ color:#A9AEB3 }
.card.ink h3{ color:#F2F3F2 }
@media (min-width:820px){ .card.wide.flip{ flex-direction:row-reverse } }

/* ---------- Atmospheres ----------
   Every feature card is a dark card with a mesh gradient of its own, for
   now (the owner, 27 Sep 2026), in the app's colours: emerald into teal
   (Live counts), lime and olive (the reach ring), green into cyan (Directions),
   night blue and violet (Nearby on the watch), orange into plum (Favorites
   on the watch), teal and mint (Nearby), blues and purple (Widgets), and
   dawn, sunset and the island's green along the close-ups' horizons.

   A mesh is wide fields of colour running into one another. .claude/build_atmos.py draws them
   into img/atmos/ as SVG, gradients the browser draws at the screen's own
   resolution (they were AVIF, and looked pixelated), and the card wears
   one as its background over --atmos-base, the mesh's own darkest colour,
   so there is no seam. The cards are dark in both themes, and take
   the ink card's light type (.ink). A wide card's painting keeps its light
   on the device's side and has a portrait twin for the phone layout, where
   the device sits under the words. A tall card's is pinned to its foot,
   where the device is. A close-up's is a band pinned to the clip's top edge
   (the clip is 4:5 and as wide as the card), so its horizon meets the clip.
   Nothing moves. */
.card{ position:relative; isolation:isolate; --px:40px }
.card.shot{ container-type:inline-size }
/* A veil of the card's own base over the side its words are on, sized to
   the card and not to the drawing, so however a drawing is cropped the words
   stay on the dark: over the top of a tall card, the left of a wide one.
   Measured, the description's grey keeps 4.5:1 or better on every card, at
   phone, tablet and desktop widths. */
.card.atmos{ --veil-to:to bottom; --veil:78%;
  background:linear-gradient(var(--veil-to), color-mix(in srgb, var(--atmos-base) var(--veil), transparent),
      color-mix(in srgb, var(--atmos-base) calc(var(--veil) / 2), transparent) var(--veil-mid, 26%), transparent var(--veil-end, 52%)),
    var(--atmos-img) center bottom / cover no-repeat, var(--atmos-base) }
/* A wide card's words fill its left half, so its veil reaches further: at
   820px, where the half is narrowest, the old reach left Live counts' last
   words on the light at 4.1:1. */
.card.wide.atmos{ --veil-to:to right; --veil-mid:36%; --veil-end:62%; background-position:0 0, right center, 0 0 }
/* A flipped wide card has its device on the left and its words on the right,
   so its drawing sits left and the veil runs from the right. */
@media (min-width:820px){ .card.wide.flip.atmos{ --veil-to:to left; background-position:0 0, left center, 0 0 } }
/* A close-up's band sits on the clip's top edge. It is painted by a layer of
   the card's own, as wide as the card and 1040/560 of that tall from the
   foot up: the clip covers the bottom 700/560 (it is 4:5), and the band
   (560×340) shows above it. aspect-ratio does this without measuring; the
   card's own background-position could not, since container units in a
   container's own rules measure its ancestor, not itself. */
.card.shot.atmos{ background:var(--atmos-base) }
.card.shot.atmos::before{ content:""; position:absolute; left:0; right:0; bottom:0; z-index:-1; aspect-ratio:560 / 1040;
  background:var(--atmos-img) center top / 100% auto no-repeat; pointer-events:none }
.a-comp{ --atmos-base:#160805; --atmos-img:url("img/atmos/comp.svg") }
.a-counts{ --atmos-base:#04140E; --atmos-img:url("img/atmos/counts.svg") }
.a-reach{ --atmos-base:#091306; --atmos-img:url("img/atmos/reach.svg") }
.a-route{ --atmos-base:#03101A; --atmos-img:url("img/atmos/route.svg") }
.a-wnear{ --atmos-base:#050A1E; --atmos-img:url("img/atmos/wnear.svg") }
.a-wfav{ --atmos-base:#150807; --atmos-img:url("img/atmos/wfav.svg") }
.a-near{ --atmos-base:#03130F; --atmos-img:url("img/atmos/near.svg") }
.a-widgets{ --atmos-base:#080A20; --atmos-img:url("img/atmos/widgets.svg") }
.a-look{ --atmos-base:#050B1C; --atmos-img:url("img/atmos/look.svg") }
.a-fav{ --atmos-base:#13060D; --atmos-img:url("img/atmos/fav.svg") }
.a-live{ --atmos-base:#05081A; --atmos-img:url("img/atmos/live.svg") }
@media (max-width:819px){
  .a-counts{ --atmos-img:url("img/atmos/counts-tall.svg") }
  .a-comp{ --atmos-img:url("img/atmos/comp-tall.svg") }
  .a-reach{ --atmos-img:url("img/atmos/reach-tall.svg") }
  .card.wide.atmos{ --veil-to:to bottom; --veil:88%; --veil-mid:30%; --veil-end:58%; background-position:0 0, center bottom, 0 0 }
}
/* On the dark page a dark card needs an edge: a hairline, as the nav has. */
.card.atmos{ box-shadow:var(--card-edge) }

/* A close-up: no device, only the app, cropped and followed by a moving
   camera (cut ahead of time, see .claude/video/README.md). It is a
   screen recording rather than a product image, so unlike a device it may
   be cropped, zoomed and moved: it bleeds off the card's foot and sides,
   and as the card comes up the page it settles from a slight zoom, where the
   browser can tie an animation to scrolling. The still under it is a
   moment of the same take (.claude/video/stills.py). */
.card.shot{ padding-bottom:0 }
.card.shot .card-copy{ margin-bottom:96px }   /* the band a close-up's light shows in, under the words */
.shot-art{ position:relative; margin:auto -40px 0; aspect-ratio:4 / 5; overflow:clip; border-top:1px solid var(--hair-2) }
.shot-art img, .shot-art video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:50% 40% }
.shot-art video{ opacity:0; transition:opacity .5s var(--ease) }
.shot-art video.on{ opacity:1 }
.shot-art .vid-toggle{ color:#F2F3F2; background:rgb(20 22 26 / .42); -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px) }
.shot-art .vid-toggle:hover{ background:rgb(20 22 26 / .6) }
@supports (animation-timeline:view()){
  .js .shot-art img, .js .shot-art video{ animation:settle linear both; animation-timeline:view(); animation-range:entry 0% cover 45% }
}
@keyframes settle{ from{ scale:1.16 } to{ scale:1 } }

/* The beats: what the card's clip shows, in order. Each lights while its
   part plays, with a bar that fills underneath, and jumps the clip there
   when pressed (home.js). Without script, or with reduced motion, they are
   a plain list of what the feature does. */
.beats{ margin:30px 0 0; padding:0; list-style:none; display:grid; counter-reset:beat; max-width:26em }
.beats li{ position:relative; counter-increment:beat; padding:12px 0 13px 34px; font:500 15px/1.35 var(--sans); color:var(--ink-3) }
.beats li::before{ content:counter(beat, decimal-leading-zero); position:absolute; left:0; top:15px;
  font:600 11px/1 var(--sans); letter-spacing:.04em; font-variant-numeric:tabular-nums; color:var(--ink-3); pointer-events:none }
.beats li::after{ content:""; position:absolute; left:34px; right:0; bottom:0; height:1px; background:var(--hair) }
.beats.live li{ padding:0 }
.beat{ all:unset; box-sizing:border-box; position:relative; display:block; width:100%; padding:12px 0 13px 34px; cursor:pointer;
  color:var(--ink-3); transition:color var(--mid) var(--ease) }
.beat:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; border-radius:6px }
.beats li.now .beat, .beat:hover{ color:var(--ink) }
.beats li.now::before{ color:var(--accent) }
.beat-bar{ position:absolute; left:34px; bottom:0; z-index:1; height:1px; width:calc((100% - 34px) * var(--f, 0));
  background:var(--ink) }
.beats li:not(.now) .beat-bar{ opacity:.35 }
.card.ink .beats li, .card.ink .beats li::before, .card.ink .beat{ color:#848B92 }
.card.ink .beats li.now .beat, .card.ink .beat:hover{ color:#F2F3F2 }
.card.ink .beats li.now::before{ color:var(--brand) }
.card.ink .beats li::after{ background:rgb(255 255 255 / .12) }
.card.ink .beat-bar{ background:#F2F3F2 }

/* Smaller reasons, two to a row: a glyph, a heading and a sentence each. */
.minis{ display:grid; gap:20px; margin-top:20px }
@media (min-width:820px){
  .minis{ grid-template-columns:repeat(2, minmax(0, 1fr)) }
  /* An odd one out at the end sits centred under the pairs, at their width. */
  .minis li:last-child:nth-child(odd){ grid-column:1 / -1; justify-self:center; width:calc(50% - 10px) }
}
.minis li{ background:var(--paper); border-radius:var(--r-lg);
  padding:32px 40px; display:flex; gap:20px; align-items:flex-start; font-size:16px; line-height:1.55 }
.minis svg{ flex:none; width:26px; height:26px; margin-top:1px; stroke:var(--ink); fill:none; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round }
.minis h3{ margin-bottom:6px; font:640 18px/1.25 var(--sans); letter-spacing:-.015em; color:var(--ink) }

/* ---------- Cities ---------- */
.cities-head{ display:grid; gap:24px; align-items:center }
@media (min-width:940px){ .cities-head{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:48px } }
.cities-head .h2{ margin-bottom:18px }
.cities-head .h2 .roll{ color:var(--accent); display:inline-block; position:relative; white-space:nowrap; transition:width var(--slow) var(--ease) }
/* The name and the full stop after it stay on one line: around an
   inline-block a browser may always break, so a long name could push the
   stop onto a line of its own. */
.roll-line{ white-space:nowrap }
/* The longest name a drag can bring up, "Rivas-Vaciamadrid." with its stop,
   is 8.45em wide: under 360px the heading's size comes down so it fits the
   column. */
@media (max-width:359px){ .cities-head .h2{ font-size:calc((100vw - var(--gutter) * 2) / 8.5) } }
.roll-word{ display:inline-block; white-space:nowrap }
.roll-word .ch{ display:inline-block; transition:translate 460ms var(--ease-out), opacity 460ms var(--ease-out) }
.roll-word.enter .ch{ translate:0 .6em; opacity:0 } .roll-word.leave .ch{ translate:0 -.6em; opacity:0 }
.globe-wrap{ width:min(100%, 560px); margin-inline:auto; position:relative }
.globe{ display:block; width:100%; height:auto; overflow:hidden; cursor:grab; touch-action:pan-y }
.globe.dragging{ cursor:grabbing }
.globe-body{ fill:color-mix(in oklab, var(--ink) 4%, transparent); stroke:var(--hair) }
.globe-grid polyline{ fill:none; stroke:var(--hair) }
.globe-land polyline{ fill:none; stroke:var(--ink-2) }
.globe polyline, .globe-body{ stroke-width:1; vector-effect:non-scaling-stroke }
.globe-city-dot{ fill:var(--brand); fill-opacity:.6; transition:fill-opacity .2s }
.globe-hit{ fill:transparent; cursor:pointer }
.globe-city:hover .globe-city-dot{ fill-opacity:1 }
.globe-dot{ fill:var(--brand) }
.globe-halo{ fill:var(--brand); opacity:.5 }
.js .globe-halo{ animation:pin-pulse 2.2s cubic-bezier(.2,.6,.3,1) infinite }
:root[data-motion="paused"] .globe-halo{ animation:none }
@keyframes pin-pulse{ 0%{ transform:scale(.35); opacity:.5 } 70%,100%{ transform:scale(1.6); opacity:0 } }
.regions{ margin-top:64px; display:grid; gap:20px; grid-template-columns:repeat(2, minmax(0,1fr)); align-items:start }
@media (min-width:900px){ .regions{ grid-template-columns:repeat(4, minmax(0,1fr)) } }
.regions > li{ background:var(--paper); border-radius:var(--r-lg); padding:28px }
.regions h3{ font:700 24px/1.1 var(--sans); letter-spacing:-.03em; margin-bottom:6px }
.regions .count{ font:600 11px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--accent); margin-bottom:18px; display:block }
.regions h4{ margin:14px 0 4px; font:600 11px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink-3) }
.regions ul li{ font-size:14.5px; line-height:1.7; color:var(--ink-2) }

/* ---------- FAQ ---------- */
.faq-list{ border-top:1px solid var(--hair) }
.q{ border-bottom:1px solid var(--hair) }
.q summary{ list-style:none; cursor:pointer; display:flex; justify-content:space-between; align-items:center; gap:24px; padding:22px 0;
  font:500 17px/1.3 var(--sans); color:var(--ink); letter-spacing:-.01em }
.q summary::-webkit-details-marker{ display:none }
.q-mark{ position:relative; width:14px; height:14px; flex:none }
.q-mark::before, .q-mark::after{ content:""; position:absolute; inset:50% 0 auto 0; height:1.5px; background:var(--ink); translate:0 -50%;
  transition:rotate var(--mid) var(--ease), opacity var(--mid) }
.q-mark::after{ rotate:90deg }
.q[open] .q-mark::after{ rotate:0deg; opacity:0 }
.q-a{ padding:0 0 24px; max-width:60ch; color:var(--ink-2); font-size:16px; line-height:1.6 }
@supports (interpolate-size:allow-keywords){
  .q{ interpolate-size:allow-keywords }
  .q::details-content{ block-size:0; overflow:clip; transition:block-size var(--mid) var(--ease), content-visibility var(--mid) allow-discrete }
  .q[open]::details-content{ block-size:auto }
}
.faq-callout{ margin-top:40px; background:var(--paper); border-radius:var(--r-lg); padding:22px 22px 22px 28px;
  display:flex; gap:24px; align-items:center; justify-content:space-between; flex-wrap:wrap }
.faq-callout p{ font-size:15px } .faq-callout strong{ color:var(--ink); font-weight:600 }

/* ---------- Download ---------- */
.get{ padding-bottom:var(--sec) }
.get-card{ background:var(--paper); border-radius:var(--r-lg); padding:clamp(56px, 8vw, 96px) 32px; text-align:center;
  display:flex; flex-direction:column; align-items:center }
.get-icon{ width:96px; height:96px; margin-bottom:32px }
.get-card .h2{ margin:0 0 16px }
.get-card .body{ margin:0 auto 32px; text-align:center; text-wrap:balance }

/* ---------- Footer ----------
   The same on every page: the brand and its line, four short lists (the
   last one is the pages in the other language), the wordmark sunk into the
   ground, and the legal lines. The lists' headings are h2s, after the
   page's own, and each list's <nav> takes its name from its heading. */
.foot{ position:relative; overflow:clip; border-top:1px solid var(--hair) }
.foot-grid{ display:grid; gap:36px 24px; grid-template-columns:repeat(2, minmax(0, 1fr)); position:relative; z-index:2; padding:64px 0 }
.foot-brand{ grid-column:1 / -1 }
@media (min-width:800px){ .foot-grid{ grid-template-columns:repeat(4, minmax(0, 1fr)) } }
@media (min-width:1080px){ .foot-grid{ grid-template-columns:1.5fr repeat(4, minmax(0, 1fr)) } .foot-brand{ grid-column:auto } }
.foot-h{ margin:0 0 14px; font:600 12px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink-3) }
.foot li{ margin:0 0 10px } .foot li a{ text-decoration:none; color:var(--ink-2); font-size:15px; transition:color var(--fast) }
.foot li a:hover{ color:var(--ink) }
.foot-brand p{ margin-top:12px; font-size:14px; color:var(--ink-3); max-width:28ch }
.foot-mail{ display:inline-block; margin-top:14px; font:500 14px/1 var(--sans); color:var(--ink-2); text-decoration:none; border-bottom:1px solid var(--hair) }
/* The wordmark, sunk into the ground. It drifts 24px to the right as the
   page scrolls (home.js writes --sy, 0 to 1, while it is on screen) and
   comes to rest centred as the page ends. Without script, and under reduced
   motion, it is centred and still. */
.foot-mega{ position:relative; height:clamp(110px, 17vw, 240px); overflow:clip }
.foot-mega span{ position:absolute; left:50%; bottom:-.16em; translate:calc(-50% + (var(--sy, 1) - 1) * 24px) 0;
  font:800 clamp(110px, 19vw, 280px)/1 var(--sans); letter-spacing:-.06em; white-space:nowrap;
  color:color-mix(in oklab, var(--ink) 7%, var(--ground)); user-select:none }
.foot-legal{ position:relative; z-index:2; display:flex; justify-content:space-between; flex-wrap:wrap; gap:8px 24px; padding:18px 0 22px;
  border-top:1px solid var(--hair); font:500 12px/1.5 var(--sans); color:var(--ink-3) }
.foot-credit{ flex-basis:100% }

/* ---------- Pages ----------
   The legal pages and the 404: one column of reading in the one-pager's
   type and tokens, under the same nav and over the same footer. A section's
   heading stands well clear of the questions under it (28px against the
   body's size in 600), and the privacy policy opens on a list of its
   sections. Links in the text are the accent, underlined. */
.page{ padding:clamp(40px, 6vw, 72px) 0 var(--sec) }
.page .narrow{ max-width:680px }
.page h1{ margin-bottom:16px; font:740 clamp(38px, 5.2vw, 54px)/1.02 var(--sans); letter-spacing:-.035em }
.page .updated{ margin:0 0 40px; font-size:14px; line-height:1.5; color:var(--ink-3) }
.page h2{ margin:56px 0 14px; font:680 clamp(24px, 2.6vw, 28px)/1.15 var(--sans); letter-spacing:-.022em; scroll-margin-top:104px }
.page h3{ margin:30px 0 6px; font:600 17px/1.4 var(--sans); letter-spacing:-.01em }
.page h2 + h3{ margin-top:22px }
.page p, .page li{ font-size:17px; line-height:1.65 }
.page p{ margin:0 0 16px }
.page ul{ margin:0 0 16px; padding-left:22px; list-style:disc }
.page li{ margin-bottom:8px }
.page li::marker{ color:var(--ink-3) }
.page strong{ color:var(--ink); font-weight:600 }
.page a{ color:var(--accent); text-decoration-color:color-mix(in oklab, var(--accent) 45%, transparent);
  text-decoration-thickness:.06em; text-underline-offset:.16em }
.page a:hover{ text-decoration-color:currentColor }
/* The privacy policy's sections, listed under its intro: two columns where
   they fit. */
.toc{ margin:32px 0 0; padding:24px 28px 16px; background:var(--paper); border-radius:var(--r) }
.page .toc-h{ margin:0 0 14px; font:600 12px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--ink-3) }
.toc ol{ margin:0; padding:0 0 0 1.7em }
@media (min-width:640px){ .toc ol{ columns:2; column-gap:44px } }
.page .toc li{ margin:0 0 8px; break-inside:avoid; font-size:15px; line-height:1.4; color:var(--ink-3) }
.toc li::marker{ font-variant-numeric:tabular-nums }   /* the figures only: Inter's tnum spaces a hyphen out too */
.page .toc a{ color:var(--ink); text-decoration:none }
.page .toc a:hover{ text-decoration:underline }

/* ---------- Small screens ---------- */
@media (max-width:819px){
  .card{ padding:32px 26px 0; --px:26px }
  .card-copy{ font-size:16px }
  .card-copy h3{ margin-bottom:10px; font-size:24px }
  .card-art{ padding:36px 0 }
  .card-art .vid-toggle{ right:50%; translate:50% 0; bottom:20px }
  .card.shot .card-copy{ margin-bottom:80px }
  .shot-art{ margin:auto -26px 0 }
  .minis li{ padding:26px }
}
/* Room under the device for the button, only where a clip can play. */
@media (max-width:819px) and (prefers-reduced-motion:no-preference){
  :root:not([data-videos="off"]) .card-art:has(.vid-toggle){ padding-bottom:72px }
}
@media (max-width:600px){
  .hero{ padding:116px 0 72px }
  .hero-in{ gap:44px }
  :root{ --city-scale:.8 }
  .regions{ grid-template-columns:1fr }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  *, *::before, *::after{ animation:none !important; transition:none !important }
  html{ scroll-behavior:auto }
  .hero{ --p:0 !important }
  .js .reveal{ opacity:1; translate:0 }
  .dev-video, .shot-art video, .vid-toggle{ display:none !important }
  .js .motion, .js .nav-panel-motion{ display:none }   /* nothing moves, so nothing to pause */
  .kin .w > span{ translate:0 }
}
