/* Scandiplan — bespoke design layer on top of Tailwind.
   Goal: a first-party Apple travel app on iOS/macOS Golden Gate. Liquid Glass
   on the navigation/control layer (toolbar, trays, chips, nodes), readable
   tinted material for dense itinerary content, system typography, restrained
   motion. Warm putty palette carried over from the original Scandiplan. */

:root {
  /* ---- Danish editorial design tokens ---------------------------------- */
  /* Flat warm-cream atmosphere — no gradient bloom, no grain wash. */
  --gg-bg-top: #f4f3ee;
  --gg-bg-bottom: #ece9e2;
  --canvas-grain: transparent;
  --canvas-grain-soft: transparent;
  /* Crisp near-black hairlines replace the glass rims. */
  --gg-border-soft: rgba(20, 18, 15, 0.16);
  --gg-border-medium: rgba(20, 18, 15, 0.9);
  --gg-highlight: transparent;
  /* No blur anywhere — flat panels. Kept as tokens so every
     `backdrop-filter: var(--gg-blur-*)` resolves to none. */
  --gg-blur-soft: none;
  --gg-blur-strong: none;
  --gg-panel-fill: #ffffff;
  --gg-panel-blur: none;
  --gg-panel-ios-first-paint: #ffffff;
  --side-sheet-fill: #ffffff;
  --side-sheet-blur: none;
  --side-sheet-ios-tint: #ffffff;
  --gg-panel-rim: none;
  --gg-panel-shadow: 0 18px 44px -20px rgba(20, 18, 15, 0.4);
  --gg-focus: #2b3a8c;
  --ink: #1a1714;          /* near-black text */
  --muted: #6b6357;        /* secondary text */
  --faint: #9b9384;        /* tertiary text */
  --line: #1a1714;         /* crisp black hairline borders */
  --line-strong: #1a1714;
  --surface: #ffffff;      /* flat white panel on cream */
  --canvas: #f4f3ee;       /* warm cream page bg + Safari overscroll chrome */
  --accent: #2d3eaa;       /* Nyhavn blue — the signature accent, vivid */
  --accent-deep: #232c88;
  --accent-soft: #e7eaf4;
  --pill: #1a1714;         /* black primary button */
  --pill-hover: #000000;
  --btn-hover-shadow: #ffffff; /* white hard offset behind ink buttons */
  --radius: 3px;           /* sharp editorial corners */
  --radius-sm: 2px;
  /* Centred shell gutters — mirrors .scandi-app-shell max-w-screen-2xl px-4. */
  --app-shell-max: 96rem;
  --app-shell-pad: 1rem;
  --app-content-inset: var(--app-shell-pad);
  /* Motion language — crisp, snappy, restrained. */
  --ease-ios: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-open: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.26, 0.64, 1);
  --ease-reveal: cubic-bezier(0.55, 0.04, 0.85, 0.4);
  --shadow-sm: none;
  --shadow-md: 0 12px 30px -16px rgba(20, 18, 15, 0.345);
  --shadow-shelf: 0 2px 0 0 var(--line);
  --shadow-offset: 6px 6px 0 0 var(--line);
  --shadow-offset-sm: 4px 4px 0 0 var(--line);
  --shadow-offset-ink: 6px 6px 0 0 var(--btn-hover-shadow);
  --shadow-offset-ink-sm: 4px 4px 0 0 var(--btn-hover-shadow);
  /* Planned rec cards: quietly recessed via a heavier black inset, no glass. */
  --planned-bg: #e4dfd1;

  /* ---- Poster accent palette (category / status / color-grid blocks) --- */
  --poster-red: #d6463a;
  --poster-orange: #e07a3c;
  --poster-yellow: #f2c23a;
  --poster-blue: #5b91cf;
  --poster-green: #5fa86a;
  --poster-brown: #94755f;
  --poster-deep: #2b3a8c;

  /* ---- Tailwind palette channels (light) ------------------------------- */
  /* The four named palettes (index.html) resolve to these "R G B" triplets so
     every bg-/text-/border- utility re-themes. fjord = the deep-blue signature
     accent; stone = warm cream→black neutral; slate = near-black text; sand =
     warm brown/tan. html.dark (end of file) overrides them. */
  --c-fjord-50: 231 234 244;  --c-fjord-100: 211 217 238; --c-fjord-200: 178 188 224;
  --c-fjord-300: 138 152 204; --c-fjord-400: 90 108 168;  --c-fjord-500: 43 58 140;
  --c-fjord-600: 35 48 118;   --c-fjord-700: 30 40 100;   --c-fjord-800: 26 34 82;
  --c-fjord-900: 22 29 66;    --c-fjord-950: 14 19 46;

  --c-stone-50: 244 243 238;  --c-stone-100: 237 235 230; --c-stone-200: 224 221 214;
  --c-stone-300: 201 193 174; --c-stone-400: 161 153 137; --c-stone-500: 120 113 101;
  --c-stone-600: 90 84 75;    --c-stone-700: 62 58 51;    --c-stone-800: 40 37 33;
  --c-stone-900: 26 23 20;    --c-stone-950: 18 16 14;

  --c-slate-50: 244 242 237;  --c-slate-100: 234 231 224; --c-slate-200: 216 211 201;
  --c-slate-300: 186 180 169; --c-slate-400: 138 131 121; --c-slate-500: 96 90 82;
  --c-slate-600: 66 61 55;    --c-slate-700: 44 41 37;    --c-slate-800: 30 27 23;
  --c-slate-900: 22 20 17;    --c-slate-950: 14 13 11;

  --c-sand-50: 247 241 230;  --c-sand-100: 240 230 211; --c-sand-200: 227 208 176;
  --c-sand-300: 209 181 140; --c-sand-400: 188 150 104; --c-sand-500: 168 124 74;
  --c-sand-600: 148 117 95;  --c-sand-700: 122 92 66;   --c-sand-800: 98 75 56;
  --c-sand-900: 79 60 46;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;  /* stop iOS auto-inflating text in landscape */
  text-size-adjust: 100%;
  color-scheme: light;
  background-color: var(--canvas);
  overflow-x: hidden;
  /* A definite height (not just min-height) so body's `min-height: 100%`
     actually resolves — without it a view shorter than the viewport (the
     desktop calendar/map) ends mid-screen and the body's atmosphere gradient
     stops with it, leaving a flat band below. Content still overflows and
     scrolls normally, and percentages don't involve dvh, so Safari 26's
     collapsing URL bar behavior is unaffected. */
  height: 100%;
}

/* iOS standalone PWA: keep the system chrome area the same warm canvas tone.
   (html only — body owns the atmosphere gradient, whose top edge matches, and
   #root stays transparent so the gradient shows through in standalone too.) */
@media all and (display-mode: standalone) {
  html {
    background-color: var(--canvas);
  }
}
body {
  -webkit-font-smoothing: antialiased;
  /* Flat warm-cream page — editorial, no gradient bloom or grain. The colour
     equals --canvas so Safari's chrome, overscroll and PWA status bar all read
     as one continuous surface. */
  background-color: var(--canvas);
  background-image: none;
  color: var(--ink);
  letter-spacing: -0.005em;
  margin: 0;
  /* Keep the document itself in normal page flow. Safari 26's Liquid Glass
     toolbar lets regular scrolling content pass behind the browser chrome, but
     fixed/sticky bottom elements can get clipped above it. */
  min-height: 100%;
  /* Side gutters only. Top/bottom safe areas are handled per-component so
     page content can scroll edge-to-edge under iOS Safari's translucent status
     bar and URL bar (like nytimes.com). */
  padding-right: env(safe-area-inset-right, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  /* No grey flash on tap; smoother iOS scrolling. */
  -webkit-tap-highlight-color: transparent;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: none;
  /* Belt-and-suspenders with #root's clip: no horizontal page scroll from a
     full-bleed card or sub-pixel 100vw rounding. `clip` keeps sticky working. */
  overflow-x: clip;
  overscroll-behavior-x: none;
}

/* Keep the app root in normal document flow; avoid viewport-height constraints
   that make Safari 26 lay out the page only above its floating URL bar. */
#root {
  /* Transparent so the body's atmosphere gradient shows through; the PWA
     media query above re-solidifies it for the status-bar region. */
  background-color: transparent;
  min-height: 100%;
  /* Let an expanded stop card bleed to the viewport edges (see `.stop-fullbleed`)
     without the sub-pixel 100vw overflow adding a horizontal scrollbar. `clip`
     (not hidden/auto) doesn't create a scroll container, so the day-plan column's
     position:sticky and the mobile top bar are unaffected. */
  overflow-x: clip;
  max-width: 100vw;
}

.scandi-app-shell {
  /* Avoid 100dvh on Safari 26 — it tracks the reduced visual viewport above the
     floating URL bar and leaves a flat canvas band instead of edge-to-edge bleed. */
  min-height: 100%;
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
  max-width: 100%;
}

@media (min-width: 640px) {
  .scandi-app-shell {
    padding-bottom: 1.25rem;
  }
}

/* ---- Type & craft refinements ----------------------------------------- */
/* Balance short display headings so multi-word titles wrap evenly. Body copy
   deliberately does NOT get `text-wrap: pretty`: Safari's implementation
   re-rags the whole paragraph, wrapping lines well short of the right edge —
   on phone-width columns the city descriptions read as wasted space. */
h1, h2, h3, .font-display, .display-xl { text-wrap: balance; }
/* Tabular figures so counts, dates, temps and times never shift width as they
   change (opt-in via the class). */
.tnum { font-variant-numeric: tabular-nums; }
/* On-brand text selection instead of the browser default blue. */
::selection { background: var(--accent-soft); color: var(--ink); }

/* Fraunces editorial headlines: let the variable optical-size axis track the
   rendered size, soften the contrast a touch, and tighten the headline tracking
   so city names read like a travel-magazine masthead rather than body serif. */
.font-editorial {
  font-optical-sizing: auto;
  font-variation-settings: 'opsz' 48, 'SOFT' 30, 'wght' 560;
  letter-spacing: -0.012em;
}
/* Thin, unobtrusive scrollbars on pointer devices (iOS overlays its own). */
@media (hover: hover) and (pointer: fine) {
  ::-webkit-scrollbar { width: 8px; height: 8px; }
  ::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 9999px; }
  ::-webkit-scrollbar-thumb:hover { background: var(--faint); }
  ::-webkit-scrollbar-track { background: transparent; }
}

/* Phone form controls. Inputs keep their own Tailwind size class (the transport
   editor's fields are text-xs) so dense panels stay compact and match the
   surrounding UI — we deliberately don't bump them up here. Trade-off: type below
   16px means iOS Safari may zoom slightly on focus. Width is left to each input's
   own class (w-[6.5rem] for times, w-full for dates); forcing 100% here once
   overrode those and pushed adjacent fields off-row. */
@media (max-width: 640px) {
  input[type="time"], input[type="date"] {
    min-height: 28px;
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
    /* iOS Safari: shrink the formatted date text so it stays inside the field. */
    -webkit-appearance: none;
    appearance: none;
  }
}

/* Comfortable touch targets: interactive controls get a 40px hit area on
   touch devices without changing their visual size. */
@media (hover: none) and (pointer: coarse) {
  button, a, [role="button"], input[type="checkbox"], input[type="radio"] {
    touch-action: manipulation;  /* kill the 300ms tap delay + double-tap zoom */
  }
}

/* Display headings (trip name, section titles) — Outfit, tight tracking. */
.font-display {
  font-family: 'Outfit', ui-sans-serif, system-ui, sans-serif;
  font-weight: 600;
  letter-spacing: -0.022em;
}
.display-xl {
  font-family: 'Fraunces', 'Archivo', ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.05;
}

/* Map link brand marks: the Apple silhouette reads optically smaller and lower
   than Google's G at equal boxes, so give it +1pt and a tiny baseline lift. */
.map-apple-glyph {
  width: calc(0.8em + 1pt);
  height: calc(0.8em + 1pt);
  display: block;
  overflow: visible;
  transform: translateY(-0.09em);
  flex-shrink: 0;
}
.map-google-glyph {
  width: 0.8em;
  height: 0.8em;
  display: block;
  overflow: visible;
  flex-shrink: 0;
}

/* ---- Liquid Glass materials (Golden Gate) ------------------------------ */
/* Navigation & control surfaces get real glass: translucent layered fills,
   backdrop blur, hairline borders, a lit top rim. Dense itinerary content
   stays on near-opaque tinted material for readability. */

/* Specular rim: a 1px gradient hairline that reads as light catching the top
   edge and curved corners of a pane of glass — bright top-left, falling away,
   with a faint return along the bottom (light wrapping the far edge). Painted
   with mask-compositing so it hugs any border-radius. The host paints its own
   flat border as the base; this sits on top as the lighting. */
.gg-rim::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  /* A hair thicker than 1px so the lit edge actually reads as the bevelled face
     of a glass pane catching light, not a faint hairline. */
  padding: 1.25px;
  background: linear-gradient(158deg,
    rgba(255, 255, 255, 0.78) 0%,
    rgba(255, 255, 255, 0.4) 19%,
    rgba(255, 255, 255, 0.04) 46%,
    rgba(255, 255, 255, 0.1) 68%,
    rgba(255, 255, 255, 0.46) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  z-index: 1;
}
/* Second pass of the rim: a crisp, bright sliver hugging just the very top edge
   and a brighter glow gathering along the bottom — the strong specular catch
   and the return-light that sell the pane's thickness/refraction. */
.gg-rim::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 1;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    inset 0 -10px 18px -14px rgba(255, 255, 255, 0.4);
}
.gg-rim {
  position: relative;
}

/* Generic light glass — quiet rows, secondary surfaces. The stacked inset
   shadows fake thickness: lit top rim, a soft inner bloom, and a faint dark
   gather along the bottom edge where a real pane would compress the light. */
.gg-glass {
  position: relative;
  /* Thinner white film than before so the heavier blur behind it shows through
     as clear, refracted material rather than a milky wash. */
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.09));
  border: 1px solid var(--gg-border-soft);
  -webkit-backdrop-filter: var(--gg-blur-soft);
  backdrop-filter: var(--gg-blur-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 14px 26px -18px rgba(255, 255, 255, 0.48),
    inset 0 -11px 18px -14px rgba(255, 255, 255, 0.28),
    inset 0 -1px 0 rgba(65, 55, 42, 0.07);
}

/* Sticky top bar geometry. Full-bleed to the browser's edges no matter how
   wide the viewport gets (browser zoomed out past the shell's max-w-screen-2xl
   cap, ultrawide monitors): the margins pull to the viewport edges and the
   matching padding pushes the content back into the centered app column —
   exactly equivalent to the old -mx-4/px-4 while the shell fills the window.
   IMPORTANT: no transform/filter/will-change on this element — iOS Safari
   stops position:sticky elements from sticking once they carry their own
   transform. The compositing hint lives on the ::before layer below instead. */
.app-top-bar {
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-left: var(--app-content-inset);
  padding-right: var(--app-content-inset);
  /* NOTE: do NOT promote this sticky bar to its own compositor layer (e.g.
     `transform: translateZ(0)` / `will-change: transform`). It fixed a ±1px
     momentum-scroll shimmer, but on an iOS standalone PWA *warm resume* WebKit
     rebuilds compositing layers and fails to re-attach the sticky scrolling node
     to the rebuilt layer — so the header stops sticking entirely until a reload.
     A plain (uncomposited) sticky bar re-establishes correctly on resume. */
  /* Drops in from the top once on first mount (React handoff from the boot
     spinner). No fill-forwards, so the transform reverts to none afterward. */
  animation: headerDropIn 0.6s var(--ease-ios) backwards;
}
.app-top-bar.is-scrolled {
  box-shadow: var(--shadow-shelf);
}
@keyframes headerDropIn {
  from { transform: translateY(-100%); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .app-top-bar { animation: none; }
}

/* The app's native control surface: the sticky top bar. Content scrolls
   beneath it through the blur; the hairline + lit rim give it a crisp
   window-chrome edge. The scrolled state adds elevation.
   The glass itself (fill + heavy blur + lit rim) is painted on a ::before
   layer rather than the sticky element: the sticky box must stay free of
   transforms (see .app-top-bar), and giving the blur its own composited layer
   stops iOS re-rasterizing it against fractional scroll offsets — the old
   1px "creep" while scrolling. Only the crisp border-bottom hairline stays on
   the element (it sits outside the padding box, beyond the pseudo's reach). */
.gg-toolbar {
  border-bottom: 1px solid var(--gg-border-medium);
  transition: border-color 0.35s var(--ease-ios);
}
.gg-toolbar::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Same clear liquid-glass recipe as the floating modals (--gg-panel-fill), so
     the header reads as the same pane of glass rather than a milky strip. A
     touch more blur than the modal since live content scrolls under the bar. */
  background: var(--gg-panel-fill);
  -webkit-backdrop-filter: blur(30px) saturate(1.9) brightness(1.0);
  backdrop-filter: blur(30px) saturate(1.9) brightness(1.0);
  /* Crisper lit top rim + a touch deeper gather above the bottom hairline so the
     thinner pane still reads with depth and a clean window-chrome edge. (Painted
     here, on top of this layer's own backdrop blur, so the 1px lines stay crisp.) */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42),
    inset 0 -1px 0 rgba(65, 55, 42, 0.05);
  transform: translateZ(0);
}
.gg-toolbar-scrolled {
  box-shadow: 0 8px 22px -10px rgba(54, 49, 42, 0.22);
  transition: box-shadow 0.35s var(--ease-ios);
}

/* Merged app bar + stuck stop header: one glass pane, one drop shadow. */
html[data-merged-stop-head] .app-top-bar {
  margin-bottom: 0;
  z-index: 41;
  box-shadow: none;
  transition: box-shadow 0.35s var(--ease-ios), margin-bottom 0.35s var(--ease-ios);
}
html[data-merged-stop-head] .gg-toolbar {
  border-bottom-color: transparent;
}
html[data-merged-stop-head] .gg-toolbar-scrolled {
  box-shadow: none;
}

/* Continuous glass tray — the horizontal stop strip and other grouped
   navigation rows sit in one shared material instead of separate web cards. */
.gg-tray {
  position: relative;
  background: linear-gradient(180deg, rgba(250, 252, 254, 0.48), rgba(242, 246, 250, 0.28));
  border: 1px solid rgba(65, 55, 42, 0.12);
  border-radius: 18px;
  -webkit-backdrop-filter: var(--gg-blur-soft);
  backdrop-filter: var(--gg-blur-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.62),
    inset 0 -8px 16px -14px rgba(65, 55, 42, 0.12),
    0 12px 30px -22px rgba(54, 49, 42, 0.24);
}
/* Recessed variant — a sunken translucent trough (mirrors the segmented-control
   look) so the stop tiles read as raised cards sitting inside an inset rail. */
.gg-tray--inset {
  background: rgba(96, 84, 66, 0.10);
  border: 1px solid rgba(70, 62, 48, 0.12);
  border-radius: 15px;
  box-shadow:
    inset 0 1.5px 3px rgba(54, 49, 42, 0.14),
    inset 0 0 0 0.5px rgba(54, 49, 42, 0.035),
    inset 0 -1px 0 rgba(255, 255, 255, 0.42),
    0 1px 0 rgba(255, 255, 255, 0.48);
  -webkit-backdrop-filter: blur(14px) saturate(1.26);
  backdrop-filter: blur(14px) saturate(1.26);
}
html.dark .gg-tray--inset {
  background: rgba(0, 0, 0, 0.26);
  border-color: rgba(255, 255, 255, 0.07);
  box-shadow:
    inset 0 1.5px 4px rgba(0, 0, 0, 0.5),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06),
    0 1px 0 rgba(255, 255, 255, 0.05);
}

/* Edge fades for the horizontally-scrollable stop ribbon. Shown only on the
   side that has more tiles off-screen, fading to the canvas colour so a clipped
   tile dissolves into the rail edge instead of looking cut off. */
.ribbon-fade {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2.5rem;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
  z-index: 5;
}
.ribbon-fade.is-on { opacity: 1; }
.ribbon-fade--l {
  left: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  background: linear-gradient(90deg, rgba(250, 248, 243, 0.97), rgba(250, 248, 243, 0));
}
.ribbon-fade--r {
  right: 0;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: linear-gradient(270deg, rgba(250, 248, 243, 0.97), rgba(250, 248, 243, 0));
}
html.dark .ribbon-fade--l { background: linear-gradient(90deg, rgba(0, 0, 0, 0.97), rgba(0, 0, 0, 0)); }
html.dark .ribbon-fade--r { background: linear-gradient(270deg, rgba(0, 0, 0, 0.97), rgba(0, 0, 0, 0)); }
@media (prefers-reduced-motion: reduce) {
  .ribbon-fade { transition: none; }
}

/* Compact inset tile inside the tray — one stop in the strip. Hover thickens
   the material (brighter bloom, deeper shadow) rather than scaling it. */
.gg-stop-tile {
  position: relative;
  background: linear-gradient(180deg, rgba(250, 252, 254, 0.84), rgba(242, 246, 250, 0.64));
  border: 1px solid rgba(65, 55, 42, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78), 0 1px 2px rgba(54, 49, 42, 0.07);
  transition: border-color 0.14s ease, box-shadow 0.14s ease, transform 0.14s var(--ease-ios);
}
.gg-stop-tile:hover {
  border-color: var(--gg-border-medium);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86),
    inset 0 10px 18px -16px rgba(255, 255, 255, 0.58),
    0 4px 12px -4px rgba(54, 49, 42, 0.18);
}
.gg-stop-tile:active {
  transform: scale(0.99);
}

/* Circular liquid-glass bubble — transport icons on the timeline spine,
   trip-end nodes, and route markers on the map. Same refractive material as
   map labels: the emoji sits beneath a clear lens; .gg-rim adds the specular
   hairline on top. */
.gg-node {
  position: relative;
  box-sizing: border-box;
  display: grid;
  place-items: center;
  isolation: isolate;
  flex-shrink: 0;
  border-radius: 9999px;
  overflow: hidden;
  /* Mostly CLEAR center so the page/map refracts through the heavy blur (the
     milkiness lives only at the lensing edge), with a bright white glass rim. */
  background:
    radial-gradient(135% 135% at 32% 22%,
      rgba(247, 251, 253, 0.56),
      rgba(235, 244, 249, 0.24) 42%,
      rgba(212, 226, 235, 0.15) 62%,
      rgba(168, 190, 204, 0.28) 86%,
      rgba(247, 251, 253, 0.36) 100%);
  border: 1px solid rgba(247, 251, 253, 0.68);
  -webkit-backdrop-filter: blur(9px) saturate(1.28) brightness(1.0);
  backdrop-filter: blur(9px) saturate(1.28) brightness(1.0);
  box-shadow:
    inset 0 1px 1px rgba(247, 251, 253, 0.78),
    inset 0 -3px 6px -2px rgba(96, 138, 170, 0.30),
    inset 2px 0 3px -2px rgba(247, 251, 253, 0.52),
    inset -2px 0 3px -2px rgba(247, 251, 253, 0.34),
    0 1px 2px rgba(20, 38, 52, 0.16),
    0 4px 10px -2px rgba(20, 38, 52, 0.28);
}
/* Emoji layer — sits under the lens sheen and rim highlights. */
.gg-node-emoji {
  position: relative;
  z-index: 0;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  line-height: 1;
  transform-origin: center;
  transform: scale(0.94);
  filter: saturate(1.08);
}
.transport-glyph,
.transport-map-glyph {
  display: inline-grid;
  place-items: center;
  line-height: 0;
  transform-origin: center;
}
.transport-map-glyph {
  width: 15px;
  height: 15px;
}
.transport-glyph-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
/* Glossy dome pass — sells depth so the glyph reads inside the bubble. */
.gg-node-lens {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 1;
  /* Crisp top-left specular crescent + soft bottom light-gather, painted OVER
     the emoji so the glyph reads as sealed under the curved glass. */
  background:
    radial-gradient(48% 34% at 33% 17%, rgba(247, 251, 253, 0.7), rgba(255, 255, 255, 0) 74%),
    radial-gradient(70% 48% at 52% 112%, rgba(232, 241, 247, 0.28), rgba(255, 255, 255, 0) 60%);
  box-shadow: inset 0 1px 0 rgba(247, 251, 253, 0.62);
}
/* Map route markers — Leaflet divIcon wrapper; inner .gg-node is the bubble. */
.scandi-transport {
  background: transparent !important;
  border: none !important;
}
.scandi-transport .gg-node {
  /* 30px sphere around a 16px glyph — the air between glyph and rim is what
     lets it read as glass instead of a sticker. */
  width: 30px;
  height: 30px;
  border-radius: 9999px;
  font-size: 12px;
}
.gg-node.gg-rim::before,
.gg-node.gg-rim::after { z-index: 2; }

/* Glass event row — trip start/end blocks, quiet full-width actions. */
.gg-event-row {
  position: relative;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.38));
  border: 1px solid var(--gg-border-soft);
  -webkit-backdrop-filter: var(--gg-blur-soft);
  backdrop-filter: var(--gg-blur-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65),
    inset 0 -7px 14px -12px rgba(65, 55, 42, 0.09);
}

/* Compact native status chip. Tint rides on currentColor (set a text-* class
   on the element) so one rule covers every tone — amber bookings, rose pacing,
   neutral empties — and the fill/border always stay in the same hue family. */
.gg-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 26px;
  padding: 0 10px;
  border-radius: 9999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  white-space: nowrap;
  flex-shrink: 0;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  background:
    linear-gradient(180deg, rgba(247, 251, 253, 0.48), rgba(224, 235, 242, 0.18)),
    color-mix(in srgb, currentColor 10%, rgba(235, 244, 249, 0.38));
  -webkit-backdrop-filter: blur(8px) saturate(1.16);
  backdrop-filter: blur(8px) saturate(1.16);
  box-shadow: inset 0 1px 0 rgba(247, 251, 253, 0.42), 0 1px 2px rgba(54, 49, 42, 0.06);
}
button.gg-chip {
  cursor: pointer;
  transition: transform 0.26s var(--ease-spring), box-shadow 0.16s ease;
}
button.gg-chip:hover {
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), 0 4px 10px -3px rgba(54, 49, 42, 0.18);
}
button.gg-chip:active { transform: scale(0.97); }
/* Inline variant for tight rows (transport warnings, stay advice). */
.gg-chip-sm {
  min-height: 21px;
  padding: 0 7px;
  font-size: 10px;
  gap: 4px;
}

/* Tinted glass banner — route advisory and similar full-width notices. */
.gg-banner {
  border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
  background:
    linear-gradient(180deg, rgba(247, 251, 253, 0.42), rgba(224, 235, 242, 0.2)),
    color-mix(in srgb, currentColor 9%, rgba(235, 244, 249, 0.32));
  -webkit-backdrop-filter: var(--gg-blur-soft);
  backdrop-filter: var(--gg-blur-soft);
  box-shadow: inset 0 1px 0 rgba(247, 251, 253, 0.42);
}

/* Weather as a compact native info capsule. */
.gg-weather-pill {
  display: inline-flex;
  align-items: center;
  min-height: 26px;
  padding: 0 9px;
  border-radius: 9999px;
  background: rgba(238, 246, 250, 0.46);
  border: 1px solid var(--gg-border-soft);
  box-shadow: inset 0 1px 0 rgba(247, 251, 253, 0.55);
}

/* Floating glass menu / popover — denser than the toolbar layer it floats
   over: more opaque, stronger rim light, clearer drop shadow. Pair with
   .gg-rim for the specular hairline. */
.gg-menu {
  position: relative;
  /* Uniform liquid-glass panel (shared with .gg-sheet and the welcome modal):
     clear refractive fill + complete specular rim. The heavy abstracting blur
     keeps labels legible even over the busy map / recommendation lists. */
  background: var(--gg-panel-fill);
  border: 1px solid var(--gg-border-medium);
  border-radius: 14px;
  -webkit-backdrop-filter: var(--gg-panel-blur);
  backdrop-filter: var(--gg-panel-blur);
  box-shadow: var(--gg-panel-rim), var(--gg-panel-shadow);
}

/* Dense menu variant — for small popovers (the desktop profile picker) that
   float near the top over light chrome with little behind them to refract. The
   clear shared recipe reads flat there, so add a touch of warm fill and a
   heavier, more saturated blur to bring the frosted refraction back, while
   keeping the same rim/sheen so it stays in the glass family. */
.gg-menu--dense {
  /* Floats over the busy timeline, so names need a solid frosted backing to read.
     Keep the diagonal sheen for glassiness, but layer it over a much more opaque
     pearl base so content behind doesn't bleed through the labels. */
  background:
    linear-gradient(125deg,
      rgba(247, 251, 253, 0.36) 0%,
      rgba(232, 241, 247, 0.14) 30%,
      rgba(224, 235, 242, 0.10) 60%,
      rgba(232, 241, 247, 0.16) 100%),
    linear-gradient(180deg, rgba(247, 251, 253, 0.68), rgba(229, 237, 242, 0.58));
  -webkit-backdrop-filter: blur(24px) saturate(1.45) brightness(1.0);
  backdrop-filter: blur(24px) saturate(1.45) brightness(1.0);
}

/* Popover emergence: grows out of its trigger (set a transform-origin class
   on the element) with a quick start that settles softly — a few px of travel
   and a slight scale reading as the pane arriving from depth. Deliberately NO
   opacity ramp: while an element (or any ancestor) sits below opacity 1 it
   forms the backdrop root, so its backdrop-filter can't sample the page behind
   — a fading glass pane renders CLEAR and only snaps frosted once the fade
   lands on exactly 1. Transform-only keeps the frost on from the first frame. */
@keyframes ggPopIn {
  from { transform: translateY(-4px) scale(0.92); }
  to { transform: translateY(0) scale(1); }
}
.gg-pop-in { animation: ggPopIn 0.18s var(--ease-ios) both; }

/* Inline disclosure reveal — a quick, smooth real-height expand + fade for
   content that opens in place (travel details, notes, compare options). Uses the
   modern grid-rows accordion so it animates height with no JS measuring. Wrap as
   `<div class="disclose"><div>…content…</div></div>` (one grid child). */
@keyframes discloseIn {
  from { grid-template-rows: 0fr; opacity: 0; }
  to { grid-template-rows: 1fr; opacity: 1; }
}
.disclose {
  display: grid;
  grid-template-rows: 1fr;
  animation: discloseIn 0.26s var(--ease-ios) both;
}
.disclose > * { min-height: 0; overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
  .disclose { animation: none; }
}

/* Toggleable reveal — slides open AND shut. The content is kept mounted through
   the close (see useReveal); the `is-open` class drives the grid-rows accordion
   both ways. Wrap as `<div class="reveal"><div>…</div></div>` (one grid child). */
.reveal {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  /* Closing (transition TO the base state): height, fade and gap all run on the
     SAME duration + ease so the panel collapses as one motion — no ease-out tail
     crawling the last 10%, and no empty-gap shrinking after the content has
     already faded (both of which read as a second "finish"). */
  transition: grid-template-rows 0.24s cubic-bezier(0.4, 0, 1, 1), opacity 0.24s cubic-bezier(0.4, 0, 1, 1), margin-top 0.24s cubic-bezier(0.4, 0, 1, 1);
}
.reveal.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
  /* Opening (transition TO is-open): ease-out, settling into place. */
  transition: grid-template-rows 0.32s var(--ease-ios), opacity 0.26s ease, margin-top 0.32s var(--ease-ios);
}
.reveal > * { min-height: 0; overflow: hidden; }
/* Once open + settled, drop the animating `1fr` track for `auto` so the final
   height is content-based. Fixes Safari leaving a too-tall grid row (extra space
   below the section) after the 0fr→1fr open until a scroll forces a reflow. */
.reveal.is-settled { grid-template-rows: auto; transition: none; }
.reveal.is-settled > * { overflow: visible; }
/* Variant that also eases the gap above the panel open (timeline leg editor). */
.reveal--gap.is-open { margin-top: 0.5rem; }
@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal.is-open { transition: opacity 0.15s ease; }
}

/* Measured-height collapse (see useCollapse) — animates an explicit px height so
   the content below reflows perfectly smoothly, unlike grid `fr`. The height is
   set inline; this owns the easing, fade and gap. */
.collapse {
  overflow: hidden;
  opacity: 0;
  transition: height 0.3s var(--ease-ios), opacity 0.22s ease, margin-top 0.3s var(--ease-ios);
}
.collapse.is-open { opacity: 1; }
.collapse--gap.is-open { margin-top: 0.5rem; }
@media (prefers-reduced-motion: reduce) {
  .collapse { transition: opacity 0.15s ease; }
}

/* Modal / sheet glass — the strongest, most opaque layer in the hierarchy.
   Dense forms and lists live here, so readability wins: high-opacity pearl
   over a strong blur, with the deepest shadow separation. */
.gg-sheet {
  position: relative;
  /* Uniform liquid-glass panel (shared with .gg-menu and the welcome modal):
     clear refractive fill + complete specular rim, floating over the undimmed
     page. The heavy abstracting blur keeps dense forms/lists legible. */
  background: var(--gg-panel-fill);
  border: 1px solid var(--gg-border-medium);
  -webkit-backdrop-filter: var(--gg-panel-blur);
  backdrop-filter: var(--gg-panel-blur);
  box-shadow: var(--gg-panel-rim), var(--gg-panel-shadow);
}

/* Settings + Claus — shared clear side-sheet glass, matched to the floating
   modals' glassiness (the tokens below point at the modal recipe). */
.settings-sheet,
.trip-chat-sheet {
  background: var(--side-sheet-fill);
  -webkit-backdrop-filter: var(--side-sheet-blur);
  backdrop-filter: var(--side-sheet-blur);
}
@supports (-webkit-touch-callout: none) {
  .settings-sheet,
  .trip-chat-sheet { background-color: var(--side-sheet-ios-tint); }
}

/* Scrim under modals/sheets: a transparent click-catcher only. The content
   behind stays clear and undimmed — the floating glass panel is "just opaque
   enough" to own its own legibility (via its fill + own backdrop blur), and its
   big drop shadow provides the separation, so we never darken the page. */
.gg-scrim {
  background: transparent;
}

/* iOS can paint a backdrop-filter surface once before the frosted layer is
   ready, which makes modals flash clear. A quiet base tint covers that first
   paint while the final shared glass recipe still supplies the visible sheen. */
@supports (-webkit-touch-callout: none) {
  .gg-menu,
  .gg-sheet,
  .settings-sheet,
  .trip-chat-sheet,
  .welcome-modal-panel {
    background-color: var(--gg-panel-ios-first-paint);
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    will-change: transform, opacity, backdrop-filter;
  }
}

/* Dark smoky glass for tooltips and the drag-count flag — polished, not
   a flat slate rectangle. */
.gg-tooltip {
  background: linear-gradient(180deg, rgba(54, 48, 40, 0.92), rgba(40, 35, 28, 0.88));
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 8px 20px -6px rgba(33, 29, 24, 0.45);
}

/* Toolbar icon affordance: circular, material appears on hover (macOS-style),
   deepens on press. Visual size unchanged from the old square hover. */
.gg-icon-btn {
  border-radius: 9999px;
  transition: background-color 0.16s ease, transform 0.26s var(--ease-spring), color 0.16s ease;
}
.gg-icon-btn:hover { background-color: rgba(65, 55, 42, 0.07); }
.gg-icon-btn:active { background-color: rgba(65, 55, 42, 0.13); transform: scale(0.96); }

/* ---- Liquid glass segmented controls (view switcher, mode tabs, steppers) --- */
.glass-segment {
  --seg-h: 38px;
  --seg-pad: 4px;
  --seg-item-h: 30px;
  --seg-item-px: 14px;
  --seg-gap: 7px;
  --seg-font: 13px;
  --seg-icon: 14px;
  position: relative;
  display: inline-flex;
  width: 100%;
  height: var(--seg-h);
  padding: var(--seg-pad);
  border-radius: 999px;
  /* Recessed translucent trough — the page tint shows through the glass and the
     interior is darkened/inset so the raised lens reads as the only bright pill.
     (No white capsule fill, which previously stacked under the lens.) */
  background: rgba(96, 84, 66, 0.10);
  border: 1px solid rgba(70, 62, 48, 0.12);
  box-shadow:
    inset 0 1.5px 3px rgba(54, 49, 42, 0.14),
    inset 0 0 0 0.5px rgba(54, 49, 42, 0.035),
    inset 0 -1px 0 rgba(255, 255, 255, 0.42),
    0 1px 0 rgba(255, 255, 255, 0.48);
  backdrop-filter: blur(14px) saturate(1.26);
  -webkit-backdrop-filter: blur(14px) saturate(1.26);
}
.glass-segment--compact {
  --seg-h: 34px;
  --seg-item-h: 26px;
  --seg-item-px: 0;
  --seg-icon: 14px;
}
/* Extra-small — used for the AM/PM toggle in the time field, where the default
   compact size dwarfs the little hour/minute inputs beside it. */
.glass-segment--xs {
  --seg-h: 24px;
  --seg-item-h: 18px;
  --seg-item-px: 0;
  --seg-font: 10px;
  --seg-icon: 11px;
  --seg-pad: 3px;
}
.glass-segment--xs.glass-segment--inline .glass-segment__item { padding: 0 6px; }
.glass-segment__track {
  position: relative;
  display: grid;
  /* minmax(0, 1fr) — NOT 1fr — so the three segments are forced to exactly
     equal widths. Plain 1fr keeps an implicit min-content floor, which let the
     wider labels ("Calendar") blow their columns out past 1/3 and overflow the
     trough, so the fixed 1/3 lens never lined up with the active item. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Pin the single row to the trough's inner height so the items (which carry a
     slightly taller --seg-item-h) center on the lens's vertical midline rather
     than sitting against the top edge. */
  grid-template-rows: 100%;
  width: 100%;
  height: 100%;
  z-index: 1;
}
.glass-segment__lens-wrap {
  position: absolute;
  inset-block: 0;
  left: 0;
  /* Exactly one segment wide; translateX(idx * 100%) then steps it cell-to-cell
     and it lines up pixel-for-pixel with the equal columns above. */
  width: calc(100% / 3);
  padding: 0;
  pointer-events: none;
  transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* The raised glass bubble — a bright, clear lens that refracts the trough.
   Strong specular top, soft drop shadow below, faint inner rim. */
.glass-segment__lens {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 999px;
  overflow: hidden;
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.84) 0%,
      rgba(250, 252, 254, 0.64) 48%,
      rgba(242, 246, 250, 0.50) 100%
    );
  border: 1px solid rgba(255, 255, 255, 0.72);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.82),
    inset 0 0 0 0.5px rgba(70, 62, 48, 0.05),
    inset 0 -3px 6px -2px rgba(255, 255, 255, 0.42),
    0 2px 5px rgba(40, 32, 22, 0.16),
    0 7px 18px -4px rgba(40, 32, 22, 0.16);
  backdrop-filter: blur(5px) saturate(1.22) brightness(1.0);
  -webkit-backdrop-filter: blur(5px) saturate(1.22) brightness(1.0);
}
/* Specular refraction arc: a crisp bright sheen across the top third, fading
   out — light catching the curved face of the bubble. */
.glass-segment__lens::before {
  content: "";
  position: absolute;
  inset: 1px 1px auto 1px;
  height: 52%;
  border-radius: 999px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.64),
    rgba(255, 255, 255, 0.08) 70%,
    transparent
  );
  pointer-events: none;
}
/* A faint warm return-light gather along the bottom edge for thickness. */
.glass-segment__lens::after {
  content: "";
  position: absolute;
  inset: auto 6% -1px 6%;
  height: 38%;
  border-radius: 999px;
  background: radial-gradient(
    120% 100% at 50% 120%,
    rgba(255, 255, 255, 0.32),
    transparent 70%
  );
  pointer-events: none;
}
.glass-segment__item {
  position: relative;
  z-index: 1;
  /* Center within the grid row so the label sits exactly on the lens's vertical
     midline, and allow the cell to size below content width so the equal
     columns hold (paired with minmax(0, 1fr) on the track). */
  align-self: center;
  min-width: 0;
  height: var(--seg-item-h);
  padding: 0 var(--seg-item-px);
  border: 0;
  border-radius: 999px;
  background: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--seg-gap);
  color: rgba(36, 33, 29, 0.58);
  font: 520 var(--seg-font)/1 'Outfit', ui-sans-serif, system-ui, sans-serif;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    color 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
    background-color 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.glass-segment__item:hover {
  color: rgba(36, 33, 29, 0.76);
  background: rgba(255, 255, 255, 0.18);
}
.glass-segment__item[data-active="true"],
.glass-segment__item[aria-current="page"] {
  color: rgba(28, 26, 22, 0.94);
  background: transparent;
}
.glass-segment__item[data-active="true"]:hover,
.glass-segment__item[aria-current="page"]:hover {
  color: rgba(28, 26, 22, 0.94);
  background: transparent;
}
.glass-segment__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--seg-icon);
  height: var(--seg-icon);
  flex-shrink: 0;
  opacity: 0.9;
}
.glass-segment__label {
  font-size: var(--seg-font);
  font-weight: 520;
}
.glass-segment--inline {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  width: auto;
  height: auto;
  min-height: var(--seg-h);
  flex-wrap: nowrap;
}
.glass-segment--inline::before { display: none; }
.glass-segment--inline .glass-segment__track {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  width: auto;
  height: auto;
  grid-template-columns: none;
}
.glass-segment--scroll {
  overflow-x: auto;
  scrollbar-width: none;
  max-width: 100%;
}
.glass-segment--scroll::-webkit-scrollbar { display: none; }

/* Scroll-aware edge fades for the mode ribbon. The wrapper is non-scrolling so
   the fades hold their place (and don't dim the segment's own frosted edge);
   they only show on the side with more chips off-screen, like the top ribbon. */
.seg-scroll-wrap { position: relative; }
.seg-fade {
  position: absolute;
  top: 1px;
  bottom: 1px;
  width: 1.5rem;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 3;
}
.seg-fade.is-on { opacity: 1; }
/* Outer corners follow the segment's 999px pill (not radius-sm) and sit 1px off
   the left/right rim — like the 1px top/bottom inset — so the fade never paints
   over the pill's rounded border. */
.seg-fade--l { left: 1px; border-radius: 999px 0 0 999px; background: linear-gradient(90deg, rgba(240, 242, 245, 0.97), rgba(240, 242, 245, 0)); }
.seg-fade--r { right: 1px; border-radius: 0 999px 999px 0; background: linear-gradient(270deg, rgba(240, 242, 245, 0.97), rgba(240, 242, 245, 0)); }
html.dark .seg-fade--l { background: linear-gradient(90deg, rgba(0, 0, 0, 0.97), rgba(0, 0, 0, 0)); }
html.dark .seg-fade--r { background: linear-gradient(270deg, rgba(0, 0, 0, 0.97), rgba(0, 0, 0, 0)); }
@media (prefers-reduced-motion: reduce) { .seg-fade { transition: none; } }
.glass-segment--inline .glass-segment__item {
  flex-shrink: 0;
  padding: 0 12px;
  height: var(--seg-item-h);
  overflow: hidden;
}
/* Keep the label/icon above the active item's specular sheen. */
.glass-segment__item > * { position: relative; z-index: 2; }
.glass-segment--inline .glass-segment__item[data-active="true"] {
  color: rgba(28, 26, 22, 0.94);
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.84) 0%,
      rgba(250, 252, 254, 0.64) 48%,
      rgba(242, 246, 250, 0.50) 100%
    );
  border: 1px solid rgba(255, 255, 255, 0.72);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.82),
    inset 0 0 0 0.5px rgba(70, 62, 48, 0.05),
    0 2px 5px rgba(40, 32, 22, 0.16),
    0 7px 18px -4px rgba(40, 32, 22, 0.16);
  backdrop-filter: blur(5px) saturate(1.22) brightness(1.0);
  -webkit-backdrop-filter: blur(5px) saturate(1.22) brightness(1.0);
}
.glass-segment--inline .glass-segment__item[data-active="true"]::before {
  content: "";
  position: absolute;
  inset: 1px 1px auto 1px;
  height: 52%;
  border-radius: 999px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.64),
    rgba(255, 255, 255, 0.08) 70%,
    transparent
  );
  pointer-events: none;
  z-index: 1;
}
.glass-segment--stepper {
  --seg-h: 36px;
  --seg-item-h: 28px;
  width: auto;
  display: inline-flex;
}
.glass-segment--stepper .glass-segment__track {
  display: inline-flex;
  align-items: center;
  grid-template-columns: none;
}
.glass-segment--stepper .glass-segment__item {
  padding: 0 12px;
  min-width: 2.25rem;
}
.glass-segment--stepper .glass-segment__item--value {
  min-width: 2.5rem;
  font-weight: 600;
  color: rgba(28, 26, 22, 0.92);
  cursor: default;
  pointer-events: none;
}
.glass-segment--stepper .glass-segment__item--value:hover {
  background: transparent;
  color: rgba(28, 26, 22, 0.92);
}
.glass-segment--stepper .glass-segment__item:disabled {
  opacity: 0.35;
  cursor: default;
  pointer-events: none;
}
.glass-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.625rem;
  border-radius: 999px;
  border: 1px solid rgba(70, 62, 48, 0.10);
  background: rgba(255, 255, 255, 0.20);
  color: rgba(36, 33, 29, 0.62);
  font-size: 12px;
  font-weight: 520;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    color 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
    background 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
    box-shadow 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.glass-chip:hover {
  color: rgba(36, 33, 29, 0.78);
  background: rgba(255, 255, 255, 0.30);
  border-color: rgba(70, 62, 48, 0.14);
}
.glass-chip {
  position: relative;
  overflow: hidden;
}
.glass-chip > * { position: relative; z-index: 2; }
.glass-chip[data-active="true"] {
  color: rgba(28, 26, 22, 0.94);
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.84) 0%,
      rgba(250, 252, 254, 0.64) 48%,
      rgba(242, 246, 250, 0.50) 100%
    );
  border: 1px solid rgba(255, 255, 255, 0.72);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.82),
    inset 0 0 0 0.5px rgba(70, 62, 48, 0.05),
    0 2px 5px rgba(40, 32, 22, 0.16),
    0 6px 14px -4px rgba(40, 32, 22, 0.14);
  backdrop-filter: blur(5px) saturate(1.22) brightness(1.0);
  -webkit-backdrop-filter: blur(5px) saturate(1.22) brightness(1.0);
}
.glass-chip[data-active="true"]::before {
  content: "";
  position: absolute;
  inset: 1px 1px auto 1px;
  height: 52%;
  border-radius: 999px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.64),
    rgba(255, 255, 255, 0.08) 70%,
    transparent
  );
  pointer-events: none;
  z-index: 1;
}

/* ---- Surfaces ---------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.card-hover { transition: box-shadow 0.22s var(--ease-ios), border-color 0.22s ease, transform 0.22s var(--ease-ios); }
.card-hover:hover { box-shadow: 0 14px 32px -12px rgba(54, 49, 42, 0.20), 0 5px 12px -8px rgba(54, 49, 42, 0.13); border-color: var(--line-strong); transform: translateY(-1px); }
/* Already-planned recs: the recessed --planned-bg fill + border carry the
   "planned" read on their own. */
.rec-planned {
  position: relative;
  background: var(--planned-bg);
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
}
.rec-planned.card-hover:hover {
  transform: none;
  border-color: var(--line-strong);
  box-shadow: var(--shadow-sm);
}

/* Timeline stop card: this rule owns the FULL transition list (expand/collapse
   margins + radius on the 400ms iOS curve, hover lift, tap tint). Longhands
   only — a `transition:` shorthand in a :hover/:active rule would replace the
   whole list while the pointer is over the card, which is exactly when the
   collapse runs, snapping the full-bleed margins instead of animating them. */
.stop-card-interactive {
  transition-property: margin, border-radius, border-color, box-shadow, transform, opacity, background-color;
  transition-duration: 0.4s, 0.4s, 0.4s, 0.26s, 0.26s, 0.26s, 0.2s;
  transition-timing-function: var(--ease-ios), var(--ease-ios), var(--ease-ios), var(--ease-ios), var(--ease-ios), ease, ease;
}
/* Width (full-bleed margins) and height (.detail-clip panel) must share ONE
   curve, or the sides visibly lag the downward growth on phones. Both ride the
   base 0.4s iOS curve in every direction — the old phone-only ease-in override
   on the margins was exactly what desynced the open (sides slow, down quick). */
.stop-card-interactive:hover {
  background-color: rgb(250 250 249); /* warm near-white tint */
}
.stop-card-interactive:active {
  background-color: rgb(214 211 209); /* stone-300 — solid press */
  /* Same structural timings; only the press tint (last entry) is instant. */
  transition-duration: 0.4s, 0.4s, 0.4s, 0.26s, 0.26s, 0.26s, 0ms;
}
/* Open/close: suppress the tap tint so a collapse click doesn't flash gray
   through the sticky header gap or linger over the height animation. */
.stop-card-interactive.stop-card-animating,
.stop-card-interactive.stop-card-animating:hover,
.stop-card-interactive.stop-card-animating:active {
  background-color: var(--surface);
}
.stop-card-interactive.stop-card-animating:hover .thumb-tap-overlay,
.stop-card-interactive.stop-card-animating:active .thumb-tap-overlay {
  opacity: 0;
  transition: opacity 0ms;
}
/* One clock for margin, radius, and shadow during expand/collapse. */
.stop-card-interactive.stop-card-animating {
  transition-duration: 0.4s, 0.4s, 0.4s, 0.4s, 0.4s, 0.26s, 0.2s;
  transition-timing-function: var(--ease-ios), var(--ease-ios), var(--ease-ios), var(--ease-ios), var(--ease-ios), ease, ease;
  transform: none !important;
}
@media (max-width: 639.98px) {
  .stop-card-interactive.stop-card-animating.stop-fullbleed {
    transition-duration: 0.4s, 0.4s, 0.4s, 0.4s, 0.4s, 0.26s, 0.2s;
    transition-timing-function: var(--ease-open), var(--ease-open), var(--ease-open), var(--ease-open), var(--ease-open), ease, ease;
  }
}
/* Open: snappy steep-start curve so the full-bleed width + radius begin growing
   immediately (not the old slow ease-in), matching the height. Close stays on
   the base iOS curve via .stop-card-animating. */
.stop-card-interactive.stop-card-opening {
  transition-duration: 0.4s, 0.4s, 0.4s, 0.4s, 0.4s, 0.26s, 0.2s;
  transition-timing-function: var(--ease-open), var(--ease-open), var(--ease-open), var(--ease-open), var(--ease-open), ease, ease;
}
.stop-card-opening .stop-head-actions {
  transition-duration: 0.48s;
  transition-timing-function: var(--ease-reveal);
}
/* The header band's left padding is what slides the city name to the bleed edge
   on open. Match it to the thumbnail's 240ms collapse (was the slow 0.48s reveal)
   so the name moves left promptly instead of drifting. */
.stop-card-opening .stop-head-band {
  transition-duration: 0.24s;
  transition-timing-function: var(--ease-ios);
}
/* Edit/trash tools snap in quickly on open (they push the city name, so a slow
   0.48s reveal felt like a laggy shove) — keep them brisk and start on tap. */
.stop-card-opening .stop-head-actions__tools {
  transition-duration: 0.22s;
  transition-timing-function: var(--ease-ios);
}
/* Ambient "light off the thumbnail": a heavily blurred, low-opacity wash of the
   city photo that spills from the thumbnail (left) across the card and fades out,
   so each collapsed card quietly glows with its image's colours behind the text.
   Sits below the content via negative z within the card's own isolate context. */
.stop-ambient {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-size: cover;
  background-position: center;
  /* Real, clean colour from the photo: keep brightness near neutral (too much
     washes it to a pale beige), let saturation + contrast pull the actual hues
     out (green, blue, brick), and a touch less blur preserves their variety. */
  filter: blur(26px) saturate(2.2) brightness(1.16) contrast(1.14);
  opacity: 0.42;
  /* Overscan so the wandering drift never reveals the card edge. */
  transform: scale(1.12);
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 30%, rgba(0, 0, 0, 0.45) 64%, transparent 95%);
  mask-image: linear-gradient(90deg, #000 0%, #000 30%, rgba(0, 0, 0, 0.45) 64%, transparent 95%);
  /* Parallax drift so the colour feels alive — wandering across the card. */
  animation: ambientDrift 10s ease-in-out infinite;
  pointer-events: none;
  border-radius: inherit;
}
html.dark .stop-ambient { opacity: 0.42; }

/* Frost: the middle layer between the thumbnail-colour wash and the text. A
   clean, cool white veil that lifts the colour toward white for crisp text
   contrast while still reading as frosted glass — no grain (it read as a
   see-through speckle). inset 0 / radius-inherit so it matches the card edges. */
.stop-frost {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  border-radius: inherit;
  background-color: rgba(248, 250, 253, 0.46);
  /* Very fine, grayscale film grain (high-frequency fractal noise) for the
     frosted-glass texture — subtle enough to read as grain, not visible dots. */
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='160'%20height='160'%3E%3Cfilter%20id='gf'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.92'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3CfeColorMatrix%20type='saturate'%20values='0'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23gf)'%20opacity='0.32'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
html.dark .stop-frost {
  background-color: rgba(20, 24, 30, 0.36);
}
@keyframes ambientDrift {
  0% { transform: scale(1.12) translate3d(0, 0, 0); }
  33% { transform: scale(1.24) translate3d(6%, -4.5%, 0); }
  66% { transform: scale(1.2) translate3d(-4%, 4.5%, 0); }
  100% { transform: scale(1.12) translate3d(0, 0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .stop-ambient { animation: none; transform: scale(1.06); }
}

/* Collapsed header layout. Mobile: city + country inline, stats below, hotel last.
   Desktop: 2-column grid — name top-left, hotel below, stay-stats pinned right. */
.shg-name {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.5rem;
  min-width: 0;
}
.shg-name > .shg-city {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Owns the full first line; only ellipsizes if a single name is wider than
     the whole row. */
  flex: 0 1 auto;
}
.shg-name > .shg-country {
  flex-shrink: 0;
  /* Always drop onto its own line beneath the city name (consistent across
     collapsed and expanded cards) — flex-basis:100% forces the wrap. */
  flex-basis: 100%;
}
@media (min-width: 640px) {
  .shg-name {
    gap: 0.25rem 0.5rem;
  }
  .stop-head-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 1.5rem;
  }
  .stop-head-grid > .shg-name { grid-column: 1; grid-row: 1; }
  .stop-head-grid > .shg-hotel { grid-column: 1; grid-row: 2; }
  .stop-head-grid > .shg-stats {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
    justify-self: end;
    text-align: right;
    margin-top: 0;
  }
}

/* City header band — padding/gap share the card's iOS curve. */
.stop-head-band {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.75rem 0.375rem 0.75rem 0.875rem;
  transition: gap 0.4s var(--ease-ios), padding-left 0.4s var(--ease-ios);
}
@media (min-width: 640px) {
  .stop-head-band {
    padding-top: 1.125rem;
    padding-bottom: 1.125rem;
    padding-left: 0.875rem;
  }
}
.stop-head-band.is-wide {
  gap: 0.5rem;
}
/* Drop the band's left pad while the card is actually full-bleed. Tied to `wide`
   (which flips at the START of a collapse), and transitioned, so the gap grows
   back IN STEP with the returning thumbnail instead of snapping in at the end
   (which jumped the text). Expanded cards are already inset by
   --app-content-inset, so the name still lines up flush with the wordmark. */
.stop-head-band.is-bleed {
  padding-left: 0;
}
/* Expanded full-bleed cards: line the city header up with the app top bar. */
.stop-fullbleed .stop-sticky-head,
.stop-fullbleed .flex.items-stretch:has(.stop-head-band.is-wide) {
  padding-left: var(--app-content-inset);
  padding-right: var(--app-content-inset);
}
.stop-head-actions {
  display: flex;
  align-items: center;
  gap: 0;
  transition: gap 0.4s var(--ease-ios);
}
.stop-head-actions.is-wide { gap: 0.125rem; }
.stop-head-actions__tools {
  max-width: 0;
  opacity: 0;
  pointer-events: none;
  transition: max-width 0.4s var(--ease-ios), opacity 0.4s var(--ease-ios);
}
.stop-head-actions__tools.is-wide {
  max-width: 5rem;
  opacity: 1;
  pointer-events: auto;
}
.stop-head-actions .stop-chevron {
  transition: transform 0.4s var(--ease-ios);
}
.stop-head-actions:not(.is-wide) .stop-chevron {
  transform: rotate(-90deg);
}
/* Thumbnail dimmer — sits above color chip + image, reacts to the same
   hover/active on the outer card so the whole surface (text + photo) moves
   together as one piece of feedback. */
.thumb-tap-overlay {
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}
.stop-card-interactive:hover .thumb-tap-overlay {
  opacity: 0.08;
}
.stop-card-interactive:active .thumb-tap-overlay {
  opacity: 0.20;
  transition: opacity 0ms;
}

/* iOS Safari auto-zooms the page whenever a focused form control has a
   font-size under 16px (e.g. our text-sm/text-xs inputs). Pin every text-
   entry control to 16px on phones so tapping into one — the day-plan "add"
   field, the chat composer, date inputs — never triggers that zoom.
   Exception: the leg hour/minute boxes (.tf-time-num) opt out so they can match
   the small AM/PM toggle beside them instead of towering over it. */
@media (max-width: 639.98px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not(.tf-time-num),
  textarea,
  select {
    font-size: 16px !important;
  }
}

/* ---- Typography helpers ----------------------------------------------- */
.section-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
}
.section-hint {
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--faint);
}

/* Expanded stop card — keep one clean surface, readable type. */
.stop-detail {
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.stop-detail .section-label {
  color: #5c5547;
}
.stop-detail .section-hint {
  color: #7f7768;
  font-weight: 500;
}
.stop-detail-blurb {
  padding-left: 0;
}

/* An expanded timeline stop breaks out of the centered app shell to span the
   full browser width — a magazine-style feature spread. The negative margins
   pull it to the viewport edges regardless of the shell's max-width (the plain
   `-mx-4` only cleared the shell's own gutter). #root's `overflow-x: clip`
   absorbs the tiny 100vw/scrollbar overflow. */
.stop-fullbleed {
  /* The rail's right-only breathing pad shifts this card's container ~half-a-pad
     left of viewport-centre, so symmetric `calc(50% - 50vw)` margins leave a gap
     on the right. Bias each side by half the pad to reach the true edges. */
  margin-left: calc(50% - 50vw + (var(--tl-rail-pad, 6px) / 2));
  margin-right: calc(50% - 50vw - (var(--tl-rail-pad, 6px) / 2));
}

/* Expanded stop card: pin its header (city name + nights + weather) just beneath
   the app's sticky top bar while you scroll the long day-plan below, so a quick
   tap on it (or its chevron) is always in reach to collapse the card. The shelf
   shadow only appears once the header is actually stuck. `--app-bar-h` is the
   measured top-bar height, published from App.js. */
.stop-sticky-head {
  position: sticky;
  top: calc(var(--app-bar-h, 3.5rem) - 1px);
  z-index: 20;
  transition: background-color 0.35s var(--ease-ios), box-shadow 0.35s var(--ease-ios);
}
html[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck {
  top: calc(var(--app-bar-h, 3.5rem) - 1px);
  isolation: isolate;
}
.stop-sticky-head.is-stuck:not(.is-merged) {
  box-shadow: var(--shadow-shelf);
}
html.dark .stop-sticky-head.is-stuck:not(.is-merged) {
  box-shadow: var(--shadow-shelf);
}
/* Stuck + merged: city band shares the app bar's shelf shadow. */
html[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck {
  background-color: transparent;
  box-shadow: var(--shadow-shelf);
}
html[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--gg-panel-fill);
  -webkit-backdrop-filter: blur(30px) saturate(1.9) brightness(1.0);
  backdrop-filter: blur(30px) saturate(1.9) brightness(1.0);
  box-shadow: inset 0 -1px 0 rgba(65, 55, 42, 0.05);
  transform: translateZ(0);
  transition: opacity 0.35s var(--ease-ios);
}
html.dark[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck {
  box-shadow: var(--shadow-shelf);
}
html.dark[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck::before {
  -webkit-backdrop-filter: blur(32px) saturate(1.45) brightness(0.86) contrast(1.03);
  backdrop-filter: blur(32px) saturate(1.45) brightness(0.86) contrast(1.03);
  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}

/* Merged app bar + city header → one continuous pane. The two stacked glass
   layers each painted the diagonal sheen from --gg-panel-fill, so the streak
   "restarted" at the join and the bar's bottom hairline drew a hard line —
   together reading as two panes with a crease. Give both the SAME flat vertical
   fill (no directional sheen) and drop the bar's bottom hairline (keep only its
   top rim), so the matched backdrop layers read as a single uniform header. */
html[data-merged-stop-head] .gg-toolbar::before,
html[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck::before {
  background: linear-gradient(180deg, rgba(247, 251, 253, 0.09), rgba(236, 243, 249, 0.045));
}
html[data-merged-stop-head] .gg-toolbar::before {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42);
}
html.dark[data-merged-stop-head] .gg-toolbar::before,
html.dark[data-merged-stop-head] .stop-sticky-head.is-merged.is-stuck::before {
  background: linear-gradient(180deg, rgba(32, 35, 28, 0.24), rgba(24, 27, 20, 0.18));
}

/* Desktop "Day by day" sidebar: stick it just below the expanded card's pinned
   header (and the app bar) rather than behind them. `--stop-head-h` is the
   header's measured height, published per card from StopBlock. */
@media (min-width: 1024px) {
  .stop-detail-aside {
    position: sticky;
    align-self: flex-start;
    top: calc(var(--app-bar-h, 3.5rem) + var(--stop-head-h, 4.5rem) + 0.5rem);
  }
}

/* ---- Buttons ---------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.5rem 0.85rem;
  border-radius: var(--radius-sm);
  font-size: 0.875rem; font-weight: 500;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.26s var(--ease-spring), box-shadow 0.15s ease;
  border: 1px solid transparent;
  cursor: pointer;
}
.btn:active { transform: translateY(1px) scale(0.985); }
.btn-primary { background: var(--pill); color: #fffefb; }
.btn-primary:hover { background: var(--pill-hover); }
.btn-accent { background: var(--accent); color: #fffefb; }
.btn-accent:hover { background: var(--accent-deep); }
.btn-quiet { color: var(--muted); }
.btn-quiet:hover { background: #f0f1f2; color: var(--ink); }
.btn-outline { border-color: var(--line-strong); color: var(--ink); background: #fff; }
.btn-outline:hover { background: #fafafa; border-color: #cfd5da; }

/* ---- Badges & chips (quiet, single muted accent) ---------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: 0.625rem; font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.1rem 0.4rem;
  border-radius: 9999px;
  color: var(--accent);
  background: var(--accent-soft);
  line-height: 1.2;
  white-space: nowrap;
}
.chip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.6875rem; font-weight: 500;
  padding: 0.12rem 0.45rem;
  border-radius: 9999px;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--line);
  line-height: 1.2;
  white-space: nowrap;
}

/* Drawn country flags: keep the existing SVG flags, but force every instance
   into the same clipped, middle-aligned mini badge so corners and baselines are
   consistent in cards, ribbons, labels, and modals. */
.flag-glyph {
  overflow: hidden;
  border-radius: 0.26rem;
  vertical-align: -0.12em;
  line-height: 1;
  transform: translateY(0.04em);
  box-shadow: inset 0 0 0 0.5px rgba(54, 70, 82, 0.14);
}
.flag-glyph-svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Tier ranking: a small dot + label, one muted accent (no rainbow fills). */
.tier { display: inline-flex; align-items: center; gap: 0.28rem; font-size: 0.625rem; font-weight: 700; color: var(--muted); line-height: 1.2; white-space: nowrap; }
.tier-dot { width: 0.45rem; height: 0.45rem; border-radius: 9999px; background: #9aa0a6; display: inline-block; }
/* Distinct hues per tier: sage green (must-see) → amber (worth it) → cool gray (optional). */
.tier-1 .tier-dot { background: #5c8a4a; }
.tier-1 { color: #3f6a30; }
.tier-2 .tier-dot { background: #cf8a2c; }
.tier-2 { color: #a4661c; }
.tier-3 .tier-dot { background: #9aa0a6; }
.tier-3 { color: #7c828a; }
/* Tier 4: a "deep cut" — even quieter than tier 3 (hollow dot). */
.tier-4 .tier-dot { background: transparent; border: 1.5px solid #b6ac98; width: 0.42rem; height: 0.42rem; }
.tier-4 { color: #938a76; }

/* ---- Scrollbars ------------------------------------------------------- */
.scrollbar-none { scrollbar-width: none; }
.scrollbar-none::-webkit-scrollbar { display: none; }
.scrollbar-thin { scrollbar-width: thin; scrollbar-color: #d6cfbe transparent; }
.scrollbar-thin::-webkit-scrollbar { height: 8px; width: 8px; }
.scrollbar-thin::-webkit-scrollbar-track { background: transparent; }
.scrollbar-thin::-webkit-scrollbar-thumb { background: #ddd8cc; border-radius: 9999px; }
.scrollbar-thin::-webkit-scrollbar-thumb:hover { background: #c2bbab; }

/* ---- Claus ------------------------------------------------------------ */
.trip-chat-sheet {
  --trip-chat-text: 15px;
  --trip-chat-leading: 1.5;
  touch-action: manipulation;
  position: absolute; /* .gg-sheet is relative; anchor right/inset via utilities */
}
/* Bump the chat text up on phones — 14px reads too small there. */
@media (max-width: 639px) {
  .trip-chat-sheet { --trip-chat-text: 16px; }
}
/* Full-width phone sheet: square edges so the composer isn't clipped by rim/corners. */
.trip-chat-sheet--mobile {
  border-radius: 0;
  border-left: none;
  border-right: none;
  border-bottom: none;
  overflow: hidden;
}
.trip-chat-sheet--mobile.gg-rim::before,
.trip-chat-sheet--mobile.gg-rim::after {
  display: none;
}
.trip-chat-sheet--mobile .trip-chat-composer {
  overflow: visible;
  background: var(--side-sheet-fill);
}
@supports (-webkit-touch-callout: none) {
  .trip-chat-sheet--mobile .trip-chat-composer {
    background-color: var(--side-sheet-ios-tint);
  }
}
.trip-chat-sheet--mobile .trip-chat-input-shell {
  border-radius: 9999px;
  overflow: hidden;
}
.trip-chat-sheet--mobile .trip-chat-input-shell--multiline {
  border-radius: 1.35rem;
}
.trip-chat-sheet--mobile .trip-chat-messages {
  -webkit-overflow-scrolling: touch;
}
.trip-chat-header {
  position: relative;
  z-index: 2;
  background: var(--side-sheet-fill);
  -webkit-backdrop-filter: var(--side-sheet-blur);
  backdrop-filter: var(--side-sheet-blur);
  /* The header's 1px bottom border shares its exact edge with the top of the
     scrolling messages list. In Safari that shared edge isn't always repainted
     when the list scrolls, so the divider line vanishes mid-scroll. Promote the
     header to its own compositor layer so its border paints independently. */
  transform: translateZ(0);
}
@supports (-webkit-touch-callout: none) {
  .trip-chat-header { background-color: var(--side-sheet-ios-tint); }
}
/* Desktop: the header doubles as a drag handle for moving the floating card. */
.trip-chat-header--drag {
  cursor: grab;
  touch-action: none;
}
.trip-chat-header--dragging {
  cursor: grabbing;
}
.trip-chat-sheet--resizing {
  transition: none !important;
  user-select: none;
}
.trip-chat-resize {
  position: absolute;
  touch-action: none;
  z-index: 30;
  background: transparent;
}
/* Left edge → width (card is anchored bottom-right, so dragging left grows it). */
.trip-chat-resize--x {
  left: 0;
  top: 0;
  bottom: 0;
  width: 7px;
  margin-left: -3px;
  cursor: ew-resize;
}
/* Top edge → height. */
.trip-chat-resize--y {
  top: 0;
  left: 0;
  right: 0;
  height: 7px;
  margin-top: -3px;
  cursor: ns-resize;
}
/* Top-left corner → both axes at once; sits above the two edge strips. */
.trip-chat-resize--corner {
  top: 0;
  left: 0;
  width: 15px;
  height: 15px;
  margin-top: -3px;
  margin-left: -3px;
  cursor: nwse-resize;
  z-index: 31;
}
.trip-chat-resize--x:hover {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent, #5f7a54) 22%, transparent), transparent);
}
.trip-chat-resize--y:hover {
  background: linear-gradient(0deg, color-mix(in srgb, var(--accent, #5f7a54) 22%, transparent), transparent);
}
/* Desktop: Claus floats as a resizable card in the bottom-right corner (not a
   full-height side sheet). It uses the same hard offset shadow as the app's
   cards on hover (no soft blur), and there's no scrim — the page underneath
   stays fully interactive. */
.trip-chat-sheet.trip-chat-sheet--float {
  box-shadow: var(--shadow-offset) !important;
}
.trip-chat-text,
.trip-chat-messages {
  font-size: var(--trip-chat-text);
  line-height: var(--trip-chat-leading);
}
.trip-chat-suggestion {
  font-size: 13px;
  line-height: 1.4;
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid rgba(214, 220, 230, 0.5);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 1px 2px rgba(54, 49, 42, 0.05);
  -webkit-backdrop-filter: blur(10px) saturate(1.2);
  backdrop-filter: blur(10px) saturate(1.2);
}
.trip-chat-suggestion:hover {
  background: rgba(255, 255, 255, 0.72);
  border-color: color-mix(in srgb, var(--accent) 30%, #c8d2dc);
  color: var(--accent-deep, #4a5f42);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 6px 16px -8px rgba(54, 49, 42, 0.18);
}

/* Starter-prompt marquee — two rows of tappable chips drifting in opposite
   directions, so a lot of agentic ideas fit without a tall wall of buttons. */
.trip-chat-marquee {
  display: flex;
  flex-direction: column;
  gap: 0;
  /* Bleed to the panel edges; fade chips in/out at the sides. */
  margin: 0 -1rem;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
/* Rows scroll horizontally — auto-scrolled by JS (which also pauses on hover),
   and the user can swipe/drag/wheel through them too. Scrollbar hidden. The
   vertical padding gives the hover lift + drop shadow room so overflow-y:hidden
   (needed alongside overflow-x:auto) doesn't clip the top of a hovered pill. */
.trip-chat-marquee-row {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  padding-block: 0.8rem;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
.trip-chat-marquee-row::-webkit-scrollbar { display: none; }
.trip-chat-marquee-track {
  display: flex;
  align-items: center;
  width: max-content;
}
.trip-chat-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Uniform right-margin on every chip (not flex `gap`) keeps the two duplicated
     copies exactly equal width, so the -50% loop is seamless. */
  margin-right: 0.6rem;
  /* Hug the text, but cap the width so long prompts wrap onto two lines. The
     min-height reserves two lines so every pill is the same height — a short
     one-line prompt sits centred rather than as a stubby single-row pill. */
  max-width: 15rem;
  min-height: 3.3rem;
  white-space: normal;
  text-align: center;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  padding: 0.5rem 1.05rem;
  border-radius: 9999px;
  /* Quiet, fresh light-green by default so the suggestions read as secondary. */
  border: 1.5px solid color-mix(in srgb, #63a852 42%, transparent);
  background: color-mix(in srgb, #63a852 17%, var(--surface));
  color: color-mix(in srgb, #63a852 76%, var(--ink));
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
/* Long prompts wrap to at most two lines. */
.trip-chat-chip-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
/* Hover/press: come forward to a stronger green, lift, and cast the app's hard
   offset shadow (no blur) like the cards. Adaptive via --line (near-black in
   light, light in dark); the row's vertical padding keeps it from clipping. */
.trip-chat-chip:hover {
  background: color-mix(in srgb, #63a852 30%, var(--surface));
  border-color: color-mix(in srgb, #63a852 66%, var(--line));
  color: color-mix(in srgb, #63a852 56%, var(--ink));
  transform: translateY(-2px);
  box-shadow: var(--shadow-offset-sm);
}
.trip-chat-chip:active {
  transform: translateY(0);
  box-shadow: 2px 2px 0 0 var(--line);
}
.trip-chat-chip:disabled { opacity: 0.5; cursor: default; box-shadow: none; transform: none; }
/* Concierge brief's own suggestion rows sit beneath the typed-in brief as the
   main chat screen — ~25% smaller than the starter pills, with the two rows
   pulled closer together. */
.trip-chat-marquee--concierge .trip-chat-marquee-row { padding-block: 0.45rem; }
.trip-chat-marquee--concierge .trip-chat-chip {
  margin-right: 0.5rem;
  max-width: 13rem;
  min-height: 2.7rem;
  font-size: 12px;
  padding: 0.45rem 0.9rem;
}
/* ── Overnight briefing as an 80mm thermal RECEIPT ─────────────────────────
   The managed-agent brief renders as a printable receipt: paper white even in
   dark mode (it's a physical object), monospace body, dashed rules, one riso
   spot-ink accent on screen (forced to pure black in print). 80mm ≈ 302px. */
.trip-chat-receipt {
  --paper: #fdfcf7;
  --r-ink: #17140f;
  --r-spot: #b8442c; /* riso spot ink (screen only) */
  position: relative;
  width: min(100%, 302px);
  margin: 0 auto;
  padding: 1rem 0.9rem 0.85rem;
  background: var(--paper);
  color: var(--r-ink);
  border: 1px solid color-mix(in srgb, var(--r-ink) 24%, transparent);
  border-bottom: none;
  border-radius: 1px 1px 0 0;
  box-shadow: 4px 4px 0 0 var(--line);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New', monospace;
  font-size: 11.5px;
  line-height: 1.55;
  font-variant-emoji: text;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
html.dark .trip-chat-receipt { --paper: #fdfcf7; color: #17140f; box-shadow: 4px 4px 0 0 rgba(255, 255, 255, 0.5); }
/* "Prints out of the slot": on load the receipt feeds down as if off a thermal
   head — revealed top→bottom, the paper tipped forward (curling out of the roll)
   and easing flat as it fully emerges, with a soft print-head shadow tracking
   the reveal edge and the leading edge settling into a slight resting curl.
   A short delay lets the printer "warm up" before the paper starts to feed.
   Runs once when the card mounts; typewriter re-renders don't restart it. */
.trip-chat-receipt.receipt-print-in {
  animation: receipt-print-in 2300ms cubic-bezier(0.22, 0.61, 0.2, 1) 560ms both;
  transform-origin: top center;
  will-change: clip-path, transform;
}
@keyframes receipt-print-in {
  0%   { clip-path: inset(0 0 100% 0); transform: perspective(1600px) rotateX(-11deg) translateY(-7px); }
  60%  { transform: perspective(1600px) rotateX(3deg) translateY(0); }
  82%  { transform: perspective(1600px) rotateX(-1.2deg) translateY(0); }
  100% { clip-path: inset(0 -12px -12px 0); transform: perspective(1600px) rotateX(0) translateY(0); }
}
/* The print head's soft shadow, riding just behind the reveal edge as it feeds
   down. Its dark lower lip tracks the boundary; the body sits on the freshly
   emerged paper above it. Same timing/easing as the reveal so they stay locked. */
.trip-chat-receipt.receipt-print-in::before {
  content: '';
  position: absolute;
  left: -1px;
  right: -1px;
  top: -30px;
  height: 30px;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--r-ink) 3%, transparent) 40%,
    color-mix(in srgb, var(--r-ink) 10%, transparent) 74%,
    color-mix(in srgb, var(--r-ink) 20%, transparent) 100%);
  animation: receipt-printhead 2300ms cubic-bezier(0.22, 0.61, 0.2, 1) 560ms both;
}
@keyframes receipt-printhead {
  0%   { top: -30px; opacity: 0; }
  7%   { opacity: 1; }
  90%  { opacity: 1; }
  100% { top: calc(100% - 0px); opacity: 0; }
}
/* The leading (bottom) edge curls forward off the paper as it comes to rest — a
   thin underside shadow band that fades in once the sheet has fully fed out. */
.trip-chat-receipt.receipt-print-in::after {
  content: '';
  position: absolute;
  left: -1px;
  right: -1px;
  bottom: 0;
  height: 20px;
  z-index: 2;
  pointer-events: none;
  border-radius: 0 0 3px 3px;
  background: linear-gradient(to bottom,
    transparent 0%,
    rgba(255, 255, 255, 0.35) 46%,
    color-mix(in srgb, var(--r-ink) 13%, transparent) 100%);
  opacity: 0;
  animation: receipt-curl 900ms ease-out 2760ms both;
}
@keyframes receipt-curl {
  0%   { opacity: 0; transform: translateY(-3px) scaleY(0.6); transform-origin: bottom; }
  100% { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .trip-chat-receipt.receipt-print-in { animation: none; }
  .trip-chat-receipt.receipt-print-in::before { display: none; }
  .trip-chat-receipt.receipt-print-in::after { animation: none; opacity: 1; }
}
/* The print clone is a static snapshot — never animate, clip, or curl it. */
#receipt-print-root .trip-chat-receipt.receipt-print-in {
  animation: none !important;
  clip-path: none !important;
  transform: none !important;
}
#receipt-print-root .trip-chat-receipt.receipt-print-in::before,
#receipt-print-root .trip-chat-receipt.receipt-print-in::after {
  display: none !important;
}
/* Torn bottom edge: a strip of paper-colored teeth pointing down. */
.receipt-tear {
  position: absolute;
  left: -1px;
  right: -1px;
  bottom: -7px;
  height: 7px;
  background-image:
    linear-gradient(45deg, var(--paper) 50%, transparent 50%),
    linear-gradient(-45deg, var(--paper) 50%, transparent 50%);
  background-size: 9px 7px;
  background-position: 0 0, 4.5px 0;
  background-repeat: repeat-x;
}
/* Masthead: stamp glyph, Fraunces logo, letterspaced mono lines, double rule. */
.receipt-masthead {
  text-align: center;
  padding-bottom: 0.55rem;
  margin-bottom: 0.55rem;
  border-bottom: 4px double var(--r-ink);
}
.receipt-stamp { display: block; margin: 0 auto 0.15rem; color: var(--r-spot); }
.receipt-brand {
  font-size: 24px;
  font-weight: 900;
  line-height: 1.1;
  letter-spacing: 0.01em;
  color: var(--r-ink);
}
.receipt-sub {
  margin-top: 0.15rem;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  text-transform: uppercase;
  color: var(--r-spot);
}
.receipt-agents {
  margin-top: 0.3rem;
  font-size: 7.75px;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--r-ink) 62%, transparent);
}
/* Report body: receipt-tuned markdown. */
.receipt-body { color: var(--r-ink); }
.receipt-body .trip-chat-md { font-size: inherit; }
.receipt-body .trip-chat-md-h1,
.receipt-body .trip-chat-md-h2,
.receipt-body .trip-chat-md-h3,
.receipt-body .trip-chat-md-h4 {
  margin: 0.4rem 0 0.25rem;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  text-align: center;
  color: var(--r-ink);
}
.receipt-body .trip-chat-md-hr {
  border: none;
  border-top: 1.5px dashed color-mix(in srgb, var(--r-ink) 55%, transparent);
  margin: 0.5rem 0 0.45rem;
}
/* Section headers flanked by solid diamonds — a pure-black print ornament. */
.receipt-body .trip-chat-md-h1::before,
.receipt-body .trip-chat-md-h2::before,
.receipt-body .trip-chat-md-h3::before,
.receipt-body .trip-chat-md-h4::before { content: "◆ "; letter-spacing: 0; }
.receipt-body .trip-chat-md-h1::after,
.receipt-body .trip-chat-md-h2::after,
.receipt-body .trip-chat-md-h3::after,
.receipt-body .trip-chat-md-h4::after { content: " ◆"; letter-spacing: 0; }
.receipt-body .trip-chat-md-ul { list-style: none; margin: 0.1rem 0; padding: 0; }
.receipt-body .trip-chat-md-ul > li { margin: 0.24rem 0; padding: 0; }
.receipt-body .trip-chat-md-p { margin: 0.15rem 0; text-align: center; }
/* Bold tokens (times, transit codes) print as inverted stamp badges — solid
   black chips with paper text, the classic transit-ticket look. */
.receipt-body strong {
  font-weight: 800;
  background: var(--r-ink);
  color: var(--paper);
  padding: 0 0.3em;
  border-radius: 1px;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.receipt-body .trip-chat-md-a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
/* Local phrases — a little language card on the brief (also printed to the
   Epson). Dictionary-style rows: local phrase · dotted leader · English gloss. */
.receipt-phrases {
  margin-top: 0.6rem;
  padding-top: 0.55rem;
  border-top: 1.5px dashed color-mix(in srgb, var(--r-ink) 55%, transparent);
}
.receipt-phrases-head {
  text-align: center;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  margin-bottom: 0.4rem;
}
.receipt-phrase {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  margin: 0.16rem 0;
  font-size: 11px;
}
.receipt-phrase-loc { font-weight: 700; white-space: nowrap; }
.receipt-phrase-dot {
  flex: 1 1 auto;
  align-self: stretch;
  border-bottom: 1px dotted color-mix(in srgb, var(--r-ink) 45%, transparent);
  transform: translateY(-3px);
}
.receipt-phrase-en {
  white-space: nowrap;
  font-size: 10px;
  color: color-mix(in srgb, var(--r-ink) 62%, transparent);
}
/* Static day-map snapshot inside the receipt (screen only — thermal can't
   reproduce it; the route strip is the print stand-in). */
.receipt-map {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1.5px dashed color-mix(in srgb, var(--r-ink) 55%, transparent);
}
.receipt-map-head {
  text-align: center;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  margin-bottom: 0.4rem;
}
.receipt-map-canvas {
  width: 100%;
  height: 172px;
  border: 1.5px solid var(--r-ink);
  border-radius: 2px;
  overflow: hidden;
  background: #efece6;
  filter: grayscale(1) contrast(1.05);
  transition: opacity 0.3s ease;
  cursor: default;
}
.receipt-map-canvas .leaflet-container { background: #efece6; cursor: default; }
.receipt-map-canvas .leaflet-interactive,
.receipt-map-canvas .leaflet-marker-icon { cursor: default; }
/* Numbered key under the map: each row ties a pin to its name, in pin order.
   The little badges echo the map pins — filled ink circle + number, a hollow
   dashed circle for best-guess (≈) spots, and the bed glyph for the hotel. */
.receipt-map-key {
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.22rem;
}
.receipt-map-key-item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  font-size: 10px;
  line-height: 1.3;
}
.receipt-map-key-n {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  border-radius: 9999px;
  background: var(--r-ink);
  color: var(--paper);
  font-weight: 700;
  font-size: 9px;
  display: grid;
  place-items: center;
}
.receipt-map-key-n.is-approx {
  background: transparent;
  border: 1.4px dashed color-mix(in srgb, var(--r-ink) 60%, transparent);
}
.receipt-map-key-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.receipt-map-key-approx .receipt-map-key-name {
  font-style: italic;
  color: color-mix(in srgb, var(--r-ink) 62%, transparent);
}
/* Controls live OUTSIDE the paper (in the chat, not the receipt) so they're
   never cloned into the print — Run again / Print, and the live re-run steps. */
.receipt-controls { width: min(100%, 302px); margin: 0.55rem auto 0; }
.receipt-controls .tc-activity { padding-right: 0; max-width: none; }
.receipt-buttons { display: flex; gap: 0.5rem; justify-content: center; }
/* Buttons: mono, stamped-outline; invert on hover like a rubber stamp. */
.trip-chat-rerun {
  display: inline-flex;
  align-items: center;
  gap: 0.38rem;
  font-family: inherit;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--r-ink, var(--ink));
  background: transparent;
  border: 1.5px solid var(--r-ink, var(--line));
  border-radius: 2px;
  padding: 0.32rem 0.6rem;
  cursor: pointer;
  transition: background 0.13s ease, color 0.13s ease, transform 0.12s ease;
}
.trip-chat-rerun:hover:not(:disabled) { background: var(--r-ink, var(--ink)); color: var(--paper, #fff); }
.trip-chat-rerun:active:not(:disabled) { transform: translateY(1px); }
.trip-chat-rerun:disabled { opacity: 0.6; cursor: default; }
.trip-chat-rerun-ico { display: inline-flex; align-items: center; justify-content: center; }
.trip-chat-rerun-ico .block-spinner { --block-spinner-size: 0.72rem; }
.trip-chat-run-error { margin: 0.45rem 0 0; font-size: 10.5px; color: #b45309; }
/* When the Epson bridge is live, the print button gets a solid "ready" fill so
   it reads as the primary action. */
.trip-chat-rerun--epson { background: var(--r-ink, var(--ink)); color: var(--paper, #fff); }
.trip-chat-rerun--epson:hover:not(:disabled) { filter: contrast(1.15); }
/* Little status line under the controls: a pulsing dot + "auto-prints" note. */
.receipt-print-hint {
  width: min(100%, 302px);
  margin: 0.4rem auto 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--r-ink, var(--ink)) 62%, transparent);
}
.receipt-print-hint.is-warn { color: #b45309; }
.receipt-print-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #2f7d4f;
  box-shadow: 0 0 0 0 rgba(47, 125, 79, 0.5);
  animation: receipt-print-pulse 2s ease-out infinite;
}
.receipt-print-hint.is-warn .receipt-print-dot { background: #b45309; animation: none; }
@keyframes receipt-print-pulse {
  0% { box-shadow: 0 0 0 0 rgba(47, 125, 79, 0.5); }
  70% { box-shadow: 0 0 0 5px rgba(47, 125, 79, 0); }
  100% { box-shadow: 0 0 0 0 rgba(47, 125, 79, 0); }
}
@media (prefers-reduced-motion: reduce) { .receipt-print-dot { animation: none; } }
/* Footer: a Danish thank-you, receipt-style. */
.receipt-foot { margin-top: 0.7rem; }
.receipt-tak {
  margin-top: 0.4rem;
  text-align: center;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.4em;
  text-indent: 0.4em;
}

/* ── Print: isolate the cloned receipt at the TM-m30II 72mm print width ──── */
#receipt-print-root { display: none; }
@media print {
  html.receipt-printing body > :not(#receipt-print-root) { display: none !important; }
  html.receipt-printing #receipt-print-root { display: block !important; }
  html.receipt-printing #receipt-print-root .trip-chat-receipt {
    /* TM-m30II: 80mm roll, 72mm printable (576 dots @ 203dpi). Pure black on
       white, no rounded corners/shadow — everything a 1-bit head can render. */
    width: 72mm;
    margin: 0;
    padding: 0 1mm;
    border: none;
    border-radius: 0;
    box-shadow: none;
    color: #000;
    background: #fff;
    --r-ink: #000;
    --r-spot: #000; /* thermal is one ink: spot color prints black */
    --paper: #fff;
    font-size: 12px;
  }
  /* The day map DOES print here — a PDF/real printer renders it fine (only the
     raw ESC/POS thermal path treats it as mush). Just the paper-tear edge, a
     screen-only decorative flourish, is hidden. */
  html.receipt-printing #receipt-print-root .receipt-tear { display: none !important; }
}
/* NOTE: the 80mm @page rule (sized to the measured content) is injected by
   printBriefing() only for the receipt print, so normal browser printing of
   the app is untouched. */
/* "New chat" pill — a quiet accent chip so it reads as a button, not floating text. */
.trip-chat-new {
  color: var(--accent-deep, #4a5f42);
  border-color: color-mix(in srgb, var(--accent) 32%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.trip-chat-new:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
}
html.dark .trip-chat-new {
  color: #bcd6ac;
  border-color: rgba(156, 184, 141, 0.32);
  background: rgba(156, 184, 141, 0.13);
}
html.dark .trip-chat-new:hover:not(:disabled) {
  background: rgba(156, 184, 141, 0.22);
}
.trip-chat-bubble-user {
  background: linear-gradient(150deg, #76946a 0%, #4f6c40 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 4px 14px -2px rgba(77, 102, 68, 0.34);
}
.trip-chat-upload-msg {
  display: grid;
  gap: 0.42rem;
  justify-items: end;
}
.trip-chat-upload-thumbs {
  display: inline-flex;
  align-items: center;
  gap: 0.38rem;
  padding: 0.25rem;
  border-radius: 0.72rem;
  background: rgba(255, 255, 255, 0.16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.trip-chat-upload-thumb {
  width: 4.35rem;
  height: 4.35rem;
  border-radius: 0.58rem;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.22);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.36), 0 8px 18px -10px rgba(0, 0, 0, 0.42);
  transform: rotate(-1.5deg);
}
.trip-chat-upload-thumb:nth-child(even) { transform: rotate(1.3deg); }
.trip-chat-upload-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.trip-chat-upload-more {
  display: inline-grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
}
.trip-chat-upload-caption {
  max-width: 11rem;
  color: rgba(255, 255, 255, 0.9);
  font-size: 0.74rem;
  line-height: 1.15;
  font-weight: 650;
  text-align: right;
}
.trip-chat-bubble-assistant {
  background: rgba(255, 255, 255, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.78);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85), 0 6px 20px -8px rgba(54, 49, 42, 0.12);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  /* iOS Safari: backdrop-filter on ancestors can swallow taps on nested links */
  transform: translateZ(0);
}
.trip-chat-bubble-assistant .trip-chat-md {
  position: relative;
  z-index: 1;
}
.trip-chat-applied-check {
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 9999px;
  background: #dcefe3;
  color: #2f7d55;
}
html.dark .trip-chat-applied-check {
  background: rgba(74, 161, 113, 0.22);
  color: #7fd0a3;
}
.trip-chat-activity {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #4d6b5c;
  font-weight: 500;
  width: auto;
  max-width: min(100%, 22rem);
  padding: 0.6rem 0.8rem !important;
  font-size: 15px;
  line-height: 1.35;
}
.trip-chat-activity .block-spinner {
  --block-spinner-size: 1.05rem;
}
/* The label's default 1.35 line-height leaves extra descender space under the
   glyphs that the solid spinner icon doesn't have, so text reads as sitting
   low next to it even though both boxes are geometrically centered. Tighten
   just the label's line-height to pull the visible ink back to icon-center. */
.trip-chat-activity-label {
  line-height: 1;
}

/* ── Claude.ai-style "activity" section (thinking + web searches) ─────────── */
.tc-activity { max-width: 100%; }
.tc-activity-card {
  width: auto;
  max-width: min(100%, 30rem);
  border: 1.5px solid color-mix(in srgb, var(--line, #1a1714) 42%, transparent);
  border-radius: 4px;
  background: color-mix(in srgb, var(--accent-deep, #4a5f42) 5%, var(--surface, #fff));
  overflow: hidden;
}
/* Clickable header rows (summary bar + each block header). */
.tc-act-summary,
.tc-act-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.7rem;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: color-mix(in srgb, var(--accent-deep, #4a5f42) 62%, #475569);
  font-size: 12.5px;
  line-height: 1.3;
  transition: background-color 0.15s ease;
}
.tc-act-summary:hover,
.tc-act-row:hover { background: color-mix(in srgb, var(--accent-deep, #4a5f42) 8%, transparent); }
.tc-act-summary { font-weight: 600; }
/* Each block sits under the summary bar, divided by a hairline. */
.tc-act-item + .tc-act-item,
.tc-act-body .tc-act-item:first-child .tc-act-row {
  border-top: 1px solid color-mix(in srgb, var(--line, #1a1714) 18%, transparent);
}
.tc-act-ico,
.tc-act-state {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1rem;
  color: color-mix(in srgb, var(--accent-deep, #4a5f42) 70%, #64748b);
}
.tc-act-ico .block-spinner,
.tc-act-state .block-spinner { --block-spinner-size: 0.85rem; }
.tc-act-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  /* The header IS the model's thinking text — keep it regular weight, never
     bold, so it reads as a thought rather than a UI heading. */
  font-weight: 400;
}
/* Typewriter caret trailing the thinking header while it types. */
.tc-tw-caret {
  display: inline-block;
  width: 0.5em;
  height: 1em;
  margin-left: 1px;
  vertical-align: -0.12em;
  background: currentColor;
  opacity: 0.55;
  border-radius: 0.5px;
  animation: tc-tw-blink 0.9s steps(1, end) infinite;
}
@keyframes tc-tw-blink { 0%, 55% { opacity: 0.55; } 56%, 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .tc-tw-caret { animation: none; } }
.tc-act-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.95rem;
  height: 0.95rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-deep, #4a5f42) 20%, transparent);
  color: color-mix(in srgb, var(--accent-deep, #4a5f42) 92%, #334155);
}
.tc-act-warn { color: #b45309; }
.tc-act-chev {
  flex: 0 0 auto;
  opacity: 0.5;
  transition: transform 0.2s ease;
}
.tc-act-chev.is-open { transform: rotate(180deg); }
/* Query + result count under a search header. */
.tc-act-sub {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0 0.7rem 0.5rem 2.2rem;
  margin-top: -0.15rem;
  font-size: 11.5px;
  color: color-mix(in srgb, var(--muted, #64748b) 88%, transparent);
}
.tc-act-query {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-style: italic;
}
.tc-act-count { flex: 0 0 auto; white-space: nowrap; opacity: 0.8; }
/* Smooth collapse: grid-rows 0fr→1fr needs no measured height. */
.tc-act-panel,
.tc-act-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}
.tc-act-panel.is-open,
.tc-act-body.is-open { grid-template-rows: 1fr; }
.tc-act-panel-clip,
.tc-act-body-clip { overflow: hidden; min-height: 0; }
/* Reasoning steps — every distinct thought, each a quiet dot-marked line so the
   whole chain reads as discrete steps. Regular weight (never bold). */
.tc-think-steps {
  padding: 0.15rem 0.85rem 0.6rem 2.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.tc-think-step {
  position: relative;
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  font-weight: 400;
  color: color-mix(in srgb, var(--ink, #1a1714) 62%, #64748b);
}
.tc-think-step::before {
  content: "";
  position: absolute;
  left: -0.9rem;
  top: 0.62em;
  width: 4px;
  height: 4px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-deep, #4a5f42) 42%, transparent);
}
/* Source list. */
.tc-src-list { padding: 0.1rem 0.55rem 0.55rem; }
.tc-src {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.35rem;
  border-radius: 3px;
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.tc-src:hover { background: color-mix(in srgb, var(--accent-deep, #4a5f42) 9%, transparent); }
.tc-src-ico {
  width: 16px;
  height: 16px;
  border-radius: 3px;
  flex: 0 0 auto;
  object-fit: cover;
  background: color-mix(in srgb, var(--line, #1a1714) 8%, transparent);
}
.tc-src-ico--fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted, #64748b);
}
.tc-src-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 12.5px;
  color: color-mix(in srgb, var(--ink, #1a1714) 80%, transparent);
}
.tc-src-domain {
  flex: 0 0 auto;
  max-width: 8rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 11px;
  color: color-mix(in srgb, var(--muted, #64748b) 85%, transparent);
}
/* Dark mode. */
html.dark .tc-activity-card {
  border-color: rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.03);
}
html.dark .tc-act-summary,
html.dark .tc-act-row { color: rgba(168, 198, 176, 0.92); }
html.dark .tc-act-summary:hover,
html.dark .tc-act-row:hover { background: rgba(255, 255, 255, 0.05); }
html.dark .tc-act-item + .tc-act-item,
html.dark .tc-act-body .tc-act-item:first-child .tc-act-row { border-top-color: rgba(255, 255, 255, 0.08); }
html.dark .tc-act-ico,
html.dark .tc-act-state { color: rgba(168, 198, 176, 0.85); }
html.dark .tc-act-sub { color: rgba(148, 163, 184, 0.85); }
html.dark .tc-think-step { color: rgba(203, 213, 225, 0.82); }
html.dark .tc-src:hover { background: rgba(255, 255, 255, 0.05); }
html.dark .tc-src-title { color: rgba(226, 232, 240, 0.9); }
html.dark .tc-src-domain { color: rgba(148, 163, 184, 0.8); }
html.dark .tc-act-check { background: rgba(168, 198, 176, 0.22); color: rgba(190, 225, 200, 0.95); }
.trip-chat-reasoning {
  max-width: min(100%, 34rem);
  padding: 0.15rem 0.125rem;
  color: rgba(71, 85, 105, 0.92);
  font-size: 0.87rem;
  line-height: 1.42;
}
.trip-chat-reasoning-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 1.35rem;
  font-weight: 400;
  color: color-mix(in srgb, var(--accent-deep, #4a5f42) 72%, #334155);
}
/* Spinner sits inline with the reasoning text — size it to the text height
   (em-relative) so it never towers over the line it's labelling. */
.trip-chat-reasoning-head .block-spinner {
  --block-spinner-size: 1.15em;
}
.block-spinner {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: calc(var(--block-spinner-size) * 0.11);
  flex: 0 0 auto;
  --block-spinner-size: 1.05rem;
  /* Faint resting tone for the unlit ring cells, so the full 3×3 square always
     reads even when the lit comet is a straight 1×3 run along one edge. */
  --block-spinner-ghost: rgba(20, 18, 15, 0.1);
  width: var(--block-spinner-size);
  height: var(--block-spinner-size);
  aspect-ratio: 1 / 1; /* always a perfect square */
  image-rendering: pixelated;
}
html.dark .block-spinner { --block-spinner-ghost: rgba(186, 201, 235, 0.14); }
.block-spinner--md {
  --block-spinner-size: 1.5rem;
}
.block-spinner--lg {
  --block-spinner-size: 2.75rem;
}
/* 3×3 ring of 9 cells (child 5 is the empty centre). A 3-block comet travels
   CLOCKWISE around the 8 perimeter cells: each cell holds a fixed Claus colour
   and lights only for the 3 steps the comet covers it — so each step exactly one
   NEW colour leads in while the trailing two keep the colour they were just lit
   with. Off cells are transparent (the comet, not a full grid). The grid fills
   row-major, so children map to ring positions out of order (see the mapping). */
.block-spinner-pixel {
  border-radius: 1px;
  background-color: transparent;
  will-change: background-color;
}
.block-spinner-pixel:nth-child(1) { animation: block-comet-0 1.6s steps(1, end) infinite; } /* top-left      */
.block-spinner-pixel:nth-child(2) { animation: block-comet-1 1.6s steps(1, end) infinite; } /* top-mid       */
.block-spinner-pixel:nth-child(3) { animation: block-comet-2 1.6s steps(1, end) infinite; } /* top-right     */
.block-spinner-pixel:nth-child(4) { animation: block-comet-7 1.6s steps(1, end) infinite; } /* mid-left      */
.block-spinner-pixel:nth-child(5) { background-color: transparent; }                         /* centre: empty */
.block-spinner-pixel:nth-child(6) { animation: block-comet-3 1.6s steps(1, end) infinite; } /* mid-right     */
.block-spinner-pixel:nth-child(7) { animation: block-comet-6 1.6s steps(1, end) infinite; } /* bottom-left   */
.block-spinner-pixel:nth-child(8) { animation: block-comet-5 1.6s steps(1, end) infinite; } /* bottom-mid    */
.block-spinner-pixel:nth-child(9) { animation: block-comet-4 1.6s steps(1, end) infinite; } /* bottom-right  */
/* Legacy seam overlay — no longer emitted; kept as a guard for any briefly
   browser-cached boot markup. The grid `gap` draws the separation now, so
   neutralise it (display:none also drops it from the grid). */
.block-spinner-seams { display: none; }
.trip-chat-reasoning-label {
  min-width: 0;
  color: inherit;
  /* A short two-line window pinned to its bottom (see ReasoningSummary). The
     smooth scroll-behavior eases each line advance so the reasoning reads as one
     continuous typewriter flowing across lines rather than a jumpy scroll. */
  display: block;
  max-height: 2.9em;
  overflow: hidden;
  scroll-behavior: smooth;
}
.trip-chat-composer {
  position: relative;
  z-index: 2;
  border-top: 1px solid rgba(214, 220, 230, 0.28);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(244, 249, 252, 0.14));
  -webkit-backdrop-filter: blur(12px) saturate(1.2);
  backdrop-filter: blur(12px) saturate(1.2);
}
.trip-chat-composer-inner {
  --trip-chat-pill-h: 2.75rem;
  align-items: center;
  gap: 0.5rem;
}
.trip-chat-composer-inner:has(.trip-chat-input-shell--multiline) {
  align-items: flex-end;
}
.trip-chat-input-shell {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  min-height: var(--trip-chat-pill-h);
  border-radius: 9999px;
  border: 1px solid rgba(214, 220, 230, 0.45);
  background: rgba(255, 255, 255, 0.58);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8), 0 4px 18px rgba(54, 49, 42, 0.04);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  -webkit-backdrop-filter: blur(8px) saturate(1.1);
  backdrop-filter: blur(8px) saturate(1.1);
  overflow: hidden;
}
.trip-chat-input-shell--multiline {
  align-items: flex-end;
  border-radius: 1.35rem;
}
.trip-chat-inline-tools {
  align-self: center;
  padding-right: 0.375rem;
}
.trip-chat-input-shell--multiline .trip-chat-inline-tools {
  align-self: flex-end;
  padding-bottom: 0.375rem;
}
.trip-chat-input-shell:focus-within {
  border-color: color-mix(in srgb, var(--accent) 38%, #c8d2dc);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 10%, transparent), 0 1px 0 rgba(255, 255, 255, 0.8) inset;
}
.trip-chat-input-shell:not(.trip-chat-input-shell--multiline) {
  height: var(--trip-chat-pill-h);
}
.trip-chat-input-shell:not(.trip-chat-input-shell--multiline) .trip-chat-input {
  align-self: stretch;
  height: 100%;
  padding: 0 0.375rem 0 0.875rem;
  line-height: calc(var(--trip-chat-pill-h) - 2px);
}
.trip-chat-input {
  display: block;
  box-sizing: border-box;
  min-height: 0;
  padding: 0.5rem 0.375rem 0.5rem 0.875rem;
  line-height: 1.35;
  max-height: 7rem;
  font-size: 16px; /* iOS Safari won't zoom on focus at 16px+ */
  scrollbar-width: none;
  -ms-overflow-style: none;
  overflow-x: hidden;
  overflow-y: auto;
  word-break: break-word;
  -webkit-appearance: none;
  border-radius: 0;
}
@media (min-width: 640px) {
  .trip-chat-input { font-size: var(--trip-chat-text); }
}
.trip-chat-input::-webkit-scrollbar { display: none; width: 0; height: 0; }

.trip-chat-send {
  width: var(--trip-chat-pill-h);
  height: var(--trip-chat-pill-h);
  padding: 0;
  border: none;
  background: linear-gradient(145deg, #5f7a54, #4d6644);
  color: #fff;
  box-shadow: 0 2px 8px rgba(77, 102, 68, 0.28);
}
.trip-chat-send-icon {
  display: block;
  width: 0.9375rem;
  height: 0.9375rem;
}
.trip-chat-attach {
  display: grid;
  place-items: center;
  width: 1.95rem;
  height: 1.95rem;
  padding: 0;
  border-radius: 9999px;
  border: 1px solid transparent;
  background: transparent;
  color: rgba(100, 116, 139, 0.72);
  box-shadow: none;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
}
.trip-chat-attach:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.62);
  border-color: rgba(148, 163, 184, 0.22);
  color: var(--accent-deep, #4a5f42);
}
.trip-chat-attach:active:not(:disabled) { transform: scale(0.97); }
.trip-chat-attach:disabled { opacity: 0.45; }
.trip-chat-attach-icon {
  display: block;
  width: 0.95rem;
  height: 0.95rem;
}
.trip-chat-web-toggle {
  display: grid;
  place-items: center;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  margin: 1px;
  border-radius: 9999px;
  border: 1px solid transparent;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
.trip-chat-web-toggle:active:not(:disabled) { transform: scale(0.97); }
.trip-chat-web-toggle--on:active:not(:disabled) { transform: translateY(1px) scale(0.97); }
.trip-chat-web-toggle:disabled { opacity: 0.45; }
.trip-chat-web-toggle-icon {
  display: block;
  width: 0.93rem;
  height: 0.93rem;
}
.trip-chat-web-toggle--off {
  border-color: transparent;
  background: transparent;
  color: rgba(100, 116, 139, 0.72);
  box-shadow: none;
}
.trip-chat-web-toggle--off:hover:not(:disabled) {
  color: var(--accent-deep, #4a5f42);
  border-color: rgba(148, 163, 184, 0.22);
  background: rgba(255, 255, 255, 0.62);
}
.trip-chat-web-toggle--on {
  border-color: color-mix(in srgb, var(--accent, #5f7a54) 34%, rgba(148, 163, 184, 0.22));
  background: color-mix(in srgb, var(--accent, #5f7a54) 14%, rgba(255, 255, 255, 0.72));
  color: var(--accent-deep, #4a5f42);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
  transform: none;
}
.trip-chat-web-toggle--on:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent, #5f7a54) 18%, rgba(255, 255, 255, 0.78));
}
.trip-chat-send:not(:disabled):hover { filter: brightness(1.05); }
.trip-chat-drop-overlay {
  background: color-mix(in srgb, var(--accent, #5f7a54) 16%, rgba(250, 248, 244, 0.72));
  -webkit-backdrop-filter: blur(10px) saturate(1.12);
  backdrop-filter: blur(10px) saturate(1.12);
}
.trip-chat-drop-target {
  border-radius: 1.15rem;
  background: rgba(255, 255, 255, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.72);
  box-shadow: 0 12px 34px rgba(54, 49, 42, 0.14);
  animation: trip-chat-drop-pulse 1.4s ease-in-out infinite;
}
@keyframes trip-chat-drop-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 12px 34px rgba(54, 49, 42, 0.14); }
  50% { transform: scale(1.035); box-shadow: 0 18px 42px rgba(54, 49, 42, 0.2); }
}
/* Bouncing down-arrow — "drop it here". */
.trip-chat-drop-arrow {
  display: inline-flex;
  color: var(--accent-deep, #4a5f42);
  animation: trip-chat-drop-bounce 1s ease-in-out infinite;
}
@keyframes trip-chat-drop-bounce {
  0%, 100% { transform: translateY(-2px); }
  50% { transform: translateY(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .trip-chat-drop-target, .trip-chat-drop-arrow { animation: none; }
}
/* 3-block comet around the 8-cell ring (8 discrete steps, steps(1,end)). Ring
   index i lights its fixed colour (palette[i mod 4]: blue/red/yellow/green) for
   the 3 steps the comet covers it — window [i, i+2] — then goes transparent. So
   every step exactly one new colour leads in and the trailing two persist. i=6,7
   wrap across the 0% boundary. */
@keyframes block-comet-0 { 0% { background-color: #5b91cf; } 37.5% { background-color: var(--block-spinner-ghost); } }      /* blue   [0–2] */
@keyframes block-comet-1 { 0% { background-color: var(--block-spinner-ghost); } 12.5% { background-color: #d6463a; } 50% { background-color: var(--block-spinner-ghost); } }   /* red    [1–3] */
@keyframes block-comet-2 { 0% { background-color: var(--block-spinner-ghost); } 25% { background-color: #f2c23a; } 62.5% { background-color: var(--block-spinner-ghost); } }   /* yellow [2–4] */
@keyframes block-comet-3 { 0% { background-color: var(--block-spinner-ghost); } 37.5% { background-color: #5fa86a; } 75% { background-color: var(--block-spinner-ghost); } }   /* green  [3–5] */
@keyframes block-comet-4 { 0% { background-color: var(--block-spinner-ghost); } 50% { background-color: #5b91cf; } 87.5% { background-color: var(--block-spinner-ghost); } }   /* blue   [4–6] */
@keyframes block-comet-5 { 0% { background-color: var(--block-spinner-ghost); } 62.5% { background-color: #d6463a; } }       /* red    [5–7] */
@keyframes block-comet-6 { 0% { background-color: #f2c23a; } 12.5% { background-color: var(--block-spinner-ghost); } 75% { background-color: #f2c23a; } }      /* yellow [6,7,0] */
@keyframes block-comet-7 { 0% { background-color: #5fa86a; } 25% { background-color: var(--block-spinner-ghost); } 87.5% { background-color: #5fa86a; } }      /* green  [7,0,1] */
html.dark .trip-chat-suggestion {
  background: rgba(42, 48, 62, 0.6);
  border-color: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.82);
}
html.dark .trip-chat-suggestion:hover {
  background: rgba(52, 59, 76, 0.78);
  border-color: rgba(156, 184, 141, 0.32);
}
html.dark .trip-chat-bubble-assistant {
  background: rgba(44, 50, 66, 0.85);
  border-color: rgba(255, 255, 255, 0.13);
  color: rgba(255, 255, 255, 0.92);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 8px 22px -10px rgba(0, 0, 0, 0.6);
}
html.dark .trip-chat-activity { color: rgba(168, 198, 176, 0.95); }
html.dark .trip-chat-reasoning {
  color: rgba(226, 232, 240, 0.82);
}
html.dark .trip-chat-reasoning-head {
  color: rgba(202, 224, 190, 0.95);
}
html.dark .trip-chat-reasoning-label {
  color: rgba(202, 224, 190, 0.95);
}
/* Dark mode: the grab-bar header and the composer footer take the app's dark
   slate-blue (not the black conversation area) so the chrome reads as its own
   surface. */
html.dark .trip-chat-header,
html.dark .trip-chat-composer {
  /* !important + the higher-specificity html.dark selector to beat the flat
     `.trip-chat-composer { background: var(--canvas) !important }` further down. */
  background: #2a3344 !important;
  border-top-color: rgba(255, 255, 255, 0.08) !important;
}
@supports (-webkit-touch-callout: none) {
  html.dark .trip-chat-header { background-color: #2a3344 !important; }
}
html.dark .trip-chat-input-shell {
  background: rgba(40, 46, 60, 0.62);
  border-color: rgba(255, 255, 255, 0.12);
}
html.dark .trip-chat-input { color: rgba(255, 255, 255, 0.92); }
html.dark .trip-chat-input::placeholder { color: rgba(255, 255, 255, 0.38); }
html.dark .trip-chat-attach {
  background: transparent;
  border-color: transparent;
  color: rgba(255, 255, 255, 0.56);
  box-shadow: none;
}
html.dark .trip-chat-attach:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.82);
}
html.dark .trip-chat-web-toggle--off {
  border-color: transparent;
  background: transparent;
  color: rgba(255, 255, 255, 0.56);
  box-shadow: none;
}
html.dark .trip-chat-web-toggle--off:hover:not(:disabled) {
  color: rgba(255, 255, 255, 0.82);
  border-color: rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.08);
}
html.dark .trip-chat-web-toggle--on {
  border-color: rgba(156, 184, 141, 0.28);
  background: rgba(156, 184, 141, 0.14);
  color: rgba(218, 235, 205, 0.96);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  transform: none;
}
html.dark .trip-chat-web-toggle--on:hover:not(:disabled) {
  background: rgba(156, 184, 141, 0.18);
}
html.dark .trip-chat-drop-overlay {
  background: rgba(0, 0, 0, 0.7);
}
html.dark .trip-chat-drop-target {
  background: rgba(44, 50, 66, 0.95);
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.34);
}
html.dark .trip-chat-drop-target .text-slate-800 { color: rgba(255, 255, 255, 0.9); }
html.dark .trip-chat-drop-target .text-slate-500 { color: rgba(255, 255, 255, 0.58); }

.trip-chat-md {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  font-size: var(--trip-chat-text, 15px);
  line-height: var(--trip-chat-leading, 1.55);
}
.trip-chat-md--streaming > *:last-child {
  min-height: 1.1em;
}
.trip-chat-md > *:first-child { margin-top: 0; }
.trip-chat-md > *:last-child { margin-bottom: 0; }
.trip-chat-md-p {
  margin: 0;
  white-space: normal;
}
.trip-chat-md-h1 {
  margin: 0;
  font-size: 1.05em;
  font-weight: 650;
  line-height: 1.35;
  letter-spacing: -0.01em;
}
.trip-chat-md-h2 {
  margin: 0;
  font-size: 1em;
  font-weight: 650;
  line-height: 1.35;
}
.trip-chat-md-h3,
.trip-chat-md-h4 {
  margin: 0;
  font-size: 0.95em;
  font-weight: 650;
  line-height: 1.35;
  color: color-mix(in srgb, var(--ink, #334155) 88%, var(--accent, #5f7a54));
}
.trip-chat-md-h4 + .trip-chat-md-ul,
.trip-chat-md-h4 + .trip-chat-md-ol,
.trip-chat-md-h3 + .trip-chat-md-ul,
.trip-chat-md-h3 + .trip-chat-md-ol {
  margin-top: -0.2rem;
}
.trip-chat-md-h5,
.trip-chat-md-h6 {
  margin: 0;
  font-size: 0.8em;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink, #64748b) 75%, var(--accent, #5f7a54));
}
.trip-chat-md-ul,
.trip-chat-md-ol {
  margin: 0;
  padding-left: 1.15rem;
}
.trip-chat-md-ul { list-style: disc; }
.trip-chat-md-ol { list-style: decimal; }
.trip-chat-md-ul li,
.trip-chat-md-ol li {
  margin: 0.3rem 0;
  padding-left: 0.15rem;
}
.trip-chat-md-ul li::marker,
.trip-chat-md-ol li::marker {
  color: color-mix(in srgb, var(--accent, #5f7a54) 70%, #94a3b8);
}
.trip-chat-md-hr {
  margin: 0.55rem 0;
  border: 0;
  border-top: 1px solid rgba(214, 207, 190, 0.55);
}
.trip-chat-md-a {
  position: relative;
  z-index: 2;
  color: var(--accent-deep, #4a5f42);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: color-mix(in srgb, var(--accent, #5f7a54) 28%, transparent);
}
.trip-chat-md-a:hover {
  color: var(--accent, #5f7a54);
}
.trip-chat-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.82em;
  padding: 0.08rem 0.35rem;
  border-radius: 0.35rem;
  background: rgba(214, 207, 190, 0.22);
  border: 1px solid rgba(214, 207, 190, 0.35);
}
html.dark .trip-chat-md-h3,
html.dark .trip-chat-md-h4,
html.dark .trip-chat-md-h5,
html.dark .trip-chat-md-h6 {
  color: rgba(255, 255, 255, 0.82);
}
html.dark .trip-chat-md-hr { border-top-color: rgba(255, 255, 255, 0.12); }
html.dark .trip-chat-md-a {
  color: rgba(190, 215, 178, 0.96);
}
html.dark .trip-chat-md-a:hover {
  color: rgba(218, 235, 205, 1);
}
/* The receipt is a printed/paper artifact — these dark-mode overrides above
   have an extra `html` element in their selector, so they'd otherwise beat
   .receipt-body's own heading/divider/link colors on specificity alone and
   wash the receipt's headings out to near-white against the paper. Reset
   them back to ink so the receipt never changes with device theme. */
html.dark .receipt-body .trip-chat-md-h1,
html.dark .receipt-body .trip-chat-md-h2,
html.dark .receipt-body .trip-chat-md-h3,
html.dark .receipt-body .trip-chat-md-h4 {
  color: var(--r-ink);
}
html.dark .receipt-body .trip-chat-md-hr {
  border-top-color: color-mix(in srgb, var(--r-ink) 55%, transparent);
}
html.dark .receipt-body .trip-chat-md-a,
html.dark .receipt-body .trip-chat-md-a:hover {
  color: inherit;
}
html.dark .trip-chat-code {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.12);
}

/* ---- Drag + drop affordances ------------------------------------------ */
.dragging { opacity: 0.45; }
/* Drop target: a gentle glass glow — tinted fill + soft accent ring — rather
   than a dashed web outline. */
.drop-active {
  outline: 1.5px solid color-mix(in srgb, var(--accent) 55%, transparent);
  outline-offset: -1.5px;
  background: color-mix(in srgb, var(--accent) 9%, rgba(255, 255, 255, 0.6));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent);
}

/* The original element while its copy is being dragged: settles back into a
   faded "hole" so it reads as lifted out of the page. !important beats the
   element's own :hover transform. */
/* ---- iOS-style inset-grouped day-plan slots --------------------------- */
/* Each slot's items live in one rounded "card" with hairline separators
   between flush rows, the way a native iOS grouped list reads — rather than a
   stack of separately-bordered web cards. */
.ios-group {
  background: #fff;
  border: 1px solid rgba(65, 55, 42, 0.08);
  border-radius: 14px;
  box-shadow: 0 1px 2px rgba(20, 30, 40, 0.05), 0 8px 20px -14px rgba(20, 30, 40, 0.18);
  /* overflow stays visible so an editing row's search dropdown isn't clipped;
     the first/last rows round their own corners to tuck into the card edge. */
}
.ios-group > :first-child { border-top-left-radius: 13px; border-top-right-radius: 13px; }
.ios-group > :last-child { border-bottom-left-radius: 13px; border-bottom-right-radius: 13px; }
html.dark .ios-group {
  background: var(--surface);
  border-color: rgba(255, 255, 255, 0.07);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.ios-row { position: relative; transition: background-color 0.16s ease; }
/* iOS cell press: the whole row warms briefly on tap. */
.ios-row:active { background-color: rgba(65, 55, 42, 0.05); }
html.dark .ios-row:active { background-color: rgba(255, 255, 255, 0.05); }
/* Inset hairline between consecutive rows (aligned under the label text, past
   the leading grip + glyph). `--full` spans edge to edge for "Add" rows. */
.ios-row + .ios-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: 3.5rem;
  right: 0;
  border-top: 1px solid rgba(65, 55, 42, 0.09);
}
.ios-row.ios-row--full::before { left: 0; }
html.dark .ios-row + .ios-row::before { border-top-color: rgba(255, 255, 255, 0.08); }

/* A single editable cell (the lodging field) that wears the same surface as the
   grouped cards, so it sits in the same visual family. */
.ios-field {
  border-color: rgba(65, 55, 42, 0.08);
  box-shadow: 0 1px 2px rgba(20, 30, 40, 0.05), 0 8px 20px -14px rgba(20, 30, 40, 0.18);
}
html.dark .ios-field { border-color: rgba(255, 255, 255, 0.07); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); }

.is-dragging {
  opacity: 0.4 !important;
  transform: scale(0.98) !important;
  box-shadow: none !important;
  filter: saturate(0.75);
  cursor: grabbing;
}

/* The floating drag image (a clone handed to setDragImage): lifted into a
   stronger glass layer — slight tilt + scale, deep shadow, and a bright rim
   so it reads as a pane raised above the app, not a playful sticker. */
.drag-ghost {
  transform: rotate(1.5deg) scale(1.02);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85),
    0 0 0 1px rgba(255, 255, 255, 0.5),
    0 26px 56px -12px rgba(54, 49, 42, 0.36),
    0 10px 20px -8px rgba(54, 49, 42, 0.24);
  opacity: 0.98;
  z-index: 9999;
  pointer-events: none;
}

/* ---- Touch drag (day-plan reordering) --------------------------------- */
/* The grip owns the touch gesture so a touch starts a drag, not a scroll/select;
   the rest of the card keeps native scrolling. */
.touch-grip {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  cursor: grab;
}
.touch-grip:active { cursor: grabbing; }
/* Insertion line drawn while a touch drag hovers a slot. */
.touch-drop-line {
  position: fixed;
  height: 2px;
  border-radius: 9999px;
  background: var(--accent);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.7);
  z-index: 10001;
  pointer-events: none;
}
/* Suppress text selection / callouts everywhere during a touch drag. */
body.touch-dragging {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* Smoothly animated, snappy expand/collapse via animatable grid rows.
   Staggered: on open — height expands first, then content fades in.
              on close — content fades first, then height collapses. */
.collapsible { display: grid; }
.collapsible > .collapsible-inner { overflow: hidden; min-height: 0; }
/* Once the open animation settles, stop clipping so descendants can use
   position: sticky (e.g. the day-plan column) and overflow the card freely. */
.collapsible > .collapsible-inner.is-done { overflow: visible; }
.collapsible-open {
  grid-template-rows: 1fr;
  opacity: 1;
  transition: grid-template-rows 0.16s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.13s ease 0.05s;
}
.collapsible-closed {
  grid-template-rows: 0fr;
  opacity: 0;
  transition: opacity 0.1s ease,
              grid-template-rows 0.14s cubic-bezier(0.22, 1, 0.36, 1) 0.04s;
}

/* Full-page view changes: the selected view pushes the old one sideways, iOS
   style. The stage clips only horizontally during the motion so the outgoing
   page can leave cleanly without creating a document-level scrollbar. */
.view-push-stage {
  position: relative;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  overflow-x: visible;
}
.view-push-stage.is-animating {
  min-height: max(var(--view-min-height, 0px), 16rem);
  overflow: hidden;
}
.view-pane {
  width: 100%;
}
.view-pane-content {
  width: 100%;
}
.view-pane-exit {
  position: absolute;
  inset: 0;
}
.view-pane-enter {
  position: relative;
}
.view-push-in-forward,
.view-push-out-forward,
.view-push-in-back,
.view-push-out-back {
  animation-duration: 0.46s;
  animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
  animation-fill-mode: both;
  will-change: transform;
}
/* A whisper of scale so the incoming view settles forward and the outgoing
   recedes — depth, without any cross-fade that would show through. */
@keyframes viewPushInForward { from { transform: translateX(100%) scale(0.985); } to { transform: translateX(0) scale(1); } }
@keyframes viewPushOutForward { from { transform: translateX(0) scale(1); } to { transform: translateX(-100%) scale(0.985); } }
@keyframes viewPushInBack { from { transform: translateX(-100%) scale(0.985); } to { transform: translateX(0) scale(1); } }
@keyframes viewPushOutBack { from { transform: translateX(0) scale(1); } to { transform: translateX(100%) scale(0.985); } }
.view-push-in-forward { animation-name: viewPushInForward; }
.view-push-out-forward { animation-name: viewPushOutForward; }
.view-push-in-back { animation-name: viewPushInBack; }
.view-push-out-back { animation-name: viewPushOutBack; }

/* Stop-card detail panel: animate a measured pixel `height` on this clip box
   instead of grid-template-rows. The panel inside is laid out ONCE at its
   natural height; per frame the browser only changes this box's height and
   clips — no subtree relayout — which is what keeps iOS Safari at 60fps.
   JS sets the inline height (0 → measured → auto); this rule owns the timing. */
.detail-clip {
  overflow: hidden;
  height: 0;
  will-change: height;
  transition: height 0.4s var(--ease-ios);
}
.detail-clip.is-open { overflow: visible; will-change: auto; }

/* City thumbnail image easing in over its color fill once the (async) wiki
   image resolves — a soft opacity+scale settle so it never hard-pops. */
@keyframes thumbImgIn { from { opacity: 0; transform: scale(1.04); } to { opacity: 1; transform: scale(1); } }
.thumb-img { animation: thumbImgIn 0.45s cubic-bezier(0.33, 1, 0.68, 1) both; }

/* Opacity-only settle for thumbnails that also carry a hover transform (rec
   cards), so the fade-in never fights the group-hover scale. */
@keyframes imgFadeIn { from { opacity: 0; } to { opacity: 1; } }
.img-fade-in { animation: imgFadeIn 0.4s ease-out both; }

/* Loading shimmer shown in a thumbnail box while its wiki image resolves. */
@keyframes imgShimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.img-skeleton {
  background-color: rgba(0, 0, 0, 0.04);
  background-image: linear-gradient(100deg, transparent 18%, rgba(0, 0, 0, 0.06) 40%, rgba(0, 0, 0, 0.1) 50%, rgba(0, 0, 0, 0.06) 60%, transparent 82%);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: imgShimmer 1.3s ease-in-out infinite;
}
html.dark .img-skeleton {
  background-color: rgba(255, 255, 255, 0.05);
  background-image: linear-gradient(100deg, transparent 18%, rgba(255, 255, 255, 0.08) 40%, rgba(255, 255, 255, 0.13) 50%, rgba(255, 255, 255, 0.08) 60%, transparent 82%);
}
@media (prefers-reduced-motion: reduce) {
  .img-fade-in { animation: none; }
  .img-skeleton { animation: none; }
}
.timeline-thumb-img {
  inset: 0;
  opacity: 1;
  transform: none;
}
.timeline-thumb > .absolute {
  inset: 0;
}
.timeline-thumb > .thumb-tap-overlay {
  inset: 0;
}
/* Stop index: compact badge tucked in the thumbnail's top-left corner. */
.thumb-index {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  width: 1.41rem;
  height: 1.41rem;
  border: none;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
  color: #fff;
  font-family: var(--font-display, inherit);
  font-size: 12.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}
@media (min-width: 640px) {
  .thumb-index {
    width: 1.56rem;
    height: 1.56rem;
    font-size: 14px;
  }
}
/* Collapsed stop cards: the card is overflow-hidden + rounded-[3px] with a 1.5px
   border, so its inner clip curve is 3px − 1.5px = 1.5px. Match the thumbnail's
   left corners to that exact inner radius so no white arc shows in the corner. */
.timeline-rail .stop-card-interactive.tl-content .timeline-thumb {
  border-top-left-radius: 1.5px;
  border-bottom-left-radius: 1.5px;
  overflow: hidden;
  isolation: isolate;
}

/* Floating rec detail card entrance/exit — a real bottom sheet on mobile, a
   softly scaled card on desktop. */
.rec-detail-sheet {
  display: flex;
  flex-direction: column;
  height: auto;
  /* A true bottom sheet: flush to the screen's bottom edge, rounded only at the
     top, taller, with the home-indicator inset paid inside the white. */
  max-height: min(88dvh, 46rem);
  margin-bottom: 0;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.rec-detail-scroll {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* Mobile: a true sheet — fully opaque, glides up on the iOS decelerate and
   drops away on an accelerate curve (108% clears the sheet's drop shadow).
   No opacity ramp: a translucent sheet mid-flight reads as flicker. */
@keyframes recCardIn { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes recCardOut { from { transform: translateY(0); } to { transform: translateY(108%); } }
.rec-card-enter { animation: recCardIn 0.42s cubic-bezier(0.32, 0.72, 0, 1) both; }
.rec-card-leave { animation: recCardOut 0.28s cubic-bezier(0.4, 0, 1, 1) both; }
@media (min-width: 640px) {
  /* Entrance is transform-only (like mobile): an opacity ramp on the glass
     card disables its backdrop-filter until it lands on exactly 1, flashing
     the card clear before it frosts. The exit may fade — frost loss while
     leaving is invisible. */
  @keyframes recCardInDesktop { from { transform: scale(0.95) translateY(10px); } to { transform: scale(1) translateY(0); } }
  @keyframes recCardOutDesktop { from { opacity: 1; transform: scale(1) translateY(0); } to { opacity: 0; transform: scale(0.97) translateY(8px); } }
  .rec-detail-sheet {
    height: auto;
    max-height: min(78vh, 38rem);
    margin-bottom: 0;
  }
  .rec-card-enter { animation: recCardInDesktop 0.3s cubic-bezier(0.22, 1, 0.36, 1) both; }
  .rec-card-leave { animation: recCardOutDesktop 0.22s cubic-bezier(0.4, 0, 1, 1) both; }
}

/* Modal / bottom-sheet entrance — the densest glass layer arriving from just
   below with a whisper of scale: heavier and more deliberate than a popover
   (≈300ms), settling on the iOS decelerate. One language for all dialogs.
   Transform-only (no opacity ramp, and no fade on the scrim around it): any
   opacity below 1 on the sheet or an ancestor disables the sheet's
   backdrop-filter until the fade completes, flashing it clear before frosting. */
@keyframes sheetIn { from { transform: translateY(18px) scale(0.99); } to { transform: translateY(0) scale(1); } }
.sheet-enter { animation: sheetIn 0.3s cubic-bezier(0.32, 0.72, 0, 1) both; }
@media (min-width: 640px) {
  @keyframes sheetInDesktop { from { transform: translateY(10px) scale(0.985); } to { transform: translateY(0) scale(1); } }
  .sheet-enter { animation: sheetInDesktop 0.28s cubic-bezier(0.32, 0.72, 0, 1) both; }
}

/* ---- Range slider ----------------------------------------------------- */
input[type="range"].fjord-range {
  -webkit-appearance: none; appearance: none;
  height: 4px; border-radius: 9999px; background: #e7e2d6; outline: none;
}
input[type="range"].fjord-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 16px; height: 16px; border-radius: 9999px;
  background: var(--accent); cursor: pointer; border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(20, 38, 52, 0.25);
}
input[type="range"].fjord-range::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 9999px;
  background: var(--accent); cursor: pointer; border: 2px solid #fff;
}

/* ---- Motion ----------------------------------------------------------- */
/* Quiet tactile press for any interactive element — a precise compress, not
   a playful shrink. */
.press { transition: transform 0.12s cubic-bezier(0.22, 1, 0.36, 1); }
.press:hover { transform: translateY(-1px); }
.press:active { transform: scale(0.97); }

/* Staggered entrance for the timeline. Each stop+leg lifts and settles with a
   touch of spring overshoot, cascading down the page so the trip "assembles"
   rather than just appearing. */
@keyframes timelineRise {
  0% { opacity: 0; transform: translateY(26px) scale(0.965); }
  55% { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
/* Gate the entrance on .is-sync-revealed (flips as the loading veil begins to
   lift), so the jump-to-hidden happens behind the still-opaque veil and the
   cascade plays through the fade rather than finishing behind it. */
@media (prefers-reduced-motion: no-preference) {
  .is-sync-revealed .stagger > * { animation: timelineRise 0.55s var(--ease-spring) both; }
  .is-sync-revealed .stagger > *:nth-child(1) { animation-delay: 0.06s; }
  .is-sync-revealed .stagger > *:nth-child(2) { animation-delay: 0.13s; }
  .is-sync-revealed .stagger > *:nth-child(3) { animation-delay: 0.2s; }
  .is-sync-revealed .stagger > *:nth-child(4) { animation-delay: 0.27s; }
  .is-sync-revealed .stagger > *:nth-child(5) { animation-delay: 0.34s; }
  .is-sync-revealed .stagger > *:nth-child(6) { animation-delay: 0.41s; }
  .is-sync-revealed .stagger > *:nth-child(7) { animation-delay: 0.48s; }
  .is-sync-revealed .stagger > *:nth-child(8) { animation-delay: 0.55s; }
  .is-sync-revealed .stagger > *:nth-child(9) { animation-delay: 0.62s; }
  .is-sync-revealed .stagger > *:nth-child(n+10) { animation-delay: 0.69s; }
}

/* Visible-only focus ring — clean for mouse users, clear for keyboard.
   System-blue, like first-party Apple apps; never color-only feedback. */
:focus-visible { outline: 2.5px solid color-mix(in srgb, var(--gg-focus) 75%, transparent); outline-offset: 2px; border-radius: 8px; }
button:focus:not(:focus-visible), a:focus:not(:focus-visible) { outline: none; }

/* ---- Misc ------------------------------------------------------------- */
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.live-dot { animation: live-pulse 1.6s ease-in-out infinite; }

/* Dock-style "poof" when a block is dragged off and dropped to delete. */
@keyframes poof {
  0% { transform: scale(0.5); opacity: 0; }
  25% { transform: scale(1.1); opacity: 0.95; }
  100% { transform: scale(2.3); opacity: 0; }
}
.poof-puff { animation: poof 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards; }

/* First-paint loader (lives in index.html #root until React mounts) — centers
   the same spinner on the page background so loading starts with the spinner,
   not a blank dotted screen. */
.boot-loader {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  z-index: 50;
}

/* ---- Sync loading overlay — spinner veil, single opacity fade -------------- */
.sync-load-overlay {
  /* Transparent, viewport-centred (matching the first-paint .boot-loader so the
     spinner never jumps when React mounts) — loading shows the usual background
     with just the spinner. Content is held hidden separately (shell reveal rule)
     and animates in as the spinner fades; the header drops in over the top. */
  background: transparent;
  opacity: 1;
  transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: auto;
}
.sync-load-overlay.is-hiding {
  opacity: 0;
  pointer-events: none;
}

@keyframes syncShimmer {
  to { background-position: 200% 0; }
}

.sync-load-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.95rem;
  /* No entrance animation: the overlay spinner takes over from the identical
     first-paint .boot-loader spinner, so it must appear in the exact same spot
     at full opacity. Animating it in (fade/slide/scale) made it look like a
     second spinner "replacing" the first when React mounts. Exit animation
     (is-hiding) below is unaffected. */
  transition: transform 0.22s var(--ease-ios), opacity 0.18s ease;
}
.sync-load-overlay.is-hiding .sync-load-stack {
  transform: scale(1.06);
  opacity: 0;
}

/* Blocky snake spinner for boot + sync overlay (lg size). */
.sync-load-spinner {
  color: transparent;
}

/* Wordmark with a slow light sweep, so the brand feels alive while loading. */
.sync-load-word {
  font-family: 'Outfit', system-ui, sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  background: linear-gradient(100deg, rgba(120, 113, 100, 0.45) 30%, rgba(106, 129, 96, 0.95) 50%, rgba(120, 113, 100, 0.45) 70%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: syncShimmer 1.8s linear infinite;
}
html.dark .sync-load-word {
  background: linear-gradient(100deg, rgba(255, 255, 255, 0.35) 30%, rgba(255, 255, 255, 0.95) 50%, rgba(255, 255, 255, 0.35) 70%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
}
.sync-load-tag {
  margin-top: -0.45rem;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: rgba(120, 113, 100, 0.7);
  animation: syncTagPulse 2s ease-in-out infinite;
}
html.dark .sync-load-tag { color: rgba(255, 255, 255, 0.55); }
@keyframes syncTagPulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.95; }
}
@media (prefers-reduced-motion: reduce) {
  .block-spinner-pixel, .sync-load-word, .sync-load-tag { animation: none; }
}

/* Trip shell sharpens as the veil lifts. */
/* Loading reads as: usual background + spinner → cards animate in. Keep the
   header and page background, but hold the content (insights + cards) hidden
   while the initial sync runs; the per-card stagger (gated on .is-sync-revealed)
   plays the rise as they come in, and the rest fades up with it. */
.scandi-app-shell > :not(.app-top-bar) {
  transition: opacity 0.24s var(--ease-ios);
}
.scandi-app-shell.is-sync-revealing > :not(.app-top-bar) {
  opacity: 0;
}

/* Respect reduced-motion: collapse all animation/transition to near-instant. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

.no-spin::-webkit-outer-spin-button,
.no-spin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Drop the native clock picker button on time inputs — it crowds the field and
   clips the AM/PM, and we don't need its dropdown. */
.time-no-picker::-webkit-calendar-picker-indicator { display: none; -webkit-appearance: none; }
.time-no-picker::-webkit-inner-spin-button,
.time-no-picker::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

.clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* Timeline — strict 3 columns: a left DATE column, a continuous center SPINE
   with node icons, and the CONTENT (cards / legs) on the right. Widths are
   defined once here so every row lines up into one clean rail. */
:root { --tl-date: 2.25rem; --tl-spine: 1.75rem; --tl-gap: 0.375rem; }
@media (min-width: 640px) { :root { --tl-date: 2.75rem; --tl-spine: 1.75rem; --tl-gap: 0.5rem; } }
.tl-row { position: relative; }
.tl-date { position: absolute; left: 0; width: var(--tl-date); }
.timeline-rail .tl-date {
  text-align: right;
  padding-right: 0.3rem;
  color: rgba(65, 55, 42, 0.68);
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0;
}
.timeline-rail .tl-date > span {
  white-space: nowrap;
}
@media (min-width: 640px) {
  .timeline-rail .tl-date { font-size: 0.8125rem; }
}
.tl-spine { position: absolute; top: 0; bottom: 0; left: var(--tl-date); width: var(--tl-spine); }
.timeline-rail {
  position: relative;
  --tl-rail-color: rgba(65, 55, 42, 0.16);
  --tl-rail-pad: 6px;
  /* Breathing room so card shadows aren't clipped at the content edges. */
  padding-right: var(--tl-rail-pad);
}
.timeline-rail::before {
  content: "";
  position: absolute;
  top: 2.5rem;
  bottom: 2.5rem;
  left: calc(var(--tl-date) + (var(--tl-spine) / 2));
  transform: translateX(-50%) scaleY(1);
  transform-origin: top center;
  width: 2px;
  border-radius: 999px;
  background: var(--tl-rail-color);
  pointer-events: none;
}
/* The rail draws itself down the page as the stops cascade in — the trip's
   thread being laid out before you. Gated on .is-sync-revealed so it runs as
   the loading veil lifts, not behind it. */
@keyframes railDraw {
  from { transform: translateX(-50%) scaleY(0); }
  to { transform: translateX(-50%) scaleY(1); }
}
@media (prefers-reduced-motion: no-preference) {
  .is-sync-revealed .timeline-rail::before {
    animation: railDraw 0.95s var(--ease-ios) both;
    transform-origin: top center;
  }
}
html.dark .timeline-rail {
  --tl-rail-color: rgba(148, 168, 242, 0.30);
}
/* Timeline spine nodes — same liquid-glass bubble as map route markers
   (.scandi-transport .gg-node), centered on the rail. */
.timeline-rail .tl-spine .tl-spine-node {
  width: 1.38rem;
  height: 1.38rem;
  font-size: 0.66rem;
}
.timeline-rail .tl-spine .tl-spine-node .gg-node-emoji {
  transform: scale(0.86);
}
/* The rail is drawn once by .timeline-rail so row gaps stay connected without
   overlapping per-row segments that make travel stretches look darker. */
.tl-spine-line {
  display: none;
}
/* First / last rows: start (end) the rail at the node, not stubbing past it
   into the ribbon above or below the departure card. */
.tl-spine-line--start { top: 50%; }
.tl-spine-line--end { bottom: 50%; }
/* Collapsed content indents past date + spine; an expanded stop card swaps to
   .stop-fullbleed (still a normal block, so the full-bleed math is unchanged)
   and paints over the date/spine. */
.tl-content { margin-left: calc(var(--tl-date) + var(--tl-spine) + var(--tl-gap)); }
/* Travel legs stay aligned with the stop cards on the left, but reach a few px
   further right into the page's side padding so the "station time → station
   time" route has more room to stay on one line. Cards are unchanged. */
.tl-leg { margin-right: -0.875rem; }
.timeline-rail,
.timeline-stop-group,
.timeline-rail .tl-row {
  max-width: 100%;
  /* No overflow-x clip here — expanded stop cards use .stop-fullbleed negative
     margins to reach the viewport edges; clipping this stack cut the detail
     panel off on both sides. #root/body already absorb sub-pixel 100vw bleed. */
  overflow-x: visible;
}
.timeline-stop-group {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.timeline-rail .stop-card-interactive.tl-content {
  position: relative;
  z-index: 1;
  margin-bottom: 0.125rem;
  overflow: hidden;
  border-radius: 1.65rem; /* softer, glassier corners */
  border-color: rgba(120, 142, 168, 0.16);
  /* Faint lit-top → cool-settled-bottom fill so the white card reads as a pane
     of glass rather than flat paper. */
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.6), rgba(234, 240, 248, 0.32));
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.95),          /* crisp lit top edge */
    inset 0 -18px 28px -22px rgba(48, 56, 70, 0.13),     /* soft inner bottom gather */
    0 1px 2px rgba(54, 49, 42, 0.11),
    0 6px 18px -2px rgba(54, 49, 42, 0.18),
    0 16px 40px -8px rgba(54, 49, 42, 0.24);
}
.timeline-rail .stop-card-interactive.tl-content:hover {
  z-index: 2;
  box-shadow:
    0 14px 36px -12px rgba(54, 49, 42, 0.30),
    0 7px 16px -10px rgba(54, 49, 42, 0.24);
}
html.dark .timeline-rail .stop-card-interactive.tl-content {
  background-image: none;
  border-color: rgba(255, 255, 255, 0.09);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    inset 0 -18px 28px -24px rgba(0, 0, 0, 0.6),
    0 1px 2px rgba(0, 0, 0, 0.6),
    0 10px 28px -10px rgba(0, 0, 0, 0.78),
    0 22px 48px -22px rgba(0, 0, 0, 0.86);
}
html.dark .timeline-rail .stop-card-interactive.tl-content:hover {
  box-shadow:
    0 18px 42px -16px rgba(0, 0, 0, 0.86),
    0 7px 18px -10px rgba(0, 0, 0, 0.76);
}
.timeline-rail .gg-event-row {
  position: relative;
  z-index: 1;
  margin-bottom: 0.125rem;
}
.timeline-rail .gg-event-row:hover {
  z-index: 2;
}
html.dark .timeline-rail .tl-date {
  color: var(--accent);
}
/* Calendar bands wear the same liquid-glass language as the rest of the app: a
   crisp lit top edge and a faint gathered shade at the bottom, so each stay reads
   as a smooth tinted lozenge rather than a flat color fill. */
.cal-band-cell::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -7px 11px -9px rgba(54, 49, 42, 0.22);
}
html.dark .cal-band-cell::after {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -7px 11px -9px rgba(0, 0, 0, 0.4);
}
/* Country flag rides in a small frosted chip, lifting it off the tinted band. */
.cal-flag-chip {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  font-size: 0.72rem;
  line-height: 1;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.62);
  box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.75), 0 1px 1.5px rgba(54, 49, 42, 0.14);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
html.dark .cal-flag-chip {
  background: rgba(255, 255, 255, 0.15);
  box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.22), 0 1px 1.5px rgba(0, 0, 0, 0.4);
}

/* Calendar month grid — keep city labels below day numbers on narrow phones. */
.cal-block-label {
  top: 1.35rem;
  bottom: 0;
  padding: 0 0.5rem 0.375rem 0.75rem; /* clear the left edge bar + day gutter */
}
.cal-day-num {
  top: 0.42rem;
  left: 0.7rem;
}
.cal-day-num--left {
  left: 0.82rem;
}
.cal-day-num--right {
  left: auto;
  right: 0.64rem;
}
.cal-block-city { font-size: 0.75rem; line-height: 1.25; }
.cal-block-nights { font-size: 0.6875rem; line-height: 1.2; }
@media (min-width: 640px) {
  .cal-block-label {
    top: 0;
    justify-content: center;
    padding: 0 0.625rem 0.375rem 0.875rem;
  }
  .cal-block-city { font-size: 0.8125rem; }
  .cal-block-nights { font-size: 0.75rem; }
}
@media (max-width: 639px) {
  .cal-block-label--solo { display: none; }
  .cal-block-nights { display: none; }
}

/* Map view — fill the viewport below the sticky header so canvas isn't bare at
   the bottom. Stay transparent so the body's warm atmosphere gradient is the
   single continuous backdrop; painting a flat --canvas here would leave a seam
   where the rectangle ends and the gradient's darker base shows through. */
.view-pane--map {
  min-height: calc(100dvh - env(safe-area-inset-top, 0px) - 9.5rem);
}
/* Wide-desktop fill (lg+): the header's insight chips collapse onto one row, so
   the sticky header settles to ~105px — much shorter than the 9.5rem reserved
   for the taller stacked header at narrower widths. With the base value the map
   pane stopped ~27px above the viewport, the body content ended there too, and
   the html element's own solid --canvas (the light top of the page gradient)
   showed through beneath the body gradient as a pale bar along the bottom.
   Reserve the real header (~105px) + the app-shell's 20px bottom padding (=
   125px) so the body reaches the viewport bottom exactly: no white bar, no
   scrollbar, gradient stays continuous. Scoped to lg (1024px) where the header
   height is stable at ~105px; below lg the header is taller and stacks (chips
   wrap), so the page already meets/exceeds the viewport and the base value's
   own bottom reservation keeps things continuous without this override forcing
   a scrollbar. Phones (<640px) size the panel via the map's JS instead. */
@media (min-width: 1024px) {
  .view-pane--map {
    min-height: calc(100dvh - env(safe-area-inset-top, 0px) - 125px);
  }
}
.view-pane--map .view-pane-content {
  min-height: inherit;
  display: flex;
  flex-direction: column;
}
.map-view-shell {
  flex: 1;
  min-height: calc(100dvh - env(safe-area-inset-top, 0px) - 11rem);
  display: flex;
  flex-direction: column;
}

/* Phones: the map's JS sizes the panel to end one 16px gutter above the
   bottom of the screen (matching the shell's px-4 side gutters, so the glass
   card floats with an even margin all around). The rem-based min-height
   reservations (tuned for desktop header heights) must not stretch the pane
   past that, and the shell's own bottom padding goes too — in map view the
   map (plus its gutter) IS the bottom of the page. The small margin-top tops
   the insights row's 12px up to the same 16px rhythm. */
/* Desktop/tablet: the map view is one self-contained screen — the rem-based
   min-heights are estimates that can leave the page ~1px taller than the
   viewport, so clamp the shell to exactly 100dvh and clip the hair of overflow
   rather than letting the whole page scroll a pixel. (Phones keep their JS
   sizing + dynamic toolbar handling below.) */
@media (min-width: 640px) {
  .scandi-app-shell--map { height: 100dvh; overflow: hidden; }
}
@media (max-width: 639.98px) {
  .view-pane--map { min-height: 0; }
  .map-view-shell { min-height: 0; margin-top: 0.25rem; }
  .scandi-app-shell--map { padding-bottom: 0; }
}

/* Trip overview map — explicit height avoids 0-height fitBounds on mobile Safari. */
.scandi-map-panel {
  flex: 1;
  height: 70vh; /* fallback when dvh unsupported */
  height: min(560px, calc(100dvh - env(safe-area-inset-top, 0px) - 12rem));
  min-height: 240px;
}
/* Stylized vector map: no raster tiles, so the Leaflet container background IS
   the sea. (The map div itself is the .leaflet-container, hence the compound
   selector — a descendant selector matches nothing.) Cool Nordic water in
   light, deep night in dark. */
.scandi-map-panel.leaflet-container { background: #a7cad9; }
html.dark .scandi-map-panel.leaflet-container { background: #000000; }

/* Vector-map place labels — replace the labels the raster basemap used to carry.
   Country names read as quiet uppercase poster type; city names are smaller with
   a dot. Leaflet positions the marker ROOT with a transform and paints a white
   box on it, so reset the box and put the centering transform on an INNER span
   (transforming the root would fight Leaflet's positioning). */
.scandi-country-label,
.scandi-city {
  background: transparent;
  border: 0;
  width: 0 !important;
  height: 0 !important;
  pointer-events: none;
}
.scandi-country-label > span {
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -50%);
  font-family: 'Archivo', 'Outfit', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  color: #6f6757;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
html.dark .scandi-country-label > span {
  color: #c6cad2;
  text-shadow: none;
}
.scandi-city > span {
  position: absolute;
  left: 0;
  top: 0;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 3px;
  white-space: nowrap;
}
.scandi-city-dot {
  width: 3.5px;
  height: 3.5px;
  flex: none;
  border-radius: 9999px;
  background: #9aa1ab;
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.4);
}
/* Quiet, low-contrast reference labels — they shouldn't compete with the trip
   pins/route. */
.scandi-city-name {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: rgba(90, 99, 112, 0.62);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
}
html.dark .scandi-city-dot { background: #5d6675; box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.45); }
html.dark .scandi-city-name { color: rgba(170, 178, 189, 0.6); text-shadow: none; }
@media (max-width: 639px) {
  /* First-paint fallback only — the map's JS measures and sets the real
     height (with the 16px bottom gutter) right away. No wrapper min-heights
     here: they'd override the min-height:0 rules above (same specificity,
     later in source) and stretch the page past the JS-sized card — the old
     "map too tall / page scrolls" bug on phones. */
  .scandi-map-panel {
    height: calc(100svh - env(safe-area-inset-top, 0px) - 12rem);
    min-height: 280px;
  }
}

/* Leaflet sits below our overlays/menus. */
.leaflet-container { font: inherit; background: var(--canvas); }
.leaflet-pane, .leaflet-top, .leaflet-bottom { z-index: 0; }
/* Theme the stock zoom control as a small glass capsule: hairline, blur, lit
   rim — the container owns the radius, the buttons stay square inside it. */
.leaflet-bar {
  border: 1px solid var(--gg-border-soft) !important;
  border-radius: 10px !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85), 0 2px 8px -2px rgba(54, 49, 42, 0.18) !important;
  overflow: hidden;
  -webkit-backdrop-filter: var(--gg-blur-soft);
  backdrop-filter: var(--gg-blur-soft);
}
.leaflet-bar a {
  border-radius: 0 !important;
  background: rgba(250, 252, 254, 0.82) !important;
  color: var(--muted) !important;
  border-bottom-color: var(--gg-border-soft) !important;
}
.leaflet-bar a:hover { background: rgba(255, 255, 255, 0.95) !important; color: var(--ink) !important; }
/* Kill the lingering focus ring on the +/- zoom buttons after a tap/click. */
.leaflet-control-zoom a:focus,
.leaflet-control-zoom a:focus-visible,
.leaflet-control-zoom a:active {
  outline: none !important;
  box-shadow: none !important;
}
/* Attribution line: quiet warm chip instead of the stock white strip. */
.leaflet-control-attribution {
  background: rgba(250, 252, 254, 0.82) !important;
  color: var(--faint) !important;
  font-size: 10px !important;
  border-radius: 8px 0 0 0;
}
.leaflet-control-attribution a { color: var(--muted) !important; }

/* Map hover tooltips as round liquid-glass pills — the same material language
   as the app's chips: translucent layered white over a backdrop blur, hairline
   ink border, lit top rim, soft drop shadow. The default speech-bubble arrow
   is dropped (a floating pill, not a callout). */
.leaflet-tooltip {
  border-radius: 9999px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 254, 250, 0.55));
  border: 1px solid var(--gg-border-soft);
  color: var(--ink);
  font: 600 11px/1.2 Outfit, ui-sans-serif, sans-serif;
  letter-spacing: -0.01em;
  padding: 3px 10px;
  -webkit-backdrop-filter: blur(14px) saturate(1.6);
  backdrop-filter: blur(14px) saturate(1.6);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -6px 10px -8px rgba(65, 55, 42, 0.18),
    0 3px 10px -2px rgba(54, 49, 42, 0.22);
}
.leaflet-tooltip-top::before,
.leaflet-tooltip-bottom::before,
.leaflet-tooltip-left::before,
.leaflet-tooltip-right::before { display: none; }

/* Tiny always-on labels next to day-map pins — same pill, one size down. */
.leaflet-tooltip.scandi-maplabel {
  font: 600 10px/1.15 Outfit, ui-sans-serif, sans-serif;
  padding: 2px 8px;
  white-space: nowrap;
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.scandi-maplabel-approx { color: #7c828a; font-style: italic; }
.scandi-maplabel-lodge { color: #8b633e; border-color: #e7dcc3; }
/* (The receipt day-map carries no labels — just numbered pins + route — so the
   plan above it stays the single source of names.) */
/* Trip-map stop names: persistent, a notch larger than the day-map labels,
   sitting above their numbered pins. Past stops gray out with their pins. */
.leaflet-tooltip.scandi-maplabel-stop {
  font-size: 11px;
  color: #243240;
  /* Trip stop names read as solid labels, not glass — keep them fully opaque. */
  background: #ffffff;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.leaflet-tooltip.scandi-maplabel-past { color: #94a3b8; }

/* Direction of travel on the trip map: the dashed route line drifts slowly
   from each stop toward the next (the path is drawn in itinerary order, and a
   decreasing dash offset moves the dashes "forward" along it). One dash period
   (2 + 8) per loop so the cycle is seamless; slow enough to read as a current,
   not a marquee. The global prefers-reduced-motion rule freezes it. */
@keyframes scandiRouteFlow {
  from { stroke-dashoffset: 0; }
  to { stroke-dashoffset: -10; }
}
.scandi-route-flow {
  animation: scandiRouteFlow 1.6s linear infinite;
}
/* Pause the flow while the map is actively panning/zooming: the forever
   stroke-dashoffset animation otherwise repaints the whole vector overlay
   (every country polygon) each frame, which stuttered the gesture. It resumes
   the instant the map settles, so the idle look is unchanged. */
.scandi-map-panel.is-map-moving .scandi-route-flow {
  animation: none;
}
.scandi-country-dim {
  pointer-events: none;
  mix-blend-mode: multiply;
}

.tickets-modal-root {
  display: grid;
  place-items: stretch;
}
.welcome-modal-panel.tickets-modal-panel {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-height: 100dvh;
  overflow: hidden;
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.tickets-modal-scroll {
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* ---- One-time startup modals — shared "welcome" glass panel ---------------- */
/* Shared by every one-time startup modal (Klaus gag, mobile onboarding, desktop
   notification prompt, edit recap, tickets, etc). Reuses the shared .gg-rim
   specular hairline but overrides the fill/refraction so the live Timeline
   stays clearly visible and refracts through both the scrim and the panel.
   Does NOT touch shared .gg-glass/.gg-menu/.gg-sheet/.glass-* utilities. */

/* Transparent scrim: the Timeline behind stays fully clear and undimmed — no
   blur, no darkening. Only the centered glass panel refracts the view. */
.welcome-modal-scrim {
  background: transparent;
}

/* Clear refractive panel: a thin warm-white film over a heavy, saturated, bright
   blur so the colorful Timeline behind bends through as obvious frosted glass
   rather than a milky card. Edge highlights + a lit top rim + a soft bottom
   light-gather (layered over .gg-rim) sell the pane's depth and refraction. */
.welcome-modal-panel {
  position: relative;
  /* Uniform liquid-glass panel — the same shared clear/refractive recipe and
     specular rim used by .gg-sheet and .gg-menu, but with a much deeper, more
     dramatic drop shadow so the bigger welcome modal floats well above the
     Timeline. The colorful Timeline bends and blooms through. */
  background: var(--gg-panel-fill);
  border: 1px solid var(--gg-border-medium);
  -webkit-backdrop-filter: var(--gg-panel-blur);
  backdrop-filter: var(--gg-panel-blur);
  box-shadow:
    var(--gg-panel-rim),
    0 2px 8px -2px rgba(20, 16, 10, 0.34),
    0 26px 50px -24px rgba(20, 16, 10, 0.46),
    0 52px 110px -34px rgba(20, 16, 10, 0.54);
}

/* High-contrast text on the floating glass modals (welcome / morning brief,
   the "make an app" onboarding card). The clear refractive panel lets the
   colorful Timeline bloom through behind the copy, so the warm muted greys
   used elsewhere read low-contrast here — push the body/secondary text toward
   black. Scoped to the panel (descendant specificity 0,2,0 beats Tailwind's
   single-class utilities) so the rest of the app keeps its softer hierarchy,
   and the semantic accent tones (amber heads-up, emerald all-clear, fjord
   links) are deliberately left untouched. */
.welcome-modal-panel .text-slate-800 { color: #16130f; }   /* headings — near-black */
.welcome-modal-panel .text-slate-700 { color: #1c1813; }
.welcome-modal-panel .text-slate-600 { color: #24201a; }   /* body copy */
.welcome-modal-panel .text-slate-500 { color: #2f2a22; }   /* sub-labels */
.welcome-modal-panel .text-slate-400 { color: #423c33; }   /* dates / tertiary */

/* Glassy CTA, but saturated and prominent: a vivid sage pane with a translucent
   glass sheen (lit rim + specular top) over a saturated fill, so it pops as the
   clear primary action while still reading as liquid glass. */
.welcome-cta {
  position: relative;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0) 44%),
    linear-gradient(180deg, #5f8350, #44603a);
  -webkit-backdrop-filter: blur(10px) saturate(1.7) brightness(1.02);
  backdrop-filter: blur(10px) saturate(1.7) brightness(1.02);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.85),
    inset 0 -12px 18px -12px rgba(255, 255, 255, 0.32),
    inset 0 -1px 0 rgba(24, 32, 20, 0.32),
    0 16px 30px -10px rgba(40, 56, 32, 0.5),
    0 4px 10px -4px rgba(40, 56, 32, 0.4);
  color: #fff;
  text-shadow: 0 1px 2px rgba(20, 30, 16, 0.55);
  transition: background 0.16s ease, transform 0.12s var(--ease-ios), box-shadow 0.16s ease;
}
.welcome-cta:hover {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0) 44%),
    linear-gradient(180deg, #6a9259, #4c6c40);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.92),
    inset 0 -12px 18px -12px rgba(255, 255, 255, 0.38),
    inset 0 -1px 0 rgba(24, 32, 20, 0.34),
    0 20px 36px -10px rgba(40, 56, 32, 0.56),
    0 5px 12px -4px rgba(40, 56, 32, 0.44);
}

/* =======================================================================
   DARK MODE — "Nordic night"
   Driven by `html.dark` (set by the boot script + src/lib/theme.js). The
   warm putty/sage day palette becomes a warm near-black with the sage accent
   lifted so it reads on dark. Three layers do the work:
     1. Re-point the Tailwind palette channels (--c-*) so every utility flips.
     2. Re-point the design tokens (--ink/--surface/--gg-* …) so the glass
        material and shared surfaces follow.
     3. A handful of scoped overrides for places that bake light values
        directly (the glass highlight rims, bg-white, status tints, the
        welcome modal's forced-dark text, map tooltips).
   ======================================================================= */
html.dark {
  color-scheme: dark;

  /* 1 — Tailwind palette channels (dark). Neutrals invert (high steps become
     light text, low steps become dark surfaces); fjord splits role (low =
     dark sage fills, high = light sage text). */
  --c-fjord-50: 26 32 26;    --c-fjord-100: 34 42 32;   --c-fjord-200: 47 58 43;
  --c-fjord-300: 62 77 57;   --c-fjord-400: 124 152 110;--c-fjord-500: 150 177 135;
  --c-fjord-600: 172 198 156;--c-fjord-700: 193 214 178;--c-fjord-800: 211 228 198;
  --c-fjord-900: 224 237 213;--c-fjord-950: 235 244 226;

  --c-stone-50: 16 19 26;    --c-stone-100: 22 26 35;   --c-stone-200: 35 41 54;
  --c-stone-300: 51 59 75;   --c-stone-400: 82 92 110;  --c-stone-500: 118 129 148;
  --c-stone-600: 150 161 178;--c-stone-700: 181 191 206;--c-stone-800: 207 214 226;
  --c-stone-900: 228 233 242;--c-stone-950: 241 244 250;

  /* Slate carries nearly all text — its steps sit WELL lighter than a straight
     inversion would give: 400 (the app's workhorse secondary) reads at ~9:1 on
     the card surface, and even 300 (quiet icon affordances, placeholders)
     stays clearly visible. Err bright: dim text was the #1 dark-mode
     complaint. Cool/blue-neutral tint so text sits in the same family as the
     blue-gray surfaces (no warm-on-cool clash). */
  --c-slate-50: 18 21 28;    --c-slate-100: 27 31 41;   --c-slate-200: 43 49 63;
  --c-slate-300: 123 133 151;--c-slate-400: 177 187 203;--c-slate-500: 199 208 221;
  --c-slate-600: 219 225 236;--c-slate-700: 233 238 245;--c-slate-800: 244 247 251;
  --c-slate-900: 250 251 254;--c-slate-950: 253 254 255;

  --c-sand-50: 28 23 16;     --c-sand-100: 36 29 19;    --c-sand-200: 51 41 26;
  --c-sand-300: 74 58 35;    --c-sand-400: 122 96 56;   --c-sand-500: 168 133 76;
  --c-sand-600: 196 166 115; --c-sand-700: 214 195 156; --c-sand-800: 231 220 195;
  --c-sand-900: 243 238 225;

  /* 2 — Design tokens — flat Nordic-night editorial, cool blue-gray family. */
  --ink: #e9edf5;          /* cool near-white text */
  --muted: #9aa4b6;
  --faint: #6d7688;
  --line: #ccd4e2;         /* crisp cool hairline borders on dark */
  --line-strong: #ccd4e2;
  --surface: #2a3344;      /* lighter blue-gray raised panel */
  --canvas: #000000;       /* pure black page bg */
  --accent: #94a8f2;       /* lightened Nyhavn blue for dark, vivid */
  --accent-deep: #aec0f5;
  --accent-soft: #28324e;
  --pill: #2e3c84;         /* deep-blue primary on dark */
  --pill-hover: #36459a;
  --btn-hover-shadow: #ffffff;

  /* Flat — no gradient, no grain. Top MUST equal --canvas so iOS overscroll /
     PWA status bar blend (boot script + theme.js use the same value). */
  --gg-bg-top: #000000;
  --gg-bg-bottom: #000000;
  --canvas-grain: transparent;
  --canvas-grain-soft: transparent;
  --gg-border-soft: rgba(186, 201, 235, 0.16);
  --gg-border-medium: var(--line);
  --gg-highlight: transparent;
  --gg-blur-soft: none;
  --gg-blur-strong: none;
  --gg-panel-fill: var(--surface);
  --gg-panel-blur: none;
  --gg-panel-ios-first-paint: var(--surface);
  /* Full-screen takeovers (Settings + Claus chat) read as the app itself —
     pure-black background with the blue-gray surfaces (cards, rows, bubbles)
     floating on top for contrast. */
  --side-sheet-fill: #000000;
  --side-sheet-blur: none;
  --side-sheet-ios-tint: #000000;
  --gg-panel-rim: none;
  --gg-panel-shadow: 0 18px 44px -20px rgba(0, 0, 0, 0.7);

  --shadow-sm: none;
  --shadow-md: 0 14px 34px -16px rgba(0, 0, 0, 0.805);
  --planned-bg: #161b25;
}

/* Tailwind's box-shadow utilities (shadow-soft/lift/drag, index.html) bake warm
   light shadows; switch them to neutral black so raised cards do not glow. */
html.dark .shadow-soft { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.58), 0 8px 24px -10px rgba(0, 0, 0, 0.74); }
html.dark .shadow-lift { box-shadow: 0 18px 42px -14px rgba(0, 0, 0, 0.82), 0 7px 18px -10px rgba(0, 0, 0, 0.72); }
html.dark .shadow-drag { box-shadow: 0 24px 54px -12px rgba(0, 0, 0, 0.88), 0 9px 22px -9px rgba(0, 0, 0, 0.78); }

/* 3 — Surfaces & utilities that hard-code light values --------------------- */

/* Page canvas: <body> carries a static `bg-stone-50` class (index.html), whose
   class specificity (0,1,0) outranks the `body { background: var(--canvas) }`
   base rule (0,0,1) — so in dark the page painted #10131a instead of the pure
   black canvas. (Invisible in light, where bg-stone-50 equals the cream canvas.)
   Re-assert the canvas with a class-bearing selector that outranks the utility. */
html.dark body { background-color: var(--canvas); }

/* Sticky top bar: its Tailwind `bg-stone-50` resolves to #10131a in dark — a
   shade lighter than the pure-black canvas — so the full-width bar read as a
   gray band across the top (and tinted Safari's status-bar region, since it
   spans the safe-area inset). Pin it to the canvas so the header melts into the
   black page, exactly as the cream header melts into the cream canvas in light.
   The `is-scrolled` shadow + bottom hairline still separate it once content
   scrolls beneath. */
html.dark .app-top-bar { background-color: var(--canvas); }

/* Cards: plain bg-white becomes the blue-gray raised surface. */
html.dark .bg-white { background-color: var(--surface); }
html.dark .border-white { border-color: rgba(255, 255, 255, 0.10); }
/* Lift cards off the pure-black page: a faint top highlight rim + a soft drop
   shadow give them gentle depth and a refined edge against the black. */
html.dark .card,
html.dark .rec-planned,
html.dark .stop-card-interactive {
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.05),
    0 1px 2px rgba(0, 0, 0, 0.6), 0 10px 26px -16px rgba(0, 0, 0, 0.85);
}
html.dark .card-hover:hover {
  border-color: var(--line-strong);
  box-shadow: 0 18px 42px -18px rgba(0, 0, 0, 0.86), 0 6px 16px -10px rgba(0, 0, 0, 0.72);
}
/* Calendar panel sits flush-black on the page (not the raised blue-gray
   surface) — the month grid reads as the app itself, with the colored stop
   bands carrying the contrast. */
html.dark .card.calendar-card { background: var(--canvas) !important; }
/* "+ Add" slot affordance: the near-black dotted border vanishes on the dark
   card, so lighten it to a legible cool hairline (and a touch brighter on
   hover) for clear contrast. */
html.dark .slot-add-btn { border-color: rgba(199, 208, 221, 0.4); }
html.dark .slot-add-btn:hover { border-color: rgba(199, 208, 221, 0.7); }
html.dark .rec-planned.card-hover:hover {
  box-shadow: var(--shadow-sm);
}
html.dark .stop-card-interactive.shadow-lift {
  box-shadow: 0 18px 42px -14px rgba(0, 0, 0, 0.82), 0 7px 18px -10px rgba(0, 0, 0, 0.72);
}

/* Custom utility classes (.badge/.chip/.tier/.btn-*) that bake light fills. */
html.dark .btn-quiet:hover { background: rgba(255, 255, 255, 0.08); }
html.dark .btn-outline { background: rgba(255, 255, 255, 0.04); }
html.dark .btn-outline:hover { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.2); }
html.dark .badge { background: var(--accent-soft); }
html.dark .chip { background: var(--accent-soft); }
html.dark .flag-glyph { box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.22); }
html.dark .tier-1 { color: #9bcb86; }  html.dark .tier-1 .tier-dot { background: #7fb267; }
html.dark .tier-2 { color: #e0a455; }  html.dark .tier-2 .tier-dot { background: #d49236; }
html.dark .tier-3 { color: #9aa2ad; }
html.dark .tier-4 { color: #b6ad9c; }
html.dark input[type="range"].fjord-range { background: rgba(255, 255, 255, 0.14); }

/* Recommendations / section headings bake warm dark grays that vanish on the
   dark surface — lift them to a readable cool light. */
html.dark .stop-detail .section-label { color: rgba(233, 237, 245, 0.9); }
html.dark .stop-detail .section-hint { color: rgba(233, 237, 245, 0.55); }

/* Side-sheet (Settings) buttons hard-code a near-black hairline that reads as a
   harsh black outline on the dark sheet — switch those borders to a light gray. */
html.dark .settings-sheet .border-\[\#1a1714\] { border-color: rgba(255, 255, 255, 0.18) !important; }

/* View switcher: the active segment is near-black (bg-[#1a1714]) and disappears
   on the dark toolbar — ring it with a light border so the selection reads. */
html.dark .seg-toggle button[data-active="true"] {
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.85);
}

/* Glass rims: the specular hairlines are painted in opaque white; on dark glass
   that reads as a chalky outline, so dial the highlight gradients/box-shadows
   down to a faint lit edge. */
html.dark .gg-rim::before {
  background: linear-gradient(158deg,
    rgba(255, 255, 255, 0.34) 0%,
    rgba(255, 255, 255, 0.16) 19%,
    rgba(255, 255, 255, 0.02) 46%,
    rgba(255, 255, 255, 0.05) 68%,
    rgba(255, 255, 255, 0.24) 100%);
}
html.dark .gg-rim::after {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 -10px 18px -14px rgba(255, 255, 255, 0.12);
}

html.dark .gg-glass {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.03));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 14px 26px -18px rgba(255, 255, 255, 0.10),
    inset 0 -11px 18px -14px rgba(0, 0, 0, 0.4),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}

html.dark .gg-toolbar { border-bottom-color: rgba(255, 255, 255, 0.12); }
html.dark[data-merged-stop-head] .gg-toolbar { border-bottom-color: transparent; }
html.dark .gg-toolbar::before {
  background: linear-gradient(180deg, rgba(30, 28, 24, 0.28), rgba(20, 18, 16, 0.20));
  -webkit-backdrop-filter: blur(32px) saturate(1.45) brightness(0.86) contrast(1.03);
  backdrop-filter: blur(32px) saturate(1.45) brightness(0.86) contrast(1.03);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}
html.dark .gg-toolbar-scrolled { box-shadow: 0 8px 22px -10px rgba(0, 0, 0, 0.6); }

html.dark .gg-tray {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.052), rgba(255, 255, 255, 0.026));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10),
    inset 0 -8px 18px -16px rgba(0, 0, 0, 0.5),
    0 12px 32px -20px rgba(0, 0, 0, 0.6);
}
html.dark .gg-stop-tile {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.068), rgba(255, 255, 255, 0.035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4);
}
html.dark .gg-stop-tile:hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 10px 18px -16px rgba(255, 255, 255, 0.09),
    0 4px 12px -4px rgba(0, 0, 0, 0.5);
}

/* Transport bubbles: a clear dark dome instead of a bright white one, with a
   cool rim so the emoji still reads as sealed under glass (works over the
   dark_all map tiles too). */
html.dark .gg-node {
  background:
    radial-gradient(135% 135% at 32% 22%,
      rgba(255, 255, 255, 0.18),
      rgba(255, 255, 255, 0.07) 42%,
      rgba(60, 72, 84, 0.14) 62%,
      rgba(40, 54, 70, 0.34) 86%,
      rgba(255, 255, 255, 0.10) 100%),
    rgba(28, 30, 34, 0.50);
  border: 1px solid rgba(255, 255, 255, 0.24);
  -webkit-backdrop-filter: blur(9px) saturate(1.28) brightness(0.86);
  backdrop-filter: blur(9px) saturate(1.28) brightness(0.86);
  box-shadow:
    inset 0 1px 1px rgba(255, 255, 255, 0.32),
    inset 0 -3px 6px -2px rgba(0, 0, 0, 0.5),
    inset 2px 0 3px -2px rgba(255, 255, 255, 0.18),
    inset -2px 0 3px -2px rgba(255, 255, 255, 0.12),
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 4px 10px -2px rgba(0, 0, 0, 0.55);
}
html.dark .gg-node-lens {
  background:
    radial-gradient(48% 34% at 33% 17%, rgba(255, 255, 255, 0.30), rgba(255, 255, 255, 0) 74%),
    radial-gradient(70% 48% at 52% 112%, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0) 60%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
}

/* Tinted chips/banners: the white film base reads chalky on dark — swap for a
   faint light wash so currentColor still tints the hue. */
html.dark .gg-chip {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.03)),
    color-mix(in srgb, currentColor 16%, rgba(255, 255, 255, 0.04));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4);
}
html.dark .gg-banner {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.025)),
    color-mix(in srgb, currentColor 14%, rgba(255, 255, 255, 0.03));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
html.dark .gg-weather-pill {
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

html.dark .gg-menu--dense {
  background:
    linear-gradient(125deg,
      rgba(255, 255, 255, 0.08) 0%,
      rgba(255, 255, 255, 0.03) 30%,
      rgba(255, 255, 255, 0.02) 60%,
      rgba(255, 255, 255, 0.05) 100%),
    linear-gradient(180deg, rgba(40, 36, 30, 0.9), rgba(28, 25, 20, 0.86));
  -webkit-backdrop-filter: blur(24px) saturate(1.35) brightness(0.84);
  backdrop-filter: blur(24px) saturate(1.35) brightness(0.84);
}
html.dark .settings-sheet,
html.dark .trip-chat-sheet {
  background: var(--side-sheet-fill);
  -webkit-backdrop-filter: var(--side-sheet-blur);
  backdrop-filter: var(--side-sheet-blur);
}
@supports (-webkit-touch-callout: none) {
  html.dark .settings-sheet,
  html.dark .trip-chat-sheet { background-color: var(--side-sheet-ios-tint); }
}
/* Section dividers + control borders inside side sheets: the warm stone
   hairlines sit right on the dark surface and disappear. Lift just the pane's
   borders to clear light hairlines (scoped, so the rest of the app keeps its
   quieter borders). */
html.dark .settings-sheet .border-stone-100,
html.dark .trip-chat-sheet .border-stone-100 { border-color: rgba(255, 255, 255, 0.12); }
html.dark .settings-sheet .border-stone-200,
html.dark .trip-chat-sheet .border-stone-200 { border-color: rgba(255, 255, 255, 0.17); }
/* Accent-outlined controls (the selected Tyler/Edwin pill, Enable
   notifications, Load route) carry sage fjord borders that on dark sit dark
   sage on a dark sage fill — invisible. Lift them to a clear sage hairline. */
html.dark .settings-sheet .border-fjord-100,
html.dark .trip-chat-sheet .border-fjord-100 { border-color: rgba(156, 184, 141, 0.32); }
html.dark .settings-sheet .border-fjord-200,
html.dark .trip-chat-sheet .border-fjord-200 { border-color: rgba(156, 184, 141, 0.42); }
html.dark .settings-sheet .border-fjord-300,
html.dark .trip-chat-sheet .border-fjord-300 { border-color: rgba(156, 184, 141, 0.6); }

/* Trip start/end glass rows — the light recipe is a 60% white film, which
   rendered as a glaring silver band on the dark timeline. */
html.dark .gg-event-row {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
    inset 0 -7px 14px -12px rgba(0, 0, 0, 0.4);
}
/* Trip-start / trip-end bookend rows recede into the background in dark mode —
   they read as chrome, not content. Flatten the fill, soften the border, and
   drop the specular rim so they sit quietly behind the real stop cards. */
html.dark .tl-end-card {
  background: rgba(255, 255, 255, 0.025);
  border-color: rgba(255, 255, 255, 0.05);
  box-shadow: none;
}
html.dark .tl-end-card.gg-rim::before,
html.dark .tl-end-card.gg-rim::after { display: none; }

/* Segmented view switcher: the active lens is a near-opaque WHITE pill in
   light — a flashlight on the dark toolbar. Deepen the trough and turn the
   lens into smoked glass with light labels. */
html.dark .glass-segment {
  background: rgba(0, 0, 0, 0.28);
  border-color: rgba(255, 255, 255, 0.10);
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.45),
    inset 0 0 0 0.5px rgba(0, 0, 0, 0.2),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06),
    0 1px 0 rgba(255, 255, 255, 0.05);
}
html.dark .glass-segment__lens {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.15) 0%,
    rgba(255, 255, 255, 0.08) 48%,
    rgba(255, 255, 255, 0.045) 100%);
  border-color: rgba(255, 255, 255, 0.18);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.21),
    inset 0 0 0 0.5px rgba(255, 255, 255, 0.04),
    inset 0 -3px 6px -2px rgba(255, 255, 255, 0.075),
    0 2px 5px rgba(0, 0, 0, 0.35),
    0 7px 18px -4px rgba(0, 0, 0, 0.35);
}
html.dark .glass-segment__lens::before {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.17),
    rgba(255, 255, 255, 0.03) 70%,
    transparent);
}
html.dark .glass-segment__lens::after {
  background: radial-gradient(120% 100% at 50% 120%,
    rgba(255, 255, 255, 0.08),
    transparent 70%);
}
html.dark .glass-segment__item { color: rgba(244, 240, 232, 0.6); }
html.dark .glass-segment__item:hover {
  color: rgba(244, 240, 232, 0.85);
  background: rgba(255, 255, 255, 0.08);
}
html.dark .glass-segment__item[data-active="true"],
html.dark .glass-segment__item[aria-current="page"],
html.dark .glass-segment__item[data-active="true"]:hover,
html.dark .glass-segment__item[aria-current="page"]:hover {
  color: rgba(252, 250, 245, 0.98);
  background: transparent;
}
/* Inline variant (mode tabs, AM/PM) has no moving lens — the active item paints
   its own bubble, so without a dark treatment it keeps the bright light-mode
   white fill/border/sheen and reads far too shiny on dark. Mute it to match the
   toned-down lens. */
html.dark .glass-segment--inline .glass-segment__item[data-active="true"] {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.14) 0%,
    rgba(255, 255, 255, 0.075) 48%,
    rgba(255, 255, 255, 0.04) 100%);
  border-color: rgba(255, 255, 255, 0.17);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 0 0 0.5px rgba(255, 255, 255, 0.04),
    0 2px 5px rgba(0, 0, 0, 0.30),
    0 6px 14px -4px rgba(0, 0, 0, 0.30);
  backdrop-filter: blur(5px) saturate(1.12);
  -webkit-backdrop-filter: blur(5px) saturate(1.12);
}
html.dark .glass-segment--inline .glass-segment__item[data-active="true"]::before {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.16),
    rgba(255, 255, 255, 0.04) 70%,
    transparent);
}

/* Opacity-suffixed white utilities (welcome / insight / onboarding list
   surfaces) are separate classes the plain .bg-white override can't reach —
   they stayed 55% white panels on dark. */
html.dark .bg-white\/55 { background-color: rgba(255, 255, 255, 0.08); }
html.dark .bg-white\/45 { background-color: rgba(255, 255, 255, 0.06); }
html.dark .active\:bg-white\/40:active { background-color: rgba(255, 255, 255, 0.10); }
html.dark .border-white\/60 { border-color: rgba(255, 255, 255, 0.14); }
html.dark .border-white\/50 { border-color: rgba(255, 255, 255, 0.12); }
html.dark .divide-white\/40 > :not([hidden]) ~ :not([hidden]) { border-color: rgba(255, 255, 255, 0.10); }

/* Route-advice body copy uses an opacity-modified amber (.text-amber-900/90)
   — same story: the plain .text-amber-900 override can't reach it. */
html.dark .text-amber-900\/90 { color: rgba(252, 211, 77, 0.9); }

/* The rest of the opacity-suffixed stock-hue text/fills (heat notes on rec
   cards, unplanned-panel labels, booking-guide "why" lines) — each is its own
   class, so each needs its own dark mapping. */
html.dark .text-amber-700\/90 { color: rgba(252, 211, 77, 0.9); }
html.dark .text-amber-700\/80 { color: rgba(252, 211, 77, 0.8); }
html.dark .text-amber-500\/80 { color: rgba(251, 191, 36, 0.8); }
html.dark .text-emerald-700\/90 { color: rgba(110, 231, 183, 0.9); }
html.dark .bg-amber-50\/80 { background-color: rgba(245, 158, 11, 0.10); }
/* Pacing meter's "slow paced" label (text-sky-600) was a dark blue on dark. */
html.dark .text-sky-600 { color: #7dd3fc; }

/* Toolbar icon-button hover/press: the warm-ink washes vanish on dark. */
html.dark .gg-icon-btn:hover { background-color: rgba(255, 255, 255, 0.08); }
html.dark .gg-icon-btn:active { background-color: rgba(255, 255, 255, 0.14); }

/* Glass-menu rows (profile picker) use warm-ink arbitrary hover washes that
   disappear on dark — give them a faint light wash instead. */
html.dark .gg-menu button:hover { background-color: rgba(255, 255, 255, 0.07); }
html.dark .gg-menu button:active { background-color: rgba(255, 255, 255, 0.11); }

/* Solid sage fills (Add buttons, active segments, pacing) pair with white
   text, but the dark fjord channels are tuned for TEXT duty (light sage) —
   pin the fills to the light theme's deep sage values so white-on-sage
   buttons keep their contrast. */
html.dark .bg-fjord-400 { background-color: rgb(132 154 120); }
html.dark .bg-fjord-500 { background-color: rgb(106 129 96); }
html.dark .bg-fjord-600 { background-color: rgb(86 104 78); }
html.dark .bg-fjord-700 { background-color: rgb(70 83 64); }
html.dark .hover\:bg-fjord-700:hover { background-color: rgb(70 83 64); }

/* Status tints (amber/rose/emerald): soft light backgrounds → dark hue washes,
   their dark text → light. Solid action buttons (-400/-500/-600/-700 fills with
   white text) are intentionally left vivid. */
html.dark .bg-amber-50 { background-color: rgba(245, 158, 11, 0.12); }
html.dark .bg-amber-100 { background-color: rgba(245, 158, 11, 0.18); }
html.dark .border-amber-100 { border-color: rgba(245, 158, 11, 0.22); }
html.dark .border-amber-200 { border-color: rgba(245, 158, 11, 0.30); }
html.dark .text-amber-600 { color: #fbbf24; }
html.dark .text-amber-700,
html.dark .text-amber-800,
html.dark .text-amber-900 { color: #fcd34d; }
html.dark .hover\:bg-amber-100:hover { background-color: rgba(245, 158, 11, 0.20); }

html.dark .bg-rose-50 { background-color: rgba(244, 63, 94, 0.12); }
html.dark .bg-rose-100 { background-color: rgba(244, 63, 94, 0.18); }
html.dark .border-rose-200 { border-color: rgba(244, 63, 94, 0.30); }
html.dark .ring-rose-100 { --tw-ring-color: rgba(244, 63, 94, 0.25); }
html.dark .text-rose-500 { color: #fb7185; }
html.dark .text-rose-600 { color: #fb7185; }
html.dark .text-rose-700,
html.dark .text-rose-800,
html.dark .text-rose-900 { color: #fda4af; }
html.dark .hover\:bg-rose-50:hover { background-color: rgba(244, 63, 94, 0.14); }
html.dark .hover\:bg-rose-100:hover { background-color: rgba(244, 63, 94, 0.20); }
html.dark .hover\:text-rose-500:hover,
html.dark .hover\:text-rose-600:hover { color: #fb7185; }
html.dark .hover\:text-rose-700:hover { color: #fda4af; }

html.dark .bg-emerald-50 { background-color: rgba(16, 185, 129, 0.12); }
html.dark .bg-emerald-100 { background-color: rgba(16, 185, 129, 0.18); }
html.dark .border-emerald-100 { border-color: rgba(16, 185, 129, 0.20); }
html.dark .border-emerald-200 { border-color: rgba(16, 185, 129, 0.30); }
html.dark .text-emerald-600 { color: #34d399; }
html.dark .text-emerald-700,
html.dark .text-emerald-800,
html.dark .text-emerald-900 { color: #6ee7b7; }
html.dark .hover\:text-emerald-900:hover { color: #6ee7b7; }

/* The remaining stock hues — tag chips (violet/sky), day-plan hint boxes
   (sky/indigo), and the per-stop calendar colors (violet/sky/teal/indigo/
   orange) — get the same treatment: pale backgrounds → dark hue washes,
   dark text → the hue's -300 tint. Solid -500 chips stay vivid. */
html.dark .bg-violet-100 { background-color: rgba(139, 92, 246, 0.20); }
html.dark .border-violet-200 { border-color: rgba(139, 92, 246, 0.32); }
html.dark .text-violet-800 { color: #c4b5fd; }

html.dark .bg-sky-50 { background-color: rgba(14, 165, 233, 0.12); }
html.dark .bg-sky-100 { background-color: rgba(14, 165, 233, 0.18); }
html.dark .border-sky-100 { border-color: rgba(14, 165, 233, 0.22); }
html.dark .border-sky-200 { border-color: rgba(14, 165, 233, 0.32); }
html.dark .text-sky-700,
html.dark .text-sky-800 { color: #7dd3fc; }

html.dark .bg-indigo-50 { background-color: rgba(99, 102, 241, 0.14); }
html.dark .bg-indigo-100 { background-color: rgba(99, 102, 241, 0.20); }
html.dark .border-indigo-100 { border-color: rgba(99, 102, 241, 0.24); }
html.dark .text-indigo-800,
html.dark .text-indigo-900 { color: #a5b4fc; }

html.dark .bg-teal-100 { background-color: rgba(20, 184, 166, 0.18); }
html.dark .text-teal-800 { color: #5eead4; }

html.dark .bg-orange-100 { background-color: rgba(249, 115, 22, 0.18); }
html.dark .text-orange-800 { color: #fdba74; }

/* Welcome / morning-brief modal forces near-black text for its light glass over
   the colorful timeline. On dark the panel is dark too — flip that copy light. */
html.dark .welcome-modal-panel .text-slate-800 { color: #f8f5ee; }
html.dark .welcome-modal-panel .text-slate-700 { color: #f0ebe1; }
html.dark .welcome-modal-panel .text-slate-600 { color: #e4ded1; }
html.dark .welcome-modal-panel .text-slate-500 { color: #d2cbbd; }
html.dark .welcome-modal-panel .text-slate-400 { color: #b9b1a0; }

/* Scrollbars. */
html.dark .scrollbar-thin { scrollbar-color: rgba(255, 255, 255, 0.18) transparent; }
html.dark .scrollbar-thin::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.16); }
html.dark .scrollbar-thin::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.26); }
@media (hover: hover) and (pointer: fine) {
  html.dark ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.16); }
  html.dark ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.26); }
}

/* Leaflet labels on the dark_all basemap. The earlier warm translucent pills
   blended into the muddy brown-grey map; these are near-solid, slightly cool
   near-black with crisp near-white text and a dark halo so each label reads
   clearly and lifts off the tiles. */
html.dark .leaflet-tooltip {
  background: rgba(18, 19, 22, 0.94);
  border-color: rgba(255, 255, 255, 0.22);
  color: #f7f9fb;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 0 0 1px rgba(0, 0, 0, 0.55),
    0 4px 12px -2px rgba(0, 0, 0, 0.7);
}
html.dark .leaflet-tooltip.scandi-maplabel-stop {
  color: #ffffff;
  /* Solid, not the 0.94-alpha glass the other map labels use. */
  background: #121316;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
html.dark .scandi-country-dim { mix-blend-mode: normal; }
html.dark .scandi-maplabel-past { color: #aab2bd; }
html.dark .scandi-maplabel-approx { color: #aeb6c1; }
html.dark .scandi-maplabel-lodge { color: #f0c79a; border-color: rgba(240, 199, 154, 0.5); }
html.dark .leaflet-control-attribution {
  background: rgba(18, 16, 13, 0.86) !important;
}
/* Zoom +/- buttons: the light bar fill is hard-coded, but the glyph color
   rides var(--muted) which flips light on dark — so they vanished (light on
   light). Dark bar + light glyphs. */
html.dark .leaflet-bar {
  border-color: rgba(255, 255, 255, 0.16) !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 2px 8px -2px rgba(0, 0, 0, 0.6) !important;
}
html.dark .leaflet-bar a {
  background: rgba(30, 28, 23, 0.92) !important;
  color: #ece6da !important;
  border-bottom-color: rgba(255, 255, 255, 0.12) !important;
}
html.dark .leaflet-bar a:hover {
  background: rgba(50, 46, 38, 0.96) !important;
  color: #ffffff !important;
}

/* The day-map pins draw on light tiles by default; their white ring still reads
   on dark_all, so they're left as-is. */

/* ---- Native-style confirm / action sheet (confirmDialog / ConfirmHost) ----
   Mobile: a bottom action sheet that slides up over a dimmed scrim, clear of the
   home indicator (safe-area padding). Desktop: a centred dialog. Replaces the
   browser's confirm/alert chrome so destructive actions feel iOS-native. */
.confirm-scrim {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  padding: 0 0.75rem;
  background: rgba(20, 22, 16, 0);
  transition: background 0.24s ease;
}
.confirm-scrim.is-open { background: rgba(20, 22, 16, 0.42); }
@media (min-width: 640px) {
  .confirm-scrim { align-items: center; }
}
.confirm-sheet {
  width: 100%;
  max-width: 30rem;
  margin-bottom: max(0.75rem, env(safe-area-inset-bottom));
  border-radius: 1.5rem;
  background: var(--gg-panel-fill);
  -webkit-backdrop-filter: var(--gg-panel-blur);
  backdrop-filter: var(--gg-panel-blur);
  box-shadow: 0 -2px 12px rgba(20, 30, 40, 0.12), 0 24px 60px -16px rgba(20, 30, 40, 0.42);
  overflow: hidden;
  transform: translateY(20px) scale(0.98);
  opacity: 0;
  transition: transform 0.26s var(--ease-ios), opacity 0.2s ease;
}
.confirm-sheet.is-open { transform: none; opacity: 1; }
@media (min-width: 640px) {
  .confirm-sheet { margin-bottom: 0; transform: translateY(8px) scale(0.985); }
}
.confirm-sheet-body { padding: 1.35rem 1.25rem 0.5rem; text-align: center; }
.confirm-sheet-title { margin: 0 0 0.35rem; font-size: 1.05rem; font-weight: 650; color: var(--ink); }
.confirm-sheet-message { margin: 0; font-size: 0.9rem; line-height: 1.45; color: var(--muted); }
.confirm-sheet-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.85rem 1rem max(1rem, env(safe-area-inset-bottom));
}
.confirm-btn {
  min-height: 3rem;
  border-radius: 0.95rem;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.16s var(--ease-spring), background 0.15s ease;
}
.confirm-btn:active { transform: scale(0.975); }
.confirm-btn--primary { background: var(--accent); color: #fff; }
.confirm-btn--primary:hover { background: var(--accent-deep); }
html.dark .confirm-btn--primary { color: #15240f; }
.confirm-btn--destructive { background: #e5484d; color: #fff; }
.confirm-btn--destructive:hover { background: #d23a3f; }
.confirm-btn--cancel { background: rgba(120, 113, 100, 0.1); color: var(--ink); }
.confirm-btn--cancel:hover { background: rgba(120, 113, 100, 0.16); }
html.dark .confirm-btn--cancel { background: rgba(255, 255, 255, 0.08); }
html.dark .confirm-btn--cancel:hover { background: rgba(255, 255, 255, 0.14); }
@media (prefers-reduced-motion: reduce) {
  .confirm-sheet { transition: opacity 0.15s ease; transform: none; }
}

/* ============================================================================
   DANISH EDITORIAL REDESIGN LAYER
   Replaces the Liquid Glass material everywhere with a flat, bold, poster
   system: warm cream canvas, crisp near-black hairlines, sharp corners — no
   blur, translucency, gradients, soft glows, or specular rims. Last in the
   file so it wins over the legacy glass rules.
   ============================================================================ */

/* Editorial display serif (Fraunces) for the display layer — print-flavored,
   with optical sizing and a hair of softness/wonk for a handcrafted feel. */
.font-editorial, .font-display {
  font-family: 'Fraunces', 'Archivo', ui-serif, Georgia, serif !important;
  font-optical-sizing: auto;
  font-variation-settings: 'SOFT' 30, 'WONK' 1;
  letter-spacing: -0.014em;
}

/* Kill ALL backdrop blur app-wide (utilities + glass classes). */
[class*="backdrop-blur"],
.gg-glass, .gg-toolbar, .gg-toolbar::before, .gg-tray, .gg-tray--inset,
.gg-menu, .gg-menu--dense, .gg-sheet, .settings-sheet, .trip-chat-sheet,
.gg-chip, .gg-chip-sm, .gg-banner, .gg-weather-pill, .gg-node, .gg-icon-btn,
.glass-segment, .glass-chip, .gg-stop-tile, .gg-tooltip, .gg-event-row, .card,
.ios-group, .ios-field, .confirm-sheet, .confirm-scrim, .gg-scrim,
.trip-chat-input-shell, .trip-chat-composer, .trip-chat-bubble-assistant {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* Remove the specular pseudo-rims + sliding lens indicators. */
.gg-rim::before, .gg-rim::after,
.glass-segment__lens, .glass-segment__lens-wrap, .gg-node-lens,
.glass-segment--inline .glass-segment__item[data-active="true"]::before,
.glass-chip[data-active="true"]::before,
.gg-toolbar::before {
  display: none !important;
}

/* Generic flat panels — white fill, black hairline, sharp corners. */
.gg-glass, .gg-tray, .gg-tray--inset, .gg-banner, .gg-weather-pill,
.gg-event-row, .gg-menu, .gg-menu--dense {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
}
.gg-menu, .gg-menu--dense { box-shadow: var(--shadow-md) !important; }

/* Top app bar: flat cream with one crisp black baseline rule. */
.gg-toolbar {
  background: var(--canvas) !important;
  background-image: none !important;
  border-bottom: 1.5px solid var(--line) !important;
  box-shadow: none !important;
}
.gg-toolbar-scrolled { box-shadow: none !important; }

/* Ink-filled primary buttons — white hard offset stack behind black fill. */
.btn-ink {
  background: var(--pill);
  color: var(--canvas);
  border: 1.5px solid var(--line);
  /* Flat at rest; the hard offset shadow only appears on hover (below). */
  box-shadow: none;
  transition: box-shadow 0.14s ease, background 0.14s ease, transform 0.1s var(--ease-ios);
}
.btn-ink:hover {
  background: var(--pill-hover);
  box-shadow: var(--shadow-offset-ink);
  transform: translate(-1px, -1px);
}
.btn-ink:active {
  transform: translate(1px, 1px);
  box-shadow: var(--shadow-offset-ink-sm);
}
/* Dark: the pill background is deep blue (not near-black), and var(--canvas)
   flips to pure black — which is unreadable on it. Keep the label light, and
   drop the white hard-offset shadow (it reads as a weird floating block in
   dark) so the button sits flat like the others. */
html.dark .btn-ink { color: #fffefb; box-shadow: none; }
html.dark .btn-ink:hover { box-shadow: none; transform: none; }
html.dark .btn-ink:active { box-shadow: none; }

/* Cards: flat white panels, black hairline, hard offset shadow on hover. */
.card {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
}
.card-hover { transition: box-shadow 0.14s ease, border-color 0.14s ease, transform 0.14s var(--ease-ios); }
.card-hover:hover {
  transform: none !important;
  box-shadow: var(--shadow-offset) !important;
  border-color: var(--line) !important;
}

/* Timeline stop cards: flat white panel, square, black hairline. */
.timeline-rail .stop-card-interactive.tl-content {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
}
.timeline-rail .stop-card-interactive.tl-content:hover {
  box-shadow: var(--shadow-offset) !important;
}
.stop-card-interactive, .stop-card-interactive:hover { background-color: var(--surface) !important; }
/* Kill the soft thumbnail colour wash/frost (it was a glassy gradient). */
.stop-ambient, .stop-frost { display: none !important; }

/* Segmented controls (view toggle / mode tabs): bordered, flat, black-active. */
.glass-segment {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
  padding: 2px !important;
}
.glass-segment__item { border-radius: 1px !important; color: var(--ink) !important; font-weight: 600 !important; }
/* Flat solid-ink selected pill — no glass border, lit top edge, drop shadow, or
   blur (the --inline variant kept all four). */
.glass-segment__item[data-active="true"],
.glass-segment__item[aria-current="page"],
.glass-segment--inline .glass-segment__item[data-active="true"],
.glass-segment--inline .glass-segment__item[aria-current="page"] {
  background: var(--ink) !important;
  background-image: none !important;
  color: var(--canvas) !important;
  border: none !important;
  box-shadow: none !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}
.glass-segment__item:hover:not([data-active="true"]):not([aria-current="page"]) {
  background: rgba(20, 18, 15, 0.06) !important;
}

.transport-mode-segment,
.transport-mode-segment .glass-segment__item {
  border-radius: 2px !important;
}
.transport-mode-segment .glass-segment__item[data-active="true"] {
  overflow: hidden;
}
.transport-mode-segment .glass-segment__item[data-active="true"]::before,
.transport-mode-segment .glass-segment__item[data-active="true"]::after {
  display: none !important;
}

/* Chips / pills: rectangular, bordered. */
.gg-chip, .gg-chip-sm, .glass-chip {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
  color: var(--ink) !important;
}
.glass-chip[data-active="true"] { background: var(--ink) !important; color: var(--canvas) !important; }

/* Icon buttons. */
.gg-icon-btn { border-radius: var(--radius-sm) !important; box-shadow: none !important; }
.gg-icon-btn:hover { background-color: rgba(20, 18, 15, 0.07) !important; }

/* Transport node bubble: flat white disc with a black ring. */
.gg-node {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  box-shadow: none !important;
}

/* Sheets / modals + scrim. */
.gg-sheet, .settings-sheet, .trip-chat-sheet, .confirm-sheet {
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: var(--shadow-md) !important;
}
/* Floating modals/dialogs sit on the raised surface; the full-screen side
   sheets (Settings + Claus chat) take the page background (pure black in dark)
   so their inner cards/rows/bubbles read with contrast. */
.gg-sheet, .confirm-sheet { background: var(--surface) !important; }
.settings-sheet, .trip-chat-sheet { background: var(--side-sheet-fill) !important; }
.gg-scrim, .confirm-scrim { background: rgba(20, 18, 15, 0.42) !important; }

/* Tooltips: solid black, no soft shadow. */
.gg-tooltip {
  background: var(--ink) !important;
  background-image: none !important;
  color: var(--canvas) !important;
  border-radius: var(--radius-sm) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  box-shadow: none !important;
}

/* iOS grouped day-plan slots → flat bordered list (no rounded group). */
.ios-group {
  background: var(--surface) !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
}
.ios-group > :first-child, .ios-group > :last-child { border-radius: 0 !important; }
.ios-field {
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
}

/* Buttons. */
.btn { border-radius: var(--radius-sm); font-weight: 600; }
.btn-outline {
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
}
.btn-outline:hover { background: var(--ink) !important; color: var(--canvas) !important; border-color: var(--line) !important; }
.badge { border-radius: var(--radius-sm); }

/* Tailwind soft-shadow utilities → editorial. */
.shadow-soft { box-shadow: var(--shadow-sm) !important; }
.shadow-lift { box-shadow: var(--shadow-md) !important; }
.shadow-drag { box-shadow: var(--shadow-md) !important; }

/* Loading spinner → poster pixels keep their own fills. */
.sync-load-spinner {
  color: transparent !important;
}

/* Crisp focus ring. */
:focus-visible { outline: 2.5px solid var(--accent) !important; outline-offset: 2px; border-radius: 0 !important; }

/* Trip-chat surfaces → flat editorial (kill tint, blur, gradients, soft rims). */
.trip-chat-suggestion {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
  color: var(--ink) !important;
}
.trip-chat-suggestion:hover {
  background: var(--surface) !important;
  border-color: var(--line) !important;
  color: var(--ink) !important;
  box-shadow: var(--shadow-offset-sm) !important;
}
/* "New chat" pill → bordered black-on-cream chip. */
.trip-chat-new {
  background: var(--surface) !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm) !important;
  color: var(--ink) !important;
}
.trip-chat-new:hover:not(:disabled) {
  background: var(--ink) !important;
  color: var(--canvas) !important;
}
/* User bubble: solid ink block (no gradient). */
.trip-chat-bubble-user {
  background: var(--ink) !important;
  background-image: none !important;
  color: var(--canvas) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
}
/* Assistant bubble: flat surface, black hairline. */
.trip-chat-bubble-assistant {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
}
/* Composer bar: flat canvas with one crisp top rule. */
.trip-chat-composer {
  background: var(--canvas) !important;
  background-image: none !important;
  border-top: 1.5px solid var(--line) !important;
}
/* Header / grab bar: same flat canvas beige as the composer footer. */
.trip-chat-header {
  background: var(--canvas) !important;
  background-image: none !important;
}
/* Input shell: rectangular bordered field. */
.trip-chat-input-shell,
.trip-chat-input-shell--multiline {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
}
.trip-chat-input-shell:focus-within {
  border-color: var(--accent) !important;
  box-shadow: none !important;
}
/* Send button: solid ink, square. */
.trip-chat-send {
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
}
.trip-chat-attach,
.trip-chat-web-toggle {
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
}
html.dark .trip-chat-bubble-user { color: var(--canvas) !important; }

/* Welcome / morning-brief / onboarding floating panel → flat editorial card. */
.welcome-modal-panel {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  box-shadow: var(--shadow-md) !important;
}
.welcome-modal-scrim { background: rgba(20, 18, 15, 0.42) !important; }
/* Primary CTA on those panels → solid accent block. */
.welcome-cta {
  background: var(--accent) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  box-shadow: var(--shadow-offset-ink) !important;
  color: #fffefb !important;
  text-shadow: none !important;
}
.welcome-cta:hover {
  background: var(--accent-deep) !important;
  background-image: none !important;
  box-shadow: var(--shadow-offset-ink) !important;
  transform: translate(-1px, -1px);
}

/* ----------------------------------------------------------------------------
   Punch-list polish: planned-card fill, flat map labels, de-glassed expanded
   city header, squared thumbnails, clean chat focus, dot-halftone scrims.
   ------------------------------------------------------------------------- */

/* Placed recommendation cards: the .card override forces a white fill, so
   re-assert the light-gray "planned" fill (and keep the inset ring subtle). */
.rec-planned,
.rec-planned.card-hover:hover {
  background: var(--planned-bg) !important;
  background-image: none !important;
}

/* Map labels (Leaflet tooltips) → flat editorial chips, no glass/blur/pill. */
.leaflet-tooltip {
  border-radius: var(--radius-sm) !important;
  background: var(--surface) !important;
  background-image: none !important;
  border: 1.5px solid var(--line) !important;
  color: var(--ink) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  box-shadow: 2px 2px 0 0 var(--line) !important;
}

/* Expanded city header pinned under the app bar → read as ONE merged bar with
   the Claus header: the SAME flat canvas fill (not the card's surface), a single
   shelf shadow beneath, and no dividing line or side hairlines. */
.stop-sticky-head { transition: box-shadow 0.2s ease, background-color 0.2s ease !important; }
.stop-sticky-head.is-stuck {
  background-color: var(--canvas) !important;
  box-shadow: 0 3px 0 0 var(--line) !important;
}
.stop-sticky-head.is-stuck::before { display: none !important; }
/* Full-bleed expanded card bleeds to the viewport edges; keep the top rule while
   dropping side/bottom lines so the in-page city card still has a clear start,
   and the stuck header can still read like the app bar. */
.stop-fullbleed {
  border-style: solid !important;
  border-color: var(--line) !important;
  border-width: 1.5px 0 0 !important;
}

/* Timeline thumbnail: match its left corners to the card's INNER radius
   (outer var(--radius) minus the 1.5px border). Using the full outer radius
   over-rounded the image so the white card background showed through at the
   corner — the 'white notch'. */
.timeline-rail .stop-card-interactive.tl-content .timeline-thumb {
  border-top-left-radius: calc(var(--radius) - 1.5px) !important;
  border-bottom-left-radius: calc(var(--radius) - 1.5px) !important;
}

/* Chat composer: the global focus outline doubled the shell's focus border into
   a "ring". Let the shell border alone signal focus. */
.trip-chat-input:focus,
.trip-chat-input:focus-visible { outline: none !important; }
.trip-chat-input-shell:focus-within {
  border-color: var(--accent) !important;
  box-shadow: none !important;
}
/* Send button matches the single-line composer height exactly. */
.trip-chat-send {
  height: var(--trip-chat-pill-h, 2.75rem) !important;
  width: var(--trip-chat-pill-h, 2.75rem) !important;
  align-self: center !important;
}

/* Dark-dot halftone behind every modal scrim. NOT a flat veil — the darkening
   comes from a fine grid of near-black dots over a barely-there tint, so content
   reads through the gaps and the backdrop feels screened, like print halftone. */
.gg-scrim, .confirm-scrim, .welcome-modal-scrim {
  background-color: rgba(20, 18, 15, 0.14) !important;
  background-image: radial-gradient(rgba(10, 9, 7, 0.92) 1.2px, transparent 1.5px) !important;
  background-size: 4px 4px !important;
  background-position: 0 0 !important;
}
/* On the pure-black dark theme the dot halftone read as a weird screen-door
   moiré over the page. Drop the pattern and use a clean dim veil instead. */
html.dark .gg-scrim, html.dark .confirm-scrim, html.dark .welcome-modal-scrim {
  background-color: rgba(0, 0, 0, 0.55) !important;
  background-image: none !important;
}

/* Stop-ribbon tiles: flat (were liquid glass w/ soft shadow). Borderless — the
   tray's outer border + each tile's colored left edge give the structure, so the
   strip reads as one clean poster rail, not boxes-in-a-box. Hover = a hint of sky. */
.gg-stop-tile {
  background: transparent !important;
  background-image: none !important;
  border: none !important;
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
  transition: background-color 0.14s ease !important;
}
.gg-stop-tile:hover { background: var(--accent-soft) !important; transform: none !important; }
.gg-stop-tile:active { transform: none !important; }
/* Ribbon tray: a clean flat rail, no inset trough or drop shadow. Match the
   corner radius to the tiles inside (2px) so the rounded border + overflow clip
   don't leave a visible corner arc against the squarer tiles. */
.gg-tray, .gg-tray--inset {
  background: var(--surface) !important;
  box-shadow: none !important;
  border-radius: var(--radius-sm) !important;
}

/* Timeline spine transport mark: no circle, no fill — just the glyph in a small
   canvas-masked gap that breaks the rail line above and below it. */
.timeline-rail .tl-spine .tl-spine-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  background: var(--canvas);
  padding: 0.34rem 0.24rem;
  line-height: 0;
  border-radius: 0;
}
.timeline-rail .tl-spine .tl-spine-mark:hover { color: var(--accent); }

/* Leaflet zoom + controls → flat editorial: square, black hairline, ink hover. */
.leaflet-bar {
  border: 1.5px solid var(--line) !important;
  border-radius: var(--radius-sm) !important;
  box-shadow: none !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  overflow: hidden;
}
.leaflet-bar a {
  background: var(--surface) !important;
  color: var(--ink) !important;
  border-bottom: 1.5px solid var(--line) !important;
  font-weight: 700 !important;
}
.leaflet-bar a:last-child { border-bottom: none !important; }
.leaflet-bar a:hover { background: var(--ink) !important; color: var(--canvas) !important; }
.leaflet-control-attribution {
  background: rgba(244, 243, 238, 0.86) !important;
  border-radius: 0 !important;
  border-top: 1.5px solid var(--line) !important;
  border-left: 1.5px solid var(--line) !important;
}
html.dark .leaflet-control-attribution {
  background: rgba(23, 21, 15, 0.86) !important;
}

/* Calendar bands: flat color blocks — drop the liquid-glass gloss (lit top edge
   + gathered bottom shade) that read as a gradient inside each block. */
.cal-band-cell::after { box-shadow: none !important; }

/* ───────────────────────── Map Story Mode (cinematic globe) ──────────────── */
.story-overlay {
  position: fixed;
  inset: 0;
  z-index: 3000;
  background:
    radial-gradient(120% 90% at 50% -10%, #16213f 0%, #0a1024 55%, #05070f 100%);
  overflow: hidden;
  animation: storyFadeIn 0.4s ease both;
}
@keyframes storyFadeIn { from { opacity: 0; } to { opacity: 1; } }
/* Faint starfield over the deep-space backdrop (under the globe canvas). */
.story-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1px 1px at 20% 30%, rgba(255, 255, 255, 0.7), transparent),
    radial-gradient(1px 1px at 70% 20%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(1.5px 1.5px at 40% 70%, rgba(255, 255, 255, 0.55), transparent),
    radial-gradient(1px 1px at 85% 65%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(1px 1px at 55% 45%, rgba(255, 255, 255, 0.4), transparent),
    radial-gradient(1.5px 1.5px at 12% 80%, rgba(255, 255, 255, 0.5), transparent);
  opacity: 0.7;
  pointer-events: none;
}
/* Higher specificity than MapLibre's own `.maplibregl-map { position: relative }`
   (its CSS is injected AFTER ours, so an equal-specificity rule would lose and
   the container would collapse to height:0, rendering into a 0-size viewport). */
.story-overlay .story-map { position: absolute; inset: 0; height: 100%; width: 100%; }
.story-map canvas { outline: none; }
.story-btn, .story-launch { cursor: pointer; }
.story-launch {
  position: absolute;
  z-index: 40;
  right: 1.55rem;
  top: 1.55rem;
  display: grid;
  place-items: center;
  width: 3.35rem;
  height: 3.35rem;
  border-radius: 999px;
  background: #f4f3ee;
  color: #1a1714;
  border: 1.5px solid #1a1714;
  box-shadow: 2px 2px 0 0 rgba(26, 23, 20, 0.32), 0 8px 18px rgba(26, 23, 20, 0.16);
  transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.story-launch:hover {
  background: #fffaf0;
  transform: translateY(-1px);
  box-shadow: 2px 3px 0 0 rgba(26, 23, 20, 0.32), 0 10px 20px rgba(26, 23, 20, 0.18);
}
.story-launch:active {
  transform: translateY(1px);
  box-shadow: 1px 1px 0 0 rgba(26, 23, 20, 0.32), 0 5px 12px rgba(26, 23, 20, 0.14);
}
.story-launch svg {
  width: 1.8rem;
  height: 1.8rem;
  display: block;
  fill: currentColor;
  /* A triangle's visual mass sits left of its bounding box; nudge right so the
     play glyph looks optically centred in the round button. */
  transform: translateX(2px);
}

/* Stop markers — anchored at a ZERO-SIZE point so the dot's grow/flag toggle
   can never shift the anchor (no jitter); children are absolutely positioned
   around the point. Labels stay on so the end overview is fully labelled. */
.story-marker { position: relative; width: 0; height: 0; pointer-events: none; will-change: transform; }
.story-marker-disc {
  position: absolute; left: 0; top: 0;
  width: 20px; height: 20px; border-radius: 999px;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.5);
  transition: width 0.12s ease, height 0.12s ease;
  overflow: hidden;
}
.story-marker-disc svg { display: block; width: 100%; height: 100%; }
.story-marker-disc--plain { border: 1.5px solid #fff; }
.story-marker-label {
  position: absolute; left: 0; top: 13px;
  transform: translateX(-50%);
  font-family: 'Outfit', system-ui, sans-serif; font-weight: 700; font-size: 11px;
  color: #fff; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 2px rgba(0, 0, 0, 0.85);
  white-space: nowrap;
}
.story-marker.is-active .story-marker-disc { width: 28px; height: 28px; }
.story-marker.is-active .story-marker-label { top: 17px; font-weight: 800; font-size: 12px; }

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

/* ─────────────── Trip flyover (satellite + 3D terrain, Danish-poster UI) ───── */
/* Flat dark backdrop while tiles stream in; satellite covers it once loaded. */
.flyover-overlay { background: #0b1722; }
.flyover-overlay::before { display: none; }

/* Light top/bottom scrims so the cream poster chips read over bright imagery. */
.flyover-scrim {
  position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background:
    linear-gradient(180deg, rgba(4, 10, 18, 0.4) 0%, rgba(4, 10, 18, 0) 22%),
    linear-gradient(0deg, rgba(4, 10, 18, 0.42) 0%, rgba(4, 10, 18, 0) 22%);
}

/* Editorial control surfaces: cream, hairline ink border, hard offset shadow. */
.flyover-close {
  position: absolute; z-index: 6; cursor: pointer;
  top: max(1rem, calc(env(safe-area-inset-top) + 0.4rem)); right: 1rem;
  display: inline-grid; place-items: center;
  width: 2.5rem; height: 2.5rem; border-radius: 999px;
  color: #1a1714; background: #f4f3ee;
  border: 1.5px solid #1a1714;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  transition: transform 0.12s ease, background 0.12s ease;
}
.flyover-close:hover { transform: scale(1.06); background: #fffaf0; }
.flyover-close:active { transform: scale(0.96); }

/* Loading / error states. Opaque so the pre-warm camera stepping stays hidden. */
.flyover-center {
  position: absolute; inset: 0; z-index: 7;
  background: #0b1722;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.9rem; color: rgba(244, 243, 238, 0.92); font-size: 0.85rem; text-align: center; padding: 2rem;
}
.flyover-center--err p { max-width: 22rem; line-height: 1.5; }
.flyover-loading-card {
  width: min(420px, calc(100vw - 28px));
  display: grid;
  gap: 30px;
  justify-items: stretch;
}
.flyover-loading-term {
  min-width: 0;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
  color: rgba(244, 243, 238, 0.78);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.flyover-loading-meta {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: #f4f3ee;
  text-align: center;
  white-space: nowrap;
}
.flyover-progress {
  width: 100%; height: 6px; border-radius: 999px;
  margin: 0;
  background: rgba(244, 243, 238, 0.16); overflow: hidden;
  border: 1px solid rgba(244, 243, 238, 0.22);
}
.flyover-progress-fill {
  width: 0%; height: 100%;
  background: var(--poster-green, #5fa86a);
  border-radius: 999px;
  transition: width 0.24s ease;
}
.flyover-errbtn {
  cursor: pointer; padding: 0.5rem 1rem; border-radius: 3px;
  background: #f4f3ee; color: #1a1714; font-weight: 700; font-size: 0.8rem;
  border: 1.5px solid #1a1714; box-shadow: 3px 3px 0 0 rgba(26, 23, 20, 0.25);
}

/* Bottom dock — one cream editorial panel holding the trip context AND the
   transport controls (no separate top chip). */
.flyover-dock {
  position: absolute; z-index: 6;
  left: 50%; transform: translateX(-50%);
  bottom: max(1rem, calc(env(safe-area-inset-bottom) + 0.5rem));
  width: min(34rem, calc(100vw - 1.6rem));
  display: flex; flex-direction: column; gap: 0.55rem;
  padding: 0.7rem 0.85rem 0.65rem;
  border-radius: 6px;
  background: #f4f3ee;
  border: 1.5px solid #1a1714;
  box-shadow: 3px 3px 0 0 rgba(26, 23, 20, 0.25);
  animation: flyoverDockIn 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes flyoverDockIn { from { opacity: 0; transform: translate(-50%, 14px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* Trip context block at the top of the dock. */
.flyover-meta { min-width: 0; padding: 0 0.1rem; }
.flyover-eyebrow {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0; text-transform: none;
  color: rgba(26, 23, 20, 0.5); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.flyover-metarow {
  display: flex; align-items: baseline; gap: 0.6rem; margin-top: 0.08rem;
}
.flyover-title {
  flex: 1 1 auto; min-width: 0;
  font-family: 'Archivo', 'Outfit', system-ui, sans-serif;
  font-weight: 800; font-size: 1.18rem; line-height: 1.1; letter-spacing: -0.01em;
  color: #1a1714;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.flyover-sub {
  flex: none; font-family: 'Outfit', system-ui, sans-serif;
  font-weight: 600; font-size: 0.75rem; color: rgba(26, 23, 20, 0.6);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Controls row: play · scrubber · speed. */
.flyover-controls { display: flex; align-items: center; gap: 0.7rem; }
.flyover-play {
  flex: none; cursor: pointer;
  display: inline-grid; place-items: center;
  width: 2.4rem; height: 2.4rem; border-radius: 999px;
  background: #f4f3ee; color: #1a1714; border: 1.5px solid #1a1714;
  box-shadow: 2px 2px 0 0 rgba(26, 23, 20, 0.24);
  transition: transform 0.12s ease;
}
.flyover-play:hover { transform: scale(1.05); }
.flyover-play:active { transform: scale(0.95); }

.flyover-scrub {
  position: relative; flex: 1 1 auto; height: 1.4rem;
  display: flex; align-items: center; cursor: pointer; touch-action: none;
}
.flyover-scrub-track {
  position: absolute; left: 0; right: 0; height: 5px; border-radius: 999px;
  background: rgba(26, 23, 20, 0.18);
}
.flyover-scrub-fill {
  position: absolute; left: 0; right: 0; height: 5px; border-radius: 999px;
  background: var(--poster-green, #5fa86a); transform-origin: left center; transform: scaleX(0); will-change: transform;
}
.flyover-scrub-thumb {
  position: absolute; top: 50%; left: 0;
  width: 15px; height: 15px; border-radius: 999px;
  background: #f4f3ee; border: 2px solid #1a1714;
  box-shadow: 2px 2px 0 0 rgba(26, 23, 20, 0.28);
  transform: translate(-50%, -50%); will-change: left;
}
.flyover-speed {
  flex: none; cursor: pointer; min-width: 2.6rem;
  padding: 0.3rem 0.5rem; border-radius: 4px;
  font-family: 'Outfit', system-ui, sans-serif; font-weight: 700; font-size: 0.82rem;
  color: #1a1714; background: #f4f3ee; border: 1.5px solid #1a1714;
  font-variant-numeric: tabular-nums;
  transition: transform 0.12s ease;
}
.flyover-speed:hover { transform: translateY(-1px); }

/* Current-position dot — a liftable disc (the glyph) + a ground shadow, so a
   flying leg visibly rises off the terrain. MapLibre owns the root transform. */
.flyover-mover-root { position: relative; width: 0; height: 0; }
.flyover-mover-lift {
  position: absolute; left: 0; top: 0;
  display: block; transition: transform 0.1s linear; will-change: transform;
}
.flyover-mover {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 999px;
  background: #f4f3ee; color: #1a1714;
  border: 2px solid #1a1714;
  box-shadow: 2px 2px 0 0 rgba(26, 23, 20, 0.28), 0 3px 9px rgba(0, 0, 0, 0.32);
  transform: translate(-50%, -50%);
}
.flyover-mover-glyph { display: grid; place-items: center; width: 16px; height: 16px; }
.flyover-mover-glyph svg { width: 16px; height: 16px; display: block; }
.flyover-mover-shadow {
  position: absolute; left: 50%; top: 50%;
  width: 16px; height: 6px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.4); filter: blur(1px);
  transform: translate(-50%, -50%); opacity: 0;
}
.flyover-attrib {
  position: absolute; z-index: 4; right: 0.6rem;
  bottom: max(0.35rem, env(safe-area-inset-bottom));
  max-width: 60vw; text-align: right;
  font-size: 0.56rem; line-height: 1.2; color: rgba(255, 255, 255, 0.62);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); pointer-events: none;
}

@media (max-width: 380px) {
  .flyover-title { font-size: 1.05rem; }
}
@media (prefers-reduced-motion: reduce) {
  .flyover-dock { animation: none; }
}

/* ═══ Editorial + riso print layer ═══════════════════════════════════════
   A more graphic, handcrafted pass: colored hairlines under section labels,
   an always-on hard-offset "print" shadow on the hero cards, and a two-color
   ink/paper duotone on the city photos (the riso overprint canvas). */

/* Editorial: a short ochre rule under every section label. */
.section-label { position: relative; padding-bottom: 0.36rem; }
.section-label::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 1.5rem;
  height: 2px;
  border-radius: 1px;
  background: var(--rule-accent, #c0842e);
}

/* Lean into the hard-offset shadow: stop cards keep a resting print shadow (not
   only on hover), so the graphic riso feel is always present. Yields to the
   card's own richer hover shadow. Adapts to dark via --line. */
.timeline-rail .stop-card-interactive.tl-content:not(:hover):not(.stop-card-animating) {
  box-shadow: 4px 4px 0 0 var(--line);
}

/* Day-plan mini-map inside the briefing report. */
.trip-chat-brief-map {
  margin-top: 0.75rem;
  padding-top: 0.65rem;
  border-top: 1px solid color-mix(in srgb, #63a852 22%, transparent);
}
.trip-chat-brief-map > * { border-radius: 4px; }
