  :root {
    /* --color-accent mirrors the Python CARD_ACCENT_COLOR constant
       (render.py) as a literal - there's no mechanism for HTML_SHELL's
       .format()-based templating to pull a Python constant into an
       arbitrary CSS position, so if CARD_ACCENT_COLOR ever changes,
       update this literal too. */
    --color-accent: #f0512e;
    /* Core neutral/text palette, extracted 2026-08-07 (design-system
       Phase 3b). These are the exact literals already in heaviest use
       across the file - tokenized here so a future palette change is
       one edit, not a find-replace across hundreds of call sites. This
       first pass is a pure alias (token value == the literal it
       replaces), so it changes zero pixels; collapsing the near-
       duplicate off-palette browns/oranges into these is a separate,
       reviewable step. */
    --color-ink: #33261e;      /* darkest body text / headings */
    --color-body: #6b5e52;     /* standard body-text brown */
    --color-muted: #9c8f82;    /* secondary / muted text */
    --color-border: #d8d4cc;   /* light tan hairline borders */
    --color-close-bg: #f5efe1; /* circular close-button disc */
    /* Corner-radius scale, extracted 2026-08-07 (design-system Phase
       3b). Step 1 = the unambiguous "fully round" family only:
       --radius-pill absorbs 999px AND the 100px/200px variants (all
       render identically as a full pill on the short/wide elements
       they're used on - buttons, inputs, pill rows - a radius only
       differs below ~2x the element's own smaller dimension). Pure
       alias, zero pixels changed. The numeric mid-scale (6/8/12/16px
       etc.) is a separate reviewed step. */
    --radius-pill: 999px;
    --radius-circle: 50%;
    /* Numeric radius scale (step 2), named 2026-08-07. Pure alias -
       each token is the exact value already in use, so nothing moves;
       this just centralizes the scale. The steps are granular (2px
       apart through the middle) because that's what the app grew
       organically - lg/xl/2xl/3xl (10-16px) sit close and are a fair
       candidate to tighten later, but tightening moves pixels so it's
       a separate reviewed decision, not this pass. The rare one-off
       radii (2/3/4/5/15/18/22/24px, 1-4 uses each) are intentionally
       left literal, not dignified with a scale name. */
    --radius-sm: 6px;
    --radius-md: 8px;      /* default - the app's workhorse radius */
    --radius-lg: 10px;
    --radius-xl: 12px;
    --radius-2xl: 14px;
    --radius-3xl: 16px;
    --radius-4xl: 20px;
    --radius-5xl: 28px;
    /* Elevation shadow scale, extracted 2026-08-07 (design-system Phase
       3b). Collapses ~55 near-identical card/panel shadows (~30 unique
       values, mixing warm-brown and muddy black) onto 4 warm-brown
       steps - the warm family reads cleaner on the cream background.
       Approved against a rendered scale + in-context examples. NOT
       touched: the orange accent/button shadows, the directional
       drawer/sheet edge shadows, border-via-shadow insets, focus
       glows, and explicit `none` - all legitimately their own thing. */
    --shadow-sm: 0 1px 3px rgba(80,60,40,0.10);
    --shadow-md: 0 4px 16px rgba(80,60,40,0.10);
    --shadow-lg: 0 8px 24px rgba(80,60,40,0.12);
    --shadow-xl: 0 20px 40px rgba(80,60,40,0.16);
    /* Spacing scale (2px grid), extracted 2026-08-07 (design-system
       Phase 3b). The app uses a real 2px-step scale, not a 4px grid -
       these name the even steps in actual use. Swapped into single-
       value padding/gap declarations (pure alias, zero pixels changed);
       multi-value shorthands and margins stay literal (shorthands read
       worse as var() soup; negative margins are precise positioning).
       Rare large one-offs (30/40/50px) left literal - not scale steps. */
    --space-2: 2px;
    --space-4: 4px;
    --space-6: 6px;
    --space-8: 8px;
    --space-10: 10px;
    --space-12: 12px;
    --space-14: 14px;
    --space-16: 16px;
    --space-18: 18px;
    --space-20: 20px;
    --space-22: 22px;
    --space-24: 24px;
    --space-26: 26px;
    --space-28: 28px;
    --space-32: 32px;
    /* Type scale, extracted 2026-08-07 (design-system Phase 3b). The
       app had 33 font-sizes stepping in 0.5px increments - real drift,
       not a deliberate scale. Collapsed to 12 whole-px steps. The body
       range (12-16) stays granular on purpose: 13/14/15/16 are each
       heavily used for distinct text roles, so forcing them together
       would visibly resize huge amounts of text for little gain. What
       got removed: every 0.5px variant (snapped to nearest whole,
       imperceptible) and the rare one-off large sizes (21/22/23/25/26/
       27/30 -> nearest step). Pixel-named (--text-13) like --space-*,
       since a 12-step scale doesn't map cleanly to t-shirt names. */
    --text-10: 10px;
    --text-11: 11px;
    --text-12: 12px;
    --text-13: 13px;
    --text-14: 14px;
    --text-15: 15px;
    --text-16: 16px;
    --text-18: 18px;
    --text-20: 20px;
    --text-24: 24px;
    --text-32: 32px;
    --text-48: 48px;
    --color-greeting: var(--color-body);
    --ease-decel: cubic-bezier(0.16, 1, 0.3, 1);
    /* User-chosen avatar background (see the Profile tab's avatar-
       color-row/selectAvatarColor) - falls back to --color-accent (the
       original fixed avatar color) whenever no profiles.avatar_color is
       set, via var()'s own fallback rather than a JS default, so an
       unauthenticated visitor's avatar circle needs no JS at all to
       look right. */
    --user-avatar-color: var(--color-accent);
    /* Same value .nav-drawer's own desktop width media query uses -
       shared via custom property (rather than repeating the literal in
       both places) so #app-shift-wrap's push-over margin (a sibling of
       .nav-drawer, not a descendant of it) always matches the drawer's
       real rendered width exactly. Only ever consumed inside a
       min-width:900px block - harmless to leave defined at every width. */
    --nav-drawer-desktop-w: clamp(300px, 28vw, 360px);
  }
  @media (prefers-reduced-motion: reduce) {
    .bg-cloud, .start-logo, .start-tagline-line, .app-bg, .home-content-fade {
      animation: none !important;
      transition: opacity 0.2s ease !important;
      transform: none !important;
    }
  }
  /* Form controls don't inherit body's font by default in any browser
     (textarea in particular falls back to a UA-stylesheet monospace,
     not body's own sans stack) - every text field/dropdown/button in
     the app gets Baloo 2 explicitly here so none of them silently read
     as the browser's system font. A specific selector elsewhere (e.g.
     a bold 700-weight button) still wins over this on weight/size, only
     the family was missing app-wide. */
  input, textarea, select, button {
    font-family: 'Baloo 2', sans-serif;
  }
  /* Field active state, per Figma's Components frame (node 434:4661):
     accent border + soft orange glow, fading in on focus rather than
     snapping - applies app-wide so every text field (however each one's
     resting style is defined) picks up the same active treatment. */
  input[type="text"], input[type="email"], input[type="password"],
  input[type="date"], input[type="time"], textarea, select {
    transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
  }
  input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus,
  input[type="date"]:focus, input[type="time"]:focus, textarea:focus, select:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 11px rgba(240,81,46,0.66);
  }
  body {
    font-family: 'Google Sans Flex', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    max-width: 480px;
    margin: 0 auto;
    padding: 24px 16px;
    background: #fbf3e7;
    color: #2c2c2a;
    /* Targeted attempt at "tapping a date pill scrolls to the right
       spot, then drifts back up slightly" - this is the classic
       signature of iOS's elastic/rubber-band overscroll kicking in
       when a programmatic smooth-scroll's target lands at or near the
       page's max scrollable position, then snapping back once the
       animation "overshoots" past the real bottom. Applies to every
       browser on iOS, not just Safari - Apple requires all of them
       (Chrome included) to run on the same WebKit engine underneath,
       so this isn't a Safari-only fix. contain stops that bounce from
       happening on body's own scroll without changing any layout/
       measurement - unconfirmed against the real device yet, flagged
       here in case it needs revisiting. */
    overscroll-behavior-y: contain;
  }
  /* Trip detail screens (Itinerary/Expenses/Ideas) get a flat page
     background instead of the cream body default - toggled on body in
     showTripCategory/showHomeScreen (see .app-bg-hidden just below) so
     the Start/Home screens' own illustrated background is completely
     untouched by this. Compound selector beats the plain `body` rule
     above regardless of source order. #fcf7f0 (not the previous pale
     blue) plus hiding .app-bg entirely - not just its scenery, see
     .app-bg-hide-scenery elsewhere - matches the flat, illustration-
     free canvas every Itinerary v3 Figma frame uses; the Home Screen's
     own app-bg gradient is untouched since it never gets this class. */
  body.trip-screen-bg {
    background: #fcf7f0;
  }
  body.trip-screen-bg .app-bg {
    display: none;
  }
  h1 { font-family: 'Baloo 2', sans-serif; font-size: var(--text-18); font-weight: 700; }
  /* Header styling copied from the Itinerary Figma file - #fbf3e7
     (see body, above) as the cream page background everything else
     sits on top of. Flush square-bottomed white bar with no shadow of
     its own on every screen - on a trip screen, .trip-subtabs
     (below) continues this same white surface with no gap, and is
     the one that actually gets the rounded-bottom-corners + soft
     shadow treatment, so the two read as one continuous white card
     rather than two separate stacked boxes. On the home screen
     (no .trip-subtabs at all) this just ends flush, which reads
     fine on its own. */
  .page-header {
    padding: 14px 16px 18px;
    margin: -24px -16px 0;
    background: #675f55;
  }
  /* Back arrow removed on mobile too now (direct feedback) - was
     already desktop-only-hidden below since the logo/avatar row plus
     the rest of that screen's own navigation already covered getting
     back to Home there; the mobile nav drawer's hamburger trigger
     (first button in this same row) covers the same job here, so the
     reasoning now applies at every width. JS only ever toggles this
     button's `visibility` (see showTripCategory/showHomeScreen), never
     `display`, so this unconditional rule can't be fought by an inline
     style the way a JS-driven `display` toggle could. */
  .page-header .trip-title-back-btn { display: none; }
  /* Avatar (button.page-header-avatar-btn, below where .home-avatar-btn
     is defined) and the trip-switcher chevron (.trip-switcher-btn's own
     rule, further down) are also removed on mobile - see their own
     rules for why, kept next to what they're overriding instead of
     here so cascade order can't quietly undo them again. */
  /* Title stacks to two lines on mobile now (trip name, then date
     directly below, no separator) instead of one run-on line with a
     bullet between them (direct feedback) - reverts to this card's
     ORIGINAL two-line design (see .page-title-sub's own history
     comment on why it went inline in the first place) but mobile-only
     this time; desktop keeps the inline treatment (see the >=900px
     override below), which has more width to spare. Scoped by
     #page-title specifically (not the shared .page-title-main/-sub/
     -bullet classes bare) so this doesn't reach into the Overview
     card's own reuse of .page-title-main or the trip-switcher
     dropdown's reuse of all three - both need to stay exactly as they
     are. */
  #page-title .page-title-main, #page-title .page-title-sub { display: block; }
  #page-title .page-title-bullet { display: none; }
  /* On desktop this becomes the exact same white bar as
     .home-custom-header - same padding, no curve, pinned in place -
     not just its logo+avatar dropped onto the old dark bar (that read
     as a mismatched patch). The back button disappears entirely here
     (the logo/avatar row plus the rest of this page's own navigation
     already cover getting back to the Home Screen); the trip title
     recolors to the same dark-on-light scheme the rest of this page
     already uses instead of the white-on-dark scheme the mobile dark
     bar needs. Compound selectors throughout so these reliably beat
     each element's own base color rule regardless of source order.
     Sticky, with .trip-subtabs/.itinerary-sticky-stack/
     .idea-category-tabs/.trip-map-col all shifted down by this bar's
     own live-measured height (--page-header-h, see
     syncStickyHeaderOffsets) so the whole stack pins cleanly instead
     of overlapping. */
  @media (min-width: 900px) {
    .page-header {
      /* Without this, the 50px padding above adds on top of the
         element's existing full-bleed width (see .page-header's own
         margin trick) instead of being carved out of it, overflowing
         the viewport by 100px and forcing a horizontal scrollbar. */
      box-sizing: border-box;
      padding: 32px 50px 36px;
      background: #fff;
      border-radius: 0;
      box-shadow: var(--shadow-md);
      position: sticky;
      top: 0;
      z-index: 20;
    }
    /* trip-title-back-btn stays hidden here too now - see its own
       unconditional rule above, which already covers desktop. */
    /* Same dark-on-light flip .trip-switcher-btn gets right below -
       the hamburger trigger is white against the mobile dark bar
       (.page-header's default background) but needs to become dark
       once that bar turns white at this width. */
    .page-header .nav-drawer-trigger { color: #2c2c2a; }
    .page-header .page-title-main { color: #2c2c2a; }
    .page-header .page-title-sub,
    .page-header .page-title-bullet { color: #6b6a63; }
    /* Real bug: the switcher arrow (added after this block already
       existed) never got its own dark-on-light override here, so on
       desktop specifically it stayed white-on-white against this same
       header's white background - functionally present (every click
       handler worked) but genuinely invisible, not just easy to miss. */
    .page-header .trip-switcher-btn { color: #2c2c2a; display: flex; }
    /* Title-stacking change above (direct feedback) was mobile-only -
       restored to its original desktop treatment (inline title/date/
       bullet) here. The avatar's own desktop restore isn't here - see
       button.page-header-avatar-btn's mobile-hide rule further down
       (near .home-avatar-btn) for why it has to sit AFTER that rule in
       source order instead. */
    #page-title .page-title-main, #page-title .page-title-sub { display: inline; }
    #page-title .page-title-bullet { display: inline; }
  }
  .page-header-inner {
    display: flex;
    align-items: center;
    gap: var(--space-8);
  }
  .page-header h1 {
    margin: 0;
    text-align: center;
  }
  /* Wraps the title itself (unchanged) plus the new trip-switcher arrow
     (see #trip-switcher-btn) - takes over the flex:1 the h1 used to
     claim directly against .page-header-inner, so the header's overall
     balance (logo/back-btn on one side, avatar on the other) is
     unaffected by this new wrapper existing. Real bug: the h1 itself
     ALSO kept its own flex:1, which made it (not this wrapper) claim
     all the extra width - centering the title text in that full-width
     box while the arrow sat tight against its right edge, nowhere near
     the date. Only .page-title-wrap should grow; the h1+arrow pair
     inside it should size to content and get centered as one group. */
  .page-title-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
    gap: var(--space-2);
    min-width: 0;
    position: relative;
  }
  .trip-switcher-btn {
    background: none;
    border: none;
    color: #ffffff;
    padding: var(--space-2);
    cursor: pointer;
    /* Hidden on mobile (direct feedback) - the nav drawer's own Trips
       list already offers a way to jump between trips in the same
       category, so this in-header control was redundant there.
       _updateTripSwitcher (JS) clears this button's inline style
       (`btn.style.display = ''`) rather than setting an explicit value
       whenever more than one trip exists in the category, so this
       class-level rule is what actually decides visibility in that
       case - no inline-style fight to worry about. Restored to flex on
       desktop (see the >=900px override, .page-header .trip-switcher-
       btn's own color-flip rule, which now also sets display back). */
    display: none;
    flex-shrink: 0;
  }
  .trip-switcher-btn .material-symbols-outlined {
    font-size: var(--text-20);
    transition: transform 150ms ease;
  }
  .trip-switcher-btn-open .material-symbols-outlined {
    transform: rotate(180deg);
  }
  .trip-switcher-menu {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 8px;
    background: #fff;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-lg);
    padding: var(--space-6);
    min-width: 220px;
    max-width: 90vw;
    z-index: 50;
  }
  .trip-switcher-item {
    display: block;
    width: 100%;
    text-align: left;
    font-family: inherit;
    font-size: var(--text-14);
    color: #3a352c;
    background: none;
    border: none;
    border-radius: var(--radius-md);
    padding: 10px 12px;
    cursor: pointer;
  }
  .trip-switcher-item:hover {
    background: #f7f2ea;
  }
  /* .page-title-main/-sub/-bullet default to white text (readable
     against the header's own dark background) - this menu reuses the
     exact same .trip-title-text markup on a white dropdown instead, so
     all three need dark overrides here or the city name would render
     white-on-white. */
  .trip-switcher-item .page-title-main {
    color: #3a352c;
    font-weight: 700;
  }
  .trip-switcher-item .page-title-bullet {
    color: var(--color-muted);
    margin: 0 6px;
  }
  .trip-switcher-item .page-title-sub {
    color: var(--color-muted);
  }
  /* Logo (same element/markup as .home-custom-header-inner) only ever
     shows up here on desktop - on mobile this row is already tight with
     just the back button and a (sometimes two-line) trip title, and the
     Home Screen is one tap away via the back button anyway. The avatar
     stays visible at every width though - it's the only way to reach
     Account settings from inside a trip, and a user's own initials/photo
     are a small enough circle not to fight the title for space.
     Compound selector (not just .page-header-logo alone) so this
     reliably beats .home-logo-small's own base rule regardless of which
     one ends up later in the stylesheet. */
  .page-header-logo.home-logo-small {
    display: none;
  }
  @media (min-width: 900px) {
    .page-header-logo.home-logo-small { display: block; }
  }
  .page-title-main {
    display: inline;
    color: #ffffff;
  }
  /* Inline with the trip name (was its own block/line below it) - the
     separator (·) lives in the Python-side string that builds this
     span (see the trip-title assembly), not here - see .page-title-
     bullet for its own spacing. */
  .page-title-sub {
    display: inline;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-15);
    font-weight: 600;
    color: #efebe6;
  }
  /* 20px clear of the trip name on one side and the date on the other -
     was just a plain space-dot-space in the string, too tight. */
  .page-title-bullet {
    display: inline;
    margin: 0 20px;
    color: #efebe6;
  }
  .home-screen-hidden { display: none; }
  /* Independent of body's own max-width, which a >=900px media query
     further down (see .trip-section's two-column desktop layout)
     intentionally clears to `none` for the itinerary view - without
     its own cap here, the Home Screen's trip card would stretch to
     the full browser width on a wide desktop window. */
  #home-screen {
    position: relative;
    max-width: 620px;
    margin: 0 auto;
  }
  /* No background of its own anymore - the Home Screen now shows the
     exact same shared .app-bg-sky gradient the Start Screen uses
     (carried straight over, rather than a second, differently-toned
     gradient of its own), so the two screens read as one continuous
     background rather than crossfading between two different looks.
     The header itself is NOT a separate rounded/shadowed panel (see
     .home-custom-header) - it reuses .page-header's own flush,
     shadow-less white-bar treatment instead, so the two screens'
     headers read as the same surface too. */
  .page-header-hidden { display: none; }
  /* .home-custom-header (see HTML_SHELL/TRIPS_PAGE_TEMPLATE) used to be
     rendered inside #home-screen and inherit its visibility for free
     from #home-screen's own home-screen-hidden toggle - now a sibling
     of .page-header instead (both permanent, full-width, un-pushed
     rows above #app-shift-wrap - see .nav-drawer's own "top:" comment
     for why), so it needs this same explicit hidden class, toggled in
     step with .page-header-hidden by showHomeScreen/showTripCategory. */
  .home-header-hidden { display: none; }

  /* Shared decorative illustration behind the Start Screen, the Home
     Screen, and the itinerary view too - mounted once and left in
     place across the Stage-2 transition rather than being duplicated
     per screen. Fixed, so it stays put in the viewport as the
     itinerary view scrolls (rather than scrolling away with the
     document), matching the Home Screen's own always-visible
     background instead of only showing once at the top. */
  .app-bg {
    position: fixed;
    top: 0;
    bottom: 0;
    /* Real bug, only visible above a 620px viewport (every mobile test
       naturally stays under that): the old `inset:0` (pins left:0 AND
       right:0) plus `max-width:620px; margin:0 auto` never actually
       centers - browsers resolve left/right first (auto margins
       collapse to 0 since width:auto), THEN clamp the box to max-width
       without redistributing the freed space back into the margins.
       The box stayed flush against the left edge with the body's own
       plain background showing through the gap on the right - masked
       for a long time by the illustration art's own irregular edges,
       glaring now that .app-bg-sky is a flat rectangle. left:50% +
       translateX(-50%) is the reliable centering trick for a box whose
       resolved width isn't known ahead of time (100% clamped by
       max-width), unlike the margin:auto version above. */
    left: 50%;
    width: 100%;
    max-width: 620px;
    transform: translateX(-50%);
    /* Negative, not 0 - a positioned element at z-index:0 paints AFTER
       (in front of) plain in-flow content per CSS stacking order (that
       only holds a "z-index:auto" level, not stacking-context level
       0), so this was covering ordinary cards/content that have no
       z-index of their own instead of sitting behind them. -1
       unambiguously stays behind all normal-flow content. */
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
  }
  .app-bg > div {
    position: absolute;
  }
  /* Full strength on the Start/Home screens; dialed back to a faint
     wash behind the itinerary view instead of being fully hidden
     there (see showTripCategory/showHomeScreen) - transition makes
     that handoff a fade rather than an abrupt swap. */
  .app-bg-faded {
    opacity: 0.22;
    transition: opacity 400ms ease;
  }
  .app-bg-sky {
    position: absolute;
    inset: 0;
    /* Explicit white backing behind the gradient - the blue stop is
       translucent by design, so without this it was blending against
       the body's warm eggshell background (#fbf3e7) showing through
       instead of white, reading as a darker, muddier blue. Matches the
       Figma "Sign in - Desktop" frame's own base gradient - on THIS
       screen (unlike Home/Itinerary, see .app-bg-hide-scenery below)
       the dune-hill illustration is still visible and is what actually
       supplies the warm cream tone lower down, so this stays a plain
       soft blue wash rather than fading to cream itself - doing both
       would double up and look muddy. */
    background-color: #fff;
    background-image: linear-gradient(180deg, rgba(104,149,219,0.22) 0%, rgba(104,149,219,0.22) 15%, rgba(104,149,219,0.08) 55%, #fff 85%);
  }
  /* Home/Itinerary only (illustration hidden, see .app-bg-hide-scenery
     itself) - real bug caught by a direct side-by-side screenshot
     comparison (Figma left, this page right): the flat CSS pulled from
     Figma's Dev Mode inspector for the equivalent "Desktop - 1" (Trips)
     frame (a sharp ~4-point transition around 60-64%, ending in pure
     white) only describes ONE background rectangle layer, shared with
     the Sign-in frame above - the actual composited Trips frame the
     user sees in Figma also has the same soft blurred ellipse shapes
     layered on top of it (just without an illustration in front to
     read as intentional), producing a much longer, smoother fade that
     ends in a warm cream, not white. Rather than reproduce those extra
     layers (the user explicitly wants just a flat gradient here, no
     illustration shapes), these stops are colors sampled directly from
     the two screenshots at 5%-height increments down the frame,
     reconstructing the same visual fade with a single gradient. */
  .app-bg-hide-scenery .app-bg-sky {
    background-color: #f1e5d7;
    background-image: linear-gradient(180deg, #ccd8ec 0%, #f1e5d7 78%);
  }
  .app-bg svg { display: block; width: 100%; height: 100%; }
  .bg-dune-back, .bg-dune-mid { overflow: hidden; }
  .bg-dune-back-inner, .bg-dune-mid-inner { width: 100%; height: 100%; }
  /* Matches Figma's own rotate+flip on these two layers exactly (the
     source shapes are drawn "upside down" relative to how they should
     sit as background hills) - Ellipse 3 (back) is rotate(-177.1deg)
     scaleY(-1), Ellipse 2 (mid) is rotate(180deg) scaleY(-1), applied
     in that order to match Tailwind's fixed rotate-then-scale
     composition order. */
  .bg-dune-back-inner { transform: rotate(-177.1deg) scaleY(-1); }
  .bg-dune-mid-inner { transform: rotate(180deg) scaleY(-1); }
  .bg-dune-back { left: -60%; top: 72.2%; width: 212%; height: 42.1%; }
  .bg-dune-mid { left: -45.7%; top: 61.1%; width: 189.8%; height: 45.2%; }
  .bg-dune-front { left: -24.6%; top: 73.2%; width: 127.2%; height: 29.9%; }
  .bg-skyline { left: 55.5%; top: 56.8%; width: 51.3%; height: 10.7%; }
  .bg-tree-1 { left: 10%; top: 67.9%; width: 13.4%; height: 17.5%; }
  .bg-tree-2 { left: 21.5%; top: 75.1%; width: 9.2%; height: 12.1%; }
  /* Trees/skyline/dune hills/clouds hidden on the Home Screen and the
     Itinerary/Ideas views alike (see showTripCategory/showHomeScreen
     toggling this class) per request - just the flat .app-bg-sky
     gradient stays, matching the Figma reference exactly. */
  .app-bg-hide-scenery .bg-skyline,
  .app-bg-hide-scenery .bg-tree,
  .app-bg-hide-scenery .bg-dune,
  .app-bg-hide-scenery .bg-cloud {
    display: none;
  }
  /* Home Screen only (see showHomeScreen/_hideAppBgScenery) - the
     gradient bleeds the full viewport width above 620px instead of
     staying capped/centered like every other screen, matching the
     Figma "Desktop - 1" frame's own full-bleed background exactly
     (direct feedback: the cream body color showing on either side of
     the centered box read as a bug, not a deliberate margin). NOT
     applied to the Itinerary/Ideas views (.app-bg-hide-scenery alone,
     without this) - those deliberately keep the narrower, content-
     column-tracking illustration once a trip's map column claims the
     rest of a wide desktop viewport (see the @900px override below). */
  .app-bg-full-bleed {
    left: 0;
    width: 100%;
    max-width: none;
    transform: none;
  }
  /* Fully hides the shared illustration on trip detail screens (see
     body.trip-screen-bg above) - Start/Home screens never get this
     class, so their illustration is unaffected. */
  .app-bg-hidden {
    display: none;
  }
  /* Moved closer in/up from its old -6%/31.2% spot (mostly hidden,
     peeking from behind the left edge) so it now sits near the
     greeting text instead of off past the illustration's own edge -
     see .home-content-inset's own comment for where that text lands. */
  .bg-cloud-1 { left: 6%; top: 20%; width: 27.4%; height: 5.8%; }
  .bg-cloud-2 { left: 84.3%; top: 46.6%; width: 19.2%; height: 4.6%; }
  /* Same top band as bg-cloud-1 (20%) but on the right side, for a
     balanced pair near the greeting instead of just the one cloud on
     the left - bg-cloud-2 stays where it was, lower and further right. */
  .bg-cloud-3 { left: 68%; top: 17%; width: 19.2%; height: 4.6%; }
  @media (prefers-reduced-motion: no-preference) {
    .bg-cloud {
      opacity: 0.6;
      transform: translateX(-20px);
      animation: cloud-settle 800ms var(--ease-decel) forwards,
                 cloud-drift 42s ease-in-out 800ms infinite;
    }
    .bg-cloud-2 { animation-delay: 100ms, 900ms; }
    .bg-cloud-3 { animation-delay: 200ms, 1400ms; }
    @keyframes cloud-settle {
      to { opacity: 1; transform: translateX(0); }
    }
    /* Was a barely-perceptible 10px sway over 18s - widened and slowed
       way down (now a real ~90px round trip over 42s) so the drift
       actually reads as clouds slowly moving, not a subtle wobble.
       Starts/ends at 0 (not the endpoints) so the handoff from
       cloud-settle above lands smoothly instead of jump-cutting to a
       mid-sway position. Still a back-and-forth sway rather than a
       true one-directional endless drift - the illustration itself is
       a fixed-width, bounded scene (not a repeating/tiled background),
       so a cloud drifting off one edge for good would just leave empty
       sky rather than looping back in from the other side. */
    @keyframes cloud-drift {
      0% { transform: translateX(0); }
      25% { transform: translateX(45px); }
      75% { transform: translateX(-45px); }
      100% { transform: translateX(0); }
    }
  }

  .start-screen {
    position: fixed;
    top: 0;
    bottom: 0;
    /* See .app-bg's own comment above - inset:0 + max-width + margin:auto
       doesn't actually center above a 620px viewport, same fix. */
    left: 50%;
    width: 100%;
    max-width: 620px;
    transform: translateX(-50%);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    /* Roughly matches Figma's own logo position (~20% down the frame)
       rather than dead-centering it in the viewport. */
    padding-top: 22%;
    /* No background of its own (see .app-bg-sky) - transparent here so
       the shared illustration behind it (sky + dunes/trees/clouds)
       shows through around the logo/tagline instead of being covered
       by an opaque layer in front of it. */
    /* Defense-in-depth: force-hides itself ~5s after paint even if the
       Stage-2 JS never runs at all (e.g. a script error elsewhere in
       the page) - nothing else on this fixed, full-bleed, top-most
       layer can otherwise be dismissed without JS. */
    animation: start-screen-force-hide 0s 5s forwards;
  }
  @keyframes start-screen-force-hide {
    to { opacity: 0; pointer-events: none; }
  }
  .start-screen-exit {
    transition: opacity 350ms ease;
    opacity: 0;
    pointer-events: none;
  }
  html.skip-splash .start-screen { display: none; }
  .start-logo {
    position: relative;
    z-index: 1;
    width: 251px;
    max-width: 62%;
  }
  .start-tagline {
    position: relative;
    z-index: 1;
    text-align: center;
    margin-top: 18px;
  }
  .start-tagline-line {
    font-family: 'Baloo 2', sans-serif;
  }
  .start-tagline-line-1 {
    font-size: var(--text-18);
    font-weight: 400;
    color: var(--color-greeting);
  }
  .start-tagline-line-2 {
    font-size: var(--text-24);
    font-weight: 700;
    /* Same brown as the logo wordmark itself (var(--color-greeting) =
       #6b5e52 = the logo SVG's own fill color), not a separate darker
       near-black tone. */
    color: var(--color-greeting);
  }
  @media (prefers-reduced-motion: no-preference) {
    .start-logo, .start-tagline-line {
      opacity: 0;
      animation-duration: 550ms;
      animation-timing-function: var(--ease-decel);
      animation-fill-mode: forwards;
    }
    .start-logo {
      transform: translateY(18px);
      animation-name: start-fade-slide;
      animation-delay: 300ms;
    }
    .start-tagline-line {
      transform: translateY(12px);
      animation-name: start-fade-slide;
      animation-duration: 400ms;
    }
    .start-tagline-line-1 { animation-delay: 900ms; }
    .start-tagline-line-2 { animation-delay: 1250ms; }
    @keyframes start-fade-slide {
      to { opacity: 1; transform: translateY(0); }
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .start-logo, .start-tagline-line { opacity: 1; }
  }

  /* Same edge-bleed/sticky mechanics as .page-header (same negative
     margin trick, same z-index), but WITH the rounded-bottom + shadow
     .page-header itself doesn't have. On a trip screen that curve
     comes from .trip-subtabs sitting flush beneath the (flush)
     .page-header, continuing it into one visually-curved white
     surface - the Home Screen has no equivalent second element below
     its header, so the header itself carries the curve directly here,
     for the same overall "curved white block up top" read. */
  .home-custom-header {
    /* Extra padding vs. .page-header's own 14px/18px - measured
       empirically against the itinerary header's REAL rendered height
       (a two-line title, which runs taller than plain font-size math
       predicts), not derived analytically. */
    padding: 32px 16px 36px;
    margin: -24px -16px 0;
    background: #fff;
    border-radius: 0 0 20px 20px;
    box-shadow: var(--shadow-md);
    position: sticky;
    top: 0;
    z-index: 20;
  }
  /* Unlike .page-header (bleeds only to body's own edges, which is
     already the full viewport on desktop - see body's max-width:none
     above), this one sits inside #home-screen, which keeps its 620px
     cap at every width on purpose (see #home-screen's own comment) so
     the home screen's trip cards don't stretch edge-to-edge on a wide
     desktop window. The header alone still breaks out of that cap via
     the standard width:100vw + negative-viewport-margin full-bleed
     trick, rather than lifting #home-screen's own cap. */
  @media (min-width: 900px) {
    .home-custom-header {
      /* Without this, the 50px padding below adds on top of the
         100vw already set, overflowing the viewport by 100px and
         forcing a horizontal scrollbar. */
      box-sizing: border-box;
      width: 100vw;
      margin-left: calc(50% - 50vw);
      margin-right: calc(50% - 50vw);
      padding-left: 50px;
      padding-right: 50px;
      border-radius: 0;
    }
  }
  .home-custom-header-inner {
    display: flex;
    align-items: center;
    gap: var(--space-12);
  }
  /* Was justify-content:space-between back when this row only ever had
     two children (logo, avatar) - adding the hamburger trigger as a
     third child in front of the logo would've centered the logo
     instead of keeping it flush left. Same visual result as before
     (logo left, avatar right) via margin-left:auto on the avatar
     button instead, now that there are three children to arrange. */
  .home-custom-header-inner .home-avatar-btn {
    margin-left: auto;
  }
  @media (prefers-reduced-motion: no-preference) {
    /* Slides down into place rather than fading up like the rest of
       .home-content-fade - the header is what reads as "arriving"
       once the Start Screen has fully faded out. Compound selector
       (not just .home-custom-header) so this wins over
       .home-content-fade's own translateY(8px) regardless of
       stylesheet order, at equal specificity otherwise. */
    .home-custom-header.home-content-fade { transform: translateY(-24px); }
  }
  /* Back to Figma's own 130px/43px sizing - a same-height match against
     the itinerary page's header turns out to need this larger size, not
     a smaller one: that header commonly carries a two-line title (city
     + dates), which makes its real rendered height taller than a
     single-line assumption would suggest (measured ~109px vs. ~68px
     with the smaller 36px avatar this rule used previously). */
  /* A <button>, not a <span> - clicking the logo (either header) jumps
     straight back to the Home Screen, same as the trip header's old
     back arrow did, just relocated onto the one element that's always
     present on every screen. Reset to plain/borderless so it still
     reads as a logo mark, not an obvious button. */
  .home-logo-small {
    width: 130px;
    display: block;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
  }
  .home-avatar-btn {
    width: 43px;
    height: 43px;
    flex-shrink: 0;
    border-radius: var(--radius-circle);
    border: none;
    background: var(--user-avatar-color);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    overflow: hidden;
    position: relative;
  }
  /* Trip-header avatar removed on mobile (direct feedback) - Account
     settings is still reachable there via the nav drawer's own footer
     row (see .nav-drawer-footer), so this isn't a lost affordance,
     just no longer duplicated in the header itself. Element+class
     selector (not just .page-header-avatar-btn alone) so this reliably
     beats .home-avatar-btn's own `display: flex` above regardless of
     source order - same class-name-collision reasoning as
     .page-header-logo.home-logo-small elsewhere in this file. */
  button.page-header-avatar-btn { display: none; }
  /* Restored on desktop - original "avatar stays visible at every
     width" design, only mobile changed above. Has to come AFTER the
     mobile-hide rule right above (not back up with this file's other
     >=900px .page-header overrides) - same element+class specificity
     as that rule, so whichever is later in source wins regardless of
     which one sits inside the @media block. */
  @media (min-width: 900px) {
    button.page-header-avatar-btn { display: flex; }
  }
  .home-avatar-icon { width: 18px; display: block; }
  /* Initials fallback (see _avatar_inner_html/_avatar_initials) - a
     nicer, more personal placeholder than the plain icon glyph above,
     shown whenever user_config has no user_photo_url set yet. */
  .home-avatar-initials {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-16);
    color: #fff;
    line-height: 1;
  }
  /* Real user photo, once user_config's user_photo_url is set (see
     _avatar_inner_html/get_user_photo_url) - clipped by .home-avatar-
     btn's own circular overflow:hidden above. Deliberately oversized
     (130%, centered) rather than a plain 100%x100% fill - real source
     photos turned out to already carry their OWN rounded/circular
     transparent masking that doesn't line up pixel-for-pixel with
     this button's circle, leaving a thin gap of transparent PNG right
     at the edge that let the accent-orange background bleed through
     in a ring around the photo. Oversizing pushes the photo's own
     edge (wherever it actually falls) safely outside the button's
     visible circle regardless of exactly how any given photo was
     pre-masked, without needing to know or fix that per-image. */
  .home-avatar-photo {
    position: absolute;
    top: -15%;
    left: -15%;
    width: 130%;
    height: 130%;
    object-fit: cover;
    display: block;
  }
  /* Desktop-only avatar chip (circle + first name) for the trip-page
     header's avatar button specifically (see .page-header-avatar-btn -
     the Home Screen's own bare avatar button has no "page-header-"
     class and stays a plain circle at every width, per the "Profile
     Drawer" handoff only showing this chip on the trip page). Below
     900px .page-header-avatar-btn falls back to .home-avatar-btn's own
     plain circle unchanged - .page-header-avatar-circle/-name are
     inert there (no override needed, just unused). */
  .page-header-avatar-name {
    display: none;
  }
  @media (min-width: 900px) {
    .page-header-avatar-btn {
      width: auto;
      height: auto;
      border-radius: var(--radius-pill);
      background: #FBF7F0;
      border: 1px solid rgba(74,63,53,0.1);
      padding: 8px 14px 8px 8px;
      gap: var(--space-8);
      overflow: visible;
    }
    .page-header-avatar-circle {
      width: 34px;
      height: 34px;
      flex-shrink: 0;
      border-radius: var(--radius-circle);
      background: var(--user-avatar-color);
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
      position: relative;
    }
    .page-header-avatar-circle .home-avatar-initials {
      font-size: var(--text-15);
    }
    .page-header-avatar-name {
      display: block;
      font-size: var(--text-14);
      font-weight: 700;
      color: var(--color-body);
    }
    .page-header-avatar-name:empty {
      display: none;
    }
  }
  /* ---- Mobile nav drawer (Figma "Mobile Nav - Open", node 452:5160) -
     left-edge slide-in drawer reachable from the hamburger trigger in
     both .page-header-inner (trip screens) and .home-custom-header-inner
     (Home Screen). Trips list itself is never baked in server-side -
     _populateNavDrawerTrips (JS) reads the same real .home-trip-card/
     .home-nav-preview elements the Home Screen's own Active/Upcoming/
     Past tabs already render from, so the drawer always reflects
     whatever's really on the page (including live/app-created trips)
     rather than a second, separately-maintained data source. ---- */
  .nav-drawer-trigger {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    flex-shrink: 0;
    color: #ffffff;
  }
  .home-custom-header-inner .nav-drawer-trigger { color: var(--color-ink); }
  /* Three-bar icon (see NAV_DRAWER_TRIGGER_ICON_HTML) rather than a
     Material Symbol - morphs into an X via the .nav-drawer-trigger-active
     modifier (toggled in step with the drawer's own open state, see
     _syncNavDrawerTriggerIcon) instead of swapping to a different glyph,
     so open/close reads as one continuous animation. background:
     currentColor so the bars pick up the same white/dark-on-light
     color rules .nav-drawer-trigger already carries above, with no
     separate color override needed per context. */
  .nav-drawer-trigger-bars {
    width: 22px;
    height: 16px;
    position: relative;
    display: block;
  }
  .nav-drawer-trigger-bar {
    position: absolute;
    left: 0;
    width: 100%;
    height: 2px;
    border-radius: 2px;
    background: currentColor;
    transition: transform 0.3s ease-in-out, opacity 0.2s ease-in-out, top 0.3s ease-in-out;
  }
  .nav-drawer-trigger-bar:nth-child(1) { top: 0; }
  .nav-drawer-trigger-bar:nth-child(2) { top: 7px; }
  .nav-drawer-trigger-bar:nth-child(3) { top: 14px; }
  .nav-drawer-trigger-active .nav-drawer-trigger-bar:nth-child(1) {
    top: 7px;
    transform: rotate(45deg);
  }
  .nav-drawer-trigger-active .nav-drawer-trigger-bar:nth-child(2) { opacity: 0; }
  .nav-drawer-trigger-active .nav-drawer-trigger-bar:nth-child(3) {
    top: 7px;
    transform: rotate(-45deg);
  }
  .nav-drawer-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.57);
    backdrop-filter: blur(41px);
    -webkit-backdrop-filter: blur(41px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease-in-out;
    /* Above every other overlay (.card-overlay's 500, the confirm
       modal's 1001) - the nav drawer is reachable from any screen, so
       it always needs to win regardless of what else might be open. */
    z-index: 1099;
  }
  .nav-drawer-backdrop-visible {
    opacity: 1;
    pointer-events: auto;
  }
  .nav-drawer {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 73vw;
    max-width: 320px;
    background: #fff;
    box-shadow: var(--shadow-md);
    z-index: 1100;
    display: flex;
    flex-direction: column;
    transform: translateX(-100%);
    transition: transform 0.3s ease-in-out;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .nav-drawer-open { transform: translateX(0); }
  /* Narrower persistent-feeling panel on desktop (still the same
     slide-in/out overlay, just proportioned as a trip navigator/
     context switcher rather than a full-width mobile menu) - stays
     open by default (see _initNavDrawerDesktopDefault) and through
     Home/Trips navigation (see _maybeCloseMobileNav) until explicitly
     collapsed via the arrow in its own header, rather than closing on
     every action the way the mobile modal does. The font-size/icon-size
     bump for this width lives further down (see the @media block right
     after .nav-drawer-footer-name) - deliberately AFTER every base
     .nav-drawer-* text rule below, not here alongside this rule, since
     an equal-specificity plain rule appearing later in the stylesheet
     always wins the cascade regardless of a media query matching. */
  @media (min-width: 900px) {
    .nav-drawer {
      /* max-width:320px (the mobile cap, above) would otherwise still
         win over this - clamp() already bakes 300-360px into the
         variable itself, so this rule doesn't need its own min/max. */
      width: var(--nav-drawer-desktop-w);
      max-width: none;
      /* Starts below the shared header (.page-header or .home-custom-
         header, whichever is currently showing - see syncStickyHeaderOffsets,
         which now measures both into these two custom properties, only
         one ever non-zero at a time) instead of at the true viewport top,
         now that the drawer no longer carries its own logo/header row on
         desktop (see .nav-drawer-header's own comment below) - the real
         header spans the full page width uninterrupted, with the drawer
         sitting underneath its left portion. */
      top: calc(var(--page-header-h, 0px) + var(--home-header-h, 0px));
      /* Below the header's own z-index (20), not above it like the
         mobile modal needs to be (1100, so it can cover everything
         including the header there) - .page-header/.home-custom-header
         cast their shadow downward by exactly the same box-shadow value
         this drawer also carries, and that shadow's blur radius bleeds
         14px into whatever sits directly beneath it. At 1100 the
         drawer's own opaque white box was painting over that sliver and
         hiding it, so the drawer looked flat/shadowless right under the
         header while the real page content right next to it (never
         above the header's z-index) visibly got the same shadow line. */
      z-index: 15;
    }
  }
  body.nav-drawer-scroll-lock { overflow: hidden; }
  /* #app-shift-wrap (see NAV_DRAWER_HTML's call sites) wraps only the
     actual screen content ({content} - #home-screen/.trip-category-
     screen) now, NOT the header (.page-header/.home-custom-header,
     both hoisted to be direct .nav-drawer siblings, see HTML_SHELL/
     TRIPS_PAGE_TEMPLATE) - the header stays one continuous full-width
     bar regardless of drawer state (see the mobile-nav-drawer desktop
     mock: a single hamburger/logo/title/avatar row, not a second logo
     duplicated inside the drawer), and only the content below it pushes
     over. Margin (not transform) on purpose: a transform here would
     make this wrapper the containing block for every position:fixed
     descendant inside it (card overlays, the confirm modal, etc.),
     silently breaking their viewport-relative positioning the moment
     the drawer happens to be open. */
  #app-shift-wrap {
    transition: margin-left 0.3s ease-in-out;
  }
  @media (min-width: 900px) {
    .app-shift-wrap-pushed {
      margin-left: var(--nav-drawer-desktop-w);
    }
  }
  .nav-drawer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 16px 14px;
    border-bottom: 1px solid rgba(74,63,53,0.08);
  }
  /* Mobile-only - the drawer's own logo/close row would otherwise
     duplicate the real header (now a permanent sibling above the
     drawer on desktop, see .nav-drawer's own top: comment) and there's
     nothing left to close with once the shared header's own hamburger
     doubles as the close control (see toggleMobileNav) - so this whole
     row disappears there and the nav content below starts right under
     the real header instead. */
  @media (min-width: 900px) {
    .nav-drawer-header { display: none; }
    .nav-drawer-main { padding-top: 14px; }
  }
  .nav-drawer-logo svg { width: 108px; display: block; }
  .nav-drawer-close {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--radius-circle);
    border: none;
    background: var(--color-close-bg);
    color: var(--color-body);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
  }
  .nav-drawer-close .material-symbols-outlined { font-size: var(--text-15); }
  .nav-drawer-main { padding: 6px 0; }
  .nav-drawer-main-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-12);
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    padding: 12px 20px;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-15);
    color: var(--color-ink);
    transition: background-color 0.2s ease-in-out, width 0.2s ease-in-out, margin-left 0.2s ease-in-out, margin-right 0.2s ease-in-out;
  }
  .nav-drawer-main-item .material-symbols-outlined {
    font-size: var(--text-18);
    color: var(--color-ink);
  }
  .nav-drawer-chevron {
    margin-left: auto;
    font-size: var(--text-20) !important;
    color: var(--color-muted);
    transition: transform 0.2s ease;
  }
  .nav-drawer-trips-toggle-open .nav-drawer-chevron { transform: rotate(180deg); }
  .nav-drawer-trips {
    border-top: 1px solid rgba(74,63,53,0.08);
    padding: 8px 0;
  }
  /* Small gap between rows - without it, the current row's permanent
     beige card and a neighboring row's grey hover card (both inset the
     same 20px each side, see .nav-drawer-trip-row-current/:hover) sit
     directly flush against each other with zero vertical space,
     reading as one merged blob instead of two distinct cards. */
  .nav-drawer-trip-list { display: flex; flex-direction: column; gap: var(--space-4); }
  .nav-drawer-trip-list.nav-drawer-trips-hidden { display: none; }
  .nav-drawer-trip-row {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
    width: 100%;
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    /* Left padding (50px) matches where the "T" in the Trips row's own
       label actually starts (20px button padding + ~18px icon +12px
       gap, measured via getBoundingClientRect - not derived blind) so
       every trip row's content lines up with it instead of with the
       icon/dot-adjacent 20px every other row uses. */
    padding: 10px 20px 10px 50px;
    border-radius: var(--radius-md);
    transition: background-color 0.2s ease-in-out, width 0.2s ease-in-out, margin-left 0.2s ease-in-out, margin-right 0.2s ease-in-out;
  }
  /* Wraps the stacked status/dates/title block (see _navDrawerTripRow,
     JS) now that the row itself is row-direction (text on the left,
     the "N DAYS TO GO" countdown badge on the right, when there is
     one) - min-width:0 so a long trip title still truncates/wraps
     within its own column instead of pushing the countdown off the
     edge of the drawer. */
  .nav-drawer-trip-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
  }
  /* Same real "N DAYS TO GO" pill the Home Screen's own trip cards show
     (see .home-preview-countdown-number/-label) - a compact version of
     the exact same treatment, not a separate design, since it's the
     exact same real countdown value (see _populateNavDrawerTrips). */
  .nav-drawer-trip-countdown {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    flex-shrink: 0;
  }
  .nav-drawer-trip-countdown-number {
    display: block;
    background: rgba(240,81,46,0.1);
    color: var(--color-accent);
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 700;
    font-size: var(--text-13);
    line-height: 1;
    border-radius: var(--radius-pill);
    padding: 4px 8px;
    white-space: nowrap;
  }
  .nav-drawer-trip-countdown-label {
    display: block;
    font-size: var(--text-10);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--color-body);
    white-space: nowrap;
  }
  /* Highlights whichever trip is the current context (matched against
     sessionStorage's activeTripId, same value showTripCategory already
     maintains) - the Figma handoff's own highlighted row, doubling as
     the desktop "trip context switcher" cue. Inset 20px each side
     (rather than the edge-to-edge treatment every other row gets) so
     the beige highlight reads as its own card floating in the drawer -
     width explicitly shrunk by the same 40px the margin adds, since
     this element isn't box-sizing:border-box and would otherwise
     overflow .nav-drawer-trip-list by exactly that amount. */
  .nav-drawer-trip-row-current {
    background: #fcf7f0;
    width: calc(100% - 40px);
    margin: 0 20px;
  }
  /* Divider between consecutive plain rows only - the sibling
     combinator naturally skips both sides of .nav-drawer-trip-row-
     current (its beige card already reads as its own separated
     block, see that rule's own comment) and never adds a trailing
     line after the actual last row, since there's no next sibling to
     match. Same subtle tint as .nav-drawer-trips' own top border. */
  .nav-drawer-trip-row:not(.nav-drawer-trip-row-current) + .nav-drawer-trip-row:not(.nav-drawer-trip-row-current) {
    border-top: 1px solid rgba(74,63,53,0.08);
  }
  /* Static/inline now, not position:absolute - it's built as the first
     child inside .nav-drawer-trip-status's own flex row (see
     _navDrawerTripRow, JS), so it naturally sits right before the
     status label, vertically centered with it via that row's own
     align-items:center, rather than floating at a separately-guessed
     pixel offset that didn't reliably line up with the label's real
     text baseline. */
  .nav-drawer-status-dot {
    flex-shrink: 0;
    width: 7px;
    height: 7px;
    border-radius: var(--radius-circle);
    background: var(--color-muted);
    /* Hangs the dot out to the left of the row's own left edge (7px
       width + the row's 6px gap = 13px) so "UPCOMING" itself lines up
       flush with the dates/title text below it, which have no dot -
       the gap still applies after this negative margin, so the text
       lands at exactly x:0 instead of x:13px (see .nav-drawer-trip-
       status's own comment on how the dot is positioned). */
    margin-left: -13px;
  }
  .nav-drawer-status-active .nav-drawer-status-dot { background: #78d49c; }
  .nav-drawer-status-upcoming .nav-drawer-status-dot { background: var(--color-accent); }
  .nav-drawer-status-planning .nav-drawer-status-dot { background: #bebebe; }
  .nav-drawer-trip-status {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 700;
    font-size: var(--text-10);
    letter-spacing: 0.02em;
    color: var(--color-muted);
  }
  .nav-drawer-status-active .nav-drawer-trip-status { color: #78d49c; }
  .nav-drawer-status-upcoming .nav-drawer-trip-status { color: var(--color-accent); }
  .nav-drawer-status-planning .nav-drawer-trip-status { color: var(--color-ink); }
  .nav-drawer-trip-dates {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 600;
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 6px;
  }
  .nav-drawer-trip-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-15);
    line-height: 1.15;
    color: var(--color-ink);
    margin-top: 0;
  }
  .nav-drawer-trip-empty {
    padding: 10px 20px;
    font-size: var(--text-15);
    color: var(--color-muted);
  }
  .nav-drawer-past-toggle {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    margin-top: 6px;
    /* Same 50px left inset as .nav-drawer-trip-row (see its own
       comment) - "Past Trips" is nested under Trips just like the
       rows above it, so its label lines up with the same "T". */
    padding: 12px 20px 12px 50px;
    border-top: 1px solid rgba(74,63,53,0.08);
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-15);
    color: var(--color-ink);
    transition: background-color 0.2s ease-in-out, width 0.2s ease-in-out, margin-left 0.2s ease-in-out, margin-right 0.2s ease-in-out;
  }
  .nav-drawer-past-toggle-open .nav-drawer-chevron { transform: rotate(180deg); }
  .nav-drawer-past-list { display: none; }
  .nav-drawer-past-list.nav-drawer-past-list-open { display: flex; flex-direction: column; }
  .nav-drawer-footer {
    margin-top: auto;
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-10);
    padding: 16px 20px;
    border-top: 1px solid rgba(74,63,53,0.08);
    background: none;
    border-left: none;
    border-right: none;
    border-bottom: none;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.2s ease-in-out, width 0.2s ease-in-out, margin-left 0.2s ease-in-out, margin-right 0.2s ease-in-out;
  }
  /* Same 20px-each-side inset as .nav-drawer-trip-row-current (see its
     own comment) - margin-left/right specifically, not the margin
     shorthand, since .nav-drawer-past-toggle/.nav-drawer-footer already
     carry their own margin-top (6px / auto) that a shorthand here would
     silently zero out. */
  .nav-drawer-main-item:hover,
  .nav-drawer-trip-row:hover,
  .nav-drawer-past-toggle:hover,
  .nav-drawer-footer:hover {
    background: rgba(74,63,53,0.05);
    width: calc(100% - 40px);
    margin-left: 20px;
    margin-right: 20px;
    border-radius: var(--radius-md);
  }
  .nav-drawer-trip-row-current:hover { background: #f5ecdd; }
  .nav-drawer-footer-avatar {
    width: 27px;
    height: 27px;
    flex-shrink: 0;
    border-radius: var(--radius-circle);
    background: var(--user-avatar-color);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    position: relative;
  }
  .nav-drawer-footer-avatar .home-avatar-initials { font-size: var(--text-10); }
  .nav-drawer-footer-avatar .home-avatar-photo {
    position: absolute;
    top: -15%;
    left: -15%;
    width: 130%;
    height: 130%;
    object-fit: cover;
    display: block;
  }
  .nav-drawer-footer-name {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-15);
    color: var(--color-ink);
  }
  /* Text/icons scale up once the drawer becomes the wider desktop
     panel - sized for a phone-width panel by default, which read too
     small once stretched out to a whole desktop sidebar. Placed after
     every base .nav-drawer-* rule above on purpose (see .nav-drawer-
     open's own comment) so these actually win the cascade instead of
     being silently overridden by a later plain rule of equal
     specificity. */
  @media (min-width: 900px) {
    .nav-drawer-logo svg { width: 130px; }
    .nav-drawer-close { width: 30px; height: 30px; }
    .nav-drawer-close .material-symbols-outlined { font-size: var(--text-18); }
    .nav-drawer-main-item { padding: 16px 22px; font-size: var(--text-16); }
    .nav-drawer-main-item .material-symbols-outlined { font-size: var(--text-20); }
    .nav-drawer-chevron { font-size: var(--text-20) !important; }
    .nav-drawer-trip-row { padding: 12px 22px 12px 56px; }
    .nav-drawer-status-dot { width: 8px; height: 8px; margin-left: -14px; }
    .nav-drawer-trip-status { font-size: var(--text-12); }
    .nav-drawer-trip-dates { font-size: var(--text-14); }
    .nav-drawer-trip-title { font-size: var(--text-16); }
    .nav-drawer-trip-empty { font-size: var(--text-16); }
    .nav-drawer-trip-countdown-number { font-size: var(--text-15); padding: 6px 8px; }
    .nav-drawer-trip-countdown-label { font-size: var(--text-10); }
    .nav-drawer-past-toggle { padding: 16px 22px 16px 56px; font-size: var(--text-16); }
    .nav-drawer-footer { padding: 18px 22px; }
    .nav-drawer-footer-avatar { width: 32px; height: 32px; }
    .nav-drawer-footer-avatar .home-avatar-initials { font-size: var(--text-12); }
    .nav-drawer-footer-name { font-size: var(--text-16); }
  }
  /* Wraps just the greeting/Upcoming-Past tabs/trip-card group (not the
     header, and not the "Plan a new trip" button/illustration below,
     which keep their own existing spacing) so this one desktop-only
     rule can inset the group 50px from each side of the illustration
     and push the whole cluster down together. Each child still carries
     its own individually-tuned (and asymmetric - 20px/4px, 13px, etc.)
     mobile-only horizontal margin, which would otherwise stack on top
     of this padding - zeroed out here so the wrapper's own 50px is the
     only inset on desktop, applied uniformly to all three. */
  @media (min-width: 900px) {
    .home-content-inset {
      padding: 0 50px;
      margin-top: 100px;
    }
    .home-content-inset .home-greeting,
    .home-content-inset .home-tab-row,
    .home-content-inset .home-tab-cards {
      margin-left: 0;
      margin-right: 0;
    }
  }
  .home-greeting {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-20);
    color: var(--color-greeting);
    /* 20px less than before - moves this and everything stacked below
       it (tab row, tab cards) up by the same amount, since they're all
       just in-flow siblings after this one. */
    margin: 20px 4px 0 20px;
  }
  /* Pending-invite variant of .home-trip-card (see _insertPendingTripCard,
     JS) - same card shell/spacing, but inert (no cursor:pointer/onclick -
     there's no detail view to open yet) with Accept/Decline in place of
     the normal countdown/weather content. Sits in whichever tab the
     trip's own dates classify it into, same as a real card, rather than
     a generic top-of-page banner - see that function's own comment for
     why the banner version got replaced. */
  .pending-trip-card {
    cursor: default;
  }
  .pending-trip-card-name {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-16);
    color: #3a352c;
  }
  .pending-trip-card-note {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 4px;
  }
  .pending-trip-card-actions {
    display: flex;
    gap: var(--space-8);
    margin-top: 16px;
  }
  .pending-trip-accept, .pending-trip-decline {
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 700;
    border-radius: var(--radius-md);
    padding: 8px 14px;
    cursor: pointer;
  }
  .pending-trip-accept {
    color: #fff;
    background: var(--color-accent);
    border: none;
  }
  .pending-trip-decline {
    color: var(--color-body);
    background: none;
    border: 1px solid var(--color-border);
  }
  .home-tab-row {
    display: flex;
    gap: var(--space-24);
    margin: 18px 4px 0 20px;
  }
  .home-tab-btn {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-15);
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-ink);
    opacity: 0.31;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 0 0 4px;
    cursor: pointer;
  }
  .home-tab-btn-active {
    opacity: 1;
    border-bottom-color: var(--color-accent);
  }
  .home-tab-cards { margin: 24px 13px 0; }
  /* Card spacing/radius/background per the Itinerary v3 Figma frames -
     see .compact-row's own comment on why white, not cream. */
  .home-trip-card {
    display: none;
    background: #ffffff;
    border: 1px solid #ededed;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    transition: box-shadow 150ms ease;
    /* Uniform 24px - the real source of the top/bottom mismatch was
       .home-nav-preview's own extra margin-top (see that rule's
       comment), not this padding; trimming padding-bottom here first
       was the wrong fix (moved the mismatch instead of removing it). */
    padding: var(--space-24);
    overflow: hidden;
    cursor: pointer;
  }
  .home-trip-card:hover {
    box-shadow: var(--shadow-xl);
  }
  .home-trip-card-active { display: block; }
  /* Cards for one category are consecutive DOM siblings (see
     _home_trip_cards_html) - this only visually manifests between
     ones actually showing, since every non-active card is
     display:none and takes up no space regardless. */
  .home-trip-card + .home-trip-card { margin-top: 6px; }
  /* "Nd" pill with its DAYS TO GO/LEFT/AGO caption stacked directly
     below it (not the plain "N / DAYS TO GO" numeral+caption style the
     old preview-card widget originally used elsewhere - still used
     as-is anywhere else that reuses .home-preview-countdown - and no
     longer the chevron-only affordance this used right before it,
     either: the label already gets computed per classification in
     _build_home_preview, just was never actually shown). */
  .home-trip-card .home-preview-countdown {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
  }
  .home-trip-card .home-preview-countdown-number {
    background: rgba(240,81,46,0.1);
    color: var(--color-accent);
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 700;
    font-size: var(--text-14);
    line-height: 1;
    border-radius: var(--radius-pill);
    padding: 8px 12px;
  }
  .home-trip-card .home-preview-countdown-label {
    display: block;
    font-size: var(--text-10);
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--color-body);
    white-space: nowrap;
  }
  .plan-new-trip-btn {
    display: block;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-18);
    font-weight: 500;
    color: var(--color-accent);
    background: rgba(255,255,255,0.82);
    border: none;
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-lg);
    padding: 14px 32px;
    margin: 36px auto 40px;
    cursor: pointer;
  }
  @media (prefers-reduced-motion: no-preference) {
    .home-content-fade {
      opacity: 0;
      transform: translateY(8px);
      transition: opacity 350ms var(--ease-decel), transform 350ms var(--ease-decel);
    }
    .home-content-fade.is-visible {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .home-content-fade { opacity: 1; }
  }
  html.skip-splash:not(.skip-splash-redirect) .home-content-fade { opacity: 1; transform: none; }
  /* .home-nav-preview (wraps this card's whole inner content) used to
     carry margin-top:10px here, stacking on top of .home-trip-card's
     own 24px padding for a 34px total gap to "Minneapolis" - the
     bottom had no equivalent extra margin after its last row
     (.weather-widget-partial's margin-top only adds space BEFORE it,
     not after), just the card's own padding-bottom. Removed so both
     sides come from that one shared padding value instead. */
  .home-preview-header-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-12);
  }
  .home-preview-city {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-18);
    font-weight: 800;
    line-height: 17px;
    color: var(--color-ink);
  }
  .home-preview-dates {
    font-family: 'Google Sans Flex', sans-serif;
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-muted);
    margin-top: 2px;
  }
  .home-preview-countdown {
    text-align: right;
    white-space: nowrap;
  }
  .home-preview-countdown-number {
    font-size: var(--text-32);
    font-weight: 700;
    line-height: 1;
    color: #3b6ea5;
  }
  .home-preview-countdown-label {
    font-size: var(--text-11);
    font-weight: 400;
    letter-spacing: 0.03em;
    color: #6b6a63;
    margin-top: 2px;
  }
  .trip-category-screen { display: none; }
  .trip-category-screen-active { display: block; }
  .trip-tab-empty {
    color: #9a9890;
    font-size: var(--text-14);
    padding: 24px 0;
    text-align: center;
  }
  .trip-section { margin-bottom: 8px; }
  /* 2026-07-27 decision: only one trip shows at a time within a
     category now, even when it holds more than one (see
     showTripCategory) - previously every trip sharing a category
     rendered in one continuous scroll, which made it trivially easy to
     scroll from one trip's itinerary straight into an unrelated one's.
     Two classes (not just a bare .trip-section-hidden) so this beats
     the desktop grid rule below regardless of source order. */
  .trip-section.trip-section-hidden { display: none; }
  .trip-map-col { display: none; }
  @media (min-width: 900px) {
    body { max-width: none; }
    .idea-maps-link-google { display: none; }
    .trip-section {
      display: grid;
      grid-template-columns: 480px 1fr;
      align-items: start;
      gap: var(--space-24);
    }
    .trip-map-col {
      display: block;
      position: sticky;
      /* Backstop in case a future timing gap reopens (see the old
         beige-band history below): a sticky child's PAINTED box can
         sit lower than the space it reserves in the grid if its
         natural/static position computes higher than its sticky `top`.
         White here means any such strip reads as "part of the page"
         instead of a stray beige seam. Scoped to this column
         specifically (not the shared .trip-section parent, which was
         tried first) - painting the whole grid's parent white leaked
         into .trip-content-col's own card gaps on the left, which have
         no background of their own and are supposed to show the cream
         body background between cards. */
      background: #fff;
      /* top/height intentionally key off --page-header-h alone, NOT
         --subtabs-h: .trip-subtabs lives INSIDE the content column
         (left track only), so it has no counterpart in this column's
         grid track. This used to add --subtabs-h too, which shoved
         this column's start down to align with the subtabs bar's
         bottom edge - correct on the left, but on the right it just
         reserved a beige strip the width of this column with nothing
         drawn in it (no subtabs bar here to fill it). */
      top: calc(var(--page-header-h, 0px) + 20px);
      /* Sticky can only hold an element AT OR BELOW its natural/static
         position, never push it down - so the 20px added to `top`
         above needs the same 20px added to the natural position via
         margin-top, or the difference reopens as an unpainted gap
         during scroll (the original beige-band bug this column's
         history keeps circling back to). 44px below = the 20px top
         gap plus the same 24px bottom gap this column always had. */
      margin-top: 20px;
      height: calc(100vh - var(--page-header-h, 0px) - 44px);
      /* Body has its own 16px right padding - 4px more here gets this
         column's edge to a 20px gap from the actual browser edge,
         matching the top/bottom spacing instead of sitting flush. */
      margin-right: 4px;
      /* All four corners now, not just the left two - the right edge
         used to sit flush against the browser edge (no visible corner
         there to round), but now has its own 20px gap same as the
         other three sides, so a square right edge would look like a
         mistake next to the rounded left one. */
      border-radius: var(--radius-2xl);
      overflow: hidden;
      box-shadow: var(--shadow-sm);
    }
    .trip-map {
      width: 100%;
      height: 100%;
    }
    .map-search-overlay {
      display: none;
      position: absolute;
      top: 16px;
      left: 16px;
      right: 16px;
      z-index: 5;
    }
    .map-search-overlay-visible { display: block; }
    /* Floating category tag above each Ideas-map pin (see
       CategoryPinLabel/showAllIdeasOnMap) - positioned via a custom
       OverlayView rather than google.maps.Marker's own native label,
       since that renders centered ON the icon (no floating-above
       option) and can't be styled as a pill. left/top are set in JS
       to the marker's own projected pixel point (its anchor, i.e. the
       pin's tip); this transform does the rest - -50% centers it
       horizontally over that point, -58px clears the 50px-tall pin
       icon (see _destMarkerIcon) plus an 8px gap. pointer-events:none
       so a tap still reaches the marker underneath, not the label. */
    .map-pin-label {
      position: absolute;
      transform: translate(-50%, -58px);
      background: #fff;
      border: 1px solid rgba(51,38,30,0.08);
      color: var(--color-ink);
      font-family: 'Google Sans Flex', sans-serif;
      font-weight: 600;
      font-size: var(--text-11);
      white-space: nowrap;
      padding: 4px 8px;
      border-radius: var(--radius-sm);
      box-shadow: var(--shadow-sm);
      pointer-events: none;
      z-index: 1;
    }
    .map-search-input {
      display: block;
      width: 50%;
      margin: 0 auto;
      box-sizing: border-box;
      font-family: inherit;
      font-size: var(--text-14);
      padding: 12px 16px;
      border: none;
      border-radius: var(--radius-md);
      background: #fff;
      box-shadow: var(--shadow-md);
    }
    .map-search-chips {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: var(--space-8);
      margin-top: 10px;
    }
    .map-search-chip {
      font-family: inherit;
      font-size: var(--text-13);
      font-weight: 600;
      color: #2c2c2a;
      background: #fff;
      border: none;
      border-radius: var(--radius-md);
      padding: 8px 14px;
      cursor: pointer;
      box-shadow: var(--shadow-sm);
      white-space: nowrap;
    }
    /* Was centered/anchored against the full viewport, which is only
       correct on mobile, and on the Home Screen/Start Screen at any
       width (both single-column, no map ever competing for space).
       Scoped to .app-bg-hide-scenery specifically - the same class
       showTripCategory already adds (and showHomeScreen removes,
       see below) to mark "currently viewing a trip's Itinerary/Ideas/
       Expenses screen" - so once .trip-section's map column claims the
       rest of a wide desktop viewport (above), just that trip-screen
       illustration tracks the content column (480px + body's 16px left
       padding) instead of drifting into the map's real estate.
       (Neither the Ideas nor Expenses tab's own add button is a fixed
       FAB any more - see .idea-sticky-add-btn/.expense-sticky-add-btn's
       own comments - so neither needs a desktop-only override here.)
       Real bug (screenshot, Figma-vs-HTML gradient comparison): this
       used to be `margin: 0` alone, which relied on .app-bg's own base
       rule still using margin-based centering. Once that base rule
       switched to left:50%+translateX(-50%) (see .app-bg's own
       comment - the margin:auto version never actually centered above
       620px), a bare `margin:0` here stopped doing anything, silently
       leaving this flush-left override inert - the box went back to
       being dead-centered like every other screen, so its right edge
       landed mid-content instead of past it, cutting a visible hard
       seam straight through the day-pills/cards. left:0 + transform:
       none is the equivalent flush-left override for the new
       positioning method. Home Screen is unaffected - it always also
       carries .app-bg-full-bleed (see that class), which sets the same
       left:0/transform:none PLUS width:100%/max-width:none, so this
       narrower rule never gets to matter there. */
    .app-bg.app-bg-hide-scenery {
      left: 0;
      transform: none;
    }
  }
  .trip-title { display: none; }
  .trip-title-back-btn {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    color: #ffffff;
    display: flex;
    align-items: center;
    flex-shrink: 0;
  }
  .trip-title-back-btn .material-symbols-outlined {
    font-size: var(--text-32);
  }
  /* Not sticky by default - only the tappable date pills and the day/
     "Show Journey Cards" row below stay pinned on scroll now; the trip
     name and this tab row scroll away normally, reappearing as the
     page scrolls back up toward the top. On mobile, initTripSubtabsReveal
     additionally toggles .trip-subtabs-fixed/-revealed (see the media
     query below) once this row has scrolled out of its natural flow
     position, so scrolling up ANY amount slides it back in as an
     overlay instead of requiring a scroll all the way back to the
     actual top of the page. */
  .trip-subtabs {
    display: flex;
    align-items: center;
    /* No fixed height anymore - .trip-subtabs-inner's own padding
       (see below) now sets the row's height, so the 30px-above-the-
       words request lands as real padding instead of being centered
       away inside a fixed box. */
    /* No bottom margin - flush against .date-pills-row right after it,
       so the two read as one continuous white surface with no visible
       seam between them (see .itinerary-sticky-stack's own comment on
       this being one unified header block now). */
    margin: 0 -16px;
    background: #fff;
  }
  /* Mobile-only reveal-on-scroll-up overlay (see initTripSubtabsReveal).
     Scoped under 900px so it never fights the desktop rule below, which
     already keeps this row permanently sticky there. position:sticky,
     NOT fixed - an earlier version used fixed+top:0, which on real iOS
     Safari rendered UNDER the browser's own address-bar chrome once it
     grew back during the very scroll-up gesture that triggers this
     reveal (fixed pins to the layout viewport, which the toolbar's
     show/hide animation doesn't update in step, so the toolbar could
     end up drawn on top of it - confirmed via a real-device screenshot
     showing the date row half swallowed by the address bar). sticky
     doesn't have that failure mode - it's what .itinerary-sticky-stack/
     .idea-category-tabs already use with no such issue, so this now
     matches their approach instead of introducing a second, riskier
     one. Its own static (in-flow) position is early in the document
     (right after .page-header), so once scrolled well past that,
     sticky engages immediately at top:0 same as if it'd been sticky
     the whole time - toggling the property on mid-scroll works fine,
     sticky is re-evaluated continuously rather than "primed" once.
     Starts translated fully off-screen the instant it's promoted (i.e.
     right as it would otherwise have scrolled away) so there's no
     flash of it sitting stuck at top:0 before a scroll-up gesture asks
     for it. translateZ(0)/backface-visibility/will-change match
     .itinerary-sticky-stack's own GPU-layer promotion (see its
     comment) - the standard fix here for real iOS Safari dropping/
     mis-painting position:sticky content, same risk since this is now
     the same kind of element. z-index above .itinerary-sticky-stack's
     10 so it overlays that stack rather than fighting it for the same
     strip of viewport - but that alone left the date pills/day-toggle
     row hidden UNDERNEATH this bar the instant it slid in, since both
     sit pinned at top:0 with this one painted on top.
     updateTripSubtabsReveal also pushes .itinerary-sticky-stack/
     .idea-category-tabs down by this bar's own height via
     --subtabs-reveal-h (see those rules) whenever it's revealed, so
     the whole header block slides down together instead of the tab
     row just covering what's underneath it. */
  @media (max-width: 899.98px) {
    .trip-subtabs.trip-subtabs-fixed {
      position: sticky;
      top: 0;
      z-index: 15;
      transform: translateY(-100%) translateZ(0);
      transition: transform 0.22s ease;
      -webkit-backface-visibility: hidden;
      backface-visibility: hidden;
      will-change: transform;
    }
    .trip-subtabs.trip-subtabs-fixed.trip-subtabs-revealed {
      transform: translateY(0) translateZ(0);
      box-shadow: var(--shadow-md);
    }
  }
  /* Sticky on desktop only - there's enough vertical room there to
     keep the header, tab row, and date pills all pinned at once,
     unlike mobile where that would eat too much of the viewport. Sits
     ABOVE .itinerary-sticky-stack
     (below) but BELOW .page-header (now sticky too, see its own CSS
     comment) - top is that header's real measured height, not 0, so
     the two don't overlap. See syncStickyHeaderOffsets. */
  @media (min-width: 900px) {
    .trip-subtabs {
      position: sticky;
      top: var(--page-header-h, 0px);
      z-index: 11;
    }
  }
  /* Full width, 20px insets on both sides - the grey divider (a bit
     darker than before - #d3d1d1) spans exactly this same 20px-inset
     width since it's a border on this element, not .trip-subtabs
     itself (which stays full-bleed for its background). A fixed 20px
     gap between tabs (not justify-content:space-between, which
     stretched them across the full row width and made the spacing
     depend on how many tabs happen to be showing) keeps the spacing
     the same regardless of tab count. */
  .trip-subtabs-inner {
    display: flex;
    /* flex-end, not center - the active tab's own orange border-bottom
       (see .trip-subtab-btn-active) needs to sit flush against this
       row's grey divider border-bottom right below it, not floating
       above it with a gap from being vertically centered in extra
       bottom padding that no longer exists (see padding below). */
    align-items: flex-end;
    /* space-between (not a fixed gap) so the tabs spread evenly across
       the row's full width, per an explicit follow-up request - see the
       git history on this rule if that ever needs revisiting, since an
       earlier version deliberately avoided this for the same fixed-gap
       reasons noted in the (now superseded) comment this replaced. */
    justify-content: space-between;
    width: 100%;
    /* 16px above the tab words (Overview/Itinerary/Ideas/Expenses) -
       shared by every trip subtab page since they all render this same
       row, not per-page. No bottom padding at all - see align-items
       above, the orange/grey lines touch. Scrolls horizontally (same
       recipe as .date-pills-row) rather than wrapping a label mid-word -
       confirmed needed once a dynamic count suffix (e.g. "Expenses (10)")
       no longer reliably fits this row's available width on a real
       narrow-viewport trip. */
    overflow-x: auto;
    scrollbar-width: none;
    padding: 16px 20px 0;
    border-bottom: 1px solid #d3d1d1;
  }
  .trip-subtabs-inner::-webkit-scrollbar {
    display: none;
  }
  .trip-subtab-btn {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-15);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-muted);
    background: none;
    white-space: nowrap;
    flex-shrink: 0;
    border: none;
    border-bottom: 3px solid transparent;
    /* Bumped bottom padding (was 4px) to grow the row's overall height
       per an explicit follow-up request - top gap to the underline
       stays intentionally tight per the original Figma note above. */
    padding: 6px 2px 8px;
    margin-bottom: -1px;
    cursor: pointer;
  }
  .trip-subtab-btn-active {
    color: var(--color-ink);
    border-bottom-color: var(--color-accent);
  }
  .trip-subtab-panel { display: none; }
  .trip-subtab-panel-active { display: block; }
  /* People tab (see _people_panel_html) - who's sharing this trip.
     Replaces the old inline attendee-initial badge that used to sit
     next to the trip title (a one-letter hint easy to mistake for
     something else, e.g. Madrid's "M" for Melanie) with a real list -
     same per-person color hash (AVATAR_PALETTE), just given its own
     dedicated place instead of squeezed into the header. */
  .people-panel {
    padding-top: 30px;
    padding-bottom: 40px;
  }
  .people-row {
    display: flex;
    align-items: center;
    gap: var(--space-14);
    background: #fff;
    border-radius: var(--radius-3xl);
    box-shadow: var(--shadow-md);
    padding: 14px 20px;
    margin-bottom: 12px;
  }
  .people-avatar {
    width: 40px;
    height: 40px;
  }
  .people-name {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }
  .people-name-top {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 600;
    font-size: var(--text-16);
    color: var(--color-ink);
    display: flex;
    align-items: center;
    gap: var(--space-8);
  }
  /* Pipeline-synced flight(s) for this member - see renderPeoplePanel's
     _fetchMemberFlights/_formatFlightLine. Never shown for the viewer's
     own row (they already see their own flight on their own itinerary)
     - only co-travelers'. Absent entirely for a member with no synced
     flight, same collapse-to-nothing pattern as .card-notes-thread:empty.
     A real flight number (see _flightTrackerUrl) makes this a link out
     to a live tracker - display:block keeps it its own line either way,
     link or plain div. */
  .people-member-flight {
    display: block;
    font-family: inherit;
    font-size: var(--text-13);
    color: var(--color-muted);
    text-decoration: none;
  }
  a.people-member-flight:hover {
    text-decoration: underline;
  }
  /* Compact per-person expense total (not itemized - the real breakdown
     lives on the Expenses tab) - clicking jumps there via switchSubTab.
     Shown for every member including the viewer's own row, unlike
     flights - there's no other "my running total" summary shown
     anywhere else on this page, so it's not redundant here the way a
     repeated flight would be. */
  .people-member-expense {
    display: block;
    font-family: inherit;
    font-size: var(--text-13);
    color: var(--color-muted);
    text-decoration: none;
    cursor: pointer;
  }
  .people-member-expense:hover {
    text-decoration: underline;
  }
  /* Owner-only, live-trip-only (see renderPeoplePanel's post-render
     visibility toggle) - same muted trailing-icon-button treatment as
     .expense-row-delete, pushed to the row's far edge since .people-row
     has no other trailing element to share space with. */
  .people-remove-btn {
    margin-left: auto;
    flex-shrink: 0;
    background: none;
    border: none;
    padding: var(--space-4);
    color: #b4b2a9;
    cursor: pointer;
    display: flex;
  }
  .people-remove-btn .material-symbols-outlined {
    font-size: var(--text-20);
  }
  .people-you-badge {
    font-family: inherit;
    font-weight: 700;
    font-size: var(--text-11);
    color: var(--color-muted);
    background: #f7f2ea;
    border-radius: var(--radius-md);
    padding: 2px 8px;
  }
  /* Live-trip-only (see _people_panel_html's supabase_trip_id_attr) -
     same visual treatment as .expense-signin-*/.idea-signin-*, mirrored
     for People. */
  .people-signin-prompt {
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-xl);
    padding: var(--space-16);
    margin-bottom: 12px;
  }
  .people-signin-title {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    margin-bottom: 12px;
  }
  .people-signin-email {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: 8px;
  }
  /* ── Primary button (design-system Finding 1, 2026-08-08) ──────────
     One base class for every accent CTA - was ~19 hand-styled copies of
     the same color/shape/press boilerplate at inconsistent sizes. Base
     = the shared identity (accent fill, white text, pill, press state)
     + a default size. Variants: -sm (compact, for inline prompt
     buttons), -block (full-width form submits). Contextual extras a
     given button still needs (a floating shadow, a margin, flex:1,
     an entrance animation) stay on that button's own feature class;
     only the duplicated identity/size props moved here. */
  .btn-primary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    font-family: inherit;
    font-size: var(--text-15);
    font-weight: 700;
    color: #fff;
    background: var(--color-accent);
    border: none;
    border-radius: var(--radius-pill);
    padding: 12px 22px;
    cursor: pointer;
    transition: transform 120ms ease-out;
  }
  .btn-primary:active {
    transform: scale(0.97);
    filter: brightness(0.88);
  }
  .btn-primary .material-symbols-outlined {
    font-size: var(--text-18);
  }
  .btn-primary-sm {
    font-size: var(--text-14);
    padding: 10px 16px;
  }
  .btn-primary-block {
    width: 100%;
  }
  /* Secondary/neutral button - the muted counterpart to .btn-primary
     (2026-08-08). Same geometry, no accent fill: for dismiss/back-out
     actions that sit beside a primary. Applied to the padded modal
     Cancel; the inline text-link cancels (.idea-cancel-link etc.) are a
     separate "text link" pattern and intentionally stay as-is. */
  .btn-secondary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    font-family: inherit;
    font-size: var(--text-15);
    font-weight: 600;
    color: var(--color-muted);
    background: none;
    border: none;
    border-radius: var(--radius-pill);
    padding: 12px 22px;
    cursor: pointer;
    transition: transform 120ms ease-out;
  }
  .btn-secondary:active {
    transform: scale(0.97);
  }
  .btn-secondary-sm {
    font-size: var(--text-14);
    padding: 10px 16px;
  }

  .people-signin-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 8px;
  }
  .people-invite-form {
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-xl);
    padding: var(--space-16);
    margin-bottom: 12px;
  }
  .people-invite-title {
    font-size: var(--text-14);
    font-weight: 600;
    color: #3a352c;
    margin-bottom: 10px;
  }
  .people-invite-row {
    display: flex;
    gap: var(--space-8);
  }
  .people-invite-email {
    flex: 1;
    min-width: 0;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
  }
  .people-invite-role {
    flex: 0 0 auto;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-13);
    color: #3a352c;
    padding: 10px 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: #fff;
  }
  .people-invite-btn {
    flex-shrink: 0;
  }
  .people-invite-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 8px;
  }
  .people-danger-zone {
    margin-top: 8px;
  }
  .people-delete-trip-btn {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    font-weight: 700;
    color: #c94a3d;
    background: #fff;
    border: 1px solid #f0d4d0;
    border-radius: var(--radius-md);
    padding: 12px 16px;
    cursor: pointer;
  }
  /* ---- Overview tab (see _overview_panel_html) - trip summary card
     plus People/Checklist/Reminders collapsible cards. Design handoff:
     design_handoff_trip_overview/README.md - tokens below are taken
     directly from its Design Tokens section. Avatar colors deliberately
     do NOT use this card's own fixed tint pair from the handoff - kept
     on the same AVATAR_PALETTE hash every other avatar in the app uses
     (_people_panel_html/_peopleAvatarColor), per direct feedback. */
  .overview-panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    padding-top: 20px;
    padding-bottom: 4px;
  }
  .overview-card {
    background: #fff;
    border-radius: 22px;
    box-shadow: var(--shadow-md);
  }
  .overview-summary-card {
    padding: 22px 22px 20px;
    /* Square bottom corners, since .overview-nav-row (see its own
       comment) sits flush against this edge and carries the rounded
       corners for the pair instead - together they read as one
       continuous card with the nav row as its sticky "footer",
       matching direct feedback that the two looked like separate,
       disconnected pieces before. */
    border-radius: 22px 22px 0 0;
  }
  .overview-summary-title-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-12);
  }
  .overview-summary-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-24);
    line-height: 1.2;
    color: var(--color-ink);
    margin: 0;
  }
  /* .page-title-main (the sticky header's own title span, reused here
     so _applyTripTitleOverride's rename patch reaches this card too -
     see _overview_panel_html's docstring) defaults to white, meant for
     the header's own dark/photo background - overridden back to this
     card's own text color since it sits on a plain white card here. */
  .overview-summary-title .page-title-main {
    color: inherit;
  }
  .overview-title-edit-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    border: none;
    background: none;
    padding: 0;
    margin-top: 2px;
    color: var(--color-muted);
    cursor: pointer;
  }
  .overview-title-edit-btn .material-symbols-outlined {
    font-size: var(--text-20);
  }
  .overview-title-edit-input {
    flex: 1;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-24);
    line-height: 1.2;
    color: var(--color-ink);
    border: none;
    border-bottom: 2px solid var(--color-accent);
    padding: 0 0 2px;
    background: none;
  }
  .overview-title-edit-input:focus {
    outline: none;
    /* Underline-style inline edit, not a boxed field - opt out of the
       global focus glow (see SHARED_APP_STYLE's field active state
       rule) so it keeps its existing accent-underline-only treatment. */
    box-shadow: none;
  }
  .overview-summary-dates {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 600;
    font-size: var(--text-15);
    color: var(--color-muted);
    margin-top: 2px;
  }
  .overview-place-chips {
    display: flex;
    gap: var(--space-8);
    flex-wrap: wrap;
    /* Same 12px as .overview-summary-address's own margin-top below -
       this row sits directly between the dates line above and the
       address line below, so it reads as centered between the two
       instead of sitting closer to one than the other (was 14px/10px,
       a visible mismatch). */
    margin-top: 12px;
  }
  .overview-place-chip {
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-ink);
    background: #f3e7d6;
    border-radius: var(--radius-pill);
    padding: 8px 16px;
  }
  /* Auto-resolved from the trip's own events (see
     _overview_primary_address) - same maps-link shape _address_link_html
     gives every per-card Address row (copy button omitted here - see
     .overview-copy-address-btn's own comment on why that moved to its
     own row below), just muted to this card's own secondary-text color
     instead of the accent link color a card row uses, since here it's
     supporting detail under the title rather than a primary field.
     align-items:flex-start (not center) - a long address wraps to 2+
     lines, and centering the icon against the whole wrapped block
     looked like it was floating relative to the first line specifically,
     per direct feedback. */
  .overview-summary-address {
    display: flex;
    align-items: flex-start;
    gap: var(--space-6);
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-muted);
    margin-top: 12px;
  }
  /* Nudged down 1px from the text's own top - the material icon glyph's
     visual weight sits slightly higher within its box than the address
     text's cap-height does, so a plain flex-start left them looking
     misaligned by a hair rather than truly flush. */
  .overview-summary-address-icon {
    margin-top: 1px;
  }
  .overview-summary-address .material-symbols-outlined {
    font-size: var(--text-16);
    flex-shrink: 0;
  }
  .overview-summary-address-link {
    color: var(--color-muted);
  }
  /* Standalone "Copy Address" row under the address (see
     _renderOverviewAddress/_overview_panel_html's address_row_html) -
     split out from the old inline trailing icon-only .copy-address-btn
     per direct feedback, so the copy action reads as its own clearly
     labeled tap target instead of a small icon easy to miss next to the
     maps link. Icon leads the label, same left-icon convention as
     .copy-address-btn-leading elsewhere. */
  .overview-copy-address-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    margin-top: 12px;
    padding: 0;
    border: none;
    background: none;
    font-family: inherit;
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-muted);
    cursor: pointer;
  }
  .overview-copy-address-btn .material-symbols-outlined {
    font-size: var(--text-16);
    flex-shrink: 0;
  }
  /* Manual fallback when no event resolved an address (see
     _overview_panel_html) - "+ Add address" toggles to a text input,
     matching Checklist/Reminders' own "+ Add ..." affordance shape,
     but actually wired (see openOverviewAddAddress/saveOverviewAddress)
     rather than stubbed, since this one persists real data. */
  .overview-summary-address-empty {
    margin-top: 10px;
  }
  .overview-add-address-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-accent);
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
  }
  .overview-add-address-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .overview-address-input-row {
    display: flex;
    gap: var(--space-8);
    margin-top: 4px;
  }
  .overview-address-input {
    flex: 1;
    min-width: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 8px 10px;
    font-size: var(--text-14);
    font-family: inherit;
  }
  .overview-address-save-btn {
    flex-shrink: 0;
  }
  /* Sticky People/Checklist/Reminders jump nav (see
     _overview_panel_html's overview_nav_html) - replaces the old
     Ideas/People/Checklist/Reminders stat row that used to live inside
     .overview-summary-card, per direct feedback: Ideas dropped (its
     own tab covers that), and each remaining item is now a real tap
     target (see scrollToOverviewSection), not just a read-only count.
     Pulled out to its own full-bleed sticky bar rather than staying
     nested in the summary card - same margin/sticky/z-index recipe as
     .idea-category-tabs (see that rule's own comment on the shared
     page-header/trip-subtabs stacking), so it keeps working once the
     summary card itself scrolls out of view instead of taking the
     stat row with it. */
  /* Pulled flush against .overview-summary-card above it (negative
     margin-top cancels .overview-panel's own 16px flex gap, same
     trick a negative margin uses to close a gap-based layout's own
     spacing elsewhere in this file) and rounded only on the bottom
     corners, matching that card's own top corners staying rounded and
     its bottom corners going square (see .overview-summary-card's own
     override) - real feedback was that pulling this out into its own
     fully independent floating bar (see the original version of this
     comment) read as visually disconnected from the summary card it
     used to be part of, even though it needs to be a true sibling
     (not nested inside that card) to sit at the right sticky tier
     once it detaches on scroll.
     No horizontal margin here - real feedback + screenshots settled
     this on state, not viewport width: while sitting in its natural
     unstuck position (any width), this row should match the summary
     card's own width exactly, same as .overview-summary-card's own
     100%-of-parent width - a horizontal pull here (originally added to
     reach the true viewport edge, correct on mobile's single-column
     layout but not on desktop's fixed-480px grid column - see
     .overview-nav-row-stuck's own comment for the desktop-specific
     history of this fix) read as a step where the card's rounded
     corners visibly peeked out past this row's own straight edges,
     at BOTH widths once actually compared side by side. */
  .overview-nav-row {
    display: flex;
    column-gap: var(--space-32);
    margin: -16px 0 0;
    padding: 14px 16px;
    background: #fff;
    border-radius: 0 0 22px 22px;
    position: sticky;
    top: var(--subtabs-reveal-h, 0px);
    transition: top 0.22s ease, border-radius 0.15s ease, box-shadow 0.15s ease, margin 0.15s ease;
    z-index: 10;
  }
  /* Stuck-only styling (see initOverviewPanel's IntersectionObserver +
     .overview-nav-sentinel for how "actually detached and pinned to
     the viewport top" gets detected, which plain CSS can't observe on
     its own): full-bleed to the true viewport edge, same -16px pull
     this row used unconditionally before, PLUS the shadow. Once
     pinned, this row sits directly below .page-header/.trip-subtabs,
     which both span the full viewport width even on desktop's two-
     column grid (they're outside .trip-section entirely) - staying
     confined to the 480px card's own width here while everything
     above it spans edge to edge read as "cut off," not as matching
     the card below it any more (that relationship only makes sense
     while still attached to the card, i.e. NOT stuck) - real feedback
     + screenshots confirmed this at both widths, hence this now being
     driven by stuck-state rather than a >=900px media query. No
     shadow by default (unstuck) - sitting flush against the summary
     card, an unconditional shadow there read as a seam between two
     separate cards instead of one continuous card. */
  .overview-nav-row-stuck {
    margin-left: -16px;
    margin-right: -16px;
    box-shadow: var(--shadow-md);
  }
  @media (min-width: 900px) {
    .overview-nav-row {
      top: calc(var(--page-header-h, 0px) + var(--subtabs-h, 0px));
    }
  }
  .overview-nav-sentinel {
    height: 0;
  }
  .overview-nav-item {
    display: flex;
    align-items: baseline;
    gap: var(--space-6);
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-family: inherit;
  }
  .overview-nav-value {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-20);
    color: var(--color-ink);
  }
  .overview-nav-label {
    font-size: var(--text-15);
    font-weight: 600;
    color: var(--color-muted);
  }
  .overview-section-card {
    padding: 20px 22px;
  }
  .overview-section-header {
    width: 100%;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
  }
  .overview-section-header-left {
    display: flex;
    align-items: center;
    gap: var(--space-10);
  }
  .overview-section-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-18);
    color: var(--color-ink);
  }
  .overview-section-meta {
    font-size: var(--text-15);
    font-weight: 600;
    color: var(--color-muted);
  }
  .overview-chevron {
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transform: rotate(0deg);
    transition: transform 0.15s;
  }
  .overview-chevron .material-symbols-outlined {
    font-size: var(--text-18);
    color: var(--color-muted);
  }
  .overview-section-header[aria-expanded="false"] .overview-chevron {
    transform: rotate(-90deg);
  }
  .overview-section-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    margin-top: 18px;
  }
  .overview-section-header[aria-expanded="false"] + .overview-section-body {
    display: none;
  }
  .overview-advisory-summary {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-15);
    line-height: 1.5;
    color: #5c5148;
    margin: 0;
  }
  .overview-advisory-apps {
    display: flex;
    flex-direction: column;
    gap: var(--space-10);
  }
  .overview-advisory-app-row {
    display: flex;
    flex-direction: column;
    gap: 0px;
  }
  .overview-advisory-app-name {
    font-size: var(--text-15);
    font-weight: 700;
    color: var(--color-ink);
  }
  .overview-advisory-app-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    align-self: flex-start;
    color: var(--color-accent);
    text-decoration: none;
  }
  .overview-advisory-app-link .material-symbols-outlined {
    font-size: var(--text-15);
  }
  .overview-advisory-app-note {
    font-size: var(--text-13);
    color: var(--color-muted);
  }
  /* "Add more to this trip" card (see _overview_panel_html) - sits
     before People per the Figma frame selected 2026-08-06. Icon SVG
     pulled exact from that frame via get_design_context/curl against
     the Figma desktop app's local asset server (mail glyph + a 4-point
     sparkle accent, no Material Symbols equivalent exists) - the copy
     button reuses the ordinary content_copy glyph instead of the
     frame's own custom icon, matching every other copy-to-clipboard
     button already in this file (NEW_TRIP_FORM_TEMPLATE's forward-email
     copy, .overview-copy-address-btn, .pdp-reminder-copy-btn) rather
     than forking a one-off asset for visually the same action. */
  .overview-add-more-card {
    display: flex;
    align-items: center;
    gap: var(--space-24);
    padding: 18px 20px 18px 40px;
  }
  .overview-add-more-icon {
    flex-shrink: 0;
    width: 40px;
    height: 33px;
  }
  .overview-add-more-icon svg {
    display: block;
    width: 100%;
    height: 100%;
  }
  /* Sparkle accent on the mail icon (see the inline SVG's own
     .overview-add-more-sparkle group) - twinkles on a loop rather than
     sitting static, direct request 2026-08-06. transform-box: fill-box
     pins the scale's origin to the sparkle shape's own center rather
     than the whole SVG viewport's, so it grows/shrinks in place instead
     of drifting toward the viewport origin. A quick 1.8s cycle (not the
     6-10s this file's larger decorative loaders use, see
     animation_speed_calibration.md) - this is a small icon accent, not
     a page-level illustration, and reads as inert rather than "alive" at
     a slower pace. */
  .overview-add-more-sparkle {
    transform-box: fill-box;
    transform-origin: center;
    animation: overviewAddMoreSparkleTwinkle 1.8s ease-in-out infinite;
  }
  @keyframes overviewAddMoreSparkleTwinkle {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(0.72); opacity: 0.65; }
  }
  .overview-add-more-title {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    margin-bottom: 3px;
  }
  .overview-add-more-subtitle {
    font-size: var(--text-15);
    color: var(--color-ink);
    margin-bottom: 4px;
  }
  .overview-add-more-row {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    flex-wrap: wrap;
  }
  .overview-add-more-email {
    font-size: var(--text-15);
    color: var(--color-accent);
    text-decoration: underline;
    cursor: pointer;
    word-break: break-all;
  }
  .overview-add-more-copy-btn {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: var(--space-2);
    cursor: pointer;
    color: var(--color-accent);
  }
  .overview-add-more-copy-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .overview-people-row {
    display: flex;
    align-items: center;
    gap: var(--space-14);
  }
  .overview-people-avatar {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
  }
  .overview-people-name {
    font-size: var(--text-16);
    font-weight: 600;
    color: var(--color-ink);
  }
  .overview-people-name-pending {
    color: var(--color-muted);
  }
  .overview-people-role {
    font-size: var(--text-14);
    color: var(--color-muted);
  }
  /* Owner-only "remove this member" affordance (see renderOverviewPeople's
     myRole/m.user_id gate) - same plain close-icon-button treatment as
     .checklist-item-remove-btn. margin-left:auto pins it to the row's
     right edge regardless of whether an organizer/pending role badge
     is also present before it. */
  .overview-people-remove-btn {
    background: none;
    border: none;
    color: #bbb6ab;
    cursor: pointer;
    padding: var(--space-2);
    display: flex;
    margin-left: auto;
    flex-shrink: 0;
  }
  /* Stubbed - see openOverviewAddPerson (TODO: invite flow, not
     designed here - README's own "Interactions" section). */
  .overview-add-person-btn {
    margin-top: 18px;
    width: 100%;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    background: none;
    border: 1.5px solid var(--color-accent);
    border-radius: var(--radius-pill);
    padding: var(--space-12);
    font-family: inherit;
    cursor: pointer;
  }
  .overview-add-person-plus {
    color: var(--color-accent);
    font-weight: 700;
    font-size: var(--text-18);
    line-height: 1;
  }
  .overview-add-person-label {
    color: var(--color-accent);
    font-weight: 700;
    font-size: var(--text-15);
  }
  /* "Muted inline add" affordance for Reminders' "+ Add reminder" (see
     openOverviewAddReminder) - Checklist used to share this exact look
     for its own now-removed "+ Add item" stub (see
     _trip_checklist_scope/switchChecklistScope's own comments for what
     replaced it: the real Supabase-backed checklist_items system,
     which has its own .checklist-add-row/.checklist-add-btn instead). */
  .overview-card-add-btn {
    margin-top: 16px;
    display: flex;
    align-items: center;
    gap: var(--space-8);
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
  }
  .overview-card-add-btn--centered {
    justify-content: center;
    width: 100%;
    margin-top: 8px;
  }
  .overview-card-add-plus {
    color: #b8aa9c;
    font-weight: 700;
    font-size: var(--text-18);
    line-height: 1;
  }
  .overview-card-add-label {
    color: #b8aa9c;
    font-weight: 600;
    font-size: var(--text-16);
  }
  .overview-reminders-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
  }
  .overview-reminder-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-10);
  }
  .overview-reminder-title {
    font-size: var(--text-16);
    font-weight: 600;
    color: var(--color-ink);
  }
  .overview-reminder-when {
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-muted);
    margin-top: 2px;
  }
  .overview-reminder-remove-btn {
    background: none;
    border: none;
    color: #bbb6ab;
    cursor: pointer;
    padding: var(--space-2);
    display: flex;
    flex-shrink: 0;
  }
  .overview-reminder-add-row {
    flex-direction: column;
    gap: var(--space-8);
    margin-top: 14px;
  }
  .overview-reminder-title-input, .overview-reminder-when-input {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 8px 10px;
    font-size: var(--text-14);
    font-family: inherit;
  }
  .overview-reminder-save-btn {
    align-self: flex-start;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 700;
    color: #fff;
    background: #675f55;
    border: none;
    border-radius: var(--radius-lg);
    padding: 8px 18px;
    cursor: pointer;
  }
  /* "Plan a new trip" form (see NEW_TRIP_FORM_TEMPLATE) - same overlay-
     dialog visual language as .expense-form (opaque cream card, since
     it sits on the dark scrim - see that class's own CSS comment for
     why). */
  .new-trip-form {
    display: flex;
    flex-direction: column;
    background: #fbf3e7;
    border-radius: var(--radius-4xl);
    box-shadow: var(--shadow-lg);
  }
  .new-trip-form-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-24);
    color: var(--color-ink);
    padding: 20px 20px 0;
  }
  /* Points travelers back at the one thing that actually works today
     (see travel_app_decisions_log.md's "per-email manual forwarding"
     decision) - shown above the sign-in/fields split, regardless of
     auth state, since forwarding needs no account at all. Same card
     look as .new-trip-signin-prompt right below it. */
  .new-trip-forward-hint {
    margin: 16px 20px 0;
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-xl);
    padding: 14px 16px;
    display: flex;
    align-items: flex-start;
    gap: var(--space-12);
  }
  /* Same mail+sparkle icon as .overview-add-more-icon (see that class's
     own comment for provenance), scaled down to fit this more compact
     hint card - same 46:38 aspect ratio, just a smaller box. Reuses
     .overview-add-more-sparkle for the twinkle animation rather than a
     second copy of the keyframes. */
  .new-trip-forward-hint-icon {
    flex-shrink: 0;
    width: 28px;
    height: 23px;
    margin-top: 1px;
  }
  .new-trip-forward-hint-icon svg {
    display: block;
    width: 100%;
    height: 100%;
  }
  .new-trip-forward-hint-body {
    flex: 1;
    min-width: 0;
  }
  .new-trip-forward-hint-text {
    font-size: var(--text-13);
    line-height: 1.4;
    color: var(--color-body);
    margin-bottom: 8px;
  }
  .new-trip-forward-row {
    display: flex;
    align-items: center;
    gap: var(--space-6);
  }
  .new-trip-forward-address {
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-ink);
    word-break: break-all;
  }
  /* Same visual weight as .copy-address-btn (address-link copy buttons
     elsewhere) - not reused directly since this one sits in its own
     flex row rather than inline after a link. */
  .new-trip-forward-copy-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 26px;
    height: 26px;
    padding: 0;
    border: none;
    border-radius: var(--radius-circle);
    background: #f5f1e8;
    color: var(--color-body);
    cursor: pointer;
  }
  .new-trip-forward-copy-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .new-trip-signin-prompt {
    margin: 16px 20px 20px;
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-xl);
    padding: var(--space-16);
  }
  .new-trip-signin-title {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    margin-bottom: 12px;
  }
  .new-trip-signin-email {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: 8px;
  }
  .new-trip-signin-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 8px;
  }
  .new-trip-fields {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    padding: 16px 20px 20px;
  }
  .new-trip-form-label {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-12);
    letter-spacing: 0.345px;
    text-transform: uppercase;
    color: var(--color-ink);
    margin-bottom: -4px;
  }
  .new-trip-name-input,
  .new-trip-start-input,
  .new-trip-end-input {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    background: #fff;
    box-shadow: var(--shadow-md);
    border: none;
    border-radius: var(--radius-md);
    padding: 14px 16px;
  }
  /* Reuses .idea-autocomplete-list/-item as-is (see NEW_TRIP_FORM_
     TEMPLATE/the Itinerary destinations planner) - only the
     positioning wrapper is new. */
  .new-trip-autocomplete-wrap {
    position: relative;
  }
  .new-trip-label-optional {
    color: #bfbfbf;
    text-transform: none;
  }
  /* Itinerary-tab destinations planner (see _new_trip_itinerary_html) -
     the one place an app-created trip's destinations/dates/name get
     managed, both right after creation and any time later. */
  .trip-destinations-panel {
    padding-top: 30px;
    padding-bottom: 40px;
  }
  /* Same card look as .trip-destination-row below it (shadow/radius/
     padding all matched) - was using a lighter, different shadow, read
     as visually inconsistent with the destination cards right under it. */
  .trip-dest-name-input {
    width: 100%;
    box-sizing: border-box;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    background: #fff;
    box-shadow: var(--shadow-md);
    border: none;
    border-radius: var(--radius-md);
    padding: 14px 16px;
    margin-bottom: 20px;
  }
  .trip-destinations-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-10);
    margin-bottom: 16px;
  }
  /* Live-trip day-by-day itinerary (see _buildLiveDaySections) - each
     .trip-day-section wraps one real .day-header/.day-ideas pair (same
     classes a pipeline trip's day already uses, see DAY_HEADER_TEMPLATE/
     DAY_IDEAS_TEMPLATE) plus this trip's own "+ Add event" trigger. */
  .trip-days-list {
    margin-top: 8px;
  }
  .trip-day-add-event-btn {
    margin: 0 0 12px;
  }
  /* Self-built "Add event" dialog (see openAddEventOverlay) - no
     server-rendered form exists for this yet, so its own inputs need
     their own scoped styling rather than borrowing .idea-form's. */
  .event-add-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-10);
  }
  .event-add-form input, .event-add-form select, .event-add-form textarea {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 8px 10px;
    font-size: var(--text-14);
    font-family: inherit;
    width: 100%;
    box-sizing: border-box;
  }
  .event-add-form textarea {
    resize: vertical;
  }
  .trip-destination-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-10);
    background: #fff;
    border-radius: var(--radius-3xl);
    box-shadow: var(--shadow-md);
    padding: 14px 16px;
    box-sizing: border-box;
    /* border-left set inline per-row (see _renderTripDestinations) to
       the same palette color as this destination's days in the
       calendar overview above - ties the list and the calendar
       together at a glance without repeating the legend on every row.
       Transparent default keeps a dateless destination's row unchanged. */
    border-left: 4px solid transparent;
  }
  .trip-destination-label {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-15);
    color: var(--color-ink);
    flex: 1;
    min-width: 100px;
  }
  .trip-destination-dates {
    display: flex;
    align-items: center;
    gap: var(--space-6);
  }
  .trip-dest-date-wrap {
    position: relative;
  }
  .trip-dest-date-chip {
    font-family: inherit;
    font-size: var(--text-13);
    color: var(--color-body);
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-md);
    padding: 6px 10px;
    cursor: pointer;
    white-space: nowrap;
  }
  .trip-dest-date-chip:hover {
    border-color: var(--color-border);
  }
  /* Light tint flags a destination's dates as still an inherited
     default (from the trip's own range or the previous destination),
     not yet confirmed for THIS destination specifically - otherwise
     identical-looking date values give no visual clue they're a
     placeholder someone should double-check, not a deliberate choice. */
  .trip-destination-dates--default .trip-dest-date-chip {
    background: rgba(240,81,46,0.07);
    border-color: rgba(240,81,46,0.3);
  }
  /* Custom calendar popup replacing the native <input type="date"> -
     a native picker is drawn entirely by the OS/browser and can't be
     styled to show a tinted range at all, which real dogfooding asked
     for directly: this destination's own in-between days tinted, plus
     any OTHER destination's already-claimed days as a reference tint. */
  .trip-dest-calendar-popup {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 4px;
    background: #fff;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-lg);
    padding: var(--space-12);
    z-index: 30;
    width: 246px;
  }
  .trip-dest-calendar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  .trip-dest-calendar-month-label {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-14);
    color: var(--color-ink);
  }
  .trip-dest-calendar-nav {
    background: none;
    border: none;
    font-size: var(--text-16);
    color: var(--color-body);
    cursor: pointer;
    padding: 2px 6px;
    line-height: 1;
  }
  .trip-dest-calendar-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    font-size: var(--text-11);
    font-weight: 700;
    color: var(--color-muted);
    text-align: center;
    margin-bottom: 4px;
  }
  .trip-dest-calendar-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: var(--space-2);
  }
  .trip-dest-calendar-day {
    font-family: inherit;
    font-size: var(--text-13);
    color: var(--color-ink);
    background: none;
    border: none;
    border-radius: var(--radius-circle);
    width: 30px;
    height: 30px;
    cursor: pointer;
  }
  .trip-dest-calendar-day:hover:not(:disabled) {
    background: #f2f0e9;
  }
  .trip-dest-calendar-day--disabled {
    color: var(--color-border);
    cursor: default;
  }
  /* This destination's own picked range - light accent tint spans the
     in-between days, solid accent circle marks the start/end days
     themselves (the classic range-picker look: "start date picked...
     dates in between with a tint"). */
  .trip-dest-calendar-day--own-range {
    background: rgba(240,81,46,0.12);
    border-radius: 0;
  }
  .trip-dest-calendar-day--own-endpoint {
    background: var(--color-accent);
    color: #fff;
    border-radius: var(--radius-circle);
  }
  /* A DIFFERENT destination's already-claimed range (e.g. Bohol's,
     while picking Manila's dates) - a muted neutral tint so it reads
     as "reference, not mine" rather than competing with the accent-
     colored range above. */
  .trip-dest-calendar-day--other-range {
    background: rgba(107,94,82,0.14);
  }
  .trip-dest-calendar-day--today {
    box-shadow: inset 0 0 0 1px var(--color-muted);
  }
  /* Read-only trip-wide calendar overview (see _renderTripCalendarOverview)
     - reuses the same header/weekday/grid classes as the per-destination
     popup above, but the day cells are square (not circular buttons)
     since a whole month of destination-colored days reads better as a
     flat mosaic than as individual selectable dots. */
  .trip-calendar-overview {
    margin-bottom: 20px;
  }
  .trip-calendar-overview-inner {
    background: #fff;
    border-radius: var(--radius-3xl);
    box-shadow: var(--shadow-md);
    padding: 14px 16px;
  }
  .trip-calendar-overview-day {
    font-family: inherit;
    font-size: var(--text-13);
    color: var(--color-ink);
    border-radius: var(--radius-sm);
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .trip-calendar-overview-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 16px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid #f2f0e9;
  }
  .trip-calendar-overview-legend-item {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    font-size: var(--text-13);
    color: var(--color-body);
  }
  .trip-calendar-overview-legend-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 3px;
    flex-shrink: 0;
  }
  .trip-destination-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-circle);
    background: #f2f0e9;
    border: none;
    cursor: pointer;
    color: var(--color-body);
    font-size: var(--text-14);
    line-height: 1;
    padding: 0;
    flex-shrink: 0;
  }
  .trip-dest-input {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    background: #fff;
    box-shadow: var(--shadow-md);
    border: none;
    border-radius: var(--radius-md);
    padding: 14px 16px;
  }
  .new-trip-submit-btn {
    margin-top: 8px;
  }
  .new-trip-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    text-align: center;
  }
  /* Account/Profile overlay (see ACCOUNT_OVERLAY_TEMPLATE, opened via
     openAccountOverlay from the header avatar button) - redesigned
     2026-08 per the "Profile Drawer" handoff (design_handoff_add_to_list/
     Profile Drawer.dc.html); tokens (colors/radii/spacing) below are
     lifted straight from that file's own README rather than reused from
     elsewhere, since it's a from-scratch visual pass on this surface. */
  /* Unlike CARD_TEMPLATE-based overlay content, this template's root
     has no .card class of its own, and the shared .card-overlay-panel
     it sits inside supplies no background/radius/shadow by default -
     without this it rendered as transparent text floating over the
     dimmed scrim. This overlay is always in drawer mode now (see
     openAccountOverlay), so the panel itself always supplies the real
     white/shadow chrome - flush (no radius/shadow) and stretched to
     fill the panel's full height unconditionally, not just >=900px. */
  .account-overlay-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    padding: 20px 26px 0;
  }
  /* ACCOUNT_OVERLAY_TEMPLATE's actual title/identity/tabs/panels all
     live one level deeper than .account-overlay-body itself, inside
     this wrapper (saveProfile et al. scope their querySelectors off
     .account-profile-fields specifically) - without repeating the same
     flex chain here too, this block-level div broke it: .profile-
     section-panel-active's flex:1 had nothing to grow into, so the
     cream background below stopped short of the drawer's real bottom
     edge instead of reaching it. */
  .account-profile-fields {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
  }
  .account-overlay-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-24);
    color: #4A3F35;
  }
  .account-identity-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-10);
    /* Bottom padding was 24px - +15px so the tabs (and everything in
       the panels below them) sit 15px lower than the name, without
       moving the avatar/name themselves. */
    padding: 20px 0 40px;
  }
  /* 72px variant of the same avatar circle the header button uses (see
     _avatar_inner_html/_avatar_initials) - this wrapper just sets the
     size/background, the initials/photo markup inside is identical. */
  .account-identity-avatar {
    width: 72px;
    height: 72px;
    border-radius: var(--radius-circle);
    /* Same --user-avatar-color the header button/circle use (see
       .home-avatar-btn/.page-header-avatar-circle) - was a fixed light
       tint (#FBDCD3) with orange initials before the avatar-color-row
       picker existed; switched to the solid-circle/white-initials
       treatment so any of the seven picker colors stays readable here,
       not just the original orange. */
    background: var(--user-avatar-color);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    position: relative;
  }
  .account-identity-avatar .home-avatar-initials {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-24);
    color: #fff;
  }
  .account-identity-avatar .home-avatar-photo {
    width: 130%;
    height: 130%;
    object-fit: cover;
  }
  /* "Profile Color" picker, Preferences tab (see ACCOUNT_OVERLAY_
     TEMPLATE's avatar-color-row, selectAvatarColor/saveProfile) - its
     own fixed 5-color set, unrelated to AVATAR_PALETTE/AVATAR_PALETTE_JS
     (the auto-hash color other members' avatars get when they haven't
     picked one - see _memberDisplayName's neighbors). */
  .avatar-color-row {
    display: flex;
    gap: var(--space-8);
    flex-wrap: wrap;
  }
  .avatar-color-swatch {
    width: 26px;
    height: 26px;
    border-radius: var(--radius-circle);
    border: 2px solid transparent;
    padding: 0;
    cursor: pointer;
  }
  .avatar-color-swatch-active {
    border-color: #4A3F35;
    box-shadow: 0 0 0 2px #fff inset;
  }
  .account-identity-name {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-18);
    color: #4A3F35;
  }
  .account-add-alt-address-btn {
    background: none;
    border: none;
    padding: 0;
    font-size: var(--text-13);
    font-weight: 800;
    color: var(--color-accent);
    cursor: pointer;
  }
  .account-add-alt-address-btn:disabled {
    color: rgba(74,63,53,0.35);
    cursor: default;
  }
  .account-alt-address-form {
    margin-top: 10px;
  }
  .profile-section-tabs {
    display: flex;
    gap: var(--space-4);
    margin: 0 -26px;
    padding: 0 20px;
    border-bottom: 1px solid rgba(74,63,53,0.1);
  }
  .profile-section-tab {
    flex: 1;
    text-align: center;
    background: none;
    border: none;
    border-bottom: 3px solid transparent;
    margin-bottom: -1px;
    padding-bottom: 14px;
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-muted);
    cursor: pointer;
  }
  .profile-section-tab-active {
    color: #4A3F35;
    border-bottom-color: var(--color-accent);
  }
  /* "Profile" (first tab) and "Account" (last tab) sit 30px from the
     drawer's edges on desktop, wider than the 26px every other row in
     this drawer uses, with extra gap between all four tabs so they
     read as deliberately spread rather than just matching the rest of
     the panel by coincidence. Narrower on mobile (20px inset, 4px gap)
     since the drawer itself is already the full viewport width and a
     4th tab (Checklists, the longest label) needs the room to avoid
     wrapping. */
  @media (min-width: 900px) {
    .profile-section-tabs {
      gap: var(--space-16);
      padding: 0 30px;
    }
  }
  .profile-section-panel {
    display: none;
  }
  /* Cream (#FCF7F0) starts right at the tab row's own grey hairline
     (.profile-section-tabs' border-bottom) and runs to the bottom edge
     of the drawer - full-bleed via the negative margin cancelling
     .account-overlay-body's own side padding (own padding re-added
     here), flex:1 + min-height:0 so it actually reaches the bottom on
     tall viewports instead of just wrapping tight around the fields,
     and its own scroll (not the drawer's) so the white title/identity/
     tabs area above stays pinned while this scrolls. Everything above
     this line (title/identity/tabs) stays on .account-overlay-body's
     plain white. */
  .profile-section-panel-active {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    flex: 1;
    min-height: 0;
    margin: 0 -26px;
    padding: 22px 26px 26px;
    background: #FCF7F0;
    overflow-y: auto;
  }
  /* Profile tab only - "Display name" (now "Your Name") and everything
     below it sits 65px under the tab row's hairline, wider than every
     other panel's 22px top inset. */
  .profile-section-panel-active[data-profile-section-panel="profile"] {
    padding-top: 64px;
  }
  /* Account tab only - 30px side inset instead of the shared 26px, so
     the email/password fields don't read as flush against the drawer's
     edge. */
  .profile-section-panel-active[data-profile-section-panel="account"] {
    padding-left: 30px;
    padding-right: 30px;
  }
  /* Wraps each label + its own field so the 12px gap between them is
     exact, not stacked on top of .profile-section-panel-active's own
     16px between-field-group gap (flex gap and a margin don't collapse,
     so a margin-bottom on the label alone was compounding with that
     parent gap into ~28px - real bug found live-testing). */
  .account-field-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
  }
  .account-form-label {
    font-size: var(--text-15);
    font-weight: 700;
    color: var(--color-body);
  }
  .account-row-label {
    font-size: var(--text-16);
    font-weight: 600;
    color: #4A3F35;
  }
  .account-email-display {
    font-size: var(--text-18);
    color: var(--color-muted);
    background: #FAF7F0;
    border: 1.5px solid rgba(74,63,53,0.14);
    border-radius: var(--radius-2xl);
    padding: 12px 14px;
  }
  .account-full-name-input,
  .account-home-address-input,
  .account-alt-address-input,
  .account-new-password-input,
  .account-home-airport-input {
    width: 100%;
    box-sizing: border-box;
    font-size: var(--text-18);
    color: #4A3F35;
    background: #FFFFFF;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 12px 14px;
  }
  .account-full-name-input::placeholder,
  .account-home-address-input::placeholder,
  .account-alt-address-input::placeholder,
  .account-new-password-input::placeholder,
  .account-home-airport-input::placeholder {
    color: #B3A797;
  }
  .account-full-name-input:focus,
  .account-home-address-input:focus,
  .account-alt-address-input:focus,
  .account-new-password-input:focus,
  .account-home-airport-input:focus {
    outline: none;
    border-color: var(--color-accent);
  }
  .account-home-address-input,
  .account-alt-address-input {
    min-height: 70px;
    resize: none;
  }
  .account-home-address-input:disabled,
  .account-alt-address-input:disabled {
    background: #F7F2E9;
    color: #4A3F35;
    -webkit-text-fill-color: #4A3F35;
    opacity: 1;
    cursor: default;
  }
  /* Hairline settings row shared by the dropdown/segmented/toggle rows
     below (Preferences tab) - label left, control right, per the
     handoff's "hairline rows (1px solid #ECE4D6, padding 15px 2px)". */
  .account-dropdown-row,
  .account-segmented-row,
  .account-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-12);
    padding: 16px 2px;
    border-bottom: 1px solid #ECE4D6;
  }
  .account-language-input,
  .account-currency-input {
    -webkit-appearance: none;
    appearance: none;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%239C8F82' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right center;
    border: none;
    font-family: inherit;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-muted);
    text-align: right;
    padding-right: 20px;
    cursor: pointer;
  }
  .account-dropdown-row-plain .account-home-airport-input {
    width: 90px;
    text-align: right;
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-muted);
    padding: 4px 0;
    background: transparent;
    border: none;
    /* Borderless transparent inline field (airport code, right-aligned
       in the dropdown row) - not a boxed input, radius intentionally 0;
       excluded from the 2026-08-07 input-radius unification. */
    border-radius: 0;
  }
  /* Temperature/Distance rows - a real on/off toggle switch (like Trip
     reminder emails below) flanked by its two unit labels, replacing
     the old two-button segmented-pill control those rows used to use. */
  .account-unit-toggle {
    display: flex;
    align-items: center;
    gap: var(--space-8);
  }
  .account-unit-toggle-label {
    font-size: var(--text-13);
    font-weight: 700;
    color: #B3A797;
  }
  .account-unit-toggle-label-active {
    color: var(--color-body);
  }
  /* Knob is the button's own ::before rather than extra markup - see
     ACCOUNT_OVERLAY_TEMPLATE's plain <button class="account-toggle-switch">. */
  .account-toggle-switch {
    position: relative;
    width: 42px;
    height: 24px;
    border-radius: var(--radius-pill);
    background: #E3DACB;
    border: none;
    padding: 0;
    cursor: pointer;
    flex-shrink: 0;
  }
  .account-toggle-switch::before {
    content: '';
    position: absolute;
    top: 3px;
    left: 3px;
    width: 18px;
    height: 18px;
    border-radius: var(--radius-circle);
    background: #fff;
    transition: left 200ms cubic-bezier(0.3,0.9,0.3,1);
  }
  .account-toggle-switch-on {
    background: var(--color-body);
  }
  .account-toggle-switch-on::before {
    left: 21px;
  }
  /* Temperature/Distance only - inverted from the plain on/off switch
     above so the selected unit is unambiguous at a glance: off (F/mi) is
     a white track with a brown knob, on (C/km) flips to a brown track
     with a white knob, rather than a white knob in both states. */
  .account-unit-toggle .account-toggle-switch {
    box-sizing: border-box;
    background: #fff;
    border: 1.5px solid rgba(74,63,53,0.14);
  }
  .account-unit-toggle .account-toggle-switch::before {
    background: var(--color-body);
  }
  .account-unit-toggle .account-toggle-switch-on {
    background: var(--color-body);
    border-color: var(--color-body);
  }
  .account-unit-toggle .account-toggle-switch-on::before {
    background: #fff;
  }
  .account-password-row {
    position: relative;
  }
  .account-password-row .account-new-password-input {
    padding-right: 56px;
  }
  .account-password-show-btn {
    position: absolute;
    top: 50%;
    right: 14px;
    transform: translateY(-50%);
    background: none;
    border: none;
    padding: 0;
    font-size: var(--text-13);
    font-weight: 800;
    color: var(--color-accent);
    cursor: pointer;
  }
  .account-save-btn {
    display: block;
    width: 250px;
    max-width: 100%;
    margin: 30px auto 0;
  }
  .account-save-btn-saved {
    background: #4A3F35;
  }
  .account-signout-btn {
    display: block;
    margin: 60px auto 0;
    background: none;
    border: none;
    font-size: var(--text-15);
    font-weight: 700;
    color: var(--color-accent);
    cursor: pointer;
  }
  /* The two address captions (see ACCOUNT_OVERLAY_TEMPLATE) are real
     <input>s, not <div>s, so a user with more than one address can
     rename "Home address"/"Alternate address" to something meaningful
     (e.g. "NYC" vs "Catskills") - kept in the DOM/data (home_address_label/
     alternative_address_label) for anyone who already set one, but their
     own inputs are hidden (inline style) since the new design shows only
     the generic captions and never surfaces a rename affordance. This
     CSS is dormant unless that display:none is lifted again later. */
  .account-home-label-input,
  .account-alt-label-input {
    display: block;
    width: auto;
    background: none;
    border: none;
    border-bottom: 1px dotted #c2c0bf;
    border-radius: 0;
    padding: 0 0 2px;
  }
  .account-home-label-input:focus,
  .account-alt-label-input:focus {
    outline: none;
    border-bottom-color: var(--color-muted);
    /* Same underline-style opt-out as .overview-title-edit-input above. */
    box-shadow: none;
  }
  /* Sign-in/create-account screen (see SIGNIN_SCREEN_TEMPLATE, opened
     via openSignInScreen) - lives inside the shared card-overlay
     machinery but with .card-overlay-fullpage-mode (below) making it
     read as a real page rather than a floating dialog.

     Matches the Figma "Sign in - Desktop/Mobile" frames. Mobile
     (default): .signin-illustration is the reused Start Screen scene
     full-bleed behind everything; .signin-form-panel is an absolutely-
     positioned bottom sheet that slides up over it on a delay (same
     translateY(100%)->0 move as .card-overlay-sheet-mode elsewhere,
     just animation-delayed rather than class-toggled since this panel
     is visible on mount, not opened later) - "show the animation
     first, then slide up the sign in." Desktop (min-width:900px):
     becomes a static side-by-side split, illustration on the right,
     form panel a fixed-width column on the left, no sheet animation. */
  .signin-screen-body {
    position: relative;
    min-height: 100vh;
    min-height: 100dvh;
    overflow: hidden;
  }
  .signin-illustration {
    position: absolute;
    inset: 0;
    overflow: hidden;
  }
  /* Sky is a flat gradient (see .app-bg-sky) with no fixed proportions
     to violate, so it's the one piece of this scene that's actually
     safe to resize freely - a standalone layer that always fills the
     whole panel, instead of being sized/scaled together with the
     ground elements below (which do have real proportions to protect
     - see .signin-illustration-scene). This is also what removes the
     empty-margin trade-off of the old single-scaled-box approach: any
     leftover space around the ground scene now shows as more sky
     (which belongs there) rather than a bare gap. */
  .signin-illustration-sky {
    position: absolute;
    inset: 0;
    z-index: 0;
  }
  /* .signin-illustration-scene wraps .app-bg (dunes/trees/skyline/
     clouds) - the shapes that actually need protecting from
     distortion (the dune/skyline/tree SVGs use preserveAspectRatio=
     "none" and percentage sizing, so any independent width/height
     stretch of this box distorts them - see the "never stretch"
     memory). Mobile fills the panel full-bleed (phone aspect ratios
     are all close enough to how this scene was originally drawn that
     this alone doesn't cause visible distortion); desktop overrides
     this with a fixed, non-scaling size instead (see that comment). */
  .signin-illustration-scene {
    position: absolute;
    inset: 0;
    z-index: 1;
  }
  /* .app-bg (see START_SCREEN_BG_HTML) is normally position:fixed/
     inset:0 relative to the viewport with a negative z-index (it's
     meant to sit behind ordinary page content) - nested here instead,
     it needs to fill this panel specifically and paint above this
     panel's own background instead of behind it. Its own nested
     .app-bg-sky is hidden - .signin-illustration-sky above replaces it
     so sky and ground can be positioned independently. */
  /* Direct feedback: was width:620px/max-width:100% (capped at the
     same 620px .app-bg is everywhere else in the app) - fine on an
     actual phone (narrower than 620 anyway, so max-width was already
     doing the real work) but left visible sky-only gaps on the sides
     of anything wider (a large phone in landscape, a tablet under the
     900px desktop breakpoint). width:100% unconditionally instead, so
     the ground scene always bleeds edge to edge at any mobile width -
     the dune/skyline shapes tolerate some width stretch fine (see the
     desktop comment above), same reasoning, just applied here too. */
  .signin-illustration-scene .app-bg {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    transform: none;
    width: 100%;
    max-width: none;
    margin: 0;
    z-index: 0;
  }
  .signin-illustration-scene .app-bg-sky { display: none; }
  /* Direct feedback: should start vertically centered on load - was
     padding-top:18%, but padding-top/bottom percentages always
     resolve against the containing block's WIDTH, not height (a real
     CSS gotcha), so that 18% was actually 18% of the narrow phone
     width, not a meaningful vertical position at all. justify-content
     centers correctly regardless of aspect ratio. */
  /* Direct feedback: should sit higher on load - between the two top
     clouds (.bg-cloud-1/.bg-cloud-3 sit around 17-20% down their own
     box) rather than dead-centered. padding-top:18vh, not 18% - see
     the earlier comment on this same gotcha: percentage padding
     always resolves against the containing block's WIDTH, vh is the
     one length unit that's actually relative to height regardless. */
  .signin-illustration-brand {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    padding-top: 18vh;
  }
  /* Direct feedback: smaller than the shared .start-logo's own 220px. */
  .signin-illustration-brand .start-logo {
    width: 150px;
    max-width: 40%;
  }
  /* Direct feedback: on a tall phone the base .bg-tree-1/2 top:%
     values (tuned for the Start Screen's own typical aspect) put the
     trees low enough to sit mostly behind the browser's own bottom
     toolbar - barely visible. bottom:vh instead, same reasoning as
     the desktop trees above (a fixed clearance from the bottom edge
     that doesn't depend on how tall this particular phone/toolbar
     combination made the visible area). Direct feedback (round 2):
     still too far down on a real device - real mobile Safari chrome
     (address bar, an extension suggestion bar) eats into the visible
     area in a way a desktop-browser viewport resize doesn't reproduce,
     so clearance raised well past what looked sufficient in testing. */
  .signin-illustration .bg-tree-1 { top: auto; bottom: 30vh; }
  .signin-illustration .bg-tree-2 { top: auto; bottom: 24vh; }
  /* The tagline's gap from the logo was only ever overridden for
     desktop (70px, well below) - mobile was still using the shared
     .start-tagline's own 18px, nowhere near enough to guarantee the
     tagline clears the sheet's own top edge (min-height:87vh, so a
     larger fraction of a taller viewport) once .signin-brand-up's
     vh-proportional shift lands the logo safely clear at any height -
     50px keeps positive clearance for the tagline too across the
     ~667-926px viewport-height range real phones fall in. */
  .signin-illustration-brand .start-tagline {
    margin-top: 70px;
  }
  .signin-form-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    box-sizing: border-box;
    background: #fff;
    border-radius: 28px 28px 0 0;
    box-shadow: 0 -8px 24px 0 rgba(80,60,40,0.08);
    padding: 32px 24px 40px;
    /* Grows upward from the bottom with its own content (no top:0 set)
       - capped + independently scrollable so a short/landscape mobile
       viewport can't push the top of the form (the title) up past
       .signin-screen-body's own overflow:hidden edge and clip it. */
    max-height: 88vh;
    overflow-y: auto;
    /* Direct feedback: the sheet should cover the "Less scrolling./
       More traveling." tagline once it slides up, not stop just below
       it - tall enough on its own regardless of how little the form
       content needs. Desktop resets this back to auto (see below).
       Direct feedback (round 2): covering the tagline is fine - only
       the logo needs to stay clear - so raised further, right up
       against max-height's own 88vh cap just below. */
    min-height: 87vh;
  }
  @media (prefers-reduced-motion: no-preference) {
    .signin-form-panel {
      transform: translateY(100%);
      animation: signin-sheet-up 400ms var(--ease-decel) 3600ms forwards;
    }
    @keyframes signin-sheet-up {
      to { transform: translateY(0); }
    }
    /* Direct feedback: move the logo up in step with the sheet
       sliding up (same delay/duration/easing as signin-sheet-up
       above) to a specific target once the sheet has landed - the
       logo's own CENTER sitting at the midpoint between the browser's
       top edge (0) and the sheet's top edge (min-height:87vh above ->
       the sheet's own top is at 13vh). Midpoint = 6.5vh, so logo top
       = 6.5vh - halfLogoHeight (~28px, the 150px-wide logo's own
       rendered height is ~56px). .signin-illustration-brand now
       starts at a literal padding-top:18vh (see above), so the shift
       needed is just that target minus 18vh: (6.5vh - 28px) - 18vh =
       -11.5vh - 28px. All vh-relative (not a fixed px landing spot),
       so this tracks correctly at any device height instead of only
       the specific heights it happened to get tested at. */
    .signin-illustration-brand {
      animation: signin-brand-up 400ms var(--ease-decel) 3600ms forwards;
    }
    @keyframes signin-brand-up {
      to { transform: translateY(calc(-28px - 11.5vh)); }
    }
  }
  .signin-normal-mode, .signin-reset-mode {
    max-width: 340px;
    margin: 0 auto;
  }
  .signin-title {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-24);
    font-weight: 700;
    color: var(--color-ink);
    margin: 0 0 24px;
    text-align: center;
  }
  .signin-title-signup { display: none; }
  [data-mode="signup"] .signin-title-signin { display: none; }
  [data-mode="signup"] .signin-title-signup { display: block; }
  .signin-password-field {
    position: relative;
  }
  .signin-password-field .signin-password-input {
    padding-right: 44px;
  }
  .signin-password-toggle-btn {
    position: absolute;
    right: 6px;
    top: 0;
    bottom: 12px;
    display: flex;
    align-items: center;
    background: none;
    border: none;
    padding: 0 8px;
    cursor: pointer;
    color: var(--color-muted);
  }
  .signin-password-toggle-btn .material-symbols-outlined {
    font-size: var(--text-20);
  }
  .signin-email-input,
  .signin-password-input,
  .signin-reset-password-input,
  .signin-reset-confirm-input {
    width: 100%;
    box-sizing: border-box;
    font-family: 'Google Sans Flex', 'Baloo 2', sans-serif;
    /* 16px, not 14 - iOS Safari auto-zooms the whole page on focus for
       any input with a computed font-size under 16px (direct feedback:
       "when you tap on email, the page gets bigger"). */
    font-size: var(--text-16);
    color: #3a352c;
    background: #fff;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-lg);
    padding: 18px 20px;
    margin-bottom: 14px;
  }
  .signin-email-input::placeholder,
  .signin-password-input::placeholder,
  .signin-reset-password-input::placeholder,
  .signin-reset-confirm-input::placeholder {
    color: var(--color-muted);
  }
  .signin-remember-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-12);
    margin: 4px 0 20px;
  }
  [data-mode="signup"] .signin-remember-row { display: none; }
  .signin-remember-label {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-family: 'Google Sans Flex', 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    color: #3f3f3f;
    cursor: pointer;
    white-space: nowrap;
  }
  .signin-remember-checkbox {
    width: 18px;
    height: 18px;
    margin: 0;
    accent-color: var(--color-accent);
    border-radius: 5px;
    cursor: pointer;
  }
  .signin-forgot-btn {
    background: none;
    border: none;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    color: #3f3f3f;
    cursor: pointer;
    padding: var(--space-4);
    white-space: nowrap;
  }
  .signin-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    text-align: center;
    margin: 8px 0 0;
    min-height: 16px;
  }
  .signin-status:empty {
    margin: 0;
    min-height: 0;
  }
  /* Direct feedback (with a reference screenshot): error messages were
     easy to miss as plain gray text under the button - a real banner
     (background + icon) instead, only for the error case (see
     _setSigninStatus in the JS - plain progress/success text like
     "Sending..." stays the quiet inline style above). */
  .signin-status.signin-status-error {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    text-align: left;
    background: #fbe2df;
    color: #7a3226;
    font-size: var(--text-14);
    line-height: 1.35;
    border-radius: var(--radius-2xl);
    padding: 14px 16px;
    margin: 14px 0 0;
    min-height: 0;
  }
  .signin-status.signin-status-error::before {
    content: 'cancel';
    font-family: 'Material Symbols Outlined';
    font-size: var(--text-20);
    color: #c8552d;
    flex-shrink: 0;
  }
  /* Same banner treatment as the error state, blue instead of red - for
     "check your email" confirmations, which are informational (not
     errors) but were getting missed as plain gray text just like the
     errors were. */
  .signin-status.signin-status-info {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    text-align: left;
    background: #dfeaf9;
    color: #1f4e79;
    font-size: var(--text-14);
    line-height: 1.35;
    border-radius: var(--radius-2xl);
    padding: 14px 16px;
    margin: 14px 0 0;
    min-height: 0;
  }
  .signin-status.signin-status-info::before {
    content: 'mail';
    font-family: 'Material Symbols Outlined';
    font-size: var(--text-20);
    color: #2f6fb0;
    flex-shrink: 0;
  }
  .signin-divider {
    text-align: center;
    font-family: 'Google Sans Flex', 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    color: #3f3f3f;
    margin: 14px 0;
  }
  .signin-magiclink-btn {
    width: 100%;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 600;
    color: var(--color-accent);
    background: #fff;
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-pill);
    padding: var(--space-16);
    cursor: pointer;
  }
  .signin-switch-row {
    text-align: center;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    font-weight: 500;
    color: var(--color-ink);
    margin-top: 24px;
  }
  .signin-switch-row-signup { display: none; }
  [data-mode="signup"] .signin-switch-row-signin { display: none; }
  [data-mode="signup"] .signin-switch-row-signup { display: block; }
  .signin-tab-btn {
    color: var(--color-accent);
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
  }
  .signin-reset-title {
    font-size: var(--text-16);
    font-weight: 700;
    color: #3a352c;
    text-align: center;
    margin-bottom: 16px;
  }
  @media (prefers-reduced-motion: reduce) {
    .signin-form-panel { transform: none; }
  }
  @media (min-width: 900px) {
    .signin-screen-body {
      display: flex;
      overflow: visible;
    }
    .signin-illustration {
      position: relative;
      inset: auto;
      order: 2;
      flex: 1;
    }
    /* Never stretch a graphic non-uniformly (see memory). A single
       fixed-size box (the previous approach) either left a visible sky
       gap on both sides (box narrower than the panel) or cropped
       symmetrically (box wider) - direct feedback: neither reads right
       once you notice it, and there's no one fixed width that's
       correct for every panel width anyway. Splitting by shape
       tolerance instead: the dune/sky are smooth, edge-to-edge
       silhouettes with no sharp features - .app-bg stretching to
       exactly 100% of the panel's actual width reads as "wider hill",
       not as a distorted shape, and it's what makes the ground genuinely
       reach both edges at any width instead of picking one width to
       target. Skyline and the trees are the shapes where stretching
       actually looks wrong (buildings/trunks visibly warping) - those
       get pulled OUT of the percentage system entirely below, fixed
       pixel size (their own true aspect ratio, via aspect-ratio) and
       anchored a fixed distance from the panel's own left/right edge,
       same idea as the user's own "anchor ellipse 1 left, ellipse 4
       right" - so they're always full size, at the correct edge,
       never distorted, never gapped. */
    .signin-illustration-scene {
      position: absolute;
      inset: 0;
    }
    .signin-illustration-scene .app-bg {
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      transform: none;
      width: 100%;
      max-width: none;
    }
    /* left/right/width all in vw (not fixed px) so these scale with
       browser width too - direct feedback. .signin-illustration is
       exactly 50vw wide (the flex:1/flex:1 split above), so Xvw here
       is exactly 2*X% of the panel's own width; every value below is
       simply the old fixed-px value's percentage of the 900px panel
       it was tuned against, expressed that way. aspect-ratio keeps
       height moving in lockstep with width (see the "never stretch"
       memory) - a uniform scale as the window resizes, not the
       overlap the previous fully-fixed-px version caused at narrower
       desktop widths (trees and buildings anchored to opposite edges,
       neither one shrinking, eventually colliding in the middle). */
    /* Direct feedback across several rounds, checked each time against
       a direct Figma-vs-CC screenshot comparison: 56.8% (the base
       rule's value) sat low enough to be mostly hidden behind the dune
       shapes drawn after it in the DOM (dune-back/mid/front all come
       after .bg-skyline in START_SCREEN_BG_HTML, so they paint over
       anything skyline-height low enough to overlap them - dune-mid's
       own top:61.1% is roughly where that occlusion starts). Lifting
       it all the way to 44% cleared that but then read as floating,
       disconnected from the dune's crest. 54% is the empirically-
       checked sweet spot - sits right at the dune crest, fully clear
       of the occlusion zone above it. Width also sized up in an
       earlier round, then corrected from 30vw (an arithmetic slip -
       .signin-illustration is only 50vw wide, so 30vw was 60% of the
       panel, not the intended 30%) down to the true 15vw. */
    .signin-illustration .bg-skyline {
      left: auto;
      right: -0.56vw;
      top: 54%;
      width: 15vw;
      max-width: none;
      aspect-ratio: 206.244 / 96.7952;
      height: auto;
    }
    /* Direct feedback: trees were too close to the bottom edge - a
       top:% position doesn't actually guarantee that, since these
       trees' own height is driven by vw (viewport WIDTH, so they can
       scale with browser width per direct feedback) while the panel's
       height is a completely independent dimension (viewport height).
       At a wide-but-short window, a wide (so tall, same aspect-ratio)
       tree started from a top:% position can overflow past the
       bottom edge even though the same top:% left headroom on a
       narrower/taller window - confirmed happening at 1803x1024.
       bottom:vh instead anchors the trunk's own clearance from the
       bottom edge directly, independent of the tree's own height. */
    /* Direct feedback (round 2): still too big against the Figma
       screenshot (was ~23%/16% of the panel's own width, Figma's own
       trees read closer to ~11%/7.5%) - roughly halved. Direct
       feedback (round 3): 20% bigger again (5.7->6.8vw, 3.9->4.7vw),
       moved up 200px worth of clearance. Direct feedback (round 4):
       150px of bottom clearance specifically (not just "more than
       before") - 150px at the same 1024px-tall reference is ~14.6vh;
       tree-2 set to exactly that, tree-1 kept at the same ~4vh gap
       above it the two already had. Also separated horizontally -
       tree-1 spans left:2.2vw to left+width=9vw, so tree-2's old
       left:5.4vw put it well inside that span (visibly overlapping);
       moved out past tree-1's right edge with a 1vw gap. */
    .signin-illustration .bg-tree-1 {
      left: 2.2vw;
      top: auto;
      bottom: 18.6vh;
      width: 6.8vw;
      max-width: none;
      aspect-ratio: 54 / 158;
      height: auto;
    }
    .signin-illustration .bg-tree-2 {
      left: 10vw;
      top: auto;
      bottom: 14.6vh;
      width: 4.7vw;
      max-width: none;
      aspect-ratio: 37 / 109;
      height: auto;
    }
    /* Direct feedback (round 3): logo needs to be a fixed 200px from
       the top edge - replaces the earlier vertical-center-then-
       shift-up-300px approach with a direct top anchor instead. */
    .signin-illustration-brand {
      position: absolute;
      inset: 0;
      padding-top: 200px;
      justify-content: flex-start;
      transform: none !important;
      /* Cancels the mobile-only signin-brand-up animation (see the
         reduced-motion block above) - that one coordinates with the
         sheet sliding up, which only happens on mobile; desktop's
         logo position is this static 200px-from-top instead. */
      animation: none !important;
    }
    /* Direct feedback: tagline needs ~70px of its own clearance from
       the logo - scoped to this illustration specifically (not the
       shared .start-tagline rule's own 18px, which the real Start
       Screen elsewhere in the app still uses unchanged). */
    .signin-illustration-brand .start-tagline {
      margin-top: 70px;
    }
    .signin-illustration-brand .start-logo {
      /* Direct feedback: desktop logo needs to be bigger than the
         220px/56% shared mobile size above. */
      width: 340px;
      max-width: 64%;
    }
    .signin-form-panel {
      position: static;
      order: 1;
      /* Same flex:1 as .signin-illustration above - a true 50/50 split
         (was a fixed 460px column) per direct feedback: "the login
         white areas should be the same width as the illustration". */
      flex: 1;
      width: auto;
      max-height: none;
      min-height: auto;
      overflow-y: visible;
      display: flex;
      flex-direction: column;
      justify-content: center;
      border-radius: 0;
      box-shadow: 4px 0 24px 0 rgba(80,60,40,0.06);
      padding: 60px 56px;
      transform: none !important;
      animation: none !important;
    }
  }
  /* Full-page presentation (see openSignInScreen) - opaque, fills the
     whole viewport, no visible scrim/dimmed-backdrop edges around it -
     the visual difference between "this is its own page" and "this is
     a popup over the page you were already on." Same underlying
     card-overlay plumbing as every other overlay type (see
     .card-overlay-dialog-mode/.card-overlay-sheet-mode above), just a
     third presentation variant. */
  .card-overlay.card-overlay-fullpage-mode {
    padding: 0;
    align-items: stretch;
    background: #fbf3e7;
    /* .start-screen is z-index:1000 (the splash needs to sit above
       ordinary page content while it plays) - .card-overlay's own
       shared z-index:500 let it paint on top of the sign-in gate
       whenever both are in the DOM at once (the gate can fire via
       DOMContentLoaded before the splash's own ~2s auto-dismiss timer
       finishes), showing the splash's own separate logo/tagline
       through the sign-in screen. Only the fullpage (sign-in) variant
       needs to win that fight - the ordinary dialog/sheet overlays
       never open while the splash is still showing, so their own
       z-index:500 is left alone. */
    z-index: 1001;
  }
  .card-overlay-fullpage-mode .card-overlay-panel {
    max-width: none;
    min-height: 100%;
    background: #fbf3e7;
  }
  /* No escape hatch - the whole app is gated behind sign-in (see the
     app-wide session check on DOMContentLoaded and openSignInScreen's
     own comment), so a dismiss button that just reveals the page
     underneath would make "signed out" meaningless. Same reasoning as
     .card-overlay-sheet-mode hiding the default close button for its
     own in-content one - this mode just has no replacement at all,
     since there's nothing to dismiss TO until you've actually signed
     in. */
  .card-overlay-fullpage-mode .card-overlay-close {
    display: none;
  }
  /* Segmented "toggle" look - one shared tan track; only the SELECTED
     option gets a solid brown pill behind its own text, the other
     option(s) sit directly on the track as plain text with a thin
     divider between any two adjacent unselected ones (none right next
     to the brown pill, so it doesn't read as a fourth boxed segment). */
  .checklist-tabs {
    display: flex;
    background: #E3DACB;
    border-radius: var(--radius-pill);
    padding: var(--space-4);
    margin-bottom: 14px;
  }
  .checklist-tab-btn {
    flex: 1;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-muted);
    background: transparent;
    border: none;
    border-radius: var(--radius-pill);
    padding: var(--space-10);
    cursor: pointer;
  }
  .checklist-tab-btn:not(.checklist-tab-active) + .checklist-tab-btn:not(.checklist-tab-active) {
    box-shadow: -1px 0 0 rgba(74,63,53,0.15);
  }
  .checklist-tab-btn.checklist-tab-active {
    color: #fff;
    background: #675f55;
    box-shadow: var(--shadow-md);
  }
  .checklist-items-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    margin-bottom: 14px;
  }
  /* No box any more (was background/border/border-radius per row,
     direct feedback) - the whole row is one big tap target now (see
     toggleChecklistItemRow, JS) so it needs a hover state to read as
     interactive instead, matching .nav-drawer-main-item's own subtle
     fade-in tint/transition rather than inventing a new one. Negative
     side margin + matching padding lets the hover tint bleed slightly
     past the label's own text alignment, same "not just a highlighted
     text box" effect the nav rows use. */
  .checklist-item-row {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    margin: 0 -8px;
    padding: 10px 8px;
    border-radius: var(--radius-lg);
    cursor: pointer;
    transition: background-color 0.15s ease-in-out;
  }
  .checklist-item-row:hover {
    background: rgba(74,63,53,0.05);
  }
  .checklist-item-check-wrap {
    position: relative;
    display: flex;
    flex-shrink: 0;
  }
  .checklist-item-row input[type="checkbox"] {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    /* Row itself now owns the click-to-toggle job (see
       toggleChecklistItemRow) - this stays a real, focusable checkbox
       for keyboard/screen-reader use, just no longer the only clickable
       thing in the row. */
    position: relative;
    z-index: 1;
    /* Design-audit fix 2026-08-07: was rendering browser-default blue.
       Real bug found live: accent-color only ever paints a native
       checkbox's CHECKED fill - the unchecked resting box ignores it
       entirely and stays the browser's own neutral-gray square/
       rounded-square outline no matter what border-radius says here,
       since border-radius on appearance:auto is inconsistently honored
       by the native control too. accent-color/border-radius alone
       genuinely cannot deliver "circle + green in both states" on a
       real native checkbox - confirmed the hard way (looked right on
       :checked in a screenshot, wrong live). Rebuilt as a real custom
       checkbox instead (appearance:none + our own box/checkmark) for
       full control of both states. Own green (#4E8452), not the
       brighter #2fbf71 CHECKLIST_CHECK_BURST_SVG's own checkmark
       circle uses below - two different greens on the same row by
       design (the burst SVG's own color wasn't part of this request). */
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
    background: #fff;
    border: 2px solid #4E8452;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background 150ms ease;
  }
  .checklist-item-row input[type="checkbox"]:checked {
    background: #4E8452;
  }
  /* Removes the browser's own blue focus ring that appeared on click
     (appearance:none doesn't clear it) - reported live. Keyboard users
     still get a visible, on-brand ring via :focus-visible below rather
     than losing focus indication entirely. */
  .checklist-item-row input[type="checkbox"]:focus {
    outline: none;
  }
  .checklist-item-row input[type="checkbox"]:focus-visible {
    outline: 2px solid #4E8452;
    outline-offset: 2px;
  }
  /* Plain CSS checkmark (two rotated border edges), not an SVG - the
     simplest reliable way to draw a checkmark on a custom
     (appearance:none) checkbox without a second asset to keep in sync.
     Centered via translate/rotate rather than hand-tuned left/top px
     (the earlier fixed-px version sat slightly high-left, reported
     live) - the -55% vertical nudge accounts for the checkmark's foot
     extending below its box center once rotated. */
  .checklist-item-row input[type="checkbox"]:checked::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 5px;
    height: 9px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: translate(-50%, -55%) rotate(45deg);
  }
  /* One-shot burst (Success Checkmark.svg, dropped in verbatim via
     CHECKLIST_CHECK_BURST_SVG below) centered on the checkbox - plays
     on check only (see _playChecklistCheckAnimation, called from
     toggleChecklistItem), never on uncheck or on initial render of an
     already-checked item, then removes itself. pointer-events:none so
     it can't eat the next click while it's still fading out. */
  .checklist-check-burst {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 46px;
    height: 46px;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 2;
  }
  .checklist-item-label {
    flex: 1;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    color: #3a352c;
    transition: color 0.15s ease-in-out;
  }
  .checklist-item-row.checklist-item-checked .checklist-item-label {
    color: #bbb6ab;
    text-decoration: line-through;
  }
  .checklist-item-remove-btn {
    background: none;
    border: none;
    color: #bbb6ab;
    cursor: pointer;
    padding: var(--space-2);
    display: flex;
    /* Own stacking context, above the row's hover tint and the burst
       overlay - stays a precise, unambiguous tap target ("except the
       area of the X", per direct feedback) rather than visually
       blending into either. */
    position: relative;
    z-index: 1;
  }
  .checklist-add-row {
    display: flex;
    gap: var(--space-8);
  }
  /* Matches .checklist-item-row's own height (50px, driven by its
     18px remove-icon + 10px padding + border) - a plain 10px/10px match
     on padding alone still landed 6px short since this row has no icon
     of its own to stretch it, so the padding here is bumped up that
     6px to compensate and land on the same 50px. */
  .checklist-add-row .checklist-add-input {
    margin-bottom: 0;
    flex: 1;
    min-width: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 12px 12px;
    font-size: var(--text-14);
  }
  .checklist-add-btn {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 700;
    color: #fff;
    background: #675f55;
    border: none;
    border-radius: var(--radius-lg);
    padding: 12px 18px;
    cursor: pointer;
  }
  .checklist-add-btn:disabled {
    background: var(--color-border);
    color: #a39c8f;
    cursor: default;
  }
  /* Shown in place of the items list when there's no Supabase session
     to attach checklist_items rows to (see _loadChecklistForScope) -
     the Overview surface is the one place this is reachable, since a
     signed-out visitor can still view a trip's Overview. */
  .checklist-signin-prompt {
    font-size: var(--text-14);
    color: var(--color-muted);
    text-align: center;
    padding: 14px 10px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-10);
  }
  .account-signout-btn {
    display: block;
    width: 100%;
    background: none;
    border: none;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 600;
    color: #b5493a;
    text-align: center;
    cursor: pointer;
    padding: var(--space-10);
    margin-top: 24px;
  }
  /* Expenses tab (see EXPENSES_PANEL_TEMPLATE/renderExpensesPanel) -
     day-grouped list, same row/sticky-CTA visual language as the Ideas
     tab's .idea-row/.idea-sticky-add-btn rather than a second, distinct
     design system for a second "browse a list, tap to act" surface. */
  .expenses-panel {
    padding-top: 30px;
    /* No more bottom clearance needed now that .expense-sticky-add-btn
       is a plain pill above the list, not a fixed FAB that used to
       float over the last row's own bottom edge. */
    padding-bottom: 24px;
  }
  /* Right-aligned pill sitting above the Total Spent card (not inside
     it - a design revision from an earlier attempt that nested it into
     the card's header row alongside Export CSV, which read as too
     cramped/overlapping once both buttons and the total shared one
     card). Mirrors .idea-add-row's same flex-end wrapper. */
  .expense-add-row {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
  }
  .expense-header-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-12);
    min-height: 75px;
    box-sizing: border-box;
    background: #fff;
    border-radius: var(--radius-3xl);
    box-shadow: var(--shadow-md);
    padding: var(--space-20);
    margin-bottom: 20px;
  }
  .expense-total-label {
    font-size: var(--text-11);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-muted);
  }
  .expense-total-amount {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-24);
    font-weight: 700;
    color: var(--color-ink);
    margin-top: 2px;
  }
  .expense-total-amount-line + .expense-total-amount-line {
    margin-top: 2px;
  }
  .expense-currency-symbol {
    color: var(--color-accent);
  }
  /* Right-aligned row below the expense list - Export CSV only makes
     sense once there's something to export, so it reads more like a
     closing action for the list above it than a header control (same
     reasoning .itinerary-calendar-export-row's own comment gives for
     its equivalent placement). */
  .expense-export-row {
    display: flex;
    justify-content: flex-end;
    margin-top: 16px;
  }
  .expense-export-btn {
    flex-shrink: 0;
    background: none;
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-pill);
    padding: 8px 16px;
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-accent);
    cursor: pointer;
  }
  /* Sits below the sticky date-pills/journey-toggle stack (not inside
     it - that row is a fixed centered pair per the Itinerary Figma
     file, no room budgeted for a third element) so it scrolls with the
     day content instead of competing for sticky-header space. */
  .itinerary-calendar-export-row {
    display: flex;
    justify-content: flex-end;
    padding: 12px 16px 0;
  }
  .itinerary-calendar-export-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    background: none;
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-pill);
    padding: 8px 16px;
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-accent);
    cursor: pointer;
  }
  .itinerary-calendar-export-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .expense-day-section + .expense-day-section {
    margin-top: 20px;
  }
  .expense-day-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-body);
    margin-bottom: 8px;
  }
  .expense-day-subtotal {
    font-weight: 700;
    color: var(--color-ink);
    text-transform: none;
    letter-spacing: normal;
  }
  .expense-row {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    background: #fff;
    border-radius: var(--radius-2xl);
    box-shadow: var(--shadow-sm);
    padding: 12px 14px;
    cursor: pointer;
  }
  .expense-row + .expense-row {
    margin-top: 8px;
  }
  .expense-row-icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-circle);
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f1eae5;
    color: var(--color-body);
  }
  .expense-row-icon .material-symbols-outlined {
    font-size: var(--text-20);
  }
  .expense-row-content {
    flex: 1;
    min-width: 0;
  }
  .expense-row-vendor {
    font-size: var(--text-15);
    font-weight: 700;
    color: var(--color-ink);
  }
  .expense-row-category {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 1px;
  }
  .expense-row-amount {
    flex-shrink: 0;
    font-size: var(--text-15);
    font-weight: 700;
    color: var(--color-ink);
    white-space: nowrap;
  }
  .expense-row-delete {
    flex-shrink: 0;
    background: none;
    border: none;
    padding: var(--space-4);
    color: #b4b2a9;
    cursor: pointer;
    display: flex;
  }
  .expense-row-delete .material-symbols-outlined {
    font-size: var(--text-18);
  }
  /* Reusable empty-state component (design-backlog 2026-08-08). One
     shared pattern - icon disc + headline + body + optional CTA - so a
     genuinely-empty tab reads as intentional and actionable, not like a
     stray italic one-liner. A lighter sibling of the Ideas tab's own
     animated card-stack empty state (buildIdeaEmptyState): same visual
     language (Baloo 2 headline, muted body, accent pill CTA) without
     the bespoke illustration/animation, so any tab can adopt it.
     Filtered "no results in this subset" messages (e.g. .idea-list-
     empty) deliberately stay simple one-liners - they're not tab-empty. */
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 44px 20px 28px;
  }
  .empty-state-icon {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-circle);
    background: #f4efe8;
    margin-bottom: 18px;
  }
  .empty-state-icon .material-symbols-outlined {
    font-size: var(--text-32);
    color: var(--color-muted);
  }
  .empty-state-headline {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-20);
    font-weight: 800;
    color: var(--color-ink);
  }
  .empty-state-body {
    font-size: var(--text-14);
    line-height: 1.5;
    font-weight: 500;
    color: var(--color-muted);
    max-width: 260px;
    margin-top: 6px;
  }
  .empty-state-cta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    margin-top: 18px;
    background: var(--color-accent);
    color: #fff;
    font-family: inherit;
    font-size: var(--text-14);
    font-weight: 700;
    border: none;
    border-radius: var(--radius-pill);
    padding: var(--space-10) var(--space-20);
    cursor: pointer;
    transition: transform 120ms ease-out;
  }
  .empty-state-cta:active {
    transform: scale(0.97);
    filter: brightness(0.88);
  }
  .empty-state-cta .material-symbols-outlined {
    font-size: var(--text-18);
  }
  /* Compact variant for empty states that live *inside* an Overview card
     section (People, Reminders) rather than filling a whole tab like
     Expenses. Smaller icon + tighter padding so it reads as a calm
     placeholder within the card, not a full-screen empty view. */
  .empty-state.empty-state-compact {
    padding: 20px 16px;
  }
  .empty-state-compact .empty-state-icon {
    width: 44px;
    height: 44px;
    margin-bottom: 12px;
  }
  .empty-state-compact .empty-state-icon .material-symbols-outlined {
    font-size: var(--text-24);
  }
  .empty-state-compact .empty-state-headline {
    font-size: var(--text-16);
  }
  .empty-state-compact .empty-state-body {
    font-size: var(--text-13);
  }
  /* Live-trip-only (see _expenses_panel_html's supabase_trip_id_attr) -
     hidden entirely for every non-live trip's Expenses tab. */
  .expense-signin-prompt {
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-xl);
    padding: var(--space-16);
    margin-bottom: 12px;
  }
  .expense-signin-title {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    margin-bottom: 12px;
  }
  .expense-signin-email {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: 8px;
  }
  .expense-signin-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 8px;
  }
  /* Live-trip-only (see _ideas_panel_html's supabase_trip_id_attr) - same
     visual treatment as .expense-signin-* above, mirrored for Ideas. */
  .idea-signin-prompt {
    background: #fff;
    border: 1px solid #e8e4dc;
    border-radius: var(--radius-xl);
    padding: var(--space-16);
    margin-bottom: 12px;
  }
  .idea-signin-title {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    margin-bottom: 12px;
  }
  /* Lock glyph making the in-tab sign-in prompts read as a real gate,
     not an aside (design-backlog 2026-08-08). Shared by all four
     prompts' titles (idea/expense/people/new-trip). */
  .signin-prompt-icon {
    font-size: var(--text-18);
    color: var(--color-accent);
  }
  .idea-signin-email {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: 8px;
  }
  .idea-signin-status {
    font-size: var(--text-13);
    color: var(--color-muted);
    margin-top: 8px;
  }
  .expense-settle-up-box {
    background: #f7f2ea;
    border-radius: var(--radius-xl);
    padding: 14px 16px;
    margin-bottom: 12px;
  }
  .expense-settle-up-line {
    font-size: var(--text-14);
    font-weight: 600;
    color: #3a352c;
    margin-bottom: 6px;
  }
  .expense-settle-up-btn {
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 700;
    color: #4a7c59;
    background: #fff;
    border: 1px solid #4a7c59;
    border-radius: var(--radius-pill);
    padding: 8px 14px;
    cursor: pointer;
    margin-top: 4px;
  }
  /* 2026-08-04: moved from a fixed bottom-right FAB to a plain pill
     above the list, matching Ideas' own "+ Add to list" move (see
     .idea-sticky-add-btn's own comment) - per direct feedback, for the
     same reasons: easier to find/reach than a floating button that
     could sit behind the on-screen keyboard or a card overlay. Moved
     again the same day into .expense-header-row's right side, then
     once more into its own .expense-add-row above the Total Spent
     card (design mockup showed the button floating above the card,
     not sharing its row with the total) - margin stays 0 since
     .expense-add-row's own `margin-bottom` supplies the spacing below
     it. Still the same name (expense-sticky-add-btn) since nothing
     else about it changed - only its position in
     EXPENSES_PANEL_TEMPLATE and this rule. */
  .expense-sticky-add-btn {
    box-shadow: 0px 6px 10px rgba(240,81,46,0.25);
  }
  /* ---- Add Expense form (see EXPENSE_FORM_TEMPLATE) - every value
     below pulled directly from the Figma "Add Expense" frame via Dev
     Mode MCP (get_design_context/get_variable_defs), not approximated
     from existing CSS elsewhere on this page. ---- */
  .expense-form {
    display: flex;
    flex-direction: column;
    /* Figma's own frame has no card wrapper around these fields - they
       float directly on its light gradient page background there. This
       form is shown as an overlay on the dark scrim instead (see
       openAddExpenseOverlay, consistent with every other add/detail
       flow this session), so it needs an opaque background of its own
       or the gaps between individual white fields would show the dark
       scrim through in patches - the app's own cream body tone reads
       closer to Figma's real background than plain white would. */
    background: #fbf3e7;
    border-radius: var(--radius-4xl);
    box-shadow: var(--shadow-lg);
  }
  .expense-form-fields {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    padding: 20px 20px 8px;
  }
  .expense-form-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-24);
    color: var(--color-ink);
  }
  .expense-upload-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    width: 100%;
    box-sizing: border-box;
    background: rgba(255,255,255,0.7);
    border: 1px dashed #564c43;
    border-radius: var(--radius-3xl);
    padding: 14.5px 17.5px;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-13);
    color: var(--color-muted);
    cursor: pointer;
  }
  .expense-upload-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .expense-upload-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  .expense-upload-status {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    font-family: 'Baloo 2', sans-serif;
    font-weight: 600;
    font-size: var(--text-13);
    color: #5c8a6e;
    margin-top: -8px;
  }
  .expense-upload-status-visible {
    display: flex;
  }
  /* Screenshot-based idea capture (see IDEAS_PANEL_TEMPLATE) - same
     visual treatment as .expense-upload-*, mirrored for Ideas. */
  .idea-upload-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    width: 100%;
    box-sizing: border-box;
    background: rgba(255,255,255,0.7);
    border: 1px dashed #564c43;
    border-radius: var(--radius-pill);
    padding: 14.5px 17.5px;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-13);
    color: var(--color-muted);
    cursor: pointer;
  }
  .idea-upload-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .idea-upload-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  .idea-upload-status {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    font-family: 'Baloo 2', sans-serif;
    font-weight: 600;
    font-size: var(--text-13);
    color: #5c8a6e;
    margin-top: -8px;
  }
  .idea-upload-status-visible {
    display: flex;
  }
  .expense-form-label {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-12);
    letter-spacing: 0.345px;
    text-transform: uppercase;
    color: var(--color-ink);
    margin-bottom: -8px;
  }
  /* Split-with picker (see _populateExpenseSplitPicker) - live-trip-
     only. Add defaults to every current member checked, matching the
     trigger's own default when split_with is left null; Edit reflects
     the expense's real current split instead. */
  .expense-split-with {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
  }
  .expense-split-with-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
  }
  .expense-split-with-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-family: inherit;
    font-size: var(--text-14);
    color: var(--color-ink);
  }
  /* A settled share is locked (see update_expense_splits, which never
     touches a settled row) - dimmed to read as "can't change this",
     not just an oddly-disabled checkbox. */
  .expense-split-with-row-settled {
    color: var(--color-muted);
  }
  .expense-amount-input-wrap {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    background: #fff;
    box-shadow: var(--shadow-md);
    border-radius: var(--radius-md);
    padding: 14px 16px;
    transition: box-shadow 0.2s ease-out;
  }
  /* The visible "field" here is this wrap (padded pill around a $ icon
     + a borderless input) - the global input:focus glow (see
     SHARED_APP_STYLE's field active state rule) would otherwise land
     on the bare input's own small unpadded box instead of the pill
     that actually reads as the field, so it's redirected via
     :focus-within onto the wrap and cancelled on the input itself. */
  .expense-amount-input-wrap:focus-within {
    box-shadow: 0 0 11px rgba(240,81,46,0.66);
  }
  .expense-amount-input:focus {
    box-shadow: none;
  }
  .expense-amount-currency-input {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-15);
    color: var(--color-accent);
    background: none;
    border: none;
    outline: none;
    cursor: pointer;
    padding: 0;
  }
  .expense-amount-input {
    flex: 1;
    border: none;
    outline: none;
    background: none;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-18);
    color: var(--color-ink);
    padding: 0;
  }
  .expense-amount-input::placeholder {
    color: #757575;
  }
  /* flex-wrap, not a single row that shrinks to fit - a trip longer
     than 4 days (plus the "Add Date" pill always at the end) wraps to
     more rows at a fixed ~4-per-row width instead of every pill
     getting progressively narrower. */
  .expense-day-pills {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
  }
  .expense-day-pill {
    flex: 0 0 calc(25% - 3px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: 40px;
    box-sizing: border-box;
    /* Same translucent tan as .date-pill's own rounded-rect "date
       button" tier (rgba(216,199,184,0.37), see that rule's own
       comment) - unified 2026-08-07 (design-audit Finding 2/3): the
       expense pills were the one pair still on a white+shadow fill
       instead of this tier's flat tan. Deliberately NOT touching the
       separate full-pill/999px "choice chip" tier (.idea-city-toggle-
       btn, .itinerary-toggle-btn, .mode-btn) - those stay their own
       tier per the same audit, not folded into this one. */
    background: rgba(216,199,184,0.37);
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    font-family: 'Baloo 2', sans-serif;
  }
  .expense-day-pill-weekday {
    font-weight: 700;
    font-size: var(--text-10);
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: #828282;
    opacity: 0.8;
  }
  .expense-day-pill-date {
    font-weight: 700;
    font-size: var(--text-12);
    color: var(--color-body);
  }
  .expense-day-pill-selected {
    background: var(--color-body);
  }
  .expense-day-pill-selected .expense-day-pill-weekday,
  .expense-day-pill-selected .expense-day-pill-date {
    color: #fff;
  }
  /* Dashed "add a date outside the trip range" affordance (per the
     Figma update that added this) - same dashed-border visual language
     as .expense-upload-btn, always sorts last in the row regardless of
     how many custom dates get added (see handleAddDateSelected). */
  .expense-day-pill-add {
    position: relative;
    border: 1px dashed #828282;
    color: #828282;
    font-weight: 700;
    font-size: var(--text-12);
  }
  /* The real <input type="date"> sits directly on top of the pill,
     fully transparent, rather than being hidden elsewhere and opened
     via a proxied .click() from the pill's own onclick - real iOS
     Safari doesn't reliably open a native date picker from a
     programmatic/synthetic click, only from a genuinely trusted tap on
     the input itself. Covering the whole pill means the visible tap
     the person makes IS the tap on the real control, so there's no
     synthetic-click step to be blocked at all. */
  .expense-add-date-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    border: none;
    opacity: 0;
    cursor: pointer;
  }
  .expense-category-pills {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
  }
  .expense-category-pill {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 40px;
    box-sizing: border-box;
    /* Same rounded-rect "date button" tier as .date-pill/.expense-day-
       pill (rgba(216,199,184,0.37) flat tan, no shadow) - unified
       2026-08-07, see .expense-day-pill's own comment for the full
       reasoning. */
    background: rgba(216,199,184,0.37);
    border: none;
    border-radius: var(--radius-md);
    padding: 8px 14px;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 700;
    font-size: var(--text-12);
    color: #828282;
    cursor: pointer;
  }
  .expense-category-pill-selected {
    background: var(--color-body);
    color: #fff;
  }
  /* Only ever populated by _applyExpenseToForm when an existing
     expense's category has no matching pill (a pipeline-only category
     like Airfare/Meals/Registration/Other) - explains why Save stays
     disabled instead of leaving the person to guess. :empty keeps it
     invisible/takes no space the rest of the time. */
  .expense-category-hint:empty {
    display: none;
  }
  .expense-category-hint {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-11);
    font-weight: 600;
    color: var(--color-accent);
    margin: -4px 0 8px 0;
  }
  .expense-description-input {
    width: 100%;
    box-sizing: border-box;
    min-height: 77px;
    background: #fff;
    box-shadow: var(--shadow-md);
    border: none;
    border-radius: var(--radius-md);
    padding: 14px 16px;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 400;
    font-size: var(--text-14);
    color: var(--color-ink);
    resize: none;
  }
  .expense-description-input::placeholder {
    color: #757575;
  }
  /* Plain in-flow footer, not sticky/fixed - this used to be
     position:sticky so the CTA stayed reachable while the rest of the
     form scrolled underneath it, but .card-overlay-panel (its
     ancestor) has its own permanent `transform: scale()` for the
     open/close animation (see .card-overlay-panel), and a transform on
     an ancestor establishes a new containing block that breaks
     position:sticky/fixed resolution against the real scrolling
     ancestor - this rendered fine in desktop Chrome (more forgiving
     here) but broke on real iOS Safari, where the button floated
     mid-form instead of pinning to the bottom (see the "overlay is cut
     off" report). Simplest fix: don't fight the transform, just let
     the button flow at the end of the form like every other field. */
  .expense-submit-footer {
    border-radius: 0 0 20px 20px;
    padding: 8px 20px 20px;
  }
  .expense-submit-btn {
    box-sizing: border-box;
    box-shadow: 0px 10px 13px rgba(240,81,46,0.35);
  }
  .expense-submit-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    box-shadow: none;
  }
  /* Figma's Time Zone / currency treatment: small tan squares with
     stacked text (e.g. "1h" / "BEHIND" / gap / "2:37" / "PM") - not the
     translucent icon+single-line pill this widget used before it was
     redirected to only ever render on the Home Screen's trip card. */
  /* Timezone/currency box(es) and the weather day columns sit side by
     side in one row (see the .home-widgets-row wrapper), not stacked -
     .context-widget itself only needs to lay out multiple boxes
     (timezone + currency) next to each other when both are present. */
  .home-widgets-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-20);
    margin: 32px 0 0;
  }
  .context-widget {
    display: flex;
    gap: var(--space-10);
    flex-shrink: 0;
  }
  .weather-widget {
    flex: 1;
    min-width: 0;
    /* -30px was overlapping .context-box outright (the 20px row gap
       minus 30px is negative) - this narrower box (min-width dropped
       90px->40px across the last two rounds) already pulls this
       widget's default position well left of where it started, so a
       smaller additional pull here is enough without colliding. */
    margin-left: -10px;
  }
  .context-box {
    display: flex;
    flex-direction: column;
    /* Centers .context-box-inner as one block within the box's width -
       not each line individually (that would lose the shared left
       edge between e.g. "1h" and "2:37", different widths, reading as
       ragged instead of a clean left-aligned block). See
       .context-box-inner for the actual left-aligned text. */
    align-items: center;
    background: rgba(246,238,228,0.44);
    border-radius: 18px;
    padding: 10px 14px 10px 24px;
    min-width: 40px;
    color: var(--color-body);
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 700;
  }
  .context-box-line-lg { font-size: var(--text-20); line-height: 1.3; }
  .context-box-line-sm { font-size: var(--text-10); text-transform: uppercase; line-height: 1.3; }
  .context-box-gap { height: 10px; }
  /* Figma's weather section: plain day columns side by side (day
     label / icon / one combined high-low line), separated by a thin
     divider - no card background, no "Weather Forecast" header, no
     separate condition line of its own. */
  .weather-widget-days {
    display: flex;
  }
  .weather-widget-day {
    flex: 1;
    text-align: center;
    padding: 0 10px;
  }
  .weather-widget-day + .weather-widget-day {
    border-left: 1px solid #e4ddd2;
  }
  /* 2 days on mobile (3 side by side reads cramped in this narrow
     widget), 3 on desktop where there's room - render.py always emits
     up to 3 day-cells (see _weather_widget_html's own cap), this just
     hides the 3rd below the same 900px breakpoint the desktop 2-col
     trip layout already uses. */
  .weather-widget-day:nth-child(3) {
    display: none;
  }
  @media (min-width: 900px) {
    .weather-widget-day:nth-child(3) {
      display: block;
    }
  }
  .weather-widget-day-label {
    font-size: var(--text-11);
    font-weight: 700;
    margin-bottom: 8px;
    white-space: nowrap;
  }
  .weather-widget-day-weekday { color: var(--color-muted); }
  .weather-widget-day-num { color: var(--color-ink); }
  .weather-widget-icon {
    font-size: var(--text-20);
    line-height: 1;
  }
  .weather-icon {
    color: #3b6ea5;
    vertical-align: middle;
  }
  .weather-widget-day-temps {
    font-size: var(--text-18);
    font-weight: 700;
    color: var(--color-body);
    margin-top: 6px;
    white-space: nowrap;
  }
  .weather-widget-unavailable {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    font-size: var(--text-13);
    color: #78766f;
  }
  /* "See full trip forecast" row (Figma "Weather" frame, node 227:804)
     - opens the .weather-sheet bottom sheet below via
     openWeatherForecastOverlay. A <button> (not a plain div, unlike
     several other tappable rows in this file) since it's genuinely
     just a text link with no other content to anchor a click handler
     to - reset to look like plain centered text, not a default
     button. */
  .weather-forecast-link {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    width: 100%;
    height: 20px;
    margin-top: 4px;
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-13);
    color: var(--color-accent);
  }
  .weather-forecast-link-icon {
    font-size: var(--text-13);
    color: var(--color-accent);
  }
  /* "4-Day Forecast" bottom sheet (Figma "Weather" frame, node
     227:811) - reuses the shared .card-overlay/.card-overlay-panel
     scrim+show/close JS (see openWeatherForecastOverlay/
     closeCardOverlay) with the .card-overlay-sheet-mode visual variant
     (see that rule) rather than the default centered-card presentation,
     since this is a true bottom sheet (flush to the screen's bottom
     edge, rounded top corners only, shadow pointing up) - not another
     instance of the existing detail-card overlay. */
  .weather-sheet {
    background: #fff;
    box-shadow: 0 -14px 15px rgba(0,0,0,0.18);
    border-radius: 26px 26px 0 0;
    padding: 18px 20px 26px;
  }
  .weather-sheet-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 12px;
  }
  /* Explains any placeholder "Not available yet" rows below (see
     _weather_sheet_row_html) - lives here, under the header, rather
     than on the collapsed 2-day widget, since this sheet is where an
     unavailable day actually shows up as a row a viewer would
     otherwise wonder about. */
  .weather-sheet-partial {
    font-size: var(--text-10);
    color: #9a9890;
    padding-bottom: 12px;
    text-align: center;
  }
  /* Same static-snapshot reasoning as .travel-time-freshness - see
     _checked_at_label. */
  .weather-sheet-freshness {
    font-size: var(--text-10);
    color: #9a9890;
    text-align: center;
    padding-top: 8px;
  }
  .weather-sheet-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-16);
    color: #241a12;
  }
  .weather-sheet-close {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--radius-circle);
    border: none;
    background: var(--color-close-bg);
    color: var(--color-body);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
  }
  .weather-sheet-close .material-symbols-outlined {
    font-size: var(--text-14);
  }
  .weather-sheet-row {
    display: flex;
    align-items: center;
    gap: var(--space-28);
    padding: 8px 0 10px;
    border-bottom: 1px solid rgba(36,26,18,0.07);
  }
  .weather-sheet-daydate {
    width: 42px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    font-family: 'Baloo 2', sans-serif;
    text-align: center;
  }
  .weather-sheet-weekday {
    font-weight: 600;
    font-size: var(--text-11);
    color: #828282;
    line-height: 1.4;
  }
  .weather-sheet-date {
    font-weight: 700;
    font-size: var(--text-20);
    color: var(--color-body);
    line-height: 1.4;
  }
  .weather-sheet-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-20);
    line-height: 1;
  }
  .weather-sheet-condition {
    flex: 1;
    min-width: 0;
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 400;
    font-size: var(--text-13);
    color: var(--color-body);
  }
  .weather-sheet-temps {
    flex-shrink: 0;
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 800;
    font-size: var(--text-14);
    color: var(--color-body);
    white-space: nowrap;
  }
  /* "Not available yet" placeholder row (see _weather_sheet_row_html) -
     italic + faded (#c2c0bf, vs. the regular #6b5e52 above) rather than
     a placeholder icon, per the Figma frame's own unavailable-day row. */
  .weather-sheet-condition-unavailable {
    font-style: italic;
    color: #c2c0bf;
  }
  .weather-sheet-temps-unavailable {
    color: #c2c0bf;
  }
  /* No longer sticky - .date-pills-row above is the one persistent
     day indicator now (see that rule); this is just an in-flow marker
     matching the Itinerary Figma file's small "Day and Date" pill. */
  .day-header {
    padding: 28px 0 10px;
    cursor: pointer;
  }
  /* .day-header-first (set in Python - see DAY_HEADER_TEMPLATE call
     sites), not :first-of-type - the day-header immediately follows
     .date-pills-row as a sibling <div> (see itinerary_html), so it's
     never actually its parent's first <div> of that type;
     :first-of-type silently never matched any real day-header,
     permanently landing every trip's first day at the full 28px top
     padding instead of this reduced one - a real ~20px stray gap on
     every trip, not a scroll-state or viewport bug. */
  .day-header-first { padding-top: 8px; }
  /* Plain text, no pill background - the Itinerary Figma file's "Wed
     May 27" style applied consistently to every inline day marker, not
     just the first. Always visible now - the sticky duplicate label
     that used to get hidden alongside whichever day matched it was
     removed per direct feedback. */
  .day-header-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
  }
  .day-header-weekday {
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    opacity: 0.75;
  }
  .day-header-date {
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
  }
  .card {
    background: white;
    border: 1px solid #e0ded5;
    border-radius: var(--radius-2xl);
    padding: var(--space-32);
    margin-bottom: 14px;
    box-shadow: var(--shadow-sm);
  }
  .card, .travel-time-card, .free-block-card, .idea-card, .journey-card-summary {
    transition: box-shadow 0.15s ease;
  }
  /* Only real event cards with a resolvable address (see data-address
     on their .day-item wrapper) actually do anything when clicked -
     showing the same hover affordance on e.g. a flight or train card
     (no single address to show on the map) implied a click action
     that doesn't exist. .journey-card-summary always does something
     (its Show Route link), so it's unconditional like .compact-row
     below rather than gated on [data-address]. */
  .day-item[data-address] .card:hover, .travel-time-card:hover, .free-block-card:hover, .idea-card:hover, .journey-card-summary:hover {
    box-shadow: var(--shadow-md);
  }
  /* Unlike the rule above, every .compact-row opens the tap-to-expand
     overlay regardless of whether it has a resolvable address - not
     conditioned on [data-address]. */
  .compact-row:hover {
    box-shadow: var(--shadow-lg);
  }
  .card-hero {
    position: relative;
    background-size: cover;
    background-position: center;
    border-radius: 14px 14px 0 0;
    margin: -32px -32px 16px;
    padding: 20px 32px 16px;
    color: white;
  }
  .card-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0.2), rgba(0,0,0,0.6));
    border-radius: 14px 14px 0 0;
  }
  .card-hero > * {
    position: relative;
    z-index: 1;
  }
  .card-hero .card-type {
    color: white !important;
    opacity: 0.9;
  }
  .card-hero .card-title {
    color: white;
    text-shadow: 0 1px 4px rgba(0,0,0,0.6);
  }
  .card-hero-attribution {
    font-size: var(--text-10);
    color: #b4b2a9;
    text-align: right;
    margin: -12px 0 12px;
  }
  .card-header {
    display: flex;
    justify-content: space-between;
    font-size: var(--text-12);
    margin-bottom: 6px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .card-time { color: #888780; font-weight: 600; }
  .card-title-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-10);
    margin-bottom: 10px;
  }
  .card-title {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-18);
    font-weight: 700;
  }
  .card-icon {
    flex-shrink: 0;
    font-size: var(--text-48) !important;
  }
  .card-title-rule {
    border: none;
    border-top: 1px solid #2c2c2a;
    margin: 0 0 10px;
  }
  .card-subtitle {
    font-size: var(--text-13);
    color: #78766f;
    margin-bottom: 8px;
  }
  .alert {
    background: #fdf6ea;
    border: 1px solid #f0d9a8;
    color: #6b5a34;
    border-radius: var(--radius-md);
    padding: 10px 12px;
    font-size: var(--text-13);
    line-height: 1.6;
    margin: 16px 0 8px;
  }
  .details {
    font-size: var(--text-13);
    color: #5f5e5a;
    line-height: 1.6;
    margin-top: 4px;
  }
  .history {
    font-size: var(--text-11);
    color: #b4b2a9;
    margin-top: 8px;
    border-top: 1px solid #eee;
    padding-top: 6px;
  }
  /* Ticket QR - see event["ticket_qrs"] and _ticket_qr_html. Sized to
     stay comfortably scannable on a phone screen without dominating
     the rest of the card's details. A booking with more than one real
     code (e.g. a festival PDF with a separate ticket per person, plus
     a parking pass and shower passes) renders each stacked in its own
     labeled item - see card-ticket-qr-group/-item/-label. A single-
     code booking omits the label entirely, same as before. */
  .card-ticket-qr-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-18);
    margin-top: 14px;
  }
  .card-ticket-qr-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-6);
  }
  .card-ticket-qr-label {
    font-size: var(--text-13);
    font-weight: 600;
    color: #888780;
    text-align: center;
  }
  .card-ticket-qr-item img {
    width: 160px;
    height: 160px;
    border: 1px solid #e0ded5;
    border-radius: var(--radius-md);
    padding: var(--space-8);
  }
  .card-invite-row {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    margin-top: 10px;
  }
  /* ---- Hotel/Restaurant/Event/Tour Activity card family (v2) ----
     Shares .card for the outer shadow/radius/hover behavior (see
     .day-item[data-address] .card:hover above) but overrides padding
     to 0 so a hero image (hotel only, for now) can bleed to the
     card's own edges without the negative-margin trick .card-hero
     uses elsewhere in the file. */
  .card.card-v2 {
    font-family: 'Baloo 2', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    border: none;
    border-radius: var(--radius-5xl);
    padding: 0;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
  }
  .card-v2-hero {
    position: relative;
    height: 150px;
    background-size: cover;
    background-position: center;
  }
  .card-v2-hero-tall {
    height: 190px;
  }
  .card-v2-body {
    padding: 20px 22px 24px;
  }
  .card-tag {
    display: inline-block;
    color: #ffffff;
    font-size: var(--text-12);
    font-weight: 700;
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    white-space: nowrap;
  }
  .card-tag-hero {
    position: absolute;
    top: 14px;
    left: 16px;
    z-index: 1;
  }
  .card-tag-standalone {
    margin-bottom: 10px;
  }
  .card-v2-title {
    font-size: var(--text-18);
    font-weight: 700;
    color: var(--color-ink);
    line-height: 1.3;
  }
  .card-v2-accent-line {
    font-size: var(--text-14);
    font-weight: 700;
    margin-top: 2px;
  }
  .card-accent-link {
    color: var(--color-accent);
    text-decoration: underline;
  }
  .card-checkin-line {
    display: flex;
    gap: var(--space-22);
    padding-top: 14px;
    font-size: var(--text-14);
    font-weight: 600;
    color: #4a3f2e;
  }
  .card-room-line {
    font-size: var(--text-14);
    color: #4a3f2e;
    padding: 8px 0 14px;
    line-height: 1.5;
  }
  .card-cuisine-line {
    font-size: var(--text-14);
    color: var(--color-muted);
    margin-top: 4px;
  }
  .card-cancellation-banner {
    background: #e3efdf;
    color: #5a9a5f;
    font-size: var(--text-13);
    font-weight: 700;
    padding: 12px 18px;
    border-radius: var(--radius-pill);
    margin-top: 12px;
    line-height: 1.4;
  }
  .card-columns-row {
    display: flex;
    gap: var(--space-28);
    flex-wrap: wrap;
    padding-top: 14px;
  }
  .card-column {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }
  .card-column-label {
    font-size: var(--text-12);
    font-weight: 700;
    color: var(--color-muted);
    white-space: nowrap;
  }
  .card-column-value {
    font-size: var(--text-15);
    font-weight: 700;
    color: var(--color-ink);
  }
  .card-divided-list {
    width: 100%;
    margin-top: 8px;
  }
  .card-divided-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-12);
    padding: 12px 0 12px;
    border-top: 1px solid rgba(51,38,30,0.08);
  }
  .card-divided-label {
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-muted);
    flex: 0 0 92px;
  }
  .card-divided-value {
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-ink);
    text-align: left;
    flex: 1;
  }
  .card-divided-value.card-accent {
    color: var(--color-accent);
  }
  .card-divided-value.card-regular {
    font-weight: 400;
  }
  /* Deliberately not flexbox - a long address's own <a> text already
     wraps to 2+ lines by itself, and flex wraps whole items rather
     than reflowing text, which stranded the button alone on its own
     line below a wrapped address. inline-flex + vertical-align lets it
     ride along inline at the end of the wrapped text instead, like a
     trailing icon in a paragraph. */
  .copy-address-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    vertical-align: middle;
    margin-left: 4px;
    padding: 0;
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
  }
  .copy-address-btn .material-symbols-outlined {
    font-size: var(--text-16);
  }
  /* Compact-row-only variant (see _compact_row_subtitle) - icon leads
     the address text instead of trailing it, colored black rather than
     the muted default, per the Itinerary v3 Figma frame. */
  .copy-address-btn-leading {
    margin-left: 0;
    margin-right: 4px;
    color: #000000;
  }
  /* Own white card, not a child of .card-v2 - full_card_html (see
     _event_notes_html's call site) is a complete "<div class="card
     card-v2">...</div>" string per card type, so appending here lands
     this as a sibling of that div rather than nested inside it. Styled
     to match .card so the two read as one attached block instead of
     the notes section floating over the dimmed overlay background. */
  .card-notes-section {
    background: white;
    border: 1px solid #e0ded5;
    border-radius: var(--radius-2xl);
    padding: 20px 32px;
    box-shadow: var(--shadow-sm);
  }
  .card-notes-label {
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-muted);
    margin-bottom: 8px;
  }
  .card-notes-input {
    width: 100%;
    min-height: 60px;
    font-size: var(--text-14);
    font-family: inherit;
    color: var(--color-ink);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 10px 12px;
    resize: vertical;
    box-sizing: border-box;
  }
  /* Chat-style: other authors' notes on this card, no per-entry box -
     just "Author · timestamp" above the text, stacked with a thin
     divider - see _renderCardNoteThread. Empty (no other notes yet)
     collapses to nothing rather than leaving blank space. */
  .card-notes-thread {
    display: flex;
    flex-direction: column;
    gap: var(--space-10);
    margin-bottom: 12px;
  }
  .card-notes-thread:empty {
    display: none;
    margin-bottom: 0;
  }
  .card-notes-entry {
    padding-bottom: 10px;
    border-bottom: 1px solid #ede9de;
  }
  .card-notes-entry:last-child {
    border-bottom: none;
    padding-bottom: 0;
  }
  .card-notes-entry-meta {
    font-size: var(--text-12);
    color: var(--color-muted);
    margin-bottom: 2px;
  }
  .card-notes-entry-text {
    font-size: var(--text-14);
    color: var(--color-ink);
    white-space: pre-wrap;
    word-break: break-word;
  }
  .card-notes-compose {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
  }
  /* Empty (nothing typed yet, no status to report) collapses to nothing
     rather than reserving blank space under the textarea. */
  .card-notes-status {
    font-size: var(--text-12);
    color: var(--color-muted);
  }
  .card-notes-status:empty {
    display: none;
  }
  /* Hidden until _hydrateCardNotes confirms this card's trip actually
     has Supabase presence and the viewer is signed in - sharing has no
     meaning for a purely local/anonymous note. */
  .card-notes-visibility {
    display: none;
  }
  .card-notes-visibility.card-notes-visibility-active {
    display: flex;
    justify-content: flex-end;
  }
  .card-notes-visibility-select {
    font-size: var(--text-13);
    color: var(--color-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 4px 8px;
    background: white;
  }
  .card-notes-share-picker {
    background: #fbf8f2;
    border: 1px solid #e0ded5;
    border-radius: var(--radius-lg);
    padding: var(--space-12);
  }
  .card-notes-share-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    font-size: var(--text-14);
    color: var(--color-ink);
    margin-bottom: 10px;
  }
  .card-notes-share-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
  }
  .card-notes-share-apply-btn {
    font-size: var(--text-13);
    font-weight: 600;
    color: white;
    background: var(--color-ink);
    border: none;
    border-radius: var(--radius-md);
    padding: 6px 14px;
    cursor: pointer;
  }
  /* ---- Compact row (default itinerary view - see _compact_row_html) ----
     No venue photo here by design - see _card_photo_header_html's hero
     image, which is reserved for the full tap-to-expand card only.
     The outer row is the flex container (main content + chevron) so
     align-items: center centers the chevron against the WHOLE card's
     height, not just one inner section of it. */
  /* Card spacing/radius/background per the Itinerary v3 Figma frames -
     white, not cream (every card in the frame is bg-white with a
     border, only the page behind them is the cream #fcf7f0). */
  .compact-row {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    background: #ffffff;
    border: 1px solid #ededed;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    margin-bottom: 6px;
    overflow: hidden;
    cursor: pointer;
  }
  /* Repeat trigger for a multi-day event's interior day (see
     _ongoing_compact_row_html) - reads as a continuation of something
     already introduced on an earlier day, not a new booking that just
     appeared. Tinted with lodging's own accent green (see
     COMPACT_ROW_TAG_COLORS["hotel"]) rather than a washed-out white,
     since a multi-day span reads like lodging even when the underlying
     event isn't literally type "hotel" (a multi-day bike rental, a
     festival pass, etc.) - direct request 2026-08-06 ("don't keep it
     white with the same style as the rest"). */
  .compact-row-ongoing {
    background: #eef4ee;
    box-shadow: none;
    border: 1px solid #cfe0cf;
    border-left: 4px solid #4e8452;
  }
  .compact-row-ongoing .compact-row-time {
    color: #4e8452;
  }
  .compact-row-ongoing .compact-row-title {
    color: #3a6740;
  }
  .compact-row-main {
    flex: 1;
    min-width: 0;
  }
  /* Badge now lives in the SAME grid as the time/title (grid-column: 2,
     grid-row: 1, below) instead of a separate flex sibling with its own
     fixed left padding - that older layout put the badge flush at the
     card's own left edge, misaligned with the title (which starts well
     to the right of the time column). Sharing the grid's column 2
     track pins the badge to the same left edge the title itself uses,
     per the Itinerary v3 Figma frame (badge ~x96 vs. title ~x88 in that
     frame - close enough to read as aligned). margin-bottom (not
     row-gap, which is shared by every row pair) restores the ~10px gap
     to the title specifically, independent of row-gap's own title-to-
     address spacing (see .compact-row-content) - the two are tuned
     together so badge-to-title stays 10px regardless of what row-gap
     is set to. */
  .compact-row-tag-row {
    grid-column: 2;
    grid-row: 1;
    line-height: 0;
    margin-bottom: 0px;
  }
  /* Minimal Cards frame's asymmetric pill: rounded on three corners,
     squared on the top-left - pulled as an exact border-radius value
     from Dev Mode (Tailwind's rounded-tr/-br/-bl-[100px], no
     rounded-tl class = 0 on that corner), not approximated. Sets its
     own line-height (see .compact-row-tag-row above) so its actual
     height comes from font-size + padding, not the wrapper's
     collapsed line box. */
  .compact-row-tag {
    display: inline-block;
    line-height: 1.4;
    color: #ffffff;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-11);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    padding: 4px 12px;
    border-radius: 0 999px 999px 999px;
    white-space: nowrap;
  }
  /* Grid (not flex) so the time cell shares row 2 with the title no
     matter how many lines the title/address wrap to - see the Minimal
     Cards frame, where time is vertically level with the title line
     specifically, not centered against the whole text block. Row 1
     (padding-top: 0) is the badge - see .compact-row-tag-row's own
     comment on why it moved into this grid. column-gap widens at the
     desktop breakpoint (see the min-width: 900px override below),
     matching every other mobile/desktop split in this file rather than
     a fixed value or a fluid clamp() this file doesn't otherwise use.
     Column 1 is a fixed 56px (not auto) and left padding is 20px (not
     18px) - both changed 2026-08-06 to exactly match
     .ideas-card-content-assigned's own values (see that rule's own
     comment on the auto-sizing bug this same fix already solved
     there). A card with NO time at all (e.g. an untimed event like
     Wawaka Lake 5K, which renders no .compact-row-time span) still
     reserves the same 56px column 1 with a fixed track, so its own
     pill/title/address start at the exact same x as a timed card's -
     real bug found live via a vertical guide line held up against a
     no-time event card next to a timed idea card, direct request
     2026-08-06. */
  .compact-row-content {
    display: grid;
    grid-template-columns: 56px 1fr;
    /* 30px flat, no wider desktop breakpoint any more - direct request
       2026-08-06 (real reference screenshot: content sat too far right
       of the time column at desktop width, wanted brought in to a 30px
       gap same as mobile). */
    column-gap: 30px;
    row-gap: var(--space-10);
    align-items: baseline;
    padding: 0 18px 16px 20px;
  }
  .compact-row-title {
    grid-column: 2;
    grid-row: 2;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
    line-height: 1.3;
  }
  .compact-row-subtitle {
    grid-column: 2;
    grid-row: 3;
    font-size: var(--text-14);
    font-weight: 500;
    color: var(--color-muted);
    line-height: 1.15;
  }
  /* Hanging indent (see _compact_row_html) - padding-left pushes the
     whole block right by the leading copy icon's own width + gap
     (22px + 4px, see .copy-address-btn-leading), then text-indent
     pulls line 1 back by the same amount so the icon sits back at the
     column's own left edge; a wrapped second line has no icon to pull
     back for, so it naturally lands at the indented position, lined up
     with line 1's TEXT rather than its icon. */
  .compact-row-subtitle-address {
    padding-left: 26px;
    text-indent: -26px;
  }
  /* Address text is a real maps link now (see _compact_row_subtitle) -
     inherits the muted .compact-row-subtitle color rather than the
     browser's default blue/purple visited-link colors, underline kept
     as the only visual cue that it's tappable (matches .card-accent-link
     elsewhere, just without the orange - this row's icon is already the
     black-vs-orange departure from that convention, per the Itinerary
     v3 Figma frame). */
  .compact-row-address-link {
    color: inherit;
    text-decoration: underline;
  }
  .compact-row-time {
    grid-column: 1;
    grid-row: 2;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-ink);
    white-space: nowrap;
  }
  .compact-row-chevron {
    flex-shrink: 0;
    margin-right: 18px;
    font-size: var(--text-20) !important;
    color: var(--color-muted);
  }
  /* ---- Tap-to-expand overlay (see openCardOverlay/closeCardOverlay) ----
     Per the Itinerary Figma file's "cards" frame: a dark scrim with the
     card's own surface (white .card-v2 / colored .transport-card, each
     already carrying its own radius+shadow) floating directly on top -
     no separate tan "sheet" behind it. The overlay itself scrolls
     (rather than an inner max-height panel) so a tall card (e.g. a
     flight's full timeline) can extend past one screen instead of being
     clipped. */
  body.card-overlay-open {
    overflow: hidden;
  }
  /* .date-pills-row/.idea-category-tabs/.overview-nav-row are all
     position:sticky - on real iOS Safari, a sticky element that was
     actively "stuck" via scroll right before the overlay opened
     (exactly the normal flow: scroll down to find a card/tap Add to
     list) can stay promoted to an elevated compositing layer that
     ignores normal z-index stacking against a later-appended fixed
     sibling like .card-overlay-scrim, letting it visibly poke through
     the scrim regardless of the scrim's z-index - confirmed happening
     to .idea-category-tabs too (the trip's tab row/category chips
     showing through the "Add to list" overlay), not just Itinerary's
     two rows this rule originally covered. Hiding them outright while
     any card overlay is open sidesteps that stacking ambiguity
     entirely - nothing to bleed through if it isn't rendered. */
  body.card-overlay-open .date-pills-row,
  body.card-overlay-open .idea-category-tabs,
  body.card-overlay-open .overview-nav-row {
    visibility: hidden;
  }
  /* On iOS Safari the address bar/toolbar can show or hide *while the
     overlay is already open* (its own animation, not always tied
     cleanly to a visualViewport event we can catch in time), so a
     precisely viewport-sized dark layer can momentarily fall short and
     let the real page flash through un-dimmed at an edge. Split into
     two layers instead: .card-overlay-scrim is deliberately oversized
     (well past any possible viewport in any toolbar state) and pointer-
     events:none, so it always fully covers the visible screen no matter
     what the toolbar is doing - permanently fixes the "page shows
     through" symptom regardless of timing. .card-overlay itself stays
     precisely sized (see _syncCardOverlayViewport) since ITS size still
     matters for scroll math and the close button's position, but an
     imperfect moment there is now just a minor positioning wobble, not
     a visible hole in the scrim.
     Appended as a SIBLING of .card-overlay directly under body (see
     _ensureCardOverlay), not a child of it - .card-overlay has
     overflow-y:auto, and a scrolling ancestor clips position:fixed
     descendants to its OWN box regardless of how oversized the
     descendant's own top/left/right/bottom are, which silently
     defeated the whole oversizing trick when the scrim lived inside it.
     top/left/right/bottom below are just a starting-point fallback -
     an earlier version used -20vh/-20vw, but plain vh on iOS Safari
     doesn't reliably mean "large viewport" on every device, so once
     the toolbar fully hid (viewport grew past what -20vh assumed) the
     scrim fell short again at the bottom. _ensureCardOverlay overrides
     these with a margin computed from window.screen's actual hardware
     dimensions instead, which are provably >= any possible browser
     viewport in any toolbar state or orientation. */
  .card-overlay-scrim {
    position: fixed;
    top: -50vh;
    left: -50vw;
    right: -50vw;
    bottom: -50vh;
    background: rgba(0,0,0,0.63);
    pointer-events: none;
    opacity: 0;
    /* Unified overlay motion (2026-08-08): all overlay transitions -
       scrim fade, dialog scale-in, sheet slide-up, drawer slide-in -
       share 300ms + --ease-decel so every overlay feels like one
       system (was a mix of 0.25s ease and a one-off 320ms bezier). */
    transition: opacity 300ms var(--ease-decel);
    z-index: 499;
  }
  .card-overlay-scrim-visible {
    opacity: 1;
  }
  .card-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100vh;
    height: 100dvh;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 24px 16px calc(24px + env(safe-area-inset-bottom));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    opacity: 0;
    pointer-events: none;
    transition: opacity 300ms var(--ease-decel);
    z-index: 500;
  }
  .card-overlay-visible {
    opacity: 1;
    pointer-events: auto;
  }
  /* No auto vertical-centering margin here on purpose - `margin: auto`
     inside a scrollable flex container is a known Safari bug (the
     bottom auto-margin doesn't reliably collapse to 0 once content
     overflows), which was silently eating the last bit of scroll room
     and clipping a tall card's true bottom padding on real iPhones.
     Top-anchored + the overlay's own padding is less "perfectly
     centered" for short cards but scrolls reliably for tall ones. */
  .card-overlay-panel {
    max-width: 480px;
    width: 100%;
    position: relative;
    transform: scale(0.96);
    opacity: 0;
    transition: transform 300ms var(--ease-decel), opacity 300ms var(--ease-decel);
  }
  .card-overlay-visible .card-overlay-panel {
    transform: scale(1);
    opacity: 1;
  }
  /* Fixed to the overlay/viewport, not the scrolling card - a sibling
     of .card-overlay-panel rather than a descendant of it, so its own
     transform (scale, above) never becomes this button's containing
     block. Stays put and clickable no matter how far a tall card
     scrolls. Icon is the Figma "close button" asset (a white circle
     with the X cut out of it via the path's own winding) - no extra
     background div needed, the SVG paints its own disc. */
  .card-overlay-close {
    position: fixed;
    top: 20px;
    right: 20px;
    width: 35px;
    /* top/right above are just the fallback before JS runs -
       _syncCardOverlayViewport sets explicit top/left in step with the
       same window.visualViewport numbers .card-overlay itself uses,
       rather than trusting the browser's own native fixed-position
       "right" math to agree with that independently. */
    height: 35px;
    padding: 0;
    border: none;
    /* Unified close treatment (2026-08-08): tan disc + dark X, matching
       .nav-drawer-close / .weather-sheet-close (was a dark #3a3a3a
       circle + white X SVG). A drop shadow keeps it legible even when it
       floats over a photo hero - the one context the old dark circle was
       handling that a flat tan disc otherwise wouldn't. */
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-circle);
    background: var(--color-close-bg);
    color: var(--color-body);
    box-shadow: 0 2px 8px rgba(30,20,10,0.25);
    cursor: pointer;
    z-index: 2;
  }
  .card-overlay-close .material-symbols-outlined {
    font-size: var(--text-20);
  }
  /* ---- Generic confirm modal (see showConfirmModal) - branded
     replacement for window.confirm(), for any destructive/hard-to-
     reverse action (remove member, delete trip/expense, decline
     invite) that wants a real "are you sure" instead of the browser's
     own generic popup. Its own small overlay rather than reusing
     .card-overlay above - that system carries a lot of machinery (iOS
     toolbar viewport tracking, scrolling a tall card) a two-button
     confirmation never needs. z-index sits above .card-overlay's 500
     since a confirm can be triggered from inside an already-open card
     overlay (e.g. a future "delete" button on an expense's own edit
     overlay). */
  .confirm-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(51,38,30,0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-24);
    box-sizing: border-box;
    z-index: 700;
    opacity: 0;
    transition: opacity 0.15s ease;
  }
  .confirm-modal-backdrop-visible {
    opacity: 1;
  }
  .confirm-modal-panel {
    background: #fff;
    border-radius: var(--radius-4xl);
    padding: var(--space-24);
    max-width: 340px;
    width: 100%;
    box-sizing: border-box;
    box-shadow: var(--shadow-xl);
    transform: scale(0.96);
    transition: transform 0.15s ease;
  }
  .confirm-modal-backdrop-visible .confirm-modal-panel {
    transform: scale(1);
  }
  .confirm-modal-message {
    font-size: var(--text-15);
    line-height: 1.4;
    color: var(--color-ink);
    margin: 0 0 20px;
  }
  .confirm-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-10);
  }
  /* styling now from .btn-secondary .btn-secondary-sm (2026-08-08) */
  /* Same danger color .people-delete-trip-btn already uses - not the
     brand orange above, so a destructive confirm reads as visibly
     different from a routine one (e.g. confirming an invite). */
  .confirm-modal-confirm-btn-destructive {
    background: #c94a3d;
  }
  /* ===== Lodging ticket card (see _hotel_card_html + the Figma
     "Lodging Main/Who/Notes" frames). A deep-brown mat frames a cream
     ticket; a perforation notch (two mat-colored discs, clipped to half
     by the inner card's overflow, plus a dashed line) sits between the
     stay grid and the confirmation number. A DETAILS / WHO'S HERE /
     NOTES tab bar swaps the lower half (switchCardTab). The bespoke
     ticket surfaces (mat/cream/pill) are scoped vars here; all text,
     accent, radii and spacing use the global design tokens. ===== */
  .lodging-ticket {
    --lodging-mat: #000000;    /* solid black ticket frame (100% opacity) */
    --lodging-paper: #fbf8f3;  /* warm cream ticket surface */
    --lodging-hairline: rgba(51,38,30,0.12);
    background: var(--lodging-mat);
    padding: var(--space-20);
    /* Override .card's 1px #e0ded5 stroke so the dark mat runs true
       edge-to-edge instead of sitting inside a pale hairline border. */
    border: none;
    border-radius: var(--radius-5xl);
    box-shadow: var(--shadow-lg);
    font-family: 'Baloo 2', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .lodging-ticket-inner {
    position: relative;
    background: var(--lodging-paper);
    border-radius: var(--radius-4xl);
    overflow: hidden;
  }
  .lodging-ticket-head { padding: var(--space-20) var(--space-20) var(--space-16); }
  .lodging-ticket-top {
    display: flex;
    /* the shared close disc floats at the viewport corner
       (.card-overlay-close), so the pill sits alone here */
    margin-bottom: var(--space-12);
  }
  .lodging-pill {
    display: inline-block;
    /* Same green treatment as the Minimal Cards hotel pill
       (COMPACT_ROW_TAG_COLORS["hotel"] #4e8452 at 0.8, white text) so
       the expanded ticket's tag matches the list row it opened from. */
    background: rgba(78, 132, 82, 0.8);
    color: #ffffff;
    border-radius: var(--radius-pill);
    padding: var(--space-6) var(--space-12);
    font-size: var(--text-10);
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }
  .lodging-title {
    color: var(--color-ink);
    font-size: var(--text-24);
    font-weight: 800;
    line-height: 1.15;
    margin-bottom: var(--space-8);
  }
  .lodging-address {
    color: var(--color-body);
    font-size: var(--text-14);
    line-height: 1.4;
    margin-bottom: var(--space-6);
  }
  .lodging-links { display: flex; gap: var(--space-20); margin-bottom: var(--space-16); }
  .lodging-link {
    background: none;
    border: none;
    padding: 0;
    color: var(--color-accent);
    font-family: inherit;
    font-size: var(--text-12);
    font-weight: 700;
    text-decoration: underline;
    cursor: pointer;
  }
  .lodging-stay-grid { display: flex; align-items: stretch; gap: var(--space-12); }
  .lodging-stay-col { flex: 1 1 0; min-width: 0; }
  .lodging-stay-col-nights { flex: 0 0 auto; }
  .lodging-stay-divider { width: 1px; background: var(--lodging-hairline); align-self: stretch; }
  .lodging-stay-label,
  .lodging-stay-dow {
    color: var(--color-muted);
    font-size: var(--text-10);
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .lodging-stay-label { margin-bottom: var(--space-6); }
  .lodging-stay-date {
    color: var(--color-ink);
    font-size: var(--text-18);
    font-weight: 800;
    line-height: 1.15;
  }
  .lodging-stay-time { color: var(--color-body); font-size: var(--text-13); margin-top: var(--space-2); }
  .lodging-stay-nights {
    color: var(--color-ink);
    font-size: var(--text-18);
    font-weight: 800;
    margin-top: var(--space-18);
  }
  .lodging-ticket-notch {
    position: relative;
    height: 22px;
    background-image: repeating-linear-gradient(to right, rgba(51,38,30,0.22) 0 5px, transparent 5px 11px);
    background-repeat: no-repeat;
    background-position: left center;
    background-size: 100% 2px;
  }
  .lodging-ticket-notch::before,
  .lodging-ticket-notch::after {
    content: '';
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 22px;
    height: 22px;
    border-radius: var(--radius-circle);
    background: var(--lodging-mat);
  }
  .lodging-ticket-notch::before { left: -11px; }
  .lodging-ticket-notch::after { right: -11px; }
  .lodging-ticket-lower { padding: 0 var(--space-20) var(--space-20); }
  .lodging-confirmation { padding-bottom: var(--space-16); }
  .lodging-confirmation-label {
    color: var(--color-muted);
    font-size: var(--text-10);
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: var(--space-4);
  }
  .lodging-confirmation-row { display: flex; align-items: center; gap: var(--space-10); }
  .lodging-confirmation-number {
    font-family: 'Menlo', 'SFMono-Regular', ui-monospace, monospace;
    font-size: var(--text-24);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--color-ink);
  }
  .lodging-confirmation-copy {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--color-accent);
    display: inline-flex;
    align-items: center;
  }
  .lodging-confirmation-copy .material-symbols-outlined { font-size: var(--text-16); }
  /* Card tab bar - shared shell name (.card-tab*) though only the
     lodging ticket uses it today; switchCardTab scopes by .lodging-ticket. */
  .card-tabs {
    display: flex;
    /* Spread across the full width (Details flush left so it lines up
       with the confirmation number above it, Notes flush right) rather
       than stretched into equal thirds - the labels sit their natural
       width apart with the most breathing room the row allows. */
    justify-content: space-between;
    gap: var(--space-16);
    border-bottom: 1px solid var(--lodging-hairline);
    margin-bottom: var(--space-16);
  }
  .card-tab {
    flex: 0 0 auto;
    background: none;
    border: none;
    border-bottom: 2.5px solid transparent;
    margin-bottom: -1px;
    padding: 0 0 var(--space-12);
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 800;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--color-muted);
    cursor: pointer;
    text-align: left;
  }
  .card-tab-active { color: var(--color-ink); border-bottom-color: var(--color-accent); }
  .lodging-detail-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-16);
    padding: var(--space-12) 0;
    border-bottom: 1px solid rgba(51,38,30,0.08);
  }
  .lodging-detail-label {
    color: var(--color-muted);
    font-size: var(--text-11);
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    white-space: nowrap;
  }
  .lodging-detail-value { color: var(--color-ink); font-size: var(--text-14); font-weight: 700; text-align: right; }
  .lodging-detail-link { color: var(--color-accent); text-decoration: underline; }
  .lodging-policy { padding-top: var(--space-16); }
  .lodging-policy-label {
    color: var(--color-muted);
    font-size: var(--text-11);
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    margin-bottom: var(--space-6);
  }
  .lodging-policy-body { color: var(--color-body); font-size: var(--text-13); line-height: 1.5; }
  .lodging-who-list { display: flex; flex-direction: column; gap: var(--space-14); padding-bottom: var(--space-16); }
  .lodging-who-row { display: flex; align-items: center; gap: var(--space-10); }
  .lodging-who-icon { font-size: var(--text-18); color: #4c9a5f; }
  .lodging-who-row-absent .lodging-who-icon { color: var(--color-muted); }
  .lodging-who-name { color: var(--color-ink); font-size: var(--text-14); font-weight: 700; }
  .lodging-who-row-absent .lodging-who-name { color: var(--color-body); }
  .lodging-who-suffix { color: var(--color-muted); font-weight: 500; }
  .lodging-who-footer {
    border-top: 1px solid rgba(51,38,30,0.09);
    padding-top: var(--space-14);
    color: var(--color-muted);
    font-size: var(--text-12);
  }
  /* The tab already reads NOTES; the section's own "Notes" label would
     be redundant inside the panel. */
  .card-tab-panel[data-card-tab-panel="notes"] .card-notes-label { display: none; }
  /* The notes panel had two nested white boxes: the .card-notes-section
     card AND the textarea's own field box. Drop the inner (smaller) box
     so there's a single container - the textarea's "Add a note..."
     placeholder just sits top-left directly on the section, the same
     top-left spot it held inside the old field box. */
  .card-tab-panel[data-card-tab-panel="notes"] .card-notes-input {
    border: none;
    background: transparent;
    padding: 0;
    min-height: 44px;
  }
  .transport-card {
    font-family: 'Baloo 2', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    border-radius: var(--radius-5xl);
    padding: 58px 28px 26px;
    margin-bottom: 14px;
    color: #ffffff;
    box-shadow: var(--shadow-md);
  }
  .transport-card-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-10);
    margin-bottom: 49px;
  }
  .transport-card-badge {
    display: inline-block;
    background: rgba(255,255,255,0.22);
    border-radius: var(--radius-pill);
    padding: 8px 14px;
    font-size: var(--text-12);
    font-weight: 700;
  }
  .transport-card-provider {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-4);
    text-align: right;
  }
  .transport-card-provider-label {
    font-size: var(--text-11);
    font-weight: 700;
    opacity: 0.8;
  }
  .transport-card-provider-value {
    font-size: var(--text-15);
    font-weight: 700;
  }
  .transport-card-times {
    display: flex;
    justify-content: space-between;
    font-size: var(--text-13);
    font-weight: 600;
    opacity: 0.85;
    margin-bottom: 6px;
  }
  /* Codes/cities + the dashed line + icon share ONE flex row with
     align-items:center, so the dashed line (centered in .transport-
     card-path via the ::before below) lines up with the vertical
     middle of both the endpoint text AND the icon circle - the
     duration caption is pulled out with position:absolute so it can't
     throw that shared centerline off. Secondary captions and terminal
     info live in their own rows below (see .transport-card-line-
     secondary/-terminal) rather than stacked inside this row, for the
     same reason. */
  .transport-card-line {
    display: flex;
    align-items: center;
    margin-bottom: 3px;
  }
  .transport-card-endpoint-primary {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-32);
    font-weight: 800;
    line-height: 1.15;
  }
  .transport-card-endpoint-primary-right {
    text-align: right;
  }
  /* City and terminal both sit close under the airport/city code
     (small gaps below) - the terminal row carries no bottom margin of
     its own since it's the last line of this endpoint block; spacing
     before whatever comes next (see .transport-detail-list) is added
     there instead, not here. */
  .transport-card-line-secondary {
    display: flex;
    justify-content: space-between;
    margin-bottom: 3px;
  }
  .transport-card-line-terminal {
    display: flex;
    justify-content: space-between;
  }
  .transport-card-endpoint-secondary {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-13);
    font-weight: 600;
    opacity: 0.85;
    word-wrap: break-word;
  }
  .transport-card-endpoint-secondary-right {
    text-align: right;
  }
  /* Same font size as .transport-card-times, per design - terminal
     info reads as "part of the schedule," not a footnote. */
  .transport-card-endpoint-terminal {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-13);
    font-weight: 600;
    opacity: 0.85;
  }
  .transport-card-endpoint-terminal-right {
    text-align: right;
  }
  .transport-card-path {
    position: relative;
    flex: 1 1 90px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 8px;
  }
  .transport-card-path::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    border-top: 3px dashed rgba(255,255,255,0.5);
  }
  .transport-card-icon {
    position: relative;
    z-index: 1;
    font-size: var(--text-18) !important;
    background: rgba(255,255,255,0.28);
    border-radius: var(--radius-circle);
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .transport-card-duration {
    position: absolute;
    left: 50%;
    top: 100%;
    transform: translateX(-50%);
    margin-top: 4px;
    font-size: var(--text-12);
    font-weight: 600;
    opacity: 0.85;
    white-space: nowrap;
  }
  .transport-detail-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 16px 24px;
    margin: 49px 0 4px;
  }
  .transport-detail-item {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }
  .transport-detail-item-wide {
    flex-basis: 100%;
  }
  .transport-detail-label {
    font-size: var(--text-11);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    opacity: 0.8;
  }
  .transport-detail-value {
    font-size: var(--text-16);
    font-weight: 800;
  }
  .transport-detail-flight-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
  }
  .transport-detail-flight-link-icon {
    font-size: var(--text-14) !important;
  }
  .transport-card-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-10);
    background: rgba(255,255,255,0.18);
    border-radius: var(--radius-3xl);
    padding: 14px 16px;
    margin-top: 14px;
    font-size: var(--text-14);
    line-height: 1.4;
  }
  .transport-card-alert-icon {
    font-size: var(--text-18) !important;
    flex-shrink: 0;
    margin-top: 1px;
  }
  .transport-card-alert-text {
    flex: 1;
  }
  .transport-card-history {
    font-size: var(--text-11);
    opacity: 0.75;
    margin-top: 10px;
  }
  .detail-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-10);
    margin-top: 4px;
  }
  .detail-item {
    display: flex;
    gap: var(--space-28);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    line-height: 1.5;
  }
  .detail-label {
    color: #888780;
    width: 50px;
    flex-shrink: 0;
  }
  .detail-value {
    color: #2c2c2a;
    font-weight: 500;
  }
  /* Same white/no-colored-border/shadow look as the collapsed
     .journey-card-summary it always sits directly under (never shown
     any other way, per .travel-time-card.journey-card-expanded's own
     comment) - not its own orange-bordered "Journey Card" shell -
     per direct feedback, matching the Itinerary v3 Figma frame's
     minimal route card. Top corners square (bottom-only radius) so
     the two read as one continuous card rather than a visible seam -
     see .journey-card-summary's own matching bottom-corner rule.
     gap:16px (flex column) replaces per-child margins for spacing
     between mode-toggle/stats-row/link - see those rules below. */
  .travel-time-card {
    background: #ffffff;
    border-radius: 0 0 8px 8px;
    /* Left padding clears the connector line (x=26) with a real gap before
       the mode toggle / stats, direct feedback 2026-08-09 ("enough gap
       between the dotted line and the toggle") - the old 24px let the
       toggle sit right on top of the line. Slightly tighter elsewhere so
       the contents (now up to 4 mode buttons incl. Bike) still breathe. */
    padding: 16px 18px 16px 44px;
    margin-bottom: 6px;
    box-shadow: var(--shadow-md);
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
  }
  @media (min-width: 900px) {
    .travel-time-card {
      padding-left: 50px;
      padding-right: 50px;
    }
  }
  /* Both this and .day-date-toggle-row used to be two INDEPENDENT
     sticky elements (this one at top:0, the other at top:var(--pills-h)
     computed from this one's live-measured height) - reliable in every
     desktop browser tested, but real iOS Safari was observed to drop
     this row's sticky behavior entirely partway through a long scroll,
     leaving .day-date-toggle-row stuck alone flush at the very top
     with nothing above it. Coordinating two independent sticky
     elements via a JS-measured offset custom property was the
     suspected fragile point, so both are now plain (non-sticky) flow
     children of ONE shared sticky wrapper instead - see
     .itinerary-sticky-stack, which is the only position:sticky element
     in this stack now. */
  .date-pills-row {
    display: flex;
    justify-content: center;
    gap: var(--space-8);
    overflow-x: auto;
    /* Defensive floor - was observed disappearing entirely on real iOS
       Safari at certain scroll depths even after being merged into one
       sticky wrapper with .day-date-toggle-row, still under
       investigation. Guarantees this row keeps its own visible height
       regardless of whatever's causing that, even if it doesn't fix
       the root cause. */
    min-height: 34px;
    padding: 14px 4px 8px;
    /* scroll-snap-align:start (below, on .date-pill) treats the
       container's own padding-left as scrollable-past rather than
       protected space, so the browser auto-scrolls on initial layout
       to snap the first pill flush to the viewport edge - confirmed
       via scrollLeft matching padding-left with nobody having scrolled
       anything. scroll-padding-left is the property that actually
       protects it - must stay equal to padding-left above. */
    scroll-padding-left: 4px;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
  }
  .date-pills-row::-webkit-scrollbar {
    display: none;
  }
  /* Once there are enough pills to need horizontal scroll (see
     syncPillRowAlignment), switch from centered to left-aligned with
     more leading room - centering an overflowing scroll container is
     unreliable across browsers (the "before" side can become
     unreachable), and a flush-left first pill against the header's
     edge read cramped compared to a deliberate 50px inset. */
  .date-pills-row.pill-row-scrollable {
    justify-content: flex-start;
    padding-left: 50px;
    scroll-padding-left: 50px;
  }
  /* Sized to its own content now (was flex:1 1 0, deliberately growing
     to fill the row so a short trip's pills spanned its full width to
     match the cards below) - that grow behavior was exactly why a
     4-day trip's pills looked like they had huge padding: each one
     stretched to ~1/4 of the row regardless of what "MON Jul 27"
     actually needs, since the real CSS padding was already small.
     flex:0 1 auto hugs content instead; still allowed to shrink (not
     0 0 auto) so a many-day trip's row can still compress before
     falling back to horizontal scroll. */
  .date-pill {
    flex: 0 1 auto;
    display: flex;
    flex-direction: row;
    align-items: baseline;
    justify-content: center;
    gap: var(--space-4);
    /* Matches .idea-category-tab's own padding exactly, for the same
       reason the background color was unified earlier - one consistent
       tappable-pill pattern across both tabs. */
    padding: 6px 14px;
    border: none;
    border-radius: var(--radius-md);
    /* Same translucent tan as the unselected Ideas-tab category chips
       (rgba(216,199,184,0.37), same base RGB as the old solid #d8c7b8)
       for visual consistency between the two tappable-pill patterns -
       both sit on the same white sticky-header background now. */
    background: rgba(216,199,184,0.37);
    font-family: inherit;
    cursor: pointer;
    scroll-snap-align: start;
  }
  /* nowrap - "Jul 27" wrapping to "Jul" / "27" on two lines read as a
     bug even before the min-width fix above; keeping it as a second
     line of defense. */
  .date-pill-weekday {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-10);
    font-weight: 700;
    letter-spacing: 0.05em;
    color: #7a6a5a;
    white-space: nowrap;
  }
  .date-pill-date {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-ink);
    white-space: nowrap;
  }
  .date-pill-active {
    background: var(--color-body);
  }
  .date-pill-active .date-pill-weekday {
    color: #c9b6a8;
  }
  .date-pill-active .date-pill-date {
    color: #ffffff;
  }
  /* Trip-wide "Journeys" / "Free time" toggle pair - sits below the date
     pills, above the day's cards, as a flow child of
     .itinerary-sticky-stack (see toggleItineraryMode), which is what
     already keeps it permanently visible/sticky while scrolling -
     direct request 2026-08-06, replacing an earlier hide-on-scroll-down
     behavior (removed, see updateItineraryToggleRowReveal's own git
     history) that worked against that. Centered (direct request
     2026-08-08). No panel/background of its own - sits directly on the
     white .itinerary-sticky-stack (the warm #FBF8F3 panel from the first
     Figma pass was removed, direct request 2026-08-08; the pills carry
     their own light-grey outline below so they still read on white). No
     shared label above the pills (see .itinerary-toggle-icon below). */
  .itinerary-toggle-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-10);
    padding: 16px 16px 14px;
  }
  /* Pill: a slight light-grey outline (#e8e4dc, the app's standard
     hairline) on BOTH states, so the pills read on the white stack now
     that there's no panel behind them. Off = light-grey #E5E5E5 fill +
     muted brown text (direct request 2026-08-09 - was a transparent fill,
     which read as "on" against the white stack); on
     (.itinerary-toggle-btn-active below) swaps to a white fill + soft
     shadow, so grey=off / white=on. Selected states are independent per
     button (see toggleItineraryMode) - both can be active together, direct
     request 2026-08-06. */
  .itinerary-toggle-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
    background: #E5E5E5;
    border: 1px solid #e8e4dc;
    color: #6b5b4c;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 700;
    text-decoration: none;
    padding: 8px 15px;
    border-radius: var(--radius-pill);
    cursor: pointer;
    white-space: nowrap;
    transition: background 150ms ease, color 150ms ease,
                border-color 150ms ease, box-shadow 150ms ease;
  }
  /* Selected/on: white fill + soft drop shadow, darker text. Keeps the
     same #e8e4dc outline as the off state (no border swap) so the box
     size never shifts on toggle and the outline stays consistent. */
  .itinerary-toggle-btn-active {
    background: #ffffff;
    color: #33261e;
    box-shadow: 0 1px 1.5px rgba(80, 60, 40, 0.12);
  }
  /* Per-pill themed icon (see _setItineraryToggleBtnState) - a
     journey/route glyph on Journeys, a sun on Free time (Figma node
     511:11560). Paths stroke: currentColor, so the per-mode/per-state
     color rules below drive them: full color (route orange / free-time
     green) when the pill is on, muted brown when off. Decoupled from the
     text color on purpose - the icon stays colorful while the text is
     near-black. */
  .itinerary-toggle-icon {
    width: var(--text-15);
    height: var(--text-15);
    flex-shrink: 0;
    color: #8A7A6B;
  }
  .itinerary-toggle-btn[data-mode="routes"].itinerary-toggle-btn-active
    .itinerary-toggle-icon {
    color: #E8542E;
  }
  .itinerary-toggle-btn[data-mode="free-time"].itinerary-toggle-btn-active
    .itinerary-toggle-icon {
    color: #5B8A6B;
  }
  /* The ONE sticky element for this whole stack now (see .date-pills-
     row's own comment on why two independent sticky rows got merged
     into this single wrapper). Owns the background/shadow that used to
     be split across its two children, since it's the thing actually
     pinned to the viewport now - a sticky element needs SOME opaque
     backing regardless of color, or content scrolling underneath shows
     straight through it. Square bottom edge (no border-radius) per an
     earlier request, which also sidesteps the "content poking through
     the curve" issue this stack kept running into before.
     Full-bleed margin lives here now too (was on each child
     individually) - the children lost their own background when it
     moved up to this wrapper, so if only THEY had the negative margin,
     their content would bleed edge-to-edge while the wrapper's own
     painted white background stayed inset, showing the header not
     reaching the edges. */
  .itinerary-sticky-stack {
    position: sticky;
    /* --subtabs-reveal-h defaults to 0px (unset) so this is plain
       top:0 normally. On mobile, updateTripSubtabsReveal sets it to
       .trip-subtabs' own live height while that row is revealed as a
       fixed overlay (see .trip-subtabs.trip-subtabs-fixed's own CSS
       comment) - pushes this whole stack's stuck position down so the
       tab row slides in ABOVE it instead of painting over the date
       pills. The transition matches .trip-subtabs' own 0.22s so both
       move together as one visual block instead of one snapping. */
    top: var(--subtabs-reveal-h, 0px);
    transition: top 0.22s ease;
    z-index: 10;
    margin: 0 -16px;
    background: #fff;
    box-shadow: var(--shadow-md);
    /* Forces its own GPU compositing layer - the standard fix for real
       iOS Safari dropping/mis-painting position:sticky content (see
       .date-pills-row's comment on that row vanishing at scroll depth)
       once it's been promoted/demoted between layers mid-scroll. Cheap
       and harmless even if it turns out not to be the root cause. */
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    /* Stronger, persistent version of the same GPU-layer hint above -
       translateZ(0) alone only promotes it once, at creation; reported
       still recurring after that fix shipped, so adding this too
       rather than assuming translateZ(0) alone was sufficient. */
    will-change: transform;
  }
  /* On desktop both .page-header and .trip-subtabs (above) are sticky
     too, stacked ahead of this one - shift this stack down by both of
     their real heights (--page-header-h + --subtabs-h, kept live by
     syncStickyHeaderOffsets) so all three pin cleanly instead of
     overlapping. Mobile is unaffected - falls back to plain top:0 same
     as always. */
  @media (min-width: 900px) {
    .itinerary-sticky-stack {
      top: calc(var(--page-header-h, 0px) + var(--subtabs-h, 0px));
    }
  }
  /* Per-card expand/collapse control (see toggleJourneyCard) - lives in
     .journey-card-summary, never inside the full card itself. A bare
     chevron affordance now (was the "View Modes"/"Hide Modes" text link)
     - the text ate too much of a phone-width row and forced the route
     labels to truncate to uselessness (e.g. "Ho..." / "Airport (JF..."),
     direct feedback 2026-08-09. The chevron flips up when expanded via
     .journey-card-toggle-btn-expanded below. */
  .journey-card-toggle-btn {
    display: flex;
    align-items: center;
    background: none;
    border: none;
    padding: 0;
    /* #6b5e52 (not the brand orange) - the muted default; turns orange
       once the card is expanded (see .journey-card-toggle-btn-expanded). */
    color: var(--color-body);
    text-decoration: none;
    cursor: pointer;
    flex-shrink: 0;
  }
  .journey-card-toggle-btn:hover {
    color: var(--color-accent);
  }
  /* The chevron itself - a plain outline glyph (FILL 0, per the Material
     Symbols pinned-axis gotcha) that rotates 180deg when its card is
     expanded, reading as expand-down -> collapse-up. */
  .journey-card-toggle-icon {
    font-size: var(--text-20) !important;
    line-height: 1;
    transition: transform 0.2s ease;
  }
  /* Chevron flips up + turns orange the whole time its card is expanded
     (see toggleJourneyCard) - not just on hover, per direct feedback - so
     it reads as "on" rather than reverting to the muted default the
     instant the pointer moves away. */
  .journey-card-toggle-btn-expanded {
    color: var(--color-accent);
  }
  .journey-card-toggle-btn-expanded .journey-card-toggle-icon {
    transform: rotate(180deg);
  }
  /* The collapsed route row - just the route ("Airport -> Hotel") and
     the Show Route link, per the Itinerary v3 - 3 routes Figma frame. A
     separate sibling of the full .travel-time-card it controls (see
     JOURNEY_CARD_SUMMARY_TEMPLATE/toggleJourneyCard), not that card
     collapsed in place - tapping its link reveals the full card exactly
     as it already exists (unchanged), it doesn't morph this row into
     it. Visually matches .compact-row (white, rounded, no colored
     border) rather than .travel-time-card's bordered treatment, since
     it isn't part of the bordered "Journey Card" shell itself.
     Deliberately half the height of the full card's own padding
     (8px/16px here vs. 20px/24px on .travel-time-card) - a slim summary
     row, not a shrunk-down card. Hidden by default now (see
     .trip-subtab-panel.show-routes-mode below) - only shown once the
     "Show Routes" trip toggle is on, per the same Figma frame's
     default/toggled states. */
  .journey-card-summary {
    position: relative;
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-16);
    height: 45px;
    background: #ffffff;
    border-radius: var(--radius-md);
    /* Left padding clears the glyph node (a 22px white circle centered on
       the connector line at x=26 -> right edge ~37px) with ~9px before the
       route text, direct feedback 2026-08-09. A phone-width row has no room
       to also align the route text with the title column above/below (the
       old 92px), which pushed the labels into uselessly-truncated stubs.
       Desktop keeps the title-aligned padding (see the min-width:900px
       override below), where there's room for both. */
    padding: 0 16px 0 46px;
    margin-bottom: 6px;
    box-shadow: var(--shadow-md);
  }
  /* Flattens the touching corners and closes the gap to the full
     .travel-time-card the moment it's expanded (see toggleJourneyCard)
     - together with that card's own matching bottom-only radius, the
     two read as one continuous card instead of two stacked ones. */
  .journey-card-summary:has(+ .travel-time-card.journey-card-expanded) {
    border-radius: 8px 8px 0 0;
    margin-bottom: 0;
  }
  @media (min-width: 900px) {
    .journey-card-summary {
      padding-left: 118px;
      padding-right: 28px;
    }
  }
  /* Dotted vertical line + circular node marking this route step, per
     the Itinerary v3 - 3 routes Figma frame - unlike a self-contained
     icon, this deliberately BLEEDS past the card's own top/bottom edge
     (top/bottom: negative the exact gap .journey-card-summary's own
     margin-bottom - and every other itinerary card's - leaves between
     stacked cards) so the dashes visually touch the card above and the
     card below instead of floating in the middle of this one pill.
     Plain CSS shapes (not the SVG this used to be) specifically because
     a dashed border tiles cleanly at any length - stretching an SVG's
     viewBox to an arbitrary runtime height would distort the dash/node
     proportions instead. Positioned near .compact-row-time's own
     left edge (not flush at the card's edge) to match the Figma
     frame's own connector-to-text spacing. */
  .journey-connector-line {
    position: absolute;
    left: 25px;
    top: -6px;
    bottom: -6px;
    width: 2px;
    /* Dashes drawn with a repeating gradient (journeys orange), NOT a
       dashed border: a thin dashed border-left on a zero-width box
       silently fails to paint on real iOS Safari ("dotted lines still not
       showing up on mobile", 2026-08-09) even though desktop renders it
       fine. A background gradient paints identically everywhere and still
       tiles cleanly at any runtime height. 2px wide, centered on x=26. */
    background-image: repeating-linear-gradient(
      to bottom, #E8542E 0, #E8542E 4px, transparent 4px, transparent 8px);
    pointer-events: none;
  }
  /* A route card whose origin is Home (see JOURNEY_CARD_SUMMARY_TEMPLATE's
     summary_class) has nothing above it to connect to - starting the
     line at 50% (the node's own vertical center) instead of -6px means
     the dashes begin AT the node and only run down into the card
     below, rather than stubbing upward into empty space above it. */
  .journey-card-summary-origin-home .journey-connector-line {
    top: 50%;
  }
  /* Mirror of the rule above for the day's LAST leg (see
     JOURNEY_CARD_SUMMARY_TEMPLATE's summary_class/destination_is_lodging) -
     heading back to the hotel or home has nothing below it to connect
     to, so the line stops at the node's own vertical center instead of
     bleeding down into empty space. */
  .journey-card-summary-destination-lodging .journey-connector-line {
    bottom: 50%;
  }
  /* The node marker is the journeys glyph now (direct request 2026-08-09,
     "replace the dot with the journey icon"): a white circle carrying the
     orange S/route glyph (see _JOURNEY_CONNECTOR_GLYPH_SVG). The white
     circle masks the dashed line where it passes through, so the line
     reads as running INTO the node from above/below rather than straight
     across it. color drives the glyph (stroke: currentColor). */
  .journey-connector-node {
    position: absolute;
    left: 15px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-circle);
    background: #ffffff;
    color: #E8542E;
    pointer-events: none;
  }
  .journey-connector-glyph {
    width: 15px;
    height: 15px;
    display: block;
  }
  /* Second node marking where the connector reaches once expanded (see
     _updateJourneyConnectorReach) - the FIRST .journey-connector-node
     stays fixed at this card's own vertical center the whole time, per
     direct feedback ("the circle graphic disappears on Show Route,
     should stay") - this is an entirely separate element, not that
     same node relocated. A small orange dot terminal (overrides the
     glyph-node size/fill above). Hidden until JS turns it on and sets
     its bottom position (overriding top/transform inline once expanded). */
  .journey-connector-node-end {
    display: none;
    left: 21.5px;
    width: 9px;
    height: 9px;
    background: #E8542E;
  }
  .journey-card-summary-route {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-ink);
    white-space: nowrap;
    /* Flex items default to min-width:auto, which refuses to shrink
       below this row's own nowrap content width - a long venue/airport
       name (e.g. "John F. Kennedy International Airport") then forces
       this whole row past its container instead of truncating, taking
       the page's horizontal scroll bounds with it (the page visibly
       shifts left with a gap on the right once anything scrolls that
       overflow into view). min-width:0 lets it shrink; the ellipsis
       truncation itself lives on .journey-card-summary-label below. */
    min-width: 0;
  }
  .journey-card-summary-label {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  .journey-card-summary-arrow {
    font-size: var(--text-16) !important;
    color: var(--color-accent);
    flex-shrink: 0;
  }
  /* Hidden until toggleJourneyCard adds .journey-card-expanded (tapping
     the paired .journey-card-summary's button) - every other Journey/
     Travel/Event card is untouched by this, since it's scoped to this
     one card's own class, not a shared ancestor. Excludes
     .travel-time-card-loading (see that rule, defined later in this
     stylesheet so it wins the cascade) - the "Calculating..." skeleton
     has no summary pill of its own to reveal it from, so it stays
     visible on its own like it always has. */
  .travel-time-card {
    display: none;
  }
  /* An expanded full journey card only shows while Journeys is toggled ON
     (direct request 2026-08-09: "when the journey cards are toggled off ...
     hide the full journey cards"). Scoping to .show-routes-mode means
     toggling Journeys off hides the full card too, even one left expanded
     - without this the summary pill hid but the expanded card lingered,
     since .journey-card-expanded alone kept it display:flex. */
  .trip-subtab-panel.show-routes-mode .travel-time-card.journey-card-expanded {
    display: flex;
  }
  /* Trip-wide "Show Routes" toggle (see .itinerary-toggle-row and
     toggleItineraryMode) - reveals every .journey-card-summary in this
     trip's itinerary at once, with a slide+fade so the cards read as
     sliding down into place rather than just popping in. Mutually
     exclusive with .show-free-time-mode (toggleItineraryMode clears
     one before setting the other), matching the Itinerary v3 Figma
     frames' Show Routes / Show Free Time toggle pair. */
  .trip-subtab-panel.show-routes-mode .journey-card-summary {
    display: flex;
    animation: itineraryCardReveal 320ms ease-out both;
  }
  @keyframes itineraryCardReveal {
    from { opacity: 0; transform: translateY(-18px); }
    to { opacity: 1; transform: translateY(0); }
  }
  /* Segmented mode control - track + filled-pill selected state, per
     the Itinerary Figma file's Journey Card. Replaces the old
     icon-only round buttons; width:100% + flex:1 per .mode-btn (below)
     is what makes it a full-width 3-up control regardless of how many
     modes a given card actually has. */
  .mode-toggle {
    display: flex;
    align-items: center;
    width: 100%;
    height: 32px;
    box-sizing: border-box;
    background: #f1eae5;
    border-radius: var(--radius-pill);
    padding: var(--space-4);
  }
  .mode-btn {
    flex: 1;
    align-self: stretch;
    background: none;
    border: none;
    border-radius: var(--radius-pill);
    padding: 5px 0;
    font-family: inherit;
    font-size: var(--text-11);
    font-weight: 700;
    color: var(--color-ink);
    text-align: center;
    cursor: pointer;
  }
  .mode-btn-active {
    background: var(--color-body);
    color: #ffffff;
  }
  .mode-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
  }
  /* Explains a disabled mode-toggle button (see .mode-btn:disabled
     above and _mode_unavailable_note_html) - without this a grayed-out
     "Drive" button just reads as broken, not as "this route genuinely
     doesn't offer this option." Empty/absent whenever every fetched
     mode succeeded, so it never appears as a stray empty div. */
  .travel-time-mode-note {
    font-size: var(--text-11);
    color: #7a6e63;
    padding: 6px 2px 0;
    line-height: 1.4;
  }
  /* Which actual subway/bus/train line to take (e.g. "Subway (6) ->
     Bus (M60-SBS)") - see _travel_time_card_html/switchMode. Hidden
     (empty, display:none) whenever the current mode isn't transit or
     has no line data - toggling this is switchMode's job, not CSS
     alone, since the underlying text itself needs to change too. */
  .travel-time-transit-line {
    font-size: var(--text-12);
    font-weight: 600;
    color: #3b6ea5;
    padding: 6px 2px 0;
    line-height: 1.4;
  }
  /* 3-column LEAVE BY / TRAVEL TIME / DISTANCE stat row (2 columns -
     TRAVEL TIME / DISTANCE only, no "leave by" concept - for an
     arrival-style card, see .travel-time-stats-row-arrival and
     ARRIVAL_TRAVEL_TIME_TEMPLATE). flex:1 per .travel-time-stat keeps
     the columns evenly split regardless of value text length - no
     divider lines or separating border above them, and each column's
     own content is centered (not left-aligned) per direct feedback, so
     the whole block reads as centered under the full-width Drive/
     Transit toggle above it. */
  .travel-time-stats-row {
    display: flex;
    align-items: stretch;
    width: 100%;
  }
  .travel-time-stat {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-2);
  }
  .travel-time-stat-label {
    /* 9px (was 11): "leave by, travel time and distance 2 points decrease",
       direct request 2026-08-09. */
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--color-muted);
  }
  .travel-time-stat-value {
    /* 17px (was 20): "decrease font size of the leave by, travel time and
       distance by 3", direct request 2026-08-09. No 17px scale token, so
       a literal. */
    font-size: 17px;
    font-weight: 800;
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* The unit part of a stat value (AM/PM, h, m, mi/km) - 5px smaller than
     the number so the number reads large and the unit small, direct
     request 2026-08-09. See _stat_value_html (Python) / _wrapStatUnits (JS),
     which wrap the unit letters in this span. */
  .travel-time-stat-value .travel-time-stat-unit {
    font-size: 12px;
    margin-left: 1px;
  }
  .travel-time-link {
    display: block;
    width: 100%;
    text-align: center;
    font-family: inherit;
    font-size: var(--text-12);
    font-weight: 700;
    color: var(--color-accent);
    text-decoration: none;
  }
  /* This page is a static, no-server snapshot (see the top-of-file
     comment) - a live-looking traffic-aware duration next to a leave-by
     deadline otherwise gives no hint it was computed whenever this file
     was last generated, not just now. See _checked_at_label. */
  .travel-time-freshness {
    text-align: center;
    font-size: var(--text-11);
    color: rgba(51,38,30,0.45);
    margin-top: 4px;
  }
  .travel-time-card-loading {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
  }
  .travel-time-loading-label {
    font-size: var(--text-12);
    font-weight: 700;
    color: var(--color-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .travel-time-loading-road {
    position: relative;
    height: 22px;
    overflow: hidden;
    border-bottom: 2px dashed rgba(51,38,30,0.16);
  }
  .travel-time-loading-car {
    position: absolute;
    left: -10%;
    top: 0;
    font-size: var(--text-20);
    color: var(--color-accent);
    animation: travel-time-car-drive 5s linear infinite;
  }
  @keyframes travel-time-car-drive {
    0% { left: -10%; }
    100% { left: 100%; }
  }
  .route-arrow {
    color: #4a7c59;
    font-weight: 700;
  }
  .mode-icon {
    color: var(--color-body);
    font-size: var(--text-15) !important;
    vertical-align: middle;
  }
  .empty-day-add {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 10px;
    font-size: var(--text-13);
    color: #4a7c59;
    cursor: pointer;
  }
  .day-time-label {
    font-size: var(--text-12);
    font-weight: 700;
    color: #9a988f;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin: 10px 0 4px;
  }
  /* Free Cards - white shell (no border/stroke), per the Itinerary
     Figma file's Free Cards frame. overflow:hidden is what clips the
     decorative icon (.free-block-icon, below) bleeding off the left
     edge instead of overflowing the card's own rounded corners. Hidden
     by default now (see .trip-subtab-panel.show-free-time-mode below) -
     only shown once the "Show Free Time" trip toggle is on. */
  .free-block-card {
    position: relative;
    overflow: hidden;
    background: #ffffff;
    /* --radius-2xl (14px) to match the event .card it sits beside in the
       itinerary (was a one-off 24px) - design-consistency 2026-08-08. */
    border-radius: var(--radius-2xl);
    padding: 22px 16px 20px 20px;
    margin-bottom: 6px;
    display: none;
    justify-content: center;
  }
  /* Trip-wide "Show Free Time" toggle (see .itinerary-toggle-row and
     toggleItineraryMode) - reveals every .free-block-card in this
     trip's itinerary at once, mutually exclusive with .show-routes-mode.
     Same reveal animation as the routes toggle (see
     itineraryCardReveal). */
  .trip-subtab-panel.show-free-time-mode .free-block-card {
    display: flex;
    animation: itineraryCardReveal 320ms ease-out both;
  }
  .free-block-icon {
    position: absolute;
    pointer-events: none;
  }
  .free-block-icon svg {
    display: block;
    width: 100%;
    height: 100%;
  }
  /* Centered across the full card width - the faint icon (20% opacity,
     see _free_card_icon_html) is light enough now to sit behind the
     text without competing with it. */
  .free-block-content {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-8);
    max-width: 80%;
  }
  .free-block-message {
    font-size: var(--text-13);
    font-weight: 600;
    color: #4a3f2e;
    line-height: 1.4;
  }
  .free-block-highlight {
    font-weight: 700;
    color: var(--color-accent);
  }
  /* Dashed-outline pill, not a solid fill - per the Itinerary Figma
     file's Free Cards frame, this reads as a secondary nudge (jumps
     to the trip's own Idea Cards, see jumpToIdeasFromFreeBlock) rather
     than a primary CTA competing with the highlighted message text
     above it. */
  .free-block-add-btn {
    margin-top: 2px;
    background: transparent;
    color: var(--color-muted);
    border: 1.5px dashed var(--color-muted);
    border-radius: var(--radius-pill);
    padding: 8px 20px;
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 700;
    cursor: pointer;
  }
  .day-ideas {
    margin-bottom: 12px;
  }
  .day-ideas:empty {
    margin-bottom: 0;
  }
  /* Trailing filler after the last day's content (see itinerary_html's
     own comment) - guarantees enough scroll room for the last day to
     clear the sticky header stack fully, regardless of viewport height
     or how short that last day's own content is. Height is set
     precisely by JS (syncScrollSpacer), not here - a flat guess either
     overshoots (letting you scroll way past the last day into empty
     space) or undershoots depending on viewport/content height; 0 is
     just the pre-JS fallback for the instant before that runs. */
  .itinerary-scroll-spacer {
    min-height: 0;
  }
  .idea-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: 12px 16px;
    background: white;
    border: 1px solid #e0ded5;
    border-radius: var(--radius-lg);
    margin-bottom: 12px;
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    transition: box-shadow 0.15s ease, opacity 0.5s ease, max-height 0.5s ease 0.3s, margin-bottom 0.5s ease 0.3s, padding 0.5s ease 0.3s, border-width 0.5s ease 0.3s;
  }
  .idea-card-removing {
    opacity: 0;
    max-height: 0 !important;
    margin-bottom: 0 !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    border-top-width: 0 !important;
    border-bottom-width: 0 !important;
  }
  .idea-card-skeleton {
    pointer-events: none;
  }
  .skeleton-block {
    background: linear-gradient(90deg, #eeece4 25%, #f7f6f1 37%, #eeece4 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 5s ease infinite;
    border-radius: var(--radius-sm);
  }
  .skeleton-image {
    width: 100%;
    height: 140px;
    margin-bottom: 8px;
  }
  .skeleton-line-wide {
    width: 70%;
    height: 14px;
    margin-bottom: 6px;
  }
  .skeleton-line-narrow {
    width: 40%;
    height: 12px;
  }
  @keyframes skeleton-shimmer {
    0% { background-position: 100% 0; }
    100% { background-position: 0 0; }
  }
  /* Frame 2 (see IDEA_ADD_CONTROLS_TEMPLATE's .idea-form-skeleton) -
     same .skeleton-block shimmer as .idea-card-skeleton above, just a
     taller stack of pieces (title/rating/distance/date-pill-row) to
     stand in for the real .pdp-* result card this becomes once the
     lookup resolves (see showIdeaFormResultCard). */
  .idea-form-skeleton {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
  }
  .idea-form-skeleton .skeleton-image {
    height: 160px;
    margin-bottom: 4px;
  }
  .idea-form-skeleton-line-title {
    width: 60%;
    height: 16px;
  }
  .idea-form-skeleton-line-meta {
    width: 35%;
    height: 12px;
  }
  .idea-form-skeleton-line-distance {
    width: 50%;
    height: 12px;
    margin-bottom: 6px;
  }
  .idea-form-skeleton-pill-row {
    width: 70%;
    height: 40px;
    border-radius: var(--radius-pill);
  }
  .idea-adding-overlay {
    position: absolute;
    inset: 0;
    background: rgba(255, 255, 255, 0.94);
    border-radius: var(--radius-lg);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    gap: var(--space-8);
    font-weight: 600;
    color: #4a7c59;
    text-align: center;
    padding: var(--space-12);
    overflow: hidden;
    /* An explicit height gets set inline (see addIdeaToItinerary) to
       pin this to the card's real height - border-box so that height
       already includes this padding, instead of the padding adding on
       top of it and rendering a bit taller than the card. */
    box-sizing: border-box;
  }
  .idea-add-to-itinerary {
    border-top: 1px solid #e0ded5;
    margin-top: 6px;
    padding-top: 8px;
  }
  .app-toast {
    position: fixed;
    left: 50%;
    bottom: 32px;
    transform: translateX(-50%) translateY(20px);
    background: #2c2c2a;
    color: #fff;
    padding: 12px 20px;
    border-radius: var(--radius-md);
    font-size: var(--text-14);
    font-weight: 600;
    box-shadow: var(--shadow-md);
    z-index: 1000;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease, transform 0.3s ease;
  }
  .app-toast-anchored {
    bottom: auto;
    transform: translate(-50%, calc(-100% - 20px));
  }
  .app-toast-anchored.app-toast-visible {
    transform: translate(-50%, -100%);
  }
  /* No single card to anchor to (see showToast's nearTop param) but
     bottom-center still read as too far from the top-of-list/
     top-of-itinerary spot the change actually happened - lands in the
     upper third instead. Fixed pixel value, not a %, so it holds a
     consistent visual spot regardless of page length. */
  .app-toast-top {
    top: 100px;
    bottom: auto;
  }
  .app-toast-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
  .idea-add-to-itinerary-label {
    font-size: var(--text-11);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #78766f;
    margin-bottom: 6px;
  }
  .idea-day-toggle-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
  }
  .idea-day-toggle-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    font-family: inherit;
    color: var(--color-accent);
    background: #fff;
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-lg);
    padding: 6px 12px;
    cursor: pointer;
  }
  .idea-day-toggle-weekday {
    font-size: var(--text-11);
    font-weight: 400;
    text-transform: uppercase;
    color: var(--color-accent);
    opacity: 0.75;
  }
  .idea-day-toggle-monthday {
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-accent);
  }
  /* Packed - a pale/washed-out tint of the same orange, not tappable
     (disabled in JS - see buildIdeaCard) - per the Figma "Ideas - Full
     Card" frame, which keeps a (much lighter) border rather than
     dropping it entirely the way an earlier pass here had it. */
  .idea-day-toggle-btn-busy {
    color: #eab8a8;
    background: #fff;
    border-color: #f3d4c8;
    cursor: not-allowed;
  }
  .idea-day-toggle-btn-busy .idea-day-toggle-weekday,
  .idea-day-toggle-btn-busy .idea-day-toggle-monthday {
    color: #eab8a8;
    opacity: 1;
  }
  /* Picking a day at add-time (see populateIdeaFormDaySection) is a
     toggle, not an instant commit like buildIdeaCard's post-add version -
     needs a persistent "selected" look, which that one never did. */
  .idea-day-toggle-btn-selected {
    background: var(--color-accent);
    border-color: var(--color-accent);
  }
  .idea-day-toggle-btn-selected .idea-day-toggle-weekday,
  .idea-day-toggle-btn-selected .idea-day-toggle-monthday {
    color: #fff;
  }
  .idea-day-toggle-busy-note {
    font-size: var(--text-11);
    font-style: italic;
    color: var(--color-muted);
    margin-top: 6px;
  }
  /* Optional time picker under the day-toggle row (see buildIdeaCard's
     "Add to Itinerary" section) - 2026-07-27 feedback: picking a time up
     front, not just afterward from the day view, is what actually
     produces a journey/travel-time card once the idea lands on its day. */
  .idea-add-time-input {
    display: block;
    margin-top: 8px;
    font-family: inherit;
    font-size: var(--text-13);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 4px 8px;
    background: white;
    color: #3a352c;
  }
  .idea-card-image-attribution {
    font-size: var(--text-10);
    color: #b4b2a9;
    margin-top: -2px;
  }
  .idea-card-image-attribution a {
    color: #b4b2a9;
  }
  .idea-card-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
  }
  .idea-card-actions {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    flex-wrap: wrap;
  }
  .idea-time-row {
    justify-content: flex-start;
  }
  /* Live-trip-only (see buildVoteButton) - a shared "I want to do this"
     toggle, using the votes table/RLS that already existed from the
     original migration but had no UI wired to it until now. */
  .idea-vote-btn {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-muted);
    background: #fff;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    padding: 4px 10px;
    cursor: pointer;
    flex-shrink: 0;
  }
  .idea-vote-btn .material-symbols-outlined { font-size: var(--text-18); }
  .idea-vote-btn-active {
    color: var(--color-accent);
    border-color: var(--color-accent);
    background: rgba(240,81,46,0.08);
  }
  .idea-vote-count { min-width: 8px; }
  .idea-card-actions select, .idea-time-input {
    font-size: var(--text-13);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 4px 6px;
    background: white;
  }
  .idea-time-badge {
    color: #3b6ea5;
    background: #e9f0f7;
  }
  .idea-time-label {
    background: none;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 8px;
    font-size: var(--text-13);
    color: #4a7c59;
    cursor: pointer;
  }
  .idea-icon-btn {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 2px 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    color: #78766f;
  }
  .idea-inline-message {
    font-size: var(--text-13);
    font-weight: 600;
    color: #4a7c59;
  }
  .idea-icon-btn .material-symbols-outlined {
    font-size: var(--text-18);
  }
  .idea-icon-btn-labeled {
    gap: var(--space-4);
    font-size: var(--text-13);
    font-weight: 600;
  }
  .idea-airbnb-booking {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    flex-wrap: wrap;
  }
  .idea-airbnb-book-link {
    font-size: var(--text-13);
    font-weight: 600;
    color: #3b6ea5;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 40px;
  }
  .idea-airbnb-book-link .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .idea-airbnb-booked-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    font-size: var(--text-13);
    font-weight: 600;
    color: #4a7c59;
  }
  .idea-airbnb-booked-badge .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .idea-card-distance {
    font-size: var(--text-13);
    color: #6b6a63;
    display: flex;
    align-items: center;
    gap: 40px;
    margin-bottom: 4px;
  }
  .idea-card-distance-far {
    color: #b45309;
    font-weight: 600;
  }
  .idea-closed-warning {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    background: #fdf0d5;
    color: #8a5a00;
    font-size: var(--text-13);
    font-weight: 600;
    border-radius: var(--radius-sm);
    padding: 6px 10px;
    margin-bottom: 8px;
  }
  .idea-closed-warning .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .idea-hours-phone {
    margin-bottom: 6px;
  }
  .idea-remove-btn {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 8px;
    font-size: var(--text-12);
    color: var(--color-muted);
    cursor: pointer;
  }
  .idea-notes-btn {
    background: #6b6a63;
    border: none;
    border-radius: var(--radius-pill);
    padding: 8px 16px;
    font-size: var(--text-13);
    font-weight: 700;
    color: #fff;
    cursor: pointer;
  }
  /* ---- Place Detail Panel redesign (design_handoff_place_detail_panel,
     direction 1a) - .idea-card is exclusively the idea-detail overlay's
     root now (the main Ideas-tab list moved off it well before this
     pass, first to a compact .idea-row design, later to buildIdeaListCard's
     thumbnail card), so it's safe to restyle directly rather than layering
     new modifier classes on top of a shared list-row look. Chrome (radius/shadow/
     background/max-height) is scoped to .card-overlay-drawer-mode/
     .card-overlay-sheet-mode + .idea-card specifically, never to the
     bare .card-overlay-panel/.card-overlay-content those two mode
     classes also carry - those are shared by every other overlay flow
     (Add to List, Add Expense, Plan a New Trip, the weather sheet), and
     this component must not change how any of those look. Kept the
     existing 320ms cubic-bezier(0.22,0.61,0.36,1) drawer/sheet slide
     timing (shared by every drawer/sheet consumer above) rather than the
     handoff's own .55s cubic-bezier(.16,.84,.44,1) - overriding the
     shared transition would have re-timed all of those other flows too. */
  .idea-card {
    display: flex;
    flex-direction: column;
    background: #FFFFFF;
    border: none;
    border-radius: 0;
    box-shadow: none;
    margin-bottom: 0;
    padding: 0;
    gap: 0;
    overflow: visible;
  }
  .card-overlay-drawer-mode .idea-card {
    border-radius: 20px 0 0 20px;
    box-shadow: -8px 0 40px rgba(30,20,10,.18);
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }
  .card-overlay-sheet-mode .idea-card {
    border-radius: 26px 26px 0 0;
    box-shadow: 0 -8px 30px rgba(30,20,10,.2);
    max-height: 95vh;
    max-height: 95dvh;
    overflow-y: auto;
  }
  .pdp-photo {
    position: relative;
    flex: none;
    height: 230px;
    overflow: hidden;
  }
  .card-overlay-sheet-mode .pdp-photo {
    height: 180px;
  }
  .pdp-photo-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  /* Flat brand-brown fill for ideas saved without a real photo (e.g.
     pasted from a site with no og:image) - a blank white rectangle read
     as broken/loading forever, not "no photo". */
  .pdp-photo-placeholder {
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #EFE9E1, #E4DACB);
  }
  .pdp-drag-handle {
    display: none;
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    width: 38px;
    height: 5px;
    border-radius: 3px;
    background: rgba(255,255,255,.75);
  }
  .card-overlay-sheet-mode .pdp-drag-handle {
    display: block;
  }
  .pdp-category-chip {
    position: absolute;
    left: 20px;
    bottom: 16px;
    background: #FFFFFF;
    color: #241a12;
    font-size: var(--text-13);
    font-weight: 800;
    text-transform: capitalize;
    padding: 8px 16px;
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-md);
  }
  .card-overlay-sheet-mode .pdp-category-chip {
    left: 16px;
    bottom: 14px;
  }
  .pdp-content {
    padding: 18px 26px 32px;
  }
  .card-overlay-sheet-mode .pdp-content {
    padding: 16px 20px 28px;
  }
  .pdp-source-link {
    display: inline-block;
    font-size: var(--text-12);
    color: var(--color-muted);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  .pdp-title-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-10);
    margin-top: 4px;
  }
  .pdp-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-24);
    line-height: 1.2;
    color: #241a12;
  }
  .card-overlay-sheet-mode .pdp-title {
    font-size: var(--text-20);
  }
  .pdp-rating {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-shrink: 0;
    font-size: var(--text-15);
    font-weight: 800;
    color: #241a12;
  }
  .idea-private-badge {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-shrink: 0;
    background: #F8EEDF;
    color: var(--color-body);
    font-size: var(--text-12);
    font-weight: 700;
    padding: 4px 8px;
    border-radius: var(--radius-pill);
  }
  .idea-private-badge .material-symbols-outlined {
    font-size: var(--text-14);
  }
  /* Entry point into the card_notes thread (see buildIdeaCard's
     groupNotesLink) - sand fill/body-brown text, the same "notes"
     palette the row's own note-preview line uses. */
  .pdp-group-notes-link {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    align-self: flex-start;
    margin-top: 10px;
    background: #F8EEDF;
    color: var(--color-body);
    font-family: 'Google Sans Flex', sans-serif;
    font-size: var(--text-13);
    font-weight: 700;
    border: none;
    border-radius: var(--radius-pill);
    padding: 8px 16px;
    cursor: pointer;
  }
  .pdp-group-notes-link .material-symbols-outlined {
    font-size: var(--text-15) !important;
  }
  .pdp-rating .material-symbols-outlined {
    font-size: var(--text-16) !important;
    color: #F9B723;
    font-variation-settings: 'FILL' 1;
  }
  .pdp-distance, .pdp-address-line {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    color: #7A6C5D;
    margin-top: 2px;
    display: flex;
    align-items: center;
    gap: var(--space-6);
  }
  .pdp-address-line .material-symbols-outlined {
    font-size: var(--text-16);
  }
  .pdp-hours-row, .pdp-phone-row {
    display: flex;
    align-items: center;
    gap: var(--space-14);
    padding: 8px 0;
    background: none;
    border: none;
    width: 100%;
    text-align: left;
    font-size: var(--text-14);
    color: #3A2E22;
    cursor: pointer;
    font-family: 'Baloo 2', sans-serif;
  }
  .pdp-phone-row {
    cursor: default;
    text-decoration: none;
  }
  .pdp-hours-row .material-symbols-outlined:first-child,
  .pdp-phone-row .material-symbols-outlined {
    font-size: var(--text-18);
    color: var(--color-muted);
  }
  /* No flex:1 on purpose - stretching the label to fill the row pushed
     the chevron all the way to the row's right edge, far from the time
     text it actually controls. Natural width + the chevron's own
     negative margin below keeps it sitting right next to the label
     instead. */
  .pdp-hours-chevron {
    font-size: var(--text-16) !important;
    color: var(--color-muted);
    margin-left: -8px;
    transition: transform 0.2s ease;
  }
  .pdp-hours-row-open .pdp-hours-chevron {
    transform: rotate(180deg);
  }
  .pdp-hours-list {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    line-height: 1.5;
    color: #7A6C5D;
    padding: 0 0 8px 32px;
  }
  .pdp-maps-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-accent);
    text-decoration: none;
  }
  .pdp-maps-row .material-symbols-outlined {
    font-size: var(--text-18);
    color: var(--color-accent);
  }
  .pdp-add-notes-btn {
    display: inline-block;
    margin-top: 10px;
    background: #5A4C3E;
    color: #FFFFFF;
    font-size: var(--text-14);
    font-weight: 700;
    padding: 12px 24px;
    border-radius: var(--radius-pill);
    border: none;
    cursor: pointer;
  }
  .pdp-notes-card {
    background: #FBF6EE;
    border: 1px solid rgba(51,38,30,.12);
    border-radius: var(--radius-2xl);
    padding: 12px 16px;
    margin-top: 12px;
  }
  .pdp-notes-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
  }
  .pdp-notes-card-label {
    font-size: var(--text-12);
    font-weight: 800;
    letter-spacing: 0.05em;
    color: var(--color-muted);
  }
  .pdp-notes-card-edit-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 0;
    color: var(--color-muted);
    cursor: pointer;
  }
  .pdp-notes-card-edit-btn .material-symbols-outlined {
    font-size: var(--text-15);
  }
  .pdp-notes-card-body {
    font-size: var(--text-14);
    line-height: 1.5;
    color: #3A2E22;
  }
  .pdp-status-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-10);
    margin-top: 14px;
  }
  .pdp-status-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
    background: none;
    border: 1.5px solid rgba(51,38,30,.18);
    border-radius: var(--radius-pill);
    padding: 10px 16px;
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 700;
    color: #3A2E22;
    cursor: pointer;
  }
  .pdp-status-toggle .material-symbols-outlined {
    font-size: var(--text-15) !important;
    color: currentColor;
  }
  .pdp-status-toggle-active {
    border-color: var(--color-accent);
    color: var(--color-accent);
  }
  /* Real bug found live-testing: width:100% + this card's own padding/
     border with no box-sizing:border-box added ~28px beyond the parent's
     content width, pushing the card's right edge past the panel's own
     26px/20px side padding and clipping its rounded corner against the
     panel edge. box-sizing here is what actually keeps it flush with
     every sibling element's existing margin, not a new one-off inset. */
  .pdp-reminder-card {
    display: flex;
    align-items: flex-start;
    gap: var(--space-8);
    background: #FBF3E8;
    border: 1px solid rgba(232,84,46,.28);
    border-radius: var(--radius-xl);
    padding: 12px 12px;
    margin-top: 10px;
    font-size: var(--text-13);
    line-height: 1.45;
    color: #5A4C3E;
    width: 100%;
    box-sizing: border-box;
  }
  .pdp-reminder-card .material-symbols-outlined {
    font-size: var(--text-15);
    color: var(--color-accent);
    flex-shrink: 0;
  }
  .pdp-reminder-body {
    flex: 1;
  }
  .pdp-reminder-email {
    font-weight: 700;
    color: var(--color-accent);
    cursor: pointer;
  }
  .pdp-reminder-copy-btn {
    display: inline-flex;
    vertical-align: middle;
    margin-left: 4px;
    background: none;
    border: none;
    padding: 0;
    color: var(--color-accent);
    cursor: pointer;
  }
  .pdp-reminder-copy-btn .material-symbols-outlined {
    font-size: var(--text-13);
  }
  /* White circle behind the X - plain muted-on-cream (no background)
     read as too low-contrast against the reminder card's own cream
     fill per direct feedback. */
  .pdp-reminder-dismiss-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-pill);
    background: #FFFFFF;
    box-shadow: var(--shadow-sm);
    border: none;
    padding: 0;
    color: var(--color-muted);
    cursor: pointer;
  }
  .pdp-reminder-dismiss-btn .material-symbols-outlined {
    font-size: var(--text-14) !important;
    color: var(--color-muted) !important;
  }
  .pdp-section-label {
    font-size: var(--text-12);
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-muted);
    margin: 18px 0 14px;
  }
  .pdp-day-chip-row {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-8);
  }
  .card-overlay-sheet-mode .pdp-day-chip-row {
    grid-template-columns: repeat(3, 1fr);
  }
  /* Descendant-scoped override of the shared .idea-day-toggle-btn (also
     used by the Add-idea form's own day picker - see that class's own
     CSS comment) - only chips inside this panel's grid pick up the new
     look, the add-idea form's chips are untouched. */
  .pdp-day-chip-row .idea-day-toggle-btn {
    background: #EFE9E1;
    border: none;
    border-radius: var(--radius-xl);
    padding: 8px 6px;
    align-items: center;
    gap: 0px;
  }
  .pdp-day-chip-row .idea-day-toggle-weekday {
    font-size: var(--text-11);
    font-weight: 800;
    color: var(--color-body);
    opacity: 0.85;
  }
  .pdp-day-chip-row .idea-day-toggle-monthday {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    font-weight: 800;
    color: var(--color-body);
    margin-top: 1px;
  }
  .pdp-day-chip-row .idea-day-toggle-btn-busy {
    background: #F5F1E9;
  }
  .pdp-day-chip-row .idea-day-toggle-btn-busy .idea-day-toggle-weekday,
  .pdp-day-chip-row .idea-day-toggle-btn-busy .idea-day-toggle-monthday {
    color: #D4CBBE;
  }
  /* Latent bug surfaced by _buildIdeaCardItineraryAssigned's day
     re-picker (the first case where a -selected pill can ever appear
     inside .pdp-day-chip-row - the old list-only picker never showed an
     already-assigned day, so this never mattered before): this scoped
     rule's own 2-class specificity otherwise beats the base
     .idea-day-toggle-btn-selected rule's 1-class specificity regardless
     of source order, silently keeping a selected pill beige instead of
     filled. Re-asserts a filled selected look at matching specificity -
     #6B5E52 (direct user request), not the base rule's orange. */
  .pdp-day-chip-row .idea-day-toggle-btn-selected {
    background: var(--color-body);
  }
  .pdp-day-chip-row .idea-day-toggle-btn-selected .idea-day-toggle-weekday,
  .pdp-day-chip-row .idea-day-toggle-btn-selected .idea-day-toggle-monthday {
    color: #fff;
  }
  .pdp-time-select {
    display: block;
    width: 230px;
    max-width: 100%;
    margin-top: 12px;
    background: #FFFFFF;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 12px 16px;
    font-family: inherit;
    font-size: var(--text-14);
    color: #3A2E22;
  }
  .idea-day-toggle-busy-note {
    color: var(--color-muted);
  }
  /* ---- Drawer add-to-itinerary flow (buildIdeaCard) - matched directly
     against a Figma screenshot comparison (2026-08-05, see
     travel_app_decisions_log.md), not the earlier written spec, after
     the first pass built its own interpretation instead of the real
     design. No state-tag badge in the real design at all - status is
     communicated by which controls show, not a text label. ---- */
  .pdp-add-notes-box {
    display: block;
    width: 100%;
    min-height: 91px;
    margin-top: 18px;
    background: #FBF7F0;
    border: none;
    border-radius: 15px;
    padding: 16px 20px;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-15);
    color: #959595;
    text-align: left;
    cursor: pointer;
  }
  .pdp-vote-row {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid rgba(51,38,30,.1);
  }
  /* Vote widget and the dots menu are the row's only two flex:none
     items, so .pdp-vote-row-center's own flex:1 + centered content
     genuinely centers the status toggles in the space between them,
     rather than just clustering next to the vote button. */
  .pdp-vote-row-center {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
    gap: var(--space-12);
    flex-wrap: wrap;
  }
  /* Notes-thread count, next to the vote widget - same card_notes-backed
     count .ideas-card-notes-count already shows on the mini row (see
     buildIdeaCard's own comment for why this drawer never had it before
     2026-08-06). flex: none like the vote widget/menu it sits between,
     so .pdp-vote-row-center's own centering isn't thrown off by it. */
  .pdp-notes-count {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex: none;
    background: none;
    border: none;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-muted);
    cursor: pointer;
    padding: 0;
  }
  .pdp-notes-count .material-symbols-outlined {
    font-size: var(--text-16) !important;
  }
  .pdp-vote-widget {
    display: flex;
    align-items: center;
    gap: var(--space-8);
  }
  .idea-vote-btn {
    width: 46px;
    height: 46px;
    border-radius: var(--radius-pill);
    border: 1.5px solid rgba(51,38,30,.18);
    background: #FFFFFF;
    color: #3A2E22;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
  }
  .idea-vote-btn .material-symbols-outlined {
    font-size: var(--text-18) !important;
  }
  .idea-vote-btn-active {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: #FFFFFF;
  }
  /* Plain sibling label now, not a corner badge overlaid on the circle -
     see buildVoteButton's own comment for why that earlier treatment
     (from the Place Detail Panel redesign) is superseded here. */
  .idea-vote-count {
    font-family: 'Google Sans Flex', sans-serif;
    font-size: var(--text-15);
    font-weight: 600;
    color: #676767;
    min-width: 8px;
  }
  /* Three horizontal dots (not vertical) - matches the Figma icon
     exactly (more_horiz), shown in both contexts now (not assigned-
     only) since the real design shows it identically in both frames.
     No special positioning override needed here the way the earlier
     photo-header placement needed against the drawer's fixed close
     button - this row is a normal document-flow element. */
  .pdp-vote-row .ideas-card-menu {
    position: relative;
    top: auto;
    right: auto;
  }
  .pdp-vote-row .ideas-card-menu-popover {
    right: 0;
    left: auto;
  }
  .pdp-itinerary-section {
    margin-top: 22px;
  }
  .pdp-itinerary-columns {
    display: flex;
    gap: var(--space-24);
  }
  .pdp-itinerary-column-label {
    font-size: var(--text-12);
    font-weight: 800;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: #4A3F35;
    margin-bottom: 10px;
  }
  /* Beige/brown, not the base .idea-day-toggle-btn's orange outline -
     matches the plain (non-"selected") day-pill look, same treatment as
     the TIME pill beside it, since this is now a settled value on
     display rather than one of several options being actively chosen
     (that orange-outline picker only reappears in .pdp-itinerary-
     pickers below once tapped). Same colors .pdp-day-chip-row's own
     scoped override already uses, just scoped to this column instead
     since this button isn't inside that row. */
  .pdp-itinerary-when-btn.idea-day-toggle-btn {
    background: #EFE9E1;
    border: none;
    border-radius: var(--radius-sm);
    padding: 8px 6px;
    cursor: pointer;
  }
  .pdp-itinerary-when-btn .idea-day-toggle-weekday {
    color: var(--color-body);
    opacity: .85;
  }
  .pdp-itinerary-when-btn .idea-day-toggle-monthday {
    color: var(--color-body);
  }
  /* A native <select>, not a button (see _buildIdeaCardItineraryAssigned's
     own comment on why the separate reveal-on-tap dropdown it used to
     open was redundant over just using a real select directly) -
     appearance:none strips the browser's own select chrome so it can
     still look like the WHEN pill beside it; native arrow re-added via
     a plain background-image chevron since a <select> can't contain a
     Material Symbols child span the way a button could. */
  .pdp-itinerary-time-btn {
    display: block;
    height: 45px;
    min-width: 121px;
    background: #EFE9E1 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237A6A5C'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E") no-repeat right 10px center;
    background-size: 18px;
    border: none;
    border-radius: var(--radius-sm);
    padding: 0 32px 0 12px;
    -webkit-appearance: none;
    appearance: none;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    font-weight: 800;
    color: var(--color-body);
    cursor: pointer;
  }
  .pdp-itinerary-pickers {
    display: none;
  }
  .pdp-itinerary-when-open .pdp-itinerary-pickers {
    display: block;
    margin-top: 14px;
  }
  /* Cancel/confirm footer - unassigned-state only (see
     _buildIdeaCardItineraryUnassigned's own comment for why the first
     assignment gets a deliberate confirm step everything after it
     doesn't). Solid orange regardless of whether a day's been picked
     yet, matching the real design pixel-for-pixel - not a disabled/
     enabled toggle the way the row's own equivalent button is. */
  .pdp-itinerary-footer {
    display: flex;
    align-items: center;
    gap: var(--space-16);
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid rgba(51,38,30,.1);
  }
  .pdp-itinerary-cancel-btn {
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 700;
    font-size: var(--text-14);
    /* Cancel is a dismiss/secondary action - neutral, not accent
       (design-backlog 2026-08-08). Was --color-accent; now matches
       .confirm-modal-cancel-btn so Cancel reads consistently as
       "back out", never as a primary/accent action. */
    color: var(--color-muted);
    background: none;
    border: none;
    cursor: pointer;
  }
  .pdp-itinerary-confirm-btn {
    flex: 1;
  }
  /* No top padding on purpose - .idea-category-tabs (first child) is
     full-bleed/sticky right against .trip-subtabs above it (see that
     rule's own comment); any padding here would show as a stray gap
     between the two before either of them accounts for it. */
  .ideas-panel {
    padding-top: 0;
  }
  /* Breathing room between the sticky chip row and the first card -
     .idea-row supplies its own bottom margin between cards but nothing
     supplied space above the first one, so the list started right at
     the chip row's bottom edge. */
  .idea-list {
    padding-top: 14px;
  }
  .idea-form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    margin-bottom: 16px;
  }
  .idea-form input, .idea-form select, .idea-card-editing input, .idea-card-editing select, .idea-notes-editor input {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 6px 8px;
    font-size: var(--text-13);
    font-family: inherit;
  }
  .idea-card-editing {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
  }
  .idea-notes-editor {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    margin-top: 12px;
    margin-bottom: 8px;
  }
  /* A textarea, not the generic .idea-notes-editor input rule above
     (which no longer matches - that one only ever targeted a single-
     line <input>, see buildInlineNotesEditor's own comment on why it
     became a textarea) - text starting top-left instead of vertically
     centered in a cramped one-line field is the whole point of the
     switch, so this needs its own explicit rule rather than falling
     back to shared input styling. */
  .idea-notes-editor .idea-input-note {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: var(--text-14);
    color: #3A2E22;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 12px 12px;
    resize: vertical;
    min-height: 72px;
  }
  .idea-form-fields {
    flex-basis: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
  }
  /* Generic 13px/700 label above a field - reused by City, Place, Or
     paste a link, Date, Time, Note (see IDEA_ADD_CONTROLS_TEMPLATE) so
     every field in this form reads consistently instead of each one
     inventing its own label treatment. */
  .idea-field-label {
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-body);
    margin-bottom: 7px;
  }
  .idea-url-row, .idea-link-row {
    flex-basis: 100%;
  }
  /* Frame 1 (see IDEA_ADD_CONTROLS_TEMPLATE's .idea-form-search-block) -
     one wrapper hidden/shown as a unit by showIdeaFormSkeleton/
     showIdeaFormResultCard/resetIdeaFormBackToSearch, rather than each
     row inside it (url/link/screenshot) being toggled individually.
     Needs its own column layout + gap since it now sits one level
     deeper than .idea-form-fields' own flex-direction:column/gap:16px
     (which still lays out THIS block, plus the sibling result-card/
     skeleton/day-section/note-row blocks that also live directly
     inside .idea-form-fields). */
  .idea-form-search-block {
    flex-basis: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
  }
  .idea-form-or-divider {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    font-family: 'Google Sans Flex', sans-serif;
    font-size: var(--text-12);
    font-weight: 600;
    color: #B3A797;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .idea-form-or-divider::before, .idea-form-or-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: rgba(74,63,53,0.14);
  }
  .idea-link-input-wrap {
    display: flex;
    gap: var(--space-8);
    align-items: center;
  }
  /* Same position:relative-anchor-for-an-absolute-X pattern as
     .idea-name-autocomplete-wrap (see .idea-input-clear-btn, reused
     verbatim here) - split out from .idea-link-input-wrap itself since
     that row also has the Enter button as a flex sibling, which the X's
     right:10px absolute positioning must NOT be relative to. */
  .idea-link-input-relative {
    position: relative;
    flex: 1;
    min-width: 0;
  }
  .idea-link-input-relative .idea-input-link {
    width: 100%;
  }
  /* Disabled-until-non-empty per the Add-to-list handoff (see
     handleIdeaLinkInput) - same 40%-opacity disabled convention as
     .expense-submit-btn:disabled, applied here since this is the first
     button in this form gated purely on a single field's emptiness
     rather than on an in-flight request. */
  .idea-link-enter-btn {
    flex-shrink: 0;
    width: 46px;
    height: 46px;
    border-radius: var(--radius-circle);
    border: none;
    background: var(--color-accent);
    color: #FFFFFF;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: transform 120ms ease-out;
  }
  .idea-link-enter-btn .material-symbols-outlined {
    font-size: var(--text-20);
  }
  .idea-link-enter-btn:disabled {
    opacity: 0.4;
    cursor: default;
  }
  /* 50px from the grey line is .idea-city-select-row's job whenever
     it's actually shown (see that rule's own margin-top) - Place only
     needs to own that same offset when there's no city row above it to
     own it instead (single-city trips - see initCitySelector toggling
     this class). Never both at once, or the gap doubles up. */
  .idea-form-fields-flush-top .idea-url-row {
    margin-top: 50px;
  }
  /* Shared box style for every plain text field this form adds/touches
     (Place, Or paste a link, Date, Time, the Notes tab's textarea, and
     the Places tab's own Notes field below - scoped by .idea-note-row
     since .idea-input-note is a class ALSO used by an unrelated
     single-line <input> elsewhere, buildInlineNotesEditor's own inline
     "Add notes" editor on an idea card - this box styling must not
     leak onto that one) - per the Add-to-list handoff's field spec,
     applied once here rather than duplicated per class. */
  input.idea-input-url, input.idea-input-link, input.idea-input-date,
  select.idea-input-time, input.idea-input-checkout-date,
  .idea-note-text,
  .idea-note-row .idea-input-note {
    width: 100%;
    box-sizing: border-box;
    background: #FFFFFF;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 12px 14px;
    font-size: var(--text-16);
    color: #4A3F35;
    font-family: 'Google Sans Flex', -apple-system, sans-serif;
  }
  input.idea-input-url:focus, input.idea-input-link:focus,
  input.idea-input-date:focus, select.idea-input-time:focus,
  input.idea-input-checkout-date:focus,
  .idea-note-text:focus, .idea-note-row .idea-input-note:focus {
    outline: none;
    border-color: var(--color-accent);
  }
  input.idea-input-url::placeholder, input.idea-input-link::placeholder,
  .idea-note-text::placeholder, .idea-note-row .idea-input-note::placeholder {
    color: #B3A797;
  }
  .idea-manual-address-row {
    flex-basis: 100%;
  }
  .idea-note-row {
    flex-basis: 100%;
  }
  .idea-note-row .idea-input-note {
    height: 70px;
    resize: none;
  }
  .idea-field-label-optional {
    color: #B3A797;
    font-weight: 500;
    text-transform: none;
  }
  .idea-submit-row {
    flex-basis: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-18);
  }
  /* flex:1 (not width:100%) now that Cancel sits in the same row (see
     IDEA_ADD_CONTROLS_TEMPLATE - Cancel first, per direct feedback) -
     grows to fill whatever's left after Cancel's own natural width
     instead of spanning the full row edge to edge, so Cancel keeps
     real breathing room next to it rather than being squeezed
     against the row's far edge. */
  .idea-submit-row .empty-day-add {
    flex: 1;
    background: var(--color-accent);
    border: none;
    border-radius: var(--radius-pill);
    color: white;
    font-weight: 700;
    padding: var(--space-16);
    font-size: var(--text-16);
  }
  /* "Cancel" per the Add-to-list handoff (red, underline on hover) -
     shared between the always-visible .idea-cancel-link (plain
     closeCardOverlay(), see IDEA_ADD_CONTROLS_TEMPLATE) and
     .idea-dupe-cancel-btn ("Do Not Add," wired to cancelDuplicateAdd's
     own full form.reset() - a genuinely more destructive action than a
     plain modal-dismiss Cancel, since it also clears whatever was
     resolved so far). The two swap places in the same footer slot (see
     commitIdeaWithDupeCheck/cancelDuplicateAdd) rather than ever
     showing both at once, so one shared style is enough. */
  .idea-dupe-cancel-btn, .idea-cancel-link {
    background: none;
    border: none;
    padding: 0;
    font-size: var(--text-13);
    font-weight: 600;
    /* Neutral dismiss action, not accent (design-backlog 2026-08-08) -
       these sit next to the primary Save/Add button in the Add-to-list
       drawer; Cancel in accent orange competed with the real primary
       action. Now muted like every other Cancel. */
    color: var(--color-muted);
    text-decoration: none;
    cursor: pointer;
  }
  .idea-dupe-cancel-btn:hover, .idea-cancel-link:hover {
    text-decoration: underline;
  }
  .idea-input-title {
    flex: 1 1 140px;
  }
  .idea-input-note {
    flex: 1 1 160px;
  }
  /* .idea-form-image's own sizing/.idea-form-open-link's own text style
     used to live here - both retired now that Frame 3's result card
     reuses buildIdeaCard's real .pdp-photo-img/.pdp-source-link styling
     instead of a plain inline thumbnail + link (see
     IDEA_ADD_CONTROLS_TEMPLATE - the same two elements just carry the
     .pdp-* classes now instead). Left in place would have silently
     conflicted with .pdp-photo-img's height:100%/object-fit:cover
     (same-specificity single-class rules, last-in-file wins). */
  /* The saved-idea card (.idea-card) gives .pdp-photo/.pdp-content their
     edge-to-edge look for free via its own overflow:hidden/rounded
     corners and zero side padding - .idea-form-details has neither (it
     sits inline among this form's other inset, side-padded fields via
     display:contents, see showIdeaFormResultCard), so the photo needs
     its own rounding/clipping and the content needs its side padding
     zeroed to match every other field's alignment instead of adding a
     second, wider inset on top of the form's own. */
  .idea-form-details .pdp-photo {
    height: 180px;
    border-radius: var(--radius-2xl);
  }
  .idea-form-details .pdp-content {
    padding: 12px 0 0;
  }
  /* Always visible now (was gated behind ideas.length>=5) - Essentials
     exists independent of real idea count, so the chip row is core
     navigation for this page, not an overflow-only affordance.
     Sticky/white/full-bleed the same way .date-pills-row is for the
     Itinerary tab (top:0, background:#fff, negative-margin full-bleed)
     so it reads as one continuous white surface with .trip-subtabs
     right above it, instead of floating in the tan page background -
     same idiom, just scoped to whichever .trip-subtab-panel is
     currently active rather than a second global sticky element.
     Also the LAST row of that white sticky stack (Ideas only has this
     one row, unlike Itinerary's tabs->pills->route-toggle stack), so
     it carries the rounded-bottom-corner + shadow that caps the block
     off, same as .itinerary-toggle-row does for Itinerary. */
  /* Always left-aligned starting at 20px (not centered like
     .date-pills-row, and not conditional on whether it overflows). */
  .idea-category-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    justify-content: flex-start;
    scrollbar-width: none;
    gap: var(--space-6);
    margin: 0 -16px;
    padding: 14px 20px 20px 20px;
    box-shadow: var(--shadow-md);
    position: sticky;
    /* Same mobile reveal-on-scroll-up push-down as .itinerary-sticky-
       stack's own top rule - see its comment. Without this, .trip-
       subtabs sliding in as a fixed overlay would paint straight over
       this row instead of the two moving down together. */
    top: var(--subtabs-reveal-h, 0px);
    transition: top 0.22s ease;
    z-index: 10;
    background: #fff;
  }
  /* Same .page-header/.trip-subtabs stacking as .itinerary-sticky-stack
     (see its own comment) - this is the Ideas tab's equivalent sticky
     row, one tier below .trip-subtabs, same as Itinerary's stack is. */
  @media (min-width: 900px) {
    .idea-category-tabs {
      top: calc(var(--page-header-h, 0px) + var(--subtabs-h, 0px));
    }
  }
  .idea-category-tabs::-webkit-scrollbar {
    display: none;
  }
  .idea-category-tab {
    font-family: inherit;
    font-size: var(--text-13);
    font-weight: 700;
    color: #828282;
    background: rgba(216,199,184,0.37);
    border: none;
    border-radius: var(--radius-sm);
    padding: 6px 14px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    white-space: nowrap;
    flex-shrink: 0;
  }
  .idea-category-tab-active {
    color: white;
    background: var(--color-body);
  }
  .idea-category-tab-count {
    display: none;
    font-weight: 700;
  }
  .idea-category-tab-count:not(:empty) {
    display: inline;
  }
  /* ---- Compact icon+text row, now only used by buildEssentialsRow
     (the Essentials tab's stub rows) and buildEventRow (manual events
     in the day timeline) - the main Ideas-tab list itself moved to
     buildIdeaListCard's thumbnail/corner-tab row (see .ideas-card
     further below); buildIdeaRow, the row type this class was
     originally built for, was deleted 2026-08-05 once nothing called
     it any more. Deliberately separate from .idea-card (used by
     buildIdeaCard for the day-assigned view, which needs hours/phone/
     notes/day-toggle that this simpler row doesn't show), and a
     different class than .idea-card on purpose so the existing global
     .idea-card click-to-drop-a-map-pin delegated handler doesn't fire
     for these rows - they get their own stub tap handler instead. ---- */
  .idea-row {
    display: flex;
    gap: var(--space-14);
    align-items: flex-start;
    background: white;
    border: 1px solid transparent;
    border-radius: 22px;
    box-shadow: var(--shadow-md);
    padding: 16px 18px 18px;
    margin-bottom: 14px;
    cursor: pointer;
    /* Idle no-op until .idea-row-entering/-landed toggle (see
       _revealNewIdeaRow) - transparent border above exists just so the
       border-color transition has something real to animate from. */
    transition: background 700ms ease, border-color 700ms ease,
      transform 380ms cubic-bezier(0.2,0.9,0.3,1), opacity 380ms ease;
  }
  /* Freshly-added idea/itinerary-entry row's enter state (see
     _revealNewIdeaRow) - toggled off one frame after insertion so the
     transition above actually animates from this to rest, same
     scale/translateY/opacity recipe the Add-to-list handoff specifies. */
  .idea-row-entering {
    transform: scale(0.94) translateY(-6px);
    opacity: 0;
  }
  .idea-row-landed {
    background: rgba(240,81,46,0.08);
    border-color: rgba(240,81,46,0.45);
  }
  /* Floating clone for the desktop fly-to-itinerary animation (see
     _flyIdeaCardToItinerary) - visually matches .idea-row (the real
     card type it's about to become) rather than the transport-card
     styling the original handoff prototype used, since Places/Notes
     entries render as idea rows in this app, not transport cards. */
  .idea-fly-clone {
    position: fixed;
    z-index: 600;
    pointer-events: none;
    box-sizing: border-box;
    background: #fff;
    border: 1px solid rgba(240,81,46,0.5);
    border-radius: 22px;
    padding: 16px 18px 18px;
    box-shadow: var(--shadow-xl);
    opacity: 1;
    transition: transform 560ms cubic-bezier(0.4,0.1,0.25,1), opacity 560ms ease, width 560ms ease;
  }
  .idea-fly-clone-name {
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
  }
  .idea-fly-clone-meta {
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-muted);
    margin-top: 3px;
  }
  @media (prefers-reduced-motion: reduce) {
    .idea-fly-clone { display: none; }
    .idea-row-entering { transform: none; opacity: 1; }
    .idea-row { transition: background 700ms ease, border-color 700ms ease; }
  }
  .idea-row-icon {
    width: 38px;
    height: 38px;
    border-radius: var(--radius-circle);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    margin-top: 2px;
  }
  .idea-row-icon .material-symbols-outlined {
    font-size: var(--text-18) !important;
  }
  .idea-row-icon-others { background: #ece9e0; color: #6b6a63; }
  .idea-row-icon-essentials { background: #fdecc8; color: #b98120; }
  .idea-row-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }
  .idea-row-title {
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-ink);
  }
  .idea-row-distance {
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-muted);
  }
  /* ---- Ideas-tab list row (design_handoff_ideas_card v2, compact row
     with thumbnail - approved 1b/2a) - the default representation for
     a pending (unassigned) idea in the main Ideas list, replacing
     .idea-row for that purpose (see buildIdeaListCard/renderIdeasPanel/
     buildIdeaGroupSection). Deliberately its own class namespace, not
     .idea-card/.pdp-* - that block's own comment reserves those
     exclusively for the detail overlay (buildIdeaCard). Kept the
     .ideas-card class name from the first (full-photo-card) pass at
     this handoff even though the layout underneath changed completely -
     every selector that already targets .ideas-card (insertDistanceBadges,
     the entrance-animation dispatch in _revealNewIdeaRow) keeps working
     unmodified. ---- */
  .ideas-card {
    position: relative;
    display: flex;
    /* flex-start, not the default stretch - once the meta/note-preview
       lines below made the content column reliably taller than the
       thumbnail's fixed 92px, stretch would have grown the thumbnail
       to match (a tall photo, not 92x92) while flex-start keeps it a
       true 92x92 square pinned to the row's top edge - which is also
       what keeps the title lined up with the photo's top, direct
       feedback 2026-08-04. */
    align-items: flex-start;
    gap: var(--space-14);
    background: #FFFFFF;
    border-radius: 5px;
    padding: var(--space-12);
    box-shadow: var(--shadow-lg);
    color: var(--color-ink);
    margin-bottom: 12px;
    cursor: pointer;
  }
  /* Assigned (already-on-the-itinerary) layout: thumbnail moves to the
     right (see buildIdeaListCard's own DOM-order comment) - center-
     aligned against the content column. padding-top: 0 (content is the
     tallest of the two flex children, so centering then starts it
     flush with the row's own top) is what makes the category pill
     touch the card's top edge instead of sitting 12px below it - direct
     request 2026-08-05 (addedtoitinerary.jpg). */
  .ideas-card-assigned {
    align-items: center;
    padding-top: 0;
    /* 20px, not the base rule's 12px - direct request 2026-08-06 to
       give the time column more clearance from the card's left edge,
       and the thumbnail the same clearance from the right edge. */
    padding-left: 20px;
    padding-right: 20px;
  }
  /* Same font as .compact-row-time - time lines up with the title's own
     baseline (grid-row 2, shared with .ideas-card-title below), not
     vertically centered against the whole card. */
  .ideas-card-time-col {
    grid-column: 1;
    grid-row: 2;
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-ink);
    white-space: nowrap;
  }
  .ideas-card-thumb {
    position: relative;
    width: 92px;
    height: 92px;
    flex: none;
    border-radius: 5px;
    overflow: hidden;
    background: #F0EEE9;
  }
  .ideas-card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  .ideas-card-thumb-placeholder {
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #F3E7D6, #F0EEE9);
  }
  .ideas-card-content {
    position: relative;
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* No justify-content:space-between - with a variable number of
       children (footer-slot can be visually empty when there's no
       liked/notes info at all) that spread the free space BETWEEN
       chip and title too, opening a big gap above the title even
       though title has its own small margin-top. Found live 2026-08-04
       (pillwidth.jpg). Footer-slot's own margin-top:auto below now
       does the "stick to the bottom" job instead. No top padding on
       purpose (was 3px) - the state tag/rating row is now the flush-
       with-photo top element instead of the title (added for the
       add-to-itinerary flow), but the same "no extra top padding"
       reasoning still applies to it. */
    padding: 0 4px 4px 0;
  }
  /* Content becomes a grid once assigned - same column/row-gap/baseline
     pattern the pipeline event cards' own .compact-row-content already
     uses (time in column 1 lined up with the title's own line; pill,
     title, address, meta, note preview and footer all share column 2),
     rather than inventing separate flex/margin spacing here - direct
     request 2026-08-05 to follow that existing convention. Declared
     after the base .ideas-card-content rule above (same specificity,
     so source order decides) specifically so display: grid wins over
     that rule's own display: flex. Column-gap is a flat 30px, same as
     .compact-row-content - no wider desktop breakpoint any more, direct
     request 2026-08-06 (content sat too far right of the time column
     at desktop width).

     Column 1 is a fixed 56px, NOT auto like .compact-row-content's own
     version of this grid - each .ideas-card is its OWN independent grid
     (unlike a spreadsheet, sibling grids never share track sizing), so
     auto would size column 1 to THAT card's own time string alone. Two
     cards on the same day with differently-wide times ("12:00 PM" vs
     "3:00 PM" - one digit shorter) then get two different column 1
     widths, and everything in column 2 (pill/title/address) starts at
     a different x on each card - found live 2026-08-06 via a straight
     vertical guide line held up against two stacked cards. 56px fits
     the longest real case ("12:00 PM", measured ~53px) with a little
     headroom. */
  .ideas-card-content-assigned {
    display: grid;
    grid-template-columns: 56px 1fr;
    column-gap: 30px;
    row-gap: var(--space-10);
    align-items: baseline;
  }
  /* Normal flex child of .ideas-card-top-row now (alongside the state
     tag) rather than absolutely positioned over the title - see that
     row's own CSS comment. */
  .ideas-card-rating {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 800;
    color: var(--color-ink);
  }
  /* Plain Unicode star (see buildIdeaListCard/_renderIdeaCardFooter's
     own comment) instead of the material-symbols-outlined icon font -
     that font is pinned to a single static FILL=0 instance, so it can
     never actually render solid via a FILL:1 override despite the old
     rule here setting one. line-height: 1 keeps the glyph's own extra
     vertical whitespace from throwing off .ideas-card-rating's
     align-items: center against the rating number next to it. */
  .ideas-card-rating-star {
    font-size: var(--text-14);
    line-height: 1;
    color: #F9B723;
  }
  /* Notes count, next to the like count in the footer - direct request
     2026-08-06. Same size/weight/color language as .ideas-card-liked-
     label (the like count's own number), so the two read as one
     consistent "counts" row rather than two differently-styled bits. */
  .ideas-card-notes-count {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-muted);
    cursor: pointer;
  }
  .ideas-card-notes-count .material-symbols-outlined {
    font-size: var(--text-14) !important;
  }
  .ideas-card-footer-slot {
    margin-top: auto;
    padding-top: 6px;
  }
  /* Assigned: this slot is an auto-flowed grid row now (see
     .ideas-card-content-assigned and its comment on why grid-row is
     deliberately omitted here, same as meta/note-preview above), not
     the last child of a flex column, so the flex-only "stick to the
     bottom" margin-top: auto/padding-top above don't apply. margin-top:
     5px stacks on top of the grid's own 10px row-gap for a 15px total
     gap above the footer specifically (was 20px/10px+10px, brought in
     another 5px by direct request 2026-08-06) - still wider than the
     10px title-to-address gap. */
  .ideas-card-content-assigned .ideas-card-footer-slot {
    grid-column: 2;
    margin-top: 5px;
    padding-top: 0;
  }
  .ideas-card-title {
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-16);
    line-height: 1.25;
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ideas-card-content-assigned .ideas-card-title {
    grid-column: 2;
    grid-row: 2;
    /* Full title, no ellipsis truncation - direct request 2026-08-06;
       pending stays single-line/ellipsis (base rule above), unchanged. */
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  /* Address line, right after the title - font size/weight/color now
     match the pipeline event cards' own compact-row address exactly
     (see .compact-row-subtitle), per direct request; leading copy
     button (copy-address-btn-leading, same shared class/click handler
     the pipeline cards' own address copy button already uses) replaces
     the old plain location_on icon. */
  .ideas-card-address {
    display: flex;
    align-items: center;
    gap: 0;
    font-size: var(--text-14);
    font-weight: 500;
    color: var(--color-muted);
    margin-top: 2px;
    min-width: 0;
  }
  .ideas-card-address-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Assigned: same hanging-indent block .compact-row-subtitle-address
     uses (padding-left pushes the whole block right by the leading
     copy icon's own width + gap, text-indent pulls line 1 back by the
     same amount so the icon sits at the column's left edge - a wrapped
     second line has no icon to pull back for, so it naturally lands
     indented, lined up with line 1's TEXT) instead of the flex row
     .ideas-card-address uses elsewhere - lets the full address wrap
     and be underlined/clickable-to-copy end to end, not just the
     leading icon. Direct request 2026-08-05 (addedtoitinerary.jpg). */
  .ideas-card-address-assigned {
    display: block;
    grid-column: 2;
    margin-top: 0;
    padding-left: 26px;
    text-indent: -26px;
  }
  /* Assigned: the full address text is itself underlined and
     clickable-to-copy (not just the leading icon button), and wraps
     instead of ellipsis-truncating since the row has the vertical
     room now that the thumbnail moved out of this column - direct
     request 2026-08-05 (addedtoitinerary.jpg). */
  .ideas-card-address-text-assigned {
    overflow: visible;
    white-space: normal;
    text-decoration: underline;
    cursor: pointer;
  }
  /* Distance meta line (design_handoff_ideas_card v1 had this, dropped
     in the v2 compact-row rewrite, restored 2026-08-04 by direct
     request) - dual-classed with idea-card-distance so the existing
     insertDistanceBadges/sortIdeaListByDistance (unchanged, already
     targets .ideas-card via its own selector) fill this in with real
     "X mi from your hotel"/"...home"/"...your destination" text -
     tripAnchorLabel already picks whichever of those three is right
     for this trip, covering the hotel/destination/event cases asked
     for without a new per-case branch here. */
  .ideas-card-meta {
    display: block;
    font-size: var(--text-12);
    font-weight: 600;
    color: var(--color-muted);
    margin-top: 2px;
  }
  /* grid-row deliberately omitted here (unlike the pill/time/title
     rows above, which need to line up in lockstep with column 1) -
     auto-placement slots this into the next free row in column 2,
     which is what actually lets a display:none (see :empty below)
     meta/note-preview skip its row entirely, not just collapse to 0
     height. An explicit grid-row number here was tried first and
     didn't work: the grid still reserves that row's own row-gap on
     both sides regardless of the row's content height, even at 0px -
     found live 2026-08-06 (the footer sat 40px below the address,
     not the intended 20px, because rows 4 and 5 still existed as
     empty gap-only tracks). */
  .ideas-card-content-assigned .ideas-card-meta {
    grid-column: 2;
    margin-top: 0;
  }
  .ideas-card-content-assigned .ideas-card-note-preview-slot {
    grid-column: 2;
  }
  /* :empty (no distance badge / no note yet) removes the item from
     auto-placement entirely (see the comment above) - direct request
     2026-08-06 that the footer (likes/rating) sit close to the
     address, not with the extra gap two empty in-between rows were
     otherwise adding. insertDistanceBadges/_renderIdeaCardFooter
     append real child nodes once they have something to show, which
     is what un-matches :empty again. */
  .ideas-card-content-assigned .ideas-card-meta:empty,
  .ideas-card-content-assigned .ideas-card-note-preview-slot:empty {
    display: none;
  }
  /* Latest note preview (also restored 2026-08-04, adapted for the
     compact row: a single ellipsis-clamped line rather than v1's
     2-line note strip with avatar/byline - there isn't the vertical
     room here). Tapping it opens the same note thread as the notes
     count chip - see buildIdeaListCard/openIdeaNotesThread. margin-top
     bumped from 3px to 9px (direct feedback 2026-08-04) for clearer
     separation from the distance line right above it. */
  .ideas-card-note-preview {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    font-size: var(--text-13);
    color: var(--color-body);
    margin-top: 9px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
  }
  .ideas-card-note-preview .material-symbols-outlined {
    font-size: var(--text-13) !important;
    color: var(--color-muted);
    flex: none;
  }
  .ideas-card-note-preview-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* flex-end, not space-between - direct feedback 2026-08-04 that the
     heart/vote button (buildVoteButton) duplicated the same "liked"
     info the face-pile/count already showed, so the heart was removed
     and the face-pile+count group is now the footer's only occupant,
     moved to sit at the bottom-right instead of the left. */
  .ideas-card-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-8);
  }
  /* Assigned: left-aligned (flush with the title/address copy icon
     above, instead of pushed to the row's right edge) with a wide gap
     before the rating - direct request 2026-08-05
     (addedtoitinerary.jpg). */
  .ideas-card-footer-assigned {
    justify-content: flex-start;
    gap: 50px;
  }
  .ideas-card-like-group {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    min-width: 0;
  }
  .ideas-card-face-pile {
    display: flex;
    flex: none;
  }
  .ideas-card-face {
    width: 18px;
    height: 18px;
    border-radius: var(--radius-circle);
    border: 2px solid #fff;
    margin-left: -8px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: var(--text-10);
    font-weight: 700;
  }
  .ideas-card-face:first-child {
    margin-left: 0;
  }
  .ideas-card-face-plus {
    background: #F3E7D6;
    color: var(--color-body);
  }
  .ideas-card-liked-label {
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* ---- Add-to-itinerary flow (row-level) - state tag + rating share a
     top row so neither has to be absolutely positioned over the title
     the way .ideas-card-rating used to be on its own; the itinerary
     button/pills sit between the note preview and the footer; the
     three-dot menu (assigned ideas only) anchors to the whole card's
     own corner, not the content column, so it clears the thumbnail. ---- */
  .ideas-card-top-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-8);
    width: 100%;
  }
  /* Assigned: rating no longer lives in this row (moved to the footer,
     see _renderIdeaCardFooter) - flex-start keeps the pill aligned to
     the same left edge as the title/address below it, not centered
     across the row. grid-row/-column place it in .ideas-card-content-
     assigned's own grid (see that rule) instead of a margin hack - the
     grid's row-gap alone now supplies the gap down to the title.
     Direct request/correction 2026-08-05 (addedtoitinerary.jpg). */
  .ideas-card-top-row-assigned {
    justify-content: flex-start;
    grid-column: 2;
    grid-row: 1;
  }
  .ideas-card-top-row-left {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    flex-wrap: wrap;
  }
  .ideas-card-private-icon {
    font-size: var(--text-15);
    color: var(--color-muted);
  }
  .ideas-card-menu {
    position: absolute;
    top: 6px;
    right: 6px;
  }
  .ideas-card-menu-btn {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-circle);
    background: rgba(255,255,255,.85);
    color: var(--color-ink);
    cursor: pointer;
    box-shadow: var(--shadow-md);
  }
  .ideas-card-menu-btn .material-symbols-outlined {
    font-size: var(--text-18) !important;
  }
  .ideas-card-menu-popover {
    display: none;
    position: absolute;
    top: 32px;
    right: 0;
    min-width: 200px;
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-6);
    z-index: 5;
  }
  .ideas-card-menu-open .ideas-card-menu-popover {
    display: block;
  }
  .ideas-card-menu-item {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    width: 100%;
    border: none;
    background: none;
    text-align: left;
    font-family: inherit;
    font-size: var(--text-14);
    font-weight: 600;
    color: var(--color-ink);
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
  }
  .ideas-card-menu-item:hover {
    background: #F7F4EF;
  }
  .ideas-card-menu-item .material-symbols-outlined {
    font-size: var(--text-18) !important;
    color: var(--color-body);
  }
  .ideas-card-reservation-badge {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    font-size: var(--text-11);
    font-weight: 700;
    color: #3E5C3E;
    background: #E3EDE3;
    border-radius: var(--radius-pill);
    padding: 4px 8px 4px 6px;
    white-space: nowrap;
  }
  .ideas-card-reservation-badge .material-symbols-outlined {
    font-size: var(--text-13) !important;
  }
  /* ---- Entrance animation (design_handoff_ideas_card v2's Add
     Animation canvas, "2a" - the approved unfold sequence applied to
     the compact row) - plays once via _revealNewIdeaRow adding/removing
     .ideas-card-entering right after a freshly-saved idea's row exists
     in the DOM, same "poll then toggle a class" idiom .idea-row-
     entering/-landed already used, just with real @keyframes since the
     handoff staggers three children on their own delays.
     Non-interactive for the duration via pointer-events: none. ---- */
  .ideas-card-entering {
    animation: ideasCardUnfold 440ms cubic-bezier(.3,1,.4,1) both;
    pointer-events: none;
  }
  @keyframes ideasCardUnfold {
    0% { max-height: 0; opacity: 0; transform: scaleY(.9); transform-origin: top; }
    45% { opacity: 1; }
    100% { max-height: 420px; opacity: 1; transform: scaleY(1); transform-origin: top; }
  }
  .ideas-card-entering .compact-row-tag {
    opacity: 0;
    animation: ideasCardLabelTab 280ms ease-out 280ms both;
  }
  @keyframes ideasCardLabelTab {
    0% { opacity: 0; transform: translateX(-16px); }
    100% { opacity: 1; transform: translateX(0); }
  }
  /* Address/meta/note-preview grouped with the title's own reveal (same
     delay/duration) - the approved 2a timeline only specifies 3 stops
     (chip, title, footer); these are new title-adjacent lines restored
     2026-08-04, not stops of their own. */
  .ideas-card-entering .ideas-card-title,
  .ideas-card-entering .ideas-card-address,
  .ideas-card-entering .ideas-card-meta,
  .ideas-card-entering .ideas-card-note-preview {
    opacity: 0;
    animation: ideasCardContentRise 300ms ease-out 360ms both;
  }
  .ideas-card-entering .ideas-card-footer {
    opacity: 0;
    animation: ideasCardContentRise 340ms ease-out 460ms both;
  }
  @keyframes ideasCardContentRise {
    0% { opacity: 0; transform: translateY(12px); }
    100% { opacity: 1; transform: translateY(0); }
  }
  @media (prefers-reduced-motion: reduce) {
    .ideas-card-entering {
      animation: ideasCardFadeIn 150ms ease both;
    }
    .ideas-card-entering .compact-row-tag,
    .ideas-card-entering .ideas-card-title,
    .ideas-card-entering .ideas-card-address,
    .ideas-card-entering .ideas-card-meta,
    .ideas-card-entering .ideas-card-note-preview,
    .ideas-card-entering .ideas-card-footer {
      animation: none;
      opacity: 1;
      transform: none;
    }
  }
  @keyframes ideasCardFadeIn {
    0% { opacity: 0; }
    100% { opacity: 1; }
  }
  /* 2026-08-04: moved from a fixed bottom-right FAB (over the scrolling
     list) to a pill above the list itself, per explicit request -
     easier to find/reach than a floating button that could sit behind
     the on-screen keyboard or a card overlay. Then moved again the
     same day up above .idea-signin-prompt and wrapped in .idea-add-row
     (below) for actual top-right alignment - it wasn't really
     right-aligned before, just alone on its own block-level line.
     Still the same name (idea-sticky-add-btn) since nothing else about
     it changed - only its position in IDEAS_PANEL_TEMPLATE and this
     rule. */
  .idea-add-row {
    display: flex;
    justify-content: flex-end;
  }
  .idea-sticky-add-btn {
    margin: 12px 0 4px;
    box-shadow: 0px 6px 10px rgba(240,81,46,0.25);
  }
  .idea-list-empty {
    color: var(--color-muted);
    font-size: var(--text-14);
    font-style: italic;
  }
  /* "Fanned cards" illustrated empty state (design handoff: Ideas tab -
     empty state, option 14b) - see buildIdeaEmptyState's own JS comment
     for what's deliberately not carried over from the design reference
     (bottom tab bar, header chrome - neither exists in this app). Colors
     below are the handoff's own tokens used verbatim - #33261e/#9c8f82/
     #f0512e already match this file's existing ink/muted/accent literals
     exactly, #5c8a6e green already matches an existing activity-category
     color elsewhere in this file; #5b7fa6 blue is new to this file but
     doesn't collide with anything. */
  .idea-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 36px 6px 12px;
    /* Shifted down 120px from its plain in-flow position, per direct
       feedback - the fanned-card illustration read as too close to the
       category chips/search bar above it. */
    margin-top: 120px;
    text-align: center;
  }
  .idea-empty-stack {
    position: relative;
    width: 250px;
    height: 186px;
    margin-bottom: 8px;
  }
  .idea-empty-card {
    position: absolute;
    opacity: 1;
  }
  .idea-empty-card-a { left: 6px; top: 24px; width: 150px; height: 110px; }
  .idea-empty-card-b { right: 2px; top: 16px; width: 150px; height: 110px; }
  .idea-empty-card-c { left: 46px; bottom: 0; width: 158px; height: 116px; }
  .idea-empty-card-inner {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    border-radius: 22px;
    background: #fff;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: var(--space-14);
  }
  .idea-empty-card-inner-a { box-shadow: var(--shadow-lg); transform: rotate(-11deg); --r: -11deg; }
  .idea-empty-card-inner-b { box-shadow: var(--shadow-lg); transform: rotate(10deg); --r: 10deg; }
  .idea-empty-card-inner-c { padding: var(--space-16); box-shadow: var(--shadow-xl); }
  .idea-empty-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .idea-empty-card-icon { font-size: var(--text-24); }
  .idea-empty-card-inner-c .idea-empty-card-icon { font-size: var(--text-24); }
  .idea-empty-card-icon-blue { color: #5b7fa6; }
  .idea-empty-card-icon-green { color: #5c8a6e; }
  .idea-empty-card-icon-accent { color: var(--color-accent); }
  .idea-empty-card-plus {
    width: 22px;
    height: 22px;
    border-radius: var(--radius-circle);
    background: #fbf3e7;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .idea-empty-card-plus .material-symbols-outlined {
    font-size: var(--text-13);
    font-weight: 700;
    color: var(--color-accent);
  }
  .idea-empty-bar {
    height: 8px;
    border-radius: 4px;
    background: rgba(51,38,30,0.12);
  }
  .idea-empty-bar-2 {
    margin-top: 6px;
    background: rgba(51,38,30,0.07);
  }
  .idea-empty-card-inner-c .idea-empty-bar { height: 9px; }
  .idea-empty-card-inner-c .idea-empty-bar-1 { background: rgba(51,38,30,0.14); }
  .idea-empty-card-inner-c .idea-empty-bar-2 { margin-top: 7px; background: rgba(51,38,30,0.08); }
  .idea-empty-headline {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-24);
    font-weight: 800;
    color: var(--color-ink);
    margin-top: 22px;
  }
  .idea-empty-body {
    font-size: var(--text-15);
    line-height: 1.5;
    font-weight: 500;
    color: var(--color-muted);
    max-width: 266px;
    margin-top: 8px;
  }
  .idea-empty-cta-btn {
    margin-top: 22px;
    box-shadow: 0 10px 24px rgba(240,81,46,0.3);
  }
  .idea-empty-cta-btn .material-symbols-outlined {
    font-size: var(--text-18);
    font-weight: 700;
  }
  .idea-empty-cta-btn:active {
    transform: scale(0.97);
    /* Design-audit fix 2026-08-07: darkening added for consistency
       with the rest of the primary-CTA family's own :active state. */
    filter: brightness(0.88);
    box-shadow: 0 6px 16px rgba(240,81,46,0.25);
  }
  /* .ideas-panel-empty is toggled by renderIdeasPanel alongside
     buildIdeaEmptyState - the "+ Add to list" pill above the list would
     otherwise sit redundantly right next to this empty state's own
     centered CTA, which the design calls out as "the only action" here.
     Comes back the moment the trip has its first real idea. */
  .ideas-panel-empty .idea-sticky-add-btn {
    display: none;
  }
  /* ---- Entrance (plays once per trip on first mount - see
     _ideaEmptyStatePlayed in the JS) ---- */
  @keyframes idea-empty-dealin {
    0% { opacity: 0; transform: translateY(40px) scale(0.84); }
    70% { transform: translateY(-4px) scale(1.02); }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }
  @keyframes idea-empty-fadeup {
    0% { opacity: 0; transform: translateY(14px); }
    100% { opacity: 1; transform: translateY(0); }
  }
  .idea-empty-deal-a { animation: idea-empty-dealin 720ms cubic-bezier(.2,1.3,.4,1) 150ms both; }
  .idea-empty-deal-b { animation: idea-empty-dealin 720ms cubic-bezier(.2,1.3,.4,1) 500ms both; }
  .idea-empty-deal-c { animation: idea-empty-dealin 780ms cubic-bezier(.2,1.35,.4,1) 850ms both; }
  .idea-empty-up-1 { animation: idea-empty-fadeup 600ms ease-out 1150ms both; }
  .idea-empty-up-2 { animation: idea-empty-fadeup 600ms ease-out 1300ms both; }
  .idea-empty-up-3 { animation: idea-empty-fadeup 600ms ease-out 1450ms both; }
  /* ---- Ambient (loops forever once the entrance above clears -
     deliberately mismatched 6.4/7.2/5.6s durations so the three cards
     never sync up; applied to the INNER card element so it composes
     with the wrapper's own entrance transform above instead of the two
     fighting each other) ---- */
  @keyframes idea-empty-float {
    0%, 100% { transform: translateY(0) rotate(var(--r,0deg)); }
    50% { transform: translateY(-10px) rotate(var(--r,0deg)); }
  }
  @keyframes idea-empty-floatb {
    0%, 100% { transform: translateY(-8px) rotate(var(--r,0deg)); }
    50% { transform: translateY(4px) rotate(var(--r,0deg)); }
  }
  @keyframes idea-empty-breathe {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.06); }
  }
  .idea-empty-amb-a { animation: idea-empty-float 6.4s ease-in-out 1.3s infinite; }
  .idea-empty-amb-b { animation: idea-empty-floatb 7.2s ease-in-out 1.6s infinite; }
  .idea-empty-amb-c { animation: idea-empty-breathe 5.6s ease-in-out 1.9s infinite; }
  /* No CSS-layer prefers-reduced-motion block here on purpose - unlike
     the file's other reduce-motion rules (e.g. the one right after
     :root above), these deal-in/fade-up/amb-* classes are only ever
     added by buildIdeaEmptyState's own _prefersReducedMotion() JS check
     in the first place (same JS-only gating _revealNewIdeaRow uses for
     .idea-row-entering/-landed), so there's nothing for a CSS media
     query to strip - and forcing transform:none here would wrongly
     cancel .idea-empty-card-inner-a/b's static tilt too. */
  /* One section per destination on a multi-city trip (see
     _ideaGroupCities/buildIdeaGroupSection) - mirrors .expense-day-header's
     look so grouping reads as the same pattern the Expenses tab already
     uses for its own day sections, just keyed by city instead of day. */
  .idea-group-section + .idea-group-section {
    margin-top: 20px;
  }
  .idea-group-header {
    font-family: 'Baloo 2', sans-serif;
    font-size: var(--text-13);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-body);
    margin-bottom: 8px;
  }
  .idea-form-status {
    font-size: var(--text-12);
    font-style: italic;
    color: #6b6a63;
    flex-basis: 100%;
  }
  /* A resolved link (see attemptAddOrReveal) is real progress, not a
     minor caption - the plain italic-gray treatment above read as too
     easy to miss/ignore right after a successful lookup. */
  .idea-form-status-success {
    font-size: var(--text-13);
    font-style: normal;
    font-weight: 700;
    color: #4a7c59;
  }
  .idea-name-row {
    display: flex;
    gap: var(--space-6);
    flex-basis: 100%;
  }
  .idea-name-autocomplete-wrap {
    position: relative;
    flex: 1;
  }
  .idea-name-autocomplete-wrap .idea-input-title {
    width: 100%;
  }
  .idea-autocomplete-list {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #fff;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    margin-top: 2px;
    /* Taller than before (was 160px) - each result is two lines now
       (name + address, see .idea-autocomplete-item), not one, so the
       old height only fit ~3 results before scrolling. */
    max-height: 240px;
    overflow-y: auto;
    z-index: 10;
    box-shadow: var(--shadow-md);
  }
  /* Two lines per result (name, then its full address) instead of one
     truncated combined string - real feedback was that the place's
     name alone wasn't enough to tell two similarly-named results
     apart without seeing where each actually was. border-bottom (not
     on the last item - see :last-child below) is the "separated by a
     grey line" divider between results; the address's own :empty rule
     collapses that second line away entirely for a result with no
     secondary_text (see fetchAutocomplete's own fallback), rather than
     leaving a blank line. */
  .idea-autocomplete-item {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: 8px 10px;
    font-size: var(--text-13);
    cursor: pointer;
    border-bottom: 1px solid #eae7e0;
  }
  .idea-autocomplete-item:last-child {
    border-bottom: none;
  }
  .idea-autocomplete-item:hover, .idea-autocomplete-item-active {
    background: #f2f0e9;
  }
  .idea-autocomplete-item-top-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-8);
  }
  .idea-autocomplete-item-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
  }
  .idea-autocomplete-item-address {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-12);
    color: var(--color-muted);
  }
  .idea-autocomplete-item-address:empty {
    display: none;
  }
  .idea-autocomplete-item-distance {
    flex-shrink: 0;
    font-size: var(--text-12);
    font-weight: 600;
    color: var(--color-muted);
  }
  .idea-lookup-btn {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 10px;
    font-size: var(--text-13);
    color: var(--color-accent);
    cursor: pointer;
    white-space: nowrap;
  }
  /* Add-to-list presentation (see openIdeaAddOverlay) - a small
     centered dialog card, not the top-anchored full-height panel the
     idea-detail/itinerary-card overlays use (see .card-overlay-panel's
     own comment on why THAT stays top-anchored: real Safari's
     margin:auto-in-a-scroller bug). This dialog is short and never
     scrolls, so simple flex centering on .card-overlay itself is safe
     here without hitting that bug - scoped to .card-overlay-dialog-mode
     so it doesn't change the other overlay types' layout at all. */
  .card-overlay-dialog-mode {
    justify-content: center;
  }
  .card-overlay-dialog-mode .card-overlay-panel {
    max-width: 340px;
  }
  /* Only this scrim gets the frosted-glass look (per the Figma file) -
     scoped the same way, rather than blurring the background behind
     every overlay type. */
  body.card-overlay-open.card-overlay-dialog-open #card-overlay-scrim {
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
  }
  /* True bottom-sheet presentation (see openWeatherForecastOverlay) -
     flush to the screen's bottom/side edges (no gap the way the
     default padded/centered overlay has), sliding up from the bottom
     edge rather than fading+scaling in place (the "standard sheet
     slide-up" the Weather screen spec asked for, not a new animation
     style invented for it - .card-overlay-panel's existing
     transform/opacity transition above is simply replaced by a
     translateY one here, same duration). The sheet's own close button
     (.weather-sheet-close, inside the cloned template content) is
     used instead of the default floating one, which this hides. */
  .card-overlay.card-overlay-sheet-mode {
    justify-content: flex-end;
    padding: 0;
  }
  /* Real bug found live 2026-08-06: this used to hide the generic
     close button for EVERY sheet-mode overlay, not just the weather
     sheet it was written for - openIdeaDetailOverlay (buildIdeaCard)
     also uses card-overlay-sheet-mode on mobile, and its own
     .pdp-drag-handle is purely decorative (no click handler, no real
     swipe-to-dismiss wired to it), so an idea added to the itinerary
     had literally no way to close its detail sheet on mobile - "no way
     to close and hard to scroll," reported live, was really just "no
     way to close" (fighting a stuck modal reads as a scroll problem
     too). :has() scopes the hiding to only the weather sheet, which
     genuinely has its own working close button - every other
     sheet-mode overlay now keeps the default floating X. */
  .card-overlay-sheet-mode:has(.weather-sheet-close) .card-overlay-close {
    display: none;
  }
  .card-overlay-sheet-mode .card-overlay-panel {
    max-width: none;
    transform: translateY(100%);
    opacity: 1;
  }
  .card-overlay-sheet-mode.card-overlay-visible .card-overlay-panel {
    transform: translateY(0);
  }
  /* Right-side drawer presentation (see openIdeaAddOverlay's width
     branch and openAccountOverlay, which now applies this at every
     width per direct feedback - see that function's own comment) -
     same "flex-end + no padding" trick as .card-overlay-sheet-mode
     above, just sliding in from the right edge instead of up from the
     bottom, per the Itinerary "Add to list" drawer handoff. Width is
     `100%` capped at `max-width: 432px` (not a fixed 432px) so it
     doesn't overflow a phone-width viewport now that it's no longer
     gated to >=900px - Add to List's own desktop-only behavior is
     unaffected since its own class-application is still gated by
     openIdeaAddOverlay's own isDesktopWidth check, not by this CSS. */
  .card-overlay.card-overlay-drawer-mode {
    /* .card-overlay's own base layout is flex-direction:column, so
       justify-content controls the VERTICAL axis and align-items
       controls the HORIZONTAL one - the opposite of what the sheet-
       mode rule above needs (bottom-alignment, correctly the main/
       vertical axis) vs. what a RIGHT-edge drawer needs (horizontal).
       align-items:flex-end is what actually pins this to the right
       edge instead of the base rule's inherited align-items:center,
       which was silently centering the drawer instead of docking it. */
    align-items: flex-end;
    padding: 0;
  }
  .card-overlay-drawer-mode .card-overlay-panel {
    max-width: 432px;
    width: 100%;
    height: 100%;
    height: 100dvh;
    transform: translateX(100%);
    opacity: 1;
    background: #FFFFFF;
    box-shadow: -24px 0 60px rgba(74,63,53,0.18);
    display: flex;
    flex-direction: column;
    /* transition inherited from .card-overlay-panel (unified 300ms +
       --ease-decel) - was a one-off 320ms cubic-bezier(0.22,0.61,0.36,1). */
  }
  .card-overlay-drawer-mode.card-overlay-visible .card-overlay-panel {
    transform: translateX(0);
  }
  .idea-add-dialog-title {
    display: none;
    flex-basis: 100%;
    font-family: 'Baloo 2', sans-serif;
    font-weight: 800;
    font-size: var(--text-24);
    color: #4A3F35;
    text-align: left;
    /* White patch above the grey line (see .idea-form-tabs's own
       border-bottom) - full-bleed edge to edge AND up over the panel's
       own top padding (see .idea-form-dialog-mode's 24px), not just
       side to side - a horizontal-only bleed left that top padding
       showing the cream base color above the title text instead of
       white. Padding here restores the visual top/side gap the
       negative margin ate; border-radius matches the panel's own top
       corners (28px) so the white patch's corners round together with
       the card instead of poking a square corner out of a round one. */
    background: #FFFFFF;
    border-radius: 28px 28px 0 0;
    margin: -24px -22px 0;
    padding: 24px 22px 16px;
  }
  /* Only shown/laid out this way while the form is inside the shared
     overlay - dialog card by default, right-side drawer at desktop
     widths (see the .card-overlay-drawer-mode override right below and
     openIdeaAddOverlay toggling this class on the form itself). The
     same <form> node also gets moved into the plain .idea-form-slot
     between opens, where none of this should apply. */
  .idea-form-dialog-mode {
    /* Cream below the grey line (see .idea-add-dialog-title's own
       white patch above it) - the base color for the whole panel;
       only the title carves out its own white area on top of it. */
    background: #FBF7F0;
    border-radius: var(--radius-5xl);
    padding: 24px 22px;
    box-shadow: var(--shadow-xl);
    display: flex;
    flex-wrap: wrap;
    /* The base .idea-form rule (same element, always also carries that
       class - see openIdeaAddOverlay) sets gap:6px for its own,
       unrelated context (the old inline idea-card-editing form) -
       equal specificity to this rule, so without an explicit override
       here it silently wins and adds 6px between every wrapped "row"
       (title-to-tabs, tabs-to-city-select, etc.), on top of each row's
       own explicit margin - exactly the extra gap that made "50px from
       the grey line" measure as ~56-76px instead. */
    gap: 0;
    /* Without this, the default align-content:stretch distributes any
       EXTRA vertical space (there's plenty once this becomes flex:1
       filling the full drawer height - see the drawer-mode override
       right below) evenly across each wrapped 100%-wide "row" (title,
       tabs, fields), stretching gaps in between them instead of
       packing everything at the top. */
    align-content: flex-start;
  }
  .idea-form-dialog-mode .idea-add-dialog-title {
    display: flex;
    align-items: center;
    gap: var(--space-10);
  }
  /* Frame 2/3's back arrow (see resetIdeaFormBackToSearch) - hidden via
     its own inline style until a place starts resolving, so it just
     appears alongside the existing title text rather than needing a
     separate reserved-space layout for the Frame 1 case. */
  .idea-form-back-btn {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-circle);
    border: none;
    background: #F5EFE1;
    color: #4A3F35;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
  }
  .idea-form-back-btn .material-symbols-outlined {
    font-size: var(--text-18);
  }
  .idea-form-dialog-mode .idea-submit-row {
    margin-top: 4px;
  }
  /* The card treatment above is right for a small centered dialog,
     wrong for a full-height edge panel (no rounded corners, no
     floating shadow, needs its own internal scroll instead of the
     shared overlay's page-level one). .card-overlay-content is the
     flex link between the fixed-height panel and whatever's inside it
     (Add to List's form, or ACCOUNT_OVERLAY_TEMPLATE's own body) so
     THAT content's own overflow-y is what actually scrolls, not the
     whole drawer sliding its top out of view - unconditional now that
     the drawer applies at every width, not just >=900px. */
  .card-overlay-drawer-mode .card-overlay-content {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  /* Add to List's drawer now applies at every width (openIdeaAddOverlay
     widened this per direct feedback, see that function's own comment) -
     this override used to be gated to >=900px from back when drawer-mode
     itself was desktop-only, but was never widened to match afterward,
     which meant every narrow-viewport open of this form (where most real
     usage happens) silently kept the OLD small-centered-dialog treatment
     (rounded corners + floating shadow, the base .idea-form-dialog-mode
     rule above) floating inside the full-height edge-docked drawer
     panel instead of the flush square-cornered drawer look -
     unconditional now to match .card-overlay-content right above.
     Also switches from the old flex-wrap "stack of 100%-wide rows"
     model to a real flex column: .idea-form-scroll-body (see below,
     inside .idea-form-tab-panel[places]) is what actually grows/scrolls
     now, so the overflow-y:auto this rule used to carry moves there
     instead of sitting on the whole form - that's what let the submit
     button scroll away with everything else, the thing this block
     exists to fix. */
  .card-overlay-drawer-mode .idea-form-dialog-mode {
    background: #FBF7F0;
    border-radius: 0;
    box-shadow: none;
    padding: var(--space-26);
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    flex: 1;
    min-height: 0;
    box-sizing: border-box;
  }
  /* Title/tab bar/visibility-row keep their own natural height instead
     of competing with the tab-panel below for the column's available
     space - all three still carry flex-basis:100% from the old flex-wrap
     row layout (meant "full row width" there), which would otherwise
     mean "100% of the column's height" now that the parent above is
     flex-direction:column. Real bug found live-testing (2026-08-06):
     .idea-visibility-row was added after this rule and left out of it -
     its own flex-basis:100% expanded to fill the ENTIRE drawer height in
     this mode, pushing every actual form field (search, links, etc. -
     everything inside .idea-form-tab-panel) out of view below the fold,
     so the drawer looked like it opened to a blank cream panel with just
     the checkbox at the top. */
  .card-overlay-drawer-mode .idea-add-dialog-title,
  .card-overlay-drawer-mode .idea-form-tabs,
  .card-overlay-drawer-mode .idea-visibility-row {
    flex: 0 0 auto;
  }
  /* Drawer's own 26px padding on every side (vs. the dialog card's
     24px top/22px sides) and square corners (border-radius:0, not
     28px) - same reasoning as .idea-form-tabs's own drawer override
     right below: the title's white full-bleed patch has to match
     whichever padding/corner context it's actually in. */
  .card-overlay-drawer-mode .idea-add-dialog-title {
    border-radius: 0;
    margin: -26px -26px 0;
    padding: 26px 26px 16px;
  }
  /* Drawer's own 26px side padding (vs. the dialog card's 22px) - the
     tab bar's full-bleed negative margin (see .idea-form-tabs) has to
     match whichever padding context it's actually sitting in, or it
     leaves a thin unfilled strip on each side instead of reaching
     the drawer's real edges. */
  .card-overlay-drawer-mode .idea-form-tabs {
    margin: 0 -26px 16px;
    padding: 0 26px;
  }
  /* Whichever tab-panel is actually visible (switchIdeaFormTab sets the
     other to display:none, so only one ever really competes for space
     here) is the column's one participating child - min-height:0 lets
     it shrink below its content's natural height instead of forcing
     the drawer to grow past its own fixed 100dvh, and overflow-y:auto
     is a scroll fallback if content ever runs long. */
  .card-overlay-drawer-mode .idea-form-tab-panel {
    min-height: 0;
    overflow-y: auto;
  }
  /* Notes tab specifically (label+textarea+detection chips+2 buttons,
     no separate scroll-body/pinned-footer split the way Places gets
     below) stays exactly as it always looked - hugging its own content
     height, Save button right under the textarea - rather than the
     general rule above's flex-basis:100% stretching it to fill the
     entire remaining drawer height (which it would otherwise do simply
     for being the sole participating child whenever Notes is the active
     tab) and shoving its buttons down to the very bottom. max-height
     100% + the general rule's overflow-y:auto above is still the
     fallback if this content ever runs long enough to need it. */
  .card-overlay-drawer-mode .idea-form-tab-panel[data-idea-tab-panel="notes"] {
    flex: 0 0 auto;
    max-height: 100%;
  }
  /* Pinned-footer layout for the Places tab specifically (see
     .idea-form-scroll-body/IDEA_ADD_CONTROLS_TEMPLATE) - a real sticky/
     position:sticky footer was tried and reverted elsewhere in this file
     (see .expense-submit-footer's own history) because .card-overlay-
     panel's permanent transform:scale() open/close animation breaks
     position:sticky specifically on iOS Safari (fine on desktop Chrome).
     This gets the same fixed-footer visual result a different way: this
     tab-panel becomes its own flex column (overriding the general rule
     right above), its scrollable content lives in .idea-form-scroll-body
     (the thing that actually grows/scrolls, not the panel itself - see
     next rule, kept plain overflow-y:visible here to avoid a redundant
     nested scrollbar), and .idea-submit-row sits after it as a plain,
     never-scrolling trailing flex child. No position:sticky anywhere
     in this chain. */
  .card-overlay-drawer-mode .idea-form-tab-panel[data-idea-tab-panel="places"] {
    flex-wrap: nowrap;
    flex-direction: column;
    flex: 1;
    overflow-y: visible;
  }
  .idea-form-scroll-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
  }
  .card-overlay-drawer-mode .idea-form-scroll-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }
  /* Both were sized for the general .idea-form-tab-panel rule's old
     flex-wrap:wrap layout (flex-basis:100% meant "full row width"
     there) - inside the new column-flex scroll body their main axis is
     vertical instead, where that same 100% would mean "100% of the
     scroll body's height" and force an even, squashed split between the
     two instead of each sizing to its own content, so this pins both to
     their natural height. */
  .idea-form-scroll-body > .idea-city-select-row,
  .idea-form-scroll-body > .idea-form-fields {
    flex: 0 0 auto;
  }
  /* Same fix one level up - .idea-submit-row's own flex-basis:100% (also
     shared, unmodified, by a differently-laid-out reuse of this class in
     openMoveToTripPicker) meant "full row width" under the old wrap
     layout; pin it to its own content height instead of stretching now
     that its parent here is a column. */
  .card-overlay-drawer-mode .idea-form-tab-panel[data-idea-tab-panel="places"] .idea-submit-row {
    flex: 0 0 auto;
  }
  /* Add Expense drawer (see openAddExpenseOverlay) - unconditional at
     every width, unlike Add to List above. .expense-form's own
     border-radius/shadow (see .expense-form) are for the default
     centered dialog card; in drawer mode the panel itself already
     supplies that chrome (see .card-overlay-drawer-mode .card-overlay-
     panel's own white background), so those are stripped here, but the
     background is deliberately NOT left transparent (which would show
     that white through) - #FBF7F0 matches Add to List's own beige
     dialog/drawer background (see .idea-form-dialog-mode) per direct
     feedback, so the two "add" flows read as one consistent style. */
  .card-overlay-drawer-mode .expense-form {
    background: #FBF7F0;
    border-radius: 0;
    box-shadow: none;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }
  /* "Plan a new trip" drawer (see openNewTripOverlay) - same reasoning
     as .expense-form directly above: the drawer panel itself already
     supplies the white background/shadow, so .new-trip-form's own
     dialog-card chrome is stripped here rather than doubled up. */
  .card-overlay-drawer-mode .new-trip-form {
    background: #FBF7F0;
    border-radius: 0;
    box-shadow: none;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }
  .idea-input-clear-btn {
    display: none;
    position: absolute;
    top: 50%;
    right: 10px;
    transform: translateY(-50%);
    width: 24px;
    height: 24px;
    border-radius: var(--radius-circle);
    border: none;
    background: var(--color-border);
    color: #fff;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
  }
  .idea-input-clear-btn .material-symbols-outlined {
    font-size: var(--text-15) !important;
  }
  .idea-city-select-row {
    display: none;
    flex-basis: 100%;
    /* 50px total from the grey line - .idea-form-tabs' own margin-
       bottom (16px) already contributes part of that gap; this makes
       up the other 34px rather than stacking a full 50px on top of it. */
    margin-top: 34px;
    /* 40px total to the Place field below it - .idea-form-tab-panel's
       own gap (16px, shared with the Notes tab's internal spacing, so
       not raised here) already contributes 16px between this row and
       .idea-form-fields; this margin makes up the other 24px. */
    margin-bottom: 24px;
  }
  .idea-city-toggle-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
  }
  /* Same two-color pairing as .date-pill/.date-pill-active and
     .mode-toggle/.mode-btn-active elsewhere in this app (unselected
     track #F1EAE5, selected fill #6B5E52 with white text) instead of
     the handoff's own orange-accent chip treatment - reuses the app's
     existing segmented-control language rather than introducing a
     third selected-state color scheme. */
  .idea-city-toggle-btn {
    font-family: inherit;
    font-size: var(--text-15);
    font-weight: 600;
    color: var(--color-body);
    background: #F1EAE5;
    border: none;
    border-radius: var(--radius-pill);
    padding: 10px 16px;
    cursor: pointer;
  }
  /* Not .idea-city-toggle-btn-active:hover too - this lighter tan is
     meant for the unselected #F1EAE5 chips. The active chip's own text
     is white on dark #6B5E52; this same light hover color under white
     text is illegible, see the dedicated active-hover rule below. */
  .idea-city-toggle-btn:hover:not(.idea-city-toggle-btn-active) {
    background: #e9ded6;
  }
  .idea-city-toggle-btn-active {
    font-weight: 700;
    color: #FFFFFF;
    background: var(--color-body);
  }
  /* #6B5E52 mixed 30% toward white - stays dark enough for the same
     white text to still read clearly, just a lighter hover state than
     the resting fill. */
  .idea-city-toggle-btn-active:hover {
    background: #978E86;
  }

  /* Places/Notes tab bar (see IDEA_ADD_CONTROLS_TEMPLATE's .idea-form-tabs) -
     same underline-tab visual language .idea-category-tab/.trip-subtab-btn
     already use elsewhere in this app, just new class names since this
     one's copy/spacing differs slightly (2 tabs, not a scrolling row). */
  .idea-form-tabs {
    flex-basis: 100%;
    display: flex;
    justify-content: center;
    gap: var(--space-22);
    /* White, same as .idea-add-dialog-title right above it - together
       they're the "nav" area (title + Places/Notes tabs), staying white
       as one contiguous block. .idea-form-dialog-mode's cream only
       starts below this row's own border-bottom (the grey line) - see
       that rule's own comment. Full-bleed edge to edge of the panel,
       same negative-margin/padding trick as the title, so both the
       white and the grey line reach the real edges instead of stopping
       at the form's own side padding. */
    background: #FFFFFF;
    margin: 0 -22px 16px;
    padding: 0 22px;
    border-bottom: 1px solid rgba(74,63,53,0.1);
  }
  .idea-form-tab {
    font-family: inherit;
    background: none;
    border: none;
    border-bottom: 2.5px solid transparent;
    margin-bottom: -1px;
    padding: 12px 0;
    font-size: var(--text-14);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-muted);
    cursor: pointer;
  }
  /* "Move Notes 50px to the right" - an explicit offset on top of the
     tab bar's own centering/gap, not a replacement for either. */
  .idea-form-tab[data-idea-tab="notes"] {
    margin-left: 50px;
  }
  .idea-form-tab-active {
    color: #4A3F35;
    border-bottom-color: var(--color-accent);
  }
  .idea-form-tab-panel {
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-16);
  }

  .idea-visibility-row {
    flex-basis: 100%;
    margin: -6px 0 2px;
  }
  .idea-visibility-label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--color-body);
    cursor: pointer;
  }
  .idea-visibility-label input[type="checkbox"] {
    /* Design-audit fix 2026-08-07: matches .signin-remember-checkbox's
       size (18px) and uses the real token instead of a hardcoded hex -
       this was itself the drift the audit found, in this session's own
       code. */
    width: 18px;
    height: 18px;
    accent-color: var(--color-accent);
    border-radius: 5px;
    cursor: pointer;
  }
  .idea-visibility-label .material-symbols-outlined {
    font-size: var(--text-16);
    color: var(--color-muted);
  }

  .idea-screenshot-row {
    flex-basis: 100%;
    display: flex;
    gap: var(--space-8);
  }
  .idea-screenshot-row .idea-upload-btn {
    flex: 1;
  }
  .idea-camera-btn {
    flex-shrink: 0;
    width: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #FFFFFF;
    border: 1.5px solid rgba(74,63,53,0.14);
    border-radius: var(--radius-pill);
    color: var(--color-body);
    cursor: pointer;
  }
  .idea-camera-btn:hover {
    background: rgba(74,63,53,0.04);
  }
  /* Direct camera capture (capture="environment" on its file input,
     see IDEA_ADD_CONTROLS_TEMPLATE/.idea-camera-input) only means
     anything on a device that actually has a camera to open - desktop
     browsers either ignore the attribute and fall back to the same
     plain file picker "Upload a screenshot" already opens (making this
     a confusing duplicate button) or don't support it at all. Same
     >=900px breakpoint as the drawer/dialog split, standing in for
     "desktop" the same way it does everywhere else in this form. */
  @media (min-width: 900px) {
    .idea-camera-btn {
      display: none;
    }
  }

  /* Date on its own row per direct feedback (used to sit side by side
     with Time/Checkout in this same .idea-form-day-time-row - see
     IDEA_ADD_CONTROLS_TEMPLATE's new .idea-form-date-row, a sibling
     above this one now). Plain block, not a flex row - a single field
     (native input + the date-pill row fronting it) needs no column
     layout of its own. */
  .idea-form-date-row {
    width: 100%;
  }
  /* Time/Checkout's own row (see .idea-form-day-time-col below) - only
     ever one of the two visible at a time (see syncLodgingFormMode),
     so this row's own flex-wrap/min-width guard against squeezing is
     mostly moot today now that Date no longer shares it, but kept as-is
     rather than stripped down, in case a future Lodging redesign ever
     shows both side by side again. */
  .idea-form-day-time-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-12);
    width: 100%;
    /* Same rhythm as .idea-form-fields' own row gap - .idea-form-day-
       section has no flex/gap of its own (a plain block wrapper), so
       the space between this row and .idea-form-date-row right above
       it needs its own margin instead. */
    margin-top: 16px;
  }
  .idea-form-day-time-col {
    flex: 1 1 140px;
    min-width: 140px;
  }
  /* Frame 3's tappable day pills (see renderIdeaDatePillRow) - reuses
     .date-pill/.date-pill-weekday/.date-pill-date/.date-pill-active
     verbatim (the Itinerary tab's own date-pill row, render.py:7794-7842)
     rather than a second pill style, just without that row's centered/
     scroll-snap treatment since this one lives in a narrower column
     alongside Time, not a full-width sticky header. */
  .idea-date-pill-row {
    display: flex;
    gap: var(--space-6);
    overflow-x: auto;
    scrollbar-width: none;
    padding: 2px 0;
  }
  .idea-date-pill-row::-webkit-scrollbar {
    display: none;
  }

  /* Notes tab - freeform capture with client-side person/place/time
     detection (see detectIdeaNoteEntities) offering to turn the note
     into a real itinerary entry, distinct from the Places tab's
     search-driven flow. */
  .idea-note-text {
    height: 150px;
    resize: none;
    flex-basis: 100%;
  }
  .idea-note-detection-panel {
    flex-basis: 100%;
    background: rgba(240,81,46,0.08);
    border-radius: var(--radius-3xl);
    padding: var(--space-16);
  }
  .idea-note-detection-heading {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-size: var(--text-14);
    font-weight: 700;
    color: var(--color-accent);
    margin-bottom: 12px;
  }
  .idea-note-detection-heading .material-symbols-outlined {
    font-size: var(--text-16) !important;
  }
  .idea-note-detected-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
  }
  .idea-note-detected-chip {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    background: #FFFFFF;
    border-radius: var(--radius-pill);
    padding: 8px 12px;
    font-size: var(--text-14);
    font-weight: 600;
    color: #4A3F35;
  }
  .idea-note-detected-chip .material-symbols-outlined {
    font-size: var(--text-13) !important;
    color: var(--color-accent);
  }
  /* Same pill shape as .idea-submit-row .empty-day-add (Places tab's
     Save to Ideas/Save to Itinerary) - every CTA in this form reads as
     one consistent button language rather than the handoff's own
     mix of 16px-radius buttons alongside that one pill. */
  .idea-note-create-event-btn {
    flex-basis: 100%;
    width: 100%;
    background: #FFFFFF;
    border: 1.5px solid var(--color-accent);
    border-radius: var(--radius-pill);
    padding: var(--space-16);
    font-size: var(--text-16);
    font-weight: 700;
    color: var(--color-accent);
    cursor: pointer;
    font-family: 'Google Sans Flex', sans-serif;
  }
  .idea-note-save-btn {
    flex-basis: 100%;
  }

  /* Design-audit fix 2026-08-07: real tap/press feedback for the
     primary-CTA button family (Finding 1 - background:#f0512e, no
     shared base class yet) - before this, :active was used exactly
     ONCE in the whole stylesheet (.idea-empty-cta-btn, which this
     matches, and which also picked up the brightness darkening below
     for consistency). Grouped here rather than touching each button's
     own rule block, since the effect itself is identical everywhere;
     each button's own transition property already got the matching
     `transform 120ms ease-out` added alongside its existing
     transitions (see each rule above). filter:brightness (not a second
     hardcoded darker hex) so this keeps working correctly even if a
     button's own base color ever changes. */
  .people-signin-btn:active,
  .people-invite-btn:active,
  .overview-address-save-btn:active,
  .new-trip-signin-btn:active,
  .account-save-btn:active,
  .checklist-signin-btn:active,
  .expense-signin-btn:active,
  .idea-signin-btn:active,
  .confirm-modal-confirm-btn:active,
  .pdp-itinerary-confirm-btn:active,
  .idea-link-enter-btn:active,
  .idea-note-save-btn:active {
    transform: scale(0.97);
    filter: brightness(0.88);
  }
  /* Same press effect, plus a reduced shadow (matching
     .idea-empty-cta-btn's own precedent) for the buttons that carry
     one at rest. */
  .expense-sticky-add-btn:active,
  .idea-sticky-add-btn:active {
    transform: scale(0.97);
    filter: brightness(0.88);
    box-shadow: 0px 3px 6px rgba(240,81,46,0.2);
  }
  .expense-submit-btn:active {
    transform: scale(0.97);
    filter: brightness(0.88);
    box-shadow: 0px 5px 8px rgba(240,81,46,0.25);
  }

  /* Design-audit fix 2026-08-07: a light "pop" on check for the two
     checkboxes that had no feedback at all (.checklist-item-row's own
     checkbox already has its own dedicated confetti-burst animation -
     see CHECKLIST_CHECK_BURST_SVG in app.js - deliberately left
     untouched here rather than replaced, since a to-do item getting
     ticked off reasonably earns more delight than a plain settings/
     privacy checkbox). Runs once each time the box transitions to
     checked - CSS re-triggers a keyframe animation automatically on
     that state change, no JS needed. */
  @keyframes checkboxPop {
    0% { transform: scale(1); }
    45% { transform: scale(1.35); }
    100% { transform: scale(1); }
  }
  .signin-remember-checkbox:checked,
  .idea-visibility-label input[type="checkbox"]:checked {
    animation: checkboxPop 240ms ease-out;
  }

