/* ============================================================
   FONT: Inter — the same typeface notion.com uses, and the standard
   free stand-in for Apple's SF Pro. We SELF-HOST one small file
   (public/fonts/inter.woff2) so it works offline and in places where
   Google Fonts is blocked (e.g. mainland China / the Shanghai trip).
   It's a "variable" font: one file covers every weight 100–900.
   ============================================================ */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;            /* the whole weight range, from one file */
  font-display: swap;              /* show text immediately, swap in Inter when ready */
  src: url('fonts/inter.woff2') format('woff2');
}

/* Reset: remove default spacing and make sizing predictable */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* Buttons/inputs don't inherit the page font by default — make them */
button, input, select { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }

/* ============================================================
   SIMPLE GLASS — the one look every box in the app shares.
   A box is just four things:
     1) a dark SEE-THROUGH tint (you can still see the globe through it)
     2) a soft BLUR of whatever sits behind it, so text stays readable
     3) a thin light OUTLINE
     4) ROUND corners
   That's the whole material. Change the knobs below and every box
   in the app follows.
   ============================================================ */
:root {
  color-scheme: dark;                /* native date/time pickers render dark */

  --glass-bg:     rgba(10, 10, 14, 0.55);     /* the dark see-through tint */
  --glass-blur:   blur(14px);                 /* how soft the background gets behind a box */
  --glass-border: rgba(255, 255, 255, 0.14);  /* the thin light outline */
  --glass-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);  /* soft shadow so boxes float */
  --press:        rgba(255, 255, 255, 0.16);  /* any box while your finger is on it */
  --hover:        rgba(255, 255, 255, 0.09);  /* mouse-over preview, one notch lighter
                                                  than resting, one notch quieter than --press —
                                                  see it before you commit to the click */
  /* Rows that live INSIDE an already-glass card (menu rows, the ideas "add" button)
     skip the full floating --glass-shadow (double-blur reads muddy) but still want
     SOME depth so they don't feel like a flatter, cheaper material than everything
     else in the app — a quiet fraction of the real thing. */
  --flat-shadow:  0 2px 10px rgba(0, 0, 0, 0.28);

  /* corner roundness */
  --r-pill: 999px;
  --r-card: 24px;
  --r-chip: 14px;

  /* Motion — snappy, eased, never bouncy */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-snap: cubic-bezier(0.32, 0.72, 0, 1);
  /* True ease-in-ease-out (slow start, fast middle, slow end) — DESKTOP day-turn only
     (nav arrows + trackpad/mouse drag). --ease-snap above starts almost instantly
     (no ease-in), which is right for a finger already moving on a touchscreen but
     reads as an abrupt jolt on desktop, where the "swipe" is a released drag with
     no ongoing finger contact. Must match DESKTOP_DAY_TURN_MS in app.js. */
  --ease-in-out-snap: cubic-bezier(0.45, 0, 0.2, 1);
  --dur-view: 0.28s;
  --dur-item: 0.24s;
  --dur-tap: 0.16s;
  /* Was 0.11s — enough for the old ~22px fade, too short for a full pane-width
     slide (see the day-turn JS: a live drag now moves the ENTIRE day, not a
     sliver of it). Must match DAY_TURN_MS in app.js. */
  --dur-day-turn: 0.22s;
  /* Desktop-only day turn: faster than the touch version above — a mouse/trackpad
     release has no finger still touching the glass to "carry" the motion, so it
     needs to snap over noticeably less time to still read as quick. */
  --dur-day-turn-desktop: 0.15s;
}

/* Whole-word text — wrap at spaces; only break a word if it alone is wider
   than the box (long URLs / codes). Never force mid-word chops for normal text. */
.wrap-words {
  white-space: pre-line;
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;
  max-width: 100%;
  min-width: 0;
}

html {
  height: 100%;
  background: #000;                 /* pitch black — the base layer of the page */
  -webkit-tap-highlight-color: transparent;  /* no grey flash on iPhone taps */
  overflow: hidden;                 /* no page scroll — keyboard must not pan the shell */
  overflow-x: hidden;               /* no sideways page scroll — vertical only */
  overscroll-behavior: none;        /* kill rubber-band / keyboard-driven overscroll */
  /* iOS Safari (and Chrome-on-iOS, same WebKit engine underneath) silently
     boosts font sizes in narrow text columns to help readability — a real-
     device-only behavior Chrome DevTools' desktop device simulation never
     reproduces, since that's plain Blink with no text-autosizer at all. That
     mismatch is exactly why fonts look right in DevTools but oversized on a
     real iPhone. Pinning this to 100% turns the boost off so our own sizes
     are what actually render. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body {
  height: 100%;
  color: #fff;
  /* Inter first; fall back to Apple's own SF Pro if the file ever fails to load */
  font-family: 'Inter', -apple-system, system-ui, sans-serif;
  letter-spacing: -0.011em;         /* the slightly tight tracking that reads as "Apple" */
  -webkit-font-smoothing: antialiased;       /* crisp, thin text on Mac/iPhone */
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;                 /* no scrolling — it fills the screen like an app */
  overflow-x: hidden;
  overscroll-behavior: none;
  /* Behave like an app, not a web page: long-pressing never highlights text or
     pops up the iOS copy/lookup bubble. We turn selection back ON for inputs below. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/* The only place you SHOULD be able to select/edit text is a form field */
input { -webkit-user-select: text; user-select: text; }

/* The starfield canvas sits behind the globe map, clipped to the globe half only
   (#globe has overflow:hidden) — it used to be position:fixed across the WHOLE
   viewport, which on the stacked mobile layout meant it kept redrawing, every
   frame, forever, an entire invisible copy of itself under the opaque calendar
   half. That's real, constant GPU fill-rate cost for zero visible benefit — a
   big part of why animations felt clunky on a real phone despite looking fine
   in a desktop browser (desktop GPUs don't notice; mobile GPUs do). */
#stars {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;                       /* behind #map, which paints on top */
  display: block;
}

/* The app fills the screen and stacks the two halves vertically.
   FIXED to the layout viewport (inset: 0) — NOT height: 100dvh.
   Why: 100dvh shrinks when the mobile keyboard opens, so the whole
   globe/calendar flex layout compressed and "jumped up" with the keyboard.
   A fixed shell stays put; the keyboard overlays on top (see also the
   viewport meta interactive-widget=overlays-content). */
#app {
  position: fixed;
  inset: 0;
  width: 100%;
  max-width: 100%;
  height: auto;                     /* height comes from inset: 0, not dvh */
  display: flex;
  flex-direction: column;
  z-index: 1;                       /* ...and the app sits above the stars */
  overflow: hidden;
  overflow-x: hidden;               /* vertical layout only — never pan sideways */
}

/* Two fixed halves. Portrait = stacked 50/50 (globe top, calendar bottom).
   Landscape = side-by-side 50/50 width (globe left, calendar right).
   Calendar always paints ABOVE the globe (higher z-index) so the map canvas
   can never cover the back button or crop the calendar header at the seam. */
#globe, #calendar {
  flex: 0 0 50%;
  min-height: 0;                    /* lets the halves shrink correctly */
  min-width: 0;                     /* flex children can shrink so text wraps, not scroll-x */
  max-width: 100%;
  overflow-x: hidden;
  position: relative;
}
#globe {
  z-index: 1;
  overflow: hidden;                 /* hard-clip map/menus to this half only */
}
#calendar {
  z-index: 2;                       /* never sit under the globe at the split */
  overflow-x: hidden;
  overflow-y: hidden;               /* scroll lives inside day/month views, not here */
}

/* Portrait (phone/tablet upright): exact half-and-half height. */
@media (orientation: portrait) {
  #app { flex-direction: column; }
  #globe {
    flex: 0 0 50%;
    max-height: 50%;
    height: 50%;
  }
  #calendar {
    flex: 0 0 50%;
    max-height: 50%;
    height: 50%;
  }
}

/* The app splits LEFT/RIGHT (globe left, calendar right) whenever the screen is
   WIDER than it is tall (landscape). When it's TALLER than wide (portrait) the two
   halves stay STACKED (globe on top, calendar below). One simple rule, no gaps:
   orientation: landscape = wider-than-tall, portrait = taller-than-wide. */
@media (orientation: landscape) {
  #app { flex-direction: row; }
  #globe {
    flex: 1 1 50%;                   /* left half — can grow if calendar is fixed 50% */
    max-height: none;
    height: auto;
  }
  #calendar {
    flex: 0 0 50%;                   /* right half — fixed half of the screen width */
    max-height: none;
    height: auto;
    /* Breathe down from the top edge so the day-view back button and month
       title never sit flush against the seam (where the globe used to clip them).
       Also clear the iPhone notch on the RIGHT edge when the phone is rotated
       that way — calendar is always the right half, so only the right inset
       matters (the left notch sits over the globe, not over this panel). */
    padding-top: 20px;
    padding-right: max(16px, env(safe-area-inset-right, 0px));
  }
}

/* ============================================================
   THE GLASS RECIPE — every box in the app, in one place.
   ============================================================ */
#globe-menu-btn, #globe-logo-btn, .globe-trip-bubble, #back-to-month, #detail-back, .day-nav-bubble, .item,
#add-card, .sheet-card, #item-menu-card button, #trip-menu-card button, #chat-menu-card button,
#add-cancel, .btn-cancel, .airport-list, .nav-btn, .contact-btn, .copy-bubble, .res-tile,
#addmenu-import, .addmenu-cat, .tr-hotel-suggest, .res-accordion, .res-traveler-flat {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);   /* Safari spells it with -webkit- */
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
}

/* ============================================================
   HOVER — a mouse gets a preview of "this is pressable" before it
   commits to a click; touch never sees this (every :active press
   state above already covers touch, and pointer:fine keeps :hover
   from getting stuck after a tap on a hybrid device). One rule per
   material family, same lighten-on-hover language everywhere so a
   button in the calendar and a button in the menu read as the same
   kind of thing, not two different apps stitched together.
   ============================================================ */
@media (pointer: fine) {
  /* Floating glass (the recipe above) — the deep shadow reads as "closer" on hover. */
  #globe-menu-btn:hover, #globe-logo-btn:hover, .globe-trip-bubble:hover,
  #back-to-month:hover, #detail-back:hover, .day-nav-bubble:hover:not(:disabled),
  .item:hover, #item-menu-card button:hover, #trip-menu-card button:hover, #chat-menu-card button:hover,
  #add-cancel:hover, .btn-cancel:hover, .nav-btn:hover, .contact-btn:hover,
  .copy-bubble:hover, .res-tile-copy:hover, .res-tile-link:hover,
  .tr-hotel-suggest:hover, .airport-option:hover, .res-acc-head:hover,
  #google-signin:hover, .globe-menu-row:hover, .globe-menu-trip:hover,
  #addmenu-import:hover, #globe-menu-card .addmenu-cat:hover, #ai-menu-mic:hover {
    background: var(--hover);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
  }
  /* Flat rows nested in an already-glass card — same lighten, no shadow escalation
     (they were never meant to look like they're floating above their own card). */
  .sidetrip-add-btn:hover, .fill-ideas-btn:hover,
  #ai-history-btn:hover, #ai-new-chat-btn:hover, .ai-history-item:hover {
    background: rgba(255, 255, 255, 0.12);
  }
  /* The one solid, high-contrast primary action (Add / Save / Log in) — a hair
     dimmer on hover, the same quiet tell every white pill on the web uses. */
  #add-save:hover, .btn-save:hover, #ai-menu-submit:hover {
    background: rgba(255, 255, 255, 0.88);
  }
}

/* Shared motion curves — views, cards, list items */
@keyframes view-in {
  from { opacity: 0; transform: scale(0.97) translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes item-in {
  from { opacity: 0; transform: scale(0.96) translateY(5px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pop-in {
  from { opacity: 0; transform: scale(0.97) translateY(8px); }
  to   { opacity: 1; transform: scale(1)    translateY(0); }
}
@keyframes overlay-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Calendar panels ease in whenever they become visible */
#month-view:not([hidden]),
#day-view:not([hidden]),
#detail-view:not([hidden]) {
  animation: view-in var(--dur-view) var(--ease-out) both;
}

/* Pop-ups: dimmed backdrop + card settle */
#add-overlay:not([hidden]),
.sheet-overlay:not([hidden]),
#item-menu:not([hidden]),
#trip-menu:not([hidden]),
#chat-menu:not([hidden]) {
  animation: overlay-in 0.2s ease both;
}
#add-card, .sheet-card {
  animation: pop-in 0.26s var(--ease-out) both;
}
#item-menu-card button, #trip-menu-card button, #chat-menu-card button {
  animation: pop-in 0.24s var(--ease-out) both;
}
#item-menu-card button:nth-child(1), #trip-menu-card button:nth-child(1), #chat-menu-card button:nth-child(1) { animation-delay: 0.02s; }
#item-menu-card button:nth-child(2), #trip-menu-card button:nth-child(2), #chat-menu-card button:nth-child(2) { animation-delay: 0.05s; }
#item-menu-card button:nth-child(3), #trip-menu-card button:nth-child(3), #chat-menu-card button:nth-child(3) { animation-delay: 0.08s; }
#chat-menu-card button:nth-child(4) { animation-delay: 0.11s; }

/* Anyone who asked their phone for less motion gets stillness, full stop */
@media (prefers-reduced-motion: reduce) {
  #month-view:not([hidden]), #day-view:not([hidden]), #detail-view:not([hidden]),
  #add-overlay:not([hidden]), .sheet-overlay:not([hidden]), #item-menu:not([hidden]), #trip-menu:not([hidden]), #chat-menu:not([hidden]),
  #add-card, .sheet-card, #item-menu-card button, #trip-menu-card button, #chat-menu-card button,
  .day-flow .item, #detail-view:not([hidden]) .res-section-panel,
  #detail-view:not([hidden]) .detail-nav { animation: none; }
  #day-stage, .day-stage-ghost { transition: none !important; }
  .res-acc-body { transition: none !important; }
  .res-acc-chev { transition: none !important; }
}

/* ---------- TOP HALF: the real globe (MapLibre map) ---------- */
/* #globe overflow/z-index live with the shared #globe,#calendar rules above
   so the map can never paint over the calendar half (back button crop bug). */
#map {
  position: absolute;
  inset: 0;                        /* fill the whole top half */
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  background: transparent;         /* let the starry background show around the globe */
  overflow: hidden;
}
/* MapLibre injects a canvas sized to the container — keep it clipped to #map. */
#map .maplibregl-canvas-container,
#map .maplibregl-canvas {
  max-width: 100% !important;
  max-height: 100% !important;
}
/* MapLibre can inject its own attribution — keep it off the globe */
.maplibregl-ctrl-attrib,
.maplibregl-ctrl-logo {
  display: none !important;
}
/* A reservation's spot within the city. DEFAULT is a round black BUBBLE with a white
   ring, holding that entry's own monochrome glyph. SELECTED (you hover its card in the
   day view, or the pin itself) morphs the circle into a teardrop PINPOINT, pops the
   name underneath and lifts it above its neighbours.
   The shape is drawn by ::before rather than on the element, so the pin body can spin
   45° into a teardrop while the glyph on top of it stays upright. */
.day-loc-icon {
  display: block;
  position: relative;
  width: 32px;
  height: 32px;
  box-sizing: border-box;
  color: #ffffff;
  opacity: 0;
  transform: scale(0.3);
  /* The marker is anchored by its CENTRE, so the middle of this box is the exact spot
     on the map. A circle is happy there. A teardrop is not: its tip is what marks the
     place, so on selection the whole shape lifts by --pin-tip-lift and the tip comes to
     rest on the centre — i.e. on the spot, and on the route line running through it.
     Rotating a 32px square 45° puts its sharp corner 16 × √2 ≈ 22.63px below the middle,
     which is precisely how far it has to rise. Scaling then pivots about the tip too. */
  --pin-tip-lift: 22.63px;
  transform-origin: 50% 50%;
  transition: opacity 0.26s var(--ease-out), transform 0.26s var(--ease-out);
  cursor: pointer;
  pointer-events: auto;
}
/* The bubble itself. Rounding one corner and rotating -45° turns the circle into a
   teardrop whose point sits straight down — both are transitions of the SAME shape,
   which is what makes the change read as a morph rather than a swap. */
.day-loc-icon::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 32px;
  height: 32px;
  box-sizing: border-box;
  background: #000000;
  border: 3px solid #ffffff;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: border-radius 0.26s var(--ease-out), transform 0.26s var(--ease-out),
              background-color 0.26s var(--ease-out), border-color 0.26s var(--ease-out);
}
.day-loc-icon.in { opacity: 1; transform: scale(1); }
.day-loc-icon.sel::before,
.day-loc-icon:hover::before {
  border-radius: 50% 50% 50% 0;
  transform: translateY(calc(-1 * var(--pin-tip-lift))) rotate(-45deg);
}
/* The glyph and the idea ring ride up with the shape — otherwise the pin lifts off and
   leaves its own icon behind on the spot. */
.day-loc-icon.sel svg,
.day-loc-icon:hover svg,
.day-loc-icon.sel::after,
.day-loc-icon:hover::after {
  transform: translateY(calc(-1 * var(--pin-tip-lift)));
}
/* Viator's select bounce, matched: a quick overshoot on a springy curve. Theirs starts
   at opacity 0 because their marker is REMOUNTED on select; ours already exists, so it
   stays visible throughout — same motion, no flicker. */
.day-loc-icon.sel {
  animation: pin-bounce-select 0.36s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes pin-bounce-select {
  0% { transform: scale(0.6) translateY(6px); }
  60% { transform: scale(1.15) translateY(-4px); }
  100% { transform: scale(1) translateY(0); }
}
/* The name pill, tucked just under the pin's TIP — measured from the centre (the spot),
   not the bottom of the box, because the shape has lifted off it. */
.day-pin-label-wrap {
  position: absolute;
  top: calc(50% + 7px);
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity 0.2s ease-in-out;
}
.day-loc-icon.sel .day-pin-label-wrap,
.day-loc-icon:hover .day-pin-label-wrap { opacity: 1; }
.day-pin-label {
  display: block;
  max-width: 170px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  background: #ffffff;
  color: #000000;
  font-size: 12.5px;
  line-height: 18px;
  font-weight: 500;
  letter-spacing: 0.05px;
  text-align: center;
  border-radius: 9999px;
  padding: 2px 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35);
}
/* An idea's pin: same bubble, plus a bright dashed ring (matches trip-flag idea).
   Lives on ::after because ::before is the bubble. */
.day-loc-icon-idea::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1.5px dashed rgba(255, 255, 255, 0.92);
  pointer-events: none;
  transition: transform 0.26s var(--ease-out);
}
.day-loc-icon svg {
  display: block;
  position: absolute;
  top: 7px;
  left: 7px;
  width: 18px;
  height: 18px;
  z-index: 1;
  transition: color 0.26s var(--ease-out), filter 0.26s var(--ease-out),
              transform 0.26s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .day-loc-icon { transition: none; }
  .day-loc-icon::before { transition: none; }
  .day-loc-icon svg { transition: none; }
  .day-loc-icon-idea::after { transition: none; }
  .day-loc-icon.sel { animation: none; }
  .day-pin-label-wrap { transition: none; }
}
/* Menu button — top-right of the globe */
/* The menu bubble and the trip flag bubbles share size and shape (a circle).
   ONE knob — --globe-btn — sizes both, plus the icon and the flag-stack spacing,
   so they always match. Following Apple HIG / Material, icon buttons are FIXED
   comfortable sizes that STEP UP at breakpoints (not fluid % — that bloats on big
   screens). 44px everywhere — Apple HIG's minimum comfortable tap target; the old
   37px phone size was below spec and hard to hit one-handed. */
:root { --globe-btn: 44px; }
#globe-menu-btn, #globe-logo-btn, .globe-trip-bubble {
  width: var(--globe-btn);
  height: var(--globe-btn);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease, opacity 0.2s ease;
}
/* 44px holds across iPad and desktop too — comfortable for both touch and mouse,
   and keeps the button + flag bubbles a consistent size everywhere. */
#globe-menu-btn {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 16px);
  right: 16px;            /* 16px matches the calendar's edge margin — one rhythm */
  z-index: 6;
}
#globe-menu-btn:active, .globe-trip-bubble:active { transform: scale(0.93); background: var(--press); }
/* Icon scales with the button (55% ≈ 24px inside a 44px circle). */
#globe-menu-btn svg { display: block; width: 55%; height: 55%; }
#globe-menu-btn[aria-expanded="true"] { opacity: 0.92; }

/* Brand button — the whale logo, in the corner OPPOSITE the menu button (same
   mirrored placement the email-reading spinner uses): portrait = top-left,
   landscape/desktop = top-right. Visual only for now — no behavior wired up. */
#globe-logo-btn {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 16px);
  left: 16px;
  right: auto;
  z-index: 6;
  padding: 0;
}
#globe-logo-btn img { display: block; width: 55%; height: 55%; object-fit: contain; }
#globe-logo-btn:active { transform: scale(0.93); background: var(--press); }
/* Tucked away while ITS OWN menu is open — but stays put (and clickable) while
   the OTHER menu is open, so you can switch straight from one to the other. */
#globe.ai-open #globe-logo-btn { display: none; }

@media (orientation: landscape) {
  #globe-logo-btn { left: auto; right: 16px; }
}
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  #globe-logo-btn {
    top: calc(env(safe-area-inset-top, 0px) + 24px);
    right: 24px;
  }
}

/* "Reading your email" badge — sits in the button's own corner so it travels
   with it (portrait: top-right; landscape/desktop: top-left — see below),
   instead of a second fixed screen position to keep in sync. Big enough to
   actually notice, wearing the whale logo (clipped to a circle, same trick
   as the trip flag bubbles) with a soft glowing halo that pulses while an
   email is being read — then holds still on a checkmark once it's done. */
#email-processing-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
  animation: email-badge-glow 1.6s ease-in-out infinite;
}
#email-processing-badge[hidden] { display: none; }
.email-badge-logo {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}
/* The checkmark sits over the logo, hidden until the pass finishes. Its own
   flex-centering (rather than the parent's) keeps the pop-in scale centered. */
.email-badge-check {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8));
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
#email-processing-badge.done {
  animation: none;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.28), 0 2px 10px rgba(0, 0, 0, 0.6);
}
#email-processing-badge.done .email-badge-logo { opacity: 0.35; }
#email-processing-badge.done .email-badge-check { opacity: 1; transform: scale(1); }
@keyframes email-badge-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5), 0 2px 10px rgba(0, 0, 0, 0.6); }
  50%      { box-shadow: 0 0 0 6px rgba(255, 255, 255, 0), 0 2px 10px rgba(0, 0, 0, 0.6); }
}
@media (prefers-reduced-motion: reduce) {
  #email-processing-badge { animation: none; }
  .email-badge-check { transition: none; }
}

/* Globe-quadrant "reading a forwarded email" indicator — a standalone plane-
   around-the-globe spinner (same animation as #import-spinner, distinct class
   names so its sizing doesn't collide with that instance) that lives OUTSIDE
   the menu so it's visible whether or not the menu is open. Pinned to the
   TOP CENTER of the globe quadrant in every layout — deliberately its own
   fixed spot rather than either corner (where the menu button and the whale
   logo already live) so it reads as a distinct, unmissable "something just
   happened" signal instead of competing for space with either of them. */
.globe-processing-icon {
  position: absolute;
  z-index: 6;
  width: var(--globe-btn);
  height: var(--globe-btn);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(6, 6, 10, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  top: calc(env(safe-area-inset-top, 0px) + 16px);
  left: 50%;
  right: auto;
  transform: translateX(-50%);
}
.globe-processing-icon[hidden] { display: none; }
.gpi-globe { position: absolute; inset: 9px; color: rgba(255, 255, 255, 0.85); }
.gpi-orbit { position: absolute; inset: 0; animation: orbit 1.4s linear infinite; }
.gpi-plane {
  position: absolute;
  top: -1px;
  left: 50%;
  width: 11px;
  height: 11px;
  margin-left: -5.5px;
  transform: rotate(90deg);
  color: #fff;
}
@media (prefers-reduced-motion: reduce) {
  .gpi-orbit { animation: none; }
}
/* Desktop — match the menu button's own 24px top inset for the same layout
   rhythm; stays horizontally centered (see the base rule's transform). */
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  .globe-processing-icon {
    top: calc(env(safe-area-inset-top, 0px) + 24px);
  }
}
/* The menu's own in-menu spinner already shows this same state while open —
   no need for two at once. */
#globe.menu-open .globe-processing-icon { display: none; }

/* "Grabbing the information from your Gmail…" — the one moment the app has
   nothing to show yet, right after a first sign-in. Sits exactly where the
   processing icon sits (same inset, same glass), just as a readable sentence
   instead of a symbol: a brand-new empty app plus a spinner reads as broken,
   a sentence reads as working. Monochrome and glass, like every other non-map
   surface in the app. */
#gmail-grab-message {
  position: absolute;
  z-index: 6;
  top: calc(env(safe-area-inset-top, 0px) + 16px);
  left: 50%;
  transform: translateX(-50%);
  max-width: min(88%, 340px);
  padding: 10px 16px;
  border-radius: 999px;
  text-align: center;
  font-size: 13px;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.92);
  background: rgba(6, 6, 10, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: gmail-grab-breathe 2.4s ease-in-out infinite;
}
#gmail-grab-message[hidden] { display: none; }
/* A slow, barely-there pulse — enough to say "still working", never enough to
   distract from the globe. */
@keyframes gmail-grab-breathe {
  0%, 100% { opacity: 0.72; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #gmail-grab-message { animation: none; opacity: 1; }
}
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  #gmail-grab-message { top: calc(env(safe-area-inset-top, 0px) + 24px); }
}
/* Same rule as the icon above: don't stack it on top of an open menu. */
#globe.menu-open #gmail-grab-message { display: none; }

/* The trip flag bubbles stack vertically right under the menu bubble
   (12px top gap + the button height + 10px gap), so they follow the button size. */
#globe-trips-bubbles {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 16px + var(--globe-btn) + 10px);
  right: 16px;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}
/* In day view we hide the flag strips (the day view is one city, not the trip list). */
#globe-trips-bubbles[hidden] { display: none; }

/* Flags on the left, the trip budget bubble on the right (see
   ai-menu-budget-btn below) — one row, space-between. Hidden by JS
   (aiMenuTopRow.hidden) when neither has anything to show, so an
   untied "New trip" chat with no trips yet doesn't leave an empty gap. */
#ai-menu-top-row {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Bottom padding is deliberately generous, not just decorative: a selected
     flag scales up (see .globe-trip-bubble-selected, up to 1.16x) and an
     idea-only flag's dashed ring extends further still — together the tallest
     case grows a good ~7-8px past the plain 44px circle this row otherwise
     only reserves. overflow-x: auto (on #ai-menu-flags below) forces
     overflow-y to auto too (a CSS overflow-module rule, not a choice here),
     so anything not accounted for in padding gets clipped instead of just
     overlapping the title below — that clipped bottom edge was exactly the
     bug. 16px clears every case with room to spare, and doubles as clean
     breathing room between the flags and the title in the chat panel's
     enlarged/landscape layout. Top padding matches so the row reads as
     centered/balanced. */
  padding: 14px 16px 16px;
  flex: 0 0 auto;
}
#ai-menu-top-row[hidden] { display: none; }
/* Same flags, reused as a horizontal row above the AI chat's title (see
   renderAiMenuFlags) — one tap opens that trip's own reservation chat instead
   of the globe jump the floating stack does. Row scrolls sideways rather than
   wrapping so it stays one clean line even with several trips, and shrinks
   (min-width: 0) to leave room for the budget bubble docked at the row's
   right edge instead of pushing it off-screen.
   overflow-x: auto clips at THIS element's own padding edge (setting
   overflow-x also forces overflow-y: auto, a CSS overflow-module rule) — a
   selected flag's scale(1.16) paints past the plain circle, so without
   padding here that growth gets clipped on whichever edges are flush with
   this box, worst on the first flag (left/top/bottom all flush at once) —
   that's the dashed/cropped-looking edge you get right after tapping the
   leftmost flag. 10px padding gives every edge room to breathe; the
   matching -10px margin cancels it back out so the row's own size/position
   is unchanged. */
#ai-menu-flags {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
  min-width: 0;
  padding: 10px;
  margin: -10px;
}
#ai-menu-flags[hidden] { display: none; }
#ai-menu-flags .globe-trip-bubble { flex: 0 0 auto; }
/* The budget bubble — same glass circle as a trip flag (.globe-trip-bubble),
   docked at the row's right edge, only shown once the open chat is tied to a
   trip (see updateAiFlagSelection). Never shrinks even if the flags row
   scrolls under it. */
#ai-menu-budget-btn { flex: 0 0 auto; }
#ai-menu-budget-btn[hidden] { display: none; }
/* The destination flag fills the whole circle. --flag-size bumps the
   regular (confirmed) size a little past the plain --globe-btn (the one
   knob everything else still uses). Confirmed flags keep the plain solid
   glass edge (the shared GLASS RECIPE rule above already gives every
   .globe-trip-bubble a 1px solid border) — only .globe-trip-bubble-idea
   below swaps that for a dashed ring, so the outline marks "tentative"
   and nothing else. */
.globe-trip-bubble {
  --flag-size: calc(var(--globe-btn) * 1.08);
  width: var(--flag-size);
  height: var(--flag-size);
  overflow: hidden;
  position: relative;
}
.globe-trip-bubble img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}
/* Idea-only trip flag (paste-a-link / unconfirmed): the ONLY flag state with
   an outline — a dashed ring instead of the confirmed flag's plain solid
   glass edge, so "tentative" is the one thing that gets a border here.
   border-color is transparent (hides the plain 1px glass border every other
   glass box gets) and the dashed ring is drawn by ::after right at the
   bubble's own edge. Also a touch bigger than a confirmed flag (relative to
   the already-bumped --flag-size) so it reads as distinct at a glance too.
   The flag image is padded down to fit inside the ring instead of touching
   it. */
.globe-trip-bubble-idea {
  --idea-size: calc(var(--flag-size) * 1.1);
  width: var(--idea-size);
  height: var(--idea-size);
  border-color: transparent;
  background: rgba(8, 8, 12, 0.35);
  padding: calc(var(--idea-size) * 0.11);
}
.globe-trip-bubble-idea::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1.75px dashed rgba(255, 255, 255, 0.92);
  pointer-events: none;
}
/* The flag for whichever trip is currently framed on the globe/calendar —
   set by updateSelectedTripFlags, in step with revealedLayoverTripId — grows
   slightly so it's obvious at a glance which trip you're working on, in both
   the globe's own flag stack and its copy in the AI chat panel. Reuses the
   same transform transition .globe-trip-bubble already has (see --dur-tap/
   --ease-snap above), so it eases in whether you tapped the flag or the
   calendar scrolled to it. z-index lifts it above snug neighbors so the
   larger circle never looks clipped by the one next to it. */
.globe-trip-bubble-selected {
  transform: scale(1.22);
  z-index: 1;
}
.globe-trip-bubble-selected:active { transform: scale(1.14); }
/* Mouse hover: a smaller, PURELY TEMPORARY grow — reverts the instant the
   pointer leaves, never sticks. Skipped once a flag is already the locked
   .globe-trip-bubble-selected size (that one only shrinks a touch on
   press, see above) and gated on real hover capability so a tap on
   touchscreens can't leave a flag stuck "hovered." The permanent, bigger
   grow only ever happens via a click/selection (.globe-trip-bubble-selected,
   set by updateSelectedTripFlags) — hover never locks anything by itself. */
@media (hover: hover) {
  .globe-trip-bubble:not(.globe-trip-bubble-selected):hover {
    transform: scale(1.08);
  }
}
/* Layover sub-menu bubble — collapsed (tucked under the primary) at 75% size so
   it doesn't look like another trip. When THIS trip's month is open (.is-shown,
   via revealLayoverBubblesFor) it grows to the SAME size as the other flags and
   sits in the stack as a peer. Scroll away / pick another trip (retractAllLayover-
   Bubbles) shrinks it back to the smaller tucked size. */
.globe-trip-bubble-layover {
  width: calc(var(--globe-btn) * 0.75);
  height: calc(var(--globe-btn) * 0.75);
  margin-top: calc(-1 * (var(--globe-btn) * 0.75 + 10px));
  opacity: 0;
  transform: translateY(-40%);
  pointer-events: none;
  transition:
    margin-top 0.34s var(--ease-out),
    transform 0.34s var(--ease-out),
    opacity 0.26s ease,
    width 0.34s var(--ease-out),
    height 0.34s var(--ease-out);
}
.globe-trip-bubble-layover.is-shown {
  /* Match primary trip flags (same as .globe-trip-bubble / --flag-size). */
  width: var(--flag-size);
  height: var(--flag-size);
  margin-top: 0;
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .globe-trip-bubble-layover { transition: none; }
}

/* When the hamburger menu is open, tuck its own button, the flag bubbles,
   the whale (AI) button away — the menu owns the whole globe quadrant and
   they'd sit on top of the centered card. */
#globe.hamburger-open #globe-menu-btn,
#globe.hamburger-open #globe-trips-bubbles,
#globe.hamburger-open #globe-logo-btn {
  display: none;
}
/* Same tuck-away when the AI menu is open instead — it sits right where the
   hamburger button and flag bubbles live, so left alone they show through on
   top of/behind it. */
#globe.ai-open #globe-menu-btn,
#globe.ai-open #globe-trips-bubbles {
  display: none;
}

/* Globe-only blur while menu is open — calendar below stays live */
#globe-menu-scrim, #ai-menu-scrim {
  position: absolute;
  inset: 0;
  z-index: 4;
  background: rgba(0, 0, 0, 0.38);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  pointer-events: auto;
}
#globe-menu-scrim[hidden], #ai-menu-scrim[hidden] { display: none !important; }

/* Menu panel — when open, fills the globe half (with a small inset) and
   flex-centers the card dead in the middle of that quadrant. Using all four
   edges gives the panel a REAL height, which is required for the card's
   max-height: 100% to work (a % max-height against an auto-height parent is
   ignored, and the menu then grows past the globe/calendar seam). */
#globe-menu {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 12px + var(--globe-btn) + 6px);
  right: 12px;
  left: 12px;
  z-index: 5;
  display: flex;
  justify-content: flex-end;
  pointer-events: none;
}
#globe-menu.open {
  /* Fill the globe half (with a small inset) and flex-center the card both
     horizontally and vertically — the whole menu box sits dead in the middle
     of its quadrant on every device/orientation. */
  inset: 0;
  top: max(10px, env(safe-area-inset-top, 0px));
  right: max(10px, env(safe-area-inset-right, 0px));
  bottom: 10px;
  left: max(10px, env(safe-area-inset-left, 0px));
  display: flex;
  justify-content: center;
  align-items: center;
  width: auto;
  max-width: none;
  transform: none;
}
#globe-menu.open .globe-menu-card {
  width: min(100%, 300px);
  max-width: min(300px, 100%);
  max-height: 100%;              /* never taller than the globe quadrant inset */
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  flex: 0 1 auto;
  min-height: 0;
  margin: auto;                  /* belt-and-braces center if a child stretches */
  transform-origin: center center;
}
#globe-menu[hidden] { display: none !important; }

/* AI travel-agent menu (whale logo button). Opened card FILLS the whole
   globe quadrant (see #ai-menu.open's inset below for the thin edge margin) —
   same on phone, tablet, and desktop. It does not grow past that with the
   conversation; long chats scroll inside #ai-menu-thread. */
#ai-menu {
  position: absolute;
  z-index: 5;
  display: flex;
  pointer-events: none;
  /* closed: no footprint (hidden attribute also forces display:none) */
  inset: auto;
}
#ai-menu[hidden] { display: none !important; }
/* Open: fill the globe half (with a small inset) and flex-center the card. */
#ai-menu.open {
  inset: 0;
  top: max(10px, env(safe-area-inset-top, 0px));
  right: max(10px, env(safe-area-inset-right, 0px));
  bottom: 10px;
  left: max(10px, env(safe-area-inset-left, 0px));
  display: flex;
  justify-content: center;
  align-items: center;
  width: auto;
  max-width: none;
  height: auto;
  transform: none;
}
#ai-menu.open .globe-menu-card {
  /* Fill the quadrant exactly (the inset above is the only margin) — width
     AND height both 100%, no cap, so it reads as one solid pane instead of a
     bubble floating with gaps around it. Still never grows past that with
     the conversation; flex: 0 0 auto keeps it from stretching further. */
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  min-width: 0;
  min-height: 0;
  flex: 0 0 auto;
  margin: auto;
  transform-origin: center center;
  overflow: hidden;              /* thread/history scroll inside, not the card shell */
}

/* ONE knob — --menu-fs — sets the open menu's base text size; every child below
   sizes in em off it, so the whole menu scales together. Stepped at breakpoints
   (Apple HIG / Material: control text is fixed sizes per device, not fluid):
   iPhone 14px, iPad 16px (touch wants bigger), desktop 15px (mouse, tighter).
   BOTH width AND height must be roomy — a landscape iPhone Pro/Max is wider
   than 768px but still short, and the bigger type was one reason the menu
   could not fit the globe half. */
:root { --menu-fs: 14px; }
@media (min-width: 768px) and (min-height: 500px)  { :root { --menu-fs: 16px; } }
@media (min-width: 1024px) and (min-height: 500px) { :root { --menu-fs: 15px; } }
.globe-menu-card {
  pointer-events: auto;
  font-size: var(--menu-fs);
  width: 100%;
  max-width: 300px;
  max-height: calc(100% - 58px);
  overflow-y: auto;
  overflow-x: hidden;               /* vertical scroll only */
  padding: 10px;
  border-radius: var(--r-card);
  background: rgba(6, 6, 10, 0.88);
  -webkit-backdrop-filter: blur(36px) saturate(1.12);
  backdrop-filter: blur(36px) saturate(1.12);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.72), inset 0 1px 0 rgba(255, 255, 255, 0.07);
  transform-origin: top right;
  animation: globe-menu-in 0.28s var(--ease-out) both;
  box-sizing: border-box;
}
@keyframes globe-menu-in {
  from { opacity: 0; transform: scale(0.2) translateY(-6px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

/* (old morph junk removed - using simple .open positioning below) */

/* Mobile / touch: slightly wider card when there is room. Centering + height
   cap for the hamburger menu live on the base #globe-menu.open rule above
   (so landscape phones keep them too — earlier, landscape rules undid the
   center and the height bound, and the menu got clipped). */
@media (max-width: 819px), (pointer: coarse) {
  #globe-menu.open .globe-menu-card {
    width: min(100%, 320px);
    max-width: min(320px, 100%);
  }
  /* AI chat fills the quadrant on mobile too — base #ai-menu.open rules,
     no override needed here. */
}

/* Landscape: the menu BUTTON and trip flag bubbles stay top-LEFT (conventional
   control home). The OPEN hamburger menu itself stays centered in the globe
   half via the base #globe-menu.open rule — landscape used to re-pin it to the
   corner WITHOUT a bottom bound, so max-height failed and the card was clipped
   at the calendar seam on short phone screens. */
@media (orientation: landscape) {
  #globe-menu-btn { top: calc(env(safe-area-inset-top, 0px) + 16px); left: 16px; right: auto; }
  #globe-trips-bubbles {
    top: calc(env(safe-area-inset-top, 0px) + 16px + var(--globe-btn) + 10px);
    left: 16px; right: auto; align-items: flex-start;
  }

  /* Compact card width only — do NOT override #globe-menu.open position. */
  #globe-menu.open .globe-menu-card {
    width: min(100%, 280px);
    max-width: min(280px, 100%);
  }
  /* AI chat: still fills the globe half (base #ai-menu.open). Do not re-pin
     it to a corner — that undid the fill and let the card float free. */
}
/* iPad landscape (touch + roomy): hamburger menu only — AI chat already
   fills the quadrant via the base #ai-menu.open rule. */
@media (orientation: landscape) and (pointer: coarse) and (min-width: 768px) and (min-height: 500px) {
  #globe-menu.open .globe-menu-card {
    width: min(100%, 300px);
    max-width: min(300px, 100%);
  }
}

/* Short landscape (phones): tighten vertical spacing so the whole hamburger
   menu fits the globe half without scrolling when content is the normal size. */
@media (orientation: landscape) and (max-height: 500px) {
  #globe-menu.open .globe-menu-card { padding: 8px; }
  #globe-menu-card .addmenu-hero-row { margin-bottom: 10px; }
  #globe-menu-card #addmenu-import { padding: 0.7em 0.9em; margin-bottom: 0; }
  #globe-menu-card .globe-menu-row { padding: 0.65em 0.9em; }
  #globe-menu-card .addmenu-divider { margin: 8px 0; }
  #globe-menu-card .addmenu-label { margin-bottom: 6px; }
  #globe-menu-card #addmenu-manual { margin-bottom: 8px; gap: 0.3em; row-gap: 0.3em; }
  #globe-menu-card .addmenu-cat { width: 2.7em; height: 2.7em; }
}

/* DESKTOP ONLY: the app-menu button lives in the TRUE top-LEFT corner of the
   globe quadrant — the conventional, scan-first home for a menu (F-pattern, like
   every nav-drawer app). The earlier top-RIGHT placement sat at the globe's inner
   edge, which on desktop is the screen's horizontal centre (the globe is the left
   half), so it read as "floating in the middle". A real outer corner reads as
   deliberate. 24px inset (desktop carries more padding than the phone's 16px),
   and 44px size (Apple HIG comfortable target; the default --globe-btn, so the
   button matches the trip-flag bubbles). */
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  #globe-menu-btn {
    left: 24px; right: auto;
    top: calc(env(safe-area-inset-top, 0px) + 24px);
  }
  /* Flag bubbles: same size as the menu button, centred on the button's axis
     (a clean vertical spine), stacked directly beneath it with the standard
     10px gap. */
  #globe-trips-bubbles {
    left: 24px; right: auto; align-items: center; width: var(--globe-btn);
    top: calc(env(safe-area-inset-top, 0px) + 24px + var(--globe-btn) + 10px);
  }
  /* Collapsed layover = smaller nested flag; revealed = same size as the other
     trip flags at this breakpoint (--globe-btn, same as the menu button). */
  .globe-trip-bubble-layover {
    width: calc(var(--globe-btn) * 0.75);
    height: calc(var(--globe-btn) * 0.75);
    margin-top: calc(-1 * (var(--globe-btn) * 0.75 + 10px));
  }
  .globe-trip-bubble-layover.is-shown {
    width: var(--flag-size);
    height: var(--flag-size);
    margin-top: 0;
  }
  /* Hamburger menu stays centered in the globe half (base #globe-menu.open).
     Only widen the card a little on roomy desktops. */
  #globe-menu.open .globe-menu-card {
    width: min(100%, 320px);
    max-width: min(320px, 100%);
  }

  /* Desktop AI chat: a bit more breathing room than the base inset — the
     card fills the box (see #ai-menu.open .globe-menu-card) so widening
     this margin both shrinks the card AND frames it, in one move. */
  #ai-menu.open {
    top: 20px; right: 20px; bottom: 20px; left: 20px;
  }
  /* Shrink the card itself to ~75% of that inset box (was 90% — still read as
     too big). #ai-menu.open is a flex container with justify-content/align-
     items: center (base rule above), so cutting width/height centers the
     card automatically — no extra margin math needed to keep it centered. */
  #ai-menu.open .globe-menu-card {
    width: 75%;
    height: 75%;
  }

  /* Day view: the back/prev/next button row sat visibly lower than the menu
     button and whale (AI) button above (#calendar's own padding-top is 28px
     here vs. their 24px inset, and the row's min-height centers the button a
     few px further down still) — pull it up so all three read as one line.
     Margin-bottom also grows here: more air between the now-larger title
     below and the Morning/Afternoon/Evening columns underneath it. */
  #day-header { margin-top: -7px; margin-bottom: 28px; }
  /* Day/month/year title: bigger, matching the month view's own desktop bump
     (#calendar-title, set in the landscape+fine block above) instead of
     staying at the base 18px used everywhere else. */
  #day-title { font-size: 23px; letter-spacing: -0.01em; }
}

/* iPad landscape (touch): keep the button on the LEFT (as it was), but lower it
   to the same level as the desktop button. Right-side move above is desktop-only. */
@media (orientation: landscape) and (pointer: coarse) and (min-width: 1024px) {
  #globe-menu-btn { left: 16px; right: auto; top: calc(env(safe-area-inset-top, 0px) + 72px); }
  #globe-trips-bubbles {
    left: 16px; right: auto; align-items: flex-start;
    top: calc(env(safe-area-inset-top, 0px) + 72px + var(--globe-btn) + 10px);
  }
}

/* Snappy luxurious animation for menu open (non-intrusive, translucent feel via glass) */
#globe-menu.open .globe-menu-card, #ai-menu.open .globe-menu-card {
  animation: menu-open-pop 0.28s var(--ease-out) both;
}

@keyframes menu-open-pop {
  from {
    opacity: 0;
    transform: scale(0.92) translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  #globe-menu.open .globe-menu-card, #ai-menu.open .globe-menu-card {
    animation: none;
  }
}

.globe-menu-row {
  display: flex;
  align-items: center;
  gap: 0.7em;
  width: 100%;
  text-align: left;
  font-size: 1em;
  font-weight: 600;
  color: #fff;
  border-radius: var(--r-chip);
  padding: 0.85em 1em;
  box-shadow: none;
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.globe-menu-row:active { background: var(--press); transform: scale(0.98); }
.globe-menu-row[aria-expanded="true"] .globe-menu-row-chev { transform: rotate(90deg); }
.globe-menu-row-icon { flex-shrink: 0; display: inline-flex; opacity: 0.9; }
.globe-menu-row-icon svg { width: 1.3em; height: 1.3em; }
.globe-menu-row-label { flex: 1; min-width: 0; }
.globe-menu-row-chev {
  flex-shrink: 0;
  font-size: 1.28em;
  color: rgba(255, 255, 255, 0.45);
  transition: transform 0.2s ease;
  line-height: 1;
}

/* ---------- AI travel-agent menu: header, chat thread, history list ----------
   Typography follows the rest of the app (see day cards + ideas panel):
     • body / reading text ≈ --menu-fs (14 phone / 16 iPad / 15 desktop)
     • secondary / muted captions a step down (~0.85–0.93em), never below ~12px
     • weight + opacity carry hierarchy more than shrinking size further
   The chat card is a flex column: header + form stay fixed, thread scrolls. */

/* Chat card: column layout so the thread eats leftover height inside the
   FIXED shell and the composer stays put at the bottom. */
#ai-menu .globe-menu-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;              /* scroll lives on the thread, not the whole card */
  padding: 10px 12px 12px;
  box-sizing: border-box;
}

/* Title on the left, "history" + "new chat" icon buttons on the right —
   same row-of-controls pattern grok.com uses above its own chat thread. */
#ai-menu-header {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 0.15em 0.15em 0.5em;
}
#ai-menu-title {
  flex: 1;
  min-width: 0;
  font-size: 1em;                /* same step as menu rows / section titles */
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: rgba(255, 255, 255, 0.9);
}
#ai-history-btn, #ai-new-chat-btn {
  flex-shrink: 0;
  width: 1.75em;
  height: 1.75em;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-chip);
  color: rgba(255, 255, 255, 0.75);
  transition: background 0.15s ease;
}
#ai-history-btn:active, #ai-new-chat-btn:active { background: var(--press); }
#ai-history-btn[aria-pressed="true"] { background: var(--press); color: #fff; }

/* The conversation itself — fills the card's leftover height (below header,
   above composer) and scrolls when the chat gets long. The card now fills
   the whole quadrant, so this just eats whatever height that leaves. */
#ai-menu-thread {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 0.1em 0.5em;
}
#ai-menu-thread[hidden] { display: none; }

/* Swaps in for #ai-menu-thread + #ai-menu-form when the budget bubble is
   tapped (see showAiBudgetView) — same "fills the card's leftover height"
   sizing, so the swap never jumps the card's own height around. */
#ai-menu-budget-view {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0 0.1em 0.5em;
}
#ai-menu-budget-view[hidden] { display: none; }
#ai-menu-budget-back {
  flex-shrink: 0;
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 8px;
  padding: 0.4em 0.7em 0.4em 0.5em;
  font-size: 0.9em;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.75);
  border-radius: var(--r-chip);
  transition: background 0.15s ease, color 0.15s ease;
}
#ai-menu-budget-back:active { background: var(--press); color: #fff; }
#ai-menu-thread[hidden] { display: none; }
#ai-menu-hint {
  font-size: 0.93em;             /* muted caption — like .detail-subtitle / .recs-sub */
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.55);
  padding: 0.15em 0.1em 0.25em;
}

/* Chat bubbles — user on the right (a quiet highlight, like Grok's own),
   the assistant flush left with no bubble background (reads as "the app
   talking", not a boxed-in reply). Body size = --menu-fs (reading text). */
.ai-msg {
  font-size: 1em;                /* was 0.88em — real reading content, not caption */
  line-height: 1.45;
  max-width: 92%;
  min-width: 0;
  overflow-wrap: break-word;
  word-break: normal;
}
.ai-msg-user {
  align-self: flex-end;
  background: rgba(255, 255, 255, 0.1);
  border-radius: var(--r-chip);
  padding: 0.5em 0.75em;
  color: #fff;
}
/* Inline edit — a traveler's own bubble only (see renderAiMessage). A small
   pencil + "Edit" sits under the text, invisible until the bubble is
   hovered, and only on a real mouse (touch has no hover, so it's simply
   never shown on a phone — nothing to tap there instead). Saving swaps this
   whole bubble's view for a textarea (.ai-msg-editing hides .ai-msg-view). */
.ai-msg-edit-btn {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  margin-top: 4px;
  padding: 0;
  border: none;
  background: none;
  color: rgba(255, 255, 255, 0.55);
  font: inherit;
  font-size: 0.78em;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.ai-msg-edit-btn:hover { color: #fff; }
.ai-msg-edit-btn svg { width: 13px; height: 13px; }
@media (hover: hover) {
  .ai-msg-user:hover .ai-msg-edit-btn {
    opacity: 1;
    pointer-events: auto;
  }
}
.ai-thread-busy .ai-msg-edit-btn { display: none; }   /* a reply is already generating */
.ai-msg-editing .ai-msg-view { display: none; }
/* A user bubble is normally shrink-wrapped to its own short text
   (align-self: flex-end on .ai-msg-user) — far too narrow for comfortably
   editing a long prompt. While editing, stretch it to the same full width
   as the composer input at the bottom of the panel, instead of staying
   pinched to the original bubble's size. */
.ai-msg-user.ai-msg-editing {
  align-self: stretch;
  max-width: 100%;
  width: 100%;
}
.ai-msg-edit-box {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 200px;
}
.ai-msg-edit-input {
  width: 100%;
  resize: none;
  overflow-y: hidden;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid var(--glass-border);
  border-radius: calc(var(--r-chip) * 0.6);
  padding: 0.4em 0.6em;
  font: inherit;
  font-size: 1em;
  line-height: 1.45;
  color: #fff;
}
.ai-msg-edit-input:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14);
}
.ai-msg-edit-actions {
  display: flex;
  gap: 14px;
  justify-content: flex-end;
  align-items: center;
}
.ai-msg-assistant {
  align-self: flex-start;
  color: rgba(255, 255, 255, 0.92);
  /* Old chats (and any stray text node) keep their real line breaks; new
     replies are laid out block by block — see appendFormattedReply. */
  white-space: pre-line;
}
/* One line of a reply. Real space between every paragraph, bullet and section
   so a long itinerary is easy to read instead of one solid chunk. */
.ai-msg-assistant .ai-p {
  margin: 0;
  white-space: normal;   /* the line is its own block now — nothing to preserve */
}
.ai-msg-assistant .ai-p + .ai-p { margin-top: 0.75em; }
/* A section label ("Day by day:") — brighter, with a bigger gap above it. */
.ai-msg-assistant .ai-h {
  color: #fff;
  font-weight: 600;
}
.ai-msg-assistant .ai-p + .ai-h { margin-top: 1.25em; }
/* Dashed items get a real bullet drawn in; numbered items keep their number.
   Both hang so a wrapped second line lines up under the first word. */
.ai-msg-assistant .ai-li {
  padding-left: 1.1em;
  position: relative;
}
.ai-msg-assistant .ai-li::before {
  content: '•';
  position: absolute;
  left: 0.2em;
  color: rgba(255, 255, 255, 0.55);
}
.ai-msg-assistant .ai-li-num {
  padding-left: 1.6em;
  text-indent: -1.6em;
}
.ai-msg a:not(.ai-msg-btn) {
  color: #fff;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Same look as the link above, but for an in-app action ("jump to this day")
   instead of an external href — a plain button dressed as the same text link. */
.ai-msg-link-btn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: #fff;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

/* A pair of real glass pill buttons (e.g. a flight search reply's "Open in
   Google Flights" + "Add tentative plan to calendar") — same glass-chip
   language as .fill-ideas-btn, not the plain underlined text links above. */
.ai-msg-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.ai-msg-btn {
  display: inline-flex;
  align-items: center;
  padding: 9px 14px;
  border-radius: 999px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: #fff;
  text-decoration: none;
  cursor: pointer;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.ai-msg-btn:hover { background: var(--hover); border-color: rgba(255, 255, 255, 0.28); }
.ai-msg-btn:active { background: var(--press); transform: scale(0.96); }
.ai-msg-btn:disabled { opacity: 0.6; cursor: default; }
/* One shared reminder under a row of pills whose date/time couldn't be
   baked into the link itself (see dateNote in buildAiChatRecommendationLinks)
   — small and quiet, not competing with the buttons or the reply above. */
.ai-msg-btn-note {
  margin-top: 6px;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.55);
}
/* ── The trip chat's opening summary ──────────────────────────────────────────
   Reads top to bottom in order of what matters: WHERE you're going, then the
   facts about it, then what's actually booked. Monochrome like everything that
   isn't the map, with the same muted tone and tabular figures the day cards use. */
.ai-msg-summary { max-width: 100%; }
.trip-summary-route {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 7px;
  font-size: 19px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: #ffffff;
}
.trip-summary-arrow {
  font-size: 14px;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.38);
}
.trip-summary-meta {
  margin-top: 5px;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
}
.trip-summary-foot {
  margin: 14px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.5);
}
/* A plain section label ("Hotel") ahead of that category's own button row —
   see the `heading` link type in renderAiMessage. */
.ai-msg-link-heading {
  margin-top: 10px;
  font-size: 13px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
}
/* Dashed outline — same "not booked yet" signal as .item-idea everywhere else. */
.ai-msg-btn-idea {
  border-style: dashed;
  border-width: 2px;
  border-color: rgba(255, 255, 255, 0.4);
}
/* Icon-only confirmation button ("View on your calendar") — same glyph as the
   reservation's own type (flight/hotel/restaurant/etc., see CATEGORY_SVG),
   ringed in a dotted circle so it reads as "tap to jump there", not a label. */
.ai-msg-btn-icon {
  width: 40px;
  height: 40px;
  padding: 0;
  justify-content: center;
  border-style: dotted;
  border-width: 2px;
  border-color: rgba(255, 255, 255, 0.4);
}
.ai-msg-btn-icon svg { width: 20px; height: 20px; }

/* History list — replaces the thread in place when the clock icon is
   tapped; same fixed card, scrolls when the list is long. */
#ai-history-list {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 0.1em 0.5em;
}
#ai-history-list[hidden] { display: none; }
.ai-history-empty {
  font-size: 0.93em;
  color: rgba(255, 255, 255, 0.5);
  padding: 0.3em 0.1em;
}
.ai-history-item {
  display: flex;
  align-items: center;
  gap: 2px;
  width: 100%;
  font-size: 0.93em;             /* secondary list row — just under body */
  padding: 0.15em 0.25em 0.15em 0.7em;
  border-radius: var(--r-chip);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  transition: background 0.15s ease;
}
.ai-history-item:active, .ai-history-item.active { background: var(--press); }
.ai-history-item-main {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  text-align: left;
  padding: 0.4em 0;
}
.ai-history-item-pin { flex-shrink: 0; font-size: 0.85em; }
.ai-history-item-title {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
  color: #fff;
}
.ai-history-item-time {
  flex-shrink: 0;
  font-size: 0.85em;             /* footer-scale timestamp */
  color: rgba(255, 255, 255, 0.5);
}
.ai-history-item-menu-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2em;
  height: 2em;
  border-radius: 999px;
  color: rgba(255, 255, 255, 0.55);
  transition: background 0.15s ease, color 0.15s ease;
}
.ai-history-item-menu-btn:hover, .ai-history-item-menu-btn:active {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}
.ai-history-item-rename-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid var(--glass-border);
  border-radius: 8px;
  padding: 0.2em 0.5em;
}

#ai-menu-form {
  display: flex;
  align-items: flex-end;         /* mic/send stay pinned to the bottom as the box grows */
  gap: 6px;
  flex-shrink: 0;
  padding-top: 0.35em;
}
#ai-menu-form[hidden] { display: none; }
/* Compact composer: same 16px as other phone inputs (stops Safari zoom),
   but less padding than before so it doesn't dominate the panel. Grows with
   text via autoGrowAiInput() up to a few lines, then scrolls. */
#ai-menu-input {
  flex: 1;
  min-width: 0;
  resize: none;
  overflow-y: hidden;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-chip);
  padding: 0.45em 0.7em;
  font-size: 16px;                 /* 16px stops iPhone Safari zooming on focus */
  line-height: 1.35;
  font-family: inherit;
  color: #fff;
  /* One clean line at rest — matches what autoGrowAiInput() sets in JS for
     an empty box, so there's no flash of a taller box before that JS runs.
     Only grows (via inline height, set by that same JS) once real text is
     typed — never for an empty/placeholder-only box. */
  height: calc(1.35em + 0.9em + 2px);
  max-height: 5.5em;               /* hard CSS cap so a tall paste can't blow the layout */
}
#ai-menu-input:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.11);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14);
}
/* The INPUT's own text stays at 16px always (hardcoded above — needed to
   stop iOS Safari zooming the whole page on focus). The PLACEHOLDER can be
   sized independently of that (::placeholder supports its own font-size),
   so it's set to --menu-fs instead — the same scale .ai-msg (the chat
   bubbles right above it) uses. On a real iPhone this closed a real, visible
   gap: the empty-box placeholder read noticeably bigger than every chat
   message, even though typed text and Safari's zoom guard were both fine. */
/* nowrap + ellipsis: belt-and-suspenders alongside the JS fix in
   autoGrowAiInput — a wrapped, 2-line placeholder is what made the resting
   composer look too tall/off-center in the first place. This keeps it a
   clean single line even if some future code path skips that JS. */
#ai-menu-input::placeholder {
  color: rgba(255, 255, 255, 0.4);
  font-size: var(--menu-fs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Mic and send — match the compact input height (not oversized vs the text). */
#ai-menu-mic, #ai-menu-submit {
  flex-shrink: 0;
  width: 2.15em;
  height: 2.15em;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-chip);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
#ai-menu-mic {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  color: #fff;
}
#ai-menu-mic:active { background: var(--press); transform: scale(0.93); }
/* Listening — a quiet red glow so it's obvious the mic is live. */
#ai-menu-mic[aria-pressed="true"] {
  background: rgba(255, 70, 70, 0.22);
  border-color: rgba(255, 120, 120, 0.4);
  color: #ff9a9a;
  animation: ai-mic-pulse 1.4s ease-in-out infinite;
}
@keyframes ai-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 90, 90, 0.35); }
  50%      { box-shadow: 0 0 0 6px rgba(255, 90, 90, 0); }
}
/* The primary action — same solid-white treatment as .btn-save, just not
   pill-shaped here (var(--r-chip) instead of var(--r-pill)). */
#ai-menu-submit {
  background: #fff;
  color: #000;
}
#ai-menu-submit:active { transform: scale(0.93); }
#ai-menu-submit:disabled { opacity: 0.4; }
@media (prefers-reduced-motion: reduce) {
  #ai-menu-mic[aria-pressed="true"] { animation: none; }
}

/* ---------- Cost view: item list, add button, running total ----------
   Lives inside the AI chat card's own budget view now (#ai-menu-budget-view)
   — .cost-list/.cost-item/etc. below are shared with that card's own
   header markup, just no separate #cost-menu-* card shell anymore. */
.cost-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 0.1em 0.5em;
}
.cost-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0.55em 0.6em;
}
/* Dashed outline for a tentative/rough-estimate cost line — same "not booked,
   not paid" signal as .item-idea on the calendar and .ai-msg-btn-idea in
   chat. */
.cost-item-tentative {
  border: 2px dashed rgba(255, 255, 255, 0.4);
  border-radius: var(--r-chip);
}
/* Same glass-recipe rectangle look the other pop-up forms use for fields
   (see .sheet-card input) — 16px stops iPhone Safari zooming on focus. */
.cost-item select, .cost-item input {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 10px;
  padding: 0.5em 0.5em;
  font-size: 16px;
  font-family: inherit;
  color: #fff;
  min-width: 0;
}
.cost-item select:focus, .cost-item input:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.11);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14);
}
.cost-item-category { flex: 0 0 auto; width: 7em; }
.cost-item-label { flex: 1 1 auto; }
/* Was 4.5em — too tight, a real amount like "$1,234.56" got clipped/hard to
   read. Wider now so the number always shows in full. */
.cost-item-amount { flex: 0 0 auto; width: 7em; text-align: right; }
.cost-item-delete {
  flex-shrink: 0;
  width: 1.7em;
  height: 1.7em;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.5);
  transition: background 0.15s ease, color 0.15s ease;
}
.cost-item-delete:active { background: var(--press); color: #fff; }
.cost-item-delete svg { width: 14px; height: 14px; }
/* City heading above that city's own expenses, on a multi-city trip — the
   budget then reads in the order the trip is travelled (first city at the
   top, final destination last), with that city's subtotal on the right and
   the grand total still at the very bottom. Monochrome and quiet: it's a
   signpost between groups, never louder than the numbers themselves. */
.cost-city-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  padding: 0 0.2em 0.35em;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.cost-list > .cost-city-heading:first-child { margin-top: 0; }
.cost-city-name {
  font-size: 0.8em;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
}
.cost-city-subtotal {
  font-size: 0.9em;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.6);
}
/* A CITY heading is a real <button> that folds its own rows away (see
   renderCostList), so it has to shed everything a browser gives a button and
   look exactly like the plain heading it sits beside — same rule under it,
   same spacing, same quiet type. Only the pointer and the chevron say it does
   anything. */
button.cost-city-heading {
  width: 100%;
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  cursor: pointer;
  text-align: left;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s ease;
}
/* A nested city heading has no rule under it (see .cost-city-heading-sub) —
   restated here because the button selector above is the more specific one. */
button.cost-city-heading-sub { border-bottom: none; }
button.cost-city-heading:hover .cost-city-name,
button.cost-city-heading:focus-visible .cost-city-name { color: rgba(255, 255, 255, 0.95); }
button.cost-city-heading:focus-visible {
  outline: 1px solid rgba(255, 255, 255, 0.35);
  outline-offset: 2px;
}
/* The name carries the chevron, so the subtotal stays hard right. */
.cost-city-name { display: inline-flex; align-items: center; gap: 0.4em; }
.cost-city-chevron {
  display: inline-flex;
  width: 0.9em;
  height: 0.9em;
  flex: none;
  opacity: 0.6;
  transition: transform 0.18s ease, opacity 0.18s ease;
}
.cost-city-chevron svg { width: 100%; height: 100%; display: block; }
button.cost-city-heading:hover .cost-city-chevron { opacity: 0.9; }
/* Folded: the chevron points right instead of down — one glyph, two states. */
.cost-city-collapsed .cost-city-chevron { transform: rotate(-90deg); }
/* A folded city keeps its subtotal at full strength: with the rows hidden it
   IS the number you're reading, so it must not stay dimmer than the country
   heading above it. Written to out-specify .cost-city-heading-sub's quieter
   nested-city colour further down the sheet, which otherwise wins on order. */
button.cost-city-heading.cost-city-collapsed .cost-city-subtotal,
button.cost-city-heading.cost-city-collapsed .cost-city-name {
  color: rgba(255, 255, 255, 0.82);
}
@media (prefers-reduced-motion: reduce) {
  .cost-city-chevron { transition: none; }
  button.cost-city-heading { transition: none; }
}
.cost-empty {
  font-size: 0.93em;
  color: rgba(255, 255, 255, 0.5);
  padding: 0.5em 0.2em;
}

.cost-add-btn {
  flex-shrink: 0;
  margin-top: 4px;
  padding: 0.7em;
  font-size: 0.95em;
  font-weight: 600;
  color: #fff;
  background: rgba(255, 255, 255, 0.07);
  border: 1px dashed rgba(255, 255, 255, 0.3);
  border-radius: var(--r-chip);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.cost-add-btn:active { transform: scale(0.98); background: var(--press); }

.cost-total-row {
  flex-shrink: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  font-size: 1.05em;
  font-weight: 700;
  color: #fff;
}

@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  .cost-item select, .cost-item input { font-size: 14px; }
}

/* Desktop chat type — match the ideas panel's reading sizes
   (title ~15–19, body ~15, caption ~13–14; never shrink real reading text).
   Thread height comes from the quadrant-filling card, not a vh cap. */
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  #ai-menu-title { font-size: 15px; }
  .ai-msg { font-size: 15px; line-height: 1.45; }
  #ai-menu-hint { font-size: 13px; }
  .ai-history-item { font-size: 14px; }
  .ai-history-item-time { font-size: 13px; }
  #ai-menu-input {
    font-size: 15px;             /* desktop can sit at body size (no iOS zoom issue) */
    padding: 0.5em 0.75em;
  }
  #ai-menu-mic, #ai-menu-submit {
    width: 36px;
    height: 36px;
  }
}

/* Portrait phone: keep the composer from eating the fixed card. */
@media (orientation: portrait) and (max-width: 819px) {
  #ai-menu-input {
    max-height: 4.2em;           /* ~3 lines */
  }
}

.globe-menu-section-label {
  font-size: 0.78em;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
  margin: 14px 4px 8px;
}
.globe-menu-trips {
  display: flex;
  flex-direction: column;
  gap: 10px;                         /* reservations never touch each other */
}
/* Push the Sign out button clear of the reservations above it */
#globe-menu-signout {
  margin-top: 16px;
}
.globe-menu-trip {
  display: flex;
  align-items: center;
  gap: 0.7em;
  width: 100%;
  text-align: left;
  font-size: 1em;
  color: #fff;
  border-radius: var(--r-chip);
  padding: 0.85em 1em;
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.globe-menu-trip:active { background: var(--press); transform: scale(0.98); }
.globe-menu-trip-icon {
  flex-shrink: 0;
  width: 1.55em;
  height: 1.55em;
  color: rgba(255, 255, 255, 0.92);
}
.globe-menu-trip-icon svg { width: 1.55em; height: 1.55em; display: block; }
/* Destination flag: a round color flag filling the circle */
.trip-flag-bubble {
  display: block;
  width: 1.55em;
  height: 1.55em;
  border-radius: 50%;
  overflow: hidden;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18);
}
.trip-flag-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.globe-menu-trip-text {
  flex: 1;
  min-width: 0;
  font-weight: 500;
  line-height: 1.3;
}
.globe-menu-trips-empty {
  font-size: 0.9em;
  color: rgba(255, 255, 255, 0.45);
  text-align: center;
  padding: 14px 8px;
}

@media (prefers-reduced-motion: reduce) {
  .globe-menu-card { animation: none; }
  #globe-menu:not([hidden]) .globe-menu-card {
    transition: none !important;
  }
}

/* Anything with the "hidden" attribute is truly hidden, even if styled here */
[hidden] { display: none !important; }

/* ---------- BOTTOM HALF: calendar ---------- */
#calendar {
  /* Bottom inset: light clear of the iPhone home indicator so the last week
     never sits under it. Kept tight on purpose so the last visible week-row
     sits fully above the Safari URL bar (not half-cropped with a fat black
     band under it). Falls back to 2px where there's no safe-area. Top padding
     keeps the day-view back button fully inside this half (never under the
     globe seam). Landscape bumps padding-top further above. */
  padding: 14px 16px calc(2px + env(safe-area-inset-bottom, 0px) / 3);
  display: flex;
  flex-direction: column;
  touch-action: pan-y;               /* vertical pan only — no sideways scroll gesture */
  box-sizing: border-box;
}

/* ---------- THE LOGIN / SIGN-IN SCREEN ----------
   Sits in the calendar quadrant before you're signed in. A single centred glass
   card with a Google button. When you sign in it fades+lifts away (.signing-out)
   and the calendar eases in behind it. */
#auth-view {
  align-items: center;
  justify-content: flex-start;     /* see #auth-card's margin:auto below */
  text-align: center;
  /* No scroll — the card below is sized (see its comment) to always fit a
     real iPhone's calendar quadrant outright, in EITHER orientation, so
     there's nothing to scroll to. overflow stays hidden (not auto/scroll)
     as pure insurance: if some extreme case (huge accessibility text
     size, a tiny old device) ever makes the card taller than its box, it
     silently clips instead of showing a scrollbar — it should never
     actually trigger given the sizing below. */
  overflow: hidden;
  padding: 8px 0;
}
#auth-card {
  width: 100%;
  max-width: 340px;
  margin: auto 0;                  /* centers vertically; clips from the bottom, not both ends, in the insurance case above */
  /* Sized to fit the TIGHTEST real case outright, with real margin to
     spare: an iPhone in LANDSCAPE, where #calendar takes the phone's own
     short dimension (as little as ~430px on the biggest iPhones, less on
     smaller ones) minus #calendar's own padding and the home-indicator
     safe area — leaving as little as ~360px for this whole card. The
     numbers below add up to well under 320px, so it fits with room to
     spare in landscape AND (with far more space available) in portrait.
     Sized back up for genuinely roomy screens (tablet/desktop) in the
     min-width/min-height block below. */
  padding: 14px 20px;
  border-radius: var(--r-card);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  animation: pop-in 0.4s var(--ease-out) both;
  flex-shrink: 0;
}
#auth-title {
  margin: 0 0 1px;
  font-size: 17px;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.02em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
#auth-logo {
  width: 18px;
  height: 18px;
  flex: none;
}
#auth-sub {
  margin: 0 0 6px;
  font-size: 12px;
  line-height: 1.2;
  opacity: 0.72;
}
#google-signin {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 8px 16px;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.2;
  font-weight: 600;
  color: #fff;
  cursor: pointer;
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  transition: transform 0.15s var(--ease-out), background 0.15s;
}
#google-signin:active { transform: scale(0.97); }
#google-signin svg { display: block; flex: none; width: 18px; height: 18px; }

/* The email/password form sitting above the Google button */
#auth-form { display: flex; flex-direction: column; text-align: left; }
#auth-form label {
  font-size: 11px;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.5);
  margin-top: 4px;
  margin-bottom: 1px;
}
#auth-form label:first-of-type { margin-top: 0; }
#auth-form input {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 12px;
  padding: 6px 11px;
  font-size: 16px;                 /* 16px stops iPhone Safari zooming on focus */
  line-height: 1.2;
  color: #fff;
}
#auth-form input:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.11);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14);
}
#auth-error {
  margin: 5px 0 0;
  font-size: 12px;
  line-height: 1.25;
  color: #ff8a8a;
  text-align: left;
}
#auth-submit { width: 100%; margin-top: 8px; padding: 9px; }
#auth-toggle { margin: 6px 0 0; text-align: center; }
#auth-toggle-btn {
  font-size: 12px;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.55);
  background: none;
  border: none;
  padding: 3px;
}
#auth-toggle-btn span { color: #fff; font-weight: 600; }
#auth-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 0 6px;
  color: rgba(255, 255, 255, 0.4);
  font-size: 11px;
  line-height: 1.2;
}
#auth-divider::before, #auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.12);
}
/* The luxurious exit: card lifts and fades as the calendar arrives. */
#auth-view.signing-out {
  animation: auth-out 0.22s var(--ease-out) both;
  pointer-events: none;
}
@keyframes auth-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: scale(1.04) translateY(-10px); }
}
@media (prefers-reduced-motion: reduce) {
  #auth-card { animation: none; }
  #auth-view.signing-out { animation: none; opacity: 0; }
}
/* Roomier screens only (tablets + desktops). BOTH width AND height must be
   roomy — width alone is not enough.

   Why: a landscape iPhone Pro/Max is WIDER than 768px (~844–932) but still
   only ~390–430px TALL. The login card lives in the right half and must use
   the compact sizes above so every field stays fully on screen. The old
   query was `@media (min-width: 768px), …` which matched those phones by
   width alone and blew the card up past the short landscape height (bottom
   of the box clipped). Requiring min-height: 500px keeps phones compact in
   either orientation while still giving iPads/desktops the roomier look. */
@media (min-width: 768px) and (min-height: 500px) {
  #auth-card { padding: 28px 24px; }
  #auth-title { margin-bottom: 6px; font-size: 26px; gap: 8px; }
  #auth-logo { width: 28px; height: 28px; }
  #auth-sub { margin-bottom: 22px; font-size: 14px; }
  #auth-form label { margin-top: 12px; margin-bottom: 4px; }
  #auth-form input { padding: 11px; }
  #auth-error { margin-top: 10px; }
  #auth-submit { margin-top: 16px; }
  #auth-toggle { margin-top: 14px; }
  #auth-divider { margin: 18px 0 14px; }
  #google-signin { padding: 13px 16px; }
}
/* Both the month grid and the single-day view fill the bottom half. No
   overflow property here on purpose: #calendar (the parent) already has
   overflow:hidden on both axes, which is all the horizontal containment
   this needs. Setting overflow-x:hidden here too (with overflow-y left
   unset) used to trip the CSS spec rule that turns a lone non-visible axis'
   sibling into overflow-y:auto — that made THIS box itself a second, tighter
   clipping container (top inset ~28px) that cropped the day title's -20px
   lift and the back button's alignment nudge, a few px above that line. */
.cal-view {
  flex: 1;
  min-height: 0;
  min-width: 0;
  max-width: 100%;
  display: flex;
  flex-direction: column;
}

/* Header rows: a title in the middle, buttons on the sides */
#calendar-header {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 18px;          /* breathing room between the month title and the weekday row */
}
#day-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  position: relative;                /* anchor the absolutely-placed back button */
  flex-shrink: 0;
  min-height: calc(var(--globe-btn) + 6px);   /* room for the back bubble (matches the menu button) + a hair of air */
}
/* The back button is lifted OUT of the row's flow so the title group can sit
   dead-centre across the FULL header width — centred with the globe in the
   stacked view, and centred in the calendar half in the side-by-side view. */
#day-header #back-to-month {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;                        /* above day content; calendar already above globe */
}
#day-header-center {
  flex: 1;
  min-width: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
/* The one calendar type-scale, locked to the iPhone-portrait proportion and used
   at EVERY screen size: the month/day TITLE is 18px and the calendar day NUMBER is
   title ÷ 1.44 (≈12.5px) — the exact ratio that looks right on the phone. Both the
   title and the day cells read from these, so the proportion never drifts. */
:root {
  --cal-title-fs: 18px;
  --cal-ratio: 1.44;
  --cal-day-fs: calc(var(--cal-title-fs) / var(--cal-ratio));
}
#calendar-title {
  font-size: var(--cal-title-fs);
  font-weight: 600;
  text-align: center;
}
#day-title {
  min-width: 0;
  font-size: var(--cal-title-fs);   /* same size as the month title (#calendar-title) */
  font-weight: 600;
  text-align: center;
  white-space: nowrap;
  letter-spacing: -0.02em;
}
@media (orientation: landscape) and (pointer: fine) {
  /* Desktop: pin the title to a fixed width so the ‹ › buttons never move as
     the day text changes length, and give them a little more breathing room. */
  #day-header-center { gap: 18px; }
  #day-title {
    flex: 0 0 300px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* Desktop month/year title: a bigger, more prominent heading (the way native
     desktop calendars do it — title is the anchor, not body-sized), with more
     whitespace between it and the weekday + grid below. Only the TITLE grows;
     the day-number scale stays as is, so the cells don't change. */
  #calendar-title { font-size: 23px; letter-spacing: -0.01em; }
  #calendar-header { margin-bottom: 48px; }
}
/* The ‹ back button — a small glass bubble, icon dead center */
#back-to-month, #detail-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  color: #fff;
  flex-shrink: 0;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
/* Day view only: the back button matches the menu bubble's size exactly
   (--globe-btn), on every screen — #detail-back keeps the smaller 34px above. */
#back-to-month {
  width: var(--globe-btn);
  height: var(--globe-btn);
}
#back-to-month svg, #detail-back svg {
  display: block;
  flex-shrink: 0;
}
#back-to-month:active, #detail-back:active { background: var(--press); transform: scale(0.94); }
/* Keep vertical centering when the absolutely-placed back button is pressed
   (plain scale would drop translateY(-50%) and jump the bubble). */
#day-header #back-to-month:active { transform: translateY(-50%) scale(0.94); }
/* Desktop only: tiny glass bubbles to step backward/forward one day */
.day-nav-bubble {
  display: none;
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.85);
  box-shadow: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.day-nav-bubble:active:not(:disabled) { background: var(--press); transform: scale(0.94); }
/* The prev/next day bubbles are a MOUSE convenience only — on touch you swipe
   left/right instead, so they never appear on a touch device. */
@media (orientation: landscape) and (pointer: fine) {
  .day-nav-bubble { display: flex; }
}
button:disabled { opacity: 0.25; }

/* ---------- DAY VIEW: three columns — Morning / Afternoon / Evening ----------
   No clock numbers; an item sits vertically by its time within its column
   (e.g. an 11 AM event lands near the bottom of Morning). */
/* #day-body is the positioning context for #day-stage (the day on screen) and,
   only while a drag/turn is happening, its "ghost" sibling — the neighboring
   day's real content, dragged in from off-screen. See the day-turn JS. */
#day-body { flex: 1; min-height: 0; overflow: hidden; position: relative; }
/* Both panes stay on their OWN GPU layer permanently (translateZ + will-change),
   rather than only while a turn is in progress: toggling will-change on right
   as the slide starts works fine in Chrome (which promotes the layer eagerly),
   but on real iPhone Safari the layer-creation cost lands on the animation's
   own first frame(s), which is what actually reads as "stuttery" there even
   though desktop Chrome never shows it. A layer this small (one screen of day
   cards) costs nothing to keep composited all the time, and it removes the
   promotion-timing race entirely instead of trying to win it. */
#day-stage, .day-stage-ghost {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  will-change: transform;
}
#day-stage {
  transform: translate3d(0, 0, 0);
  transition: transform var(--dur-day-turn) var(--ease-snap);
}
/* The ghost is purely visual — real content, dragged into view, but never
   itself interactive (its items get their real click handlers only once
   they become the actual #day-stage, after the turn commits). */
.day-stage-ghost { pointer-events: none; }
/* SWIPE PERFORMANCE (phones): while a day is turning, the glass cards drop their
   backdrop-blur (both panes' own GPU layer is permanent — see above). Recomputing
   many blurs against a moving layer every frame is what makes the swipe jitter on
   real iPhones; the blur returns the instant the turn settles and is imperceptible
   during the brief slide. This rule already covers both panes: .day-stage-ghost
   is a #day-body child too, same as #day-stage. */
#day-body.day-turning .item {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* THE DAY, AS ONE FLOWING GRID (test layout — replaced Morning/Afternoon/Evening).
   Cards run left-to-right in time order and wrap onto the next row, so reading
   order is simply the order the day happens in. Three across keeps each card
   exactly the size it was when it filled one of three columns.
   align-content: start stops a half-empty day from stretching its rows apart;
   the whole grid scrolls as one, rather than three lanes scrolling separately. */
.day-flow {
  display: grid;
  /* minmax(0, 1fr), NOT 1fr: a plain 1fr column will not shrink below its own
     content's minimum width, so one long unbreakable title ("Sultanhan Hotel -
     Special Class") made its column bulge and squeezed the other two. On a
     phone that came out as 172px next to two 99px cards. The 0 minimum is what
     forces all three to stay equal — the flex layout this replaced did the same
     job with min-width: 0 on each column. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  height: 100%;
  align-content: start;
  padding-top: 4px;
  overflow-y: auto;
  overflow-x: hidden;               /* vertical only — text wraps, never pan sideways */
  scrollbar-width: none;
}
.day-flow::-webkit-scrollbar { display: none; }

/* A saved entry. Fills the column width and is ALWAYS a PERFECT SQUARE
   (aspect-ratio 1). Extra events scroll inside the column — we never squash
   the boxes into shorter rectangles to fit more on screen. */
.item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 0;                     /* time→title spacing comes from .item-time margin-bottom */
  width: 100%;
  aspect-ratio: 1;            /* always a perfect square */
  height: auto;               /* height comes from aspect-ratio + width — never compressed */
  flex: none;                 /* don't let the column's flex stretch/squash the box */
  padding: 14px 14px 7px;     /* bottom space = HALF the top space */
  border-radius: 16px;
  font-size: 13px;
  cursor: pointer;
  -webkit-touch-callout: none;
  user-select: none;
  overflow: hidden;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.item:active { background: var(--press); transform: scale(0.97); }
/* The card's half of the map pairing: hovering a card lights its pin, and hovering a
   PIN lights the card back (.item-pin-hover, set from highlightDayCard). Both use the
   same faint lift so the two halves read as one highlight. Hover only — a phone has no
   pointer to hover with, and tapping already opens the entry. */
@media (hover: hover) {
  .item:hover { background: var(--press); }
}
.item.item-pin-hover { background: var(--press); }
.item-time {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  /* The gap between the time and the title: ~80% of the time's OWN font size, so it
     scales correctly on every screen. The content then fills downward from here. */
  margin-bottom: 0.8em;
}
.item-row {
  display: flex;
  align-items: flex-start;    /* content fills DOWN from the gap (top-aligned) */
  flex: 1;
  width: 100%;
  min-width: 0;
  min-height: 0;
  gap: 7px;
}
.item-icon { flex-shrink: 0; display: inline-flex; color: rgba(255, 255, 255, 0.9); }
.item-icon svg { width: 16px; height: 16px; display: block; }
.item-main {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;   /* lines stack from the top, filling down */
  gap: 2px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}
/* Title (the line right after the time): SAME size for EVERY event type. It's set
   explicitly here — and at each screen size below — so it can never drift between
   a flight, a restaurant, a tour, etc. */
.item-primary {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  min-height: 1.2em;
  max-width: 100%;
  overflow-wrap: break-word;
  word-break: normal;
}
.item-detail {
  font-size: 11px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.72);
  line-height: 1.3;
  min-height: 1.3em;
  max-width: 100%;
  overflow-wrap: break-word;
  word-break: normal;
}

/* Touch devices: the card is a perfect square (height = width, set on .item above),
   so its size simply follows the column width. These rules only set the padding,
   gaps, text and icon sizes for touch. A packed day scrolls (see .day-flow). */
@media (pointer: coarse) {
  #day-body .item {
    padding: 14px 14px 7px;
    gap: 0;
    font-size: 13px;
    border-radius: 16px;
  }
  #day-body .item-time { font-size: 11px; }
  #day-body .item-row { gap: 7px; }
  #day-body .item-main { gap: 2px; }
  #day-body .item-primary { font-size: 13px; }
  #day-body .item-detail { font-size: 11px; }
  #day-body .item-icon svg { width: 16px; height: 16px; }
}

/* iPad (a TOUCH device wider than a phone): the cards' padding/text sit between the
   iPhone and the desktop. The square's size follows the column width (aspect-ratio
   on .item); a packed column scrolls. min-width 768px keeps every iPhone out. */
@media (pointer: coarse) and (min-width: 768px) {
  #day-body .item {
    padding: 18px 18px 9px;
    gap: 0;
    font-size: 16px;
    border-radius: 18px;
  }
  #day-body .item-time { font-size: 12.5px; }
  #day-body .item-row { gap: 9px; }
  #day-body .item-main { gap: 3px; }
  #day-body .item-primary { font-size: 16px; }
  #day-body .item-detail { font-size: 13px; }
  #day-body .item-icon svg { width: 20px; height: 20px; }
}

/* Only a real desktop (a device with a MOUSE) gets the roomy, larger day-event
   cards. Touch devices — phones AND the small iPhone Ultra — keep the clean,
   smaller mobile font sizing (the iPhone 14 Pro Max portrait look) even when the
   layout is side-by-side. */
@media (orientation: landscape) and (pointer: fine) {
  #day-body .item {
    padding: 22px 24px 11px;
    font-size: 19px;
    border-radius: 20px;
    gap: 0;
  }
  #day-body .item-time { font-size: 13px; }
  #day-body .item-icon svg { width: 22px; height: 22px; }
  #day-body .item-primary { font-size: 19px; }
  #day-body .item-detail { font-size: 14px; }
}

/* ---------- DETAIL VIEW: one reservation, opened by TAPPING a day bubble ---------- */
#detail-header { display: flex; align-items: center; margin-bottom: 10px; }
#detail-body {
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  overflow-x: hidden;              /* vertical only */
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  overscroll-behavior-x: none;
}
#detail-body::-webkit-scrollbar { display: none; }

/* Detail sections ease in with a light stagger after the panel opens */
#detail-view:not([hidden]) .res-section-panel,
#detail-view:not([hidden]) .detail-head,
#detail-view:not([hidden]) .detail-rows,
#detail-view:not([hidden]) .detail-nav {
  animation: view-in 0.26s var(--ease-out) both;
}
#detail-view:not([hidden]) .res-section-panel:nth-child(1),
#detail-view:not([hidden]) .detail-head { animation-delay: 0.04s; }
#detail-view:not([hidden]) .res-section-panel:nth-child(2) { animation-delay: 0.07s; }
#detail-view:not([hidden]) .res-section-panel:nth-child(3) { animation-delay: 0.10s; }
#detail-view:not([hidden]) .res-section-panel:nth-child(4) { animation-delay: 0.13s; }
#detail-view:not([hidden]) .detail-nav { animation-delay: 0.12s; }

/* The reservation's headline: big icon + title, small subtitle under it */
.detail-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.detail-head .detail-icon { flex-shrink: 0; color: #fff; }
.detail-head .detail-icon svg { width: 30px; height: 30px; display: block; }
.detail-title {
  font-size: 24px;
  font-weight: 700;
  line-height: 1.1;
  max-width: 100%;
  min-width: 0;
  overflow-wrap: break-word;
  word-break: normal;
}
.detail-subtitle {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.55);
  margin-top: 3px;
  overflow-wrap: break-word;
}

/* The facts of the booking — a quiet label on the left, the value on the right */
.detail-rows { display: flex; flex-direction: column; gap: 2px; margin-bottom: 22px; }
.detail-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  padding: 11px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.detail-row .k { font-size: 13px; color: rgba(255, 255, 255, 0.5); }
.detail-row .v {
  font-size: 15px;
  font-weight: 500;
  text-align: right;
  font-variant-numeric: tabular-nums;
  min-width: 0;
  flex: 1;
  overflow-wrap: break-word;
  word-break: normal;
}
.detail-row .k { flex-shrink: 0; }

/* The navigation buttons — liquid glass, fully see-through, blurred behind so
   the label reads cleanly over the globe. */
.detail-nav-label {
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
  margin-bottom: 10px;
}
/* Two bubbles side by side: Start your drive | Order an Uber */
.detail-nav { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: auto; }
.nav-btn { min-width: 0; }
.nav-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px;
  border-radius: var(--r-pill);
  color: #fff;
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.nav-btn svg { width: 20px; height: 20px; flex-shrink: 0; }
/* "Start your drive" / "Order an Uber" always stay on ONE line */
.nav-btn span { white-space: nowrap; }
.nav-btn:active { transform: scale(0.97); background: var(--press); }

/* ── RESERVATION DETAIL — organized, uniform tiles, tap to expand ── */
.res-section-panel { margin-bottom: 10px; }
.res-section-panel:last-child { margin-bottom: 0; }
/* HEADING tier — top-level section buttons (Airline, Journey, Travelers, etc.).
   Biggest weight so a traveler's eye lands on these first. */
.res-section-panel > .res-acc-head .res-acc-summary {
  font-size: 16px;
  font-weight: 700;
}
.res-section-panel.open > .res-acc-body {
  padding-top: 4px;
}
.res-block { display: flex; flex-direction: column; gap: 8px; }
.res-stack { display: flex; flex-direction: column; gap: 6px; }
.res-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.res-tile {
  min-height: 72px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 4px;
  padding: 12px 14px;
  border-radius: var(--r-chip);
  text-align: left;
  color: #fff;
  text-decoration: none;
  box-shadow: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease, border-color 0.15s ease;
}
.res-tile-wide { grid-column: 1 / -1; min-height: 64px; }
/* Car rental: a wide left column (locations, car class) + a narrow right column
   (the small time/date values), each tile pinned to its column so the rows line
   up even when a value is missing. */
.res-tiles.res-tiles-carsplit { grid-template-columns: 3fr 1fr; }
.res-tiles-carsplit .res-col-l { grid-column: 1; }
.res-tiles-carsplit .res-col-r { grid-column: 2; }
/* Three equal bubbles forced onto one row at every width (col count set inline).
   Values wrap to more lines rather than crop, so nothing is ever cut off. */
/* Airline row: confirmation + trip dates side by side, then a slim icon column.
   Titles top-aligned so they share one visual line. */
.res-airline-row {
  display: grid;
  grid-template-columns: 1.4fr 1fr auto;
  gap: 8px;
  align-items: stretch;
}
/* The two text bubbles: borderless (neither one "highlighted"). Title stays at
   the TOP; the value fills the space below it and sits centered in the middle. */
.res-airline-row > .res-tile { min-width: 0; justify-content: flex-start; border: none; }
.res-airline-row > .res-tile .res-tile-v { flex: 1; display: flex; align-items: center; }
/* Stacked icon buttons (phone over website) filling the row's height. They KEEP
   their outline so it's clear they're tappable; the icon sits dead center. */
.res-icon-col {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 6px;
}
.res-icon-btn {
  min-height: 0;
  width: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.res-tile-ic { display: inline-flex; align-items: center; color: #fff; }
.res-tile-ic svg { width: 20px; height: 20px; display: block; }
.res-tile-k {
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
  line-height: 1.25;
}
.res-tile-v {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  line-height: 1.25;
  width: 100%;
  min-width: 0;
}
.res-tile-k { width: 100%; min-width: 0; }
/* The vendor confirmation is the single most important field — sized like a section
   title so the eye lands on it first. (Only this tile; ticket numbers stay normal.) */
.res-tile-conf .res-tile-v { font-size: 16px; font-weight: 700; }
.res-tile-copy { cursor: pointer; width: 100%; border: none; font: inherit; }
/* Plain info tiles (Dates, Check-in, etc. — not buttons or links) drop the glass
   outline so they match the flat Confirmation tile. The glass outline is reserved
   for the tappable tiles. They keep the same dark glass fill and shadow. */
.res-tile:not(.res-tile-copy):not(.res-tile-link) { border: none; }
.res-tile-copy:active, .res-tile-link:active { transform: scale(0.97); background: var(--press); }
.res-tile-copy.copied { border-color: rgba(255, 255, 255, 0.4); }
.res-tile-copy.copied .res-tile-v { color: rgba(255, 255, 255, 0.75); font-size: 13px; }
.res-tile-link .res-tile-v { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(255, 255, 255, 0.3); }

.res-accordion {
  border-radius: var(--r-chip);
  box-shadow: none;
  flex-shrink: 0;
}
.res-accordion:not(.open) { overflow: hidden; }
.res-acc-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border: none;
  background: none;
  color: #fff;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.res-acc-head:active { background: var(--press); transform: scale(0.99); }
/* SUBHEADING tier — nested accordion buttons ("Itinerary details", traveler names).
   Explicit size so they no longer inherit the oversized 16px browser default and sit
   one clear step below the section headings. */
.res-acc-summary { flex: 1; min-width: 0; width: 100%; font-size: 14px; font-weight: 600; }
.res-acc-chev {
  flex-shrink: 0;
  font-size: 18px;
  color: rgba(255, 255, 255, 0.4);
  transition: transform 0.2s ease;
  line-height: 1;
}
.res-accordion.open .res-acc-chev { transform: rotate(90deg); }
.res-acc-body {
  display: grid;
  grid-template-rows: 0fr;
  padding: 0 14px;
  transition: grid-template-rows 0.16s cubic-bezier(0.4, 0, 0.2, 1), padding 0.16s cubic-bezier(0.4, 0, 0.2, 1);
}
.res-accordion.open > .res-acc-body {
  grid-template-rows: 1fr;
  padding: 0 14px 12px;
}
.res-acc-body-inner {
  overflow: hidden;
  min-height: 0;
}
.res-accordion.open > .res-acc-body > .res-acc-body-inner {
  overflow: visible;
}
.res-acc-inner { display: flex; flex-direction: column; gap: 8px; }

.res-leg-sum { display: flex; flex-direction: column; gap: 3px; }
.res-leg-date { font-size: 11px; color: rgba(255, 255, 255, 0.45); }
.res-leg-main { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.res-leg-fn { font-size: 16px; font-weight: 700; letter-spacing: 0.02em; }
.res-leg-route { font-size: 14px; font-weight: 500; color: rgba(255, 255, 255, 0.82); }
.res-accordion.res-leg-active { border-color: rgba(255, 255, 255, 0.28); }
.res-layover {
  font-size: 10px;
  color: rgba(255, 255, 255, 0.4);
  text-align: center;
  padding: 2px 0;
}
.res-traveler-flat {
  padding: 12px 14px;
  border-radius: var(--r-chip);
  font-size: 14px;          /* subheading tier — matches a traveler name in an accordion */
  font-weight: 600;
}
.res-detail-line {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 13px;
}
.res-detail-line:last-child { border-bottom: none; }
.res-detail-k { color: rgba(255, 255, 255, 0.48); flex-shrink: 0; }
.res-detail-v {
  font-weight: 500;
  text-align: right;
  font-variant-numeric: tabular-nums;
  min-width: 0;
  flex: 1;
}

/* Legacy detail pieces (non-flight reservations) */
.detail-section { margin-bottom: 16px; }
.detail-section:last-child { margin-bottom: 0; }
.detail-section-title {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
  margin-bottom: 8px;
}
.detail-travelers { display: flex; flex-direction: column; gap: 8px; }
.detail-traveler {
  padding: 10px 12px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.04);
}
.detail-traveler-name { font-size: 15px; font-weight: 600; }
.detail-traveler-ticket {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.detail-notice {
  font-size: 12px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.72);
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
}
.detail-journey { display: flex; flex-direction: column; gap: 8px; }
.detail-leg {
  padding: 11px 12px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.04);
}
.detail-leg-date {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.48);
  margin-bottom: 4px;
}
.detail-leg-flight { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.detail-leg-route {
  font-size: 14px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.detail-leg-cities {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.52);
  margin-top: 3px;
}
.detail-leg-meta {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.48);
  margin-top: 5px;
}
.detail-layover {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.45);
  text-align: center;
  padding: 2px 0;
}
.detail-notes, .detail-airline-tips { display: flex; flex-direction: column; gap: 8px; }
.detail-note, .detail-airline-tips p {
  font-size: 13px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.68);
}
/* Tappable contact links — phone taps open the dialer, web opens a new tab */
.contact-list { display: flex; flex-direction: column; gap: 8px; }
.contact-btn {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--r-chip);
  color: #fff;
  text-decoration: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
  box-shadow: none;
}
.contact-btn:active { transform: scale(0.97); background: var(--press); }
.contact-btn-icon { flex-shrink: 0; margin-top: 2px; color: rgba(255, 255, 255, 0.9); }
.contact-btn-icon svg { width: 18px; height: 18px; display: block; }
.contact-btn-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.contact-btn-label { font-size: 14px; font-weight: 600; }
.contact-btn-value {
  font-size: 15px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.92);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(255, 255, 255, 0.35);
}
.contact-btn-hint { font-size: 11px; color: rgba(255, 255, 255, 0.48); margin-top: 2px; }

/* Copyable reference codes — tap to copy, brief "Copied" flash */
.copy-bubble-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}
.copy-bubble-wrap { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.copy-bubble-label {
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
  line-height: 1.3;
}
.copy-bubble {
  width: 100%;
  text-align: center;
  padding: 11px 10px;
  border-radius: 14px;
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: #fff;
  cursor: pointer;
  box-shadow: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease, border-color 0.15s ease;
}
.copy-bubble:active { transform: scale(0.96); background: var(--press); }
.copy-bubble.copied {
  border-color: rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.12);
  letter-spacing: 0.04em;
  font-size: 13px;
}
.detail-leg .copy-bubble-wrap { margin-top: 8px; }
.detail-leg .copy-bubble { font-size: 13px; padding: 8px 10px; }

/* Compact detail typography so more of a booking fits on screen when you tap a
   reservation (detail view only). Applies to ANY touch device — in BOTH
   orientations — plus any portrait window, so phones never get the big desktop
   detail fonts. Only a real desktop (mouse) in landscape keeps the larger sizes. */
@media (orientation: portrait), (pointer: coarse) {
  /* (Day-header centering now lives in the base rules — applies to every layout.) */
  #detail-view #detail-header { margin-bottom: 6px; }
  #detail-view #detail-body.detail-simple {
    overflow: hidden;
    justify-content: space-between;
  }
  #detail-view #detail-body.detail-rich {
    overflow-y: auto;
    overflow-x: hidden;
    justify-content: flex-start;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 16px;
  }
  #detail-view .res-block,
  #detail-view .res-stack,
  #detail-view .res-acc-inner { min-width: 0; }
  #detail-view .res-leg-main { flex-wrap: wrap; }
  #detail-view .res-section-panel .res-stack { gap: 6px; }
  #detail-view .detail-head { gap: 8px; margin-bottom: 8px; }
  #detail-view .detail-head .detail-icon svg { width: 20px; height: 20px; }
  #detail-view .detail-title { font-size: 16px; }
  #detail-view .detail-subtitle { font-size: 10px; margin-top: 2px; }
  #detail-view .detail-rows { gap: 0; margin-bottom: 8px; flex-shrink: 1; min-height: 0; }
  #detail-view .detail-row { padding: 5px 2px; }
  #detail-view .detail-row .k { font-size: 10px; }
  #detail-view .detail-row .v { font-size: 12px; }
  #detail-view .detail-nav-label { font-size: 9px; margin-bottom: 5px; }
  #detail-view .detail-nav { gap: 6px; margin-top: 0; flex-shrink: 0; }
  #detail-view .nav-btn { padding: 10px 12px; font-size: 13px; gap: 7px; }
  #detail-view .nav-btn svg { width: 16px; height: 16px; }
  #detail-view .detail-section { margin-bottom: 10px; }
  #detail-view .detail-section-title { font-size: 9px; margin-bottom: 5px; }
  #detail-view .detail-traveler-name { font-size: 13px; }
  #detail-view .detail-traveler-ticket { font-size: 10px; }
  #detail-view .detail-leg-flight { font-size: 13px; }
  #detail-view .detail-leg-route { font-size: 12px; }
  #detail-view .detail-leg-cities,
  #detail-view .detail-leg-meta { font-size: 10px; }
  #detail-view .detail-leg-date { font-size: 10px; }
  #detail-view .detail-notice { font-size: 10px; padding: 7px 9px; }
  #detail-view .detail-note,
  #detail-view .detail-airline-tips p { font-size: 12px; }
  #detail-view .contact-btn { padding: 8px 10px; }
  #detail-view .contact-btn-label { font-size: 12px; }
  #detail-view .contact-btn-value { font-size: 13px; }
  #detail-view .contact-btn-hint { font-size: 10px; }
  #detail-view .copy-bubble-grid { grid-template-columns: 1fr 1fr; gap: 5px; }
  #detail-view .copy-bubble-label { font-size: 9px; }
  #detail-view .copy-bubble { font-size: 13px; padding: 8px 7px; }
  #detail-view .res-tile { min-height: 52px; padding: 8px 10px; }
  /* All these bubbles share one size on mobile so the cramped trio (trip dates /
     call airline / manage booking) fits its phone number cleanly. */
  #detail-view .res-tile-v { font-size: 11px; letter-spacing: 0.01em; }
  #detail-view .res-tile-k { font-size: 8px; }
  /* Mobile: the two text bubbles are roughly half as tall, and the stacked icon
     buttons shrink to match (icon centered). Desktop keeps its taller sizing. */
  #detail-view .res-airline-row > .res-tile { min-height: 0; padding: 5px 9px; gap: 2px; }
  /* Confirmation matches the section title size on mobile too (beats the row override). */
  #detail-view .res-tile-conf .res-tile-v,
  #detail-view .res-airline-row > .res-tile-conf .res-tile-v { font-size: 13px; font-weight: 700; }
  #detail-view .res-icon-col { gap: 4px; }
  #detail-view .res-icon-btn { min-height: 0; width: 38px; }
  #detail-view .res-icon-btn svg { width: 15px; height: 15px; }
  #detail-view .res-leg-fn { font-size: 13px; }
  #detail-view .res-leg-route { font-size: 11px; }
  #detail-view .res-leg-date { font-size: 9px; }
  #detail-view .res-traveler-flat { font-size: 12px; padding: 10px 12px; }
  #detail-view .res-detail-line { font-size: 11px; padding: 6px 0; }
  #detail-view .res-detail-k, #detail-view .res-detail-v { font-size: 11px; }
  #detail-view .res-section-panel > .res-acc-head .res-acc-summary { font-size: 13px; }   /* heading */
  #detail-view .res-acc-summary { font-size: 12px; }                                     /* subheading (nested) */
  #detail-view .res-acc-head { padding: 10px 12px; }
  #detail-view .res-acc-chev { font-size: 16px; }
  #detail-view .res-section-panel { margin-bottom: 6px; }
  /* Bubbles INSIDE an opened dropdown: a touch smaller again so they don't feel
     crunched (mobile only). */
  #detail-view .res-acc-inner .res-tile { min-height: 46px; padding: 7px 9px; }
  #detail-view .res-acc-inner .res-tile-v { font-size: 11px; }
  #detail-view .res-acc-inner .res-tile-k { font-size: 8px; }
  #detail-view .res-acc-inner .res-detail-line,
  #detail-view .res-acc-inner .res-detail-k,
  #detail-view .res-acc-inner .res-detail-v { font-size: 10px; }
}

/* ============================================================
   iPad DETAIL VIEW — a TOUCH device wider than a phone. The compact block above
   gives iPad the phone-sized detail text (too small for the big screen); here we
   step it UP to sit between the phone and the desktop, inspired by the iPhone
   look but roomier. min-width 768px keeps every iPhone out; both orientations.
   ============================================================ */
@media (pointer: coarse) and (min-width: 768px) {
  /* Headline */
  #detail-view .detail-head { gap: 11px; margin-bottom: 12px; }
  #detail-view .detail-head .detail-icon svg { width: 26px; height: 26px; }
  #detail-view .detail-title { font-size: 20px; }
  #detail-view .detail-subtitle { font-size: 12px; margin-top: 3px; }
  /* Fact rows */
  #detail-view .detail-rows { gap: 1px; margin-bottom: 14px; }
  #detail-view .detail-row { padding: 8px 2px; }
  #detail-view .detail-row .k { font-size: 12px; }
  #detail-view .detail-row .v { font-size: 14px; }
  /* Drive / Uber nav buttons */
  #detail-view .detail-nav-label { font-size: 11px; margin-bottom: 8px; }
  #detail-view .detail-nav { gap: 8px; }
  #detail-view .nav-btn { padding: 13px 16px; font-size: 15px; gap: 9px; }
  #detail-view .nav-btn svg { width: 18px; height: 18px; }
  /* Sections / accordions */
  #detail-view .res-section-panel { margin-bottom: 8px; }
  #detail-view .res-section-panel > .res-acc-head .res-acc-summary { font-size: 15px; }  /* heading */
  #detail-view .res-acc-summary { font-size: 13px; }                                    /* subheading */
  #detail-view .res-acc-head { padding: 11px 13px; }
  #detail-view .res-acc-chev { font-size: 17px; }
  /* Tiles */
  #detail-view .res-tile { min-height: 62px; padding: 10px 12px; }
  #detail-view .res-tile-v { font-size: 13px; }
  #detail-view .res-tile-k { font-size: 9px; }
  #detail-view .res-tile-conf .res-tile-v,
  #detail-view .res-airline-row > .res-tile-conf .res-tile-v { font-size: 15px; font-weight: 700; }
  #detail-view .res-tile-ic svg { width: 18px; height: 18px; }
  #detail-view .res-icon-btn { width: 41px; }
  #detail-view .res-icon-btn svg { width: 17px; height: 17px; }
  /* Flight legs */
  #detail-view .res-leg-fn { font-size: 15px; }
  #detail-view .res-leg-route { font-size: 13px; }
  #detail-view .res-leg-date { font-size: 10px; }
  /* Travelers + misc lines */
  #detail-view .res-traveler-flat { font-size: 13px; padding: 11px 13px; }
  #detail-view .res-detail-line { font-size: 12px; padding: 7px 0; }
  #detail-view .res-detail-k, #detail-view .res-detail-v { font-size: 12px; }
  #detail-view .res-acc-inner .res-tile { min-height: 54px; padding: 9px 11px; }
  #detail-view .res-acc-inner .res-tile-v { font-size: 13px; }
  #detail-view .res-acc-inner .res-tile-k { font-size: 9px; }
  #detail-view .res-acc-inner .res-detail-line,
  #detail-view .res-acc-inner .res-detail-k,
  #detail-view .res-acc-inner .res-detail-v { font-size: 11px; }
  /* Contact + copy bubbles */
  #detail-view .contact-btn-label { font-size: 13px; }
  #detail-view .contact-btn-value { font-size: 14px; }
  #detail-view .contact-btn-hint { font-size: 11px; }
  #detail-view .copy-bubble-label { font-size: 10px; }
  #detail-view .copy-bubble { font-size: 14px; padding: 9px 9px; }
}

/* iPad PORTRAIT: give the DAY view and the reservation detail page the SAME width
   as the calendar month grid — by centring them all to the one shared width
   (--cal-maxw, defined with the month grid above). Headers centre too so the back
   ‹ button lines up with the content edge, exactly like the month view. */
@media (orientation: portrait) and (min-width: 601px) {
  #day-header, #day-body,
  #detail-header, #detail-body {
    width: 100%;
    max-width: var(--cal-maxw);
    margin-inline: auto;
  }
}

/* EVERY landscape screen (iPhone landscape, iPad landscape, desktop): give the day
   view (Morning/Afternoon/Evening columns) and the event detail page the SAME
   centred width + side margins as the month grid, so all three calendar pages line
   up to one constraint. (--sq is defined for all landscape; the grid is --sq·7+24px,
   so the day/detail views reuse exactly that.) Headers centre too so the back ‹
   button aligns with the content edge. */
@media (orientation: landscape) {
  #day-header, #day-body,
  #detail-header, #detail-body {
    width: 100%;
    max-width: calc(var(--sq) * 7 + 24px);   /* the very same width as the month grid */
    margin-inline: auto;
  }
}

/* ============================================================
   SHARED POP-UP PIECES
   ============================================================ */
/* The dimmed layer behind any pop-up */
#add-overlay, .sheet-overlay, #item-menu, #trip-menu, #chat-menu {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: rgba(0, 0, 0, 0.42);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
#item-menu { z-index: 11; align-items: flex-end; padding: 16px; }  /* sits at the bottom */
/* #trip-menu (right-click/long-press a globe flag bubble) stays centered —
   it belongs to the globe quadrant it's pinned to (see positionMenuOverGlobe),
   not a bottom sheet like #item-menu's calendar-entry edit/delete. */
#trip-menu { z-index: 11; align-items: center; justify-content: center; padding: 16px; }
/* #chat-menu (rename/pin/delete a chat) belongs to the AI menu card it's
   opened from (see positionMenuOverElement in app.js), same idea as
   #trip-menu pinning to the globe instead of the calendar. */
#chat-menu { z-index: 12; align-items: flex-end; padding: 16px; }

/* #item-menu (edit/delete one entry) is a full-viewport overlay by default,
   so its card centers on the WHOLE page — wrong the moment the globe takes
   up part of the screen (any landscape split, or the stacked portrait
   layout). JS (positionMenuOverCalendar) pins its box to the LIVE #calendar
   pane's rect instead, on every device/orientation — no media query to keep
   in sync as breakpoints change. #trip-menu gets the same pin-to-rect
   treatment but aimed at #globe instead (positionMenuOverGlobe), so its card
   centers in the globe's own quadrant rather than at the click/tap position.
   Desktop keeps a bit of extra polish: blur behind the menu, same recipe as
   #globe-menu-scrim uses for the ☰ menu. */
@media (orientation: landscape) and (pointer: fine) {
  #item-menu, #trip-menu, #chat-menu {
    background: rgba(0, 0, 0, 0.38);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }
}

/* The floating glass CARD every pop-up shares */
#add-card, .sheet-card {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  padding: 20px;
  border-radius: var(--r-card);
}
#add-card h2, .sheet-card h2 { font-size: 18px; margin-bottom: 6px; }

/* The text fields / dropdowns inside the pop-ups — the card behind them is
   already glass, so these are just a slightly lighter rectangle on it */
#add-card input, .sheet-card input,
#flight-time-ampm, .ampm {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 12px;
  padding: 11px;
  font-size: 16px;                 /* 16px stops iPhone Safari zooming on focus */
}
/* A clear "this field is focused" ring */
#add-card input:focus, .sheet-card input:focus,
#flight-time-ampm:focus, .ampm:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.11);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14);
}
#add-card label, .sheet-card label {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
  margin-top: 12px;
  margin-bottom: 4px;
}

/* The two button styles every pop-up shares: a glass "cancel" + a bright "save" */
#add-cancel, .btn-cancel {
  color: #fff;
  font-size: 16px;
  padding: 12px;
  border-radius: var(--r-pill);
  box-shadow: none;                /* it sits IN the card, not above it */
}
#add-cancel, .btn-cancel {
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
#add-cancel:active, .btn-cancel:active { background: var(--press); transform: scale(0.97); }
/* The primary action is the ONE solid thing on screen, so it leads the eye */
#add-save, .btn-save {
  font-size: 16px;
  font-weight: 600;
  padding: 12px;
  border-radius: var(--r-pill);
  color: #000;
  background: #fff;
  transition: transform var(--dur-tap) var(--ease-snap), opacity 0.2s ease;
}
#add-save:active, .btn-save:active { transform: scale(0.96); }
#add-save:disabled, .btn-save:disabled { opacity: 0.4; }

/* ---------- THE "ADD A FLIGHT" POP-UP ---------- */
/* One-way / Round trip / Multi-city — a segmented control, only shown when
   adding fresh (editing one existing leg hides it, see openAddForm). */
#trip-type-row { display: flex; gap: 8px; margin-top: 12px; }
.trip-type-btn {
  flex: 1;
  padding: 9px;
  font-size: 13px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--r-pill);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease, color 0.15s ease;
}
.trip-type-btn:active { transform: scale(0.97); }
.trip-type-btn.active { color: #000; background: #fff; }

/* One block per flight segment — point A, point B, date, airline+number. */
.flight-leg {
  padding-top: 14px;
  margin-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.flight-leg:first-child { border-top: none; margin-top: 4px; padding-top: 4px; }
.flight-leg-heading {
  font-size: 12px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.5);
  margin-bottom: 8px;
}
.flight-leg .route-row { display: flex; gap: 10px; align-items: flex-start; }
.flight-leg .flight-leg-date { width: 100%; margin-top: 10px; }
/* The DIRECT-CHILD airline field only — NOT the From/To fields nested one
   level deeper inside .route-row. ":last-child" alone matched both (each is
   "last" within its own parent), which is what put From and To on different
   baselines: the To field (last inside .route-row) got this same top margin
   the From field never did. */
.flight-leg > .airport-field { margin-top: 10px; }

#add-leg-btn {
  margin-top: 12px;
  padding: 10px;
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  background: rgba(255, 255, 255, 0.07);
  border: 1px dashed rgba(255, 255, 255, 0.3);
  border-radius: var(--r-chip);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
#add-leg-btn:active { transform: scale(0.98); background: var(--press); }

/* The list of airlines that matches what you typed */
.airline-option {
  padding: 10px;
  border-radius: 10px;
  font-size: 14px;
  cursor: pointer;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.airline-option:active { background: rgba(255, 255, 255, 0.12); transform: scale(0.99); }
.airline-option .code { color: rgba(255, 255, 255, 0.55); margin-right: 8px; }

/* ---------- THE TRANSFER / HOTEL "sheet" pop-ups ---------- */
.sheet-actions { display: flex; gap: 10px; margin-top: 18px; }
.sheet-actions button { flex: 1; }

/* Native date inputs styled to match the rest of the form */
.sheet-card input[type="date"], #add-card input[type="date"] {
  font-family: inherit;
  min-height: 44px;
}

/* "Drop off at a booked hotel?" quick-pick in the transfer form */
#tr-hotels { display: flex; flex-direction: column; gap: 6px; }
#tr-hotels:empty { display: none; }
.tr-hotels-label { font-size: 12px; color: rgba(255, 255, 255, 0.5); margin-top: 4px; }
.tr-hotel-suggest {
  display: flex;
  align-items: center;
  gap: 6px;
  text-align: left;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.9);
  border-radius: var(--r-chip);
  box-shadow: none;
  padding: 9px 11px;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.tr-hotel-suggest:active { transform: scale(0.98); background: var(--press); }
.tr-hotel-suggest svg { width: 14px; height: 14px; flex-shrink: 0; }

/* The From / To row: two airport pickers side by side (see .flight-leg .route-row) */
.airport-field { flex: 1; position: relative; }   /* the dropdown hangs off this */
.airport-field input { width: 100%; }
/* The matching-places dropdown floats below its box — darker than the usual
   glass so a list of options stays readable over anything */
.airport-list {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 5;                      /* sit above the buttons below it */
  margin-top: 4px;
  max-height: 180px;
  overflow-y: auto;
  overflow-x: hidden;
  background: rgba(12, 12, 16, 0.88);
  border-radius: var(--r-chip);
}
.airport-option {
  padding: 10px;
  font-size: 14px;
  cursor: pointer;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.airport-option:active { background: rgba(255, 255, 255, 0.12); transform: scale(0.99); }
.airport-option .code { color: rgba(255, 255, 255, 0.55); margin-right: 8px; }

#add-actions { display: flex; gap: 10px; margin-top: 18px; }
#add-actions button { flex: 1; }

/* ---------- ADD BOOKINGS PANEL (inside globe menu) ---------- */
#globe-menu-card .addmenu-cat {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: none;
}
#globe-menu-card .addmenu-cat:active {
  background: rgba(255, 255, 255, 0.15);
}
#globe-menu-card .addmenu-label {
  color: rgba(255, 255, 255, 0.62);
  letter-spacing: 0.04em;
}
#globe-menu-card #addmenu-status { color: rgba(255, 255, 255, 0.88); }

/* HERO: the headline upload action — a luxurious glass panel with a soft glow,
   an icon in its own lit circle, and a two-line label. Tap to pick, or drop here. */
/* Matches the bottom menu rows (.globe-menu-row, e.g. Sign out): same corner
   radius (--r-chip), same flat surface, border and (no) shadow — so the whole
   menu reads as one congruent set. Only the two-line hero layout is its own. */
/* HERO row: the upload button (grows to fill) + the profile avatar bubble
   beside it. Extra margin-bottom opens clear air before "Copy forwarding
   email" so the two actions don't feel stacked on top of each other. */
.addmenu-hero-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;              /* space before the email-forward row */
}
.addmenu-hero-row #addmenu-import { margin-bottom: 0; flex: 1; min-width: 0; }
#addmenu-import {
  display: flex;
  align-items: center;
  gap: 0.85em;
  width: 100%;
  text-align: left;
  color: #fff;
  border-radius: var(--r-chip);
  padding: 1em 1.05em;
  margin-bottom: 0;
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease, border-color 0.2s ease;
}
/* The signed-in user's Google profile picture — same 44px circle language as
   the globe's own menu/logo buttons, just sitting flush in the hero row
   instead of floating over the map. */
#profile-avatar {
  flex-shrink: 0;
  width: var(--globe-btn);
  height: var(--globe-btn);
  border-radius: 50%;
  overflow: hidden;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  box-shadow: var(--flat-shadow);
}
#profile-avatar[hidden] { display: none; }
#profile-avatar-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
#addmenu-import:active,
#addmenu-import.drag {
  transform: scale(0.98);
  background: var(--press);
}
/* Just the icon, inline with the text — no chip behind it (a circle back there
   read as its own separate tappable button next to the real one). */
.addmenu-import-icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}
.addmenu-import-icon svg { width: 1.5em; height: 1.5em; }
.addmenu-import-text { display: flex; flex-direction: column; gap: 0.15em; min-width: 0; }
.addmenu-import-title { font-size: 1.02em; font-weight: 600; letter-spacing: -0.01em; }
.addmenu-import-sub { font-size: 0.82em; color: rgba(255, 255, 255, 0.6); line-height: 1.25; }
#addmenu-status { font-size: 0.85em; color: rgba(255, 255, 255, 0.8); text-align: center; margin-top: 8px; white-space: pre-line; }
#addmenu-status:empty { display: none; }           /* no empty gap when there's nothing to say */

/* The "working" sign while the AI reads your files: a little world with a
   plane flying around it. The world holds still; the plane's invisible
   wrapper spins, carrying the plane in a circle. Pure CSS, nothing heavy. */
#import-spinner {
  position: relative;
  width: 54px;
  height: 54px;
  margin: 12px auto 0;
}
.spinner-globe {
  position: absolute;
  inset: 12px;                       /* the world sits small in the middle... */
  color: rgba(255, 255, 255, 0.85);
}
.spinner-orbit {
  position: absolute;
  inset: 0;                          /* ...and the orbit fills the whole box */
  animation: orbit 1.4s linear infinite;
}
.spinner-plane {
  position: absolute;
  top: -2px;
  left: 50%;
  width: 14px;
  height: 14px;
  margin-left: -7px;                 /* center the plane on the orbit's top */
  transform: rotate(90deg);          /* nose forward, the way it's flying */
  color: #fff;
}
@keyframes orbit { to { transform: rotate(360deg); } }
/* "Reduce motion" users get the message text only, no spinning */
@media (prefers-reduced-motion: reduce) {
  .spinner-orbit { animation: none; }
}
/* The divider is itself a sliver of light, not a grey line */
.addmenu-divider {
  height: 1px;
  margin: 14px 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent);
}
.addmenu-label { font-size: 0.85em; color: rgba(255, 255, 255, 0.5); text-align: center; margin-bottom: 8px; }
/* Manual-category icons: a tighter grid (smaller gap + slightly smaller tiles)
   so the row reads compact, with just enough air that icons don't touch. */
#addmenu-manual {
  display: flex;
  justify-content: center;
  gap: 0.35em;                      /* was 0.6em — more compact, still breathable */
  flex-wrap: wrap;
  margin-bottom: 12px;
  row-gap: 0.35em;
}
/* BOTTOM: each manual category is an icon in a small square of glass (no words).
   Sized in em so the squares grow with the menu's text on iPad/desktop. */
.addmenu-cat {
  width: 3.05em;                    /* was 3.4em — denser row without feeling cramped */
  height: 3.05em;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-chip);
  color: #fff;
  box-shadow: none;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.addmenu-cat:active { background: var(--press); transform: scale(0.94); }
.addmenu-cat svg { width: 1.45em; height: 1.45em; }
.addmenu-cat.disabled { opacity: 0.35; cursor: default; }   /* form not built yet */

/* ---------- LONG-PRESS MENU (Edit / Delete) ---------- */
#item-menu-card, #trip-menu-card, #chat-menu-card {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* The trip's own name above the Delete/Cancel rows. Deliberately NOT a glass
   pane like the buttons under it — it isn't pressable, and giving it the same
   material would read as a third button. Quiet, centered, and it joins the
   same staggered pop-in as the rows (it leads; they follow at 0.05s/0.08s). */
#trip-menu-title {
  margin: 0 0 2px;
  padding: 0 4px;
  text-align: center;
  font-size: 0.95em;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.72);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.8);
  animation: pop-in 0.24s var(--ease-out) 0.02s both;
}
#trip-menu-title:empty { display: none; }

/* A quiet line above a form's buttons naming what's still missing, shown only
   while Save is disabled. Monochrome and small — it's guidance, not an error. */
.sheet-hint {
  margin: 0 0 2px;
  font-size: 0.85em;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.55);
}
.sheet-hint[hidden] { display: none; }

/* A CITY heading nested under its country's heading (see renderCostList).
   Indented, smaller and quieter than the country above it, and with no rule
   under it — the country's own line already separates the section, and a
   second full-width rule two lines down just made the sheet look busy. */
.cost-city-heading-sub {
  margin-top: 2px;
  padding-left: 1.1em;
  border-bottom: none;
}
.cost-city-heading-sub .cost-city-name {
  font-size: 0.72em;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.55);
}
.cost-city-heading-sub .cost-city-subtotal {
  font-size: 0.82em;
  color: rgba(255, 255, 255, 0.45);
}
@media (prefers-reduced-motion: reduce) {
  #trip-menu-title { animation: none; }
}
/* Each menu row is its own floating pane of glass */
#item-menu-card button, #trip-menu-card button, #chat-menu-card button {
  padding: 15px;
  border-radius: 18px;
  font-size: 17px;
  color: #fff;
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
#item-menu-card button:active, #trip-menu-card button:active, #chat-menu-card button:active { background: var(--press); transform: scale(0.98); }
#item-delete, #trip-delete, #chat-delete { color: rgba(255, 255, 255, 0.65); }
#item-cancel, #trip-cancel, #chat-cancel { font-weight: 600; }

/* ---------- Delete affordance on the trip flag bubbles ----------
   Desktop: right-click opens the delete confirm (see #trip-menu, wired in
   app.js). Nothing appears on hover — the owner tried a hover-revealed
   Delete pill here and asked for it gone; right-click alone is the interaction. */

/* ---------- THE MONTH GRID (kept lightweight — NO blur on the ~60 cells:
     they must scroll like silk on an iPhone) ---------- */
/* The weekday labels (S M T W T F S) stay fixed above the scrolling weeks */
#weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.5);
  text-align: center;
  margin-bottom: 6px;
}
/* The days area scrolls continuously through every month, snapping by week.
   Vertical only — never pan sideways (trackpad / shift-wheel / overflow).
   scroll-padding-top: 0 keeps each week row's top edge flush with this box so
   day-box outlines are fully visible (never half-cropped under the weekdays). */
#days {
  flex: 1;
  min-height: 0;
  min-width: 0;
  position: relative;                  /* week.offsetTop is relative to this scroller */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-x: none;
  /* mandatory (matches the deployed app exactly): the browser settles onto a
     week row as part of the scroll's own deceleration, in one motion. The
     proximity + JS-forced-resnap combo this replaced let a fast flick travel
     further, but then visibly yanked the grid back to a row a beat AFTER the
     scroll had already stopped — a separate, delayed correction that read as
     "locking" rather than settling. Removed on request; see the scroll
     listener below (snapDaysToNearestWeek is no longer called from there). */
  scroll-snap-type: y mandatory;
  scroll-padding-top: 0;
  -webkit-overflow-scrolling: touch;   /* smooth momentum scrolling on iPhone */
  scrollbar-width: none;               /* no scrollbar bar on PC — wheel still scrolls */
}
#days::-webkit-scrollbar { display: none; }
/* Trip jump — hold CSS snap off during the program scroll (phone + desktop)
   so the animation can land exactly on the first-week target without a second
   snap pass yanking it mid-row. */
#days.is-centering {
  scroll-snap-type: none !important;
  -webkit-overflow-scrolling: auto;
}
#days.is-centering .week {
  scroll-snap-align: none !important;
  scroll-snap-stop: normal !important;
}
/* Flag/trip jump in progress (see animateScrollTop in app.js). Blocks taps
   mid-flight so a stray touch doesn't yank the glide. We deliberately do NOT
   toggle content-visibility or hide icons here — flipping those at the end of
   the scroll forced Safari to re-layout ~260 weeks in one frame, which read as
   a stutter/snap right as the ease-out finished, and delayed the bright/dim
   month paint by up to a second on iPhone.
   Opacity transition on .day is also off during the jump so month highlight
   tracks the scroll frame-by-frame instead of lagging 0.25s behind each change. */
#days.is-program-scroll {
  pointer-events: none;
}
#days.is-program-scroll .day {
  transition: none;
}
/* One week = a row of 7 day cells */
.week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin-bottom: 4px;
  scroll-snap-align: start;            /* top of day boxes = top of #days */
  scroll-snap-stop: always;            /* don't skip past a week on a fast flick */
  scroll-margin-top: 0;
}
/* Day cells: PERFECT SQUARES of see-through glass with rounded corners.
   aspect-ratio means height always equals width — so on a wide desktop
   column the squares grow big automatically, on a phone they stay snug. */
.day {
  position: relative;                   /* so the icon row can sit at the bottom */
  container-type: inline-size;          /* lets the icons size themselves off the cell width */
  aspect-ratio: 1;                      /* a perfect square, at every screen size */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--r-chip);         /* clearly rounded corners on every cell */
  font-size: var(--cal-day-fs);         /* locked to the title via --cal-ratio (1.44) */
  cursor: pointer;
  transition: transform var(--dur-tap) var(--ease-snap), opacity 0.25s ease, background 0.15s ease;
}
.day:active { background: rgba(255, 255, 255, 0.10); transform: scale(0.96); }
/* Month grid ↔ globe pairing: hovering a day lights its city's pin, and hovering that
   PIN lights every day cell in that city back (.day-pin-hover, from
   highlightMonthCellsForCity). Hover only — a phone has no pointer to hover with. */
@media (hover: hover) {
  .day[data-key]:hover { background: rgba(255, 255, 255, 0.07); }
}
.day.day-pin-hover {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.45);
}
/* Tiny, centered, monochrome event icons along the bottom of a day cell */
.day-icons {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(2px, 5cqw, 6px);        /* sits a touch above the cell's bottom edge */
  display: flex;
  align-items: center;                  /* keeps the idea ring centered against plain glyphs */
  justify-content: center;
  gap: clamp(2px, 3cqw, 3px);
  color: rgba(255, 255, 255, 0.55);     /* subtle white — present, not loud */
  pointer-events: none;                 /* taps fall through to the day cell */
  /* A day shows at most three glyphs (see DAY_ICON_CAP), and they are sized so
     three ALWAYS clear the cell's rounded corners with room to spare. Before
     this, three dashed idea rings measured 86px inside a 94px cell — under 4px
     of air on each side, which read as crowded even when it technically fit,
     and a fourth glyph spilled straight over the border into the next day.
     Nothing here is a magic number: the sizes below are container-query units,
     so the row breathes the same way at every cell size and screen. */
}
/* Icons scale with the cell width (cqw = 1% of the cell): they shrink in tight
   landscape cells so they fit and never overlap the day number, and grow on big
   desktop cells — capped so they never get oversized. Works at every screen size. */
.day-icons svg { display: block; width: clamp(7px, 17cqw, 15px); height: clamp(7px, 17cqw, 15px); }
/* An idea/draft's icon (same type-specific glyph a confirmed booking uses —
   see baseIconForType) sits inside a small dashed ring, same visual language
   as .globe-trip-bubble-idea's dashed ring elsewhere in the app: solid =
   booked, dashed = "not yet." The ring is sized off the glyph itself (em, not
   a fixed px) so it scales with the same clamp() the plain icons use above
   and never grows past "a little bigger than the icon it circles." */
.day-icon-idea {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  border-radius: 50%;
  /* The dashed ring below pokes out past this box, which the flex gap on
     .day-icons doesn't know about — without this margin, two idea rings sitting
     side by side overlap/touch instead of just their icons. It SCALES with the
     cell (cqw) like the glyphs do: a fixed margin left three rings only 3.7px
     of air in a narrow phone cell, because the glyphs shrank and the rings
     didn't. --ring is shared with ::after below so the gap and the ring it is
     compensating for can never drift apart. */
  --ring: clamp(1.5px, 2cqw, 2.5px);
  margin: 0 var(--ring);
}
.day-icon-idea::after {
  content: '';
  position: absolute;
  inset: calc(-1 * var(--ring));
  border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, 0.72);
}
/* DESKTOP ONLY: the day cells are much bigger here, so the same icons that read fine
   on a phone get lost. Bigger + near-full-white + a soft glow makes them stand out
   without breaking the app's black-and-white rule (only the globe gets real color —
   see CLAUDE.md) by introducing an unrelated accent color into the calendar. */
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  .day-icons svg {
    width: clamp(7px, 16cqw, 16px);
    height: clamp(7px, 16cqw, 16px);
    color: rgba(255, 255, 255, 0.92);
    /* A tighter, dimmer halo than before: at this smaller size the old 3px
       glow bled into its neighbour and made three icons read as one smudge. */
    filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.32));
  }
  .day-icon-idea::after { border-width: 1px; }
  .day-icon-idea { --ring: 3px; }
}
/* Desktop: lift the drive/Uber bubbles off the panel's bottom edge so they
   aren't cropped. */
@media (orientation: landscape) {
  #detail-view .detail-nav { margin-bottom: 14px; }
  #detail-body { padding-bottom: 4px; }
}
/* Folded iPhone Ultra FRONT screen — a small, near-square portrait pane
   (~474×696pt, roughly 5:4 / 4:3). Keep the same 50/50 split as every other
   phone; only the day cells get shorter so a full month still fits in the
   calendar half without forcing the globe smaller. */
@media (orientation: portrait) and (min-aspect-ratio: 3 / 5) and (max-width: 600px) {
  .day {
    aspect-ratio: auto;                   /* drop the square so rows can be shorter */
    height: calc((50dvh - 88px) / 6);     /* calendar half is 50% tall */
  }
  .week { margin-bottom: 3px; }
}
/* Days belonging to a month other than the one in focus recede into the space —
   kept very faint so they're barely there and don't distract from the focus month. */
.day.off-month { opacity: 0.12; border-color: rgba(255, 255, 255, 0.04); }
.day.muted { opacity: 0.2; cursor: default; }   /* days outside 2026–2030 */
/* Today: the same see-through glass as every other cell — it just has
   a brighter outline (and bolder number) so your eye finds it */
.day.today {
  font-weight: 600;
  border-color: rgba(255, 255, 255, 0.65);
}

/* ============================================================
   iPad & DESKTOP — keep the month readable (phones ≤600px untouched).
   On a wide screen, 7 squares stretched edge-to-edge become huge, so only
   3–4 week-rows fit. Like Apple's Calendar, we instead CAP the grid width and
   CENTER it with side padding — squares stay a comfortable size and a whole
   month stays in view. #weekdays + #days share the cap so they line up.
   ============================================================ */

/* iPad PORTRAIT (stacked; calendar is the bottom ~half). Height-driven cap so
   ~5 week-rows always fit, from the chubby iPad mini up to the 12.9". The 70vh
   factor = 7 columns × (half-screen height ÷ 5 rows); −126px is the header +
   weekday row + paddings that aren't part of the scrolling squares. */
@media (orientation: portrait) and (min-width: 601px) {
  /* One shared width so the day grid AND the reservation detail page share the
     exact same side padding on iPad portrait (see the detail view below). */
  :root { --cal-maxw: min(calc(70vh - 126px), 720px); }
  #weekdays, #days {
    width: 100%;
    max-width: var(--cal-maxw);
    margin-inline: auto;
  }
}

/* LANDSCAPE (side-by-side; calendar is the right half) — show a fixed number of
   week-rows per device, centred with padding on every side. We pick a square size
   --sq that's the SMALLER of the height-fit (so exactly --cal-rows fill the half's
   height) and the width-fit (so it never overflows the half's width); the grid is
   capped to 7·--sq wide and --cal-rows tall, then auto-margins centre the whole
   weekday+grid block (top/bottom padding) and margin-inline centres it (side
   padding). One fluid recipe; only the row count changes per device below.
     5 rows iPhone landscape · 7 rows iPad landscape · 9 rows desktop. */
@media (orientation: landscape) {
  :root {
    --cal-rows: 5;                                              /* iPhone landscape */
    --sq: min(calc((86vh - 70px) / var(--cal-rows)),           /* height-fit: N rows fill the half */
              calc((50vw - 56px) / 7));                         /* width-fit: never wider than the half */
  }
  #weekdays, #days {
    width: 100%;
    max-width: calc(var(--sq) * 7 + 24px);                     /* 7 squares + the 6 column gaps */
    margin-inline: auto;                                        /* side padding */
  }
  /* Centre the whole title + weekday + grid block as one group, so the month
     title sits just above the calendar (not pegged to the very top) with equal
     padding above and below. margin-top:auto on the FIRST item (the title) and
     margin-bottom:auto on the LAST (the grid) keeps them adjacent and centred. */
  #calendar-header { margin-top: auto; }
  #days {
    flex: 0 1 auto;                                             /* don't grow — leave room to centre */
    max-height: calc((var(--sq) + 4px) * var(--cal-rows));      /* exactly --cal-rows tall (incl. gaps) */
    margin-bottom: auto;
  }
  /* Nudge ONLY the title up a touch (the calendar grid stays centred — transform
     doesn't affect layout), so it reads a little closer to the globe's menu button
     without dragging the calendar with it. */
  #calendar-title { transform: translateY(-20px); }
}
/* iPad landscape (a touch device taller than a phone in landscape): 7 rows. */
@media (orientation: landscape) and (pointer: coarse) and (min-height: 601px) {
  :root { --cal-rows: 7; }
}
/* Desktop (mouse): 7 rows at ONE fixed, predictable content width (700px) shared
   by the month, day and detail views — the luxurious, easy-to-read desktop look.
   Overriding --sq to a fixed value (instead of the height-driven one) pins the grid
   to 700px wide (7·sq + 24px gaps); it still clamps to the half's width on a narrow
   window so it never overflows. The day/detail views reuse this same width below. */
@media (orientation: landscape) and (pointer: fine) {
  :root {
    --cal-rows: 7;
    --sq: calc(min(676px, 50vw - 56px) / 7);   /* 676 = 700 − 24px of column gaps */
  }
}
/* On a mouse/trackpad, the forced week-snap "captures" momentum and flings the
   calendar many weeks at once. Desktop scrolls FREELY instead — normal, smooth
   scrolling. Touch keeps its clean week-by-week snap (unchanged). */
@media (pointer: fine) {
  #days { scroll-snap-type: none; }
}

/* Desktop: make the calendar day NUMBERS and the weekday LETTERS bigger.
   This block sits at the very end on purpose — the base .day / #weekdays rules
   appear later in the file, and media queries don't add specificity, so an
   earlier override would lose to them. Placed last, it wins. */
@media (orientation: landscape) and (pointer: fine) {
  /* Day numbers locked to 16px on desktop; weekday labels a step down (13px),
     the usual "muted label" treatment. Title is 23px (see the desktop block
     above) — 16 × 1.44, the app's own type ratio / an augmented-fourth step. */
  .day { font-size: 16px; }
  #weekdays { font-size: 13px; }
  /* Desktop: drop the whole calendar pane a little so the day-view back
     button and header clear the globe's right edge / top chrome completely. */
  #calendar {
    padding-top: 28px;
    padding-left: 20px;
    padding-right: 20px;
  }
}

/* iPad landscape (touch) ONLY: nudge the whole calendar type up a little —
   month/day title, day numbers, and weekday letters. At the end so it wins. */
@media (orientation: landscape) and (pointer: coarse) and (min-width: 1024px) {
  #calendar-title, #day-title { font-size: 26px; }
  .day { font-size: 18px; }
  #weekdays { font-size: 15px; }
  /* Lift the month title a little higher (base landscape is -20px). */
  #calendar-title { transform: translateY(-34px); }
}

/* iPad PORTRAIT only: make just the month TITLE bigger (nothing else). */
@media (orientation: portrait) and (min-width: 601px) {
  #calendar-title { font-size: 24px; }
}

/* iPhone PORTRAIT only: the month title a little bigger, and centred in the gap
   between the globe and the weekday letters (12px padding above #calendar-header,
   18px margin below it, no gap elsewhere — splitting that 30px evenly needs +3px,
   not +6px, which used to sit it closer to the weekday row than to the globe). */
@media (orientation: portrait) and (max-width: 600px) {
  #calendar-title { font-size: 21px; transform: translateY(3px); }
  /* Five full week-rows in #days, with a few px of air under the last row so
     its day-box bottom borders aren't clipped by the scrollport edge (which
     read as a half-cropped last week above the Safari URL bar). Top week
     still sits flush; week-snap lands cleanly. */
  #days { container-type: size; }
  .week {
    height: calc((100cqh - 16px - 4px) / 5);    /* 5 rows + 4 gaps + 4px bottom air */
    margin-bottom: 4px;
  }
  .day {
    aspect-ratio: auto;                         /* height comes from the week row */
    height: 100%;
  }
  /* Flag bubbles: same size as the menu button, centred on the button's axis
     and stacked directly beneath it (same treatment as desktop). */
  #globe-trips-bubbles {
    right: 16px; left: auto; align-items: center; width: var(--globe-btn);
  }
  /* Collapsed layover = smaller nested flag; revealed = same size as the other
     trip flags on the phone (--globe-btn, same as the menu button). */
  .globe-trip-bubble-layover {
    width: calc(var(--globe-btn) * 0.75);
    height: calc(var(--globe-btn) * 0.75);
    margin-top: calc(-1 * (var(--globe-btn) * 0.75 + 10px));
  }
  .globe-trip-bubble-layover.is-shown {
    width: var(--flag-size);
    height: var(--flag-size);
    margin-top: 0;
  }
}

/* iPhone LANDSCAPE only (touch, narrower than iPad): move the month title a
   little lower. Base landscape lifts it -20px; this brings it back down. */
@media (orientation: landscape) and (pointer: coarse) and (max-width: 1023px) {
  #calendar-title { transform: translateY(-6px); font-size: 22px; }
}

/* iPhone only (portrait phone OR landscape phone — not iPad/desktop): pull the
   top chrome (menu, whale, email spinner, flag stack) closer to the status bar.
   Was safe-area + 16px — felt like a large empty band under the time/battery.
   Keep safe-area for the notch + 8px of air so it still breathes. */
@media (orientation: portrait) and (max-width: 600px),
       (orientation: landscape) and (pointer: coarse) and (max-width: 1023px) {
  #globe-menu-btn,
  #globe-logo-btn,
  .globe-processing-icon {
    top: calc(env(safe-area-inset-top, 0px) + 8px);
  }
  #globe-trips-bubbles {
    top: calc(env(safe-area-inset-top, 0px) + 8px + var(--globe-btn) + 10px);
  }
}

/* ---- Ballesta ideas (AI recommendations) ---- */
/* The "Ideas in <city>" card, and any placed idea (hotel/tour/restaurant not yet
   booked): same glass box as real entries, but a dashed outline so it reads as a
   suggestion, not a booking. */
.item-recs, .item-idea {
  border-style: dashed;
  border-width: 2px;                      /* thicker border = longer dashes, wider gaps (browsers scale both with width) */
  border-color: rgba(255, 255, 255, 0.4); /* brighter than --glass-border so "idea" vs "booked" reads at a glance */
  opacity: 0.9;
}

/* Saily eSIM + NordVPN affiliate boxes (international flights only — see
   isInternationalLeg in app.js) ride together in ONE combined slot so they
   read as a single column entry instead of two boxes scattered across
   different columns. .item-affiliate-pair takes the same perfect-square
   footprint as a normal .item (one full block); the two boxes stack inside
   it, each its own half-block container, with a small gap between. */
.item-affiliate-pair {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  aspect-ratio: 1;             /* same one-block footprint as a normal .item */
  height: auto;
  flex: none;
}
.item-sale, .item-vpn {
  aspect-ratio: auto;
  height: auto;
  flex: 1 1 0;                 /* each box = half the pair's height, minus the gap */
  min-height: 0;
  width: 100%;
  border-style: dashed;
  border-width: 2px;
  border-color: rgba(255, 255, 255, 0.4);
}

/* An affiliate offer, not a calendar entry — so it's built as its own small
   centered card instead of borrowing the entry layout (time chip on the left,
   icon beside wrapping text) that made these read as broken bookings.
   Everything is centered on one axis: brand, then one line of copy, then a
   quiet call to action. Sized in em and clamped so the whole thing still fits
   the half-block it lives in on a phone, where the pair is at its smallest. */
.item-partner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Tight by default: on a phone this box is only ~50px tall (it's half of a
     square that is itself one of three day columns), so the spacing has to
     buy room for the copy rather than spend it on air. */
  gap: 0.2em;
  padding: 0.45em 0.5em;
  text-align: center;
  overflow: hidden;
}
.partner-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  min-width: 0;
}
/* The real logo, once one is dropped into public/logos/ — capped so a wide
   wordmark and a square mark both sit comfortably in the same slot. */
.partner-logo {
  max-height: 1.5em;
  max-width: 100%;
  width: auto;
  object-fit: contain;
}
/* Icon size/colour also come from .item-icon, so the glyph matches the icon
   on every reservation card beside it. */
.partner-glyph { flex: 0 0 auto; }
/* Size comes from .item-primary / .item-detail / .item-time, which these
   elements also carry — the same type scale every reservation card uses, at
   every screen size. Only the bits that are specific to a centered offer box
   are set here. */
.partner-word {
  white-space: nowrap;
  min-height: 0;               /* the card rule reserves a line; not needed here */
}
/* Plain block on purpose. A -webkit-box line clamp here treated the inline
   "eSIM for" span as a line of its own, so "eSIM for Istanbul" broke across
   two lines even with room to spare. The box's own overflow:hidden is enough
   to keep a very long city name from spilling. */
.partner-line {
  min-height: 0;
  overflow: hidden;
  /* The column centres its children, which makes a block child shrink to fit
     its own content box rather than the space available — the copy was being
     squeezed into 55px of a 173px box and wrapping for no reason. Stretching
     it gives the text the full width; text-align on the box still centres it. */
  align-self: stretch;
}
/* HIDDEN on phones by default. Three stacked lines cannot fit ~50px, and of
   the three the call to action is the one worth losing: the brand and the one
   line saying what it's for still carry the offer, and the whole box is
   tappable anyway. Restored on the desktop layout below, where there's room. */
/* Hidden on a phone column for the same reason as the call to action: there
   isn't room, and the brand above already carries the meaning. */
.partner-what { display: none; }

.partner-cta {
  display: none;
  white-space: nowrap;
  /* .item-time reserves space under itself for the title that normally
     follows it on a card; here it's the last line, so that space is dropped. */
  margin-bottom: 0;
}
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  .item-partner { gap: 0.3em; padding: 0.7em 0.6em; }
  .partner-what { display: inline; }
  .partner-cta { display: block; }
  /* A mouse gets a hint the arrow leads off-app. */
  .item-partner:hover .partner-cta { color: rgba(255, 255, 255, 0.85); }
}

/* The ideas page fills the calendar quadrant (inside #detail-body). */
.recs-head { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
.recs-head .rec-icon svg { width: 24px; height: 24px; }
/* "Fill in ideas": desktop only for now (there's room there; phone can come later) —
   same breakpoint the day view already uses to detect a desktop layout. */
.fill-ideas-btn {
  display: none;
  margin-left: auto;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.fill-ideas-btn:active { background: var(--press); transform: scale(0.96); }
@media (orientation: landscape) and (pointer: fine) {
  .fill-ideas-btn { display: inline-flex; }
}
.recs-title { font-size: 17px; font-weight: 600; }
.recs-sub { font-size: 13px; opacity: 0.6; margin-top: 1px; }
.rec { display: flex; gap: 10px; padding: 10px 2px; border-bottom: 1px solid rgba(255,255,255,0.08);
       color: inherit; text-decoration: none; }
.rec:last-child { border-bottom: none; }
.rec-text { display: block; }
.rec-icon { flex: none; margin-top: 2px; }
.rec-icon svg { width: 18px; height: 18px; }
.rec-name { display: block; font-weight: 600; }
.rec-meta { display: block; font-size: 12px; opacity: 0.6; margin-top: 1px; }
.rec-why { display: block; font-size: 13px; opacity: 0.85; margin-top: 3px; }
.rec-note { font-size: 14px; opacity: 0.75; padding: 6px 0; }
.recs-divider { font-size: 12px; opacity: 0.55; margin: 14px 0 2px; padding-top: 10px;
                border-top: 1px solid rgba(255,255,255,0.1); }

/* Single-idea detail panel (openIdeaDetail): the pitch sits above its own "Find & book"
   row, both reusing the .rec-* classes above. */
.idea-detail-why { margin: 4px 2px 14px; }
.idea-book-link { border-top: 1px solid rgba(255,255,255,0.08); border-bottom: none; }

/* Real Google photos of the place (openIdeaDetail's .idea-photos-slot).
   Stacked vertically (no sideways filmstrip) so the app only scrolls down.
   The photos stay the one deliberately colorful thing in this monochrome
   panel — same exception the globe gets. */
.idea-photos {
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow-x: hidden;
  overflow-y: visible;
  margin: 10px 0 14px;
  max-width: 100%;
}
.idea-photo {
  display: block;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--r-chip);
  border: 1px solid var(--glass-border);
}

/* A one-line historical/cultural aside under a recommendation — quieter and
   smaller than the "why", so it reads as a bonus, not competing information. */
.rec-fact { display: block; font-size: 12px; opacity: 0.55; font-style: italic; margin-top: 3px; }

/* ---- Side trips (Ideas panel): nearby cities worth a detour during a long stay ---- */
.sidetrip-list { display: flex; flex-direction: column; gap: 14px; margin-top: 10px; }
.sidetrip-card {
  padding: 12px;
  border-radius: var(--r-card, 16px);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
}
.sidetrip-head { display: flex; gap: 10px; align-items: flex-start; }
.sidetrip-name { font-size: 15px; font-weight: 600; }
.sidetrip-why { font-size: 13px; opacity: 0.8; margin-top: 2px; }
.sidetrip-transport { font-size: 13px; opacity: 0.85; margin: 8px 0 0; }
.sidetrip-acts { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.sidetrip-act { font-size: 13px; }
.sidetrip-act-name { font-weight: 600; }
.sidetrip-act-why { opacity: 0.75; margin-left: 4px; }
.sidetrip-add-btn {
  margin-top: 10px;
  width: 100%;
  padding: 9px;
  border-radius: var(--r-chip);
  font-size: 13px;
  font-weight: 600;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: var(--flat-shadow);
  transition: transform var(--dur-tap) var(--ease-snap), background 0.15s ease;
}
.sidetrip-add-btn:active { background: var(--press); transform: scale(0.98); }
.sidetrip-add-btn:disabled { opacity: 0.6; }

/* Desktop-only type bump for the ideas/recommendations panel (X.com-style
   hierarchy: a handful of discrete steps — 15 / 14 / 13px — with tiers told
   apart mostly by weight and opacity, not by shrinking size further; X almost
   never drops real reading content below 13px, reserving anything smaller for
   throwaway footer text). The base sizes above are phone-tuned; on a real
   desktop (mouse, roomy screen) they read as too small — especially the "why"
   explanation under each idea, which is genuine reading text and deserves the
   recommendation's own body size, not a shrunken caption. */
@media (orientation: landscape) and (min-width: 1024px) and (pointer: fine) {
  .recs-title { font-size: 19px; }
  .recs-sub { font-size: 14px; opacity: 0.65; }
  .rec-meta { font-size: 13px; }
  .rec-why { font-size: 15px; opacity: 0.88; line-height: 1.4; }
  .rec-note { font-size: 15px; }
  .rec-fact { font-size: 13px; }
  .recs-divider { font-size: 13px; }
  .sidetrip-name { font-size: 16px; }
  .sidetrip-why { font-size: 15px; opacity: 0.85; line-height: 1.4; }
  .sidetrip-transport { font-size: 14px; }
  .sidetrip-act { font-size: 14px; }
  .sidetrip-add-btn { font-size: 14px; }
}


/* ---------------------------------------------------------------------------
   THE THINKING ORB — what the AI shows instead of the word "Thinking…"

   A little globe made of white dots, turning slowly. It is deliberately the
   SAME idea as the big globe this whole app is built around: white points of
   light on black, nothing coloured, nothing busy. The alternative in the
   reference (a sphere of dense vertical lines) reads heavier and more
   mechanical, which fights the "subtle, never distracting" rule.

   Built from plain divs and one CSS animation — no library, no canvas, no
   JavaScript running every frame. The browser rotates ONE element on the GPU
   and the dots come along for the ride, which is what keeps it smooth on a
   phone and free of battery cost.
   --------------------------------------------------------------------------- */
.ai-thinking {
  display: flex;
  align-items: center;
  gap: 9px;
}
.ai-orb {
  position: relative;
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  /* Shallow perspective — enough for the front dots to read as nearer without
     the sphere looking like a funnel. */
  perspective: 64px;
}
/* The faint bloom the dots sit in, so the orb reads as a lit object rather
   than a scatter of specks. Same restraint as the stars on the globe. */
.ai-orb::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.11), transparent 68%);
}
.ai-orb-sphere {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  animation: ai-orb-spin 4.2s linear infinite;
}
.ai-orb-dot {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1.8px;
  height: 1.8px;
  margin: -0.9px 0 0 -0.9px;
  border-radius: 50%;
  background: #fff;
  /* Bright enough to stay a POINT at 20px. Dimmer than this and the dots turn
     grey and smear together into a fuzzy ball. */
  opacity: 0.9;
}
/* Tilted a little off the vertical so it turns like a planet rather than a
   spinning coin. */
@keyframes ai-orb-spin {
  from { transform: rotateX(-16deg) rotateY(0deg); }
  to   { transform: rotateX(-16deg) rotateY(360deg); }
}
.ai-orb-label {
  color: rgba(255, 255, 255, 0.62);
  animation: ai-orb-breathe 2.6s ease-in-out infinite;
}
@keyframes ai-orb-breathe {
  0%, 100% { opacity: 0.52; }
  50%      { opacity: 0.92; }
}
/* Someone who has asked their phone to stop animating things means it. The
   orb stays — it's the thing telling them we're working — it just stops
   drawing attention to itself. */
@media (prefers-reduced-motion: reduce) {
  .ai-orb-sphere { animation-duration: 18s; }
  .ai-orb-label { animation: none; opacity: 0.7; }
}
