/* ===============================================================
   OurBestie — landing page

   The page is white. Two regions are blue and carry their own
   background: the hero, which paints a sky inside .hero__scene, and
   the footer. Both re-declare the ink tokens below so the components
   they contain go on working white-on-blue with no per-component
   knowledge of where they sit.
   =============================================================== */

/* ---------------------------------------------------------------
   Tokens

   The defaults are the light set, because most of the page is white.
   .hero and .footer override them back to the white-on-blue set —
   anything reading var(--ink) / var(--ink-soft) / var(--line) adapts
   on its own, so only components that hard-code a colour need to know
   which region they're in.
   --------------------------------------------------------------- */
:root {
  --ink: #0d2b4a;
  --accent: #ffc93c;
  --ink-soft: #46617f;
  /* Was #70889f, which measures 3.67:1 on white — under the 4.5:1 WCAG AA
     needs for text below 18.66px, and this token is used at 13–13.5px for
     the section eyebrows, quote attributions, the blog post count and the
     footer copy. #5b748f is 4.83:1 and close enough that nothing in the
     design shifts. */
  --ink-faint: #5b748f;
  --line: rgba(13, 43, 74, 0.12);
  /* Panels on white: a hair off-white with a drawn edge. On the blue they
     were tinted glass, which needed something behind it to tint. */
  --panel: #f5f8fc;
  --panel-line: rgba(13, 43, 74, 0.1);
  --deep: #0d3f74;
  --brand: #1e7fd1;
  --brand-deep: #14568f;

  --shadow-lift: 0 22px 60px -28px rgba(6, 34, 66, 0.75);
  --shadow-text: 0 2px 26px rgba(8, 42, 82, 0.32);
  --shadow-copy: 0 1px 2px rgba(6, 32, 64, 0.35), 0 2px 16px rgba(6, 32, 64, 0.4);
  --shadow-panel: 0 18px 44px -30px rgba(13, 43, 74, 0.5);

  --wrap: 1180px;
  --gut: clamp(20px, 5vw, 56px);
  --radius: 22px;
  --radius-sm: 14px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --nav-h: 76px;

  /* The divider drawn at every section seam. One token so all the seams stay
     the same weight — 2px reads as a deliberate rule, thinner looks like a
     hairline artefact and thicker starts to look like a bar. */
  --rule: 2px solid #000;

  /* Apple's set first, so iPhone and Mac render the exact glyphs; the rest are
     platform fallbacks. These are system fonts — they can't be bundled, so
     elsewhere this is Segoe's or Noto's drawing of the same character. */
  --font-emoji: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}

/* The blue regions. Same token names, white-on-blue values — the glass
   panels come back here because here there is a sky to tint.

   .nav is on the list because it is a sibling of <main>, not a child of
   .hero, so it inherits from :root — but it is positioned over the hero
   and never travels past it, so it is blue territory in every way that
   matters to a colour. */
.hero,
.nav,
.footer {
  --ink: #ffffff;
  --ink-soft: rgba(255, 255, 255, 0.88);
  --ink-faint: rgba(255, 255, 255, 0.7);
  --line: rgba(255, 255, 255, 0.3);
  --panel: linear-gradient(160deg, rgba(11, 55, 102, 0.26), rgba(11, 55, 102, 0.38));
  --panel-line: rgba(255, 255, 255, 0.2);
  color: var(--ink);
}

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

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

/* Lenis owns scrolling once it boots; native smooth would fight it. */
html.lenis { scroll-behavior: auto; }
html.lenis body { height: auto; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font);
  font-weight: 300;
  color: var(--ink);
  background: #ffffff;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { margin: 0; font-weight: 800; letter-spacing: -0.03em; line-height: 1.02; }
p { margin: 0; }
ol, ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* currentColor, so the ring is dark on the white page and white against
   the hero and footer without either needing its own rule. */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 6px;
}

::selection { background: var(--brand); color: #fff; }
.hero ::selection,
.footer ::selection { background: rgba(255, 255, 255, 0.9); color: #0f5fa8; }

.skip-link {
  position: fixed;
  top: 10px;
  left: 50%;
  transform: translate(-50%, -160%);
  z-index: 999;
  padding: 10px 18px;
  border-radius: 999px;
  background: #fff;
  color: #0f5fa8;
  font-size: 14px;
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

/* ---------------------------------------------------------------
   Floating travel emoji

   Scoped to the hero and absolute within it, so the glyphs scroll away
   with the blue instead of drifting on over the white sections. Their
   x/y are percentages, which now read against the hero box rather than
   the viewport — same scatter, just measured against a shorter ruler.

   In front of .hero__scene and of .hero__inner, which is where they sat
   when this layer was fixed over the whole page. pointer-events:none is
   what keeps that from swallowing clicks on the CTA underneath.

   Built by js/modules/floaters.js from FLOATERS in config.js; the tuning
   lives there, the behaviour lives here.
   --------------------------------------------------------------- */
.floaters {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden; /* a glyph parked near an edge drifts past it */
  pointer-events: none;
  /* The same fade .hero__scene uses. The lowest glyphs sit at y:91–93%,
     where the sky behind them is already dissolving into the white page —
     without this they'd be left hanging over the white, fully opaque. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 84%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 84%, transparent 100%);
}

.floater {
  /* One knob for the breakpoint to turn — the centering below is derived
     from the rendered size, so scaling the glyph alone would walk it off
     its authored point. */
  --scale: 1;
  --rendered: calc(var(--size) * var(--scale));

  position: absolute;
  left: var(--x);
  top: var(--y);
  /* Centres the glyph on its authored point. Margins rather than a
     translate: the sway animation owns `transform` outright, and a
     centering translate in the keyframes would have to be repeated on
     every stop. */
  margin-left: calc(var(--rendered) / -2);
  margin-top: calc(var(--rendered) / -2);
  opacity: var(--opacity);
  /* The shadow is what stops them reading as flat stickers pasted on the
     sky — same trick as .hero__cloud, cooler here because these sit
     against blue rather than in the cloud layer's warm light. */
  filter: blur(var(--blur)) drop-shadow(0 10px 18px rgba(6, 32, 64, 0.34));
  animation: floater-sway var(--sway) ease-in-out var(--delay) infinite;
}

.floater__glyph {
  display: block;
  font-size: var(--rendered);
  line-height: 1;
  font-family: var(--font-emoji);
  animation: floater-tilt var(--tilt-dur) ease-in-out var(--delay) infinite;
}

/* A closed loop rather than a one-way drift, so it repeats without a jump.
   Distances are in viewport units: the glyphs are scattered by percentage,
   and a px drift would read as a much bigger move on a phone than on a
   desktop. */
@keyframes floater-sway {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(1.4vw, -1.5vh, 0); }
  50%  { transform: translate3d(0.3vw, -2.9vh, 0); }
  75%  { transform: translate3d(-1.2vw, -1.4vh, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

@keyframes floater-tilt {
  0%   { transform: rotate(calc(var(--tilt) * -1)); }
  50%  { transform: rotate(var(--tilt)); }
  100% { transform: rotate(calc(var(--tilt) * -1)); }
}

@media (max-width: 720px) {
  /* FLOATERS is ordered near → far; past the eighth it's all small hazy
     background, which on a phone is just noise over the copy. */
  .floater:nth-child(n + 9) { display: none; }
  .floater { --scale: 0.74; }
}

/* ---------------------------------------------------------------
   Layout helpers
   --------------------------------------------------------------- */
.wrap {
  width: min(var(--wrap), 100%);
  margin-inline: auto;
  padding-inline: var(--gut);
}
.wrap--narrow { width: min(760px, 100%); }
.center { text-align: center; }

/* Every content section carries the seam on its own top edge, so the rule
   sits between it and whatever precedes it. The .section blocks (faq, cta)
   draw the seams below the community rail; the .community and .footer rules
   below cover the two ends of the run. Testimonials opts out with --flush:
   the rail already ends on the band's own black, and a second rule right
   under it read as a stray line. */
.section { padding: clamp(80px, 11vw, 150px) 0; border-top: var(--rule); }
.section--tight { padding: clamp(56px, 7vw, 96px) 0; }
.section--flush { border-top: 0; }

.grid { display: grid; gap: clamp(16px, 2vw, 24px); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }

@media (max-width: 900px) { .grid--3 { grid-template-columns: 1fr 1fr; } }
/* A two-up of prose panels holds up longer than a three-up, so it stacks
   later — at 620px the columns are too narrow to read comfortably. */
@media (max-width: 620px) { .grid--2, .grid--3 { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------
   Type scale
   --------------------------------------------------------------- */
.eyebrow {
  font-size: 12px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 18px;
}

/* No text-shadow on any of these: every .h2 and .lede on the page now sits
   on flat white, where a shadow tuned to lift copy off a moving sky just
   reads as a smudge. The hero keeps its own — see .hero__title. */
.h2 {
  font-size: clamp(32px, 5.2vw, 62px);
  margin-bottom: 20px;
}
.h2--big { font-size: clamp(38px, 6.6vw, 78px); }

.lede {
  font-size: clamp(16px, 1.6vw, 19px);
  color: var(--ink-soft);
  max-width: 56ch;
  margin-bottom: 54px;
}

/* Copy that sits straight on the footer's blue, with no panel to back it. */
.footer__brand p,
.footer__cols a,
.footer__cols h4,
.footer__base { text-shadow: var(--shadow-copy); }
.center .lede { margin-inline: auto; }

/* ---------------------------------------------------------------
   Buttons
   --------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  height: 52px;
  padding: 0 26px;
  border-radius: 999px;
  font-size: 15.5px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transition: transform 0.4s var(--ease), background-color 0.3s, box-shadow 0.4s var(--ease), border-color 0.3s;
  will-change: transform;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

.btn--solid {
  background: #fff;
  color: #14568f;
  box-shadow: 0 12px 30px -14px rgba(6, 34, 66, 0.7);
}
.btn--solid:hover { box-shadow: 0 20px 42px -16px rgba(6, 34, 66, 0.8); }

/* Tinted rather than transparent — a ghost button vanishes the moment a
   white cloud drifts behind it. */
.btn--ghost {
  border: 1px solid rgba(255, 255, 255, 0.45);
  color: #fff;
  background: rgba(10, 58, 108, 0.28);
  text-shadow: 0 1px 6px rgba(6, 34, 66, 0.5);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.btn--ghost:hover { background: rgba(10, 58, 108, 0.44); border-color: rgba(255, 255, 255, 0.75); }

.btn--sm { height: 42px; padding: 0 20px; font-size: 14.5px; }

/* Nav CTA — a soft glass pill. Pale gradient face, a crisp white top
   edge, and a wide diffuse halo so it lifts off the sky without moving.
   Only the light changes on hover; the button itself stays put. */
.nav__cta {
  background: linear-gradient(180deg, #fdfeff 0%, #eff4fe 42%, #d5e1f8 100%);
  color: #16355c;
  font-weight: 800;
  letter-spacing: -0.02em;
  padding: 0 22px;
  border: 1px solid rgba(255, 255, 255, 0.9);
  box-shadow:
    /* the capsule of light around the pill */
    0 0 22px 8px rgba(232, 241, 255, 0.3),
    /* grounding */
    0 10px 22px -10px rgba(8, 40, 80, 0.55),
    0 2px 4px rgba(8, 40, 80, 0.16),
    /* face modelling */
    inset 0 1px 0 #fff,
    inset 0 -3px 6px -2px rgba(150, 178, 222, 0.55);
  transition: box-shadow 0.45s var(--ease), background 0.45s var(--ease);
}

.nav__cta:hover, .nav__cta:active {
  transform: none;
  background: linear-gradient(180deg, #ffffff 0%, #f7faff 42%, #e3ecfd 100%);
  box-shadow:
    0 0 30px 12px rgba(232, 241, 255, 0.42),
    0 12px 26px -10px rgba(8, 40, 80, 0.6),
    0 2px 4px rgba(8, 40, 80, 0.16),
    inset 0 1px 0 #fff,
    inset 0 -3px 6px -2px rgba(150, 178, 222, 0.5);
}

.btn__arrow { flex: none; }

/* ---------------------------------------------------------------
   Nav
   --------------------------------------------------------------- */
.nav {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 60;
  transition: background-color 0.4s, backdrop-filter 0.4s;
}

.nav__inner {
  height: var(--nav-h);
  width: min(var(--wrap), 100%);
  margin-inline: auto;
  padding-inline: var(--gut);
  display: flex;
  align-items: center;
  gap: 28px;
}

.brand { display: inline-flex; align-items: center; gap: 10px; }
.brand__mark { border-radius: 9px; box-shadow: 0 6px 18px -8px rgba(4, 30, 60, 0.8); }
.brand__word {
  font-weight: 800;
  font-size: 21px;
  letter-spacing: -0.04em;
  text-shadow: var(--shadow-text);
}

.nav__links {
  display: flex;
  gap: 30px;
  margin-inline: auto;
  font-size: 15px;
  font-weight: 800;
  color: var(--ink-soft);
}
.nav__links a { position: relative; transition: color 0.25s; }
.nav__links a:hover { color: #fff; }
/* Whichever page you're on. The links sit at --ink-soft, so going full white
   is the whole signal — an underline would be a second one, and there are
   only three items up there to tell apart. */
.nav__links a[aria-current="page"] { color: #fff; }

.nav__burger {
  display: none;
  width: 42px;
  height: 42px;
  margin-left: auto;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: transparent;
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
}
.nav__burger span {
  width: 17px;
  height: 1.5px;
  background: #fff;
  border-radius: 2px;
  transition: transform 0.35s var(--ease), opacity 0.2s;
}
.nav__burger[aria-expanded="true"] span:first-child { transform: translateY(3.25px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:last-child { transform: translateY(-3.25px) rotate(-45deg); }

.nav__drawer {
  display: none;
  flex-direction: column;
  gap: 4px;
  padding: 8px var(--gut) 26px;
  background: rgba(16, 76, 130, 0.55);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.nav__drawer a { padding: 13px 0; font-size: 18px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
.nav__drawer .btn { margin-top: 16px; border-bottom: 0; }

@media (max-width: 860px) {
  .nav__links, .nav__cta { display: none; }
  .nav__burger { display: flex; }
  .nav__drawer.is-open { display: flex; }
}

/* ---------------------------------------------------------------
   Hero
   --------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: calc(var(--nav-h) + 40px) var(--gut) 90px;
  text-align: center;
}

.hero__inner {
  width: min(760px, 100%);
  /* Grid items default to `min-width: auto`, which floors them at their
     min-content width — and `width` cannot win against that floor. The
     waitlist pill's label is `white-space: nowrap`, so its min-content is
     ~400px; on a 375px phone that dragged this box out to 403px and took the
     whole document with it, clipping the headline, the second proof pill and
     the button's own label off the right edge. Explicit 0 lets `width`
     actually apply. */
  min-width: 0;
  position: relative;
  z-index: 1; /* above .hero__scene */
}

/* ---------------------------------------------------------------
   Hero scenery — wash + clouds + earth, hero only

   The whole of the page's blue lives in here. `overflow: hidden` is
   what guarantees it stops at the hero's edge, and the mask below is
   what stops it stopping on a ruled line.

   Stack inside .hero, back to front: 0 this, 1 .hero__inner, 2
   .floaters.
   --------------------------------------------------------------- */
.hero__scene {
  position: absolute;
  inset: 0;
  /* Was -2, to sit behind the emoji and in front of the page-wide canvas
     sky. With the sky gone that would have left it relying on painting
     behind .hero but above the propagated body background — true today,
     and false the moment anything gives html or .hero a background of
     its own. Everything in the hero is positive and explicit now. */
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Without this the scene ends on a ruled line at the hero's bottom edge,
     which reads as a rendering bug rather than a horizon. The fade belongs
     here, on the hero-sized box — the earth's own box is mostly below the
     fold, so a percentage fade on it would never reach the seam. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 84%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 84%, transparent 100%);
}

/* The hero's sky. This used to be a semi-opaque calming layer over the
   page-wide FBM canvas; with that gone it is the background itself, so it
   is fully opaque — at 0.82 it let the white page through and went pale. */
.hero__wash {
  position: absolute;
  inset: -1px;
  background:
    radial-gradient(128% 74% at 50% 116%, rgba(216, 240, 255, 0.6), rgba(216, 240, 255, 0) 62%),
    linear-gradient(180deg, #6cb0ed 0%, #8ac5f4 48%, #a9d8f8 100%);
}

/* ---- cartoon clouds ---- */
/* The <symbol> definitions carry no geometry of their own — park the defs
   sprite out of flow so it can't affect layout. */
.hero__cloud-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.hero__cloud {
  /* Per-cloud depth. Both are set on the modifiers below rather than each
     one rewriting the whole `filter`, which is how the shadow used to get
     dropped from half of them by accident. */
  --haze: 2px;
  --lift: 0.1;

  position: absolute;
  height: auto;
  overflow: visible;
  /* Every cloud carries some blur now. The crisp silhouette was the thing
     holding them in the foreground — a hard edge on a circle-pile reads as
     a cut-out, and vapour hasn't got one. The shadow is warm and barely
     there; the old cool blue one sat them on top of the sky like stickers. */
  filter: blur(var(--haze)) drop-shadow(0 14px 26px rgba(132, 106, 84, var(--lift)));
  /* No will-change here: promoting several big layers inside a masked
     parent is what drags sibling content into the masked group. */
  animation: hero-drift linear infinite;
}

/* Atmospheric perspective, carried by size and haze rather than by fading the
   near clouds out. Opacity is the one lever that can't be used freely here:
   a see-through cloud mixes the sky's blue back into the fill and cancels the
   warmth, so the near ones stay nearly solid and read as distant because they
   are smaller and softer. The far ones can thin out — at that range
   transparency IS the haze, and their going cool is what distance looks
   like. */
.hero__cloud--a { width: 32vw; left: -7vw;  top: 11%; opacity: 0.93; --haze: 2px;   --lift: 0.09; animation-duration: 82s; }
.hero__cloud--b { width: 21vw; left: 70vw;  top: 6%;  opacity: 0.88; --haze: 2.5px; --lift: 0.07; animation-duration: 104s; animation-delay: -30s; }
/* Kept off the centre line — a big soft cloud directly behind the H1 reads as
   a smudge rather than scenery, so the hazy ones sit down near the horizon. */
.hero__cloud--c { width: 36vw; left: 50vw;  top: 71%; opacity: 0.4;  --haze: 7px;   --lift: 0;    animation-duration: 128s; animation-delay: -60s; }
.hero__cloud--d { width: 17vw; left: -3vw;  top: 52%; opacity: 0.8;  --haze: 3px;   --lift: 0.05; animation-duration: 96s;  animation-delay: -15s; }
.hero__cloud--e { width: 26vw; left: 74vw;  top: 44%; opacity: 0.6;  --haze: 5px;   --lift: 0.03; animation-duration: 140s; animation-delay: -80s; }
.hero__cloud--f { width: 30vw; left: 13vw;  top: 66%; opacity: 0.36; --haze: 7px;   --lift: 0;    animation-duration: 116s; animation-delay: -45s; }

/* A slow round trip rather than a one-way pan — it loops seamlessly without
   having to wrap each cloud back around the viewport. */
@keyframes hero-drift {
  0%   { transform: translate3d(-3vw, 0, 0); }
  50%  { transform: translate3d(4vw, -1.2vh, 0); }
  100% { transform: translate3d(-3vw, 0, 0); }
}

/* ---- the earth ---- */
/* Deliberately far wider than the viewport and pushed past the bottom edge:
   only the top arc shows, so the curve reads as a horizon rather than a ball. */
.hero__earth {
  position: absolute;
  left: 50%;
  /* Sits high enough that a real slice of the globe — oceans, a couple of
     landmasses — is in frame, not just a thin rim at the bottom edge. */
  top: 60%;
  /* max(), not min() — the globe has to outgrow the viewport, otherwise the
     arc curves too tightly and reads as a ball rather than a horizon. */
  width: max(1720px, 198vw);
  aspect-ratio: 1 / 1;
  height: auto;
  transform: translateX(-50%);
  filter: drop-shadow(0 -10px 50px rgba(140, 210, 255, 0.3));
}

@media (max-width: 720px) {
  .hero__earth { width: max(1280px, 258vw); top: 62%; }
  /* Fewer, bigger clouds on a narrow screen — six at vw widths just crowds
     the headline. Width only: the haze and opacity each one carries are what
     hold it back in the scene, and they should survive the breakpoint. */
  .hero__cloud--a { width: 54vw; top: 9%; }
  .hero__cloud--b { width: 38vw; left: 64vw; }
  .hero__cloud--d { width: 32vw; top: 56%; }
  .hero__cloud--e { width: 42vw; left: 62vw; }
  .hero__cloud--c, .hero__cloud--f { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__cloud { animation: none; }
}

.hero__title {
  font-size: clamp(46px, 9.4vw, 116px);
  line-height: 0.95;
  letter-spacing: -0.045em;
  /* drop-shadow, not text-shadow: the words are clipped by their masks and
     a text-shadow would get sliced off at the mask edge. */
  filter: drop-shadow(0 8px 34px rgba(8, 42, 82, 0.38));
  margin-bottom: 26px;
}

/* Word-level entrance — JS wraps each word in a .word > span */
.hero__title .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em; /* room for descenders inside the mask */
}
.hero__title .word > span {
  display: inline-block;
  transform: translateY(110%);
  transition: transform 1s var(--ease);
}
.hero__title.is-in .word > span { transform: translateY(0); }

/* Highlighted words in the headline — hand-drawn circle + marker underline.
   These sit outside the .word mask so their strokes can overflow the glyphs. */
.hero__title .mark-circle,
.hero__title .mark-underline {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.hero__title .mark-circle::before {
  content: "";
  position: absolute;
  left: -0.18em;
  right: -0.18em;
  top: -0.16em;
  bottom: -0.12em;
  z-index: 1;
  pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 110' preserveAspectRatio='none'%3E%3Cpath d='M118 7 C58 3 20 26 14 52 C9 76 52 100 120 103 C182 105 214 78 206 48 C199 22 156 8 96 10' fill='none' stroke='%23ffc93c' stroke-width='5.5' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
}
.hero__title .mark-underline::after {
  content: "";
  position: absolute;
  left: -0.03em;
  right: -0.03em;
  bottom: -0.05em;
  height: 0.26em;
  pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 26' preserveAspectRatio='none'%3E%3Cpath d='M8 16 C80 6 150 22 292 10' fill='none' stroke='%23ffc93c' stroke-width='10' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
}

/* Store badges — hidden while the app is pre-launch and the waitlist button
   below stands in for them. The rest of the rule is kept so restoring them is
   a one-line change: set display back to inline-block. */
.hero__badge {
  display: none;
  margin-bottom: 30px;
  border-radius: 7px;
  filter: drop-shadow(0 6px 22px rgba(8, 42, 82, 0.45));
  transition: transform 0.35s var(--ease);
}
.hero__badge:hover { transform: translateY(-2px); }
.hero__badge img { display: block; width: 150px; height: auto; }

.hero__sub {
  font-size: clamp(16px, 1.9vw, 20px);
  color: rgba(255, 255, 255, 0.94);
  max-width: 46ch;
  margin: 0 auto 22px;
  text-shadow: 0 1px 3px rgba(8, 42, 82, 0.35), 0 2px 22px rgba(8, 42, 82, 0.4);
}

/* Proof pills, sat between the promise and the store badges. Solid white,
   unlike the frosted .live pill above the headline — that one is part of the
   scene and lets clouds through; these are claims, and they need to hold
   their contrast wherever the sky happens to be behind them. */
.proof {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0 auto 34px;
}
.proof__pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 18px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  color: #14568f;
  font-weight: 800;
  font-size: clamp(13.5px, 1.15vw, 15px);
  letter-spacing: -0.02em;
  white-space: nowrap;
  box-shadow: 0 14px 30px -18px rgba(6, 34, 66, 0.8);
}
/* Both are emoji, so both need the emoji stack — Gilroy has no glyph for
   either, and what a bare fallback picks is the browser's guess. */
.proof__globe,
.proof__stars {
  font-family: var(--font-emoji);
  line-height: 1;
}
.proof__globe { font-size: 1.15em; }
.proof__stars {
  font-size: 0.82em;
  letter-spacing: 0.06em;
}

.hero__cta {
  display: flex;
  gap: 12px;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}

/* Waitlist button — a black pill that plays off the black section rules and
   community band rather than the blue sky, so it reads as the one hard,
   pressable thing in a soft scene. Left-weighted padding leaves room for the
   avatars to sit inside the rounded end. */
.waitlist {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  /* Never wider than what it is sitting in. Without this the pill is free to
     size to its content and push the page out sideways. */
  max-width: 100%;
  min-width: 0;
  padding: 8px 30px 8px 10px;
  border-radius: 999px;
  background: #0b0b0d;
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 18px 40px -20px rgba(0, 0, 0, 0.85);
  transition: transform 0.35s var(--ease), border-color 0.35s var(--ease),
    box-shadow 0.35s var(--ease);
}
.waitlist:hover {
  transform: translateY(-2px);
  border-color: rgba(255, 255, 255, 0.32);
  box-shadow: 0 26px 52px -20px rgba(0, 0, 0, 0.9);
}

/* The three avatars overlap left-to-right; each carries a ring in the pill's
   own black so the overlaps read as separate coins rather than one smear. */
.waitlist__avatars {
  display: inline-flex;
  flex-shrink: 0;
}
.waitlist__avatar {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  box-shadow: 0 0 0 3px #0b0b0d;
}
.waitlist__avatar + .waitlist__avatar { margin-left: -14px; }

.waitlist__label {
  font-size: 18px;
  font-weight: 300;
  letter-spacing: -0.01em;
  color: rgba(255, 255, 255, 0.86);
  white-space: nowrap;
}

/* White variant — the wall panel is dark, so the black pill would all but
   disappear into it. Flips the same shape to a white pill with dark ink;
   the avatar rings switch to white too, so the coins still separate from
   whatever sits behind them instead of the button's own black bleeding
   through. Scoped with a modifier class rather than `.wall .waitlist` so it
   stays a deliberate opt-in if the button ever lands on another dark panel. */
.waitlist--white {
  background: #ffffff;
  border-color: rgba(13, 43, 74, 0.1);
  box-shadow: 0 18px 40px -20px rgba(0, 0, 0, 0.5);
}
.waitlist--white:hover {
  border-color: rgba(13, 43, 74, 0.18);
  box-shadow: 0 26px 52px -20px rgba(0, 0, 0, 0.55);
}
.waitlist--white .waitlist__avatar { box-shadow: 0 0 0 3px #ffffff; }
.waitlist--white .waitlist__label { color: rgba(13, 43, 74, 0.82); }

/* Phones: "Join 10k Besties on the Waitlist" is a long line to keep on one
   row inside a pill that also carries three avatars. At 18px nowrap it needs
   ~400px of the ~335px a 375px phone actually has. Smaller, tighter, and
   allowed to wrap — the pill grows a line instead of the page growing a
   sideways scroll. */
@media (max-width: 560px) {
  .waitlist {
    gap: 11px;
    padding: 8px 20px 8px 9px;
  }
  .waitlist__label {
    font-size: 15.5px;
    white-space: normal;
    text-align: left;
  }
}

/* Short windows: the hero centres a stack that is taller than a laptop
   viewport, which pushes the live count below the fold.
   Tighten until the whole hero fits on one screen. */
@media (max-height: 800px) and (min-width: 620px) {
  .hero { padding-top: calc(var(--nav-h) + 18px); padding-bottom: 46px; }
  .hero__title { font-size: clamp(42px, 7.4vw, 88px); margin-bottom: 18px; }
  .hero__badge { margin-bottom: 22px; }
  .hero__badge img { width: 136px; }
  .hero__sub { font-size: clamp(15px, 1.6vw, 18px); margin-bottom: 16px; }
  .proof { margin-bottom: 28px; }
  .proof__pill { height: 38px; }
  .live { margin-top: 20px; margin-bottom: 16px; }
}

@media (max-height: 660px) and (min-width: 620px) {
  .hero { padding-top: calc(var(--nav-h) + 10px); padding-bottom: 30px; }
  .hero__title { font-size: clamp(36px, 6.2vw, 68px); margin-bottom: 14px; }
  .hero__badge { margin-bottom: 16px; }
  .hero__badge img { width: 122px; }
  .hero__sub { font-size: 15px; margin-bottom: 12px; }
  .proof { margin-bottom: 20px; }
  .proof__pill { height: 34px; padding: 0 14px; font-size: 13px; }
  .proof__stars { letter-spacing: 0.04em; }
  .btn { height: 46px; }
  .live { margin-top: 16px; margin-bottom: 12px; padding: 9px 17px 9px 14px; font-size: 14px; }
}

/* ---------------------------------------------------------------
   Live traveller count
   --------------------------------------------------------------- */
.live {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 28px;
  margin-bottom: 34px;
  padding: 11px 20px 11px 17px;
  border-radius: 999px;
  /* Frosted white: enough haze to lift the pill off the sky, still sheer
     enough that the clouds drifting behind it read through. */
  background: rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.45);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  white-space: nowrap;
  text-shadow: var(--shadow-copy);
}
.live__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #22c55e;
  animation: live-pulse 2.2s var(--ease) infinite;
}
.live__count {
  font-weight: 800;
  letter-spacing: -0.02em;
  /* tabular figures, so the pill doesn't twitch as the number ticks */
  font-variant-numeric: tabular-nums;
}
.live__label { color: #fff; }
.live__globe { font-size: 16px; line-height: 1; }

@keyframes live-pulse {
  0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5); }
  70% { box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); }
  100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
}

@media (max-width: 420px) {
  .live { font-size: 13.5px; gap: 7px; padding: 10px 16px 10px 14px; }
}

/* ---------------------------------------------------------------
   Community — black band over a looping rail of trip cards
   The band is the one hard, opaque edge on the page: it gives the
   photography something to sit against instead of floating in sky.
   --------------------------------------------------------------- */
/* The hero → community seam. The band below is already black, so this reads
   as the band's own top edge landing on the faded-white bottom of the hero —
   the same rule as every other seam, just flush against matching colour. */
.community { border-top: var(--rule); }

.community__band {
  padding: 12px var(--gut);
  background: #000;
  text-align: center;
}
.community__band p {
  font-weight: 800;
  font-size: clamp(12.5px, 1.25vw, 15.5px);
  letter-spacing: -0.015em;
  line-height: 1.3;
  color: #fff;
}

/* The vertical padding is headroom for the hover lift and the drop
   shadows — overflow clips to the padding box, so without it the cards
   get their shadows shaved off. */
.rail {
  overflow: hidden;
  padding: clamp(26px, 3.2vw, 44px) 0 clamp(36px, 4.2vw, 60px);
}
/* No padding on the track: it would land inside `max-content` and throw
   the -50% wrap off by half of it. */
.rail__track {
  display: flex;
  width: max-content;
  animation: rail 66s linear infinite;
}
.rail:hover .rail__track,
.rail:focus-within .rail__track { animation-play-state: paused; }

/* Half the track is the duplicate, so -50% lands exactly on the seam. */
@keyframes rail {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* --- one trip card ------------------------------------------------
   Laid out like the reference card: a square photo with a label row
   across the top, the trip name bottom-left and the go button
   bottom-right. --go drives the button and the gap the title keeps
   clear of it, so the two can never drift apart.
   ------------------------------------------------------------------ */
.tcard {
  --go: clamp(36px, 3.2vw, 48px);
  --pad: clamp(13px, 1.35vw, 20px);

  position: relative;
  flex: none;
  width: clamp(196px, 19vw, 272px);
  aspect-ratio: 1;
  /* Margin, not the track's `gap`: gap puts n-1 gaps across 2n cards, so
     the two halves measure differently and the loop stutters on the wrap. */
  margin-inline-end: clamp(12px, 1.5vw, 22px);
  border-radius: clamp(18px, 1.7vw, 24px);
  overflow: hidden;
  background: #0a2749;
  transform: rotate(var(--rot, 0deg));
  box-shadow: 0 26px 52px -26px rgba(4, 26, 54, 0.9);
  transition: transform 0.55s var(--ease), box-shadow 0.55s var(--ease);
}
.tcard:hover {
  transform: rotate(0deg) translateY(-8px) scale(1.035);
  box-shadow: 0 38px 74px -28px rgba(4, 26, 54, 0.95);
}

/* Two layers: the photo, and a tint underneath it. A photo that hasn't
   been dropped into media/trips/ yet simply doesn't paint, and the card
   reads as a coloured tile rather than a broken image. */
.tcard__photo {
  position: absolute;
  inset: 0;
  /* background-image is set per card by js/modules/trips.js — see the note
     there on why it can't come through a custom property. */
  background-color: #14304f;
  background-size: cover;
  background-position: center;
  transform: scale(1.02);
  transition: transform 0.8s var(--ease);
}
.tcard:hover .tcard__photo { transform: scale(1.09); }

/* Darkened at both ends, the way the reference card is: the top label row
   needs as much help off the photo as the title does. ::after purely so the
   card stays one element in the markup. */
.tcard::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(3, 16, 34, 0.5), rgba(3, 16, 34, 0) 30%),
    linear-gradient(to top, rgba(3, 16, 34, 0.82) 2%, rgba(3, 16, 34, 0.3) 34%, rgba(3, 16, 34, 0) 62%);
  pointer-events: none;
}
.tcard__tag,
.tcard__count,
.tcard__title,
.tcard__go { position: absolute; z-index: 2; }

/* The label row. Both sides share a size so they read as one line. */
.tcard__tag,
.tcard__count {
  top: var(--pad);
  font-weight: 800;
  font-size: clamp(11.5px, 0.95vw, 13.5px);
  letter-spacing: -0.01em;
  line-height: 1;
  color: #fff;
  text-shadow: 0 1px 10px rgba(3, 16, 34, 0.65);
}
.tcard__tag {
  left: var(--pad);
  max-width: calc(100% - var(--pad) * 2 - 58px);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.tcard__count {
  right: var(--pad);
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.tcard__count svg { width: 1.2em; height: 1.2em; opacity: 0.92; }

.tcard__title {
  left: var(--pad);
  /* Held clear of the button, whatever size --go resolves to. */
  right: calc(var(--pad) + var(--go) + 12px);
  bottom: var(--pad);
  font-size: clamp(17px, 1.55vw, 23px);
  line-height: 1.06;
  /* Explicit, like the label row above it: the card is a dark photo tile
     wherever it sits, so it can't take its ink from the page. */
  color: #fff;
  text-shadow: 0 2px 14px rgba(3, 16, 34, 0.55);
}

.tcard__go {
  right: var(--pad);
  bottom: var(--pad);
  display: grid;
  place-items: center;
  width: var(--go);
  height: var(--go);
  border-radius: 50%;
  background: #fff;
  color: #14396a;
  box-shadow: 0 8px 20px -10px rgba(4, 26, 54, 0.9);
  transition: transform 0.45s var(--ease);
}
.tcard__go svg { width: 42%; height: 42%; }
.tcard:hover .tcard__go { transform: scale(1.12); }

@media (max-width: 560px) {
  .tcard { --go: 34px; --pad: 12px; }
}

@media (max-width: 620px) {
  /* Hand-placed line breaks are for wide screens; let phones wrap naturally. */
  .h2 br { display: none; }
}

/* ---------------------------------------------------------------
   Quotes
   --------------------------------------------------------------- */
.quotes { margin-top: 8px; }

/* The disclosure line under the section heading. Small and quiet, but it has
   to be there: it is what makes the quotes below "things people said" rather
   than "reviews", which is a distinction with legal weight. */
.quotes__note {
  margin: -18px 0 34px;
  font-size: 14.5px;
  color: var(--ink-faint);
}

/* A drawn card rather than tinted glass: there is nothing behind it to tint
   now, and a backdrop-filter over flat white is pure cost for no effect. */
.quote {
  margin: 0;
  padding: 32px 30px;
  border-radius: var(--radius);
  border: 1px solid var(--panel-line);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  display: flex;
  flex-direction: column;
  gap: 18px;
  transition: border-color 0.4s, box-shadow 0.5s var(--ease), transform 0.5s var(--ease);
}
.quote:hover {
  transform: translateY(-3px);
  border-color: rgba(13, 43, 74, 0.2);
  box-shadow: 0 30px 60px -30px rgba(13, 43, 74, 0.42);
}
/* Emoji stack, same as the hero pills — Gilroy has no star glyph, and a
   bare fallback is whatever the browser reaches for. */
.quote__stars {
  font-family: var(--font-emoji);
  font-size: 12px;
  letter-spacing: 0.06em;
  line-height: 1;
}
.quote blockquote {
  margin: 0;
  font-size: 18px;
  line-height: 1.5;
  letter-spacing: -0.015em;
}
.quote blockquote::before { content: "“"; }
.quote blockquote::after { content: "”"; }
/* margin-top:auto pins the attribution to the bottom, so the names line up
   across a row even when the quotes run to different lengths. */
.quote figcaption { display: flex; flex-direction: column; gap: 3px; margin-top: auto; padding-top: 4px; }
.quote strong { font-weight: 800; font-size: 15px; letter-spacing: -0.02em; }
.quote span { font-size: 13px; color: var(--ink-faint); }

/* Name row: name + verified badge + flag/country, all on one baseline and
   allowed to wrap onto a second line on a narrow card. */
.quote__name { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.quote__badge { width: 16px; height: 16px; flex: none; }
/* Flag + country reads as secondary, so it borrows the muted caption colour
   and drops the bold it would otherwise inherit from <strong>. */
.quote span.quote__flag { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; color: var(--ink-faint); }
/* Hairline ring so a white flag (Japan) still reads on the light card. */
.quote__flag-img {
  width: 22px;
  height: auto;
  aspect-ratio: 3 / 2;
  border-radius: 3px;
  overflow: hidden;
  flex: none;
  box-shadow: 0 0 0 1px rgba(13, 43, 74, 0.15);
}
.quote span.quote__verified { font-weight: 700; color: #1e7fd1; letter-spacing: -0.01em; }
/* The <symbol> sprite carries no geometry — keep it out of flow. */
.quote__flag-defs { position: absolute; width: 0; height: 0; }

/* Small round profile photo sitting inline, right before the name. */
.quote__avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  object-position: 46% 42%;
  flex: none;
  box-shadow: 0 0 0 1px rgba(13, 43, 74, 0.12);
}

/* ---------------------------------------------------------------
   FAQ — read as a chat: the question is what you'd type, the answer
   comes back as a reply bubble.
   --------------------------------------------------------------- */
.faq__stamp {
  font-size: 16px;
  color: var(--ink-soft);
  margin: 0 0 26px;
}
.faq__stamp a {
  color: var(--brand-deep);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.faq__stamp a:hover { color: var(--ink); }

.faq__item + .faq__item { margin-top: 10px; }

.faq__item summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 4px 0;
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::marker { content: ""; }

/* The typed message. It reads as the outgoing bubble in a chat, which on
   white means the brand blue — the old tinted glass had nothing left to
   tint, and a white-on-white bubble has no shape at all. */
.faq__q {
  position: relative;
  padding: 13px 20px;
  border-radius: 16px;
  background: var(--brand);
  border: 1px solid transparent;
  color: #fff;
  font-size: clamp(15.5px, 1.7vw, 18px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.35;
  transition: background 0.35s var(--ease), border-color 0.35s, transform 0.4s var(--ease);
}
.faq__item summary:hover .faq__q {
  background: #1a72bd;
  transform: translateY(-1px);
}
.faq__item[open] .faq__q {
  background: var(--brand-deep);
  box-shadow: var(--shadow-panel);
}

/* Sticker accents — deliberately a touch off-square, like a real one */
.faq__sticker {
  position: absolute;
  bottom: calc(100% - 12px);
  font-size: 19px;
  line-height: 1;
  pointer-events: none;
  filter: drop-shadow(0 4px 10px rgba(6, 32, 64, 0.45));
}
.faq__sticker--right { right: 2px; transform: rotate(7deg); }
.faq__sticker--left { left: 2px; transform: rotate(-4deg); }

/* Plus that becomes a minus */
.faq__toggle {
  position: relative;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  opacity: 0.72;
  transition: opacity 0.3s, transform 0.45s var(--ease);
}
.faq__toggle::before,
.faq__toggle::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transform: translateY(-50%);
  transition: transform 0.45s var(--ease), opacity 0.35s;
}
.faq__toggle::after { transform: translateY(-50%) rotate(90deg); }
.faq__item summary:hover .faq__toggle { opacity: 1; }
.faq__item[open] .faq__toggle { opacity: 1; transform: rotate(180deg); }
.faq__item[open] .faq__toggle::after { transform: translateY(-50%) rotate(90deg) scaleX(0); }

/* JS animates the height; this keeps it sane with JS off */
.faq__body { overflow: hidden; }
.faq__body p {
  position: relative;
  width: fit-content;
  max-width: 46ch;
  margin: 8px 0 4px clamp(28px, 6vw, 64px);
  padding: 13px 20px;
  border-radius: 18px 18px 18px 6px;
  /* The incoming bubble. It was white against the blue page; now the page is
     white, so it takes the panel tint and the question takes the colour. */
  background: var(--panel);
  border: 1px solid var(--panel-line);
  color: var(--ink-soft);
  font-size: 15.5px;
  line-height: 1.55;
}

/* The reply lands a beat after the panel has finished opening */
.faq__item[open] .faq__body p { animation: faq-pop 0.5s var(--ease) both; }

@keyframes faq-pop {
  from { opacity: 0; transform: translateY(8px) scale(0.97); transform-origin: 0 100%; }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 560px) {
  .faq__q { font-size: 15.5px; }
  .faq__body p { max-width: none; margin-left: 22px; }
}

/* ---------------------------------------------------------------
   Photo wall — the manifesto panel

   Two rows of tiles drifting in opposite directions, sunk under a veil
   heavy enough that the wall reads as texture and the copy reads as
   content. The tiles are decorative: everything here is sized so the
   words stay the brightest thing on screen.
   --------------------------------------------------------------- */
.wall {
  /* Black region, so it needs the white-on-dark ink set — but not the blue
     regions' glass panels, which need a sky to tint. */
  --ink: #ffffff;
  --ink-soft: rgba(255, 255, 255, 0.82);
  --ink-faint: rgba(255, 255, 255, 0.58);

  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: #000;
  color: var(--ink);
  border-top: var(--rule);
  border-bottom: var(--rule);
  /* Rows and copy share one grid cell rather than the copy being absolutely
     positioned over them. Absolute copy contributes no height, so on a narrow
     screen — where the lines wrap two and three deep and one row is hidden —
     the words ran straight out of the black band. Stacked this way the panel
     is always as tall as whichever layer is taller. */
  display: grid;
  grid-template-columns: 100%;
  /* Tight: the wall should fill the frame the way the reference does. Deep
     padding here leaves a dead black band top and bottom and the photos
     stop reading as a wall. */
  padding: clamp(26px, 3.2vw, 48px) 0;
}

/* The rows are laid out as normal flow and then the copy is placed over
   them, so the section's height comes from the photos rather than from a
   number that has to be kept in step with them. */
.wall__rows {
  grid-area: 1 / 1;
  align-self: center;
  display: flex;
  flex-direction: column;
  gap: clamp(9px, 0.85vw, 14px);
  /* One filter on the static parent rather than on each animated track:
     real photography lands far brighter than the reference, and darkening
     here costs one composited layer instead of sixteen. Kept gentle — the
     veil below does the heavy lifting, and stacking both is what turns the
     wall into an unreadable black field. */
  filter: brightness(0.78) saturate(0.9);
}

.wall__row { overflow: hidden; }

.wall__track {
  display: flex;
  width: max-content;
  animation: wall-drift 78s linear infinite;
}
/* Opposite directions. Half of each track is the duplicate, so 50% lands
   exactly on the seam whichever way it travels. */
.wall__track--left { animation-direction: normal; }
.wall__track--right { animation-direction: reverse; }

@keyframes wall-drift {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Photo frames. Geometry is measured off the reference: ~244px wide by
   ~382px tall with a ~14px gutter, which is 15.7vw and a 16:25 portrait.
   Uniform, not varied — the reference rows are ruled straight across, and
   any photo dropped in is cover-fitted to this box, so a whole folder of
   mixed-ratio shots still lands as one clean grid. */
.wall__tile {
  width: clamp(158px, 15.7vw, 268px);
  aspect-ratio: 16 / 25;
  margin-right: clamp(10px, 0.95vw, 16px);
  flex: none;
  /* The reference's own frame: a 0.5px hairline at a 14px radius. It is what
     keeps a dark photo from dissolving into the black behind it. */
  border: 0.5px solid rgb(98, 100, 108);
  border-radius: 14px;
}

/* The image container, 12px inside the frame's 14px. */
.wall__img {
  width: 100%;
  height: 100%;
  border-radius: 12px;
  background-color: #10151d;
  background-size: cover;
  background-position: center;
}

/* One rule per frame: the photo over a tint picked to echo that shot's
   light. The tint is what you see until the .jpg exists in media/wall/ — a
   missing file doesn't paint, so the frame stays a colour instead of
   breaking. Paths are relative to this stylesheet, hence the ../
   (media/ sits next to css/, not inside it).

   Filenames are just 1–16 — top row left-to-right (1–8), then bottom row
   left-to-right (9–16) — so dropping in a numbered batch of photos needs no
   renaming. The class names (--dinner, --loft…) are unchanged; they're only
   hooks for this rule and the drift animation, not filenames. To change a
   placeholder colour, edit its gradient here. */
.wall__tile--dinner  .wall__img { background-image: url("../media/wall/1.jpg"),  linear-gradient(200deg, #e9b478, #6b432a); }
.wall__tile--loft    .wall__img { background-image: url("../media/wall/2.jpg"),  linear-gradient(200deg, #b8c8d8, #3c4a5c); }
.wall__tile--sunset  .wall__img { background-image: url("../media/wall/3.jpg"),  linear-gradient(200deg, #f5a05c, #4a2f4e); }
.wall__tile--party   .wall__img { background-image: url("../media/wall/4.jpg"),  linear-gradient(200deg, #f0cf8e, #55432c); }
.wall__tile--bar     .wall__img { background-image: url("../media/wall/5.jpg"),  linear-gradient(200deg, #a3b2c2, #333c48); }
.wall__tile--club    .wall__img { background-image: url("../media/wall/6.jpg"),  linear-gradient(200deg, #a184e8, #2c2350); }
.wall__tile--city    .wall__img { background-image: url("../media/wall/7.jpg"),  linear-gradient(200deg, #e08a4e, #2a2d3c); }
.wall__tile--drive   .wall__img { background-image: url("../media/wall/8.jpg"),  linear-gradient(200deg, #d9614c, #2b2220); }
.wall__tile--rooftop .wall__img { background-image: url("../media/wall/9.jpg"),  linear-gradient(200deg, #f5b483, #3a4257); }
.wall__tile--pool    .wall__img { background-image: url("../media/wall/10.jpg"), linear-gradient(200deg, #6fc4dc, #1f4256); }
.wall__tile--table   .wall__img { background-image: url("../media/wall/11.jpg"), linear-gradient(200deg, #e6bd84, #4a3520); }
.wall__tile--marina  .wall__img { background-image: url("../media/wall/12.jpg"), linear-gradient(200deg, #93b8d8, #2a3a4c); }
.wall__tile--ridge   .wall__img { background-image: url("../media/wall/13.jpg"), linear-gradient(200deg, #adbccd, #2b3542); }
.wall__tile--terrace .wall__img { background-image: url("../media/wall/14.jpg"), linear-gradient(200deg, #a4c184, #2d3a2a); }
.wall__tile--camp    .wall__img { background-image: url("../media/wall/15.jpg"), linear-gradient(200deg, #efa863, #3a281d); }
.wall__tile--market  .wall__img { background-image: url("../media/wall/16.jpg"), linear-gradient(200deg, #ec8577, #35222a); }

/* WebP for the browsers that take it, JPEG above for the rest. Together
   they take the wall from 2,442 KB to 688 KB — the single largest saving
   on the site.

   Wrapped in @supports rather than declared straight: a browser that does
   not understand image-set() drops the whole `background-image`
   declaration, gradient and all, and the wall would render as sixteen
   empty boxes. The rules above stay as the floor. */
@supports (background-image: image-set(url("x.webp") type("image/webp"))) {
  .wall__tile--dinner  .wall__img { background-image: image-set(url("../media/wall/1.webp") type("image/webp"), url("../media/wall/1.jpg") type("image/jpeg")), linear-gradient(200deg, #e9b478, #6b432a); }
  .wall__tile--loft    .wall__img { background-image: image-set(url("../media/wall/2.webp") type("image/webp"), url("../media/wall/2.jpg") type("image/jpeg")), linear-gradient(200deg, #b8c8d8, #3c4a5c); }
  .wall__tile--sunset  .wall__img { background-image: image-set(url("../media/wall/3.webp") type("image/webp"), url("../media/wall/3.jpg") type("image/jpeg")), linear-gradient(200deg, #f5a05c, #4a2f4e); }
  .wall__tile--party   .wall__img { background-image: image-set(url("../media/wall/4.webp") type("image/webp"), url("../media/wall/4.jpg") type("image/jpeg")), linear-gradient(200deg, #f0cf8e, #55432c); }
  .wall__tile--bar     .wall__img { background-image: image-set(url("../media/wall/5.webp") type("image/webp"), url("../media/wall/5.jpg") type("image/jpeg")), linear-gradient(200deg, #a3b2c2, #333c48); }
  .wall__tile--club    .wall__img { background-image: image-set(url("../media/wall/6.webp") type("image/webp"), url("../media/wall/6.jpg") type("image/jpeg")), linear-gradient(200deg, #a184e8, #2c2350); }
  .wall__tile--city    .wall__img { background-image: image-set(url("../media/wall/7.webp") type("image/webp"), url("../media/wall/7.jpg") type("image/jpeg")), linear-gradient(200deg, #e08a4e, #2a2d3c); }
  .wall__tile--drive   .wall__img { background-image: image-set(url("../media/wall/8.webp") type("image/webp"), url("../media/wall/8.jpg") type("image/jpeg")), linear-gradient(200deg, #d9614c, #2b2220); }
  .wall__tile--rooftop .wall__img { background-image: image-set(url("../media/wall/9.webp") type("image/webp"), url("../media/wall/9.jpg") type("image/jpeg")), linear-gradient(200deg, #f5b483, #3a4257); }
  .wall__tile--pool    .wall__img { background-image: image-set(url("../media/wall/10.webp") type("image/webp"), url("../media/wall/10.jpg") type("image/jpeg")), linear-gradient(200deg, #6fc4dc, #1f4256); }
  .wall__tile--table   .wall__img { background-image: image-set(url("../media/wall/11.webp") type("image/webp"), url("../media/wall/11.jpg") type("image/jpeg")), linear-gradient(200deg, #e6bd84, #4a3520); }
  .wall__tile--marina  .wall__img { background-image: image-set(url("../media/wall/12.webp") type("image/webp"), url("../media/wall/12.jpg") type("image/jpeg")), linear-gradient(200deg, #93b8d8, #2a3a4c); }
  .wall__tile--ridge   .wall__img { background-image: image-set(url("../media/wall/13.webp") type("image/webp"), url("../media/wall/13.jpg") type("image/jpeg")), linear-gradient(200deg, #adbccd, #2b3542); }
  .wall__tile--terrace .wall__img { background-image: image-set(url("../media/wall/14.webp") type("image/webp"), url("../media/wall/14.jpg") type("image/jpeg")), linear-gradient(200deg, #a4c184, #2d3a2a); }
  .wall__tile--camp    .wall__img { background-image: image-set(url("../media/wall/15.webp") type("image/webp"), url("../media/wall/15.jpg") type("image/jpeg")), linear-gradient(200deg, #efa863, #3a281d); }
  .wall__tile--market  .wall__img { background-image: image-set(url("../media/wall/16.webp") type("image/webp"), url("../media/wall/16.jpg") type("image/jpeg")), linear-gradient(200deg, #ec8577, #35222a); }
}

/* Darkest through the middle, where the copy sits, and lifting towards the
   edges so the wall is still legibly a wall. */
.wall__veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(100% 68% at 50% 50%, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.8) 26%, rgba(0, 0, 0, 0.42) 55%, rgba(0, 0, 0, 0.05) 100%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.12) 24%, rgba(0, 0, 0, 0.12) 76%, rgba(0, 0, 0, 0.66));
}

.wall__copy {
  grid-area: 1 / 1;
  align-self: center;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Vertical padding so the words never sit hard against the panel's edge on
     the narrow screens where the copy is the tallest layer. */
  padding: clamp(26px, 5vw, 44px) var(--gut);
}

.wall__title {
  font-size: clamp(26px, 3.9vw, 54px);
  line-height: 1.08;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin-bottom: clamp(18px, 2.2vw, 30px);
  text-shadow: 0 2px 30px rgba(0, 0, 0, 0.9);
}

.wall__lede {
  font-size: clamp(14px, 1.35vw, 18px);
  line-height: 1.62;
  max-width: 58ch;
  color: var(--ink-soft);
  text-shadow: 0 1px 18px rgba(0, 0, 0, 0.9);
}
.wall__lede + .wall__lede { margin-top: clamp(14px, 1.6vw, 22px); }

.wall__tag {
  margin-top: clamp(20px, 2.4vw, 34px);
  font-weight: 800;
  font-size: clamp(12px, 1.1vw, 14.5px);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-shadow: 0 1px 18px rgba(0, 0, 0, 0.9);
}

@media (max-width: 620px) {
  /* One row is enough on a phone: two rows of tall tiles push the panel
     past a full screen, and the copy is what the section is for. */
  .wall__row:last-child { display: none; }
  .wall__tile { border-radius: 11px; }
  /* The authored line breaks are balanced for a wide screen; at this width
     they strand a word or two on a line of their own, so let the copy find
     its own wrap instead. */
  .wall__lede br { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .wall__track { animation: none; }
}

/* ---------------------------------------------------------------
   CTA + footer
   --------------------------------------------------------------- */
.cta { padding-bottom: clamp(90px, 12vw, 160px); }
.cta__mark {
  margin: 0 auto 28px;
  border-radius: 22px;
  box-shadow: 0 24px 50px -20px rgba(3, 18, 40, 0.9);
}

.footer {
  /* The cta → footer seam. Black rather than the old faint-white hairline, so
     it matches the seams up the page — it lands on the white cta above and the
     blue footer below, and reads on both. */
  border-top: var(--rule);
  padding-top: clamp(48px, 6vw, 76px);
  padding-bottom: 34px;
  /* Opaque from its first pixel. This used to fade in from transparent so it
     could emerge from the night end of the sky; against the white page that
     same fade just read as a grey smear, and a clean edge is the point. */
  background: linear-gradient(180deg, #0c3f74 0, #082a4d 100%);
}
.footer__inner {
  display: grid;
  grid-template-columns: 1.4fr 2fr;
  gap: clamp(34px, 5vw, 70px);
  padding-bottom: 54px;
}
.footer__brand p {
  margin-top: 16px;
  font-size: 14.5px;
  color: var(--ink-faint);
  max-width: 34ch;
}
.footer__social {
  display: flex;
  gap: 12px;
  margin-top: 16px;
}
.footer__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: rgba(255, 255, 255, 0.72);
  transition: color 0.25s, transform 0.35s var(--ease);
}
.footer__social a:hover { color: #fff; transform: translateY(-2px); }
.footer__social svg { width: 100%; height: 100%; }
/* Three groups (Company, Elsewhere, Legal) sized to their content and pushed
   to the right edge of the wrap, so they sit opposite the brand on the left.
   max-content rather than 1fr keeps each column only as wide as its longest
   link, so "Community Guidelines" doesn't stretch its neighbours. */
.footer__cols {
  display: grid;
  grid-template-columns: repeat(3, max-content);
  gap: clamp(40px, 5vw, 80px);
  justify-content: end;
}
.footer__cols h4 {
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.66);
  margin-bottom: 16px;
}
.footer__cols a {
  display: block;
  padding: 6px 0;
  font-size: 15px;
  color: var(--ink-soft);
  transition: color 0.25s, transform 0.35s var(--ease);
}
.footer__cols a:hover { color: #fff; transform: translateX(3px); }

.footer__base {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 13px;
  color: var(--ink-faint);
}

@media (max-width: 780px) {
  .footer__inner { grid-template-columns: 1fr; }
  /* Stacked under the brand now, so the three groups go back to filling the
     width from the left rather than hugging the right edge. Legal's five
     links are the tallest column, so it gets the full-width second row
     rather than squeezing into a third narrow track. */
  .footer__cols {
    grid-template-columns: 1fr 1fr;
    justify-content: stretch;
  }
  .footer__cols div:last-child { grid-column: 1 / -1; }
}

@media (max-width: 560px) {
  /* One column, groups stacked top to bottom — three side-by-side tracks
     have no room left on a phone once each link needs its own tap target. */
  .footer__cols { grid-template-columns: 1fr; gap: 32px; }
  .footer__cols div:last-child { grid-column: auto; }
}

/* ---------------------------------------------------------------
   Blog

   Nothing here introduces a colour, a radius or a shadow the landing
   page hasn't already used: the same sky in a shorter hero, the same
   drawn panel as .quote for the cards, the same black pill as
   .waitlist for the one button. It is the home page's kit, re-cut.
   --------------------------------------------------------------- */

/* ---- interior hero ----
   The landing hero takes a whole screen because it has a whole screen's
   worth of promise to make. An index page has a title, a sentence and a row
   of topics, and the same 100svh of sky under them only pushes the posts
   below the fold. The scenery inside is untouched. */
/* ---- slim page header ----
   For the legal and policy pages. The blog and careers heroes write out the
   cloud <symbol>s and the earth SVG in full — ~240 lines each — and a policy
   page is a document, not a pitch, so these get the wash alone. Short enough
   that the copy starts above the fold, tall enough that the nav has somewhere
   to sit. */
.hero--slim {
  min-height: clamp(260px, 34svh, 380px);
  padding-bottom: 18px;
}
.hero--slim .hero__inner { width: min(760px, 100%); }
.hero--slim .hero__title { font-size: clamp(32px, 5.4vw, 56px); }

/* The 404 keeps the full-height sky — it is the one page where having nothing
   below the fold is the correct amount of content. */
.hero--error .hero__inner { width: min(760px, 100%); }

.hero--page { min-height: clamp(440px, 62svh, 640px); }
.hero--page .hero__inner { width: min(860px, 100%); }
.hero--page .hero__title { font-size: clamp(38px, 6.6vw, 76px); }

/* `top` is a percentage of the hero box, but the gap between that edge and
   the top of the sphere is a fraction of the globe's own width — which is
   viewport-wide and unchanged. On a hero this much shorter that fixed offset
   eats the whole visible band and the horizon ends up under the scene's
   bottom fade, leaving the page ending on blank sky. Starting higher puts the
   arc back where the landing page has it, proportionally. The width is
   deliberately left alone: shrink the globe to suit the shorter box and the
   curve tightens until it reads as a ball rather than a horizon. */
.hero--page .hero__earth { top: 48%; }

/* .eyebrow defaults to --ink-faint, which up here is a 70% white with a
   moving sky behind it — it needs the same lift .hero__sub gets. */
.hero .eyebrow {
  color: rgba(255, 255, 255, 0.82);
  text-shadow: var(--shadow-copy);
}

/* ---- topic chips ----
   Frosted like the .live pill rather than solid like .proof__pill: these sit
   directly under the headline, where the clouds reading through is the point.

   Hidden until its own script says otherwise — the row filters the grid
   below, and a row of buttons that does nothing is worse than no row at all.
   Gated on .is-ready rather than on .no-js because the two aren't the same
   thing here: the filter is a classic script and works over file://, where
   the ES module fails and puts .no-js back. */
.chips {
  display: none;
  flex-wrap: wrap;
  justify-content: center;
  gap: 9px;
  margin-top: clamp(22px, 3vw, 32px);
}
.chips.is-ready { display: flex; }

.chip {
  height: 38px;
  padding: 0 17px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fff;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1;
  white-space: nowrap;
  text-shadow: var(--shadow-copy);
  transition: background 0.3s var(--ease), border-color 0.3s, transform 0.35s var(--ease);
}
.chip:hover {
  background: rgba(255, 255, 255, 0.3);
  border-color: rgba(255, 255, 255, 0.72);
  transform: translateY(-1px);
}

/* The selected chip goes to the same black as .waitlist — the one hard,
   opaque thing in a soft scene, which is exactly what "you are here" wants
   to be. */
.chip[aria-pressed="true"] {
  background: #0b0b0d;
  border-color: rgba(255, 255, 255, 0.18);
  text-shadow: none;
  box-shadow: 0 14px 30px -18px rgba(0, 0, 0, 0.9);
}

/* ---- the grid ---- */
.posts { margin-top: 8px; }

/* Heading on the left, running total on the right, on one baseline. The
   count is written by the filter script and is the only feedback a chip
   gives beyond the cards themselves changing. */
.posts__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: clamp(20px, 2.4vw, 30px);
}
.posts__head .h2 { margin-bottom: 0; }
.posts__count { font-size: 13.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* ---- one post card ----
   The same drawn panel as .quote. No backdrop-filter: there is flat white
   behind these, and a blur over flat white is pure cost for no effect. */
.post {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--panel-line);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  transition: transform 0.5s var(--ease), border-color 0.4s, box-shadow 0.5s var(--ease);
}
.post:hover {
  transform: translateY(-3px);
  border-color: rgba(13, 43, 74, 0.2);
  box-shadow: 0 30px 60px -30px rgba(13, 43, 74, 0.42);
}

/* `hidden` is a UA-stylesheet declaration and .post's own `display` is an
   author one, so the author rule wins outright and a filtered-out card would
   stay on screen. This is what makes [hidden] mean hidden here. */
.post[hidden] { display: none; }

.post__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  background: #14304f;
}

/* Photo over a tint, the same fallback the trip cards and the wall use: a
   cover that hasn't been dropped in yet simply doesn't paint, and the card
   reads as a coloured tile rather than a broken image. The layers are set
   inline on the element so the path resolves against the document — a
   relative url() substituted from this stylesheet would resolve against
   css/ and 404. */
.post__photo {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transform: scale(1.02);
  transition: transform 0.8s var(--ease);
}
.post:hover .post__photo { transform: scale(1.07); }

/* Darkened at the top only, unlike .tcard — nothing sits over the bottom of
   these photos, and shading an edge with nothing on it just dims the shot. */
.post__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(3, 16, 34, 0.42), rgba(3, 16, 34, 0) 46%);
  pointer-events: none;
}

.post__tag {
  position: absolute;
  z-index: 1;
  top: 14px;
  left: 14px;
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 13px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  color: #14568f;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: -0.01em;
  box-shadow: 0 10px 24px -14px rgba(6, 34, 66, 0.8);
}

.post__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 10px;
  padding: 22px 24px 24px;
}
.post__title { font-size: 21px; line-height: 1.16; }
.post__excerpt { font-size: 15px; color: var(--ink-soft); }

.post__more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--brand-deep);
}
.post__more svg { width: 16px; height: 16px; transition: transform 0.4s var(--ease); }
.post:hover .post__more svg { transform: translateX(3px); }

/* margin-top:auto pins the byline to the bottom, so the meta rows line up
   across a row of cards whose titles and excerpts run to different lengths —
   same trick as .quote figcaption. */
.post__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 16px;
  font-size: 13px;
  color: var(--ink-faint);
}
.post__avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex: none;
  background-size: cover;
  background-position: center;
  box-shadow: 0 0 0 1px rgba(13, 43, 74, 0.12);
}
.post__author { font-weight: 700; color: var(--ink-soft); }
.post__dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.55;
  flex: none;
}

/* ---- the lead story ----
   The same card laid on its side: photo down one half, copy down the other.
   It reads as the lead without needing a different colour or a badge to say
   so. The minimum height is what stops it collapsing to three lines of copy
   on a wide screen. */
.post--feature {
  flex-direction: row;
  margin-bottom: clamp(30px, 3.6vw, 48px);
}
.post--feature .post__media {
  flex: 1 1 54%;
  aspect-ratio: auto;
  min-height: 340px;
}
.post--feature .post__body {
  flex: 1 1 46%;
  justify-content: center;
  gap: 14px;
  padding: clamp(28px, 3.4vw, 46px);
}
.post--feature .post__tag { top: 18px; left: 18px; }
.post--feature .post__title { font-size: clamp(25px, 2.9vw, 38px); line-height: 1.08; }
.post--feature .post__excerpt { font-size: 16.5px; max-width: 44ch; }

@media (max-width: 860px) {
  .post--feature { flex-direction: column; }
  .post--feature .post__media { aspect-ratio: 16 / 9; min-height: 0; }
}

/* ---------------------------------------------------------------
   Careers

   Same deal as the blog above: no new colour, radius or shadow. The
   hero borrows .hero--page, the four component blocks below are the
   .quote panel re-cut into a list row, a value card, a perk line and a
   numbered step, and the one button on the page is .waitlist.
   --------------------------------------------------------------- */

/* ---- values ----
   The .quote card with an icon tile where the stars used to go. The tile
   carries its own pale blue gradient — it inherited this from the old
   .card__icon rule, which has since been deleted along with the rest of the
   feature-card block no page was using. */
.values { margin-top: 8px; }

.value {
  padding: 30px 28px 32px;
  border-radius: var(--radius);
  border: 1px solid var(--panel-line);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  transition: transform 0.5s var(--ease), border-color 0.4s, box-shadow 0.5s var(--ease);
}
.value:hover {
  transform: translateY(-3px);
  border-color: rgba(13, 43, 74, 0.2);
  box-shadow: 0 30px 60px -30px rgba(13, 43, 74, 0.42);
}

.value__icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: 22px;
  border-radius: 15px;
  /* Emoji, so it needs the emoji stack for the same reason .proof__stars
     does — Gilroy has no glyph, and a bare fallback is the browser's guess. */
  font-family: var(--font-emoji);
  font-size: 22px;
  line-height: 1;
  background: linear-gradient(160deg, #ffffff, #dcecfb);
  box-shadow: 0 10px 24px -12px rgba(4, 30, 60, 0.5);
  transition: transform 0.5s var(--ease);
}
.value:hover .value__icon { transform: translateY(-2px) rotate(-4deg); }

.value h3 { font-size: 21px; margin-bottom: 10px; }
.value p { font-size: 15px; color: var(--ink-soft); }

/* ---- open roles ----
   A list, not a grid: a job list is read top to bottom, and six cards in a
   three-up make you scan for the one thing you came for. Each row is the
   same drawn panel as .quote, laid on its side. */
.roles {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.role {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 22px clamp(18px, 2vw, 28px);
  border-radius: var(--radius);
  border: 1px solid var(--panel-line);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  transition: transform 0.45s var(--ease), border-color 0.4s, box-shadow 0.5s var(--ease);
}
.role:hover {
  transform: translateY(-3px);
  border-color: rgba(13, 43, 74, 0.2);
  box-shadow: 0 30px 60px -30px rgba(13, 43, 74, 0.42);
}

/* min-width:0 so a long title can wrap inside the row rather than shoving
   the button off the right edge — flex items refuse to shrink past their
   content without it. */
.role__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.role__team {
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1;
}
.role__title {
  font-weight: 800;
  font-size: clamp(19px, 2vw, 24px);
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.role__meta { display: flex; flex-wrap: wrap; gap: 8px; }
/* Tinted with the brand rather than the panel colour: the row itself is
   already --panel, and a chip the same shade as the thing it sits on has no
   edge at all. */
.role__tag {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 13px;
  border-radius: 999px;
  background: rgba(30, 127, 209, 0.1);
  color: var(--brand-deep);
  font-size: 12.5px;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
}

/* .tcard__go's button in the black the rest of the white page uses for
   anything pressable. margin-left:auto pins it right whatever the row holds. */
.role__go {
  display: grid;
  place-items: center;
  flex: none;
  margin-left: auto;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: #0b0b0d;
  color: #fff;
  box-shadow: 0 14px 30px -16px rgba(0, 0, 0, 0.85);
  transition: transform 0.45s var(--ease);
}
.role__go svg { width: 42%; height: 42%; }
.role:hover .role__go { transform: scale(1.12); }

@media (max-width: 560px) {
  .role { gap: 14px; padding: 20px 18px; }
  .role__go { width: 40px; height: 40px; }
}

/* ---- perks ----
   Deliberately not cards. Six panels in a row of three is a wall of boxes,
   and none of these lines is a claim that needs its own frame. */
.perks {
  margin-top: 8px;
  gap: clamp(24px, 2.8vw, 38px);
}

.perk { display: flex; align-items: flex-start; gap: 14px; }
.perk__glyph {
  flex: none;
  font-family: var(--font-emoji);
  font-size: 22px;
  line-height: 1.25;
}
.perk h3 { font-size: 17px; margin-bottom: 6px; }
.perk p { font-size: 14.5px; color: var(--ink-soft); }

/* ---- hiring steps ----
   The number is absolutely placed and the copy is indented past it, so a
   step that runs to three lines keeps its text block square instead of
   wrapping back under the badge. */
.steps {
  margin-top: 8px;
  gap: clamp(28px, 3.2vw, 44px) clamp(24px, 3vw, 48px);
}

.step { position: relative; padding-left: 62px; }

.step__n {
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-weight: 800;
  font-size: 16px;
  letter-spacing: -0.02em;
  line-height: 1;
  box-shadow: 0 12px 26px -14px rgba(30, 127, 209, 0.9);
}

.step h3 { font-size: 18px; margin-bottom: 8px; padding-top: 11px; }
.step p { font-size: 14.5px; color: var(--ink-soft); }
.step__when {
  display: block;
  margin-top: 10px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

@media (max-width: 560px) {
  .step { padding-left: 52px; }
  .step__n { width: 38px; height: 38px; font-size: 15px; }
  .step h3 { padding-top: 8px; }
}

/* ---------------------------------------------------------------
   Legal + policy prose
   /privacy, /terms, /safety, /guidelines.

   Deliberately plain. These pages exist to be read start to finish by
   someone who is worried about something, and every decorative instinct the
   rest of the site indulges works against that. No cards, no glass, no
   reveal animation — the copy is on screen the moment the page is.

   The measure is the one thing worth being fussy about: ~68 characters, which
   is where a long document stops costing the reader a line-find on every
   wrap.
   --------------------------------------------------------------- */
/* .section carries up to 150px of top padding, which is right under a
   landing-page heading and far too much under a page header whose only job is
   to name the document.

   An explicit class rather than `.section:has(> .wrap > .legal)`: :has() is
   widely supported now, but where it isn't the rule is dropped silently and
   the page comes back with 150px of dead space at the top — a failure mode
   that looks like nothing is wrong. */
.section--doc { padding-top: clamp(40px, 5vw, 64px); }

.legal {
  max-width: 68ch;
  font-size: 16.5px;
  line-height: 1.72;
  color: var(--ink-soft);
}

.legal__meta {
  margin-top: 14px;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.82);
  text-shadow: var(--shadow-copy);
}

.legal__lede {
  font-size: 19px;
  line-height: 1.6;
  color: var(--ink);
  margin-bottom: 34px;
}

.legal h2 {
  font-size: 25px;
  margin: 48px 0 14px;
  scroll-margin-top: calc(var(--nav-h) + 20px);
}
.legal h3 {
  font-size: 18px;
  margin: 30px 0 10px;
  color: var(--ink);
}
.legal p { margin-bottom: 16px; }

.legal ul {
  list-style: disc;
  padding-left: 22px;
  margin-bottom: 20px;
}
.legal li { margin-bottom: 9px; }
.legal strong { color: var(--ink); font-weight: 800; }

.legal a {
  color: var(--brand-deep);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.legal a:hover { color: var(--ink); }

/* ---- table of contents ----
   An <ol> because the sections are numbered in the document itself; the
   numbers are information, not decoration. */
.legal__toc {
  margin: 0 0 44px;
  padding: 22px 26px;
  border: 1px solid var(--panel-line);
  border-radius: var(--radius-sm);
  background: var(--panel);
}
/* Scoped through .legal__toc, not left as a bare class: the element is an
   <h2>, so `.legal h2` above (0,1,1) outranks a lone `.legal__toc-title`
   (0,1,0) and the label rendered at full 25px heading size. */
.legal__toc .legal__toc-title {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 12px;
}
.legal__toc ol {
  list-style: decimal;
  padding-left: 20px;
  columns: 2;
  column-gap: 30px;
  font-size: 15px;
}
.legal__toc li { margin-bottom: 6px; break-inside: avoid; }
.legal__toc a { text-decoration: none; }
.legal__toc a:hover { text-decoration: underline; }
@media (max-width: 620px) { .legal__toc ol { columns: 1; } }

/* ---- callouts ---- */
.legal__callout {
  margin: 0 0 34px;
  padding: 20px 24px;
  border-left: 3px solid var(--brand);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: #eff6fd;
}
.legal__callout p { margin: 0; font-size: 15.5px; }
.legal__callout--warn {
  border-left-color: #c2362e;
  background: #fdf1f0;
}

.legal__note {
  font-size: 14.5px;
  font-style: italic;
  color: var(--ink-faint);
  margin-bottom: 14px;
}
.legal__strong {
  font-size: 17px;
  color: var(--ink);
  font-weight: 800;
  margin-bottom: 18px;
}
.legal__related {
  margin-top: 46px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
  font-size: 15px;
}

/* ---- unfilled placeholder ----
   Loud on purpose. Every one of these is a fact only the company can supply,
   and a legal page that ships with "[REGISTERED ADDRESS]" in body text is
   worse than one that never shipped. Impossible to miss, and
   tools/check-site.py counts them so they cannot go live by accident. */
.legal__todo {
  display: inline;
  padding: 1px 6px;
  border-radius: 4px;
  background: #ffe8a3;
  box-shadow: inset 0 0 0 1px #d9a520;
  color: #6b4a00;
  font-weight: 800;
  font-size: 0.92em;
}

/* Cookie names, and anything else the reader is meant to match character for
   character against what their browser shows them. */
.legal__code {
  font-family: ui-monospace, "SFMono-Regular", "Consolas", monospace;
  font-size: 0.9em;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 5px;
  padding: 1px 5px;
}

/* Reopens the consent banner. A <button>, because it acts on this page rather
   than going anywhere — but it reads mid-sentence, so it is dressed as the
   links around it. */
.legal__consent {
  padding: 0;
  color: var(--brand-deep);
  font-weight: 300;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.legal__consent:hover { color: var(--ink); }

/* ---------------------------------------------------------------
   Reveal-on-scroll (the CSS half; js/modules/reveal.js flips .is-in)
   --------------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity 0.9s var(--ease) var(--reveal-delay, 0ms),
    transform 0.9s var(--ease) var(--reveal-delay, 0ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* Nothing should ever be stuck invisible if the script fails to load */
.no-js [data-reveal] { opacity: 1; transform: none; }
.no-js .hero__title .word > span { transform: none; }

/* ---------------------------------------------------------------
   Scroll parallax

   Two effects that used to come from GSAP + ScrollTrigger: the hero
   drifting up and dissolving as you leave it, and section headings
   moving against the scroll so the page feels layered.

   Both are scroll-driven CSS animations now — no JS, no third-party
   payload, and the compositor runs them off the main thread. Wrapped in
   @supports because a browser without scroll timelines must not be left
   holding the animation's first keyframe: `animation-timeline` is
   ignored there, so an unguarded rule would play both of these once, on
   load, as ordinary time-based animations. Where they aren't supported
   the page is simply static, which is exactly what it did whenever the
   GSAP request failed.
   --------------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes hero-leave {
      to { transform: translateY(-90px); opacity: 0; }
    }

    /* The section drives the timeline, the content box is what moves — a
       bare view() would measure .hero__inner itself, which is shorter than
       the hero and would start the fade at the wrong scroll position. */
    .hero { view-timeline-name: --hero; }

    .hero__inner {
      animation: hero-leave linear both;
      /* Matches the old trigger: starts as the hero's top hits the top of
         the viewport, done by the time its bottom is 30% up the screen. */
      animation-timeline: --hero;
      animation-range: exit 0% exit 70%;
    }

    @keyframes heading-counter {
      from { transform: translateY(26px); }
      to   { transform: translateY(-14px); }
    }

    /* The reveal transition also writes `transform`, so the two would
       fight over the same property on the way in. Scoping this to headings
       that have finished revealing hands the property over cleanly. */
    .section .h2.is-in,
    .section .h2:not([data-reveal]) {
      animation: heading-counter linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
  }
}

/* ---------------------------------------------------------------
   Off-screen work the browser can skip

   The photo wall is 32 tiles and the trip rail is 24 cards, all of them
   below the fold on every viewport, and all of them laid out at first paint
   whether or not anyone reaches them. `content-visibility: auto` lets the
   browser skip layout and paint until they approach the viewport;
   `contain-intrinsic-size` supplies a placeholder height so the scrollbar
   doesn't jump when they materialise.

   Deliberately not applied to .hero: it is the first thing on screen, and
   containment there would delay the largest contentful paint rather than
   help it.
   --------------------------------------------------------------- */
.wall__rows,
.rail {
  content-visibility: auto;
}
.wall__rows { contain-intrinsic-size: auto 800px; }
.rail { contain-intrinsic-size: auto 420px; }

/* ---------------------------------------------------------------
   Drawer open — lock the page behind it

   Without this the page scrolls under an overlay that covers it, so
   flicking at the drawer on a phone moves the content you cannot see and
   closing it leaves you somewhere else entirely.
   --------------------------------------------------------------- */
.is-drawer-open,
.is-drawer-open body {
  overflow: hidden;
  touch-action: none;
}

/* ---------------------------------------------------------------
   Consent banner

   Injected by js/analytics.js, never present in the markup — see that file
   for why. It sits on the white part of the page in every case, so it takes
   the :root light tokens and does not need the region-aware treatment the
   nav and footer components do.

   z-index 900 clears the nav (60) but stays under the skip link (999): a
   keyboard visitor landing on the page should still be able to see where
   "skip to content" went.
   --------------------------------------------------------------- */
.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 900;
  padding: 0 clamp(12px, 4vw, 28px) clamp(12px, 4vw, 28px);
  display: flex;
  justify-content: center;
  /* The bar is only as wide as its panel; without this the flex container
     spans the viewport and swallows clicks on whatever is beside it. */
  pointer-events: none;
}

.consent__panel {
  pointer-events: auto;
  width: min(640px, 100%);
  padding: 22px 24px;
  background: #fff;
  border: var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  /* Held down and faded until .is-open. The global reduced-motion rule
     collapses the transition to nothing, so it simply appears. */
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}

.consent.is-open .consent__panel {
  opacity: 1;
  transform: none;
}

.consent__title {
  font-size: 19px;
  margin-bottom: 7px;
}
/* Focused programmatically when reopened from the privacy page, so it takes
   a tabindex — and would otherwise draw a ring nobody asked for. */
.consent__title:focus { outline: none; }
.consent__title:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; }

.consent__text {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin-bottom: 16px;
}
.consent__text a {
  color: var(--brand-deep);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.consent__text a:hover { color: var(--ink); }

.consent__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.consent__btn {
  height: 44px;
  padding: 0 22px;
  border-radius: 999px;
  font-size: 14.5px;
  font-weight: 800;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transition: transform 0.4s var(--ease), background-color 0.3s, border-color 0.3s;
}
.consent__btn:hover { transform: translateY(-2px); }
.consent__btn:active { transform: translateY(0); }

/* The page behind this is white, so the site's .btn--solid (white face, blue
   ink) would disappear into it. Inverted here instead. */
.consent__btn--solid {
  background: var(--deep);
  color: #fff;
}
.consent__btn--solid:hover { background: var(--brand-deep); }

.consent__btn--ghost {
  border: 1.5px solid var(--line);
  color: var(--ink);
}
.consent__btn--ghost:hover { border-color: var(--ink); }

/* Refusing must not be the harder target. Below this width the two buttons
   stop fitting side by side at a comfortable tap size, so they stack at
   equal width rather than letting one wrap under the other.

   `column`, not `column-reverse`: reversing reads better under a thumb but
   leaves the tab order running bottom-to-top against what the eye sees,
   which is the mismatch WCAG 2.4.3 is about. */
@media (max-width: 430px) {
  .consent__panel { padding: 20px; }
  .consent__actions { flex-direction: column; }
  .consent__btn { width: 100%; }
}

/* ---------------------------------------------------------------
   Print

   The careers page is the one people save as a PDF, and the legal pages are
   the ones people are told to keep a copy of. Both printed the full-height
   sky, the drifting emoji and the black band. Strip the scenery, drop the
   chrome, and put link destinations in the margin where paper can use them.
   --------------------------------------------------------------- */
@media print {
  .nav,
  .footer,
  .floaters,
  .hero__scene,
  .hero__cta,
  .community,
  .wall,
  .rail,
  .skip-link,
  .consent,
  .chips { display: none !important; }

  html, body { background: #fff !important; color: #000 !important; }

  .hero,
  .hero__inner,
  .hero__title,
  .hero__sub,
  .eyebrow,
  .legal__meta {
    min-height: 0 !important;
    color: #000 !important;
    text-shadow: none !important;
  }

  .hero { padding: 0 !important; }
  .section { padding: 0 !important; }
  .legal { max-width: none; font-size: 11pt; line-height: 1.5; color: #000; }
  .legal h2, .legal h3 { break-after: avoid; }
  .legal li, .legal__callout, .legal__toc { break-inside: avoid; }

  /* A URL on paper is unreachable unless it is printed. Internal links get
     the origin spelled out, since a bare "/privacy" means nothing off-screen. */
  .legal a[href^="http"]::after { content: " (" attr(href) ")"; }
  .legal a[href^="/"]::after { content: " (ourbestie.app" attr(href) ")"; }
  .legal a::after { font-size: 9pt; color: #444; word-break: break-all; }

  /* Nothing should be waiting on a scroll event that will never fire. */
  [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------
   Reduced motion
   --------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
  .hero__title .word > span { transform: none; }
  /* Left in place, just held still — they're scenery, not motion for its
     own sake, and removing them changes the composition. */
  .floater,
  .floater__glyph { animation: none; }
}
