/* =============================================================
   Vrolu — base styles
   Imports tokens, resets, base typography, and Bootstrap overrides
   so auth pages (which still use Bootstrap classes) inherit brand.
   ============================================================= */

@import url('css/tokens.css');

/* ---------- Reset / base ---------- */

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

html {
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  background: var(--color-bg-base);
  color: var(--color-text-primary);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.6;
  transition: background-color var(--duration-base) var(--ease-out),
              color var(--duration-base) var(--ease-out);
}

a {
  /* Azure, not coral (July 16, iOS grammar): a link is a "tap here". */
  color: var(--color-action);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-out);
}

a:hover {
  color: var(--color-action-strong);
}

/* Headings ride --font-display — IBM Plex Sans Bold as of the v2.0 brand
   sweep (iOS #40, Aug 2026). Plex holds presence at weight alone; the app
   sets its 34pt titles with DEFAULT tracking, so the old Inter-era
   Apple-display tracking recipe (-0.03/-0.04em) is retired. Web heroes
   render far larger than any app title, so h1/h2 keep a light -0.01em
   touch — nothing tighter. Legacy 'opsz' declarations elsewhere in this
   file are inert on static cuts; left in place, meaningless not harmful. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: normal;
  margin: 0 0 var(--space-4);
}

h1 { font-size: var(--text-5xl); letter-spacing: -0.01em; }
h2 { font-size: var(--text-3xl); letter-spacing: -0.01em; }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }

h1:focus, h2:focus { outline: none; }

p { margin: 0 0 var(--space-4); }

code, pre, .mono {
  font-family: var(--font-mono);
  font-size: 0.92em;
  /* Data settings: digits align in columns (times, flight numbers,
     counts) — iOS gets this for free from Plex/SF, browsers don't. */
  font-variant-numeric: tabular-nums;
}

::selection {
  background: var(--color-action-soft);
  color: var(--color-text-primary);
}

/* ---------- Brand button + form primitives ---------- */

/* Button labels are display voice on iOS (VroluType.button = Plex Bold
   17pt), and the chrome is a 14pt-radius rect, not a capsule. Three
   volumes, per MyFlightsView's doctrine — "azure commits, white invites,
   ghost offers": .btn-primary (azure, the moment something happens),
   .btn-invite (moonlight fill, ink text, NO border — one per screen),
   and .btn-ghost (translucent, offers). "Prominent" scales weight, not
   hue — a button that merely opens a form doesn't earn the loud fill. */
.btn {
  font-family: var(--font-display);
  font-weight: 700;
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-5);
  border: 1px solid transparent;
  transition: transform var(--duration-fast) var(--ease-out),
              background-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

/* Azure, not coral (July 16, from the iOS grammar): a filled CTA in the
   sunset family reads as a warning. Brand warmth stays in heroes/glows. */
.btn-primary {
  background: var(--color-action);
  color: var(--color-text-inverse);
  border-color: var(--color-action);
}

.btn-primary:hover {
  background: var(--color-action-strong);
  border-color: var(--color-action-strong);
  color: var(--color-text-inverse);
}

/* The invitation (iOS 2.0 "moonlight", Aug 6): warm white fill, ink
   text, no border — a stroke on a solid fill reads as a seam. Reserve
   for the ONE ask the screen is making (hero "Get started"). */
.btn-invite {
  background: var(--color-moonlight);
  color: #1A1229;
  border-color: transparent;
}

.btn-invite:hover {
  background: #FFFFFF;
  color: #1A1229;
}

.btn-ghost {
  background: rgba(255, 255, 255, 0.08);
  color: var(--color-text-primary);
  border-color: var(--color-border-strong);
}

.btn-ghost:hover {
  background: rgba(255, 255, 255, 0.12);
  color: var(--color-text-primary);
}

[data-theme="light"] .btn-ghost {
  background: rgba(26, 18, 41, 0.05);
}

[data-theme="light"] .btn-ghost:hover {
  background: rgba(26, 18, 41, 0.09);
}

.btn:disabled,
.btn.disabled,
.btn-primary:disabled,
.btn-primary.disabled {
  background: var(--color-bg-elevated);
  color: var(--color-text-muted);
  border-color: var(--color-border);
  cursor: not-allowed;
  opacity: 1;
  box-shadow: none;
  transform: none;
}

.btn:disabled:hover,
.btn.disabled:hover {
  background: var(--color-bg-elevated);
  color: var(--color-text-muted);
  border-color: var(--color-border);
  transform: none;
  box-shadow: none;
}

.btn-link, a.btn-link { color: var(--color-action); }
.btn-link:hover { color: var(--color-action-strong); }

.btn:focus, .btn:active:focus,
.form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 3px var(--color-action-soft);
  outline: none;
}

/* ---------- Header icon button — unified top-toolbar primitive ----------
   Single primitive for icon-only header affordances (theme toggle, nav
   links collapsed to icons on mobile, avatar link, sign-out). 44x44
   satisfies iOS HIG; transparent border becomes a soft-coral fill on
   hover and active-route states. Brand CTAs that DO appear in headers
   (e.g. the marketing "Dashboard" pill for signed-in guests on the
   funnel) keep .btn / .btn-primary so the toolbar reads as a row of
   quiet icon affordances + one loud CTA where applicable.
   See UI_STYLE_GUIDE.md "Top toolbar" for the contract. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  /* transform is in the list so the global tap-feedback :active rule
     (see "Tap feedback" block below) animates the scale-down smoothly
     on both press AND release. */
  transition: background-color var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}

.icon-button:hover {
  background: var(--color-action-soft);
  color: var(--color-action);
}

/* Active-route state: same visual language as .trip-tabs__tab--active
   (gold-accent border + subtle accent fill + coral icon). One selected-
   state vocabulary across the app — pills and icon-buttons read as
   selected the same way. Previous version used a semi-transparent gray
   fill which got lost at 44x44 surface; the visible border cue is what
   carries "you are here" on small targets. */
.icon-button.active {
  border-color: color-mix(in srgb, var(--color-action) 50%, var(--color-border));
  background: color-mix(in srgb, var(--color-action) 10%, var(--color-bg-elevated));
  color: var(--color-action);
}

.icon-button:focus-visible {
  outline: 2px solid var(--color-action);
  outline-offset: 2px;
}

/* Theme toggle (static SSR — July 4 public-SSR hardening). Both glyphs are
   in the markup; html[data-theme] picks exactly one, so the icon flips the
   instant theme.js changes the attribute — no circuit, no component state.
   Default (dark) shows the sun (i.e., "switch to light"). */
.theme-toggle__icon--moon { display: none; }
html[data-theme="light"] .theme-toggle__icon--sun { display: none; }
html[data-theme="light"] .theme-toggle__icon--moon { display: block; }

.icon-button > svg {
  width: 20px;
  height: 20px;
}

.form-control,
.form-select {
  background: var(--color-bg-elevated);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}

.form-control:focus,
.form-select:focus {
  background: var(--color-bg-elevated);
  color: var(--color-text-primary);
  border-color: var(--color-action);
}

.form-control::placeholder { color: var(--color-text-muted); }

.form-label {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  /* Bootstrap defaults to margin-bottom: 0.5rem; we use flex gap on form rows
     instead, so explicitly null this out — otherwise the label sits 16px
     above its input (8px margin + 8px flex gap), orphaning it visually. */
  margin-bottom: 0;
}

/* Bootstrap card / list rebrand for auth pages */
.card {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  color: var(--color-text-primary);
}

.card-body { padding: var(--space-6); }

hr { border-color: var(--color-border); }

/* Validation */
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--color-success); }
.invalid { outline: 1px solid var(--color-danger); }
.validation-message { color: var(--color-danger); }

/* ---------- Auth pages (Identity-scaffolded surfaces) ---------- */

.auth {
  max-width: 440px;
  margin: var(--space-8) auto;
  padding: 0 var(--space-5);
}

.auth__header { margin-bottom: var(--space-6); }

.auth__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-accent);
  margin: 0 0 var(--space-2);
  text-transform: lowercase;
}

.auth__title {
  font-size: var(--text-4xl);
  margin: 0 0 var(--space-3);
  font-variation-settings: 'opsz' 96;
}

.auth__lede {
  color: var(--color-text-secondary);
  margin: 0;
}

.auth__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* ---------- Brand form primitives ----------
   Live in global app.css (not component-scoped) because Blazor's scoped CSS
   markers don't reach the <form> element rendered by the EditForm component
   — the rewritten selector wouldn't match. These classes are reused across
   New Trip, Add Traveler, Edit Traveler, etc., so global is also the right
   home architecturally. */

.form-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: 640px;
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.form-row--split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.form-row--split > div {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.form-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.form-error {
  color: var(--color-danger);
  background: var(--color-primary-soft);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
}

.form-info {
  color: var(--color-text-primary);
  background: var(--color-bg-sunken);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
}

.form-label-optional {
  margin-left: var(--space-2);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.validation-message {
  color: var(--color-danger);
  font-size: var(--text-sm);
  margin: 0;
}

@media (max-width: 540px) {
  .form-row--split { grid-template-columns: 1fr; }
}

.auth__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.auth__error {
  color: var(--color-danger);
  font-size: var(--text-sm);
  margin: 0;
}

.auth__summary {
  color: var(--color-danger);
  font-size: var(--text-sm);
  margin: 0;
}

.auth__remember {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  cursor: pointer;
}

.auth__submit {
  width: 100%;
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}

.auth__divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin: var(--space-3) 0;
}

.auth__divider::before,
.auth__divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}

.auth__passkey {
  background: transparent;
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-strong);
  width: 100%;
  padding: var(--space-3);
}

.auth__passkey:hover {
  background: var(--color-bg-elevated);
  border-color: var(--color-primary-soft);
  color: var(--color-text-primary);
}

/* Phase 1.x — external-provider button row (Google, Apple).
   Wraps the picker form so multiple buttons stack with consistent spacing.
   `.auth__external-btn` matches the visual weight of `.auth__passkey` so
   the three sign-in methods (password, external, passkey) read as peers. */
.auth__external {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.auth__external-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3);
  background: #ffffff;
  /* Locked dark label: the white background is brand-mandated by Google
     (and any future white-button provider), so the text color must be a
     theme-independent dark — using --color-text-primary inverts to white
     in dark mode and the label disappears against the white button. */
  color: #1f1f1f;
  border: 1px solid #dadce0;
  font-weight: 600;
}

.auth__external-btn:hover {
  /* Subtle off-white hover that preserves contrast with the locked dark
     label. Google's own sign-in buttons use the same grey-50 hover state.
     `color` is explicitly restated because Bootstrap's .btn:hover ships
     `color: var(--bs-btn-hover-color)` which falls back to an undefined
     value here (no .btn-X variant is in play) and overwrites our rest-
     state #1f1f1f with effectively-white — text vanishes against the
     near-white hover background. */
  background: #f8f9fa;
  border-color: #c1c5c9;
  color: #1f1f1f;
}

/* Google's branding guidelines require their multicolor "G" on a white
   button with no surrounding tint. The default rule above already gives us
   white-on-white; this is a hook for future per-provider tweaks. */
.auth__external-btn--google { /* identical to default; kept as hook */ }

/* Apple's HIG requires their wordmark on a solid black (light theme) or
   white (dark theme) button. We stay simple here and invert the standard
   styling — chunk 4 may refine once the Apple provider lands. */
.auth__external-btn--apple {
  background: #000000;
  color: #ffffff;
  border-color: #000000;
}

.auth__external-btn--apple:hover {
  background: #1a1a1a;
  border-color: #1a1a1a;
  color: #ffffff;
}

/* "Not you? Use a different account" escape hatch under the OAuth row.
   Visually quiet — sits between the OAuth buttons and the divider so it
   reads as a secondary affordance, not a competing CTA. */
.auth__external-switch {
  display: inline-block;
  margin: var(--space-2) 0 var(--space-3);
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  text-decoration: none;
}

.auth__external-switch:hover {
  color: var(--color-text-primary);
  text-decoration: underline;
}

.auth__footnote {
  margin: var(--space-4) 0 0;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  text-align: center;
}

.auth__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  text-align: center;
}

.auth__links a { color: var(--color-text-secondary); }
.auth__links a:hover { color: var(--color-action-strong); }

/* ---------- Segment landing pages (/for/*) ----------
   Shared structure across bachelorette / destination-wedding / family-
   reunion landing pages. Hero on top, three-card pitch grid, closing CTA. */

/* ---------- The app's sky (Oct 3 2026) ----------
   The marketing site's ground is the app's: iOS WorldBackdrop — the
   founder's rendered dusk sky (PromiseSky: near-black overhead, a
   sunset-lit cloud deck along the bottom), the ink edge-weld at the top,
   and DuskWash's sparse stars. Fixed to the viewport the way the app's
   backdrop sits still behind a scrolling list. Founder, Oct 3: "The app
   doesn't have sun from the top. The app has a background image."
   sky-tall is the app's own asset; sky-wide is the same render's cloud
   deck feathered across a landscape frame for desktop. */
html:has(.theme-app),
html:has(.team-page) {
  background: #0B0A16;
  color-scheme: dark;
}

/* The team pages (Oct 8 2026) sit on the same sky inside the signed-in
   shell: the shell's own fills step aside, its sidebar turns to glass. */
body:has(.team-page),
body:has(.team-page) .app {
  background: transparent;
}

/* Re-resolve the text color from the dark tokens the shell now carries. */
body:has(.team-page) .app {
  color: var(--color-text-primary);
}

body:has(.team-page) .app__sidebar {
  background: rgba(11, 10, 22, 0.55);
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
}

/* The body's own fill would paint over a negative-z fixed layer; the
   html element carries the ink instead. */
body:has(.theme-app) {
  background: transparent;
}

/* Re-resolve the text color from the dark tokens .theme-app sets (body's
   color was computed from the html-level theme). */
.theme-app {
  color: var(--color-text-primary);
}

.app-sky {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(11, 10, 22, 0.55) 0%, transparent 25%),
    url("/img/sky/sky-tall.webp") center bottom / cover no-repeat,
    #0B0A16;
}

@media (min-aspect-ratio: 4/5) {
  .app-sky {
    background:
      linear-gradient(180deg, rgba(11, 10, 22, 0.55) 0%, transparent 25%),
      url("/img/sky/sky-wide.webp") center bottom / cover no-repeat,
      #0B0A16;
  }
}

/* DuskWash's stars: a fixed, authored scatter in the upper sky. */
.app-sky::after {
  content: "";
  position: absolute;
  inset: 0 0 50% 0;
  background:
    radial-gradient(1.5px 1.5px at 8% 10%, rgba(255, 255, 255, 0.45), transparent 70%),
    radial-gradient(1px 1px at 21% 22%, rgba(255, 255, 255, 0.35), transparent 70%),
    radial-gradient(1px 1px at 33% 8%, rgba(255, 255, 255, 0.3), transparent 70%),
    radial-gradient(1.5px 1.5px at 44% 18%, rgba(255, 255, 255, 0.4), transparent 70%),
    radial-gradient(1px 1px at 57% 6%, rgba(255, 255, 255, 0.3), transparent 70%),
    radial-gradient(2px 2px at 69% 14%, rgba(255, 255, 255, 0.45), transparent 70%),
    radial-gradient(1px 1px at 82% 10%, rgba(255, 255, 255, 0.3), transparent 70%),
    radial-gradient(1.5px 1.5px at 93% 20%, rgba(255, 255, 255, 0.4), transparent 70%),
    radial-gradient(1px 1px at 14% 34%, rgba(255, 255, 255, 0.25), transparent 70%),
    radial-gradient(1.5px 1.5px at 28% 42%, rgba(255, 255, 255, 0.3), transparent 70%),
    radial-gradient(1px 1px at 49% 30%, rgba(255, 255, 255, 0.25), transparent 70%),
    radial-gradient(1.5px 1.5px at 63% 38%, rgba(255, 255, 255, 0.3), transparent 70%),
    radial-gradient(1px 1px at 77% 28%, rgba(255, 255, 255, 0.25), transparent 70%),
    radial-gradient(1px 1px at 90% 40%, rgba(255, 255, 255, 0.28), transparent 70%),
    radial-gradient(1.5px 1.5px at 38% 56%, rgba(255, 255, 255, 0.2), transparent 70%),
    radial-gradient(1.5px 1.5px at 55% 50%, rgba(255, 255, 255, 0.3), transparent 70%),
    radial-gradient(1px 1px at 86% 54%, rgba(255, 255, 255, 0.24), transparent 70%);
}

/* Phone rhythm (Oct 3 2026; founder: "Spacing is quite off... you have to
   scroll way down in between content"). Every marketing section spaces
   itself from these tokens, so a phone gets one tighter scale here
   instead of a dozen per-page overrides. Desktop keeps the full scale. */
@media (max-width: 720px) {
  .theme-app {
    --space-8: 2rem;
    --space-10: 2.5rem;
    --space-12: 3rem;
    --space-16: 4rem;
  }
}

/* ---------- The US delay map (Oct 3 2026) ----------
   DelayMap.razor + js/airport-map.js: the app's airport pulse (iOS
   PulseLight) on the dark Google basemap. Quiet = moonlight; lit = the
   sunset ramp by worst program (red stop/closure, coral ground delay,
   amber delay); a ground stop breathes. Never orchid (iOS, Sep 9 2026). */
.delay-map {
  margin: 0;
}

.delay-map__canvas {
  height: 440px;
  border-radius: 20px;
  overflow: hidden;
  border: 1px solid var(--color-border-strong);
  background: #0B0A16;
}

.delay-map__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.delay-map__legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.delay-map__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  display: inline-block;
}

.delay-map__dot--red { background: #FF5C74; box-shadow: 0 0 8px rgba(255, 92, 116, 0.7); }
.delay-map__dot--coral { background: #FF6B5B; box-shadow: 0 0 8px rgba(255, 107, 91, 0.7); }
.delay-map__dot--amber { background: #FFB347; box-shadow: 0 0 8px rgba(255, 179, 71, 0.7); }
.delay-map__dot--quiet { background: rgba(255, 248, 238, 0.55); }

.delay-map__layer {
  position: absolute;
  left: 0;
  top: 0;
}

.delay-map__light {
  --light: rgba(255, 248, 238, 0.9);
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  text-decoration: none;
}

.delay-map__light i {
  position: absolute;
  left: -3px;
  top: -3px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: radial-gradient(circle, #FFFFFF 0%, var(--light) 70%);
  box-shadow: 0 0 6px var(--light);
  opacity: 0.55;
}

/* A tap target bigger than the dot (iOS hitSize: lights are hard to tap). */
.delay-map__light::before {
  content: "";
  position: absolute;
  left: -12px;
  top: -12px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
}

.delay-map__light--groundStop,
.delay-map__light--closure { --light: #FF5C74; }
.delay-map__light--groundDelay { --light: #FF6B5B; }
.delay-map__light--delay { --light: #FFB347; }

.delay-map__light:not(.delay-map__light--quiet) i {
  left: -5px;
  top: -5px;
  width: 10px;
  height: 10px;
  opacity: 1;
  box-shadow: 0 0 10px var(--light), 0 0 22px color-mix(in srgb, var(--light) 45%, transparent);
}

.delay-map__light b {
  position: absolute;
  left: 9px;
  top: -9px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  color: #FFFFFF;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  white-space: nowrap;
}

/* The ground stop breathes (iOS PulseLight.breathes): one ring, 2.2 s. */
.delay-map__light--groundStop::after {
  content: "";
  position: absolute;
  left: -5px;
  top: -5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 1.5px solid var(--light);
  animation: delay-map-breathe 2.2s ease-out infinite;
}

@keyframes delay-map-breathe {
  from { transform: scale(1); opacity: 0.6; }
  to { transform: scale(3.2); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .delay-map__light--groundStop::after { animation: none; opacity: 0; }
}

@media (max-width: 720px) {
  .delay-map__canvas { height: 320px; }
}

/* ---------- The airport finder (Oct 3 2026) ----------
   AirportFinder.razor: a GET form to /airports?q=. Used on the home page,
   the airport index and every airport page. */
.ap-finder {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 34rem;
  padding: 6px 6px 6px var(--space-4);
  border-radius: 18px;
  border: 1px solid var(--color-border-strong);
  background: var(--color-card);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.ap-finder:focus-within {
  border-color: var(--color-action);
}

.ap-finder__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.ap-finder__icon {
  width: 20px;
  height: 20px;
  flex: none;
  color: var(--color-text-muted);
}

.ap-finder__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--color-text-primary);
  font: inherit;
  font-size: 16px; /* 16px: iOS Safari zooms into anything smaller */
  padding: var(--space-2) 0;
  outline: none;
}

.ap-finder__input::placeholder {
  color: var(--color-text-muted);
}

.ap-finder__go {
  flex: none;
  border: 0;
  border-radius: 13px;
  padding: var(--space-2) var(--space-5);
  background: var(--color-action);
  color: #FFFFFF;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* ---------- Store badges (Oct 3 2026) ----------
   StoreBadges.razor: the App Store badge, live, beside the Google Play
   badge, shown disabled with "Coming later this month" under it (founder:
   "Everywhere we see App Store I want the Google Play icon disabled").
   GooglePlaySoon.razor is the compact pill for the bars and banners that
   draw their own small App Store pill. */
.store-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-3);
}

.store-badges--center {
  justify-content: center;
}

.store-badge {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  line-height: 0;
  border-radius: 12px;
  transition: transform 0.15s ease;
}

.store-badge img {
  display: block;
  width: auto;
}

a.store-badge:focus-visible {
  outline: 2px solid var(--color-action);
  outline-offset: 4px;
}

@media (hover: hover) {
  a.store-badge:hover { transform: translateY(-1px); }
}

.store-badge--soon {
  cursor: default;
}

/* Phones: both badges at 44px tall fit side by side in a 350px column
   (132 + 149 + the gap), so the pair reads as a pair, not a stack. */
@media (max-width: 720px) {
  .store-badges { gap: var(--space-2); }
  .store-badge img { height: 44px; }
  /* Inside a padded card (pricing): 40px keeps the pair on one line. */
  .store-badges--compact .store-badge img { height: 40px; }
  .store-badges--compact .store-badge__soon { font-size: 10px; }
}

.store-badge--soon img {
  opacity: 0.45;
}

.store-badge__soon {
  /* Never wider than the smallest badge it sits under (44px tall = 149px
     wide), so the caption can't push the pair onto two lines. */
  font-size: 11px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  color: rgba(255, 255, 255, 0.62);
}

.gp-soon {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  height: 36px;
  padding: 0 11px 0 9px;
  background: #000;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 9px;
  opacity: 0.5;
  cursor: default;
}

.gp-soon__logo {
  width: 17px;
  height: 18px;
  flex: none;
}

.gp-soon__text {
  display: flex;
  flex-direction: column;
  line-height: 1.05;
  text-align: left;
}

.gp-soon__small {
  font-size: 8px;
  letter-spacing: 0.02em;
}

.gp-soon__big {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Web rework (Oct 3 2026): the inner pages wear the home page's sky — the
   app's dusk (night overhead, the sunset at the horizon), Plex, the live
   cyan. The hero is a picture of dusk, not a surface, so it stays dark in
   the light theme and sets its own text colors. */
.segment-hero {
  margin-top: calc(-1 * var(--space-8));
  padding: var(--space-10) 0 var(--space-8);
  position: relative;
  color: #FFFFFF;
  /* No fill of its own: the app's sky (.app-sky) is the ground. */
}

.segment-hero__inner {
  max-width: 880px;
  position: relative;
  z-index: 1;
}

.segment-hero__eyebrow {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-live);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: var(--space-4);
}

.segment-hero__title {
  font-family: var(--font-display);
  font-weight: 700;
  /* Oct 4 2026 (founder: "Headings are too big and too much white
     space"): a step down at every width. */
  font-size: clamp(2rem, 4.8vw, 3.75rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-5);
}

/* The accent words: the live cyan, upright — Plex carries the emphasis
   by color alone (the italic was the retired serif's habit). */
.segment-hero__title-accent {
  color: var(--color-live);
  font-style: normal;
}

.segment-hero__lede {
  font-size: clamp(var(--text-lg), 1.6vw, var(--text-xl));
  color: rgba(255, 255, 255, 0.74);
  max-width: 56ch;
  margin: 0 0 var(--space-6);
  line-height: 1.55;
}

.segment-hero__ctas {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.segment-hero__cta {
  font-size: var(--text-base);
  padding: var(--space-3) var(--space-6);
}

.segment-hero__secondary {
  color: rgba(255, 255, 255, 0.74);
  font-weight: 600;
  text-decoration: none;
}

@media (hover: hover) {
  .segment-hero__secondary:hover { color: #FFFFFF; }
}

.segment-pitch {
  padding: var(--space-8) 0 var(--space-10);
}

.segment-pitch__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-5);
}

.segment-pitch__card {
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  background: var(--color-card);
  transition: border-color var(--duration-base) var(--ease-out),
              transform var(--duration-base) var(--ease-out);
}

.segment-pitch__card:hover {
  border-color: var(--color-border-strong);
  transform: translateY(-2px);
}

.segment-pitch__eyebrow {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-xl);
  margin: 0 0 var(--space-3);
  color: var(--color-text-primary);
  font-variation-settings: 'opsz' 36;
  line-height: 1.2;
}

.segment-pitch__body {
  color: var(--color-text-secondary);
  margin: 0;
  font-size: var(--text-base);
  line-height: 1.6;
}

.segment-cta {
  margin-bottom: calc(-1 * var(--space-12));
  padding: var(--space-12) 0 calc(var(--space-12) + var(--space-6));
  text-align: center;
}

.segment-cta__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 3.4vw, var(--text-4xl));
  margin: 0 0 var(--space-4);
  font-variation-settings: 'opsz' 96;
}

.segment-cta__lede {
  color: var(--color-text-secondary);
  max-width: 48ch;
  margin: 0 auto var(--space-6);
  font-size: var(--text-lg);
  line-height: 1.55;
}

.segment-cta__price {
  display: block;
  margin-top: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------- Timeline (per-day grouping) ----------
   Reusable across the organizer trip detail and the public /t/{slug} surface.
   Lives in global CSS because Blazor scoped CSS doesn't reach the rendered
   children of EditForm and similar wrapper components — same reason the
   form-stack / form-row primitives are global.
   See documents/HANDOFF.md "Known gotchas" for the full rationale.

   Each day group is a native <details>/<summary> disclosure. Browser-native
   open/close state, accessible by default, no JS required (so the public
   view stays pure static SSR). Initial open state set per-day by the
   server-rendered `open` attribute. */

.timeline-day {
    margin-bottom: var(--space-6);
}

.timeline-day__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    cursor: pointer;
    user-select: none;
    list-style: none; /* hide the default ▸ marker on Firefox */
    margin: 0 0 var(--space-3);
    /* Promote the disclosure to a clearly tappable strip — soft surface,
       full padding, 44px+ tap target, brighter chevron. Reads as a button.
       When the day is open (see further down), the header loses its
       standalone border-radius and merges visually with the panel below,
       so the day reads as a single grouped surface. */
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--color-accent);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    /* `width: 100%` and font: inherit because TripDetail now uses a
       <button> here (vs the prior <summary>) — button defaults shrink to
       content width and use the user-agent font. */
    width: 100%;
    font-family: var(--font-mono);
    cursor: pointer;
    text-align: left;
    /* transform for the tap-feedback :active rule (see "Tap feedback"
       block below). */
    transition: background-color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

.timeline-day__header::-webkit-details-marker { display: none; } /* hide default ▸ marker on Safari */

.timeline-day__header:hover {
    background: var(--color-primary-soft);
    border-color: var(--color-primary-soft);
}

/* Open state — selectors target BOTH legacy <details>[open] (used by
   PublicTrip /t/{slug}) AND the new button + data-expanded pattern
   (TripDetail). One CSS, two markup hosts. */

.timeline-day[open],
.timeline-day[data-expanded="true"] {
    border: 1px solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border));
    border-radius: var(--radius-md);
    background: var(--color-bg-elevated);
    overflow: hidden;
}

.timeline-day[open] .timeline-day__header,
.timeline-day[data-expanded="true"] .timeline-day__header {
    background: var(--color-bg-elevated);
    border: 0;
    border-bottom: 1px solid color-mix(in srgb, var(--color-accent) 22%, var(--color-border));
    border-radius: 0;
    margin: 0;
}

.timeline-day[open] .timeline-day__header:hover,
.timeline-day[data-expanded="true"] .timeline-day__header:hover {
    background: color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-elevated));
}

/* Phase 9 — today's day group. A stronger accent border + soft fill
   so the eye lands on it in a multi-day trip timeline. Combined with
   the auto-expand-today seeding in both Razor pages, an organizer
   landing on the Itinerary tab during the trip sees today's events
   without any taps. "Today" is anchored to Trip.DestinationTimeZone
   — same single anchor the People-tab rails use. */
.timeline-day--today {
    border: 1px solid color-mix(in srgb, var(--color-accent) 55%, transparent);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-accent) 4%, var(--color-bg-elevated));
}
.timeline-day--today .timeline-day__header {
    background: color-mix(in srgb, var(--color-accent) 6%, transparent);
}
/* Keep the strong border even in the open state (the default open
   rule above mixes only 28% accent into the border, which is fainter
   than what we want for the "this is the day" treatment). */
.timeline-day--today[data-expanded="true"] {
    border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
}

.timeline-day__today-pill {
    display: inline-flex;
    align-items: center;
    margin-left: var(--space-2);
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    background: var(--color-accent);
    color: var(--color-text-inverse);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    line-height: 1.4;
}

/* Panel — present only on the TripDetail markup variant. The grid-
   template-rows 0fr -> 1fr trick is the same primitive used by the
   traveler row; see the .traveler-row__panel comment in
   TripDetail.razor.css for the full rationale. Inner div clips children
   during the sweep; opacity offset 80ms behind the height so the
   unfurl reads as "space opens, then content settles". */
.timeline-day__panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--duration-base) var(--ease-out);
}

.timeline-day[data-expanded="true"] .timeline-day__panel {
    grid-template-rows: 1fr;
}

.timeline-day__panel-inner {
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: opacity var(--duration-fast) 80ms var(--ease-out);
}

.timeline-day[data-expanded="true"] .timeline-day__panel-inner {
    opacity: 1;
}

.timeline-day__header-label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    flex: 1;
    min-width: 0;
}

.timeline-day__chevron {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: var(--color-accent);
    transition: transform var(--duration-fast) var(--ease-out);
}

.timeline-day[open] .timeline-day__chevron,
.timeline-day[data-expanded="true"] .timeline-day__chevron {
    transform: rotate(90deg);
}

.timeline-day__date {
    /* inherit from header (uppercase, mono, primary color) */
}

.timeline-day__summary {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    text-transform: none;
    letter-spacing: normal;
    flex-shrink: 0;
}

.timeline-day__list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* When the parent day is open the list flows inside the panel: tighter
   padding around the edge, no gap (rows are flush against each other
   and separated by sibling dividers below). */
.timeline-day[open] .timeline-day__list,
.timeline-day[data-expanded="true"] .timeline-day__list {
    padding: var(--space-1) var(--space-3);
    gap: 0;
}

.timeline-row {
    display: grid;
    grid-template-columns: 80px auto 1fr auto auto;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}

/* Inside an open day, regular rows lose their floating-card treatment
   (the parent panel provides the chrome) and stack flush. The
   all-arrived celebratory row is excluded — its sunset gradient is the
   whole point of the moment and reads cleanly as an accent inset card
   within the panel. */
.timeline-day[open] .timeline-row:not(.timeline-row--all-arrived),
.timeline-day[data-expanded="true"] .timeline-row:not(.timeline-row--all-arrived) {
    background: transparent;
    border: 0;
    border-radius: 0;
}

.timeline-day[open] .timeline-row + .timeline-row:not(.timeline-row--all-arrived),
.timeline-day[data-expanded="true"] .timeline-row + .timeline-row:not(.timeline-row--all-arrived) {
    border-top: 1px solid var(--color-border);
}

.timeline-row__time {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--color-text-secondary);
    text-align: right;
}

.timeline-row__icon {
    color: var(--color-text-muted);
    width: 24px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.timeline-row__icon svg {
    width: 18px;
    height: 18px;
}

.timeline-row--event .timeline-row__icon {
    color: var(--color-accent);
}

/* Emoji-glyph variant — used on event rows to mirror the type picker
   (🍽 dinner, 🥐 lunch, 🍹 drinks, 🎯 activity, 🚐 transport, ✏️ other).
   Sets a font-size matching the SVG icons it replaced; flex centering
   on .timeline-row__icon keeps the glyph aligned with time + title. */
.timeline-row__icon--glyph {
    font-size: 16px;
    line-height: 1;
}

.timeline-row__body { min-width: 0; }

.timeline-row__title {
    margin: 0;
    color: var(--color-text-primary);
    font-weight: 500;
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
}

.timeline-row__meta,
.timeline-row__notes {
    margin: var(--space-1) 0 0;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}

.timeline-row__notes {
    color: var(--color-text-secondary);
    font-style: italic;
}

.timeline-row__links {
    margin: var(--space-2) 0 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.timeline-row__chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--color-bg-sunken);
    color: var(--color-text-secondary);
    border: 1px solid var(--color-border);
    font-size: var(--text-xs);
    font-weight: 500;
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}

.timeline-row__chip:hover {
    background: var(--color-primary-soft);
    color: var(--color-accent);
    border-color: var(--color-primary-soft);
}

.timeline-row__sep { color: var(--color-text-muted); opacity: 0.5; margin: 0 var(--space-1); }

.timeline-row__flight-traveler { color: var(--color-text-primary); font-weight: 500; }
.timeline-row__flight-route { font-family: var(--font-mono); color: var(--color-text-secondary); }
.timeline-row__flight-code { font-family: var(--font-mono); color: var(--color-text-muted); font-size: var(--text-xs); }

.timeline-row__pill {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--color-bg-sunken);
    color: var(--color-text-secondary);
    border: 1px solid var(--color-border);
    white-space: nowrap;
}

/* THE STATUS COLOUR GRAMMAR, from ios/Vrolu/Theme.swift
   (FlightStatusBadge.color) — the app is upstream here and the web had
   drifted three states behind it (founder QA, Aug 28: "the color scheme is
   wrong, our docs are outdated").

   Every COOL colour tells the journey's story — green all good, cyan in the
   air now, mint rolling on the ground, blue at the gate — and the WARM
   family means exclusively "look at this": orange delayed, red cancelled.
   That is the whole rule, and it's why the three below moved.

   "On time" and "Boarding" are GREEN, not a grey default and not amber
   (founder call, July 17): white-at-60% read as DISABLED in a list, and
   amber sat visually beside orange = delayed, so the one all-is-well status
   wore a caution colour. Green means "all good, nothing to do". */
.timeline-row__pill--boarding,
.timeline-row__pill--scheduled {
    background: rgba(52, 199, 89, 0.16);
    color: var(--color-success);
    border-color: transparent;
}

/* "In flight" is CYAN, not the lit sunset (founder colour-grammar pass,
   July 16). Cyan already meant "live" everywhere else — the map trail, the
   live dot, the Live Activity's airborne palette — while the amber pill
   made the airborne state look like a warning. */
.timeline-row__pill--departed {
    background: rgba(41, 214, 255, 0.18);
    color: var(--color-live);
    border-color: transparent;
}

/* Delayed wears iOS's system orange (FlightStatusBadge → .orange), not
   the brand amber — amber is airborne, orange is trouble. */
.timeline-row__pill--delayed {
    background: rgba(255, 149, 0, 0.16);
    color: var(--color-warning);
    border-color: transparent;
}

/* Cancelled, possibly-cancelled and diverted all wear the red family
   (iOS: one Color(1.0, 0.45, 0.45) for the three). "Possibly cancelled"
   had been falling through to the grey default pill — the softest state in
   the palette on the row that most needs a second look. */
.timeline-row__pill--cancelled,
.timeline-row__pill--cancelled-uncertain,
.timeline-row__pill--diverted {
    background: rgba(255, 115, 115, 0.16);
    color: var(--color-danger);
    border-color: transparent;
}

/* v2.0 taxi — the mint the app wears for both taxi phases (#73E6C2).
   Between wheels-down and the gate, and between pushback and wheels-up,
   the plane is moving but not flying: its own colour, not landed's. */
.timeline-row__pill--taxi {
    background: rgba(115, 230, 194, 0.16);
    color: var(--color-taxi);
    border-color: transparent;
}

/* "Arrived" is the calm LANDED BLUE, not success green. Green is the
   all-good pre-departure state; blue means officially at the gate, and the
   map's completed path settles into the same blue so the trail and the pill
   above it agree. */
.timeline-row__pill--landed {
    background: rgba(115, 179, 255, 0.18);
    color: var(--color-landed);
    border-color: transparent;
}

/* Day-3 — per-flight live-data freshness badge. Sits next to the
   status pill on the timeline row. Neutral (Stale) and warning
   (VeryStale) variants only — Fresh and NeverFetched render nothing.
   Uses brand tokens; do not introduce new colors here. */
.timeline-row__freshness {
    display: inline-flex;
    align-items: center;
    font-family: var(--font-body);
    font-size: var(--text-xs);
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    background: var(--color-bg-sunken);
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
    white-space: nowrap;
    margin-left: var(--space-2);
}

.timeline-row__freshness--warning {
    background: rgba(255, 179, 71, 0.16);
    color: var(--color-warning, var(--color-accent));
    border-color: transparent;
}

/* Flight-history affordance — the entire flight row is the click target
   (opens the read-only history drawer). A chevron pinned to the right edge
   signals it; the row lifts on hover. The chevron is absolutely positioned
   so it stays OUT of the row's 5-column grid flow — otherwise it lands in an
   undefined 6th cell and wraps to its own line. Events share the row markup
   but aren't clickable. */
.timeline-row--clickable {
    cursor: pointer;
    position: relative;
    padding-right: calc(var(--space-4) + 26px);
}
@media (hover: hover) {
    .timeline-row--clickable:hover { background: var(--color-bg-sunken); }
    .timeline-row--clickable:hover .timeline-row__chevron { color: var(--color-text); }
}
.timeline-row--clickable:focus-visible {
    outline: 2px solid var(--color-accent, #FFB347);
    outline-offset: 2px;
}
.timeline-row__chevron {
    position: absolute;
    right: var(--space-4);
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    color: var(--color-text-muted);
}
.timeline-row__chevron svg { width: 18px; height: 18px; }

/* Scroll-lock while the static flight-history drawer (PublicTrip) is open —
   public-trip.js flips this class. Global because scoped CSS can't reach
   <body>. */
body.vrolu-drawer-open { overflow: hidden; }

/* Read-only flight-history drawer timeline. */
.flight-history { display: flex; flex-direction: column; gap: var(--space-4); }
.flight-history__summary { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.flight-history__route { margin: 0; font-weight: 600; }
.flight-history__empty { margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }
.flight-history__list { list-style: none; margin: 0; padding: 0; }
.flight-history__item {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-2) 0 var(--space-2) var(--space-4);
    margin-left: 5px;
    border-left: 2px solid var(--color-border);
}
.flight-history__item:last-child { border-left-color: transparent; }
.flight-history__dot {
    position: absolute;
    left: -5px;
    top: 14px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-accent);
}
.flight-history__body { flex: 1 1 auto; min-width: 0; }
.flight-history__label { margin: 0; font-weight: 500; }
.flight-history__meta { margin: 2px 0 0; color: var(--color-text-muted); font-size: var(--text-sm); }
.flight-history__when { flex-shrink: 0; color: var(--color-text-muted); font-size: var(--text-xs); white-space: nowrap; }

/* Day-3 — trip-wide outage banner. Renders just above the itinerary
   section when CircuitBreakerState.IsOpen, dismissable per-session.
   Tone: informational, not alarming. Uses --color-warning for the
   icon tint and a soft warning background; copy frames it as a
   "hiccup," not a failure. Schedules/reminders keep working. */
.trip-outage {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    margin: 0 0 var(--space-4) 0;
    background: rgba(255, 179, 71, 0.12);
    border: 1px solid rgba(255, 179, 71, 0.32);
    border-radius: var(--radius-lg, 12px);
    color: var(--color-text-primary);
}

.trip-outage__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    color: var(--color-warning, var(--color-accent));
}

.trip-outage__icon svg {
    width: 22px;
    height: 22px;
}

.trip-outage__body {
    flex: 1 1 auto;
    min-width: 0;
}

.trip-outage__title {
    margin: 0 0 2px 0;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: var(--text-sm);
    color: var(--color-text-primary);
}

.trip-outage__copy {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--color-text-secondary);
}

.trip-outage__dismiss {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-text-muted);
    border: 1px solid transparent;
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}

.trip-outage__dismiss:hover {
    background: rgba(255, 248, 242, 0.08);
    color: var(--color-text-primary);
}

.trip-outage__dismiss svg {
    width: 16px;
    height: 16px;
}

@media (max-width: 480px) {
    .trip-outage {
        padding: var(--space-3);
    }
    .timeline-row__freshness {
        font-size: 10px;
        padding: 1px var(--space-2);
    }
}

.timeline-row__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
}

.timeline-row__icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-text-muted);
    border: 1px solid transparent;
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}

.timeline-row__icon-btn:hover:not(:disabled) {
    background: var(--color-bg-sunken);
    color: var(--color-text-primary);
}

.timeline-row__icon-btn--danger:hover:not(:disabled) {
    background: var(--color-primary-soft);
    color: var(--color-danger);
}

.timeline-row__icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.timeline-row__icon-btn svg { width: 16px; height: 16px; }

.timeline-row__edit {
    grid-column: 1 / -1;
    background: var(--color-bg-sunken);
    border: 1px solid var(--color-primary-soft);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    margin: calc(-1 * var(--space-3)) calc(-1 * var(--space-4));
}

/* Phase 8 polish — "Everyone's here ✨" celebratory marker rendered as the
   final entry on the day every traveler's last leg has landed. Visual
   weight: warmer than a regular row, but not so loud it competes with the
   day's actual content. The ✨ sits in the time slot, the title says it
   plain. No actions, no pill — the moment is the whole row. */
.timeline-row--all-arrived {
    background: linear-gradient(
        135deg,
        color-mix(in srgb, var(--color-accent) 22%, transparent),
        color-mix(in srgb, var(--color-primary) 14%, transparent)
    );
    border-top: 1px solid color-mix(in srgb, var(--color-accent) 40%, transparent);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    margin-top: var(--space-2);
}

.timeline-row--all-arrived .timeline-row__time {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    color: var(--color-accent);
}

.timeline-row--all-arrived .timeline-row__title {
    color: var(--color-text-primary);
    font-family: var(--font-display);
    font-weight: 500;
    font-style: italic;
    font-variation-settings: 'opsz' 36;
}

/* Hide the empty icon cell on the celebratory row — its visual weight
   already lives in the ✨ in the time slot. */
.timeline-row--all-arrived .timeline-row__icon--all-arrived {
    visibility: hidden;
}

@media (max-width: 720px) {
    .timeline-row {
        grid-template-columns: auto 1fr auto;
        grid-template-rows: auto auto auto;
        gap: var(--space-2) var(--space-3);
    }
    .timeline-row__time { grid-column: 1; grid-row: 1; text-align: left; }
    .timeline-row__icon { grid-column: 2; grid-row: 1; text-align: left; }
    .timeline-row__pill {
        grid-column: 3;
        grid-row: 1;
        justify-self: end;
        /* Compact on mobile so it has breathing room from the viewport
           edge — was rendering tight against the right side at 375px. */
        font-size: 10px;
        padding: 2px var(--space-2);
        letter-spacing: 0.04em;
    }
    .timeline-row__body { grid-column: 1 / -1; grid-row: 2; }
    /* Freshness pill — long "Live data unavailable — last updated N min
       ago" copy with white-space:nowrap was overflowing its auto-flow
       grid cell on mobile, pushing the row width past the viewport.
       Span full width on its own row and allow wrapping. */
    .timeline-row__freshness {
        grid-column: 1 / -1;
        margin-left: 0;
        white-space: normal;
        line-height: 1.4;
        align-self: start;
    }
    .timeline-row__actions { grid-column: 1 / -1; justify-self: end; }
    .timeline-row__edit { margin: 0; }
}

.auth__devnote {
  margin-top: var(--space-6);
  padding: var(--space-5);
  background: var(--color-bg-elevated);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

.auth__devnote p { margin: 0 0 var(--space-3); color: var(--color-text-secondary); }
.auth__devnote p:last-child { margin: 0; }

/* ---------- Masthead — the iOS 2.0 "one heading per screen" pattern ----------
   Ported from the app's four tab headers (MyFlightsView etc., Aug 5):
   ONE Plex-Bold sentence-case title in second person ("Your trips"),
   with a muted MONO state line directly below carrying telemetry
   ("3 upcoming · 2 past") — mono belongs to data, not decoration.
   No eyebrow above the title; the old amber mono eyebrows spent the
   palette's alert color on the least urgent element on screen.
   Global because multiple pages (MyTrips, AdminDashboard) share it. */

.dash-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-8);
}

.dash-header__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-3xl);
  margin: 0;
}

.dash-header__state {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: var(--space-1) 0 0;
}

.dash-header__cta {
  flex-shrink: 0;
}

/* ---------- Utility classes used across layouts ---------- */

.container-page {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding-left: var(--space-5);
  padding-right: var(--space-5);
}

.text-muted { color: var(--color-text-muted); }
.text-secondary { color: var(--color-text-secondary); }
.font-display { font-family: var(--font-display); }
.font-mono { font-family: var(--font-mono); }

/* ---------- AppLayout NavLink-target classes ----------
   The following classes are applied to elements rendered by the
   Blazor <NavLink> component (the <a> tag). Blazor scoped CSS does
   NOT propagate to child-component-rendered elements, so these rules
   MUST live in global CSS — earlier attempts to scope them inside
   AppLayout.razor.css silently never matched. Per Microsoft's
   docs: "Elements rendered from child components don't have CSS
   isolation applied." Reach the elements either with ::deep from
   the parent's scoped file or (cleaner) place the rules globally.

   Applies to:
     .app__nav-link    — suitcase (Dashboard) + book (Stories) nav links
     .app__user        — the avatar + email link to /Account/Manage

   Mobile-collapse overrides for these classes also live below in the
   shared media query so the rules apply where they're needed. */

.app__nav-link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    /* Transparent border reserves the 2px box so toggling .active
       doesn't cause a layout shift. */
    border: 1px solid transparent;
    color: var(--color-text-secondary);
    text-decoration: none;
    font-weight: 500;
    font-size: var(--text-sm);
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

.app__nav-link:hover {
    background: var(--color-bg-elevated);
    color: var(--color-text-primary);
}

/* Active-route style — azure border + subtle azure fill + azure text
   (July 16: selection is a STATE, and states are azure — same grammar as
   iOS). Same vocabulary as .trip-tabs__tab--active and .icon-button.active
   so the app uses one "selected" language. */
.app__nav-link.active {
    border-color: color-mix(in srgb, var(--color-action) 50%, var(--color-border));
    background: color-mix(in srgb, var(--color-action) 10%, var(--color-bg-elevated));
    color: var(--color-action);
}

/* Desktop sidebar section label ("Learn more" above the
   marketing-adjacent destinations). Matches the visual
   vocabulary of .nav-drawer-panel__section-label so the mobile
   drawer and the desktop sidebar use the same orientation cues. */
.app__nav-section-label {
    margin: var(--space-3) 0 var(--space-1);
    padding: 0 var(--space-4);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-text-muted);
}

.app__user {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    text-decoration: none;
    color: var(--color-text-secondary);
    font-size: var(--text-sm);
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

.app__user:hover {
    background: var(--color-bg-elevated);
    color: var(--color-text-primary);
}

.app__user.active {
    border-color: color-mix(in srgb, var(--color-accent) 50%, var(--color-border));
    background: color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-elevated));
    color: var(--color-accent);
}

/* Mobile collapse — the NavLink-rendered elements morph from
   icon+text rows (desktop sidebar) to 44x44 icon-only buttons
   (mobile top bar). Same target classes as above; same global-
   CSS reasoning. */
@media (max-width: 720px) {
    .app__nav-link,
    .app__user {
        width: 44px;
        height: 44px;
        padding: 0;
        gap: 0;
        border-radius: var(--radius-pill);
        border: 1px solid transparent;
        justify-content: center;
    }

    /* Hide the text label inside the suitcase / stories NavLinks on
       mobile — the icon-only collapsed shape doesn't have room for
       "Dashboard" or "Stories". The <span> here is rendered as
       NavLink ChildContent so it lives in the global rule alongside
       the rest of the mobile collapse. */
    .app__nav-link span { display: none; }

    .app__nav-link:hover,
    .app__user:hover {
        background: var(--color-primary-soft);
        color: var(--color-accent);
    }

    .app__nav-link.active,
    .app__user.active {
        border-color: color-mix(in srgb, var(--color-accent) 50%, var(--color-border));
        background: color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-elevated));
        color: var(--color-accent);
    }
}

/* ---------- Tap feedback ----------
   Subtle scale-down on :active for interactive primitives. Gives the
   "the app heard me" feel native iOS apps have — every tap registers
   visually before any actual transition fires (route change, drawer
   open, expand). The press scales the element to 97% of its size for
   the duration of the press; release returns to baseline.

   Smoothness:
   - Elements whose base `transition` list includes `transform` (.btn,
     .icon-button, .app__nav-link) animate the scale-down + scale-up
     over --duration-fast.
   - Elements without transform in their transition snap instantly.
     That's still acceptable feedback — the press IS the cue.

   Disabled elements skipped via :not(:disabled). .btn's existing
   active style was `transform: translateY(0)` to undo the hover lift;
   stacked with scale(0.97) below so a button press both snaps down
   AND shrinks for a tactile feel.

   iOS Mobile Safari quirk: :active does NOT fire reliably on tap
   unless the element has `cursor: pointer`. Without it, iOS treats
   taps as too brief to enter the active state — visually the scale-
   down animation never plays. Edge / Chrome desktop simulators
   don't reproduce this. The matching cursor declaration immediately
   below the :active block ensures the press-feedback rule actually
   fires on real iPhones, not just in dev tools simulation. */

button:not(:disabled),
[role="button"]:not(:disabled),
.icon-button:not(:disabled),
.traveler-row__chip:not(:disabled),
.timeline-day__header:not(:disabled),
.trip-tabs__tab:not(:disabled),
.dash-trip-card,
.home-segments__card,
.traveler-row__action:not(:disabled),
.traveler-row__add-flight:not(:disabled) {
    cursor: pointer;
}

button:not(:disabled):active,
[role="button"]:not(:disabled):active,
.icon-button:not(:disabled):active,
.traveler-row__chip:not(:disabled):active,
.timeline-day__header:not(:disabled):active,
.trip-tabs__tab:not(:disabled):active,
.dash-trip-card:active,
.home-segments__card:active,
.traveler-row__action:not(:disabled):active,
.traveler-row__add-flight:not(:disabled):active {
    transform: scale(0.97);
}

.btn:not(:disabled):not(.disabled):active {
    transform: translateY(0) scale(0.97);
}


/* ---------- Blazor reconnect / error UI ---------- */

#blazor-error-ui {
  background: var(--color-bg-elevated);
  color: var(--color-text-primary);
  border-top: 2px solid var(--color-danger);
  bottom: 0;
  display: none;
  left: 0;
  padding: var(--space-3) var(--space-5);
  position: fixed;
  width: 100%;
  z-index: 1000;
  box-shadow: var(--shadow-md);
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: var(--space-4);
  top: var(--space-3);
}

.blazor-error-boundary {
  background: var(--color-danger);
  color: var(--color-text-inverse);
  padding: var(--space-4);
  border-radius: var(--radius-md);
}

.blazor-error-boundary::after { content: "An error has occurred."; }

.darker-border-checkbox.form-check-input { border-color: var(--color-border-strong); }

/* ────────── Places typeahead dropdown ──────────
   Used by <PlaceTypeahead> (NewTrip destination, AddEvent / EditEvent
   location). The visible <input> already styles itself via .form-control
   — these rules only target the suggestion menu that hangs underneath. */

.place-typeahead {
    position: relative;
}

.place-typeahead__suggestions {
    list-style: none;
    margin: var(--space-1) 0 0;
    padding: var(--space-1);
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 30;
    background: var(--color-bg-elevated);
    border: 1px solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border));
    border-radius: var(--radius-md);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
    max-height: 320px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

.place-typeahead__suggestion {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    cursor: pointer;
    min-height: 44px;          /* iOS tap-target floor */
    justify-content: center;
}

.place-typeahead__suggestion:hover,
.place-typeahead__suggestion--active {
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}

.place-typeahead__suggestion-main {
    color: var(--color-text-primary);
    font-weight: 500;
    font-size: var(--text-sm);
}

.place-typeahead__suggestion-sub {
    color: var(--color-text-muted);
    font-size: var(--text-xs);
}

/* ────────── Trip destination hero photo (Phase 8 polish) ──────────
   Wide image strip at the top of TripDetail and PublicTrip pages when
   the trip's destination has a captured Places photo. Renders nothing
   when DestinationPhotoName is null (legacy trips, free-typed
   destinations) — markup is gated upstream so this CSS only matters
   when an image is present.

   Two variants: organizer view (compact, leaves room for breadcrumb +
   header) and public view (slightly taller, sets the mood for travelers
   landing from an iMessage tap). Both bleed to the page edges for the
   "magazine cover" feel; container-page padding picks up below. */
.trip-hero {
    position: relative;
    margin: 0 0 var(--space-6);
    padding: 0;
    aspect-ratio: 21 / 9;
    max-height: 280px;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--color-bg-elevated);
    /* Gold-accent border to match the open-panel chrome vocabulary. */
    border: 1px solid color-mix(in srgb, var(--color-accent) 22%, var(--color-border));
}

.trip-hero--public {
    aspect-ratio: 16 / 9;
    max-height: 360px;
}

/* "Magazine cover" variant — image is the canvas; title + meta + status
   pill render inside .trip-hero__overlay on top of a bottom-up dark
   gradient for legibility. Used on TripDetail and PublicTrip when the
   organizer captured a destination photo from Google Places. */
.trip-hero--overlay {
    aspect-ratio: 16 / 9;
    max-height: 360px;
}

.trip-hero__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Bottom-up dark gradient lives on the overlay div itself (was a
   separate .trip-hero__gradient layer, but stacking issues with the
   sibling <img> made it unreliable across browsers). The overlay
   already renders text on top of the image, so we know its z-stack
   is correct — painting the gradient as its background guarantees
   the darkening lands wherever the text sits.

   Tuned for white text on any photo — bright skies, sandy beaches,
   snowy slopes all need a substantial floor of darkening to keep the
   title legible. */
.trip-hero__overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-5) var(--space-6);
    color: #fff;
    pointer-events: none;
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.92) 0%,
        rgba(0, 0, 0, 0.78) 25%,
        rgba(0, 0, 0, 0.55) 50%,
        rgba(0, 0, 0, 0.32) 75%,
        rgba(0, 0, 0, 0.15) 100%
    );
}

.trip-hero__title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(var(--text-3xl), 5vw, var(--text-5xl));
    line-height: 1.05;
    letter-spacing: -0.01em;
    margin: 0;
    color: #fff;
    /* Subtle drop-shadow so even busy photos don't break the title. */
    text-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
}

.trip-hero__meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    color: rgba(255, 255, 255, 0.92);
    font-size: var(--text-sm);
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
}

/* Status badge on the hero (replaces the breadcrumb pill when a photo
   is present). Translucent dark fill so the badge reads against any
   photo without fighting the gradient; status-specific tint applied
   on top via the --draft/--archived modifier. */
.trip-hero__status {
    align-self: flex-start;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.45);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.35);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.trip-hero__status--draft {
    background: color-mix(in srgb, var(--color-accent) 78%, transparent);
    color: var(--color-text-primary);
    border-color: transparent;
}

.trip-hero__status--archived {
    background: rgba(0, 0, 0, 0.55);
    color: rgba(255, 255, 255, 0.78);
    border-color: rgba(255, 255, 255, 0.20);
}

@media (max-width: 720px) {
    .trip-hero {
        aspect-ratio: 16 / 10;
        max-height: 220px;
        /* True viewport-edge bleed regardless of the wrapping container's
           padding. .app__content (TripDetail) uses --space-4 horizontal
           padding while .container-page (PublicTrip) uses --space-5, so a
           single negative-margin value can't cancel both. The 50%/50vw
           trick centers a 100vw-wide element relative to its parent's
           content box and ignores container padding entirely. */
        width: 100vw;
        max-width: 100vw;
        margin-left: calc(50% - 50vw);
        margin-right: calc(50% - 50vw);
        border-radius: 0;
        border-left: 0;
        border-right: 0;
    }

    /* Compacted from the original aspect-ratio: 4/5 + max-height: 420px.
       The portrait magazine-cover treatment looked dramatic but ate half
       the mobile viewport before the user could see any trip content,
       and the sticky utility header above now carries the trip name +
       meta anyway. 16/10 + 200px cap keeps the photo as a visual anchor
       at the top without dominating the fold. */
    .trip-hero--overlay {
        aspect-ratio: 16 / 10;
        max-height: 200px;
    }

    .trip-hero__overlay {
        padding: var(--space-3) var(--space-4);
    }

    /* Trim the overlay title in the compacted hero so it doesn't shout
       louder than the sticky bar's small name above. Sticky carries the
       persistent identity; the hero is now mood / context. */
    .trip-hero--overlay .trip-hero__title {
        font-size: var(--text-xl);
    }
}

/* ────────── Account page (Phase 8 polish redesign) ──────────
   The Identity scaffold's Manage pages stacked a marketing-sized
   display-face title over a Bootstrap sidebar of every sub-page (Profile /
   Email / Password / 2FA / Personal data / Passkeys / External logins)
   with default-blue <a> links. Replaced with a single self-contained
   page; sub-pages reachable via in-page CTAs. */

.account {
    max-width: 640px;
    margin: 0 auto;
    padding-bottom: var(--space-10);
}

.account__header {
    margin-bottom: var(--space-6);
}

.account__title {
    font-size: var(--text-4xl);
    margin: 0 0 var(--space-2);
    font-variation-settings: 'opsz' 96;
    line-height: 1.05;
}

.account__subtitle {
    margin: 0;
    color: var(--color-text-secondary);
    font-size: var(--text-base);
}

.account__loading {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* Identity strip — large gradient avatar + email + member-since. The
   only place on the page where the sunset gradient appears, mirroring
   how the dashboard sidebar's tiny avatar uses the same gradient at
   ~28px. */
.account__identity {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-8);
}

.account__avatar {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-pill);
    background: var(--gradient-sunset);
    color: var(--color-text-inverse);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-2xl);
    font-variation-settings: 'opsz' 36;
    flex-shrink: 0;
}

.account__identity-meta { min-width: 0; }

.account__identity-email {
    margin: 0 0 var(--space-1);
    font-weight: 500;
    color: var(--color-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account__identity-since {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* Sections — borrow the same panel chrome from open .timeline-day
   (gold-tinted border, header-flush-with-divider treatment) so the
   account surface visually rhymes with the rest of the polish pass. */
.account__section {
    border: 1px solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border));
    border-radius: var(--radius-md);
    background: var(--color-bg-elevated);
    margin-bottom: var(--space-6);
    overflow: hidden;
}

.account__section-header {
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid color-mix(in srgb, var(--color-accent) 22%, var(--color-border));
}

.account__section-title {
    margin: 0 0 var(--space-1);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--color-text-primary);
}

.account__section-lede {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* Method rows — name + detail + "Active" pill. iOS Settings style.
   Linked rows have a primary-soft pill; unlinked rows show no pill so
   the empty status slot reads as "this method isn't set up yet". */
.account__methods {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}

.account__method {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--color-border);
}

.account__method:first-child { border-top: 0; }

.account__method-body {
    flex: 1;
    min-width: 0;
}

.account__method-name {
    margin: 0 0 2px;
    font-weight: 500;
    color: var(--color-text-primary);
    font-size: var(--text-sm);
}

.account__method-detail {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account__method-status {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-muted);
    padding: 2px var(--space-3);
    border-radius: var(--radius-pill);
    min-width: 4ch;
    text-align: center;
}

.account__method--linked .account__method-status {
    background: color-mix(in srgb, var(--color-success) 16%, transparent);
    color: var(--color-success);
}

.account__section-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--color-border);
}

/* The .account__methods list above already provides its own border-top
   per row; when the actions row follows the list we don't double up. */
.account__methods + .account__section-actions {
    border-top: 1px solid var(--color-border);
}

.account__action {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    color: var(--color-text-secondary);
    font-size: var(--text-sm);
    font-weight: 500;
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}

.account__action:hover {
    background: var(--color-bg-sunken);
    color: var(--color-accent);
}

.account__action--danger {
    color: var(--color-danger);
}

.account__action--danger:hover {
    background: color-mix(in srgb, var(--color-danger) 10%, transparent);
    color: var(--color-danger);
}

/* Danger-zone section is muted by default — the section's gold border
   would read as "look at me!" which is exactly wrong for delete-account
   adjacency. Drop to neutral border + secondary-coded title. */
.account__section--danger {
    border-color: var(--color-border-strong);
}

.account__section--danger .account__section-header {
    border-bottom-color: var(--color-border);
}

/* ───── Subscribe toast (Option B confirmation pattern) ─────
   Plain-JS toast for the bell's direct-unsubscribe + Undo flow.
   Lives in app.css (not PublicTrip.razor.css) because subscribe.js
   appends the toast to <body> — Blazor's scoped-CSS attribute on
   PublicTrip's rules wouldn't match a body child. Bottom-center on
   mobile, lifts in from below, auto-removes after 5s. */
.vrolu-subscribe-toast {
    position: fixed;
    left: 50%;
    bottom: max(var(--space-5), env(safe-area-inset-bottom, 0));
    transform: translateX(-50%);
    z-index: 200;
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--color-bg-elevated);
    color: var(--color-text-primary);
    border: 1px solid var(--color-border-strong);
    box-shadow: 0 10px 30px -8px rgba(0, 0, 0, 0.35);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: 500;
    max-width: calc(100vw - var(--space-6));
    animation: vrolu-subscribe-toast-in var(--duration-base) var(--ease-out);
}

@keyframes vrolu-subscribe-toast-in {
    from { transform: translate(-50%, 16px); opacity: 0; }
    to   { transform: translate(-50%, 0);    opacity: 1; }
}

.vrolu-subscribe-toast--error {
    border-color: color-mix(in srgb, var(--color-danger) 50%, var(--color-border-strong));
    color: var(--color-danger);
}

.vrolu-subscribe-toast__msg {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.vrolu-subscribe-toast__undo {
    flex-shrink: 0;
    padding: var(--space-1) var(--space-3);
    border: 0;
    background: var(--color-primary);
    color: var(--color-text-inverse);
    border-radius: var(--radius-pill);
    font-family: var(--font-body);
    font-size: var(--text-xs);
    font-weight: 500;
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

.vrolu-subscribe-toast__undo:hover {
    background: var(--color-primary-strong);
}

/* ────────── Trip page panels (Phase 9 — 3-tab IA) ──────────
   TripDetail and PublicTrip render their three section panels (Itinerary,
   People, Stay) into the DOM at once; CSS hides all but the active one
   based on the wrapper's `data-active-tab` attribute. The attribute is
   driven by trip-tabs.js (reads window.location.hash). All-in-DOM means
   tab switches are instant — no re-fetch, no DOM rebuild, in-flight
   drawer state on hidden tabs survives a switch-and-return.

   These rules live in app.css (not in TripTabsNav.razor.css) because they
   target ancestors of the component scope — Blazor CSS isolation can't
   reach upward. The .trip-page wrapper sits on the page (TripDetail /
   PublicTrip), not inside the nav. */

.trip-page [data-tab] { display: none; }

.trip-page[data-active-tab="itinerary"] [data-tab="itinerary"],
.trip-page[data-active-tab="people"] [data-tab="people"],
.trip-page[data-active-tab="stay"] [data-tab="stay"] {
    display: block;
}

/* Default — no JS / before first applyHash. Shows Itinerary so the page
   isn't blank during the first paint of a freshly-loaded view. */
.trip-page:not([data-active-tab]) [data-tab="itinerary"] { display: block; }

/* Mobile bottom-bar clearance — the fixed .trip-tabs-nav floats above
   the viewport's bottom edge and overlays anything that scrolls beneath
   it. Reserve space on the body itself (not on .trip-page) so sibling
   chrome — footers, branding rows, the "Made with Vrolu" line in
   PublicTripLayout — clears the bar too, not just panel content.
   The :has() lookup limits the rule to pages that actually have a
   .trip-page in the DOM; everywhere else, no extra body padding.
   Magic number tracks the nav's min-height (56px) + vertical padding
   (~16px) + safe-area inset. */
@media (max-width: 720px) {
    body:has(.trip-page) {
        padding-bottom: calc(72px + env(safe-area-inset-bottom));
    }
}

/* ────────── People-panel rails + status pills (Phase 9) ──────────
   Arrivals / Departures horizontal-scroll rails at the top of the
   People tab on both TripDetail and PublicTrip. Same markup on both,
   so the styling lives here in app.css instead of duplicating across
   the two scoped CSS files.

   Auto-hides happen at the Razor level (the @if (count > 0) gates).
   No CSS-side empty-state.

   Status-pill classes (.traveler-status--{kind}) double as both the
   rail-chip status indicator AND the meta-line pill on the existing
   traveler-row chip below — one selector, two spots. */

.people-rail {
    margin: 0 0 var(--space-5);
}

.people-rail__title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 500;
}

.people-rail__title-icon {
    font-size: var(--text-base);
}

/* Horizontal-scroll list of chips. Hide the native scrollbar — the
   chips telegraph that scroll is possible (right edge fades on
   overflow). Touch-scroll snapping rough-aligns each chip on swipe. */
.people-rail__list {
    display: flex;
    gap: var(--space-3);
    padding: 0;
    margin: 0;
    list-style: none;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

.people-rail__list::-webkit-scrollbar { display: none; }

.people-rail__chip {
    flex-shrink: 0;
    scroll-snap-align: start;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 240px;
    max-width: 320px;
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}

.people-rail__chip-avatar {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-pill);
    background: linear-gradient(135deg, var(--color-primary-soft), var(--color-accent-soft, var(--color-bg-sunken)));
    color: var(--color-text-primary);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
}

.people-rail__chip-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}

.people-rail__chip-name {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--color-text-primary);
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.people-rail__chip-time {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Status pill — base. The traveler-status--{kind} modifiers below
   recolor the pill per state. Same class is used both on rail chips
   and on the meta line of the existing traveler-row chip in the
   panel below; sizing is intentionally uniform so the two surfaces
   read as one vocabulary. */
.traveler-status {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.02em;
    background: var(--color-bg-sunken);
    color: var(--color-text-muted);
    white-space: nowrap;
    flex-shrink: 0;
}

/* Per-kind tints. Tokens map to the existing semantic colors where
   they exist; in-flight uses a sky-blue hardcode since the palette
   doesn't ship a --color-info and reusing primary (coral) collides
   visually with cancelled. The trio reads as a movement gradient:
   orange (boarding/departs) → blue (in-flight) → green (landed). */
.traveler-status--in-flight {
    background: rgba(96, 165, 250, 0.16);
    color: rgb(125, 180, 250);
}

.traveler-status--landed {
    background: color-mix(in srgb, var(--color-success) 14%, transparent);
    color: var(--color-success);
}

.traveler-status--delayed,
.traveler-status--boarding {
    background: color-mix(in srgb, var(--color-warning, #FF9500) 14%, transparent);
    color: var(--color-warning, #FF9500);
}

.traveler-status--cancelled {
    background: color-mix(in srgb, var(--color-danger, #FF7373) 14%, transparent);
    color: var(--color-danger, #FF7373);
}

.traveler-status--departs-today {
    background: color-mix(in srgb, var(--color-accent) 18%, transparent);
    color: var(--color-text-primary);
}

.traveler-status--trip-ready,
.traveler-status--no-flights,
.traveler-status--local,
.traveler-status--watching {
    background: var(--color-bg-sunken);
    color: var(--color-text-muted);
}

/* Inside the existing traveler-row meta — flatten the pill so it
   reads as label (not a chip badge) when it's nested in a
   compact row. */
.traveler-row__meta.traveler-status {
    background: transparent;
    padding: 0;
    /* Keep the color tints (the per-kind rules above set color); just
       drop the chip chrome. */
}

@media (max-width: 480px) {
    .people-rail__chip {
        min-width: 200px;
    }
}



/* ---------------------------------------------------------------
   Mobile hamburger nav drawer (.nav-drawer)
   ---------------------------------------------------------------
   Global primitive shared by MarketingLayout and AppLayout. Both
   layouts render a `[data-marketing-nav-toggle]` button + a
   `.nav-drawer-overlay` + `.nav-drawer-panel` element; the
   js/marketing-nav.js module (document-level click delegation)
   flips `marketing-nav--open` on <html>, and the rules below
   handle the slide-in animation, backdrop, and scroll lock.
   Same selectors live globally (here) rather than in either
   layout's scoped CSS because both layouts need to match the
   selectors, and Blazor scoped CSS appends a per-layout
   [b-xxxx] attribute that would prevent cross-layout matching.

   Layout-specific overrides (z-index conflicts with sticky
   headers, etc.) stay in the respective .razor.css. The base
   visual + open/close mechanics are right here. */

.nav-drawer-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 180ms ease-out;
    z-index: 80;
}

.nav-drawer-panel {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(86vw, 360px);
    background: var(--color-bg-base);
    border-right: 1px solid var(--color-border);
    box-shadow: 8px 0 24px rgba(0, 0, 0, 0.3);
    transform: translateX(-100%);
    transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
    z-index: 90;
    display: flex;
    flex-direction: column;
    padding: var(--space-4) var(--space-5) var(--space-6);
    gap: var(--space-4);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

.nav-drawer-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nav-drawer-panel__title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    color: var(--color-text-muted);
    letter-spacing: 0.02em;
}

.nav-drawer-panel__close {
    width: 36px;
    height: 36px;
}

.nav-drawer-panel__nav {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.nav-drawer-panel__nav a {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    color: var(--color-text-primary);
    font-size: var(--text-xl);
    font-weight: 600;
    text-decoration: none;
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-border);
}

/* Lucide-style line icon in front of the label. 22px keeps it
   visually tied to the 24px-ish line-height of the label text
   without overpowering it. Stroke matches the brand vocabulary
   used elsewhere (2px Lucide). */
.nav-drawer-panel__nav a svg {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill: none;
}

.nav-drawer-panel__nav a:last-child { border-bottom: none; }

.nav-drawer-panel__nav a:hover,
.nav-drawer-panel__nav a:focus-visible {
    color: var(--color-accent-coral);
    outline: none;
}

/* Active route — matches the desktop sidebar's azure active pill
   (.app__nav-link.active) so both surfaces use one "I am here"
   vocabulary. NavLink auto-applies the .active class when the
   route matches; we wrap the label/icon row in a small horizontal
   pad so the azure border + tint reads as a distinct affordance
   rather than just colored text. */
.nav-drawer-panel__nav a.active {
    color: var(--color-action);
    background: color-mix(in srgb, var(--color-action) 10%, transparent);
    border-radius: var(--radius-md);
    padding-left: var(--space-3);
    padding-right: var(--space-3);
    margin-left: calc(-1 * var(--space-3));
    margin-right: calc(-1 * var(--space-3));
}

/* Drop the bottom border on the active link to avoid a doubled
   line where the azure pill ends — same trick the desktop sidebar's
   active state uses (transparent border on base, accent on active). */
.nav-drawer-panel__nav a.active { border-bottom-color: transparent; }

/* Identity strip at the top of the drawer panel — "Signed in as
   {name}" replaces the avatar+sign-out icons that used to sit in
   the toolbar. Avatar circle + display name; the name truncates
   with ellipsis so an unusually long email doesn't break layout. */
.nav-drawer-panel__identity {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) 0 var(--space-3);
    border-bottom: 1px solid var(--color-border);
}

.nav-drawer-panel__identity-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-accent-coral);
    color: var(--color-bg-base);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--text-base);
    flex-shrink: 0;
}

.nav-drawer-panel__identity-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.nav-drawer-panel__identity-label {
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.nav-drawer-panel__identity-name {
    font-size: var(--text-base);
    color: var(--color-text-primary);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-drawer-panel__section-label {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-text-muted);
    padding-top: var(--space-2);
}

.nav-drawer-panel__divider {
    height: 1px;
    background: var(--color-border);
    margin-top: var(--space-2);
}

.nav-drawer-panel__actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.nav-drawer-panel__cta {
    width: 100%;
    text-align: center;
    padding: var(--space-3) var(--space-5);
}

/* Visible secondary button, not muted text (founder QA, July 16: the
   muted-gray sign-in under the big azure CTA was effectively invisible —
   "a user has no way to log into web"). Same quiet-outline treatment as
   iOS's Sign Out: present, legible, clearly secondary to Get the app. */
.nav-drawer-panel__signin,
.nav-drawer-panel__signout {
    display: block;
    width: 100%;
    color: var(--color-text-primary);
    font-size: var(--text-base);
    font-weight: 500;
    text-align: center;
    padding: var(--space-3);
    background: transparent;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    text-decoration: none;
    cursor: pointer;
    font-family: inherit;
}

.nav-drawer-panel__signin:hover,
.nav-drawer-panel__signout:hover { color: var(--color-text-primary); }

.nav-drawer-panel__signout-form { margin: 0; }

/* Hamburger button — shared shape. Layouts decide WHEN it's
   visible via their own @media rules; the button itself looks
   the same wherever it lands. */
.nav-drawer-toggle {
    display: none; /* layouts opt in at their mobile breakpoint */
    width: 40px;
    height: 40px;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text-primary);
    cursor: pointer;
    flex-direction: column;
    gap: 5px;
}

.nav-drawer-toggle:hover,
.nav-drawer-toggle:focus-visible {
    border-color: var(--color-border-strong);
    outline: none;
}

.nav-drawer-toggle-bar {
    display: block;
    width: 18px;
    height: 2px;
    background: currentColor;
    border-radius: 2px;
}

/* Open state — js/marketing-nav.js toggles `marketing-nav--open`
   on <html>. Background scroll lock applies globally regardless
   of which layout opened the panel. */
:root.marketing-nav--open {
    overflow: hidden;
}

:root.marketing-nav--open .nav-drawer-overlay {
    opacity: 1;
    pointer-events: auto;
}

:root.marketing-nav--open .nav-drawer-panel {
    transform: translateX(0);
}

@media (min-width: 721px) {
    /* Desktop / tablet — hamburger surface is never shown. */
    .nav-drawer-overlay,
    .nav-drawer-panel { display: none; }
}

/* ---------------------------------------------------------------
   Legal / info document prose — shared by the About, Support,
   Privacy, and Terms pages (/about, /support, /privacy, /terms).
   A reader-comfortable single column modelled on the Stories
   .story-body prose; lives globally here (rather than four scoped
   .razor.css copies) since all four pages render identical
   long-form copy. Pairs with the global .segment-hero / .segment-cta
   sections those pages use for their top and bottom.
   ---------------------------------------------------------------- */
.legal-doc {
    padding: var(--space-6) 0 var(--space-10);
}

.legal-doc__inner {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* Constrain the reading measure on the CHILDREN, not the wrapper. The
   wrapper keeps .container-page's full --container-max width so its left
   edge lines up with the .segment-hero header above it; capping each
   block at 760px keeps a comfortable line length, left-aligned (a
   stretched flex item that hits its max-width sits at the cross-start /
   left edge). Putting the 760 cap on the wrapper itself made it a
   centered narrow column that sat indented ~220px from the hero — the
   alignment bug. */
.legal-doc__inner > * {
    max-width: 760px;
}

.legal-doc__meta {
    margin: 0 0 var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.legal-doc__inner h2 {
    margin: var(--space-5) 0 0;
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-variation-settings: 'opsz' 36;
    line-height: 1.25;
    color: var(--color-text-primary);
}

.legal-doc__inner p {
    margin: 0;
    color: var(--color-text-primary);
    font-size: var(--text-lg);
    line-height: 1.65;
}

.legal-doc__inner p em {
    color: var(--color-accent);
    font-style: italic;
}

.legal-doc__inner ul {
    margin: 0;
    padding-left: var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.legal-doc__inner li {
    color: var(--color-text-primary);
    font-size: var(--text-lg);
    line-height: 1.6;
}

.legal-doc__inner a {
    color: var(--color-accent);
}

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

/* ── Flight-share live map marks ───────────────────────────────────────────
   GLOBAL on purpose: flight-share.js mounts these into a Google Maps
   OverlayView pane, which is OUTSIDE the PublicFlight component's scoped-CSS
   subtree — so these rules can't live in the scoped .razor.css or the marks
   render unstyled (invisible).

   THE AIRCRAFT IS THE MARK (iOS LivePlaneMarker — founder ruling, Aug 31:
   "the cyan chip around the plane read as chrome, and most trackers are too
   literal"). No disc, no ring, no badge — light instead of paint. The glow
   says "live" from underneath, the way a lit thing looks lit; the plane
   itself stays a clean white silhouette, which is what keeps it out of the
   toy-airplane register that cheapens every other tracker.

   States, as data- attributes the script flips:
     data-tint="live|taxi"  cyan in the air, mint on the ground — the glow
                            carries the phase; the aircraft is white always.
     data-held              standing on the last confirmed fix. The tell is
                            the STILLNESS: the heartbeat stops, the glow
                            settles to an ember, the aircraft stays put.
     data-parked            wheels down, no surface fix — on the field by
                            knowledge. Still, like held, but at full light:
                            a known state, not a lost one. */
.flight-share__live-dot {
    position: relative;
    width: 0;
    height: 0;
    pointer-events: none;
    --mark-tint: var(--color-live);
}

.flight-share__live-dot[data-tint="taxi"] {
    --mark-tint: var(--color-taxi);
}

/* THE HEARTBEAT — made of light. A soft blurred swell with no edge to it,
   on a 2.6 s breath (sine in, sine out, not a radar sweep). Only while we
   are actually tracking: a held mark is a still picture. */
.flight-share__live-dot-breath {
    position: absolute;
    left: -20px;
    top: -20px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--mark-tint) 22%, transparent);
    filter: blur(11px);
    animation: flight-share-breath 2.6s ease-in-out infinite;
}

.flight-share__live-dot-breath::before {
    content: "";
    position: absolute;
    left: 9px;
    top: 9px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--mark-tint) 30%, transparent);
    filter: blur(5px);
    animation: flight-share-swell 2.6s linear infinite;
}

.flight-share__live-dot[data-held] .flight-share__live-dot-breath,
.flight-share__live-dot[data-parked] .flight-share__live-dot-breath {
    display: none;
}

/* The ember: always lit, dimmer while held. This is what stops a held mark
   from reading as a dead one — it is still a LIT thing on the map, just not
   a breathing one. */
.flight-share__live-dot-ember {
    position: absolute;
    left: -9.5px;
    top: -9.5px;
    width: 19px;
    height: 19px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--mark-tint) 40%, transparent);
    filter: blur(6px);
    transition: background 450ms ease-in-out;
}

.flight-share__live-dot[data-held] .flight-share__live-dot-ember {
    background: color-mix(in srgb, var(--mark-tint) 22%, transparent);
}

/* The aircraft, nose to course. Two shadows: a dark one to sit it on light
   terrain, a tinted one so the aircraft looks lit rather than pasted over
   the glow. The glyph is authored pointing NORTH, so flight-share.js rotates
   it by the compass course with no correction. */
.flight-share__live-dot-plane {
    position: absolute;
    left: -8px;
    top: -8px;
    width: 16px;
    height: 16px;
    color: #fff;
    display: block;
    transform-origin: 50% 50%;
    filter: drop-shadow(0 1px 2.5px rgba(0, 0, 0, 0.55))
            drop-shadow(0 0 5px color-mix(in srgb, var(--mark-tint) 55%, transparent));
    transition: opacity 450ms ease-in-out;
}

.flight-share__live-dot[data-held] .flight-share__live-dot-plane {
    opacity: 0.85;
    filter: drop-shadow(0 1px 2.5px rgba(0, 0, 0, 0.55))
            drop-shadow(0 0 5px color-mix(in srgb, var(--mark-tint) 28%, transparent));
}

@keyframes flight-share-breath {
    0%, 100% { transform: scale(0.88); }
    50%      { transform: scale(1.04); }
}

@keyframes flight-share-swell {
    0%   { transform: scale(0.6); opacity: 1; }
    100% { transform: scale(2.5); opacity: 0; }
}

/* ── The endpoint lights (iOS AirportDot) ──────────────────────────────────
   LIGHTS ON A MAP, not stickers pressed onto one (founder, Aug 31: "the
   solid outline looks amateur"). Real lights have FALLOFF: a wide faint
   bloom, a brighter body, and a core that runs hot toward white — which is
   what separates a lit thing from a coloured dot. Amber lives HERE and
   nowhere else on the map. Same family as the aircraft's glow, so the map's
   marks are one idea at two brightnesses.

     data-taxiing   the aircraft is rolling on this field: the light
                    breathes mint over its standing amber (the one pulse an
                    airport ever gets — the aircraft owns the only other).
     data-yielding  the aircraft is standing on this point: the bloom steps
                    back so the plane reads on top. It recedes; it never
                    goes out. */
.flight-share__airport-light {
    position: relative;
    width: 0;
    height: 0;
    pointer-events: none;
}

.flight-share__airport-light-bloom {
    position: absolute;
    left: -11px;
    top: -11px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(255, 179, 71, 0.16);
    filter: blur(7px);
    transition: opacity 300ms ease;
}

.flight-share__airport-light-body {
    position: absolute;
    left: -6px;
    top: -6px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: rgba(255, 179, 71, 0.42);
    filter: blur(4px);
    transition: background 300ms ease;
}

/* Near-white core (iOS VroluTheme.anchorWarm #FFDEBD) with amber kept in
   the falloff — the amber core read as yellow. */
.flight-share__airport-light-core {
    position: absolute;
    left: -3px;
    top: -3px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: radial-gradient(circle at center, #FFFFFF 0%, #FFDEBD 100%);
    box-shadow: 0 0 3px rgba(255, 179, 71, 0.7), 0 0.5px 1.5px rgba(0, 0, 0, 0.35);
    transition: opacity 300ms ease;
}

.flight-share__airport-light[data-yielding] .flight-share__airport-light-bloom { opacity: 0; }
.flight-share__airport-light[data-yielding] .flight-share__airport-light-body { background: rgba(255, 179, 71, 0.22); }
.flight-share__airport-light[data-yielding] .flight-share__airport-light-core { opacity: 0.75; }

/* The taxi breath, layered OVER the standing light rather than replacing it:
   the airport is still an airport, it just has something happening on it.
   Ease on a cosine so the light swells and settles rather than strobing — a
   plane taxiing is unhurried. 2.2 s, the pulse law's beat. */
.flight-share__airport-light-breath {
    display: none;
    position: absolute;
    left: -13px;
    top: -13px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(115, 230, 194, 0.30);
    filter: blur(8px);
    animation: flight-share-taxi-breath 2.2s ease-in-out infinite;
}

.flight-share__airport-light-breath::before {
    content: "";
    position: absolute;
    left: 7.5px;
    top: 7.5px;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: rgba(115, 230, 194, 0.55);
    filter: blur(3.5px);
}

.flight-share__airport-light-breath::after {
    content: "";
    position: absolute;
    left: 10.5px;
    top: 10.5px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--color-taxi);
}

.flight-share__airport-light[data-taxiing] .flight-share__airport-light-breath {
    display: block;
}

@keyframes flight-share-taxi-breath {
    0%, 100% { opacity: 0.35; }
    50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .flight-share__live-dot-breath,
    .flight-share__live-dot-breath::before,
    .flight-share__airport-light-breath {
        animation: none;
    }
    .flight-share__live-dot-breath::before { opacity: 0.5; transform: scale(1); }
}

/* The App Store badge on the inner pages (web rework, Oct 3 2026). */
.seg-badge {
  display: inline-block;
  line-height: 0;
}

.seg-badge img { height: 56px; width: auto; }

.segment-cta .seg-badge { margin-bottom: var(--space-2); }

/* =============================================================
   Team dashboard (/team) — Oct 7 2026. Global by preference: one
   place for the page and its chart primitives (.tchart*).

   Chart series colors are a validated categorical set (dataviz
   method, OKLab ΔE): slot 1 blue, 2 orange, 3 aqua, 4 yellow — in
   stack order, adjacent CVD ΔE ≥ 8.4, normal-vision ≥ 19.8, checked
   against the card surface in each theme (dark ≈ #282038, light
   #FFFFFF). Light-mode aqua and yellow are under 3:1 on white, so
   every chart ships a table view. Slots are fixed per meaning (Apple
   Ads 1, iOS organic 2, Android organic 3, Google Play ads 4), never
   by rank. Brand colors stay off the data: sunset is the brand's
   mark, azure means "tap here".
   ============================================================= */

:root,
[data-theme="dark"],
.app:has(.team-page) {
  --chart-1: #3987e5;
  --chart-2: #d95926;
  --chart-3: #199e70;
  --chart-4: #c98500;
  --chart-up: #34C759;
  --chart-down: #FF7373;
}

[data-theme="light"] {
  --chart-1: #2a78d6;
  --chart-2: #eb6834;
  --chart-3: #1baf7a;
  --chart-4: #eda100;
  --chart-up: #006300;
  --chart-down: #c62828;
}

.tchart-swatch--1 { background: var(--chart-1); }
.tchart-swatch--2 { background: var(--chart-2); }
.tchart-swatch--3 { background: var(--chart-3); }
.tchart-swatch--4 { background: var(--chart-4); }
.tchart-stroke--1 { stroke: var(--chart-1); }
.tchart-fill--1 { fill: var(--chart-1); }

.team {
  max-width: 64rem;
}

.team-notfound {
  max-width: 32rem;
  margin: var(--space-10) auto;
  text-align: center;
}

/* Range: one row of chips above everything it scopes. */
.team-range {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
}

.team-range__chip {
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  text-decoration: none;
}

.team-range__chip--on {
  background: var(--color-text-primary);
  border-color: var(--color-text-primary);
  color: var(--color-text-inverse);
  font-weight: 600;
}

@media (hover: hover) {
  .team-range__chip:not(.team-range__chip--on):hover {
    color: var(--color-text-primary);
    border-color: var(--color-text-secondary);
  }
}

.team-range__dates {
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* Headline tiles. */
.team-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.team-tile,
.team-card {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.team-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-5);
}

.team-tile__label {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
}

.team-tile__value {
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1.1;
}

.team-tile__delta,
.team-tile__sub {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.team-tile__delta--up { color: var(--chart-up); }
.team-tile__delta--down { color: var(--chart-down); }

/* Cards. */
.team-card {
  padding: var(--space-5);
  margin-bottom: var(--space-5);
}

.team-card--quiet {
  background: var(--color-card-quiet);
}

.team-card__head {
  margin-bottom: var(--space-4);
}

.team-card__title {
  font-size: var(--text-xl);
  font-weight: 700;
  margin: 0 0 var(--space-1);
}

.team-card__subtitle {
  font-size: var(--text-base);
  font-weight: 600;
  margin: var(--space-5) 0 var(--space-3);
}

.team-card__desc,
.team-card__note {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  margin: 0;
  max-width: 44rem;
}

.team-card__note {
  margin-top: var(--space-3);
  color: var(--color-text-muted);
}

/* The chart primitives. */
.tchart {
  margin: 0;
}

.tchart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.tchart__legend li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.tchart__legend strong {
  color: var(--color-text-primary);
}

.tchart-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  flex-shrink: 0;
}

.tchart__plot {
  position: relative;
  height: 220px;
  margin-left: 2.25rem;
}

.tchart__grid {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px solid var(--color-border);
}

.tchart__grid:first-child {
  border-top-color: var(--color-border-strong);
}

.tchart__grid span {
  position: absolute;
  right: calc(100% + 0.5rem);
  transform: translateY(-50%);
  font-size: 0.7rem;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.tchart__cols {
  position: absolute;
  inset: 0;
  display: grid;
  align-items: end;
  column-gap: 2px;
}

.tchart__col {
  position: relative;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  outline: none;
}

.tchart__stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: min(24px, 100%);
  border-radius: 4px 4px 0 0;
  overflow: hidden;
}

.tchart__seg {
  flex-basis: 0;
  min-height: 2px;
}

/* The readout: shown on hover (pointer devices) and on focus (keyboard,
   or a tap on a phone), pinned to the top of the plot so it never covers
   the legend. Columns past the middle anchor it to the right so it never
   runs off the card. */
.tchart__tip {
  display: none;
  position: absolute;
  top: 0;
  left: 50%;
  z-index: 3;
  min-width: 9.5rem;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-bg-overlay);
  border: 1px solid var(--color-border-strong);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  white-space: nowrap;
  pointer-events: none;
}

.tchart__tip strong {
  color: var(--color-text-primary);
}

.tchart__tip--end {
  left: auto;
  right: 50%;
}

.tchart__tip-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: 0.15rem;
}

.tchart-key {
  display: inline-block;
  width: 10px;
  height: 2px;
  border-radius: 1px;
}

.tchart__col:focus-visible .tchart__tip,
.tchart__col:focus .tchart__tip {
  display: block;
}

.tchart__col:focus-visible .tchart__stack {
  filter: brightness(1.2);
}

@media (hover: hover) {
  .tchart__col:hover .tchart__tip {
    display: block;
  }

  .tchart__col:hover .tchart__stack {
    filter: brightness(1.2);
  }
}

/* Line chart: SVG stretched to the plot, text laid over it in HTML. */
.tchart__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.tchart__line polyline {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.tchart__area {
  opacity: 0.1;
}

.tchart__dot {
  position: absolute;
  width: 10px;
  height: 10px;
  margin: 0 0 -5px -5px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--color-bg-base);
}

.tchart__endlabel {
  position: absolute;
  right: 0;
  margin-bottom: 0.6rem;
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--color-text-primary);
}

.tchart__cols--hit {
  align-items: stretch;
}

/* The crosshair: a hairline down the hovered period. */
.tchart__col--cross::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  border-left: 1px solid transparent;
}

.tchart__col--cross:focus::before,
.tchart__col--cross:focus-visible::before {
  border-left-color: var(--color-border-strong);
}

@media (hover: hover) {
  .tchart__col--cross:hover::before {
    border-left-color: var(--color-border-strong);
  }
}

.tchart__axis {
  position: relative;
  height: 1.5rem;
  margin-left: 2.25rem;
  margin-top: var(--space-2);
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.tchart__axis span {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
}

.tchart__axis span:first-child { transform: none; left: 0 !important; }
.tchart__axis span:last-child { transform: none; left: auto !important; right: 0; }

.tchart__table {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
}

.tchart__table summary {
  cursor: pointer;
  color: var(--color-text-muted);
}

.tchart__table-scroll {
  max-height: 18rem;
  overflow: auto;
  margin-top: var(--space-2);
}

.tchart__table table,
.team-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.tchart__table th,
.tchart__table td,
.team-table th,
.team-table td {
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--color-border);
  text-align: right;
  white-space: nowrap;
}

.tchart__table th:first-child,
.team-table th:first-child {
  text-align: left;
}

.tchart__table thead th,
.team-table thead th {
  color: var(--color-text-muted);
  font-weight: 500;
}

/* Where installs came from: one part-to-whole bar, then the legend
   carries the numbers. */
.team-split {
  display: flex;
  gap: 2px;
  height: 14px;
  border-radius: 7px;
  overflow: hidden;
}

.team-split__seg {
  flex-basis: 0;
  min-width: 4px;
}

.team-split__legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-2) var(--space-5);
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
}

.team-split__legend li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.team-split__label {
  color: var(--color-text-secondary);
}

.team-split__count {
  color: var(--color-text-muted);
}

/* Subscribers: the four moves under the chart. */
.team-mini {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.team-mini div {
  display: flex;
  flex-direction: column;
}

.team-mini strong {
  font-size: var(--text-xl);
}

.team-mini span {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* First flight pass: a four-step ladder, every bar against the passes
   granted, then one sentence against the break-even. */
.team-ladder {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.team-ladder__row {
  display: grid;
  grid-template-columns: minmax(10rem, 16rem) 1fr 6.5rem;
  align-items: center;
  column-gap: var(--space-4);
  font-size: var(--text-sm);
}

.team-ladder__label {
  color: var(--color-text-secondary);
}

.team-ladder__track {
  height: 10px;
  border-radius: 5px;
  background: var(--color-border);
  overflow: hidden;
}

.team-ladder__track span {
  display: block;
  height: 100%;
  min-width: 2px;
  border-radius: 0 5px 5px 0;
}

.team-ladder__value {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.team-ladder__value strong {
  font-size: var(--text-base);
}

.team-ladder__value span,
.team-ladder__note {
  color: var(--color-text-muted);
}

.team-ladder__note {
  grid-column: 2 / -1;
  font-size: 0.75rem;
  margin-top: 0.15rem;
}

.team-verdict {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  margin: var(--space-5) 0 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.team-verdict strong {
  color: var(--color-text-primary);
}

.team-verdict__icon {
  flex-shrink: 0;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  display: inline-grid;
  place-items: center;
  font-weight: 700;
  color: var(--color-text-inverse);
}

.team-verdict--good .team-verdict__icon { background: var(--chart-up); }
.team-verdict--short .team-verdict__icon { background: var(--color-warning); }

@media (max-width: 640px) {
  .team-ladder__row {
    grid-template-columns: 1fr auto;
    row-gap: var(--space-1);
  }

  .team-ladder__track {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .team-ladder__note {
    grid-column: 1 / -1;
  }
}

/* Campaign table. */
.team-table-scroll {
  overflow-x: auto;
}

.team-table {
  font-size: var(--text-sm);
}

.team-table__keyword {
  display: block;
  font-weight: 600;
  color: var(--color-text-primary);
}

.team-table__campaign,
.team-table__pct {
  font-size: 0.75rem;
  color: var(--color-text-muted);
  font-weight: 400;
}

.team-table__campaign {
  display: block;
}

.team-bar {
  display: inline-block;
  width: 4rem;
  height: 6px;
  margin-right: var(--space-2);
  vertical-align: middle;
}

.team-bar span {
  display: block;
  height: 100%;
  border-radius: 0 3px 3px 0;
}

.team-filtered {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.team-filtered strong {
  color: var(--color-text-primary);
}

@media (max-width: 640px) {
  .team-range__dates {
    margin-left: 0;
    width: 100%;
  }

  .team-card {
    padding: var(--space-4);
  }

  .tchart__plot {
    height: 180px;
  }

  .team-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .team-tile {
    padding: var(--space-3) var(--space-4);
  }

  .team-tile__value {
    font-size: 1.75rem;
  }

  .team-bar {
    display: none;
  }
}

/* =============================================================
   Team pages on the app's sky (Oct 8 2026 — founder: "I want the page
   to feel premium … App dark background or our dusk cloud picture").
   Both tabs carry .team-page + .app-sky; the shell goes dark and
   transparent around them (see "The app's sky" above). Surfaces are
   frosted ink over the sky. Azure is "tap here"; moonlight marks the
   one chosen thing; text never wears a brand color.
   ============================================================= */

.team-page {
  position: relative;
  max-width: 68rem;
}

.team-page .team-card,
.team-page .team-tile {
  background: rgba(16, 13, 30, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.09);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  box-shadow: 0 20px 48px -28px rgba(0, 0, 0, 0.7);
}

.team-page .team-card--quiet {
  background: rgba(16, 13, 30, 0.42);
}

/* The two tabs: a segmented control. */
.team-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  margin-bottom: var(--space-6);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.team-tabs__tab {
  padding: 0.45rem 1.15rem;
  border-radius: var(--radius-pill);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background-color 160ms ease, color 160ms ease;
}

.team-tabs__tab--on {
  background: var(--color-moonlight);
  color: #1A1229;
}

@media (hover: hover) {
  .team-tabs__tab:not(.team-tabs__tab--on):hover {
    color: var(--color-text-primary);
  }
}

.team-hero {
  margin-bottom: var(--space-6);
}

.team-hero__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 4vw, 2.75rem);
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-2);
}

.team-hero__lead {
  margin: 0;
  max-width: 40rem;
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--color-text-secondary);
}

/* ---- Share kit ---------------------------------------------------------- */

.share-step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 700;
  margin: 0 0 var(--space-4);
}

.share-step__n {
  display: inline-grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  font-size: 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: var(--color-text-secondary);
}

.share-section {
  margin: var(--space-8) 0;
}

.share-note {
  margin: calc(-1 * var(--space-2)) 0 var(--space-4);
  max-width: 44rem;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* 1 — the source picker. */
.share-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.share-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0 1.1rem;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.05);
  color: var(--color-text-primary);
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease, transform 120ms ease;
}

.share-chip:active {
  transform: scale(0.97);
}

.share-chip[aria-checked="true"] {
  background: var(--color-moonlight);
  border-color: transparent;
  color: #1A1229;
}

.share-chip[aria-checked="true"]::before {
  content: "";
  width: 0.9rem;
  height: 0.9rem;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12.5 4.5 4.5L19 7.5'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12.5 4.5 4.5L19 7.5'/%3E%3C/svg%3E") center / contain no-repeat;
}

.share-chip--other:not([aria-checked="true"]) {
  border-style: dashed;
  color: var(--color-text-secondary);
}

@media (hover: hover) {
  .share-chip:not([aria-checked="true"]):hover {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.24);
  }
}

.share-custom {
  margin-top: var(--space-4);
  max-width: 26rem;
}

.share-custom__label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.share-custom__input {
  width: 100%;
  min-height: 48px;
  padding: 0 1rem;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
  color: var(--color-text-primary);
  font-size: 1.05rem;
}

.share-custom__input::placeholder {
  color: var(--color-text-muted);
}

.share-custom__input:focus {
  outline: none;
  border-color: var(--color-action);
  box-shadow: 0 0 0 3px var(--color-action-soft);
}

/* The pinned reminder of the source. Under the shell's sticky toolbar on
   a phone, at the top of the window on a computer. */
.share-context {
  position: sticky;
  top: var(--space-3);
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: fit-content;
  max-width: 100%;
  margin: var(--space-5) 0 0;
  padding: 0.6rem 1.1rem;
  border-radius: var(--radius-pill);
  background: rgba(14, 12, 26, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  box-shadow: 0 10px 30px -14px rgba(0, 0, 0, 0.8);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.share-context__dot {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-accent);
}

.share-context--set {
  color: var(--color-text-primary);
}

.share-context--set .share-context__dot {
  background: var(--color-live);
  box-shadow: 0 0 0 4px var(--color-live-soft);
}

/* 2 — the replies. Text in full; the card is the picture plus the words. */
.share-messages {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr));
  gap: var(--space-4);
}

.share-message {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: rgba(16, 13, 30, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.09);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  box-shadow: 0 20px 48px -28px rgba(0, 0, 0, 0.7);
}

.share-message__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1080 / 2347;
  object-fit: cover;
  border-radius: 12px;
  box-shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.8);
}

.share-message__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.share-message__title {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0;
}

.share-message__when {
  margin: 0.2rem 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.share-message__text,
.share-message__editor {
  margin: 0 0 var(--space-4);
  font-size: 0.97rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.88);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.share-message__link {
  color: var(--color-action-strong);
  font-weight: 600;
}

.share-message__editor {
  width: 100%;
  padding: var(--space-3);
  border-radius: 12px;
  border: 1px solid var(--color-action);
  box-shadow: 0 0 0 3px var(--color-action-soft);
  background: rgba(255, 255, 255, 0.05);
  font-family: inherit;
  resize: none;
  overflow: hidden;
}

.share-message__editor:focus {
  outline: none;
}

/* Copy and Share side by side, equal; Edit and Reset small beneath. */
.share-message__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: auto;
}

.share-message__actions .share-btn--primary,
.share-message__actions [data-share-send] {
  flex: 1 1 0;
  min-width: fit-content;
}

.share-message__actions .share-btn--quiet {
  min-height: 32px;
  padding: 0 0.5rem;
}

.share-btn__short {
  display: none;
}

/* Buttons: azure commits, ghost offers. */
.share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  min-height: 42px;
  padding: 0 1rem;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: transparent;
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease, transform 120ms ease;
}

.share-btn:active {
  transform: scale(0.97);
}

.share-btn svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.share-btn--primary {
  background: var(--color-action);
  border-color: transparent;
  color: #fff;
}

.share-btn--quiet {
  border-color: transparent;
  color: var(--color-text-secondary);
}

.share-btn--small {
  min-height: 36px;
  padding: 0 0.8rem;
}

.share-btn--done {
  background: var(--color-success);
  border-color: transparent;
  color: #0B0A16;
}

@media (hover: hover) {
  .share-btn:not(.share-btn--primary):hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--color-text-primary);
  }

  .share-btn--primary:hover {
    background: var(--color-action-strong);
  }
}

.share-btn:focus-visible,
.share-chip:focus-visible,
.team-tabs__tab:focus-visible {
  outline: 2px solid var(--color-action-strong);
  outline-offset: 2px;
}

/* 3 — links. */
.share-links {
  list-style: none;
  padding: 0;
  margin: 0;
  border-radius: var(--radius-lg);
  background: rgba(16, 13, 30, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.09);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
}

.share-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}

.share-link:last-child {
  border-bottom: 0;
}

.share-link__text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.share-link__title {
  font-weight: 700;
}

.share-link__note {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

.share-link__url {
  margin-top: 0.3rem;
  font-size: 0.85rem;
  color: var(--color-action-strong);
  overflow-wrap: anywhere;
}

/* 4 — pictures: a row you swipe on a phone, a grid on a computer. */
.share-images {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-4);
}

.share-image {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.share-image img {
  width: 100%;
  height: auto;
  border-radius: 14px;
  box-shadow: 0 14px 32px -16px rgba(0, 0, 0, 0.85);
}

.share-image figcaption {
  display: flex;
  flex-direction: column;
  font-size: var(--text-sm);
}

.share-image__title {
  font-weight: 600;
}

.share-image__note {
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.share-image__actions {
  display: flex;
  gap: var(--space-2);
}

.share-image__actions .share-btn {
  flex: 1 1 0;
  min-width: 0;
}

.share-score {
  margin-top: var(--space-8);
}

/* The toast: one line of confirmation, bottom centre. */
.share-toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.7rem 1.2rem;
  border-radius: var(--radius-pill);
  background: rgba(16, 13, 30, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 16px 40px -12px rgba(0, 0, 0, 0.8);
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  font-weight: 600;
  opacity: 0;
  transform: translate(-50%, 12px);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: none;
}

.share-toast--on {
  opacity: 1;
  transform: translate(-50%, 0);
}

.share-toast svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--color-success);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 960px) {
  .share-images {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 720px) {
  /* Under the shell's sticky toolbar. */
  .share-context {
    top: 4.25rem;
  }

  .share-section {
    margin: var(--space-6) 0;
  }

  /* The words get the whole width on a phone; the picture still goes
     out with "With picture". */
  .share-message {
    grid-template-columns: minmax(0, 1fr);
    padding: var(--space-4);
  }

  .share-message__media {
    display: none;
  }

  .share-btn__long {
    display: none;
  }

  .share-btn__short {
    display: inline;
  }

  .share-link {
    flex-direction: column;
    align-items: stretch;
  }

  .share-images {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    gap: var(--space-3);
    margin: 0 calc(-1 * var(--space-4));
    padding: 0 var(--space-4) var(--space-2);
    scrollbar-width: none;
  }

  .share-images::-webkit-scrollbar {
    display: none;
  }

  .share-image {
    flex: 0 0 42%;
    scroll-snap-align: start;
  }
}

@media (prefers-reduced-motion: reduce) {
  .share-toast,
  .share-chip,
  .share-btn,
  .team-tabs__tab {
    transition: none;
  }
}
