/* Self-hosted Space Grotesk Bold (Latin subset, ~12.8KB woff2).
     Used by the wordmark + section headings. The font is served from
     /v1/fonts/space-grotesk-bold.woff2 with immutable Cache-Control;
     the URL never changes for the lifetime of this CSS hash, so a
     future font swap requires bumping the CSS hash to invalidate.
     font-display: swap renders fallback immediately + swaps when the
     woff2 arrives — avoids invisible-text-during-FOIT. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/v1/fonts/space-grotesk-bold.woff2") format("woff2");
  unicode-range: U+0020-007F, U+00A0-00FF, U+2010-2027, U+2030-205E;
}

/* Belt-and-braces overflow guard. The inline wordmark SVG carries
   overflow="visible" so the gold text can bleed past its viewBox for
   the skewX(-12) lean; on the narrowest phones (320-390px) a wordmark
   sized at 264px CSS could still push the body wider than the viewport
   via that visible bleed. overflow-x: clip on the html element kills
   the horizontal scroll without affecting position: sticky descendants
   (overflow: hidden would). 2026-05-23 mobile overflow fix. */
html {
  overflow-x: clip;
}
:root {
  /* Dark-mode-only per [[feedback_runbot_dark_mode_only]]. The
       `light dark` color-scheme declaration would let the browser
       pick light scrollbars + autofill — explicitly `dark` keeps
       the user-agent chrome consistent with the page. */
  color-scheme: dark;
  --gold-dark: #b07d31;
  --gold-light: #f4dd97;
  /* D2: tighter wordmark gradient — kept dark stop, replaced the
       extreme light stop with a single-step lighter shade so the
       gradient reads as a polished material with sheen rather than
       two contrasting colors. Used only on h1 + the hero CTA. */
  --gold-mid: #d8b366;
  --navy: #0a1628;
  --cream: #faf6ec;
  /* Live-indicator dot color. Tailwind emerald-400. Kept green
       per user preference (reverted from a 2026-05-22 cyan swap):
       the green-glow pulse is the signal users associate with
       "this is happening live". --accent-soft is the pulse-start
       glow opacity (0.5) which fades to 0 over 2.4s. */
  --accent: #4ade80;
  --accent-soft: rgba(74, 222, 128, 0.5);
  /* iOS 17 native iMessage bubble colors. Light: #e9e9eb is Apple's
       systemGray5 (incoming bubble background in light mode). Dark:
       #3a3a3c is tertiarySystemFill (incoming bubble background in
       dark mode). Both .bubble (preview) and .mockup-bubble.in
       (how-mockup) read these so every example bubble across the
       page matches real iMessage AND each other. */
  --bubble-bg-light: #e9e9eb;
  --bubble-bg-dark: #3a3a3c;
  --text-light: #1a1a1a;
  --text-light-secondary: #555;
  --text-light-tertiary: #888;
  --text-dark: #ededed;
  --text-dark-secondary: #aaa;
  --text-dark-tertiary: #888;
  /* Typography stack used by headings + wordmark. The first
       custom font loads from /v1/fonts; the rest are system fonts
       that produce a similar shape during the FOUT swap window. */
  --font-display:
    "Space Grotesk", "Inter Tight", "Helvetica Neue", system-ui, -apple-system, sans-serif;
}
body {
  font:
    16px / 1.55 -apple-system,
    BlinkMacSystemFont,
    "SF Pro Text",
    "Segoe UI",
    sans-serif;
  /* Mobile-first: narrow column. Tablet/desktop overrides below
       widen this so the asymmetric hero can spread to 2 columns
       without forcing horizontal scroll. */
  max-width: 640px;
  margin: 0 auto;
  padding: calc(44px + env(safe-area-inset-top)) 24px calc(40px + env(safe-area-inset-bottom));
  color: var(--text-light);
  background: var(--cream);
  text-align: center;
}
/* Asymmetric desktop layout (C2): widen the body so the hero can
     be 2-column. The info-grid + maker sections set their own
     narrower max-width below — only the hero benefits from the
     wider canvas. */
@media (min-width: 760px) {
  body {
    max-width: 1000px;
    padding-left: 40px;
    padding-right: 40px;
  }
}
@media all {
  body {
    color: var(--text-dark);
    /* Gradient mesh: warm gold blob top-left + cool deep-navy blob
       bottom-right layered on the base --navy fill. Adds atmospheric
       depth without competing with content — both blobs sit at ~6% to
       ~10% intensity, well below the eye's read-pull threshold but
       enough to lift the surface above flat-navy "indie app" feel.
       background-attachment: fixed pins the gradient so it doesn't
       scroll-jitter against the foreground content. 2026-05-25 polish. */
    background:
      radial-gradient(ellipse 720px 540px at 12% 8%, rgba(216, 179, 102, 0.09), transparent 60%),
      radial-gradient(ellipse 880px 620px at 88% 92%, rgba(35, 70, 130, 0.22), transparent 60%),
      var(--navy);
    background-attachment: fixed, fixed, fixed;
  }
}
/* Hero block: logo + wordmark + tagline + subhead are visually one
     unit. Logo sits tight against the wordmark cap (0px margin + h1
     line-height 0.95 tucks phantom font-space above the cap). The
     bigger gap (28) only kicks in before the CTA. */
.logo {
  /* SVG aspect tightened to 510×170 (3:1) 2026-05-23 — viewBox trimmed
       to hug the content after the text-position polish. Earlier 200-tall
       viewBox left ~65 units of dead space below the baseline; the
       trimmed box matches the actual content extent (text y=31→135,
       trails y=62→132). 3:1 aspect yields clean integer render
       dimensions at every breakpoint: 264×88 / 360×120 / 420×140.
       max-width: 100% guards against the SVG's intrinsic width="360"
       attribute pushing past the body content width on iPhone SE-class
       widths (320-360px). The aspect-ratio keeps height proportional
       so the 3:1 lockup stays intact when the width clamps down. */
  max-width: 100%;
  height: auto;
  aspect-ratio: 510 / 170;
  width: 360px;
  /* Bottom gap to the tagline. The earlier -16px was a tuck under
       the h1 wordmark (now removed); a small POSITIVE margin gives
       the unified mark room to breathe above the tagline. */
  margin: 0 auto 14px;
  display: block;
  /* Same two-layer drop-shadow recipe as before — tight dark
       drop + soft warm spread = "lit from above on warm surface".
       filter: drop-shadow follows the SVG's alpha silhouette
       including the motion trails, so they pick up the same lift. */
  filter: drop-shadow(0 2px 4px rgba(10, 22, 40, 0.08))
    drop-shadow(0 0 18px rgba(176, 125, 49, 0.16));
}
/* Stride pulse: subtle staggered opacity wave across the 3 motion
     trail bars inside the inline SVG. Single 1.6s cycle on mount
     (was 2.2s looped — see iteration-count refactor below). Middle
     bar (strongest, opacity 0.85) gets the largest swing; outer
     bars (0.55) get a tinier pulse so the focal point is the middle.
     Stagger via animation-delay creates the wave. */
@keyframes stride-pulse-outer {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 0.75;
  }
}
@keyframes stride-pulse-mid {
  0%,
  100% {
    opacity: 0.85;
  }
  50% {
    opacity: 1;
  }
}
/* 2026-05-23 polish: stride animation drops from `infinite` to `1`
     iteration with `animation-fill-mode: forwards` so the trails pulse
     ONCE on arrival, then settle into the steady gradient. Looped
     kinetic motion is a tired 2026 pattern per the landing-page
     research; restraint-motion-only earns more attention than constant
     motion. The keyframes 100% value equals the resting opacity, so
     the "forwards" final state IS the static brand state. */
.logo g rect:nth-child(1) {
  animation: stride-pulse-outer 1.6s ease-in-out 1 forwards;
  animation-delay: 0s;
}
.logo g rect:nth-child(2) {
  animation: stride-pulse-mid 1.6s ease-in-out 1 forwards;
  animation-delay: 0.18s;
}
.logo g rect:nth-child(3) {
  animation: stride-pulse-outer 1.6s ease-in-out 1 forwards;
  animation-delay: 0.36s;
}
/* Page-load fade-in for the hero. animation-fill-mode: backwards
     holds the FROM state until the animation starts so the element
     doesn't flash visible for a single frame before kicking off.
     hero-preview gets a 150ms delay so it slides in AFTER the text,
     suggesting "here's what you get" as the punchline. */
@keyframes hero-fade-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.hero-text {
  animation: hero-fade-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
.hero-preview {
  animation: hero-fade-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) 0.15s backwards;
}
@media (prefers-reduced-motion: reduce) {
  .logo g rect,
  .hero-text,
  .hero-preview {
    animation: none;
  }
}
@media all {
  .logo {
    /* Warm gold halo using --gold-mid (#d8b366) at 14% alpha instead
         of --gold-light. Aligns the symbol's atmospheric glow with the
         wordmark's gold-dark → gold-mid gradient — the whole hero
         lockup now sits in one tonal family. */
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35))
      drop-shadow(0 0 22px rgba(216, 179, 102, 0.16));
  }
}
/* C3: section dividers removed in favor of whitespace + typographic
     hierarchy. Modern athletic brands trust whitespace for separation;
     the gold hairline read as "brochure" decoration not "navigation".
     Adjacent sections (.info-grid, .maker) set their own top margins
     to produce the visual gap. */
/* h1 wrapping the wordmark SVG. UA stylesheet defaults (2em font-size,
     0.67em margins) would push the layout around; we strip them so the
     h1 is purely a semantic anchor for screen readers + page-outline
     tools. The SVG inside keeps its own dimensions. 2026-05-23 a11y. */
.logo-h1 {
  margin: 0;
  padding: 0;
  font-size: inherit;
  font-weight: inherit;
  line-height: 0;
}
.hero {
  margin-bottom: 32px;
}
/* info-grid sits BELOW the tracker-row (which has its own 32px top
     margin from .hero). The previous 8px gap was inherited from a
     pre-2026-05-23 layout where info-grid followed .hero directly;
     after the tracker-row promotion the 8px read as crammed. 32px now
     matches the hero->tracker visual rhythm and gives the how/FAQ
     section room to breathe on mobile. */
.how {
  margin-top: 32px;
}
.maker {
  margin-top: 40px;
}
h1 {
  /* B1: custom designed typeface for the wordmark. Space Grotesk
       Bold has more character than the default system-bold while
       staying highly legible — the slightly geometric counters
       (the 'o', 'b') hold the running-poster vibe. */
  font-family: var(--font-display);
  font-size: 56px;
  font-weight: 700;
  letter-spacing: -1.5px;
  line-height: 0.95;
  margin: 0 0 10px;
  /* D2: tighter wordmark gradient — single-step lighter shade
       (--gold-mid) instead of the previous high-contrast jump to
       --gold-light. Reads as a polished material with sheen, not
       two contrasting colors. */
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
@media all {
  /* Wordmark uses the SAME --gold-dark → --gold-mid palette as the
       symbol's stride trails, so the runbot text and the RB symbol
       above it share one tonal language. Earlier hardcoded lighter
       stops (#d4a45e → #ebd29e) created visible tonal drift between
       symbol (honey-gold) and wordmark (lemon-yellow) — unified
       2026-05-22 by user direction. */
  h1 {
    background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
    -webkit-background-clip: text;
    background-clip: text;
  }
}
.tagline {
  font-size: 18px;
  color: var(--text-light-secondary);
  margin: 0 0 6px;
  line-height: 1.4;
}
.subhead {
  font-size: 14px;
  color: var(--text-light-tertiary);
  margin: 0 0 28px;
  letter-spacing: 0.2px;
}
@media all {
  .tagline {
    color: var(--text-dark-secondary);
  }
  .subhead {
    color: var(--text-dark-tertiary);
  }
}
a.btn {
  display: inline-block;
  padding: 18px 36px;
  /* Same gold-dark → gold-mid gradient as the wordmark above, so the
       primary CTA reads as a continuation of the brand surface rather
       than a separate visual element. Earlier wider range
       (gold-dark → gold-light) had more pop but felt disconnected. */
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  color: var(--navy);
  text-decoration: none;
  border-radius: 14px;
  font-size: 18px;
  font-weight: 700;
  /* white-space:nowrap prevents the CTA label from wrapping to two
       lines on narrow iPhones (SE = 320px). The button is already
       sized comfortably for the label, but flex-row contexts could
       otherwise squeeze it. Pre-launch polish 2026-05-22. */
  white-space: nowrap;
  /* Inset highlight reads as a CTA "lit from above" (iOS pattern);
       colored drop-shadow matches the gold so the button feels warm
       rather than floating on a generic neutral shadow; thin 1px
       under-line gives a tiny press-receipt edge in light mode. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    0 8px 20px -6px rgba(176, 125, 49, 0.45),
    0 2px 0 rgba(10, 22, 40, 0.06);
  transition:
    transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1),
    box-shadow 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    filter 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
  /* Slow ambient pulse on the gold drop-shadow: 4s cycle, subtle ring
       expansion. Tells the eye "this is alive, click me" without being
       aggressive. Hover/focus/active disable via animation:none so the
       designed interaction states get a clean canvas. Reduced-motion
       visitors get no animation at all. 2026-05-23 v3 enticement polish. */
  animation: ctaPulse 4s ease-in-out infinite;
  animation-delay: 1.2s;
}
@keyframes ctaPulse {
  0%,
  100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.4),
      0 8px 20px -6px rgba(176, 125, 49, 0.45),
      0 0 0 0 rgba(176, 125, 49, 0),
      0 2px 0 rgba(10, 22, 40, 0.06);
  }
  50% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.4),
      0 10px 28px -4px rgba(176, 125, 49, 0.58),
      0 0 0 8px rgba(176, 125, 49, 0.08),
      0 2px 0 rgba(10, 22, 40, 0.06);
  }
}
@media (prefers-reduced-motion: reduce) {
  a.btn {
    animation: none;
  }
}
a.btn:hover,
a.btn:focus-visible {
  /* Saturate rather than brighten: brightness on a gold gradient
       washes it out, while a small saturation bump deepens the gold
       and reads as "active" without going chalky. animation:none
       disables the ambient pulse so the designed hover shadow wins. */
  animation: none;
  filter: saturate(1.08);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.45),
    0 12px 28px -8px rgba(176, 125, 49, 0.5),
    0 2px 0 rgba(10, 22, 40, 0.06);
}
a.btn:active {
  /* Press-down: button sinks ~1px, shadow collapses inward. Gives a
       tactile receipt that mirrors UIControlEvent.touchDown feedback.
       animation:none keeps the ambient pulse from fighting the press. */
  animation: none;
  transform: translateY(1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.3),
    0 2px 6px -2px rgba(176, 125, 49, 0.5);
  filter: saturate(1.1);
  transition-duration: 0.06s;
}
/* Pseudo-element arrow that slides 4px right on hover. Pseudo
     inherits text color so it stays in the navy gradient automatically;
     content is purely decorative (aria handled by the visible CTA
     label text). */
a.btn::after {
  /* CSS unicode escape for U+2192 (right arrow). The escape itself
       is double-backslashed because this content lives inside a JS
       template literal -- the runtime sees one backslash, CSS then
       parses the codepoint and renders the glyph. */
  content: " \2192";
  display: inline-block;
  margin-left: 4px;
  transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
a.btn:hover::after,
a.btn:focus-visible::after {
  transform: translateX(4px);
}
a.btn:focus-visible {
  /* Outline-offset that doesn't crash into the inset highlight. */
  outline: 2px solid var(--gold-light);
  outline-offset: 4px;
}
.secondary {
  margin-top: 22px;
  color: var(--text-light-secondary);
  font-size: 14px;
  line-height: 1.7;
}
@media all {
  .secondary {
    color: var(--text-dark-tertiary);
  }
}
/* Small hint sitting under the CTA button. Tells the user what to
     send once Messages opens (the "what next" moment) so they don't
     stall at the empty compose field. */
.cta-hint {
  margin: 14px auto 0;
  max-width: 360px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-light-tertiary);
  text-align: center;
}
@media all {
  .cta-hint {
    color: var(--text-dark-tertiary);
  }
}
/* CTA row: primary "Get started" button (save-contact was demoted
     to a small dotted-underline link below the cta-hint 2026-05-23 v3,
     so this row now holds the single primary action). Mobile-first
     centering; desktop (>=760px) further left-aligns the row to match
     .hero-text's text-align: left. */
.cta-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
@media (min-width: 600px) {
  .cta-row {
    flex-direction: row;
    justify-content: center;
    flex-wrap: wrap;
  }
}
/* Secondary-action button: outline / ghost variant of the primary
     CTA. Same shape language so they read as a family, lower visual
     weight so they don't compete. Used for save-to-contacts.
     ::before adds a tiny inline download glyph (U+2913) so the
     visitor knows the click triggers a .vcf file download before
     they tap. Hover state mirrors the primary CTA's translateY(-1px)
     lift so they read as a two-tier action system. */
a.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* Vertical padding bumped 10 → 12 so the button height pairs
       more closely with the primary .btn (which is 14px vertical
       on mobile, 18px on desktop). Side-by-side they now read as
       a two-tier action system rather than mismatched heights.
       min-height 44px guarantees the tap target meets WCAG 2.5.5 +
       Apple HIG floor regardless of where the inner glyph + label
       horizontally collapse. align-items + justify-content center
       keep the contents visually centered when the padding alone
       would put height at ~38px; the floor adds invisible touch
       slop above and below. 2026-05-25 a11y polish. */
  min-height: 44px;
  padding: 12px 20px;
  background: transparent;
  border: 1.5px solid var(--gold-dark);
  color: var(--gold-dark);
  font-size: 14px;
  font-weight: 600;
  /* Radius matches the primary .btn (14 mobile, 16 desktop) so the
       paired CTAs share the same corner curvature. */
  border-radius: 14px;
  text-decoration: none;
  background-image: none;
  transition:
    background-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1);
}
a.btn-secondary::before {
  content: "\2913";
  font-size: 12px;
  line-height: 1;
  opacity: 0.85;
  /* Inline-block + transform pairs with the hover transition below
       so the glyph bumps down a hair on hover, suggesting "pulling
       the file down" without being a heavy animation. */
  display: inline-block;
  transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
a.btn-secondary:hover,
a.btn-secondary:focus-visible {
  background-color: rgba(176, 125, 49, 0.08);
  background-size: 0 0;
  transform: translateY(-1px);
  outline: none;
}
a.btn-secondary:hover::before,
a.btn-secondary:focus-visible::before {
  transform: translateY(1px);
  opacity: 1;
}
a.btn-secondary:active {
  transform: translateY(0);
  background-color: rgba(176, 125, 49, 0.14);
  transition-duration: 0.06s;
}
@media all {
  a.btn-secondary {
    border-color: var(--gold-light);
    color: var(--gold-light);
  }
  a.btn-secondary:hover,
  a.btn-secondary:focus-visible {
    background-color: rgba(244, 221, 151, 0.08);
  }
  a.btn-secondary:active {
    background-color: rgba(244, 221, 151, 0.14);
  }
}
/* .chatbot-hint block removed 2026-05-22 (the "or just text it
     anything" lead + 3 example questions + tail). The chatbot
     capability still exists; the landing-page hint just isn't
     pulling its weight in the visual hierarchy. */
/* How-mockup: shows the first 30 seconds of conversation visually
     inside the "how it works" section. Mirrors the .preview-frame
     above (same gold-gradient top edge, same hairline outer border,
     same box-shadow, same 24px radius) so the two iMessage surfaces
     read as a paired pair: the big preview is the OUTCOME (run
     announces), the small one is the JOURNEY (setup DM). Only the
     inner padding + interior bubble scale differ. */
.how-mockup {
  margin-top: 22px;
  padding: 14px 16px 20px;
  border: 1px solid rgba(128, 128, 128, 0.16);
  border-radius: 24px;
  box-shadow: 0 8px 28px rgba(10, 22, 40, 0.06);
  background: var(--cream);
  /* Match the .preview-frame's non-selectable behavior so both
       mockups feel like screenshots rather than copyable text. Without
       this, iOS Safari would show the long-press select-and-copy menu
       on how-mockup bubbles while suppressing it on the preview-frame
       above, which reads as an inconsistency. */
  -webkit-user-select: none;
  user-select: none;
}
@media all {
  .how-mockup {
    border-color: rgba(255, 255, 255, 0.08);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
    background: var(--navy);
  }
}
/* Header bar styled to match .preview-header: centered label with a
     hairline divider, identical padding + margin so the two mockups
     line up vertically when both are visible on desktop.
     Label copy + leading micro-tag distinguishes this mockup as a
     TUTORIAL walkthrough from the hero's LIVE-message preview, which
     used to look like a near-duplicate "second iMessage screenshot."
     2026-05-23 v3 polish. */
.how-mockup-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-light);
  text-align: center;
  padding: 4px 0 12px;
  border-bottom: 1px solid rgba(128, 128, 128, 0.16);
  margin-bottom: 14px;
}
@media all {
  .how-mockup-label {
    color: var(--text-dark);
    border-bottom-color: rgba(255, 255, 255, 0.08);
  }
}
.how-mockup-step-tag {
  display: inline-block;
  margin-right: 8px;
  padding: 2px 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--gold-dark);
  background: rgba(176, 125, 49, 0.12);
  border-radius: 8px;
  vertical-align: 2px;
}
@media all {
  .how-mockup-step-tag {
    color: var(--gold-light);
    background: rgba(244, 221, 151, 0.1);
  }
}
.how-mockup-frame {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.mockup-row {
  display: flex;
  flex-direction: column;
  max-width: 85%;
}
.mockup-row.out {
  align-self: flex-end;
  align-items: flex-end;
}
.mockup-row.in {
  align-self: flex-start;
  align-items: flex-start;
}
.mockup-sender {
  font-size: 11px;
  color: var(--text-light-tertiary);
  margin-bottom: 3px;
  /* Aligns under the bubble's first letter (matching the 13px left
       padding inside .mockup-bubble) so the label tracks with the
       content it precedes, the way iMessage stacks them. */
  margin-left: 13px;
  letter-spacing: 0.1px;
}
@media all {
  .mockup-sender {
    color: var(--text-dark-tertiary);
  }
}
.mockup-bubble {
  padding: 8px 13px;
  /* Identical radius to the preview-frame .bubble so both example
       surfaces on the page render the same iOS 17 corner curvature. */
  border-radius: 18.5px;
  font-size: 13px;
  line-height: 1.38;
  max-width: 100%;
  word-wrap: break-word;
  /* San Francisco family on Apple devices for visual continuity
       with the real iMessage app the page is reproducing. */
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
}
.mockup-bubble.out {
  /* iMessage-blue (#007aff) failed WCAG AA against white text at
       13px (contrast 4.01 < 4.5). #0066D6 is the same hue family
       shifted ~10% darker to clear AA while still reading as the
       canonical iOS outgoing color. */
  background: #0066d6;
  color: #fff;
  border-bottom-right-radius: 4px;
}
.mockup-bubble.in {
  /* Same incoming-bubble color as the preview-frame .bubble so the
       two example surfaces feel like one design. See --bubble-bg-light
       / --bubble-bg-dark for the canonical values. */
  background: var(--bubble-bg-light);
  color: var(--text-light);
  border-bottom-left-radius: 4px;
}
@media all {
  .mockup-bubble.in {
    background: var(--bubble-bg-dark);
    color: var(--text-dark);
  }
}
code {
  font:
    13px / 1.4 "SF Mono",
    Consolas,
    monospace;
  background: #efe9d8;
  padding: 2px 8px;
  /* 6px radius unifies with the .faq summary hover pill (also 6px)
       and bridges between hairline-tight 4px corners and the bubble's
       18px. Reads as "small chip" rather than "sharp tag". */
  border-radius: 6px;
  color: var(--gold-dark);
}
@media all {
  code {
    background: #1f2a3f;
    color: var(--gold-light);
  }
}
/* Shared gradient-underline treatment for all text links. The
     underline uses background-image (not text-decoration) so it can
     animate: rest is 1px tall, hover doubles to 2px. The gradient
     itself is the same gold the wordmark + CTA use, so every link
     rhymes with the brand surface. */
/* In-prose links (FAQ answers, maker email) use the animated
     gold-gradient underline. .vcard-link is split into its own
     outline-pill button below since it's a standalone secondary CTA,
     not in-prose text. */
.faq a,
.maker a {
  color: var(--gold-dark);
  text-decoration: none;
  /* Underline gradient matches the wordmark + primary CTA so every
       gold surface on the page shares one tonal language. */
  background-image: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  background-size: 100% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  padding-bottom: 2px;
  transition: background-size 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.faq a:hover,
.faq a:focus-visible,
.maker a:hover,
.maker a:focus-visible {
  background-size: 100% 2px;
}
@media all {
  .faq a,
  .maker a {
    color: var(--gold-light);
  }
}
/* .vcard-link styling consolidated into .btn-secondary (above).
     The HTML element has both classes; .btn-secondary owns the
     button shape, .vcard-link is just a hook left for future
     contact-card-specific tweaks. */

/* Demoted save-contact link. Was a tier-2 .btn-secondary button paired
     with the primary CTA, but for first-time visitors who don't yet know
     what runbot is, "save contact" is a confusing ask. Lowered to a small
     inline link under the cta-hint so the primary CTA owns the hero
     attention budget alone. 2026-05-23 v3 polish. */
.vcard-link-mini {
  /* inline-flex (instead of inline-block) so the centering inside
       the expanded touch slop works correctly on Safari. The visible
       link stays its old 12px-font small dotted-underline self; we
       inflate ONLY the hit area via padding + negative margin so the
       surrounding layout (margin-top 10px) remains visually identical
       at rest. iOS rejects taps under 44pt with extra delay; this
       pattern lifts the tap area without changing the read. 2026-05-25
       a11y polish. */
  display: inline-flex;
  align-items: center;
  /* Effective margin-top still 10px from the prior position; the -14px
       compensates for the new padding-top that builds the tap slop. */
  margin: -4px -8px 0;
  padding: 14px 8px;
  font-size: 12px;
  color: var(--text-light-tertiary);
  text-decoration: none;
  letter-spacing: 0.3px;
  /* Underline now applied via :after so the dotted line wraps the
       VISIBLE text only, not the inflated tap area. Without this the
       border-bottom would draw a line under the bottom edge of the
       padded slop, breaking the "small subtle link" affordance. */
  position: relative;
  transition: color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.vcard-link-mini::after {
  content: "";
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 13px;
  height: 0;
  border-bottom: 1px dotted currentColor;
  pointer-events: none;
}
.vcard-link-mini:hover,
.vcard-link-mini:focus-visible {
  color: var(--gold-dark);
  outline: none;
}
@media all {
  .vcard-link-mini {
    color: var(--text-dark-tertiary);
  }
  .vcard-link-mini:hover,
  .vcard-link-mini:focus-visible {
    color: var(--gold-light);
  }
}

/* 3-step flow strip ("DM bot -> paste Strava -> add to group"). Sits
     between the CTA and the iMessage bubble preview so the visitor sees
     the setup loop compressed into one glanceable row BEFORE the proof
     asset, then the framed bubble preview, then the longer how-it-works
     section reinforces. Gold numbered chips reuse the wordmark gradient
     for visual continuity. Arrows are inline middle siblings so the row
     collapses gracefully if it has to wrap at very narrow widths. */
/* C1: removed .hero-flow CSS block. The 3-step setup (DM bot →
     paste Strava → add to group) was visual repetition of what the
     hero preview frame already shows; cutting it lifted the preview
     frame above the fold on mobile. */

/* iMessage bubble mockup -- shows a sample bot announcement so visitors
     get the mental model in one glance. Styled to mimic iMessage's
     incoming-bubble look (gray, fully-rounded corners). The tail
     teardrop was dropped 2026-05-22 alongside the sender avatar so
     all bubbles in the run share identical corner radii. */
.preview {
  margin-top: 44px;
  text-align: left;
}
.preview h2 {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--text-light-tertiary);
  margin: 0 0 12px;
  font-weight: 600;
  text-align: center;
}
@media all {
  .preview h2 {
    color: var(--text-dark-tertiary);
  }
}
/* Chat-window frame around the bubble cluster. Background is kept
     transparent (border + shadow only) so the frame reads as "this is
     a chat window" without introducing a new color surface. */
.preview-frame {
  /* Uniform hairline on all 4 sides + flat page-color fill. The gold
       gradient on the top border was removed 2026-05-22 — the frame now
       reads as "embedded chat" rather than "framed example screenshot",
       closer to real iMessage which has no frame at all. The hairline
       is barely perceptible (rgba 0.16 light, 0.08 dark) so the frame
       still subtly contains the chat without competing for attention. */
  border: 1px solid rgba(128, 128, 128, 0.16);
  border-radius: 24px;
  padding: 14px 16px 20px;
  box-shadow: 0 8px 28px rgba(10, 22, 40, 0.06);
  background: var(--cream);
  /* Capture horizontal swipes for group nav while letting the page
       scroll vertically. user-select:none stops the long-press text
       selection menu from intercepting the gesture on iOS. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  /* Smooth height resize when navigating between groups (Safari 18+ /
       Chrome 129+ via interpolate-size, otherwise no-op). Without this,
       the frame snaps instantly to each new group's natural height
       while the inner bubbles fade -- creates a jarring jump. The
       cubic-bezier matches the fade-in's easing curve so the box and
       its contents settle in lock-step. */
  interpolate-size: allow-keywords;
  /* Easing tuned for breath not snap: cubic-bezier(0.16, 1, 0.3, 1)
       is Apple style expo-out, quick start gently decelerating over a
       long tail. 520ms matches the bubble fade-in (450ms) plus 70ms so
       the box settles a hair AFTER the new content fully resolves. */
  transition:
    transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    height 0.52s cubic-bezier(0.16, 1, 0.3, 1),
    min-height 0.52s cubic-bezier(0.16, 1, 0.3, 1);
}
.preview-frame.is-swiping {
  transition: none;
}
@media all {
  .preview-frame {
    border-color: rgba(255, 255, 255, 0.08);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
    background: var(--navy);
  }
}
/* Header bar at the top of the preview frame, mimicking iMessage's
     group-chat title strip. Now ALSO carries the group navigation
     (prev/next chevrons + dot row) so the carousel controls feel
     native to the chat header instead of being a separate UI control
     bolted on below the frame.
       [chevron]   Run Group   [chevron]
                   5 members
                   • • ⦿ • •
     The hairline divider below separates this header cluster from
     the bubble messages. */
.preview-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  margin-bottom: 14px;
}
.preview-header-text {
  flex: 1;
  min-width: 0;
  text-align: center;
}
.preview-header-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-dark);
}
.preview-header-meta {
  font-size: 11px;
  color: var(--text-dark-tertiary);
  margin-top: 2px;
}
/* Group dot row sits BELOW the meta-line, inside the header text
     column. Tappable buttons, gold for the active position. The
     visual dot is small (5px) but each button carries a 24x24px
     transparent touch target so axe / WCAG 2.5.5 target-size pass.
     gap:2px keeps the row visually tight while leaving enough
     between-target space that adjacent hitboxes don't overlap. */
/* Prev/next chevrons flank the header text. Quiet by default —
     gold glyph in a soft circle that only gains contrast on hover.
     Hidden when there's only one group via [hidden] on the JS side.
     32x32 hits the iOS HIG 44pt comfort target on a 1.375x scale (44
     reads as too aggressive next to the 28px avatar — 32 visually
     pairs with the avatar instead of overwhelming it). */
/* Polished prev/next arrow: chevron glyph, no chrome. Dim by default
     (opacity 0.45) so it doesn't compete with the bubble preview; lifts
     to full gold on hover/focus. The chevron grows to 24px font-size so
     it reads as a clear navigation hint at a glance. Replaced the
     bordered-circle treatment + dot-pager 2026-05-23: the dots were
     redundant with the arrows and the bordered circle felt chrome-heavy. */
.group-nav-btn {
  appearance: none;
  background: transparent;
  border: 0;
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 28px;
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  color: var(--gold-light);
  opacity: 0.45;
  position: relative;
  transition:
    opacity 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1),
    color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.group-nav-btn[hidden] {
  display: none;
}
.group-nav-btn:hover,
.group-nav-btn:focus-visible {
  opacity: 1;
  color: var(--gold-mid);
  outline: none;
}
.group-nav-btn:focus-visible {
  outline: 2px solid rgba(244, 221, 151, 0.5);
  outline-offset: 2px;
  border-radius: 50%;
}
.group-nav-btn:active {
  transform: scale(0.9);
}
/* "Live updates" chip below the preview frame. Sits outside the
     iMessage chrome so it doesn't break the mockup's authenticity --
     real iMessage chat windows don't have status indicators in the
     title bar. The pulsing dot signals the page is live-fetching;
     paired with concise copy so it doesn't look like a system error. */
.live-chip {
  /* margin: 6px auto 0 pulls the chip close under the group-nav so
       they read as a paired "current state + nav" cluster instead of
       two separate annotations stacked below the preview. */
  margin: 6px auto 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-light-tertiary);
  letter-spacing: 0.2px;
}
@media all {
  .live-chip {
    color: var(--text-dark-tertiary);
  }
}
.live-chip-dot {
  /* Green-glowing live indicator (Tailwind emerald-400). User
       explicitly prefers green-pulse over the brief cyan swap; the
       glow IS the signal here, not the color hierarchy. */
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-soft);
  animation: live-pulse 2.4s ease-out infinite;
}
@keyframes live-pulse {
  0% {
    box-shadow: 0 0 0 0 var(--accent-soft);
  }
  70% {
    box-shadow: 0 0 0 6px rgba(74, 222, 128, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(74, 222, 128, 0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .live-chip-dot {
    animation: none;
  }
}
.live-chip-wrapper {
  text-align: center;
}
/* Week-stats trust counter. Sits below the live-chip in the preview
     column. Aggregate-only stats across all crews ("X mi tracked this
     week · Y runners · Z runs") so a first-time visitor gets a
     real-numbers signal that runbot is in use, not a static mock.
     `tabular-nums` keeps digit widths stable so the numbers don't
     jitter on each live-data refresh. */
.week-stats {
  margin: 8px auto 0;
  max-width: 440px;
  font-size: 12px;
  line-height: 1.5;
  text-align: center;
  color: var(--text-light-tertiary);
  letter-spacing: 0.1px;
  font-variant-numeric: tabular-nums;
}
.week-stats strong {
  font-weight: 600;
  color: var(--text-light-secondary);
}
@media all {
  .week-stats {
    color: var(--text-dark-tertiary);
  }
  .week-stats strong {
    color: var(--text-dark-secondary);
  }
}
/* Impact chip: cohort-level pre/post-runbot signal. Sits directly
     below the week-stats line so the eye reads the LIVE WEEK SO FAR
     numbers first, then the LONGER-TERM IMPACT below. Slightly larger
     font + the brand gold for the percentage so the headline gets
     visual weight without being a separate visual element. Hidden
     entirely when the cohort is empty (server returns "" for the
     placeholder). */
.impact-chip {
  /* Sits under .week-stats (preview column) as the long-term-impact
       beat below the LIVE WEEK SO FAR numbers — reading order is:
       preview-frame -> live-chip -> week-stats -> impact-chip, i.e.
       "here's what it looks like -> here's it happening now -> here's
       the long-term effect on one member." Stat-stack 2026-05-23 v2.
       Font/color: 14px + secondary so the proof-point reads heavier
       than the .week-stats trust-counter above it without competing
       with the preview frame. */
  margin: 14px auto 0;
  max-width: 440px;
  font-size: 14px;
  line-height: 1.55;
  text-align: center;
  color: var(--text-light-secondary);
  letter-spacing: 0.1px;
  font-variant-numeric: tabular-nums;
}
.impact-chip strong {
  font-weight: 700;
  /* Gold gradient on the percentage so the headline number reads
       as a brand-aligned signal, matching the wordmark + CTA gradient
       palette. Webkit text-clip with -webkit-text-fill-color is the
       cross-browser pattern for gradient text. */
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
@media all {
  .impact-chip {
    color: var(--text-dark-secondary);
  }
}
/* Tracker integration row: low-contrast strip below info-grid.
     Center-aligned, small, dimmer than body text — communicates
     "your existing tracker works" without competing with .how / .faq for
     attention. 2026-05-23 polish (Stridekick pattern). */
.tracker-row {
  /* Two-line stack: caption (small + dim) above names (slightly heavier
       + more visible). Reads as a small integrated annotation, not a
       bare checklist. 2026-05-23 polish.
       Internal gap bumped 4 -> 10px so the caption + names read as
       distinct beats rather than one cramped line. Names also get a
       slightly larger font and wider letter-spacing for breathing room
       around the · separators. */
  margin: 32px auto 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: var(--text-light-tertiary);
  letter-spacing: 0.3px;
  line-height: 1.5;
}
.tracker-caption {
  font-size: 12px;
  opacity: 0.75;
  letter-spacing: 0.4px;
}
.tracker-names {
  font-size: 14px;
  letter-spacing: 0.5px;
  word-spacing: 3px;
}
.tracker-row strong {
  color: var(--text-light-secondary);
  font-weight: 600;
}
@media all {
  .tracker-row {
    color: var(--text-dark-tertiary);
  }
  .tracker-row strong {
    color: var(--text-dark-secondary);
  }
}
/* Sticky-bottom mobile CTA bar. Restored 2026-05-23 from the earlier
     polished design (commit 5eea1e0); the always-visible variant that
     replaced it temporarily competed visually with the hero CTA.
     Behavior:
       - Ships with [hidden]; landing.js unhides + binds a passive
         scroll handler that adds .visible after scroll past ~320px.
       - Frosted-glass dark backdrop (matches dark-only policy).
       - translateY(110%) keeps it out-of-frame until .visible flips
         it to translateY(0) with a 220ms ease.
       - env(safe-area-inset-bottom) lifts above the iPhone home
         indicator.
       - Hidden on tablet+ (>=720px) where the hero CTA stays in view.
       - prefers-reduced-motion gets instant show/hide. */
.sticky-cta {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: rgba(10, 22, 40, 0.92);
  -webkit-backdrop-filter: saturate(170%) blur(14px);
  backdrop-filter: saturate(170%) blur(14px);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  transform: translateY(110%);
  transition:
    transform 220ms ease,
    opacity 220ms ease;
  z-index: 100;
  opacity: 0;
  pointer-events: none;
}
.sticky-cta.visible {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
  will-change: transform;
}
.sticky-cta .btn {
  width: 100%;
  box-sizing: border-box;
  text-align: center;
  /* Slightly tighter padding than hero .btn so the bar height stays
       around 56px total (10+10 vertical + safe-area). min-height 44px
       guards against thumb-miss when the bottom-anchored bar lands
       near the home-indicator zone on notch devices. 2026-05-25
       a11y polish; matches WCAG 2.5.5 + Apple HIG 44pt minimum. */
  min-height: 44px;
  padding: 12px 18px;
  font-size: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
@media (min-width: 720px) {
  .sticky-cta {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .sticky-cta {
    transition: none;
  }
}
/* iMessage cluster: timestamp header + sender label + bubble run.
     Layout mirrors a real group-chat thread: the sender's avatar only
     shows on the LAST bubble of a consecutive run (bottom-anchored),
     while earlier bubbles in the run leave the 28px slot empty via
     .bubble-avatar-spacer so the bubble's left edge stays aligned. */
.preview-time {
  font-size: 11px;
  color: var(--text-light-tertiary);
  text-align: center;
  margin: 0 0 14px;
  letter-spacing: 0.1px;
}
.preview-time strong {
  font-weight: 600;
  color: var(--text-light-secondary);
}
@media all {
  .preview-time {
    color: var(--text-dark-tertiary);
  }
  .preview-time strong {
    color: var(--text-dark-secondary);
  }
}
.preview-sender {
  font-size: 11px;
  color: var(--text-light-tertiary);
  padding-left: 42px; /* avatar(28) + gap(6) + 8px nudge to match iOS */
  margin-bottom: 3px;
  letter-spacing: 0.1px;
}
@media all {
  .preview-sender {
    color: var(--text-dark-tertiary);
  }
}
.bubble-row {
  display: flex;
  /* 6px gap (was 8px) tightens the avatar-to-bubble spacing to
       match iMessage proportions -- in real iMessage the avatar
       sits close enough that the tail looks like it's reaching for
       the avatar's right edge rather than floating in dead space. */
  gap: 6px;
  align-items: flex-end;
  margin-bottom: 3px;
  /* Each bubble fades + slides up over 0.7s on a shared easing curve.
       Delays evenly spaced at 1.0s gaps (0.4 / 1.4 / 2.4s) so the
       three bubbles feel like a uniform "messages arriving" rhythm.
       Previously 0.4 / 1.5 / 2.7 -- close to even but the 1.1s vs
       1.2s gaps read as subtly off when watched in sequence. Last
       bubble (leaderboard payoff) lands at 2.4s, comfortably under
       the ~3s scroll-past threshold. 2026-05-23 uniformity fix. */
  animation: slideUp 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.bubble-row.last {
  margin-bottom: 0;
}
.bubble-row.delay-1 {
  animation-delay: 0.4s;
}
.bubble-row.delay-2 {
  animation-delay: 1.4s;
}
.bubble-row.delay-3 {
  animation-delay: 2.4s;
}
/* Reserves the 28px gutter at the left of every bubble-row so the
     bubble's left edge aligns under the sender label above. The
     "rb" monogram avatar that used to live here was removed
     2026-05-22 by user direction; the spacer slot stays so the
     row geometry doesn't shift. */
.bubble-avatar-spacer {
  width: 28px;
  flex-shrink: 0;
}
.bubble {
  max-width: 340px;
  /* iOS 17 bubble proportions: 9px vertical / 14px horizontal +
       18.5px radius reads as "iMessage" the moment your eye lands on
       it. Sub-pixel padding lets the bubble hug the text tighter
       without the line wrap drifting. */
  padding: 9px 14px;
  background: var(--bubble-bg-light);
  color: var(--text-light);
  border-radius: 18.5px;
  font-size: 16px;
  /* Slightly looser line-height (was 1.35) — iMessage runs at ~1.27
       on SF Pro Text, but the smaller bot-message font here benefits
       from a bit more breathing room without inflating the bubble. */
  line-height: 1.34;
  /* Wrap defensively. The bubble can render unmoderated user content
       (run titles, comments, names from Strava). overflow-wrap:anywhere
       lets the browser break at ANY character when a single token
       would otherwise overflow the bubble; word-break:break-word is the
       weaker fallback for browsers that don't support anywhere. hyphens
       auto adds soft hyphens at dictionary points so the breaks read
       naturally on multi-syllable tokens. Without these, a long
       hashtag or URL in a Strava title can blow out the bubble width
       on 320px screens (iPhone SE) and force horizontal scroll on the
       whole page. 2026-05-25 a11y polish. */
  overflow-wrap: anywhere;
  word-break: break-word;
  hyphens: auto;
  /* San Francisco's text font on Apple devices; falls back to the
       generic system stack for cross-platform visitors. Keeps the
       letter shapes consistent with the real iMessage app instead of
       inheriting body-default ui-sans. */
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
}
@media all {
  .bubble {
    background: var(--bubble-bg-dark);
    color: var(--text-dark);
  }
}
/* Tail/tick on the LAST bubble of a sender's run. Two-pseudo trick:
     ::before extends the bubble color leftward at the bottom, ::after
     cuts a curved notch using the page background, the remainder reads
     as a teardrop tail. Dark mode swaps the notch color to navy. */
.bubble.tail {
  position: relative;
  border-bottom-left-radius: 4px;
}
.bubble.tail::before {
  content: "";
  position: absolute;
  bottom: 0;
  left: -7px;
  width: 16px;
  height: 20px;
  background: var(--bubble-bg-light);
  border-bottom-right-radius: 16px 14px;
}
.bubble.tail::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: -12px;
  width: 12px;
  height: 20px;
  background: var(--cream);
  border-bottom-right-radius: 10px;
}
@media all {
  .bubble.tail::before {
    background: var(--bubble-bg-dark);
  }
  .bubble.tail::after {
    background: var(--navy);
  }
}
/* Leaderboard variant: preserves newlines from the HTML source so the
     bubble renders one entry per line without <br> tags trailing a
     phantom leading space on every continuation line. white-space:
     pre-line collapses spaces (no source-indent leak) but preserves the
     real \n characters between entries. */
.bubble {
  /* white-space: pre-line preserves real \n line breaks from the
       source text (the bot's actual outbound announcements can include
       multi-line achievement callouts like
         "X just ran 4 mi.\n\nNew record week, 10.1 mi!"
       ) while still collapsing runs of spaces so source indentation
       in the HTML template doesn't leak as leading whitespace. */
  white-space: pre-line;
}
.bubble.leaderboard {
  /* tabular-nums forces every digit to occupy the same width. SF Pro
       Text defaults to proportional figures, so "127" is visibly
       narrower than "152" and the leaderboard columns jitter vertically.
       The leaderboard bubble is the most-shared screenshot on this
       page; rock-solid columns make it look engineered, not drifted. */
  font-variant-numeric: tabular-nums;
}
/* Leaderboard truncation (2026-05-23 v3): bubble shows the header +
     top-5 entries by default; positions 6+ + the crew-total tail live
     inside .lb-hidden with hidden=""; a "see more" button reveals them.
     Both segments inherit .bubble's white-space: pre-line so the
     server-side \n line breaks render as actual line wraps.
     The explicit [hidden] overrides below are REQUIRED — without them
     the display:block rules below win over UA's [hidden]{display:none}
     and the truncation breaks (everything stays visible). */
.lb-visible,
.lb-hidden {
  display: block;
}
.lb-hidden[hidden] {
  display: none;
}
.lb-expand {
  /* "see more" toggle under the leaderboard. The visible text sits in
     its compact pre-Phase-A position (~12px gap below the last
     leaderboard line, no visible padding inside the chat bubble), while
     the tap area expands to ~44px via padding-block + negative margins
     that pull the bounding box back into the bubble's natural layout.
     Same trick as .vcard-link-mini. Math:
       - padding-top 14 + margin-top -2 = text starts 12px below
         preceding sibling (matches pre-Phase-A margin 8 + padding 4)
       - padding-bottom 14 + margin-bottom -14 = box visually extends
         14px below text but the next layout (bubble padding) resumes
         at the text baseline so the chat bubble doesn't grow taller
       - padding-inline 8 + margin-inline -8 = side hit slop, no visual
         shift relative to leaderboard text alignment
     Earlier min-height:44px + align-items:center version centered the
     17px-tall text inside a 44px box, which read as ~13px of empty
     whitespace above AND below the glyph INSIDE the chat bubble — the
     user flagged it as "spaces for no reason". 2026-05-26 visual fix
     on the Phase A tap-target bump. */
  display: inline-block;
  margin: -2px -8px -14px -8px;
  padding: 14px 8px;
  background: transparent;
  border: 0;
  color: var(--gold-dark);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: 0.2px;
  cursor: pointer;
  text-align: left;
  transition: color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.lb-expand[hidden] {
  display: none;
}
.lb-expand:hover,
.lb-expand:focus-visible {
  color: var(--gold-mid);
  text-decoration: underline;
  outline: none;
}
@media all {
  .lb-expand {
    color: var(--gold-light);
  }
  .lb-expand:hover,
  .lb-expand:focus-visible {
    color: var(--gold-mid);
  }
}
/* Global .count-up styling. The IntersectionObserver-driven animation
     in landing.js targets any .count-up on the page (previously was
     scoped to the impact-chip). tabular-nums on the counter element
     itself keeps digit widths constant during the animation so the
     surrounding text doesn't reflow as the value ticks up. */
.count-up {
  font-variant-numeric: tabular-nums;
}
@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* Live-update transitions for group swaps. The preview-frame
     carries a --swap-dir CSS variable (set by landing.js to +1 for
     "next" or -1 for "prev"). Bubbles slide horizontally in the
     direction of travel: outgoing bubbles drift away in the swipe
     direction, incoming bubbles slide in from the opposite side.
     For pure poll-driven refreshes (no direction), --swap-dir
     defaults to 1 (slight rightward "freshness drift") which reads
     as a gentle pulse rather than an aggressive slide.
       will-change hint lifts each bubble to its own GPU layer so
     iOS Safari paints the fade smoothly during a swipe gesture
     instead of coalescing the class change with the text swap. */
.preview-frame {
  --swap-dir: 1;
}
.bubble,
.preview-header-meta {
  will-change: opacity, transform;
}
/* 2026-05-25 smoothness pass: fade durations 0.25s -> 0.35s out and
   0.5s -> 0.6s in. Matches the JS-side FADE_OUT_MS=320 / FADE_IN_MS=600
   bumps. Slower fades read as graceful where 0.25s read as snappy on
   a rotating group carousel. Slightly longer translate (24px instead
   of 18-20) so the slide direction reads more clearly. */
.bubble.is-changing,
.preview-header-meta.is-changing {
  opacity: 0;
  transform: translateX(calc(var(--swap-dir, 1) * -24px));
  transition:
    opacity 0.35s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.bubble.is-fresh,
.preview-header-meta.is-fresh {
  animation: bubble-fresh 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
/* Incoming content slides in from the SIDE OPPOSITE the
     outgoing's exit. Combined with the outgoing rule above, this
     produces a clean carousel-slide reading even on slower
     devices where the JS swap mid-transition would otherwise look
     "jumpy". */
@keyframes bubble-fresh {
  from {
    opacity: 0;
    transform: translateX(calc(var(--swap-dir, 1) * 24px));
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
/* How-it-works mockup scroll-reveal animation.
   .io-ready is added by landing.js BEFORE the IntersectionObserver
   observes the frame — so bubbles are invisible only when JS is
   running and IO is available. Without JS (or without IO support)
   this rule never applies and the bubbles stay fully visible.
   .in-view is added when the frame scrolls into the viewport;
   each .mockup-row then plays the shared slideUp keyframe with
   evenly-spaced 0.3s stagger for a "messages arriving" rhythm. */
.how-mockup-frame.io-ready .mockup-row {
  opacity: 0;
}
.how-mockup-frame.in-view .mockup-row:nth-child(1) {
  animation: slideUp 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0s both;
}
.how-mockup-frame.in-view .mockup-row:nth-child(2) {
  animation: slideUp 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.3s both;
}
.how-mockup-frame.in-view .mockup-row:nth-child(3) {
  animation: slideUp 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.6s both;
}
.how-mockup-frame.in-view .mockup-row:nth-child(4) {
  animation: slideUp 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.9s both;
}
@media (prefers-reduced-motion: reduce) {
  .bubble-row {
    animation: none;
  }
  .bubble.is-changing,
  .preview-header-meta.is-changing,
  .bubble.is-fresh,
  .preview-header-meta.is-fresh {
    animation: none;
    transition: none;
    opacity: 1;
    transform: none;
  }
  /* Reduced-motion: drop the interpolate-size height transition too
       so the frame snaps instantly instead of growing/shrinking. The
       bubble-content swap is already instant via the rules above; the
       frame should match. */
  .preview-frame {
    transition: none;
  }
  /* Reduced-motion: how-it-works mockup rows must be instantly visible;
       cancel the hidden state and any animation added by .in-view. */
  .how-mockup-frame.io-ready .mockup-row,
  .how-mockup-frame.in-view .mockup-row {
    opacity: 1;
    animation: none;
  }
}

/* Mobile-narrow tuning (<=480px). Most visitors land here on iPhone,
     so the page must read well at 375px (iPhone 16) and still survive
     320px (iPhone SE 1st gen). Adjustments: tighter outer padding,
     smaller hero glyphs, snug section gaps, full-width bubbles. */
@media (max-width: 480px) {
  body {
    padding: calc(32px + env(safe-area-inset-top)) 16px calc(32px + env(safe-area-inset-bottom));
  }
  /* Mobile lockup tuning: symbol shrunk 96 → 88 + wordmark grown
       42 → 48 brings the visible-height ratio from ~2.3× (symbol-
       dominant) down to ~1.7× — closer to the conventional
       "symbol slightly bigger than wordmark" hero proportion. The
       wordmark IS the brand asset; the symbol supports. */
  .logo {
    width: 264px;
    height: 88px;
    margin: 0 auto 8px;
  }
  h1 {
    font-size: 48px;
    letter-spacing: -1.6px;
    margin: 0 0 8px;
  }
  .tagline {
    font-size: 17px;
  }
  .subhead {
    margin: 0 0 22px;
  }
  a.btn {
    padding: 14px 24px;
    font-size: 16px;
  }
  .secondary {
    margin-top: 0;
  }
  .preview {
    margin-top: 28px;
  }
  .preview h2 {
    margin: 0 0 10px;
  }
  .preview-frame {
    padding: 12px 14px 18px;
    border-radius: 20px;
  }
  .preview-header {
    padding: 2px 0 10px;
    margin-bottom: 12px;
  }
  .preview-header-title {
    font-size: 13px;
  }
  .preview-time {
    margin: 0 0 10px;
  }
  .preview-sender {
    padding-left: 40px;
  }
  .bubble {
    max-width: 100%;
    font-size: 15px;
    line-height: 1.35;
  }
  .how,
  .faq {
    margin-top: 28px;
    font-size: 14px;
  }
  /* Center the section headings on mobile so they read as
       deliberate section labels instead of fragments of the
       left-aligned prose below them. Prose itself (ol items,
       FAQ details) stays left-aligned for legibility -- long
       centered text lines are demonstrably harder to scan
       because the eye loses its return-point at the line start. */
  .how h2,
  .faq h2 {
    font-size: 17px;
    text-align: center;
  }
  /* FAQ heading shrinks below .how at the mobile breakpoint so the
       eye lands on "how it works" first; FAQ reads as a quieter
       supporting section. 2026-05-23 subtler-FAQ pass. */
  .faq h2 {
    font-size: 14px;
  }
  .faq summary {
    text-align: left;
  } /* summaries still left-aligned with answers */
  .maker {
    margin-top: 24px;
  }

  /* ───── 2026-05-25 c: mobile vertical-density polish ─────
     Hero CTA was getting pushed below the fold on iPhone-SE-class
     widths after the v6 additions (impact kicker + hi-chip + tracker
     chips + sticky ticker). These rules shave ~80-100px of vertical
     space across the hero so the wordmark + CTA + start of the
     preview frame all sit in the first viewport. */

  /* The cta-proof line ("join 24 runners across 5 groups") is
     redundant with the lifetime-panel band below, which renders the
     same numbers in a louder treatment. Hide it on mobile so the
     impact-kicker carries the social-proof beat alone. */
  .cta-proof {
    display: none;
  }

  /* Tighter hero spacing: subhead margin 22px → 16px, impact-kicker
     margin 14px → 10px, hi-chip margin-top 12px → 8px, hi-chip
     margin-bottom 0 stays. */
  .subhead {
    margin: 0 0 16px;
  }
  .impact-kicker {
    margin-bottom: 10px;
    font-size: 13px;
    padding: 6px 12px;
  }
  .hi-chip {
    margin-top: 8px;
  }
  /* save-contact link tighter under the CTA + hi-chip pair. */
  .vcard-link-mini {
    margin-top: 8px;
  }

  /* Tracker row: caption + chips were on separate lines, eating
     ~60px tall. Inline them so the row reads as one compact line
     "works with: Strava · Garmin · Apple Health". The chips keep
     their brand-icon affordances. */
  .tracker-row {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px 10px;
    margin-top: 22px;
  }
  .tracker-caption {
    font-size: 11px;
  }

  /* Preview frame margin tighter on mobile. */
  .preview {
    margin-top: 22px;
  }

  /* Lifetime panel + side-by-side + how sections: trim the gap
     between sections so the page reads tighter overall. */
  .lifetime-panel {
    margin-top: 28px;
  }
  .side-by-side {
    margin-top: 32px;
  }
  .side-by-side-heading {
    margin-bottom: 14px;
  }
  /* Tighter side-by-side card padding on mobile so the stacked
     card+arrow+card column doesn't tower up to ~400px. */
  .sbs-card {
    padding: 12px 14px 14px;
  }
  .sbs-strava-stats {
    padding: 10px 0;
  }
  .sbs-strava-map {
    height: 48px;
  }
  .sbs-arrow-line {
    height: 16px;
  }
}

/* Desktop layout (>=900px). Mobile-first base treats the page as one
     vertical column; this query flips the hero into a 2-column grid
     (text on the left, iMessage bubble preview on the right) so desktop
     visitors see "what your group sees" alongside the pitch instead of
     scrolling past it. The same split applies to the info section
     (how-it-works | FAQ side-by-side). Typography scales up because at
     1080px max-width with 1440px viewports the mobile 56px wordmark
     looks lost; 84px reads as a deliberate hero glyph. */
@media (min-width: 760px) {
  body {
    max-width: 1080px;
    padding: 72px 40px 56px;
  }
  /* Asymmetric hero: left column holds the brand surface (logo +
       wordmark + primary CTA + cta-hint + secondary save-to-contacts
       block) as a single cohesive lockup; right column holds the
       proof-of-product (preview frame + week-stats). The 2026-05-22
       consolidation merged the former .hero-extras (save-to-contacts)
       into .hero-text so both action surfaces share the same column. */
  .hero {
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* Source order is text-first / preview-second so mobile reads
         brand -> tagline -> CTA -> proof (conventional landing flow,
         and gives screen-reader / keyboard users a coherent narrative).
         Desktop overrides visual position via grid-template-areas:
         preview lands on the LEFT (eye-pull, show-while-telling) and
         the CTA stack on the RIGHT. 2026-05-23 v3 mobile-coherence
         revert from the earlier preview-first source order. */
    grid-template-areas: "preview text";
    column-gap: 56px;
    row-gap: 32px;
    align-items: center;
    margin-bottom: 16px;
  }
  .hero-text {
    grid-area: text;
    text-align: left;
    /* Top-align with the left column so the wordmark + the tracker
       chips both sit at the top of their respective columns. Without
       this override, .hero's align-items: center would vertically
       center the shorter hero-text against the taller hero-preview
       (preview + lifetime panel), pushing the wordmark down to the
       middle of the page. Top alignment matches the conventional
       hero rhythm: brand at the top, eye flows down. v5 polish
       2026-05-23. */
    align-self: start;
    padding-top: 20px;
  }
  .hero-text .logo {
    margin: 0 0 12px;
  }
  /* cta-hint left-aligns with the rest of hero-text on desktop (mobile
       keeps it auto-centered since hero-text is text-align: center on
       small screens). Without this override it'd center within the left
       column, breaking the designed-lockup feel. */
  .hero-text .cta-hint {
    margin-left: 0;
    margin-right: 0;
    text-align: left;
  }
  /* hero-text drops the body-default text-align: center on desktop
       so the wordmark + CTA left-align under the logo. Reads as a
       designed lockup rather than a centered marquee. */
  .hero-preview {
    grid-area: preview;
    display: flex;
    flex-direction: column;
    align-items: center;
    align-self: center;
  }
  .logo {
    width: 420px;
    height: 140px;
  }
  h1 {
    font-size: 80px;
    letter-spacing: -2.5px;
    margin: 0 0 14px;
  }
  .tagline {
    font-size: 21px;
    margin: 0 0 8px;
    line-height: 1.35;
  }
  .subhead {
    font-size: 15px;
    margin: 0 0 32px;
  }
  a.btn {
    font-size: 20px;
    padding: 18px 40px;
    border-radius: 16px;
  }
  /* Desktop hero-text is text-align: left, so the paired CTA row
       also left-aligns under the wordmark for a designed lockup
       feel. Mobile keeps the row centered (its default rule). */
  .cta-row {
    justify-content: flex-start;
  }
  .secondary {
    font-size: 15px;
    margin-top: 0;
    line-height: 1.7;
  }
  /* Reset the mobile-default top margin since the preview now lives
       inside a side-by-side grid cell, not below the CTA. */
  .preview {
    margin-top: 0;
    max-width: 440px;
    width: 100%;
  }
  .preview h2 {
    font-size: 12px;
  }
  .bubble {
    max-width: 380px;
    font-size: 16px;
    line-height: 1.4;
  }
  /* .how is the standalone info section (FAQ moved to its own /faq
       page 2026-05-23). Centered with comfortable max-width so the
       4-step list + walkthrough mockup don't stretch across the full
       880px content column on wide desktops. */
  .how {
    max-width: 640px;
    margin: 32px auto 0;
  }
  .how h2 {
    font-size: 22px;
    margin: 0 0 14px;
  }
  .how {
    font-size: 16px;
  }
  .maker {
    margin-top: 60px;
    font-size: 14px;
  }
  footer {
    margin-top: 48px;
  }
}

/* "how it works" section layout (FAQ moved to /faq page 2026-05-23). */
.how {
  margin-top: 40px;
  text-align: left;
  font-size: 15px;
  color: var(--text-light-secondary);
}
@media all {
  .how,
  .faq {
    color: var(--text-dark-secondary);
  }
}
.how h2,
.faq h2 {
  /* B1: section headings get the same display typeface as the
       wordmark for typographic consistency. Slightly less weight
       than h1 (still 700 in Space Grotesk Bold, but the relative
       size signals hierarchy). */
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.3px;
  color: var(--text-light);
  margin: 0 0 12px;
}
@media all {
  .how h2,
  .faq h2 {
    color: var(--text-dark);
  }
}
/* FAQ heading recedes from the .how/.faq shared treatment — secondary
     text color, less weight, no letter-spacing tightening. The FAQ is a
     supporting reference section behind the primary onboarding flow in
     .how; this hierarchy is what "more subtle" means. 2026-05-23. */
.faq h2 {
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-light-tertiary);
}
@media all {
  .faq h2 {
    color: var(--text-dark-tertiary);
  }
}
.how ol {
  padding-left: 22px;
  margin: 0;
}
.how li {
  margin: 8px 0;
}
/* Verb emphasis: SF lets <strong> color-shift on top of weight,
     giving each step's leading verb a subtle visual anchor without
     breaking sentence rhythm. The strong sits at weight 600 + the
     primary text color while the surrounding text stays at secondary
     color, producing a quiet "tap / send / paste / add" rhythm down
     the list. */
.how li strong {
  font-weight: 600;
  color: var(--text-light);
}
@media all {
  .how li strong {
    color: var(--text-dark);
  }
}
.faq details {
  /* 10px radius harmonized with the rest of the page's radius scale
       (14/18.5/24). 8px was an orphan value. 2026-05-23 polish. */
  border-top: 1px solid rgba(128, 128, 128, 0.16);
  padding: 14px 0;
  /* Deep-link highlight: when a visitor arrives via #fragment-id
       (e.g. runbot.fit/#android-q), CSS :target paints a quiet warm
       wash so the eye finds the section without us having to flash
       anything. landing.js opens the <details> on load + hashchange
       so the answer is also visible. 1.2s fade-in matches the
       leaderboard's settle timing for a coherent feel. */
  transition: background-color 1.2s cubic-bezier(0.16, 1, 0.3, 1);
  border-radius: 10px;
}
.faq details:target {
  background-color: rgba(176, 125, 49, 0.08);
  padding: 14px 12px;
}
@media all {
  .faq details:target {
    background-color: rgba(244, 221, 151, 0.06);
  }
}
.faq details:last-of-type {
  border-bottom: 1px solid rgba(128, 128, 128, 0.16);
}
.faq summary {
  cursor: pointer;
  /* Secondary text color so summaries don't compete with .how primary
       text. Hover state lifts to primary rather than jumping to gold,
       which kept the FAQ visually loud at rest. 2026-05-23 subtler. */
  color: var(--text-light-secondary);
  font-weight: 400;
  list-style: none;
  position: relative;
  padding-right: 24px;
  /* Subtle warm-cream wash on hover so the summary feels tappable
       before you click. Transparent default keeps the FAQ visually
       quiet at rest. */
  transition:
    background-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
  /* min-height 44px + display:flex centers the visible text inside a
       WCAG-compliant tap target without bloating the visible vertical
       rhythm: padding stays 4/4 so adjacent summaries still feel
       compact, but the row is now thumb-friendly even when the
       question itself wraps to a single line. 2026-05-25 a11y polish. */
  min-height: 44px;
  display: flex;
  align-items: center;
  margin: -4px -8px;
  padding: 4px 32px 4px 8px;
  border-radius: 6px;
}
.faq summary:hover {
  /* Hover lifts secondary → primary text color instead of swapping
       to gold. The interaction now signals "hover state" through
       contrast change alone, no extra accent color competing with .how. */
  background-color: rgba(128, 128, 128, 0.04);
  color: var(--text-light);
}
@media all {
  .faq summary {
    color: var(--text-dark-secondary);
  }
  .faq summary:hover {
    background-color: rgba(255, 255, 255, 0.04);
    color: var(--text-dark);
  }
}
/* Keyboard-focus state for the accordion summary. Gold-dark outline
     with offset to match the .btn :focus-visible treatment elsewhere on
     the page — keyboard nav users get a clear "this is focused" cue.
     Without this, the only focus signal was the browser default (often
     blue) which clashed with the warm-gold brand. 2026-05-23 a11y polish. */
.faq summary:focus-visible {
  outline: 2px solid var(--gold-dark);
  outline-offset: 2px;
  border-radius: 6px;
}
@media all {
  .faq summary:focus-visible {
    outline-color: var(--gold-light);
  }
}
.faq summary::-webkit-details-marker {
  display: none;
}
.faq summary::after {
  content: "+";
  position: absolute;
  right: 8px;
  top: 4px;
  /* Quieter accordion affordance: smaller + dimmer than the previous
       gold-light treatment so it reads as a soft hint rather than a
       warm-gold beacon. The bounce easing on rotate keeps the
       interaction feel intact. */
  font-size: 16px;
  color: var(--text-light-tertiary);
  /* Overshoot easing gives the "+" a tiny bounce as it rotates,
       making the accordion feel responsive instead of mechanical. */
  transition: transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media all {
  .faq summary::after {
    color: var(--text-dark-tertiary);
  }
}
.faq details[open] summary::after {
  transform: rotate(45deg);
}
.faq details p {
  margin: 10px 0 0;
  line-height: 1.55;
}
/* .faq a / .vcard-link / .maker a all share the gradient-underline
     treatment defined earlier in the stylesheet (search for
     "Shared gradient-underline"). */
.maker {
  margin-top: 36px;
  text-align: center;
  font-size: 13px;
  color: var(--text-light-tertiary);
  line-height: 1.6;
}
@media all {
  .maker {
    color: var(--text-dark-tertiary);
  }
}
/* .maker a styling consolidated into the shared gradient-underline
     block earlier in the file. */
footer {
  margin-top: 40px;
  font-size: 13px;
  color: var(--text-light-tertiary);
  text-align: center;
}
@media all {
  footer {
    color: var(--text-dark-tertiary);
  }
}
/* Footer link: inherit the muted tertiary color at rest, but
     warm to gold on hover so it gets a tiny moment of brand
     personality. Smaller treatment than the FAQ/maker gradient
     underlines since the footer's job is "quiet utility." */
footer a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
  transition:
    color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-bottom-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
footer a:hover,
footer a:focus-visible {
  color: var(--gold-dark);
  border-bottom-color: var(--gold-dark);
  outline: none;
}
@media all {
  footer a:hover,
  footer a:focus-visible {
    color: var(--gold-light);
    border-bottom-color: var(--gold-light);
  }
}

/* Noscript fallback hint. Styled here (rather than via inline
     style attribute on the <p>) so a strict Content-Security-Policy
     style-src directive doesn't have to allow inline style="..."
     just for this one element. */
.noscript-hint {
  text-align: center;
  color: var(--text-light-tertiary);
  font-size: 12px;
  margin: 8px auto 0;
  max-width: 520px;
}

/* The hero's inline non-iOS waitlist form (.non-ios-cta, .wl-inline,
     .wl-inline-msg) was removed 2026-05-22 by user direction. The
     /waitlist standalone page is still linked from the FAQ android-q
     section for visitors who actively look for the path. */

/* ============================================================
   v4 polish (2026-05-23): background texture, metric band,
   numbered how-it-works chips + icons, CTA reveal cue, branded
   tracker row, social-proof line, footer cluster, on-landing FAQ.
   ============================================================ */

/* #8: subtle background atmosphere. Two layered radial gradients
   in --gold-dark at 3-4% alpha give the navy field a "lit from
   above-right" feeling without introducing any new color surface.
   fixed attachment so the texture stays put when the page scrolls
   (otherwise the gradient would slide off as visitors read down). */
body {
  background-image:
    radial-gradient(ellipse 800px 600px at 85% -10%, rgba(176, 125, 49, 0.07), transparent 60%),
    radial-gradient(ellipse 600px 500px at 10% 110%, rgba(176, 125, 49, 0.045), transparent 55%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* #15: CTA reveal cue. The bubble preview animation finishes its
   last bubble at 2.4s; we want a tiny "now you" beat on the primary
   CTA at exactly 2.55s so the eye arrives at the button at the
   same moment it gets a subtle pulse. ONE iteration only: the
   ambient ctaPulse already runs forever via the existing rule, so
   this is a one-shot "introduction" wave on top of it.
   delay tuning: bubble-row.delay-3 = 2.4s + 0.7s animation = 3.1s
   end; we cue at 2.55s so the wave PEAKS as the last bubble
   completes (peak at 2.85s = 2.55s + 0.3s of a 0.6s ease-out). */
@keyframes ctaIntroWave {
  0% {
    transform: translateY(0) scale(1);
  }
  50% {
    transform: translateY(-3px) scale(1.025);
  }
  100% {
    transform: translateY(0) scale(1);
  }
}
a.btn {
  /* animation property already declared above for ctaPulse; using
     comma-list to stack the intro wave alongside the ambient pulse
     would clobber the original. Instead we attach the wave via a
     dedicated wrapper class .cta-row > a.btn and a separate
     animation declaration that the cascade merges. */
}
.cta-row > a.btn {
  animation:
    ctaPulse 4s ease-in-out infinite,
    ctaIntroWave 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 2.55s 1 both;
}
@media (prefers-reduced-motion: reduce) {
  .cta-row > a.btn {
    animation: none;
  }
}

/* #4: social-proof line above the primary CTA. Pulls live numbers
   from server-rendered week-stats placeholders so it stays in sync
   with the metric band below the preview. Quiet by default; reads
   as a one-line trust signal, not a separate visual block. */
.cta-proof {
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--text-light-tertiary);
  letter-spacing: 0.2px;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}
.cta-proof strong {
  font-weight: 700;
  color: var(--gold-mid);
}
@media all {
  .cta-proof {
    color: var(--text-dark-tertiary);
  }
}
@media (min-width: 760px) {
  .cta-proof {
    text-align: left;
  }
}

/* #2 (upgraded 2026-05-23 v5): lifetime stats panel. The 4-tile metric
   band sits inside a .lifetime-panel wrapper that carries a small gold
   "since launch" header above it. Each tile: dim glyph icon, gradient-
   gold display number, labeled unit. Glass-style tile background +
   inner top hairline + lit-from-above inset highlight match the hero
   CTA's material language. tabular-nums so digits don't jitter when
   IntersectionObserver tick-up animation fires. */
.lifetime-panel {
  /* 48px (was 20px) brings the gap above the metrics into the page's
     section rhythm - every other section sits 40-56px below the one
     above, but the metrics were crowding the side-by-side at ~14px. */
  margin: 48px auto 0;
  /* Full content width (was 520px) so the metrics band aligns edge-to-edge
     with the side-by-side above + how-mockup below, like the full-width
     hero/footer tier it already matches on desktop. The 520/360 caps were
     vestigial from when this lived in the narrow hero column. */
  max-width: 100%;
  font-variant-numeric: tabular-nums;
}
.lifetime-header {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 auto 10px;
  padding: 4px 12px 4px 10px;
  border-radius: 999px;
  background: rgba(176, 125, 49, 0.08);
  border: 1px solid rgba(176, 125, 49, 0.18);
}
.lifetime-header-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  box-shadow: 0 0 6px rgba(176, 125, 49, 0.55);
  flex-shrink: 0;
}
.lifetime-header-text {
  font-family: var(--font-display);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
/* The .lifetime-header is inline-flex, wrap it in a centered block
   context. .lifetime-panel is centered itself; the header alignment
   uses text-align on the panel as a quick wrap. */
.lifetime-panel {
  text-align: center;
}
.metric-band {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  padding: 0;
  text-align: left;
}
.metric-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 16px 8px 14px;
  border-radius: 14px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.06) 0%,
    rgba(255, 255, 255, 0.025) 40%,
    rgba(176, 125, 49, 0.045) 100%
  );
  border: 1px solid rgba(255, 255, 255, 0.07);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 6px 18px -10px rgba(0, 0, 0, 0.4);
  text-align: center;
  line-height: 1.1;
  overflow: hidden;
  transition:
    transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    box-shadow 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* Top hairline accent: 1px gold gradient running across the tile top.
   Reads as "lit from above" — same recipe as the hero CTA's inset
   highlight, just rendered as a pseudo-element so it can carry the
   brand gradient instead of plain white alpha. */
.metric-tile::before {
  content: "";
  position: absolute;
  top: 0;
  left: 10%;
  right: 10%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(216, 179, 102, 0.55), transparent);
  pointer-events: none;
}
.metric-tile:hover {
  transform: translateY(-2px);
  border-color: rgba(176, 125, 49, 0.22);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    0 10px 24px -10px rgba(0, 0, 0, 0.5);
}
.metric-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  color: var(--gold-mid);
  opacity: 0.45;
  flex-shrink: 0;
  transition: opacity 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.metric-tile:hover .metric-tile-icon {
  opacity: 0.85;
}
.metric-tile-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}
.metric-tile-value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 34px;
  line-height: 1;
  letter-spacing: -1px;
  /* Refined metallic sheen ramp - same 4-stop gradient as the hero
     wordmark, so the headline numbers read as the same premium gold. */
  background: linear-gradient(135deg, #b07d31 0%, #d8b366 45%, #f4dd97 62%, #a9772b 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.metric-tile-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-dark-tertiary);
  margin-top: 2px;
  font-weight: 700;
}
@media (max-width: 480px) {
  .lifetime-panel {
    /* match the ~36-40px mobile section rhythm (was 16px, too tight) */
    margin-top: 38px;
    /* full content width so the 2x2 metric grid lines up with the edges
       of the cards above/below it (was capped at 360px = indented). */
    max-width: 100%;
  }
  .lifetime-header {
    margin-bottom: 8px;
    padding: 3px 10px 3px 8px;
  }
  .lifetime-header-text {
    font-size: 10px;
    letter-spacing: 1.2px;
  }
  .metric-band {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
  .metric-tile {
    padding: 14px 6px 12px;
    gap: 5px;
  }
  .metric-tile-icon {
    width: 16px;
    height: 16px;
  }
  .metric-tile-value {
    font-size: 28px;
    letter-spacing: -0.6px;
  }
  .metric-tile-label {
    font-size: 10px;
    letter-spacing: 0.8px;
  }
}
@media (min-width: 760px) {
  .lifetime-panel {
    /* 2026-05-26: the panel is now a standalone full-width band below the
       side-by-side (not the old in-hero-column placement the stale 14px
       was tuned for). 48px puts its top gap in the page's section rhythm. */
    margin-top: 48px;
    max-width: 100%;
  }
  .lifetime-header {
    margin-bottom: 12px;
  }
  .metric-band {
    /* Slightly larger gap between tiles on desktop. The wider column
       width gives room to breathe; 10px reads as deliberate spacing
       rather than the 8px mobile-cramped feel. */
    gap: 10px;
  }
  .metric-tile {
    /* Bigger padding on desktop so each tile carries more visual
       weight when the column is wider. */
    padding: 20px 10px 16px;
    gap: 8px;
  }
  .metric-tile-icon {
    width: 20px;
    height: 20px;
  }
  .metric-tile-value {
    font-size: 38px;
    letter-spacing: -1.2px;
  }
  .metric-tile-label {
    font-size: 11.5px;
    letter-spacing: 1.2px;
  }
}

/* #3: branded tracker row. Replaces the dim text-only row with
   color-accented chips that signal each brand without infringing
   the actual marks. Each chip: small geometric icon + brand name
   in the brand's recognizable color. Reads as "yes, your existing
   tracker works" with way more visual weight than the prior text.
   Brand colors used:
     Strava       #FC4C02 (Strava orange, well-known)
     Garmin       #007CC3 (Garmin blue)
     Apple Health #FF2D55 (Apple Health pink/red) */
.tracker-row.is-branded {
  /* Override the existing low-key tracker-row to give the branded
     variant breathing room. The non-branded variant still renders
     correctly when this class is absent. */
  gap: 14px;
  margin: 36px auto 0;
}
.tracker-row.is-branded .tracker-caption {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  opacity: 0.6;
}
.tracker-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}
.tracker-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px 7px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.1px;
  color: var(--text-dark);
  line-height: 1;
  transition:
    background-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tracker-chip:hover {
  background: rgba(255, 255, 255, 0.07);
  transform: translateY(-1px);
}
.tracker-chip-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: inline-block;
}
.tracker-chip.is-strava .tracker-chip-icon {
  color: #fc4c02;
}
.tracker-chip.is-garmin .tracker-chip-icon {
  color: #007cc3;
}
.tracker-chip.is-apple .tracker-chip-icon {
  color: #ff2d55;
}
/* Pre-preview variant: tracker row sits above the "here's what your
   group sees" heading inside .hero-preview. On mobile (single column)
   it needs breathing room ABOVE so it doesn't cluster with the
   save-contact link that ends the hero-text block right above it.
   On desktop (2-col hero), it sits independently at the top of the
   left column, so no top margin is needed. */
.tracker-row.is-pre-preview {
  margin: 28px auto 18px;
}
.tracker-row.is-pre-preview .tracker-caption {
  font-size: 11px;
  letter-spacing: 1.6px;
  opacity: 0.7;
}
@media (min-width: 760px) {
  .tracker-row.is-pre-preview {
    margin: 0 auto 18px;
  }
}

/* #7: how-it-works numbered chips + step icons. The 4-step ordered
   list gets a gold numbered chip + a 20px inline icon per step.
   Reads as a designed flow rather than a bare numbered list. The
   chip reuses the wordmark gradient so it's brand-coherent. Icons
   are inline SVGs in the HTML template (currentColor inherits from
   the parent .how-icon span color). */
.how ol.how-steps {
  list-style: none;
  padding: 0;
  margin: 16px 0 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.how-steps li {
  display: grid;
  grid-template-columns: 32px 24px 1fr;
  gap: 10px;
  align-items: center;
  padding: 12px 14px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 15px;
  line-height: 1.4;
  color: var(--text-dark-secondary);
  /* Hover-lift mirrors .metric-tile + .sbs-card so the 4-step "how it
     works" list shares the same "card breathes on cursor-in" vocabulary
     as the other showcase surfaces on the page. 2026-05-29 polish:
     before this, the steps were the only major card cluster that sat
     dead under the cursor, breaking the page's motion language. */
  transition:
    transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    background-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.how-steps li:hover {
  transform: translateY(-1px);
  border-color: rgba(176, 125, 49, 0.16);
  background: rgba(255, 255, 255, 0.04);
}
.how-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0;
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  color: var(--navy);
  flex-shrink: 0;
  /* Same lit-from-above inset highlight as the primary CTA so the
     chips feel like part of the same brand-surface family. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 2px 6px -2px rgba(176, 125, 49, 0.35);
  /* When the parent step card lifts, the gold chip's gold-soft halo
     bumps in tandem so the chip + card move as one unit. The base
     shadow stays unchanged at rest; only the warm gold ring expands
     under hover. */
  transition: box-shadow 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.how-steps li:hover .how-step-num {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    0 3px 10px -2px rgba(216, 179, 102, 0.5);
}
.how-step-icon {
  width: 20px;
  height: 20px;
  color: var(--gold-mid);
  flex-shrink: 0;
  opacity: 0.85;
}
.how-step-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}
.how-step-body strong {
  color: var(--text-dark);
  font-weight: 700;
}
@media (max-width: 480px) {
  .how-steps li {
    grid-template-columns: 28px 20px 1fr;
    gap: 8px;
    padding: 10px 12px;
    font-size: 14px;
  }
  .how-step-num {
    width: 24px;
    height: 24px;
    font-size: 12px;
  }
  .how-step-icon {
    width: 18px;
    height: 18px;
  }
}

/* #1: on-landing FAQ section. The .faq selector + nested
   details/summary styles already exist further up (lines ~1722-1860),
   built originally for an inline FAQ that was moved to /faq on
   2026-05-23. Lifting the 4 Q&As back onto the landing reuses 100%
   of those existing styles; this block only adjusts margins so the
   FAQ doesn't crowd the how-it-works section above. */
.landing-faq {
  max-width: 640px;
  margin: 40px auto 0;
}
.landing-faq h2 {
  text-align: center;
  font-family: var(--font-display);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--text-dark-tertiary);
  margin: 0 0 14px;
  font-weight: 600;
}
@media (min-width: 760px) {
  .landing-faq h2 {
    font-size: 14px;
  }
}

/* #11: footer cluster. Expands the 4-link inline footer into a
   wider band with a re-stated tagline + grouped link clusters.
   Preserves the original muted treatment so the footer still reads
   as a quiet end-of-page rather than a competing nav. */
footer.footer-cluster {
  margin-top: 56px;
  padding-top: 28px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  font-size: 13px;
  color: var(--text-dark-tertiary);
  text-align: center;
}
.footer-tagline {
  font-size: 14px;
  margin: 0 auto 18px;
  max-width: 360px;
  color: var(--text-dark-secondary);
  letter-spacing: 0.1px;
}
.footer-tagline strong {
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-weight: 700;
}
.footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 22px;
  margin: 0 auto 14px;
  max-width: 480px;
}
.footer-links a {
  color: var(--text-dark-tertiary);
  text-decoration: none;
  /* Faintly-visible dots at rest (was `transparent`, which left the
     affordance invisible until hover). 0.10 white-alpha is half of
     the body text's tertiary opacity and 2x the footer's own
     border-top (0.05) — enough to hint "this is a link" on first
     glance without making the footer compete with the body. Hover
     still transitions cleanly to gold-light via the existing rule. */
  border-bottom: 1px dotted rgba(255, 255, 255, 0.1);
  padding-bottom: 1px;
  transition:
    color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-bottom-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.footer-links a:hover,
.footer-links a:focus-visible {
  color: var(--gold-light);
  border-bottom-color: var(--gold-light);
  outline: none;
}
.footer-copyright {
  font-size: 11px;
  opacity: 0.5;
  letter-spacing: 0.3px;
  margin: 0;
}
@media (max-width: 480px) {
  footer.footer-cluster {
    margin-top: 40px;
    padding-top: 22px;
  }
  .footer-tagline {
    font-size: 13px;
  }
  .footer-links {
    gap: 6px 16px;
  }
}

/* ────────────────────────────────────────────────────────────────
   v6 enticement polish (2026-05-25). New components grouped at the
   end of the file so the existing v1-v5 layer above stays untouched.
   Order: brand-manifesto → impact-kicker → hi-chip → side-by-side →
   qr-card → mockup-step-pill → bubble-arrival animations →
   leaderboard climb arrow.
   ──────────────────────────────────────────────────────────────── */

/* Impact kicker: pre/post-runbot proof line PROMOTED above the CTA.
   Larger than the legacy .impact-chip (which lived below the preview),
   styled with a small gold ▲ arrow + a slightly louder font so the
   numbers carry. Sits in .hero-text right above the cta-proof line. */
.impact-kicker {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 auto 14px;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 14px;
  line-height: 1.4;
  background: rgba(216, 179, 102, 0.08);
  border: 1px solid rgba(216, 179, 102, 0.18);
  color: var(--text-dark-secondary);
  font-variant-numeric: tabular-nums;
  /* Subtle gold-glow on appear — single 600ms shimmer that draws the
     eye to the proof point on first paint, then settles. The bare-rim
     pill becomes the final resting state. */
  animation: impact-kicker-shimmer 1.2s cubic-bezier(0.16, 1, 0.3, 1) 0.6s both;
}
@keyframes impact-kicker-shimmer {
  from {
    opacity: 0;
    transform: translateY(4px);
    box-shadow: 0 0 0 0 rgba(216, 179, 102, 0);
  }
  60% {
    box-shadow: 0 0 0 8px rgba(216, 179, 102, 0.08);
  }
  to {
    opacity: 1;
    transform: translateY(0);
    box-shadow: 0 0 0 0 rgba(216, 179, 102, 0);
  }
}
.impact-kicker-arrow {
  font-size: 10px;
  color: var(--accent);
  line-height: 1;
  filter: drop-shadow(0 0 4px rgba(74, 222, 128, 0.4));
}
.impact-kicker-text strong {
  color: var(--text-dark);
  font-weight: 600;
}
.impact-kicker-text strong.count-up {
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
  .impact-kicker {
    animation: none;
  }
}
@media (min-width: 760px) {
  .impact-kicker {
    align-self: flex-start;
  }
}
@media (max-width: 480px) {
  .impact-kicker {
    font-size: 13px;
    padding: 6px 12px;
  }
}

/* "you'll send: hi" expectation chip. Tiny pill below the CTA so the
   visitor sees the literal first message they'll send. Reduces the
   "uh what do I type" friction at the empty Messages compose moment. */
.hi-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 12px auto 0;
  padding: 6px 12px 6px 10px;
  font-size: 12px;
  color: var(--text-dark-tertiary);
  border: 1px dashed rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
}
.hi-chip[hidden] {
  display: none;
}
.hi-chip-label {
  letter-spacing: 0.3px;
  text-transform: lowercase;
}
.hi-chip-text {
  display: inline-block;
  padding: 2px 10px;
  background: #0066d6;
  color: #fff;
  border-radius: 10px;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
  font-size: 12px;
  font-weight: 500;
}
@media (min-width: 760px) {
  .hi-chip {
    margin-left: 0;
    margin-right: 0;
    align-self: flex-start;
  }
}

/* Inline fineprint row that collapses the previously-stacked hi-chip +
   vcard-link-mini into a single horizontal pair under the primary CTA.
   Recovers ~36px of above-fold vertical space on mobile. The hi-chip
   keeps its blue "hi" badge but drops the surrounding pill chrome since
   inline-flex inside a flex row reads cleaner without nested chip
   geometry. The middle separator dot only renders when the hi-chip is
   actually visible — non-iOS visitors get the link alone via the
   adjacent-sibling `[hidden] +` rule. 2026-05-25 Phase B density polish. */
.cta-fineprint {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  column-gap: 12px;
  row-gap: 0;
  margin: 8px 0 0;
  min-height: 44px;
}
@media (min-width: 760px) {
  .cta-fineprint {
    justify-content: flex-start;
  }
}
.cta-fineprint .hi-chip.is-inline {
  margin: 0;
  padding: 6px 2px;
  border: 0;
  background: transparent;
  font-size: 12px;
}
.cta-fineprint .hi-chip.is-inline .hi-chip-text {
  /* Tighten the inline blue badge so the row reads as a single line of
     fine print rather than a stacked chip. */
  padding: 1px 8px;
  font-size: 11px;
}
.cta-fineprint-sep {
  color: var(--text-dark-tertiary);
  opacity: 0.5;
  font-size: 14px;
  line-height: 1;
  /* The dot otherwise sits below baseline relative to the blue badge.
     Negative letter-spacing isn't enough; explicit translate aligns
     the dot with the badge's optical center. */
  transform: translateY(-1px);
}
/* When the hi-chip is hidden (non-iOS visitor) the separator dot is
   an orphan with nothing to its left, so hide it too. Adjacent sibling
   selector matches even when the [hidden] element has display:none. */
.cta-fineprint .hi-chip[hidden] + .cta-fineprint-sep {
  display: none;
}
/* Reset vcard-link-mini's standalone margins when nested in the
   fineprint row. The tap-area pattern (padding + negative margin)
   from Phase A stays intact via the existing rule; we only zero out
   the top-margin that was for stacked layout. */
.cta-fineprint .vcard-link-mini {
  margin-top: -4px;
  margin-bottom: 0;
}

/* ────────── Synthetic side-by-side: Strava → iMessage ────────── */
.side-by-side {
  margin: 56px auto 0;
  max-width: 920px;
  text-align: center;
}
.side-by-side-heading {
  font-family: var(--font-display);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 1.6px;
  /* Gold-gradient clip matching .lifetime-header-text, so "from your run"
     and "since launch" read as the same family of section labels instead
     of one gold pill + one plain grey heading. */
  background: linear-gradient(135deg, var(--gold-dark), var(--gold-mid));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  margin: 0 0 18px;
  font-weight: 700;
}
.side-by-side-grid {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto auto auto;
  gap: 16px;
  align-items: stretch;
}
@media (min-width: 760px) {
  /* Desktop: 3 columns where the middle arrow column gets a fixed
     pixel width so its flex:1 lines actually have main-axis space
     to grow into. With `auto` width the column shrinks to fit its
     content (chip), the lines collapse to 0, and you lose the
     "flow between cards" visual the arrow is selling. 140px gives
     each line ~50px to render through. */
  .side-by-side-grid {
    grid-template-columns: 1fr 140px 1fr;
    grid-template-rows: auto;
    gap: 0;
    align-items: stretch;
  }
}
.sbs-card {
  position: relative;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 22px;
  padding: 16px 18px 18px;
  /* Glass material matching .metric-tile + the hero CTA: white-alpha top
     fading to a faint gold-tint bottom, over a blur, with an inset
     top-highlight. Was a flat rgba(10,22,40,.55) fill, which read flatter
     than the rest of the page. */
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.055) 0%,
    rgba(255, 255, 255, 0.02) 42%,
    rgba(176, 125, 49, 0.045) 100%
  );
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 14px 36px -12px rgba(0, 0, 0, 0.5);
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: hidden;
  /* Hover-lift transition mirrors .metric-tile's vocabulary so both
     showcase surfaces breathe the same way on cursor-in. Three
     properties + identical 0.18s cubic-bezier so the response reads as
     one motion, not three. 2026-05-29 polish: closes the "every card
     breathes, but this one sits dead" inconsistency between the
     side-by-side proof and the lifetime-stats grid. */
  transition:
    transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    box-shadow 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.sbs-card:hover {
  transform: translateY(-2px);
  border-color: rgba(176, 125, 49, 0.18);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.11),
    0 18px 42px -12px rgba(0, 0, 0, 0.55);
}
/* Lit-from-above gold hairline, same recipe as .metric-tile::before, so
   the two showcase sections share one material language. */
.sbs-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 12%;
  right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(216, 179, 102, 0.5), transparent);
  pointer-events: none;
}
.sbs-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.sbs-source-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
}
.sbs-source-pill.is-strava {
  background: rgba(252, 76, 2, 0.16);
  color: #fc6a2a;
  border: 1px solid rgba(252, 76, 2, 0.3);
}
.sbs-source-pill.is-imessage {
  background: rgba(0, 122, 255, 0.16);
  color: #4ca0ff;
  border: 1px solid rgba(0, 122, 255, 0.3);
}
.sbs-status {
  font-size: 11px;
  color: var(--text-dark-tertiary);
  letter-spacing: 0.2px;
}
.sbs-strava-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.sbs-strava-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-dark);
}
.sbs-strava-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  padding: 12px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.sbs-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sbs-stat-value {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.5px;
  color: var(--text-dark);
  font-variant-numeric: tabular-nums;
  font-family: var(--font-display);
}
.sbs-stat-label {
  font-size: 10px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--text-dark-tertiary);
}
.sbs-strava-map {
  width: 100%;
  height: 60px;
  display: block;
}
.sbs-strava-map svg {
  width: 100%;
  height: 100%;
}
/* Arrow chip between the two cards. On mobile the lines stack
   vertically so the arrow reads as a downward flow from the Strava
   card above to the iMessage card below; on desktop the layout flips
   to horizontal with lines extending left/right and the chip ("~10s
   →") centered between the cards. The glyph rotates 90° on mobile
   so "→" becomes "↓" without needing a different unicode character. */
.sbs-arrow {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.sbs-arrow-line {
  width: 1px;
  height: 24px;
  background: linear-gradient(
    180deg,
    transparent,
    rgba(216, 179, 102, 0.4),
    rgba(216, 179, 102, 0.4),
    transparent
  );
}
@media (min-width: 760px) {
  .sbs-arrow {
    flex-direction: row;
    gap: 12px;
  }
  .sbs-arrow-line {
    flex: 1;
    width: auto;
    height: 1px;
    background: linear-gradient(
      90deg,
      transparent,
      rgba(216, 179, 102, 0.4),
      rgba(216, 179, 102, 0.4),
      transparent
    );
    min-width: 20px;
  }
}
.sbs-arrow-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border: 1px solid rgba(216, 179, 102, 0.3);
  border-radius: 999px;
  font-size: 12px;
  color: var(--gold-mid);
  background: rgba(216, 179, 102, 0.06);
  letter-spacing: 0.3px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.sbs-arrow-glyph {
  font-size: 14px;
  line-height: 1;
  transform: rotate(90deg);
}
@media (min-width: 760px) {
  .sbs-arrow-glyph {
    transform: rotate(0deg);
  }
}
.sbs-imessage-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 6px;
}
.sbs-imessage-sender {
  font-size: 11px;
  color: var(--text-dark-tertiary);
  padding-left: 6px;
  letter-spacing: 0.1px;
}
.sbs-imessage-bubble {
  align-self: flex-start;
  max-width: 95%;
  padding: 10px 14px;
  background: var(--bubble-bg-dark);
  color: var(--text-dark);
  border-radius: 18.5px;
  border-bottom-left-radius: 4px;
  font-size: 14px;
  line-height: 1.4;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
  /* Land-with-a-small-bounce when the side-by-side enters the viewport.
     Driven by .is-arrived class added via IntersectionObserver in
     landing.js. Static state (no JS) renders the bubble in its
     resting place via the keyframes' from-state being normal. */
  opacity: 0;
  transform: translateY(8px) scale(0.98);
  transition:
    opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.sbs-imessage-bubble.is-arrived {
  opacity: 1;
  transform: translateY(0) scale(1);
}
@media (prefers-reduced-motion: reduce) {
  .sbs-imessage-bubble {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
/* Tapback cluster under the bot's bubble: the literal "group lights up"
   moment. Small circular reaction bubbles (iMessage tapback look) fill
   the iMessage card so it balances the taller Strava card, and pay off
   the caption's promise. */
.sbs-tapbacks {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-left: 6px;
  margin-top: 8px;
}
.sbs-tapback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--bubble-bg-dark);
  font-size: 13px;
  line-height: 1;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.sbs-tapback-more {
  font-size: 11px;
  color: var(--text-dark-tertiary);
  letter-spacing: 0.2px;
  margin-left: 2px;
}
.side-by-side-caption {
  margin: 18px auto 0;
  font-size: 12px;
  color: var(--text-dark-tertiary);
  letter-spacing: 0.3px;
}
@media (max-width: 480px) {
  .side-by-side {
    margin-top: 36px;
  }
  .sbs-card {
    padding: 14px 16px 16px;
    border-radius: 18px;
  }
  .sbs-strava-title {
    font-size: 15px;
  }
  .sbs-stat-value {
    font-size: 18px;
  }
}

/* ────────── QR card (desktop visitors only) ──────────
   Container is 118px tall with 8px padding around the SVG → 102px
   effective QR render. That's ~2.7cm on a 96ppi display — comfortably
   above the 2cm minimum that modern phone cameras can resolve
   first-try. The QR module count is ~27, so each module renders at
   ~3.8px. Background is solid near-black (rgba 0.95) so the white
   QR pixels have full ~16:1 contrast for fast scanning. */
.qr-card {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin: 20px 0 0;
  padding: 12px 18px 12px 12px;
  border: 1px solid rgba(216, 179, 102, 0.22);
  border-radius: 16px;
  background: rgba(216, 179, 102, 0.04);
}
.qr-card-svg {
  width: 118px;
  height: 118px;
  background: rgba(8, 14, 28, 0.95);
  border-radius: 8px;
  padding: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.qr-card-svg svg {
  width: 100%;
  height: 100%;
}
.qr-card-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
}
.qr-card-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dark);
  letter-spacing: 0.2px;
}
.qr-card-sub {
  font-size: 11px;
  color: var(--text-dark-tertiary);
  line-height: 1.4;
  max-width: 240px;
}

/* ────────── how-mockup step pill annotations ────────── */
.mockup-step-pill {
  display: inline-block;
  margin-bottom: 5px;
  padding: 2px 8px;
  font-size: 10px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--gold-mid);
  background: rgba(216, 179, 102, 0.08);
  border: 1px solid rgba(216, 179, 102, 0.18);
  border-radius: 6px;
  font-weight: 700;
  font-family: var(--font-display);
}
.mockup-row.out .mockup-step-pill {
  align-self: flex-end;
}
.mockup-row.in .mockup-step-pill {
  align-self: flex-start;
  margin-left: 13px;
}

/* ────────── Bubble-arrival pop animation ────────── */
.bubble-row.is-arriving .bubble:not(.is-typing) {
  animation: bubble-arrive 0.42s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes bubble-arrive {
  0% {
    opacity: 0;
    transform: translateY(10px) scale(0.92);
  }
  60% {
    transform: translateY(0) scale(1.02);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .bubble-row.is-arriving .bubble:not(.is-typing) {
    animation: none;
  }
}

/* During the looping animation, the bubble rows must not show their
   one-shot mount cadence (.delay-1/.delay-2/.delay-3 slideUp). The
   .preview-frame.is-looping flag suppresses those + makes each row
   transparent until the JS-driven loop opens it. */
.preview-frame.is-looping .bubble-row {
  animation: none;
}
.preview-frame.is-looping .bubble-row[hidden] {
  display: none;
}

/* ────────── Leaderboard climb arrow (▲ N) ────────── */
.lb-climb {
  display: inline-block;
  margin-left: 4px;
  font-size: 11px;
  color: var(--accent);
  letter-spacing: 0.2px;
  font-variant-numeric: tabular-nums;
  /* Land in, hold 1.6s, fade out. Driven by the .is-fresh class
     toggled by landing.js for ~2 seconds after a poll detects the
     runner climbed positions. */
  opacity: 0;
  transform: translateY(-2px);
  transition:
    opacity 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.lb-climb.is-fresh {
  opacity: 1;
  transform: translateY(0);
  filter: drop-shadow(0 0 6px rgba(74, 222, 128, 0.45));
}
@media (prefers-reduced-motion: reduce) {
  .lb-climb {
    transition: none;
  }
  .lb-climb.is-fresh {
    filter: none;
  }
}

/* ────────── Footer cluster: remove old "relentless" copyright tail ────────── */
/* The footer-copyright text was shortened from "© 2026 runbot · relentless"
   to "© 2026 runbot" after the brand-manifesto promotion (2026-05-25).
   No additional CSS needed; the existing .footer-copyright rule already
   handles the shorter string. */

/* ────────── Swipe affordance: mobile-only carousel touch hint ────────── */
/* Subtle "← swipe →" hint sitting between the preview-frame and the live
   chip. Mobile-only (desktop has chevrons + hover-pause). Pulses 2x for
   ~3.6s then sits at rest, and landing.js fades it out entirely on the
   first touchstart on the preview-frame. The hint is hidden via the
   [hidden] attribute when the server knows there's only one group to
   swipe between. 2026-05-25 Phase B swipe affordance. */
.swipe-hint {
  display: none;
}
.swipe-hint[hidden] {
  display: none !important;
}
@media (max-width: 760px) {
  .swipe-hint:not([hidden]) {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin: 10px auto 0;
    font-size: 10px;
    letter-spacing: 2.4px;
    text-transform: uppercase;
    color: var(--text-dark-tertiary);
    opacity: 0.45;
    /* Two pulses on first paint, then sits at rest at the from-state's
       lower-opacity level. Animation runs `2` times not `infinite` so
       the hint doesn't become a permanent visual distraction — it's a
       teach-once cue. */
    animation: swipe-hint-pulse 1.8s ease-in-out 2;
    transition: opacity 0.6s ease-out;
    will-change: opacity;
  }
}
.swipe-hint.is-dismissed {
  opacity: 0 !important;
  pointer-events: none;
}
.swipe-hint-arrow {
  font-size: 14px;
  line-height: 1;
}
.swipe-hint-label {
  font-weight: 600;
}
@keyframes swipe-hint-pulse {
  0%,
  100% {
    opacity: 0.3;
  }
  50% {
    opacity: 0.75;
  }
}
@media (prefers-reduced-motion: reduce) {
  .swipe-hint:not([hidden]) {
    animation: none;
  }
}

/* ────────── Live marquee: mobile-only top-of-page scrolling band ────────── */
/* Renders the last 3 recent runs as a thin horizontal scrolling strip
   above the hero, mobile only. Desktop hides the strip entirely (the
   live-chip below the preview frame already carries the "live" signal
   there). The strip is server-rendered with the events duplicated in
   the track so the CSS translateX(0 → -50%) animation produces a
   perfectly seamless infinite loop. Empty data -> liveMarqueeHtml is
   "" and the slot doesn't render at all (zero reserved space, no CLS).
   2026-05-25 mobile micro-marquee. */
.live-marquee {
  display: none;
}
@media (max-width: 760px) {
  .live-marquee {
    display: block;
    /* Negative top margin pulls the strip into the body's padding-top
       area so it sits at the absolute top of the viewport. Negative
       side margins escape the body's 24px horizontal padding for an
       edge-to-edge band. The 14px bottom margin gives the hero a clean
       gap below the strip. */
    margin: calc(-1 * (44px + env(safe-area-inset-top))) -24px 14px;
    /* Top padding clears the iOS notch / status bar inset; content sits
       below the safe area regardless of device. 2026-05-26 redesign:
       gold gradient + gold border swapped for a neutral subtle darken
       (vertical gradient 0.28→0.18 black overlay on body navy) and a
       faint 6% white hairline below. Reads as a distinct system bar
       without the previous gold competing with the hero brand colors. */
    padding: calc(env(safe-area-inset-top) + 7px) 0 8px;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0.18));
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    overflow: hidden;
    position: relative;
    z-index: 1;
  }
}
/* Phones <=480px (where nearly every iPhone-portrait visitor lands)
   shrink the body padding to 32px/16px in the <=480px block above, so
   the strip's negative margins have to match THAT, not the base 44px/24px.
   Without this override the band overshoots 12px above the viewport (the
   clipped, "too high" look) and runs 16px wider than the viewport (8px
   past each edge). Matching the margins pins it flush to the top edge,
   with its own padding-top clearing the notch below. */
@media (max-width: 480px) {
  .live-marquee {
    margin: calc(-1 * (32px + env(safe-area-inset-top))) -16px 14px;
  }
}
.live-marquee[hidden] {
  display: none !important;
}
.live-marquee-shell {
  /* 2026-05-26 redesign: shell padding 0 14px → 0 16px and gap 10px →
     12px for more horizontal breathing room around the LIVE label
     before the scrolling track begins. Vertical alignment center kept
     so dot, text, and track baselines all sit on the same optical line. */
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
}
.live-marquee-label {
  /* 2026-05-26 redesign: --gold-light swapped for --text-dark-tertiary
     so the LIVE label reads as supporting chrome (the pulsing green
     dot is the only colored attention element). letter-spacing 1.4 →
     1.6px for cleaner caps readability at 9.5px font-size. */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: 9.5px;
  letter-spacing: 1.6px;
  color: var(--text-dark-tertiary);
  flex-shrink: 0;
  text-transform: uppercase;
}
.live-marquee-dot {
  /* 2026-05-26 redesign: dot 6px → 5px for more subtle. Green pulse
     stays since that's the universal "live" signal regardless of the
     surrounding palette. Reuses the same live-pulse keyframes the
     preview live-chip dot uses, so the two pulses cycle in matching
     rhythm even though they live in different parts of the page. */
  display: inline-block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-soft);
  animation: live-pulse 2.4s ease-out infinite;
}
.live-marquee-track-wrap {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  /* 2026-05-26 redesign: mask fade widened 14px → 18px for a smoother
     edge transition. -webkit-mask-image carries the same definition
     for Safari, which still requires the prefix as of iOS 17. */
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent 0,
    #000 18px,
    #000 calc(100% - 18px),
    transparent 100%
  );
  mask-image: linear-gradient(
    90deg,
    transparent 0,
    #000 18px,
    #000 calc(100% - 18px),
    transparent 100%
  );
}
.live-marquee-track {
  /* 2026-05-26 redesign: item gap 18px → 20px so consecutive entries
     don't crowd each other. 28s for one full half-cycle (translateX
     0 → -50%) lets a reader scan a 3-event strip comfortably. Linear
     easing matches the constant-velocity feel of a real news ticker.
     will-change keeps the GPU committed for smooth scroll on mid-range
     Android. */
  display: inline-flex;
  align-items: baseline;
  gap: 20px;
  white-space: nowrap;
  animation: live-marquee-scroll 28s linear infinite;
  will-change: transform;
}
.live-marquee-item {
  /* 2026-05-26 redesign: font-size 11px → 11.5px for slightly more
     comfortable reading on small viewports; the entry name + run
     is the actual content, everything else is chrome. */
  color: var(--text-dark);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.1px;
}
.live-marquee-item em {
  /* 2026-05-26 redesign: em.margin-left 4px → 5px breathing room
     between the run text and its "Xm ago" suffix; opacity 0.85
     softens the time-ago so it reads as supporting metadata, not
     equal weight to the run itself. */
  color: var(--text-dark-tertiary);
  font-style: normal;
  font-size: 10px;
  font-weight: 400;
  margin-left: 5px;
  opacity: 0.85;
}
.live-marquee-sep {
  /* 2026-05-26 redesign: font-size 12px → 10px matches the surrounding
     "em" scale instead of being chunkier than the content. opacity
     0.5 → 0.35 dampens the separator so it reads as a quiet pause
     between entries rather than punctuation. */
  color: var(--text-dark-tertiary);
  opacity: 0.35;
  font-size: 10px;
}
@keyframes live-marquee-scroll {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .live-marquee-track {
    /* Reduced-motion users see the strip frozen at its starting
       position; the first event remains visible so the "live" signal
       still lands without motion sensitivity penalty. */
    animation: none;
    transform: none;
  }
}

/* ────────── Web Share API button (Phase E) ────────── */
/* Inline button living inside the footer-links nav. Mirrors the
   surrounding .footer-links a anchor styling (font, color, dotted-
   underline-on-hover treatment) so the share button reads as part of
   the same row, not a foreign control with different chrome. font:
   inherit picks up the footer's text size + family without buttons'
   default browser overrides. landing.js feature-detects navigator.share
   and only unhides this when supported, so non-iOS-or-Android visitors
   never see a button that won't work. 2026-05-26 Phase E delight. */
.share-btn {
  background: transparent;
  border: 0;
  border-bottom: 1px dotted transparent;
  color: var(--text-dark-tertiary);
  font: inherit;
  letter-spacing: 0.2px;
  cursor: pointer;
  padding: 0 0 1px;
  transition:
    color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-bottom-color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.share-btn[hidden] {
  display: none;
}
.share-btn:hover,
.share-btn:focus-visible {
  color: var(--gold-light);
  border-bottom-color: var(--gold-light);
  outline: none;
}
