/* ============================================================
   The sections from Amin's design file (2026-10-17): how it works,
   a ride story (only once there's a real one), and organisers. The
   example rides were dropped the same day.

   Built from Amin's design file, restyled to sit with the hero,
   FAQ and footer: the same two faces, white and soft-grey bands,
   and Apple's systemBlue, the app's primary colour,
   as the one accent (pins, routes, your own chat bubble). The page is sections, so each
   one is a clean band; nothing fades into the next.
   ============================================================ */

:root {
  --sx-ink: #16203a;
  --sx-ink-soft: #4b5672;
  --sx-ink-faint: #7c849a;
  --sx-line: #e3e6ee;
  --sx-band: #f4f5f8;       /* the soft grey band */
  --sx-card: #ffffff;
  --sx-chip: #eff1f6;
  --sx-navy: #141d40;       /* the footer's night, for the share bubble */
  --sx-navy-line: #34437a;
  --sx-sky: #0066d6;        /* systemBlue, deepened a touch to read as small text */
  --sx-sky-light: #a9c2ff;
  --sx-accent: #007aff;     /* Apple's systemBlue, the app's primary */
  --sx-park: #dbe8d3;
  --sx-map: #eaeef3;
  --sx-display: "Bricolage Grotesque", "Avenir Next", "Segoe UI", system-ui, sans-serif;
  --sx-body: "Figtree", "Segoe UI", system-ui, -apple-system, sans-serif;
  --sx-pad-y: clamp(72px, 10vw, 128px);
}

/* ---- shared parts ---- */
.sx-wrap {
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding-left: max(20px, env(safe-area-inset-left, 0px));
  padding-right: max(20px, env(safe-area-inset-right, 0px));
}
.story, .organisers { font-family: var(--sx-body); color: var(--sx-ink); }
.organisers h3 { margin: 0; }
.story p, .organisers p { margin: 0; }

.sx-title {
  margin: 0;
  font-family: var(--sx-display);
  font-weight: 800;
  font-size: clamp(34px, 4.6vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  text-wrap: balance;
  font-optical-sizing: auto;
  font-variation-settings: normal;
}
.sx-eyebrow {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sx-sky);
}
.sx-pill {
  justify-self: start;
  width: fit-content;
  padding: 6px 12px;
  border-radius: 999px;
  background: #e6f0ff;
  color: var(--sx-sky);
  font-size: 13px;
  font-weight: 600;
}
.sx-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.sx-chips li { font-size: 12px; font-weight: 500; padding: 6px 10px; border-radius: 999px; background: var(--sx-chip); }
.sx-avatar {
  flex: none;
  width: 36px; height: 36px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: #dceaff; color: #0055c4;
  font-size: 12px; font-weight: 600;
}
.sx-bubble { max-width: 85%; padding: 9px 13px; font-size: 13px; line-height: 1.35; }
.sx-bubble--them { align-self: flex-start; border-radius: 16px 16px 16px 4px; background: var(--sx-navy-line); }
.sx-bubble--me { align-self: flex-end; border-radius: 16px 16px 4px 16px; background: var(--sx-accent); color: #ffffff; }

/* little maps: soft land, white roads, an orange route */
.sx-map-park { fill: var(--sx-park); }
.sx-map-road { stroke: #ffffff; stroke-width: 6; fill: none; stroke-linecap: round; }
.sx-map-route { stroke: var(--sx-accent); stroke-width: 4; fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* ================= how it works ================= */
.how__screen { position: relative; aspect-ratio: 1393 / 735; border-radius: 16px; background: var(--sx-card); overflow: hidden; border: 1px solid var(--sx-line); }
.how__pin {
  position: absolute; left: 50%; top: 36px;
  width: 28px; height: 28px; margin-left: -14px; border-radius: 50%;
  background: var(--sx-accent); border: 3px solid #ffffff;
  box-shadow: 0 0 0 8px rgba(0, 122, 255, 0.28);
}
.how__label {
  position: absolute; left: 50%; top: 80px; transform: translateX(-50%);
  padding: 6px 12px; border-radius: 10px; background: #ffffff; color: var(--sx-ink);
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
/* The pin sits at 38% of the crop's height and is about 18% of its
   width across, so the label hangs just under it at any card size. */
.how__label--under-pin { top: calc(38% + 14cqw); box-shadow: 0 4px 12px rgba(20, 29, 64, 0.18); }
/* Straight on the card, no screen frame around it. */
.how__screen--bare { border: 0; padding: 0; background: transparent; container-type: inline-size; }

/* The app's ride card as real text. Every size and position is in --u,
   1/1393 of the box's width (the screenshot it was measured from), so it
   scales as one piece. The system font is the app's own SF on Apple
   devices. The emoji and the memoji row are pictures. */
.ride-mock { --u: calc(100cqw / 1393); position: relative; width: 100%; height: 100%; color: #000; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
.ride-mock__head { position: absolute; left: calc(22 * var(--u)); top: calc(3 * var(--u)); height: calc(200 * var(--u)); display: flex; align-items: center; gap: calc(33 * var(--u)); }
.ride-mock__icon { width: calc(201 * var(--u)); height: auto; max-width: none; }
.ride-mock__titles { display: grid; line-height: 1.15; }
.ride-mock__titles b { font-size: calc(66 * var(--u)); font-weight: 400; letter-spacing: 0.005em; }
.ride-mock__titles span { font-size: calc(52 * var(--u)); color: #505050; }
.ride-mock__row { position: absolute; left: calc(36 * var(--u)); right: calc(5 * var(--u)); top: calc(var(--top) * var(--u)); height: calc(113 * var(--u)); display: flex; align-items: center; font-size: calc(52 * var(--u)); color: #565656; white-space: nowrap; }
.ride-mock__emoji { width: calc(106 * var(--u)); height: auto; max-width: none; margin: 0 calc(36 * var(--u)) 0 calc(-6 * var(--u)); }
.ride-mock__chip { margin-left: auto; height: calc(87 * var(--u)); padding: 0 calc(40 * var(--u)); border-radius: 999px; background: #e9e9e9; display: flex; align-items: center; }
.ride-mock__riders { position: absolute; left: calc(33 * var(--u)); top: calc(575 * var(--u)); display: flex; }
/* The riders, one image each so each can arrive on its own: circles
   overlapping by a sliver, the card's white showing between them as a
   ring of box-shadow, each later one on top. Whole pixels, so they stay sharp. */
.ride-mock__rider {
  --rd: round(calc(157 * var(--u)), 2px);
  position: relative; width: var(--rd); height: var(--rd); border-radius: 50%;
  box-shadow: 0 0 0 round(calc(5 * var(--u)), 1px) #ffffff;
}
.ride-mock__rider + .ride-mock__rider { margin-left: round(calc(-8 * var(--u)), 1px); }
/* The app's blue pill under the riders, full content width like its "Join
   this ride" button, the chips' type size in semibold. A picture of a
   button, not a button: the card is one role="img". */
.ride-mock__more {
  position: absolute; left: calc(36 * var(--u)); right: calc(5 * var(--u)); top: calc(780 * var(--u)); height: calc(150 * var(--u));
  display: grid; place-items: center; border-radius: 999px;
  background: var(--sx-accent); color: #ffffff; font-size: calc(52 * var(--u)); font-weight: 600; letter-spacing: -0.01em;
}
/* The drawn screens look like the app, which is light. */
.how__screen--chat { display: flex; flex-direction: column; justify-content: flex-end; gap: 8px; padding: 16px; }
.how__screen--chat .sx-bubble--them { background: #e9e9eb; color: #000000; }
.how__screen--chat.how__screen--bare { padding: 0; justify-content: center; }

/* ================= how it works, second version ================= */
/* From Amin's reference: a centred head, three floating screens and the
   words centred under each, on white. The screens reuse the first
   version's pieces (map crop, ride card, chat bubbles). */
/* The page's top edge rises in one soft hump over the bottom of the hero,
   a second hill under the duck's. The hump is a half-ellipse wider than
   the screen (clipped at the sides), so it starts low at the edges. */
.how2 {
  --how2-rise: var(--hero-tail);   /* hero.css: the strip below the fold */
  position: relative;
  z-index: 3;
  overflow-x: clip;
  background: #ffffff;
  padding-block: clamp(40px, 5vw, 72px) var(--sx-pad-y);
  font-family: var(--sx-body); color: var(--sx-ink); text-align: center; }
.how2::before {
  content: "";
  position: absolute;
  left: -20%; right: -20%;
  top: calc(-1 * var(--how2-rise));
  height: calc(var(--how2-rise) + 1px);
  background: #ffffff;
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
}
.how2__inner { display: grid; gap: clamp(36px, 5vw, 56px); }
.how2__head { display: grid; justify-items: center; gap: 14px; }
.how2__head p { margin: 0; font-size: 17px; color: var(--sx-ink-soft); }
/* The words that turn over in the heading (js/rotator.js). Every word sits
   stacked in one grid cell, so the slot is always as wide as the longest
   one and the words around it never move. The word leaving glides up and
   out while the next glides up into place, on the same curve. */
.rotator { display: inline-grid; justify-items: start; vertical-align: bottom; }
.rotator__word {
  grid-area: 1 / 1;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(0.55em);
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.45s ease;
}
.rotator__word.is-current { opacity: 1; transform: none; }
.rotator__word.is-leaving { opacity: 0; transform: translateY(-0.55em); }
.rotator__word.is-reset { transition: none; }
@media (prefers-reduced-motion: reduce) { .rotator__word { transition: none; } }
.how2__step h3 { margin: 24px 0 8px; font-family: var(--sx-display); font-weight: 800; font-size: 22px; letter-spacing: -0.01em; }
.how2__step p { margin: 0; max-width: 32ch; font-size: 16px; line-height: 1.5; color: var(--sx-ink-soft); }
/* A timeline (2026-10-18, from Amin's reference): the steps run down the
   page, each screen and its words on opposite sides of a dotted line, the
   sides swapping every row and the words leaning in toward the line.
   Two rows per step, the screen spanning both, so the heading sits on the
   row line and the paragraph under it: the pair centres on the screen
   without a wrapper. */
.how2__steps {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr);
  gap: clamp(44px, 6vw, 76px); width: min(100%, 940px); margin: 0 auto; padding: 0; list-style: none;
}
.how2__step {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: clamp(64px, 9vw, 112px); align-items: center;
}
.how2__step > .how2__screen { grid-column: 2; grid-row: 1 / 3; justify-self: start; max-width: 400px; }
.how2__step > h3, .how2__step > p { grid-column: 1; justify-self: end; text-align: right; }
.how2__step > h3 { grid-row: 1; align-self: end; margin-top: 0; }
.how2__step > p { grid-row: 2; align-self: start; }
.how2__step:nth-child(even) > .how2__screen { grid-column: 1; justify-self: end; }
.how2__step:nth-child(even) > h3, .how2__step:nth-child(even) > p { grid-column: 2; justify-self: start; text-align: left; }
/* The line: one period of a wave, dotted, tiled down the gutter. pathLength
   fixes the dots at exactly 25 per tile, so they space evenly across the
   seams; the tile starts and ends on the same slope, so the wave doesn't
   kink there. It fades in and out at the ends rather than stopping dead. */
.how2__steps::before {
  content: ""; position: absolute; z-index: 0; top: -48px; bottom: -48px; left: 50%;
  width: 40px; margin-left: -20px; pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='200' viewBox='0 0 40 200'%3E%3Cpath d='M20 0C34 50 34 50 20 100S6 150 20 200' fill='none' stroke='%23007aff' stroke-width='3' stroke-linecap='round' stroke-dasharray='0 4' pathLength='100'/%3E%3C/svg%3E") center top / 40px 200px repeat-y;
  -webkit-mask-image: linear-gradient(transparent, #000 72px, #000 calc(100% - 72px), transparent);
          mask-image: linear-gradient(transparent, #000 72px, #000 calc(100% - 72px), transparent);
}
.how2__screen {
  width: 100%;
  aspect-ratio: 3 / 2;   /* tall enough to show nearly the whole map screenshot */
  border-radius: 18px;
  border: 0;   /* the shadow alone lifts it; a 1px line on top read as a drawn outline (Amin, 2026-10-18) */
  box-shadow: 0 1px 2px rgba(22, 32, 58, 0.05), 0 14px 34px rgba(22, 32, 58, 0.08);
  text-align: left;
}
/* The ride card needs room inside its frame; it's 930 of its 1393 units tall. */
.how2__screen--card { container-type: inline-size; display: flex; align-items: center; padding: 0 14px; }
.how2__screen--card .ride-mock { height: calc(930 * var(--u)); }   /* down to the bottom of the pill */
.how2__screen--chat { padding: 0; }
/* the map filling the card, with the app's ride pins over it. The map
   rounds its own corners, so the card needn't clip: a few pins sit across
   its edge, half on the map and half off, so it reads as alive rather than
   framed (2026-10-18, Amin). */
.how2__map { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: cover; border-radius: 18px; }
/* A ride pin as the app draws it (Map/PinViews.swift): a white disc with a
   faint ring and the ride's emoji, and on one the host's face over its
   lower right. Sized against the card in container units: a disc 15% of
   its width (the app's is 12%; larger reads better at this size), the emoji
   59% of the disc and the face 49%, as in the app, so a pin keeps its size
   relative to the map on a phone. */
.how2__screen--map { container-type: inline-size; overflow: visible; }
/* Every size and offset is rounded to a whole pixel, and the pin is centred
   on its point with margins rather than a transform: a bitmap drawn between
   pixels is resampled, which at 36px reads as a soft emoji. --x/--y place it
   on the card (how2.html). */
.map-pin {
  --d: round(15cqw, 2px);   /* even, so half of it is a whole pixel */
  position: absolute; left: round(var(--x), 1px); top: round(var(--y), 1px);
  margin: calc(var(--d) / -2) 0 0 calc(var(--d) / -2);
  display: grid; place-items: center; width: var(--d); height: var(--d); border-radius: 50%;
  background: #ffffff; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 8px rgba(22, 32, 58, 0.18);
}
.map-pin > img { width: round(59%, 2px); height: round(59%, 2px); }
/* An emoji whose artwork leaves more space around itself (the heart hands)
   reads small at the standard size, so it gets more of the disc. */
.map-pin--roomy > img:not(.map-pin__host) { width: round(72%, 2px); height: round(72%, 2px); }
.map-pin > .map-pin__host {
  position: absolute; left: round(67%, 1px); top: round(64%, 1px); width: round(49%, 2px); height: round(49%, 2px); border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.how2__chat-scroll {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 8px;
  padding: 0 16px;
  /* Fades out at the top and bottom, as if the chat carries on both ways. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 28%, #000 72%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 28%, #000 72%, transparent 100%);
}
.how2__chat-scroll .sx-bubble { flex: none; }

/* ================= ride story (only with a real one) ================= */
.story { padding-block: var(--sx-pad-y); }
.story__inner { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 48px; align-items: center; }
.story__photo { width: 100%; height: 100%; min-height: 420px; object-fit: cover; border-radius: 28px; }
.story__text { display: grid; gap: 20px; }
.story__text .sx-title { font-size: clamp(32px, 3.8vw, 46px); }
.story__text p { font-size: 17px; line-height: 1.55; color: var(--sx-ink-soft); }
.story blockquote { margin: 0; font-family: var(--sx-display); font-weight: 800; font-size: 24px; line-height: 1.25; letter-spacing: -0.01em; }
.story__who { font-size: 14px; color: var(--sx-ink-faint); }

/* ================= organisers ================= */
.organisers {
  padding-block: var(--sx-pad-y);
  /* The three the section keeps getting tuned on. Named here so the
     dev-only panel (_tuning.html, DEBUG + ?tune=1) can drive them live
     rather than this being edited a pixel at a time. */
  --phone-drop: -200px;   /* how far the phone hangs below the panel */
  --chat-gap: 10px;       /* air between messages in the chat */
  --org-text-pad: 0px;    /* nudges the text up (+) or down (-); 0 = centred on the panel */
}
/* On a panel of the hero's sky (hero.css .hero), so the page ends where it
   began and the phone floats in the same soft light as the duck. */
.organisers__inner {
  --org-pad: clamp(36px, 6vw, 80px);
  position: relative; isolation: isolate; overflow: hidden; width: min(1120px, calc(100% - 32px));
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 48px;
  padding: var(--org-pad) clamp(24px, 6vw, 80px) 0;
  border-radius: clamp(28px, 4vw, 44px);
  background:
    radial-gradient(120% 90% at 85% 15%, rgba(255, 255, 255, 0.22), transparent 55%),
    linear-gradient(170deg, #4a71bd 0%, #6087cd 55%, #a3a9c8 100%);
  color: #ffffff;
}
/* Centred on the panel, not on the phone. The panel pads its top but not
   its bottom (the phone hangs off that edge), so centring in its content
   box sat the text low by half that padding — and every change to the
   phone moved it again. Instead the column fills the panel's height and
   centres its own contents, with a bottom pad matching the panel's top,
   which puts its middle on the panel's middle whatever the phone does. */
.organisers__text {
  flex: 1 1 360px; max-width: 640px; display: grid; gap: 18px;
  align-self: stretch; align-content: center;
  padding-bottom: calc(var(--org-pad) + var(--org-text-pad));
}
/* A step down from the shared ramp: at the full size the second sentence
   broke across two lines and left a word on its own. */
.organisers__text .sx-title { color: #ffffff; font-size: clamp(30px, 3.4vw, 42px); }
.organisers__text .sx-pill { justify-self: start; background: rgba(255, 255, 255, 0.18); color: #ffffff; }
.organisers__seeds { position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%; border-radius: inherit; pointer-events: none; }
.organisers__demo { flex: 0 0 auto; display: flex; justify-content: center; margin-inline: auto; } /* the phone sets its own width */
/* the three steps the chat plays out */
.org-steps { display: grid; gap: 14px; margin: 0; padding: 0; list-style: none; counter-reset: org-step; }
.org-step { position: relative; padding-left: 44px; font-size: 18px; line-height: 1.45; color: rgba(255, 255, 255, 0.82); counter-increment: org-step; }
.org-step b { color: #ffffff; font-weight: 600; }
/* The number has to look centred, not just have its box centred. Tabular
   figures draw the "1" in the middle of a full-width cell, where the
   proportional one sat ~2px left (its flag pulls the box one way, the eye
   follows the stem). 600 is a weight Figtree actually loads; 700 was faked.
   Trimming the line box to the numeral's cap height centres it vertically,
   where the font's uneven ascender and descender had it ~1px high
   (browsers without text-box fall back to that 1px). */
.org-step::before {
  content: counter(org-step); position: absolute; left: 0; top: -1px;
  display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.2); color: #ffffff; font-size: 14px; font-weight: 600;
  font-variant-numeric: tabular-nums; line-height: 1; text-box: trim-both cap alphabetic;
}
/* Both stores, side by side. White on the blue panel rather than the
   hero's dark pill, which would disappear into it. The Apple mark takes
   the button's ink through currentColor; the Android one keeps its own
   green, as it does in the hero. */
.org-stores { display: flex; flex-wrap: wrap; gap: 12px; }
.org-store {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 11px 18px; border-radius: 14px; background: #ffffff; color: #16203a;
  font-weight: 600; font-size: 15px; line-height: 1.15; text-align: left; text-decoration: none;
  box-shadow: 0 10px 24px rgba(14, 22, 48, 0.18);
}
.org-store small { display: block; font-weight: 500; font-size: 11px; opacity: 0.7; }
.org-store svg { width: 20px; height: 20px; flex: none; }
a.org-store:hover { transform: translateY(-1px); }
.org-store[aria-disabled="true"] { opacity: 0.75; cursor: default; }
/* The phone rises from the panel's bottom edge and fades into the sky:
   the chat shows, the hardware mostly doesn't (picked by Amin from six
   variants, 2026-10-17). */
.organisers__demo { align-self: flex-end; }
/* the demo: a WhatsApp group on an iPhone. The screen is a real WhatsApp
   screenshot (img/wa-screen.webp, 393 x 852pt) and the chat is laid over
   it at the same scale, then the whole phone is zoomed down to 270px wide:
   zoom, unlike a transform, keeps text crisp. The screen is cut to 770pt
   tall, top (status bar, header, wallpaper) and bottom (message bar) kept,
   in a frosted frame. The chat ends above where the phone fades out. */
.phone {
  zoom: 0.76; /* 300 / 393, for the screen */
  width: 417px; padding: 12px; border-radius: 64px; margin-bottom: var(--phone-drop);
  /* a frosted frame the sky shows through, with a bright rim */
  background: rgba(255, 255, 255, 0.24);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.7), 0 -10px 60px rgba(24, 44, 110, 0.25);
  /* its lower part fades out where it runs off the panel */
  -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent 96%);
          mask-image: linear-gradient(180deg, #000 70%, transparent 96%);
}
@media (max-width: 900px) { .organisers { --phone-drop: -120px; } }
.wa {
  --wa-out: #d9fdd3; --wa-out-preview: #cdeec6; --wa-meta: #667781;
  --wa-tick: #53bdeb; --wa-link: #027eb5;
  position: relative; height: 770px; overflow: hidden; border-radius: 52px;
  background: #f3f1eb url("../img/wa-screen.webp") top center / 393px auto no-repeat;
  font-family: -apple-system, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif; color: #111b21;
  -webkit-font-smoothing: antialiased;
}
.wa svg { display: block; }
/* over the screenshot's own group name and the photo beside the call button */
.wa__title { position: absolute; left: 112px; top: 58px; width: 203px; height: 44px; display: grid; align-content: center; padding-left: 4px; background: #f3f0eb; line-height: 1.15; }
.wa__title strong { font-size: 18px; font-weight: 500; letter-spacing: -0.3px; }
.wa__title small { font-size: 14px; color: #8a8a8e; }
/* over the screenshot's battery (it read 33%): a full one, iOS's plain
   battery with no percentage */
.wa__battery { position: absolute; left: 330px; top: 19px; width: 34px; height: 18px; background: #f3f1eb; }
.wa__battery b { position: absolute; left: 3px; top: 3px; width: 24px; height: 12.5px; border: 1px solid rgba(0, 0, 0, 0.35); border-radius: 4px; }
.wa__battery b::before { content: ""; position: absolute; inset: 1.5px; border-radius: 2.5px; background: #000000; }
.wa__battery::after { content: ""; position: absolute; left: 28px; top: 7.5px; width: 1.8px; height: 4px; border-radius: 0 1px 1px 0; background: rgba(0, 0, 0, 0.4); }
/* the screenshot's message bar, pinned to the bottom of the shorter screen */
.wa__bar { position: absolute; left: 0; right: 0; bottom: 0; height: 81px; background: #f3f1eb url("../img/wa-screen.webp") bottom center / 393px auto no-repeat; }
/* The gap trades against the ride card: the column is anchored to the
   bottom of a window shorter than the chat, so each pixel here pushes
   the card about 2.3px further off the top. Its map is cropped at every
   gap, 10px of it even at the old 4px; 10px spends about 24px of the map
   on the air between messages, which is the trade Amin picked. */
.wa__body { position: absolute; left: 0; right: 0; top: 111px; bottom: 230px; overflow: hidden; display: flex; flex-direction: column; justify-content: flex-end; gap: var(--chat-gap); padding: 10px 14px 12px; }
/* The chat briefly outgrows the window it plays in, and must ride up out
   of sight rather than be squeezed into it. Flex shrinking spares the
   bubbles (their own content sets a floor) but not the "typing…" one,
   which has overflow: hidden and so can go to nothing — and being last
   to arrive, it absorbed the whole overflow. Daan's dots opened to 1px
   instead of 36 and read as a dropped frame. */
.wa__body > * { flex: 0 0 auto; }
.wa__day { align-self: center; margin-bottom: 6px; padding: 5px 11px; border-radius: 8px; background: #ffffff; box-shadow: 0 1px 0.5px rgba(11, 20, 26, 0.13); font-size: 12.5px; font-weight: 400; color: var(--wa-meta); }
.wa__msg { position: relative; max-width: 78%; padding: 6px 8px 8px 10px; border-radius: 9px; box-shadow: 0 1px 0.5px rgba(11, 20, 26, 0.13); font-size: 17px; line-height: 1.3; letter-spacing: -0.2px; }
.wa__msg p { margin: 0; }
.wa__msg + .wa__msg--in { margin-top: 2px; }
.wa__msg--in { align-self: flex-start; background: #ffffff; border-top-left-radius: 0; margin-left: 8px; }
.wa__msg--out { align-self: flex-end; width: 82%; background: var(--wa-out); border-top-right-radius: 0; margin: 0 8px 6px 0; padding: 4px 4px 8px; }
.wa__msg--out p { padding: 5px 6px 0; }
/* the tails at the top corner */
.wa__msg--in::before, .wa__msg--out::before { content: ""; position: absolute; top: 0; width: 8px; height: 13px; }
.wa__msg--in::before { left: -8px; background: #ffffff; clip-path: polygon(0 0, 100% 0, 100% 100%); }
.wa__msg--out::before { right: -8px; background: var(--wa-out); clip-path: polygon(0 0, 100% 0, 0 100%); }
.wa__name { display: block; margin-bottom: 1px; font-size: 14px; font-weight: 500; letter-spacing: -0.1px; }
.wa__name--a { color: #d3396d; }
.wa__name--b { color: #1f7aec; }
.wa__meta { float: right; display: inline-flex; align-items: center; gap: 3px; margin: 9px 0 -5px 12px; font-size: 11px; letter-spacing: 0; color: var(--wa-meta); }
.wa__ticks { width: 17px; height: 11px; fill: var(--wa-tick); }
.wa__link { color: var(--wa-link); }
.wa__preview { display: grid; overflow: hidden; border-radius: 7px; background: var(--wa-out-preview); }
.wa__preview svg { width: 100%; height: auto; aspect-ratio: 1.91; }
.wa__preview-text { display: grid; gap: 2px; padding: 8px 10px 10px; line-height: 1.25; }
.wa__preview-text strong { font-size: 15px; font-weight: 500; letter-spacing: -0.2px; }
.wa__preview-text small { font-size: 13px; color: #3b4a54; }
.wa__preview-text i { margin-top: 2px; font-size: 13px; font-style: normal; color: var(--wa-meta); }

/* ================= features bento ================= */
/* Four columns: a big tile spans two, a small one one. */
.features { padding-block: var(--sx-pad-y); font-family: var(--sx-body); color: var(--sx-ink); }
.features__inner { display: grid; gap: clamp(36px, 5vw, 56px); }
.features__head { display: grid; justify-items: center; gap: 14px; text-align: center; }
.features__head p { margin: 0; max-width: 60ch; text-wrap: balance; font-size: 17px; line-height: 1.55; color: var(--sx-ink-soft); }
.bento-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
/* A strong fade at the bottom: solid until just above the short tiles
   (js/bento.js measures where they start), barely there by their second
   row and gone before the grid ends. */
.bento-grid {
  --fade-from: 70%;
  -webkit-mask-image: linear-gradient(180deg, #000 calc(var(--fade-from) - 40px), rgba(0, 0, 0, 0.3) calc(var(--fade-from) + (100% - var(--fade-from)) * 0.3), rgba(0, 0, 0, 0.08) calc(var(--fade-from) + (100% - var(--fade-from)) * 0.6), transparent 85%);
          mask-image: linear-gradient(180deg, #000 calc(var(--fade-from) - 40px), rgba(0, 0, 0, 0.3) calc(var(--fade-from) + (100% - var(--fade-from)) * 0.3), rgba(0, 0, 0, 0.08) calc(var(--fade-from) + (100% - var(--fade-from)) * 0.6), transparent 85%);
}
.tile {
  position: relative; overflow: hidden;
  contain: layout paint; /* a mock animating inside never relayouts the grid */
  display: flex; flex-direction: column; gap: 8px;
  min-height: 270px;
  padding: 22px 0 0 22px;
  border-radius: 24px;
  border: 1px solid var(--sx-line);
  background: var(--sx-band);
}
.tile--big { grid-column: span 2; min-height: 320px; }
.tile h3 { margin: 0; padding-right: 22px; font-family: var(--sx-display); font-weight: 800; font-size: 19px; letter-spacing: -0.01em; }
.tile__sub { margin: 0; padding-right: 22px; max-width: 52ch; font-size: 15px; line-height: 1.5; color: var(--sx-ink-soft); }
/* The white mock: anchored bottom-right, bleeding off the tile's edges. */
.tile__card {
  margin-top: auto; margin-left: 16px;
  border-radius: 16px 0 0 0;
  border: 1px solid var(--sx-line); border-right: 0; border-bottom: 0;
  background: var(--sx-card);
  box-shadow: 0 10px 30px rgba(22, 32, 58, 0.08);
  font-size: 14px;
}
.tile__card--pad { display: grid; gap: 12px; padding: 18px; }
.tile__label { font-size: 12px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--sx-ink-faint); }
.tile__chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.tile__chips li { padding: 6px 10px; border-radius: 999px; background: var(--sx-chip); font-size: 12px; font-weight: 500; }
.tile__chips li.is-on { background: var(--sx-accent); color: #ffffff; }
.tile__card--image { height: 230px; overflow: hidden; }
.tile__card--image img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%; }
.tile__row { display: flex; align-items: center; gap: 10px; }
.tile__row strong { font-weight: 600; }
.tile__buttons { display: flex; gap: 8px; }
.tile__buttons b { flex: 1; padding: 9px 0; border-radius: 10px; background: var(--sx-chip); text-align: center; font-weight: 600; font-size: 13px; }
.tile__buttons b.is-primary { background: var(--sx-accent); color: #ffffff; }
.tile__note { margin: 0; font-size: 12px; color: var(--sx-ink-faint); }
/* moderation: a ride's riders, one about to be removed */
/* attendance: who's coming, and the host's edit mode for kicking riders.
   The card is pinned to the tile's bottom edge, so when a row folds away
   only the card shrinks; when it grows it may cover the text above it. */
.tile--attend { min-height: 340px; }
/* the short tiles at the end: a name and a line, no mock, fading out */
/* a tile's line in two versions: the second for a phone (the 560px query) */
.tile__sub-narrow { display: none; }
.tile--mini {
  min-height: 0; gap: 6px; padding: 20px 22px 22px;
  /* no edge to stop at: the panel just thins out into the page */
  border: 0; border-radius: 24px 24px 0 0;
  background: linear-gradient(180deg, var(--sx-band) 0%, rgba(244, 245, 248, 0) 100%);
}
.tile--mini .tile__sub { padding-right: 0; font-size: 14px; }
.tile__card--attend { position: absolute; left: 38px; right: 0; bottom: 0; z-index: 1; gap: 6px; padding-top: 14px; }
.att__top { display: flex; align-items: baseline; justify-content: space-between; padding-bottom: 2px; }
.att__top strong { font-size: 15px; font-weight: 600; }
.att__edit { display: grid; color: var(--sx-sky); font-size: 15px; font-weight: 400; }
.att__edit span { grid-area: 1 / 1; text-align: center; }
.att__edit span + span { opacity: 0; }
.tile__riders { display: grid; }
.tile__rider { position: relative; display: flex; align-items: center; gap: 10px; height: 44px; overflow: hidden; font-weight: 500; }
.tile__rider .sx-avatar { flex: none; width: 30px; height: 30px; font-size: 11px; }
.tile__tag { margin-left: auto; padding: 3px 8px; border-radius: 999px; background: var(--sx-chip); color: var(--sx-ink-soft); font-size: 11px; font-style: normal; font-weight: 600; }
.tile__tag--going { background: #e3efff; color: var(--sx-sky); }
/* the red minus, folded away until edit mode */
.att__minus {
  position: relative; flex: none; width: 0; height: 20px; margin-right: -10px;
  border-radius: 50%; background: #ff3b30; scale: 0;
}
.att__minus::after { content: ""; position: absolute; left: 5px; right: 5px; top: 9px; height: 2px; border-radius: 1px; background: #ffffff; }
.tile__rider b {
  position: absolute; right: 0; top: 50%; translate: 0 -50%;
  padding: 6px 12px; border-radius: 8px; background: #ff3b30; color: #ffffff; font-size: 12px; font-weight: 600;
  opacity: 0; pointer-events: none;
}
/* rides near you: a push notification */
.tile__push { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 14px; background: var(--sx-band); font-size: 13px; line-height: 1.3; }
.tile__push > span:last-child { display: grid; min-width: 0; }
.tile__push small { color: var(--sx-ink-soft); font-size: 12px; }
.tile__push-icon { flex: none; display: grid; place-items: center; width: 30px; height: 30px; border-radius: 8px; background: var(--sx-accent); color: #ffffff; font-family: var(--sx-display); font-weight: 800; font-size: 16px; }
.tile__card--profile { grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 16px; }
.tile__card--profile > div { display: grid; gap: 8px; }
.tile__avatar { width: 64px; height: 64px; font-size: 18px; }
.tile__flags { font-size: 13px; color: var(--sx-ink-soft); }
.tile__card--route { position: relative; height: 170px; margin-left: 16px; margin-right: 0; background: var(--sx-map); overflow: hidden; }
.tile__card--route svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* start and finish dots on the route, and the GPX file it came from */
.route__end {
  position: absolute; width: 14px; height: 14px; translate: -50% -50%;
  border-radius: 50%; background: #ffffff; border: 4px solid var(--sx-accent);
  box-shadow: 0 1px 4px rgba(22, 32, 58, 0.2);
}
.route__end--finish { background: var(--sx-accent); border-color: #ffffff; }
.route__file {
  position: absolute; left: 14px; top: 14px;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 8px 8px 8px; border-radius: 12px; background: #ffffff;
  box-shadow: 0 4px 16px rgba(22, 32, 58, 0.14);
  font-size: 13px;
}
.route__file > span:nth-child(2) { display: grid; line-height: 1.25; }
.route__file small { color: var(--sx-ink-faint); font-size: 12px; }
.route__file-icon {
  display: grid; place-items: center; width: 34px; height: 40px; border-radius: 6px;
  background: var(--sx-band); color: var(--sx-sky); font-size: 10px; font-weight: 800; letter-spacing: 0.04em;
}
.route__file b { margin-left: 6px; padding: 7px 12px; border-radius: 8px; background: var(--sx-accent); color: #ffffff; font-size: 12px; font-weight: 600; }

.tile__list { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.tile__list li { padding: 9px 12px; border-radius: 10px; background: var(--sx-band); font-weight: 500; }
.tile__list li.is-on { background: var(--sx-accent); color: #ffffff; }
.tile__tempo { display: grid; gap: 6px; padding-top: 6px; }
.tile__tempo .tempo__now { font-size: 20px; }
.tile__list--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tile__list--two li { padding-inline: 10px; font-size: 14px; white-space: nowrap; }
.tile__card--share { grid-template-columns: 64px minmax(0, 1fr); align-items: center; gap: 12px; }
.tile__card--share .tile__url { grid-column: 1 / -1; }
.tile__scan { display: grid; gap: 2px; font-size: 13px; line-height: 1.3; }
.tile__scan small { color: var(--sx-ink-faint); font-size: 12px; }
a.tile__qr { display: block; }
.tile__qr svg { display: block; width: 100%; height: 100%; }

/* Rider profiles: three profile cards fanned out like a hand of cards.
   The front one stands straight, half off the tile's right edge; the
   others pivot around a point below it, swinging out down and left. */
.fan { --w: 280px; position: relative; flex: 1; min-height: 250px; margin: 0 0 -26px; }
.fan__card {
  position: absolute; top: 0; bottom: -40px; right: calc(var(--w) * -0.5); width: var(--w);
  display: grid; align-content: start; justify-items: start; gap: 12px; padding: 22px;
  border-radius: 18px; background: #ffffff;
  box-shadow: 0 10px 30px rgba(22, 32, 58, 0.12), 0 0 0 1px rgba(22, 32, 58, 0.05);
  transform-origin: 50% 260%;
}
.fan__head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.fan__head > span:last-child { display: grid; min-width: 0; line-height: 1.25; }
.fan__card .sx-avatar { flex: none; width: 56px; height: 56px; font-size: 18px; }
.fan__card strong { font-size: 17px; font-weight: 600; }
.fan__card small { color: var(--sx-ink-faint); font-size: 13px; }
.fan__bio { margin: 0; font-size: 13px; line-height: 1.45; color: var(--sx-ink-soft); }
.fan__bike { font-size: 13px; color: var(--sx-ink-soft); white-space: nowrap; }
.fan__card--front  { z-index: 3; }
.fan__card--middle { z-index: 2; rotate: -13deg; }
.fan__card--back   { z-index: 1; rotate: -26deg; }
@media (max-width: 560px) {
  .fan { --w: 220px; }
}
.tile__qr { width: 64px; height: 64px; padding: 4px; border-radius: 8px; background: #ffffff; box-shadow: inset 0 0 0 1px var(--sx-chip); }
.tile__qr path { fill: var(--sx-ink); }
.tile__card--profile strong small { margin-left: 4px; font-weight: 500; color: var(--sx-ink-faint); }
.tile__garage { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 13px; font-weight: 500; }
.tempo { position: relative; height: 6px; margin-top: 10px; border-radius: 999px; background: linear-gradient(90deg, #cfe0ff, var(--sx-accent)); }
.tempo i { position: absolute; top: 50%; left: var(--at); width: 20px; height: 20px; border-radius: 50%; background: #ffffff; border: 2px solid var(--sx-accent); transform: translate(-50%, -50%); box-shadow: 0 2px 6px rgba(22, 32, 58, 0.18); }
.tempo__ends { display: flex; justify-content: space-between; gap: 8px; font-size: 11px; color: var(--sx-ink-faint); }
.tempo__now { font-family: var(--sx-display); font-size: 22px; font-weight: 800; }
.tile__card--summary { gap: 0; }
.summary__row { display: flex; justify-content: space-between; gap: 12px; padding-block: 8px; border-bottom: 1px solid var(--sx-line); }
.summary__row:last-child { border-bottom: 0; }
.summary__row span { color: var(--sx-ink-soft); }
.summary__row b { font-weight: 600; text-align: right; }
.tile__follow { padding: 9px 0; border-radius: 10px; background: var(--sx-accent); color: #ffffff; text-align: center; font-weight: 600; font-size: 13px; }
.tile small { color: var(--sx-ink-faint); font-size: 12px; }
.tile__url { padding: 9px 12px; border-radius: 10px; background: var(--sx-band); font-weight: 600; color: var(--sx-sky); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 900px) {
  /* Stacked on a phone, screen over words, as before: no room for a line
     between two columns. */
  .how2__steps { gap: 40px; }
  .how2__steps::before { display: none; }
  .how2__step { display: flex; flex-direction: column; align-items: center; }
  .how2__step > .how2__screen { max-width: none; }
  .how2__step > h3, .how2__step:nth-child(even) > h3 { margin-top: 24px; }
  .how2__step > h3, .how2__step > p,
  .how2__step:nth-child(even) > h3, .how2__step:nth-child(even) > p { text-align: center; align-self: center; }
  .bento-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tile--more { display: none; } /* stacked, they would only fade into a long blank */
  .tile, .tile--big { min-height: 0; }
  /* the attendance card is pinned to the bottom, so without the tile's min-height it
     covered the text: room for the card (196px at its tallest) under the text instead */
  .tile.tile--attend { padding-bottom: 212px; }
  .story__inner { grid-template-columns: minmax(0, 1fr); }
  .story__photo { min-height: 300px; }
}

@media (max-width: 560px) {
  .bento-grid { grid-template-columns: minmax(0, 1fr); }
  .tile--big { grid-column: auto; }
  .how2__head { display: none; } /* "Ride with strangers…": a phone goes straight to the map */
  .tile__sub-wide { display: none; }
  .tile__sub-narrow { display: inline; }
  .tile--desk { display: none; } /* the four short tiles: on a phone they only add scroll */
  /* nothing left for the fade to fade */
  .bento-grid { -webkit-mask-image: none; mask-image: none; margin-bottom: 0 !important; } /* beats js/bento.js's inline margin */
}

/* On a phone the features sit on the organisers panel's sky, and their top
   edge rises in a soft hump over the white above, like the page's over the
   hero (.how2::before). The hump is the sky's top colour, where the
   gradient starts, so the two read as one; the sky clears to white by the
   bottom, so the organisers panel sits on white as before. */
@media (max-width: 560px) {
  .features {
    --features-rise: 40px;
    position: relative; overflow-x: clip;
    margin-top: var(--features-rise);
    /* no highlight like the panel's: the hump can't carry it, and it showed as a step at the seam */
    background: linear-gradient(180deg, #4a71bd 0%, #6087cd 25%, #a3a9c8 55%, #ffffff 100%);
  }
  .features::before {
    content: ""; position: absolute; left: -20%; right: -20%;
    top: calc(-1 * var(--features-rise)); height: calc(var(--features-rise) + 1px);
    background: #4a71bd;
    border-radius: 50% 50% 0 0 / 100% 100% 0 0;
  }
  .features__head .sx-title { color: #ffffff; }
  .features__head p { color: rgba(255, 255, 255, 0.82); }
}

/* ================= the mocks acting out their feature =================
   js/motion.js adds .js-motion to <html> (never with reduced motion) and
   .is-live to a tile, step, ride card or the organisers demo as it comes
   into view. Without either, everything shows in its finished state. */
.js-motion { --live-ease: cubic-bezier(0.22, 1, 0.36, 1); }

/* how it works: pins pop onto the map, riders join left to right, messages arrive */
/* A ride pin's entrance as the app plays it (Map/PinViews.swift,
   playIntroIfReady): a spring from 40% size and invisible to full, then the
   host's face a beat after with its own quick pop, the pins arriving north to
   south down the map (MapScreen.introDelay; the --i order in how2.html).
   CSS has no springs, so --pin-spring is SwiftUI's
   .spring(response: 0.45, dampingFraction: 0.62) sampled into linear():
   0.8s to settle, 8% past full size at 0.28s, then a hair under and back. */
.js-motion { --pin-spring: linear(0, 0.0345, 0.1218, 0.2409, 0.3747, 0.5101, 0.638, 0.7521, 0.849, 0.9275, 0.9877, 1.0312, 1.06, 1.0764, 1.0831, 1.0824, 1.0765, 1.0674, 1.0565, 1.0451, 1.0341, 1.0241, 1.0155, 1.0083, 1.0027, 0.9986, 0.9958, 0.994, 0.9932, 0.993, 0.9934, 0.9941, 0.9949, 0.9959, 0.9968, 0.9977, 0.9985, 0.9991, 0.9996, 1, 1); }
.js-motion .how2__step .map-pin {
  opacity: 0; scale: 0.4;
  transition: opacity 0.8s var(--pin-spring), scale 0.8s var(--pin-spring);
  transition-delay: calc(0.2s + var(--i) * 0.15s);
}
.js-motion .how2__step.is-live .map-pin { opacity: 1; scale: none; }
.js-motion .how2__step .map-pin__host {
  opacity: 0; scale: 0.4;
  transition: opacity 0.25s ease-out, scale 0.25s ease-out;
  transition-delay: calc(0.38s + var(--i) * 0.15s);   /* the pin's own delay + 0.18s */
}
.js-motion .how2__step.is-live .map-pin__host { opacity: 1; scale: none; }
/* riders join one at a time, left to right, on the map pins' spring */
.js-motion .ride-mock__rider {
  opacity: 0; scale: 0.4;
  transition: opacity 0.8s var(--pin-spring), scale 0.8s var(--pin-spring);
  transition-delay: calc(0.4s + var(--n) * 0.12s);
}
.js-motion .is-live .ride-mock__rider { opacity: 1; scale: none; }
.js-motion .how2__chat-scroll .sx-bubble {
  opacity: 0; transform: translateY(10px) scale(0.96);
  transition: opacity 0.4s ease, transform 0.5s var(--live-ease);
}
.js-motion .is-live .sx-bubble { opacity: 1; transform: none; }
.js-motion .is-live .sx-bubble:nth-child(1) { transition-delay: 0.2s; }
.js-motion .is-live .sx-bubble:nth-child(2) { transition-delay: 0.6s; }
.js-motion .is-live .sx-bubble:nth-child(3) { transition-delay: 1.0s; }
.js-motion .is-live .sx-bubble:nth-child(4) { transition-delay: 1.4s; }
.js-motion .is-live .sx-bubble:nth-child(5) { transition-delay: 1.8s; }
.js-motion .is-live .sx-bubble:nth-child(6) { transition-delay: 2.2s; }
.js-motion .is-live .sx-bubble:nth-child(7) { transition-delay: 2.6s; }

/* routes draw themselves, then their stops pop up */
.js-motion .tile .sx-map-route { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.8s var(--live-ease) 0.2s; }
.js-motion .is-live .sx-map-route { stroke-dashoffset: 0; }
.js-motion .route__end, .js-motion .route__file { opacity: 0; scale: 0.8; transition: opacity 0.35s ease, scale 0.45s var(--live-ease); }
.js-motion .is-live .route__end, .js-motion .is-live .route__file { opacity: 1; scale: 1; }
.js-motion .is-live .route__end { transition-delay: 0.3s; }
.js-motion .is-live .route__end--finish { transition-delay: 1.9s; }
.js-motion .is-live .route__file { transition-delay: 2.2s; }
.js-motion .is-live .route__stop:nth-of-type(1) { transition-delay: 0.4s; }
.js-motion .is-live .route__stop:nth-of-type(2) { transition-delay: 1.0s; }
.js-motion .is-live .route__stop:nth-of-type(3) { transition-delay: 1.6s; }
.js-motion .is-live .route__km { transition-delay: 2.0s; }

/* a choice gets made: type of ride */
@keyframes live-select { from { background: var(--sx-band); color: var(--sx-ink); } }
.js-motion .tile__list li.is-on { animation-play-state: paused; }
.js-motion .tile.is-live .tile__list li.is-on { animation: live-select 0.4s ease 0.9s both; }
.js-motion .tile:not(.is-live) .tile__list li.is-on { background: var(--sx-band); color: var(--sx-ink); }

/* the tempo slider glides to Cruising */
.js-motion .tempo i { left: 2%; transition: left 1.3s var(--live-ease) 0.4s; }
.js-motion .is-live .tempo i { left: var(--at); }

/* a button gets tapped (its text changes in js/motion.js) */
@keyframes live-tap { 0%, 100% { scale: 1; } 40% { scale: 0.93; } }
.js-motion .is-live .tile__buttons .is-primary, .js-motion .is-live .tile__follow { animation: live-tap 0.3s ease 1.1s; }
.tile__follow.is-done { background: var(--sx-chip); color: var(--sx-ink); }

/* The chat plays out from the bottom up, like a real one: a message takes
   no room until it arrives, then grows in as its "typing…" gives way, so
   nothing above it jumps. The link lands, Sanne and Daan answer, and the
   host signs off.

   The bubble fades in WHILE its space opens, not after. max-height eases
   from 0 to a ceiling well above any real message, so the box reaches the
   bubble's own height early in the run; holding the bubble invisible past
   that (opacity 0 until 55%) opened an empty gap that shoved the chat up
   a third of a second before anything appeared there, which is what read
   as a skip. */
@keyframes wa-arrive {
  0% { max-height: 0; opacity: 0; translate: 0 10px; margin-block: -2px; }
  60% { opacity: 1; }
  100% { max-height: 420px; opacity: 1; translate: 0 0; margin-block: 0; }
}
.js-motion .organisers__demo .wa__msg { max-height: 0; opacity: 0; margin-block: -2px; }
.js-motion .organisers__demo.is-live .wa__msg { animation: wa-arrive 0.55s ease both; }
.js-motion .organisers__demo.is-live .wa__msg--link { animation-delay: 0.3s; }
/* A reply and the "typing…" before it share one slot, both in the same
   grid cell, and the slot's height only ever grows: 0 → the dots → the
   reply. One layout change per reply instead of two in opposite
   directions, which is what makes the handoff impossible to bounce —
   the dots cross-fade into the reply inside the slot rather than giving
   their box back first. Delays alone could never fix this: the dots took
   ~120ms to fold away and the bubble ~60ms to grow, so the tail of the
   collapse always closed the gap with nothing opening it. The slot is
   already reply-sized while the dots show, so the room is made once,
   when the dots appear.

   Each slot drives its own two children, so all three stay in step from
   one pair of values. */
.wa__turn { display: grid; }
.wa__turn > * { grid-area: 1 / 1; }
.js-motion .organisers__demo .wa__turn > * { align-self: end; justify-self: start; }
.wa__turn--sanne { --turn-at: 1s; --turn-dur: 1.4s; }
.wa__turn--daan { --turn-at: 2.5s; --turn-dur: 1.45s; }
@keyframes wa-turn {
  0% { max-height: 0; margin-block: -2px; }
  9% { max-height: 40px; margin-block: 0; }
  72% { max-height: 40px; }
  100% { max-height: 420px; }
}
@keyframes wa-turn-dots { 0%, 4% { opacity: 0; } 10%, 70% { opacity: 1; } 80%, 100% { opacity: 0; } }
@keyframes wa-turn-msg { 0%, 72% { opacity: 0; } 88%, 100% { opacity: 1; } }
.js-motion .organisers__demo .wa__turn { max-height: 0; margin-block: -2px; }
/* inside a slot the pair are driven by the slot, not by their own boxes */
.js-motion .organisers__demo .wa__turn .wa__typing,
.js-motion .organisers__demo .wa__turn .wa__msg { max-height: none; margin-block: 0; }
.js-motion .organisers__demo.is-live .wa__turn { animation: wa-turn var(--turn-dur) ease var(--turn-at) both; }
.js-motion .organisers__demo.is-live .wa__turn .wa__typing { animation: wa-turn-dots var(--turn-dur) ease var(--turn-at) both; }
.js-motion .organisers__demo.is-live .wa__turn .wa__msg { animation: wa-turn-msg var(--turn-dur) ease var(--turn-at) both; }
.js-motion .organisers__demo.is-live .wa__msg--reply { animation-delay: 4.6s; }
.js-motion .organisers__demo .sx-map-route { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.2s var(--live-ease) 0.8s; }
.js-motion .organisers__demo.is-live .sx-map-route { stroke-dashoffset: 0; }
/* "typing…": the dots themselves. Opening and giving way is the slot's
   job above; this is only how they look and bounce. */
.wa__typing { display: none; }
.js-motion .wa__typing {
  align-self: flex-start; display: flex; gap: 4px; margin-left: 8px; padding: 0 14px;
  border-radius: 9px 9px 9px 0; background: #ffffff; box-shadow: 0 1px 0.5px rgba(11, 20, 26, 0.13);
  max-height: 0; opacity: 0; overflow: hidden; margin-block: -2px;
}
.wa__typing i { width: 8px; height: 8px; margin-block: 14px; border-radius: 50%; background: #8696a0; animation: wa-dot 1s ease-in-out infinite; }
.wa__typing i:nth-child(2) { animation-delay: 0.15s; }
.wa__typing i:nth-child(3) { animation-delay: 0.3s; }
@keyframes wa-dot { 0%, 60%, 100% { translate: 0 0; opacity: 0.5; } 30% { translate: 0 -4px; opacity: 1; } }
/* each step lights up as its part of the chat happens */
.js-motion .org-step { opacity: 0.4; transition: opacity 0.5s ease; }
.js-motion .organisers__inner:has(.organisers__demo.is-live) .org-step { opacity: 1; }
.js-motion .organisers__inner:has(.organisers__demo.is-live) .org-step--post { transition-delay: 0.1s; }
.js-motion .organisers__inner:has(.organisers__demo.is-live) .org-step--share { transition-delay: 0.6s; }
.js-motion .organisers__inner:has(.organisers__demo.is-live) .org-step--join { transition-delay: 3.6s; }
.js-motion .organisers__demo .sx-map-route { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.2s var(--live-ease) 0.6s; }
.js-motion .organisers__demo.is-live .sx-map-route { stroke-dashoffset: 0; }

/* On a phone the chat is shown finished, not played, and the phone is cut
   off just under its last message rather than hanging off the panel with a
   screen of empty wallpaper below the chat: the panel is only as tall as
   the text and the conversation. */
@media (max-width: 560px) {
  .organisers { --phone-drop: -56px; } /* 16px of wallpaper under the last message, then the panel's edge */
  .phone { -webkit-mask-image: none; mask-image: none; }
  .wa { height: auto; }
  .wa__bar { display: none; }
  /* The chat scrolled up under the header, as if read a moment later: the
     day chip gone and the ride card's map half out of sight. The map is
     82% of the body's width at 320:168, so half of it is 21.5% of that
     width (a margin's % is of the width), plus the bubble's 4px top pad. */
  .wa__body { position: static; margin-top: 111px; padding-top: 0; padding-bottom: 60px; overflow: hidden; }
  .wa__day { display: none; }
  .organisers .organisers__demo .wa__body > .wa__msg.wa__msg--link { margin-top: calc(-21.5% - 4px); } /* outranks the no-animation reset above */
  /* the text without its steps, on the middle line */
  .org-steps { display: none; }
  .organisers__text .sx-pill { justify-self: center; }
  .organisers__inner { gap: 32px; }
  .organisers__text { text-align: center; justify-items: center; padding-bottom: 0; }
  .org-stores { justify-content: center; flex-wrap: nowrap; gap: 8px; } /* side by side, a little smaller to fit */
  .org-store { gap: 8px; padding: 10px 12px; font-size: 14px; }
  .js-motion .organisers__demo .wa__msg, .js-motion .organisers__demo.is-live .wa__msg,
  .js-motion .organisers__demo .wa__turn, .js-motion .organisers__demo.is-live .wa__turn,
  .js-motion .organisers__demo .wa__turn .wa__msg, .js-motion .organisers__demo.is-live .wa__turn .wa__msg { max-height: none; opacity: 1; margin-block: 0; translate: none; animation: none; }
  .js-motion .organisers__demo .wa__typing { display: none; }
  .js-motion .organisers__demo .sx-map-route { stroke-dashoffset: 0; transition: none; }
  .js-motion .org-step, .js-motion .organisers__inner:has(.organisers__demo.is-live) .org-step { opacity: 1; transition: none; }
}

/* the profile cards fan out from behind the front one */
.js-motion .fan__card--middle, .js-motion .fan__card--back { transition: rotate 0.9s var(--live-ease); }
.js-motion .fan__card--middle { transition-delay: 0.3s; }
.js-motion .fan__card--back { transition-delay: 0.4s; }
.js-motion .tile:not(.is-live) .fan__card--middle,
.js-motion .tile:not(.is-live) .fan__card--back { rotate: 0deg; }

/* the host kicks a rider, on a 7s loop: tap Edit, red minuses pop in,
   tap Mark's minus, Kick slides in, tap Kick, his row folds away, edit
   mode closes and a moment later Mark is back for the next round */
.js-motion .tile.is-live .att__edit { animation: att-edit 7s ease 0.8s infinite; }
.js-motion .tile.is-live .att__edit span:first-child { animation: att-edit-label 7s steps(1) 0.8s infinite; }
.js-motion .tile.is-live .att__edit span + span { animation: att-done-label 7s steps(1) 0.8s infinite; }
.js-motion .tile.is-live .att__minus { animation: att-minus 7s ease 0.8s infinite; }
.js-motion .tile.is-live .tile__rider--kick .att__minus { animation-name: att-minus-mark; }
.js-motion .tile.is-live .tile__rider--kick .tile__tag { animation: att-tag 7s ease 0.8s infinite; }
.js-motion .tile.is-live .tile__rider--kick b { animation: att-kick 7s ease 0.8s infinite; }
.js-motion .tile.is-live .tile__rider--kick { animation: att-row 7s ease 0.8s infinite; }
@keyframes att-edit { 0%, 6%, 12%, 100% { opacity: 1; } 8% { opacity: 0.35; } }
@keyframes att-edit-label { 0% { opacity: 1; } 10% { opacity: 0; } 72% { opacity: 1; } }
@keyframes att-done-label { 0% { opacity: 0; } 10% { opacity: 1; } 72% { opacity: 0; } }
@keyframes att-minus {
  0%, 10% { width: 0; margin-right: -10px; scale: 0; }
  16%, 66% { width: 20px; margin-right: 0; scale: 1; }
  72%, 100% { width: 0; margin-right: -10px; scale: 0; }
}
@keyframes att-minus-mark {
  0%, 10% { width: 0; margin-right: -10px; scale: 0; rotate: 0deg; }
  16%, 24% { width: 20px; margin-right: 0; scale: 1; rotate: 0deg; }
  27% { width: 20px; margin-right: 0; scale: 0.8; rotate: 0deg; }
  31%, 66% { width: 20px; margin-right: 0; scale: 1; rotate: -90deg; }
  72%, 100% { width: 0; margin-right: -10px; scale: 0; rotate: 0deg; }
}
@keyframes att-tag { 0%, 28% { opacity: 1; } 32%, 84% { opacity: 0; } 92%, 100% { opacity: 1; } }
@keyframes att-kick {
  0%, 28% { opacity: 0; translate: 16px -50%; scale: 1; }
  33%, 42% { opacity: 1; translate: 0 -50%; scale: 1; }
  45% { opacity: 1; translate: 0 -50%; scale: 0.88; }
  48%, 50% { opacity: 1; translate: 0 -50%; scale: 1; }
  54%, 100% { opacity: 0; translate: 0 -50%; scale: 1; }
}
@keyframes att-row {
  0%, 48% { opacity: 1; translate: 0 0; height: 44px; }
  55% { opacity: 0; translate: 40px 0; height: 44px; }
  62%, 80% { opacity: 0; translate: 0 0; height: 0; }
  92%, 100% { opacity: 1; translate: 0 0; height: 44px; }
}
/* the push slides in */
.js-motion .tile__push { opacity: 0; translate: 0 -12px; transition: opacity 0.4s ease 0.5s, translate 0.5s var(--live-ease) 0.5s; }
.js-motion .tile.is-live .tile__push { opacity: 1; translate: 0 0; }

/* Off screen, the looping mocks hold still (js/motion.js). */
.js-motion .is-paused, .js-motion .is-paused * { animation-play-state: paused !important; }
/* The wheel glide's trailing layers (js/motion.js), only while it glides:
   a layer left at rest on a half pixel renders its text soft. */
.is-gliding .how2__inner, .is-gliding .story__inner, .is-gliding .features__inner,
.is-gliding .organisers__inner, .is-gliding .faq__inner, .is-gliding .footer__wrap { will-change: translate; }

/* One gap between sections, not two: they're all white now, so a section
   that follows another keeps only the space the one above leaves. */
main > section:not(:first-child) { padding-top: 0; }
