  /* ── Mapbox nav controls — glass applied to the control group, not outer wrapper ── */
  /* Attribution lives in this container; offset to clear #locate-btn (right: 16, bottom: 16, 34px) */
  .mapboxgl-ctrl-bottom-right {
    right: 56px;
    bottom: 24px;
  }
  .mapboxgl-ctrl-group {
    background: var(--glass-action-bg) !important;
    backdrop-filter: var(--glass-blur-action) !important;
    -webkit-backdrop-filter: var(--glass-blur-action) !important;
    border: var(--glass-border) !important;
    border-radius: var(--radius-md) !important;
    padding: var(--space-xs) !important;
    box-shadow: none !important;
    overflow: visible !important;
    display: flex !important;
    flex-direction: column !important;
    gap: var(--space-xs) !important;
  }
  .mapboxgl-ctrl-group button {
    width: 34px !important;
    height: 34px !important;
    border-radius: 50% !important;
    background: rgba(17,30,56,0.5) !important;
    border: 1px solid rgba(156,189,231,0.18) !important;
    box-shadow: none !important;
    transition: background 0.15s, border-color 0.15s !important;
  }
  .mapboxgl-ctrl-group button:hover {
    background: rgba(24,52,95,0.85) !important;
    border-color: rgba(245,194,94,0.35) !important;
  }
  /* Zoom + icon */
  .mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M10 4v12M4 10h12' stroke='rgba(156,189,231,0.8)' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") !important;
    filter: none !important;
  }
  /* Zoom - icon */
  .mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M4 10h12' stroke='rgba(156,189,231,0.8)' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") !important;
    filter: none !important;
  }
  /* Compass */
  .mapboxgl-ctrl-compass .mapboxgl-ctrl-icon { filter: none !important; }
  .mapboxgl-ctrl-compass-arrow { fill: rgba(156,189,231,0.7) !important; }
  .mapboxgl-ctrl-compass-arrow path:first-child { fill: var(--accent) !important; }
  /* Attribution — hidden until container hover */
  .mapboxgl-ctrl-attrib {
    background: transparent !important;
    border: none !important;
    opacity: 0 !important;
    transition: opacity 0.2s !important;
    margin-top: var(--space-2xs) !important;
  }
  .mapboxgl-ctrl-bottom-right:hover .mapboxgl-ctrl-attrib { opacity: 1 !important; }
  .mapboxgl-ctrl-attrib-button {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 20px !important;
    height: 20px !important;
    border-radius: 50% !important;
    background: rgba(17,30,56,0.72) !important;
    border: 1px solid rgba(156,189,231,0.22) !important;
    color: rgba(156,189,231,0.7) !important;
    font-size: 10px !important;
    cursor: pointer !important;
  }
  .mapboxgl-ctrl-attrib.mapboxgl-compact {
    display: block !important;
    min-width: 0 !important;
  }

  .popup-name {
    font-family: 'Inter', sans-serif;
    font-size: var(--text-body);
    font-weight: 600;
    color: var(--accent);
    margin-bottom: var(--space-xs);
  }

  .popup-meta {
    font-size: 12px;
    color: var(--muted);
  }

  .popup-address {
    font-size: var(--text-caption);
    color: var(--muted);
    opacity: 0.7;
    margin-top: var(--space-2xs);
  }

  .popup-status {
    margin-top: var(--space-sm);
    font-size: 12px;
    font-weight: 600;
  }

  .popup-status.sunny         { color: var(--accent); }
  .popup-status.shaded        { color: var(--muted); }
  .popup-status.opening-soon  { color: var(--blue-300); }

  .popup-actions {
    margin-top: var(--space-md);
    display: flex;
    gap: var(--space-sm);
  }

  .popup-directions-btn, .popup-edit-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    border-radius: var(--radius-sm);
    padding: var(--space-sm) var(--space-md);
    font-size: var(--text-caption);
    font-family: inherit;
    cursor: pointer;
    text-decoration: none;
  }

  /* Map-popup actions stay a compact floating-chrome context (12px icons) —
     like .sd-suggest-btn, NOT migrated to the 44px role pills, which would
     dominate the small bubble. Directions = jordy secondary, Edit = honey. */
  .popup-directions-btn {
    background: rgba(156,189,231,0.12);
    border: 1px solid rgba(156,189,231,0.3);
    color: var(--blue-300);
  }
  .popup-directions-btn:hover { background: rgba(156,189,231,0.22); }

  .popup-edit-btn {
    background: rgba(245,194,94,0.12);
    border: 1px solid rgba(245,194,94,0.3);
    color: var(--accent);
  }
  .popup-edit-btn:hover { background: rgba(245,194,94,0.22); }

  /* LEFT PANEL — venue list, floats below search bar */
  #panel {
    position: absolute;
    top: 70px;      /* 16 search-top + 46 search-h + 8 gap */
    left: 16px;
    bottom: 16px;
    /* Was 336 px — felt cramped for a venue card that holds a 64 px
       thumbnail + title + sun-summary + meta. 380 px gives the cards
       a comfortable reading width without pushing toward sidebar-as-
       wall territory (Google Maps' sidebar is 408 px; Apple Maps
       inspector is ~340 px). Top-strip aligns to this same column. */
    width: 380px;
    border-radius: var(--radius-lg);
    /* Pure-frost glass — matches the accept panel (alpha-0 Delft base + heavy
       22px frost), replacing the earlier Jordy 25% wash + 4px chrome surface.
       The map shows through, blurred; cards sit opaque on top. The mobile
       bottom-sheet rule below keeps only the top border (l/r/b: none). */
    background: var(--glass-panel-bg);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: 1px solid var(--line-l);
    box-shadow: var(--shadow-1);
    display: flex;
    flex-direction: column;
    /* overflow:visible so the FTS scrub popup can extend above the
       panel top during scrubbing. The venue list manages its own
       overflow-y:auto for card scrolling. */
    overflow: visible;
    transition: top 0.3s ease, transform 0.28s ease, opacity 0.28s ease, height 0.3s ease, border-radius 0.3s ease;
    /* z-index 901 so the FTS scrub popup (a child of #panel) can render
       ABOVE the top-strip (z-index 900) when the expanded weather
       popup overshoots into the strip's vertical band. #panel creates
       a stacking context — children can't escape — so the only way
       for the popup to clear the strip is for the whole panel column
       to stack above it. Both surfaces are vertically separated
       (panel top:70, strip top:14) so the swap has no visual impact
       elsewhere. */
    z-index: 901;
  }

  #panel-header {
    padding: 0 var(--space-lg);
    flex-shrink: 0;
    overflow: visible;
    display: none;  /* MAP VIEW moved into #list-sun-header */
  }

  /* Brand logo (now in floating-brand) */
  #brand-logo {
    display: flex;
    align-items: center;
    flex-shrink: 0;
  }
  #brand-logo img {
    /* Sized close to the wordmark's height so the lockup reads balanced (was
       32px — ~2.5× the cap height, top-heavy next to the 18px wordmark). */
    height: 26px;
    width: auto;
    object-fit: contain;
    display: block;
    /* Soft lift so the mark reads over any map band (no pill behind it now). */
    filter: var(--label-mark-shadow);
  }
  #brand-name {
    font-family: 'Inter', sans-serif;
    font-size: 18px;
    /* Official wordmark spec (brand pack): Inter 900, letter-spacing -5/180em.
       White + the floating-label halo so it reads over the map (the brand's
       on-surface colour is cream; over the map we use the label treatment). */
    font-weight: 900;
    letter-spacing: -0.028em;
    color: var(--label-text);
    text-shadow: var(--label-halo);
    line-height: 1;
    flex-shrink: 0;
  }


  /* Controls (now in floating-bottom) */
  #panel-controls {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    overflow: visible;
  }

  /* Search input */
  #venue-search {
    width: 100%;
    background: transparent;
    border: none;
    color: var(--text);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-body);
    padding: 0;
    outline: none;
  }
  #venue-search::placeholder { color: var(--muted); opacity: 0.7; }

  /* Intent shortcuts — segmented control row below the arc */
  #time-presets-row {
    position: relative;
    display: flex;
    gap: var(--space-2xs);
    padding: var(--space-2xs);
    margin-top: var(--space-md);
    background: rgba(156,189,231,0.08);
    border: 1px solid rgba(156,189,231,0.14);
    border-radius: var(--radius-md);
  }
  #intent-pill {
    position: absolute;
    background: var(--accent);
    border-radius: var(--radius-sm);
    transition: left 0.22s cubic-bezier(0.34,1.56,0.64,1),
                top  0.22s cubic-bezier(0.34,1.56,0.64,1),
                width 0.22s cubic-bezier(0.34,1.56,0.64,1),
                height 0.22s cubic-bezier(0.34,1.56,0.64,1);
    pointer-events: none;
    z-index: 0;
  }
  .intent-btn {
    position: relative;
    flex: 1;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    color: rgba(156,189,231,0.78);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-caption);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: var(--space-md) var(--space-xs);
    cursor: pointer;
    transition: color 0.15s;
    white-space: nowrap;
    z-index: 1;
    text-align: center;
  }
  .intent-btn:hover:not(.active) { color: rgba(156,189,231,0.85); }
  .intent-btn.active { color: #111E38; }

  /* Area chips — hidden */
  #area-chips { display: none; }
  .area-chip {
    background: rgba(156,189,231,0.1);
    border: 1px solid rgba(156,189,231,0.18);
    border-radius: var(--radius-lg);
    color: rgba(156,189,231,0.45);
    font-family: 'Inter', sans-serif;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: var(--space-xs) var(--space-lg);
    cursor: pointer;
    white-space: nowrap;
    flex-shrink: 0;
    transition: all 0.15s;
  }
  .area-chip:hover:not(.active) { color: rgba(156,189,231,0.8); border-color: rgba(156,189,231,0.35); }
  .area-chip.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #111E38;
  }

  /* Venue list header: title (left) · panel toggle (right) */
  /* Venue peek: mobile-only collapsed preview — hide on desktop */
  #venue-peek { display: none; }

  #venue-header {
    display: flex;
    align-items: center;
    justify-content: flex-end;  /* toggle icon right-aligned, count hidden */
    padding-bottom: 0;
    gap: var(--space-md);
  }
  /* Legacy sun-count duplicate — hide to avoid double header */
  #venue-count { display: none; }
  #venue-count.count-sunny { color: var(--accent); }
  /* map-view-btn removed — viewport filter is always active */
  #sort-row {
    display: flex;
    justify-content: flex-end;
    padding: var(--space-xs) var(--space-lg) var(--space-xs);
    flex-shrink: 0;
  }
  #sort-panel {
    position: fixed;
    z-index: 1100;
    border-radius: var(--radius-md);
    padding: var(--space-sm);
    display: none;
    flex-direction: column;
    min-width: 160px;
    /* Raised dropdown (DESIGN.md Phase 2.5): OPAQUE cream + light hairline +
       shadow-2. Was --content-bg (cream 80%) — the translucency let the navy
       cards bleed through (DESIGN-FIXES "dropdown bleed-through"). Opaque fixes
       it; no backdrop-filter needed on an opaque surface. Ink text (base.css). */
    background: var(--surface-raised) !important;
    border: 1px solid var(--line-l);
    box-shadow: var(--shadow-2);
  }
  #sort-panel.open { display: flex; }
  .sort-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1099;
    background: transparent;
  }

  .sort-btn {
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: rgba(17,30,56,0.78);
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    font-weight: 500;
    padding: var(--space-lg) var(--space-lg);
    cursor: pointer;
    transition: color 0.12s, background 0.12s,
                transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
    white-space: nowrap;
    text-align: left;
    width: 100%;
    -webkit-tap-highlight-color: transparent;
    transform-origin: center;
    will-change: transform;
  }
  @media (hover: hover) and (pointer: fine) {
    .sort-btn:hover:not(.active) { color: var(--content-text); background: rgba(17,30,56,0.06); }
  }
  .sort-btn.active {
    background: rgba(245,194,94,0.28);
    color: var(--content-text);
    font-weight: 700;
  }
  .sort-btn:active { transform: scale(0.97); }
  .sort-btn:focus { outline: none; }
  .sort-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  body[data-fx] .sort-btn.lens-fx-tilting {
    transition: transform 0.05s linear, color 0.12s, background 0.12s;
  }
  /* Border-flash animation skipped — same reasoning as filter pills. */
  #sort-toggle-btn {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    height: 36px;
    padding: 0 var(--space-lg);
    background: var(--glassctl-bg);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    border: var(--glassctl-border);
    border-radius: var(--radius-md);
    box-shadow: var(--glassctl-raise);
    color: var(--glassctl-icon);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 600;
    cursor: pointer;
    flex-shrink: 0;
    transition: color 0.12s, border-color 0.12s, background 0.12s;
    -webkit-tap-highlight-color: transparent;
    outline: none;
  }
  #sort-toggle-btn .sort-chevron {
    color: rgba(17,30,56,0.55);
    display: inline-block;
    transition: transform 0.18s ease;
  }
  /* Specificity note: the in-header rule above (#list-sun-header #sort-toggle-btn)
     has 2 IDs, so hover/open need to match or beat it. */
  #list-sun-header #sort-toggle-btn:hover {
    background: var(--glassctl-bg-hover);
  }
  #list-sun-header #sort-toggle-btn.open {
    border-color: rgba(245,194,94,0.55);
    background: rgba(245,194,94,0.32);
    color: var(--accent-on);
    box-shadow: var(--glassctl-raise);
  }
  #list-sun-header #sort-toggle-btn.open .sort-chevron { transform: rotate(180deg); color: var(--accent-on); }
  /* Keep the legacy non-header sort-toggle-btn hover (lower specificity is fine
     where the element isn't inside #list-sun-header). */
  #sort-toggle-btn:hover { background: var(--glassctl-bg-hover); }
  #sort-toggle-btn.open  {
    border-color: rgba(245,194,94,0.55);
    background: rgba(245,194,94,0.32);
    color: var(--accent-on);
  }
  #sort-toggle-btn.open .sort-chevron { transform: rotate(180deg); color: var(--accent-on); }

  /* Legacy class — keep for any remaining references */
  .filter-toggle-btn {
    background: none;
    border: 1px solid rgba(156,189,231,0.2);
    border-radius: var(--radius-sm);
    color: var(--muted);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-caption);
    font-weight: 500;
    padding: var(--space-2xs) var(--space-md);
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
  }
  .filter-toggle-btn:hover:not(.active) { color: var(--text); }
  .filter-toggle-btn.active {
    background: var(--accent-dim);
    border-color: var(--accent);
    color: var(--accent);
  }

  /* Venue list */
  #venue-list {
    flex: 1;
    overflow-y: auto;
    /* See --app-pad-b token (top of CSS). The scrollable list pads
       its bottom so the last card scrolls up clear of the home-
       indicator gesture zone / Safari URL-bar reserve. */
    padding: var(--space-xs) var(--space-lg) var(--app-pad-b);
    scrollbar-width: thin;
    scrollbar-color: rgba(245,194,94,0.2) transparent;
  }

  #venue-list::-webkit-scrollbar { width: 4px; }
  #venue-list::-webkit-scrollbar-track { background: transparent; }
  #venue-list::-webkit-scrollbar-thumb { background: rgba(245,194,94,0.2); border-radius: var(--radius-none); }

  /* Section headers — "Sol nå · 8" / "Sol senere · 6". Always rendered, even
     for empty buckets: the empty-section text inlines under the header. Hidden
     during slider scrubs so the count doesn't flicker as venues cross the
     bucket boundary. */
  .venue-section-header {
    /* No horizontal padding — the section header flush-aligns with the
       venue cards' outer edges (cards span venue-list's inner width). */
    padding: var(--space-xs) 0 var(--space-sm);
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-muted);
    text-transform: none;
    letter-spacing: 0;
    /* Animatable collapse: the peek / sliding states set max-height + padding
       to 0 (see below). Revealing transitions max-height 0 → 40 (clamps at the
       ~22px content) so the header grows in and PUSHES the list down, instead
       of popping into place. max-height clamps a single-line label with room. */
    overflow: hidden;
    max-height: 40px;
    transition: max-height 240ms ease, padding 240ms ease, opacity 200ms ease;
  }
  /* First section header sits closer to the day-header (no extra top
     padding) since the bottom hairline already provides separation. */
  #venue-list > .venue-section-header:first-child {
    padding-top: 0;
  }
  .venue-section-header .section-count { opacity: 0.65; margin-left: var(--space-xs); }
  .empty-section {
    padding: var(--space-xs) var(--space-xs) var(--space-md);
    color: var(--muted);
    font-size: 12px;
    font-style: italic;
    opacity: 0.7;
  }
  /* (.empty-all / .suggest-empty retired 2026-05-25 → canonical .empty-state below) */

  /* ── Canonical empty state (DESIGN.md → "Empty-state anatomy") ─────────────
     glyph · title · sub · ≤1 CTA · escape link. Surface-aware via the --ink
     modifier (cream surfaces); default targets the dark Delft list/detail. */
  .empty-state {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    max-width: 300px; margin: 0 auto;
    padding: var(--space-4xl) var(--space-2xl);
  }
  .empty-state .es-glyph { margin-bottom: var(--space-md); color: var(--text-secondary); line-height: 0; }
  .empty-state .es-glyph svg { width: 28px; height: 28px; }
  .empty-state .es-title { font-size: var(--text-subtitle); font-weight: var(--fw-subtitle); line-height: 1.3; color: var(--text); }
  .empty-state .es-sub { font-size: var(--text-caption); line-height: 1.45; margin-top: var(--space-2xs); color: var(--text-secondary); max-width: 32ch; }
  .empty-state .es-cta-slot { margin-top: var(--space-lg); }
  .empty-state .es-link {
    margin-top: var(--space-md); background: none; border: 0; cursor: pointer;
    font-size: var(--text-label); font-weight: 500; color: var(--muted);
    text-decoration: underline; text-underline-offset: 3px;
  }
  .empty-state .es-link:hover { color: var(--text); }
  .empty-state--ink .es-title { color: var(--panel-text); }
  .empty-state--ink .es-glyph,
  .empty-state--ink .es-sub,
  .empty-state--ink .es-link { color: var(--ink-muted); }
  .empty-state--ink .es-link:hover { color: var(--panel-text); }

  body.list-scrubbing .venue-section-header { opacity: 0; }
  body.list-scrubbing .empty-section { opacity: 0; }

  /* Skeleton crossfade (Phase 7) — on scrub only the card CONTENTS fade; the
     BOXES stay put (real + skeleton share the same .venue-card box).
     - The opacity transition is scoped to body.list-scrubbing so it never
       touches the initial card mount (that was the page-load flash).
     - The skeleton's own whole-card fade-in (skeletonFadeIn) is suppressed
       during scrub, so the box appears solid instantly instead of fading in
       (that was the flash when the skeletons showed up). Its ::after sheen
       keeps animating. Swap + race guard live in _injectScrubSkeletons. */
  body.list-scrubbing #venue-list .venue-card > * { transition: opacity var(--dur-fast) var(--ease-standard); }
  #venue-list.list-xfading .venue-card > * { opacity: 0; }
  body.list-scrubbing .venue-card.skeleton { animation: none; }
  @media (prefers-reduced-motion: reduce) { body.list-scrubbing #venue-list .venue-card > * { transition: none; } }

  /* Pull-tab indicator: appears only when the in-viewport list is so short
     it can't trigger natural scroll-pagination (fewer than ~5 venues). Tap
     to load the next nearest batch. Visual is intentionally subtle — a thin
     chevron + faint label, no button affordance — so it reads as a hint,
     not a CTA. */
  #list-expand-tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    width: 100%;
    margin: var(--space-md) 0 var(--space-xl);
    padding: var(--space-md) var(--space-lg);
    background: transparent;
    border: 0;
    color: var(--muted);
    font: inherit;
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0.02em;
    cursor: pointer;
    opacity: 0.6;
    transition: opacity 160ms ease, color 160ms ease;
    -webkit-tap-highlight-color: transparent;
  }
  #list-expand-tab:hover {
    opacity: 1;
    color: var(--accent);
  }
  #list-expand-tab .list-expand-label {
    opacity: 0.85;
  }

  @keyframes cardIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  @keyframes cardInDimmed {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 0.5; transform: translateY(0); }
  }

  .venue-card {
    /* Cream-frost card (2026-05-27) — same material as the detail panel, so the
       list and the detail sheet stop clashing. Translucent cream fill + bright
       rim + ink text; the sun signal lives in fills (fillbar, sun pill), never
       in coloured fonts. */
    background: var(--card-cream-bg);
    /* No backdrop-filter — cards sit inside #panel which already blurs (22px
       frost). Per-card blur would re-add ~30 GPU compositing layers during
       scroll (the Android perf regression we removed); the panel's frost is the
       backdrop, the 0.70 cream fill is the card. */
    border: 1px solid var(--card-cream-border);
    /* Flip the text + subject tokens to INK within the card scope: name/meta/
       sub use --text/--text-secondary/--muted; pills use --cool/--rain; the
       fillbar track + skeleton blocks use --fill-track / --skel-*. Overriding
       them here adapts all of those to cream in one place. */
    --text: var(--content-text);
    --text-secondary: var(--ink-muted);
    --muted: var(--content-muted);
    --cool: var(--content-muted);          /* was light grey #B5BCC8 — illegible on cream */
    --rain: var(--rain-ink);               /* deeper rain blue, legible on cream */
    --fill-track: var(--line-l-faint);     /* ink track behind the honey fill */
    --skel-block: var(--line-l-faint);     /* ink skeleton blocks on cream */
    --skel-sheen: var(--skel-sheen-light);
    color: var(--content-text);
    border-radius: var(--radius-md);
    padding: var(--space-lg) var(--space-lg);
    margin-bottom: var(--space-md);
    cursor: pointer;
    box-shadow: var(--card-cream-shadow);
    transition: border-color 0.12s ease-out, background 0.12s ease-out, opacity 0.2s;
    position: relative;
    overflow: visible;
    contain: layout style;
    display: flex;
    flex-direction: column;
    /* Internal row gap — bumped from 3 to 4 for slightly more breathing.
       Pills and timeline still get individual margin-top where needed. */
    gap: var(--space-xs);
    /* Tabular figures so sun-hours / distances / times don't twitch as digits
       change (DESIGN.md → Typography). Container-level: only affects digit
       glyph width, harmless on labels. */
    font-variant-numeric: tabular-nums;
  }
  /* The closed-card variant is a single row, not the 3-row column. Override
     the column flex so name + "opens at" badge sit side-by-side. */
  .venue-card.closed-card { gap: 0; }
  .venue-card.closed-card .closed-row { flex: 1; }
  /* Content-change fade with stagger: cardIn fires when the venue list's
     card SET changes (date change, sort change, panel open from hidden).
     Slider-scrub re-renders keep [data-mounted] so the animation doesn't
     re-fire on every tick — ui-list.js clears data-mounted only when the
     content hash actually changes. Staggered nth-child delays give the
     row a gentle cascade rather than all cards flashing in at once. */
  #venue-list:not([data-mounted]) .venue-card {
    animation: cardIn 0.32s cubic-bezier(0.2, 0.8, 0.3, 1) both;
  }
  #venue-list:not([data-mounted]) .venue-card:nth-child(1)  { animation-delay: 0ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(2)  { animation-delay: 30ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(3)  { animation-delay: 60ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(4)  { animation-delay: 90ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(5)  { animation-delay: 120ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(6)  { animation-delay: 150ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(7)  { animation-delay: 175ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(8)  { animation-delay: 200ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(9)  { animation-delay: 220ms; }
  #venue-list:not([data-mounted]) .venue-card:nth-child(n+10) { animation-delay: 240ms; }

  /* Hover only on devices with a real pointer. On iOS Safari :hover sticks
     after a tap until the user taps elsewhere — that's the "blue box" the
     user reported on the source card after the panel closes.
     New: 1px lift + honey-tinted border + lifted shadow. Modern card-hover
     convention (Stripe / Linear). */
  .venue-card {
    transition: border-color 0.15s ease-out, background 0.12s ease-out, opacity 0.2s,
                transform 0.15s cubic-bezier(0.32, 0.72, 0, 1), box-shadow 0.15s ease-out;
  }
  @media (hover: hover) and (pointer: fine) {
    .venue-card:hover {
      border-color: var(--accent-border);  /* honey-tinted rim — warm hover affordance */
      background: var(--card-cream-bg-hover);
      transform: translateY(-1px);
      /* Flat lift on cream — the floating-bar shadow reads better on light than
         the dark --shadow-2. */
      box-shadow: var(--panel-shadow);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .venue-card:hover { transform: none; }
  }
  /* Suppress the mobile browser's default tap-highlight overlay. */
  .venue-card { -webkit-tap-highlight-color: transparent; }

  .venue-card.selected {
    /* Keep the cream card fill; selection is signalled by the honey ::before
       ring below (honey ring on cream = a clean accent, no font colour needed). */
    background: var(--card-cream-bg);
  }
  /* Selection outline that *emanates from the click position*. The lens-fx
     pointerdown handler in lens-effects.js sets --ripple-x / --ripple-y on
     the card before selection runs. The pseudo-element below is a full
     honey-accent border whose visibility is clipped by a circle that
     starts at the click point with 0% radius and grows to 150% — so the
     outline appears to "wipe" outward from where the user actually
     touched, then settles into a static ring. */
  .venue-card.selected::before {
    content: '';
    position: absolute;
    inset: -1px;
    border: 2px solid var(--accent);
    border-radius: inherit;
    pointer-events: none;
    z-index: 3;
    clip-path: circle(150% at var(--ripple-x, 50%) var(--ripple-y, 50%));
    animation: card-select-emanate 520ms cubic-bezier(0.32, 0.72, 0, 1);
  }
  @keyframes card-select-emanate {
    0%   { clip-path: circle(0% at var(--ripple-x, 50%) var(--ripple-y, 50%)); opacity: 0.85; }
    100% { clip-path: circle(150% at var(--ripple-x, 50%) var(--ripple-y, 50%)); opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    .venue-card.selected::before { animation: none; clip-path: none; }
  }

  .venue-card.state-shadow .card-new-hero-main {
    color: var(--text);
    font-weight: 700;
  }

  .venue-card.state-rain .card-new-hero-main {
    color: var(--text);
    font-weight: 700;
  }

  .venue-card.state-done {
    opacity: 0.48;
  }

  .venue-card.state-done .card-new-hero-main {
    color: var(--muted);
    font-weight: 700;
  }

  /* Closed venue (outside opening hours): same muted treatment as state-done.
     The card reads as "not relevant right now" — no sun/weather narrative,
     just the closed verdict (rendered by venueState in ui-shared.js). */
  .venue-card.state-closed {
    opacity: 0.48;
  }
  .venue-card.state-closed .card-new-hero-main {
    color: var(--muted);
    font-weight: 700;
  }

  .venue-card.editing {
    box-shadow: 0 0 0 2px rgba(245,194,94,0.55), 2px 4px 18px rgba(0,0,0,0.4);
    border-left-color: var(--accent);
    opacity: 1 !important;
  }

  /* Card top row: name+meta left, status right */
  .card-top {
    display: flex;
    align-items: flex-start;
    gap: var(--space-lg);
  }

  .card-left {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
  }

  .card-right {
    flex-shrink: 0;
    min-width: 115px;
    text-align: right;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-2xs);
  }

  .card-new-name {
    font-size: var(--text-body);
    font-weight: 700;
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
  }

  .card-new-meta {
    font-size: var(--text-caption);
    font-weight: 500;
    /* Secondary text = cream-at-opacity, same hue as the cream title
       (DESIGN.md principle 3) — was a raw rgba, now the --text-secondary token. */
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    gap: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.35;
    letter-spacing: 0.005em;
  }

  .card-new-meta span {
    display: inline;
  }

  .card-new-meta .card-meta-dot {
    opacity: 0.4;
    margin: 0 var(--space-xs);
  }

  .card-new-hero-main {
    font-size: 14px;
    font-weight: 700;
    color: var(--accent);
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    /* Letter-spacing 0 instead of -0.01em — tight tracking with heavy weight
       in saturated honey was reading as a dense block. Slightly looser =
       calmer without losing emphasis. */
    letter-spacing: 0;
  }

  .card-new-hero-sub {
    font-size: var(--text-caption);
    font-weight: 500;
    /* Match .card-new-meta — cream secondary-text token for cohesive hue. */
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    line-height: 1.3;
  }

  /* ── New venue-card grid — 4 stacked blocks (name+duration / meta / pills /
     full-width timeline). The compact card and tooltip still use the older
     .card-top + .card-new-* classes; the main list card uses these. */
  .card-row1 {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
  }
  .card-name {
    flex: 1;
    min-width: 0;
    font-size: var(--text-body);
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
  }
  .card-duration {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    font-size: 14px;
    font-weight: 600;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
    white-space: nowrap;
  }
  .card-duration .sun-glyph {
    font-size: var(--text-label);
    line-height: 1;
  }
  .card-meta {
    font-size: var(--text-caption);
    font-weight: 500;
    /* Secondary text = cream-at-opacity, same hue as the cream title
       (DESIGN.md principle 3). Was --muted cool grey, which broke hue cohesion. */
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    gap: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.3;
  }
  .card-meta span { display: inline; }
  .card-meta .card-meta-dot { opacity: 0.4; margin: 0 var(--space-xs); }
  .card-meta .card-meta-opens { color: var(--text-secondary); opacity: 0.85; }
  /* Walking-person glyph on the walk-time meta. Plain inline element
     (no flex wrapper) so the baseline stays aligned with the rest of
     the meta row and the icon doesn't get clipped by the row's
     overflow:hidden. vertical-align nudges the icon onto the text
     middle; margin-right gives the digits a small gap. */
  .card-meta .walk-glyph {
    display: inline-block;
    vertical-align: -2px;
    margin-right: var(--space-2xs);
    opacity: 0.85;
  }

  .card-pills {
    display: flex;
    gap: var(--space-sm);
    flex-wrap: nowrap;
    min-width: 0;
    margin: var(--space-xs) 0;
  }
  .card-pill {
    height: 26px;
    padding: 0 var(--space-lg);
    border-radius: var(--radius-md);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex-shrink: 1;
    min-width: 0;
    max-width: 100%;
  }
  .card-pill.pill-sol {
    background: var(--accent-dim);
    border: 1px solid rgba(245,194,94,0.35);
    color: var(--accent);
  }
  .card-pill.pill-skygge {
    background: rgba(156,189,231,0.10);
    border: 1px solid rgba(156,189,231,0.22);
    color: var(--muted);
  }
  .card-pill.pill-skyer {
    background: rgba(156,189,231,0.14);
    border: 1px solid rgba(156,189,231,0.30);
    color: var(--cool);
  }
  .card-pill.pill-regn {
    background: rgba(96,140,200,0.18);
    border: 1px solid rgba(96,140,200,0.40);
    color: var(--rain);
  }
  /* Operational hours pills: neutral white-on-glass, distinct from the
     subject-colored disruption pills above. Renders only when hours are
     the binding constraint — see buildCardPillsV2 in ui-shared.js. */
  .card-pill.pill-stenger,
  .card-pill.pill-aapner {
    background: rgba(255,242,235,0.10);
    border: 1px solid rgba(255,242,235,0.18);
    color: rgba(255,242,235,0.75);
  }
  /* Overflow / opportunity pill: sun-token, names additional later windows
     ("+ X sol fra HH:MM" / "+ N sol senere") or pill-overflow ("+ N mer"). */
  .card-pill.pill-overflow {
    background: rgba(245,194,94,0.18);
    border: 1px solid rgba(245,194,94,0.35);
    color: var(--accent);
  }
  /* On the cream cards most honey TEXT goes ink (the honey FILLS — pill bg/
     border, fillbar, selection ring — carry the warmth). Scoped to .venue-card
     so non-card honey text (score, edit-tool, popups) is untouched. */
  .venue-card .card-pill.pill-sol,
  .venue-card .card-pill.pill-overflow,
  .venue-card .tl-label-sol {
    color: var(--text);
  }
  /* EXCEPT the sun-signal texts (sun-hours "☀ 5h 50m", "+Xh" opportunity).
     Option 5: a honey-dim CHIP (honey bg + border) with amber text — the chip's
     warmth carries the "sun", and amber-on-honey-dim reads cleanly on cream. */
  .venue-card .card-new-hero-main,
  .venue-card .card-duration,
  .venue-card .card-sun-dur,
  .venue-card .card-disrupt-sun {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    color: var(--accent-on-light);
    background: var(--accent-dim);
    border: 1px solid var(--accent-border);
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-sm);
    /* Pull the chip right by its own right padding so the TEXT aligns with the
       right-aligned bare text below it (the chip bg just extends to the edge). */
    margin-right: calc(var(--space-sm) * -1);
  }
  /* Shade gap = the cool counterpart: a Jordy-blue chip (jordy-tint bg + jordy
     border + deep-blue text) mirroring the sun chip, so warm=sun / cool=shade
     read as opposite signals. (Layout/justify come from .card-disrupt above.) */
  .venue-card .card-disrupt-shade {
    background: var(--surface-chrome);
    border: 1px solid var(--line-d-strong);
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-sm);
    margin-right: calc(var(--space-sm) * -1);
  }
  /* Hours pills were cream-text on a Delft card (invisible on cream) → a faint
     ink wash + ink text. */
  .venue-card .card-pill.pill-stenger,
  .venue-card .card-pill.pill-aapner {
    background: var(--line-l-faint);
    border-color: var(--line-l);
    color: var(--text-secondary);
  }

  /* ── v2 list-card layout ──────────────────────────────────────────────
     Applied via `.card-compact`. The detail panel uses `.card-rich` and
     keeps its prior layout (timeline + labels). For the list variant:
       • card-meta is a 1fr/auto grid: meta-text left, anchor right
       • card-pills is always rendered (preserves card height)
       • bottom 3px fill bar shows sun-fraction of remaining day */
  .venue-card.card-compact {
    /* Bar is flush bottom (3px tall); padding-bottom matches padding-top so
       the visual breathing space above the name row equals the space below
       the meta row. The card clips to its own rounded shape so the fill
       bar's corners follow the 12px card-corner curve. */
    padding-bottom: var(--space-lg);
    overflow: hidden;
  }
  .venue-card.card-compact .card-meta {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: var(--space-md);
  }
  .venue-card.card-compact .card-meta-left {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .venue-card.card-compact .card-anchor {
    justify-self: end;
    flex-shrink: 0;
    color: var(--muted);
    /* Match the venue meta strength — sub-text but not so faint it falls
       below the area · type · distance line on the same row. */
    opacity: 0.92;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  /* Deterministic card height (Phase 5e + 3-row redesign). The list card and
     its skeleton are both .card-3row, so one reservation keeps the scrub swap
     jump-free and gives the Phase-7 value-crossfade a stable box. Fits row1 +
     row2 + row3 + lifted fill bar; border-box so the value is the TOTAL height
     (no global box-sizing reset). Value verified on the Cloudflare preview. */
  .venue-card.card-3row {
    box-sizing: border-box;
    min-height: 92px;
  }
  .venue-card.card-compact .card-pills {
    margin: var(--space-sm) 0 var(--space-2xs);
    justify-content: flex-end;
  }
  /* Pills get the 8px backdrop blur from --glass-blur-pill. Cards
     don't blur (parent panel does), but pills sit on top of everything
     and benefit from the subtle haze. */
  .venue-card.card-compact .card-pill {
    backdrop-filter: var(--glass-blur-pill);
    -webkit-backdrop-filter: var(--glass-blur-pill);
  }
  /* ── Fill bar — flush against the card's bottom border ──
     The card clips to its 12px corner radius, so the bar's bottom-left
     and bottom-right corners follow the card's curve naturally. Track
     spans the full width; fill is one contiguous level meter from the
     left. */
  .venue-card.card-compact .card-fillbar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: var(--fill-track);
    pointer-events: none;
  }
  .venue-card.card-compact .card-fillbar-fill {
    display: block;
    height: 100%;
    /* The "% sun" bar stays bright honey (per user) — a solid honey fill reads
       fine on cream, and it keeps the sun signal vivid. */
    background: var(--accent);
  }

  /* ── 3-row deterministic list card ──────────────────────────────────────
     row1 (name | sun+duration) reuses .card-row1/.card-name/.card-duration.
     row2 = sub (dept/street/area) | anchor; row3 = meta cluster | shade event.
     Both rows are 1fr/auto grids so the right column right-aligns and the left
     ellipsizes. The fill bar lifts into flow (no longer flush to the border). */
  .venue-card.card-3row { gap: var(--space-xs); }

  .venue-card.card-3row .card-row2,
  .venue-card.card-3row .card-row3 {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: var(--space-md);
    font-size: var(--text-caption);
  }
  /* Row 2 left — dept/street (cream secondary) or area (weaker, muted) */
  .venue-card.card-3row .card-sub {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-secondary);
  }
  /* "fine · coarse" — the fine neighborhood (or the coarse area when there's no
     finer one) is the prominent place identity: heavier than the meta row, a
     step below the name. The coarse area is muted context after the dot. */
  .venue-card.card-3row .card-sub-fine { font-weight: 600; color: var(--text-secondary); }
  .venue-card.card-3row .card-sub-coarse { font-weight: 400; color: var(--muted); }
  .venue-card.card-3row .card-sub-sep { opacity: 0.4; margin: 0 var(--space-xs); }

  /* Row 3 left — category glyph + label · walk · distance(muted). Spacing
     comes from the dot margins + a glyph↔label gap, not one flat gap (which
     read cramped/uneven). */
  .venue-card.card-3row .card-meta-left {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 0;
    color: var(--text-secondary);
  }
  .venue-card.card-3row .card-meta-dot {
    opacity: 0.4;
    margin: 0 var(--space-sm);
    flex-shrink: 0;
  }
  .venue-card.card-3row .card-cat,
  .venue-card.card-3row .card-walk {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    flex-shrink: 0;
  }
  .venue-card.card-3row .card-walk .walk-glyph { opacity: 0.85; }
  .venue-card.card-3row .card-dist { color: var(--muted); flex-shrink: 0; }

  /* Row 3 right — shade event, styled like the anchor (muted text, not a pill) */
  .venue-card.card-3row .card-disrupt {
    justify-self: end;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    color: var(--muted);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }
  /* Shade gap = "less sun" → muted (default). Sun opportunity = "more sun
     later" → amber on the honey-dim chip (matches the sun-hours), so the two
     read as opposite signals at a glance. (More specific than the chip rule
     above, so the colour must be set here too.) */
  .venue-card.card-3row .card-disrupt-sun { color: var(--accent-on-light); }
  /* Shade chip text — deep cool blue (more specific than the .card-disrupt
     muted default above, so the colour is set here). */
  .venue-card.card-3row .card-disrupt-shade { color: var(--rain-ink); }
  /* Binding closing-time distinction on the row-2 anchor (subtle, no pill) */
  .venue-card.card-3row .card-anchor-closes { font-style: italic; }

  /* Lifted fill bar — into flow above the bottom padding, own rounded corners.
     Overrides the absolute/flush .card-compact rule above (same specificity,
     later in source). */
  .venue-card.card-3row .card-fillbar {
    position: static;
    margin-top: var(--space-sm);
    height: 3px;
    border-radius: var(--radius-sm);
    overflow: hidden;
  }

  /* ── Card timeline — full-width strip + labels row at the bottom. ──
     The block is inset by 18px on each side so the bar's start cap sits
     under the center of the leftmost label and there's room for the
     right-edge label to fit without clipping. */
  .card-timeline-block {
    margin-top: var(--space-sm);
    padding: 0 var(--space-xl);
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
  }
  .card-timeline {
    position: relative;
    width: 100%;
    /* Thick bar (matches the accept-panel timeline's 48px) — the detail card
       uses the same thick timeline, with its time labels kept above. Thick
       enough for the recessed "inset" channel (drawTimeline drawIndent). */
    height: 48px;
  }
  .card-timeline-labels {
    position: relative;
    height: 11px;
    font-size: 9.5px;
    font-weight: 500;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    line-height: 11px;
    opacity: 0.8;
  }
  .tl-label {
    position: absolute;
    transform: translateX(-50%);
    white-space: nowrap;
  }
  /* Highlight the current-time label so the user can read the bar's domain
     start at a glance. Pill labels stay in --muted; sun pill keeps accent. */
  .tl-label-current { color: var(--text); opacity: 0.9; }
  .tl-label-sol     { color: var(--accent); opacity: 0.95; }

  .timeline-track {
    position: relative;
    height: 12px;
    background: rgba(156,189,231,0.08);
    overflow: visible;
  }
  /* Canvas mini-timeline (and FTS canvas) — fills its container box.
     The canvas's bitmap is set programmatically; the `width`/`height` props
     here are CSS dimensions, the bitmap dpr-scales separately. */
  .card-timeline-canvas {
    display: block;
    width: 100%;
    height: 100%;
    background: transparent; /* canvas paints the bg itself */
  }

  /* Weather segments — flat by default, track clips outer edges */
  .timeline-track .wx {
    position: absolute;
    top: 0;
    bottom: 0;
    border-radius: 0;
  }

  /* Shadow boundary caps — rounded ends at shadow gap edges */
  .timeline-track .cap-l         { border-radius: var(--radius-none) 0 0 var(--radius-none); }
  .timeline-track .cap-r         { border-radius: 0 var(--radius-none) var(--radius-none) 0; }
  .timeline-track .cap-l.cap-r   { border-radius: var(--radius-none); }

  .card-emoji {
    font-size: var(--text-subtitle);
    flex-shrink: 0;
  }

  .card-badge {
    font-size: 10px;
    font-weight: 700;
    padding: var(--space-2xs) var(--space-md);
    border-radius: var(--radius-md);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    flex-shrink: 0;
  }

  .card-badge.sunny {
    background: rgba(245,194,94,0.2);
    color: var(--accent);
  }

  .card-badge.shaded {
    background: rgba(138,154,176,0.15);
    color: var(--muted);
  }
  .card-badge.opening-soon {
    background: rgba(156,189,231,0.15);
    color: #9CBDE7;
  }
  .card-badge.closing-soon {
    background: rgba(255,140,60,0.15);
    color: #ffaa55;
  }
  .card-badge.neutral {
    background: rgba(138,154,176,0.15);
    color: #9CBDE7;
  }

  .card-body {
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
  }
  /* Score badge pill — unified style per design spec */
  .card-sun-badge {
    flex-shrink: 0;
    font-size: var(--text-caption);
    font-weight: 600;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-none);
    white-space: nowrap;
    background: var(--accent-dim);
    color: var(--accent);
  }
  .card-sun-badge.tier-high,
  .card-sun-badge.tier-mid,
  .card-sun-badge.tier-low,
  .card-sun-badge.tier-poor {
    background: var(--accent-dim);
    color: var(--accent);
    border: none;
    box-shadow: none;
  }
  /* Sun status line */
  .card-sun-line {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    font-size: var(--text-caption);
    font-weight: 700;
    letter-spacing: 0.06em;
  }
  .card-sun-line.sunny        { color: var(--accent); }
  .card-sun-line.overcast     { color: rgba(165,170,178,0.85); }
  .card-sun-line.rainy        { color: rgba(156,189,231,0.85); }
  .card-sun-line.neutral      { color: rgba(165,170,178,0.85); }
  .card-sun-line.muted        { color: var(--muted); opacity: 0.55; }
  .card-sun-line.opening-soon { color: #9CBDE7; }
  .card-sun-line.closing-soon { color: #ffaa55; }

  .card-watch {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .card-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }

  .card-top-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-sm);
  }

  /* ── Card sun row: dial + duration left, label+time pushed right ─────── */
  .card-sun-row {
    display: flex;
    align-items: center;
    position: relative;
  }
  .card-sun-row::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 10%;
    height: 80%;
    width: 1px;
    background: rgba(156,189,231,0.15);
    pointer-events: none;
  }
  /* Left half: dial + duration centered in available space */
  .card-sun-left {
    flex: 1;
    display: flex;
    align-items: center;
    gap: var(--space-xs);
  }
  .card-dial-col {
    flex-shrink: 0;
  }
  .card-dial {
    display: block;
    opacity: 0.92;
    filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
  }
  .card-sun-dur {
    flex-shrink: 0;
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
  }
  .card-sun-dur.dur-sunny    { color: rgba(245,194,94,0.82); }
  .card-sun-dur.dur-cloudy   { color: rgba(156,189,231,0.65); }
  .card-sun-dur.dur-overcast { color: rgba(165,170,178,0.82); }
  .card-sun-dur.dur-rainy    { color: rgba(156,189,231,0.82); }
  /* Right block: left-aligned within its half */
  .card-sun-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: var(--space-2xs);
    min-width: 0;
    padding-left: var(--space-md);
  }
  .card-sun-label {
    font-family: 'Inter', sans-serif;
    font-size: 9px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #9CBDE7;
  }
  .card-sun-info.sunny        .card-sun-label { color: rgba(245,194,94,0.65); }
  .card-sun-info.overcast     .card-sun-label { color: rgba(165,170,178,0.65); }
  .card-sun-info.rainy        .card-sun-label { color: rgba(156,189,231,0.65); }
  .card-sun-info.neutral      .card-sun-label { color: rgba(165,170,178,0.65); }
  .card-sun-info.opening-soon .card-sun-label { color: rgba(156,189,231,0.65); }
  .card-sun-info.closing-soon .card-sun-label { color: rgba(255,170,85,0.65); }
  .card-sun-time {
    font-family: 'Inter', sans-serif;
    font-weight: 700;
    font-size: 24px;
    color: #FFF2EB;
    line-height: 1;
    letter-spacing: -0.02em;
  }
  .card-sun-info.sunny    .card-sun-time { color: var(--accent); }
  .card-sun-info.overcast .card-sun-time { color: rgba(165,170,178,0.9); }
  .card-sun-info.rainy    .card-sun-time { color: rgba(156,189,231,0.9); }
  .card-sun-info.neutral  .card-sun-time { color: rgba(165,170,178,0.9); }

  .card-score-num {
    flex-shrink: 0;
    font-family: 'Inter', sans-serif;
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    text-align: right;
  }
  .card-score-num span {
    display: block;
    font-family: 'Inter', sans-serif;
    font-size: 8px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    opacity: 0.5;
    margin-top: 1px;
  }
  .card-score-num.tier-high { color: var(--accent); }
  .card-score-num.tier-mid  { color: rgba(156,189,231,0.8); }
  .card-score-num.tier-low  { color: #a8a482; }
  .card-score-num.tier-poor { color: #78a0d8; }

  /* Bloom glow — radiates from the top-right corner, gold→blue by score */
  .card-bloom {
    position: absolute;
    top: 0; right: 0;
    width: 190px; height: 160px;
    pointer-events: none;
  }
  .card-bloom.tier-high,
  .card-bloom.tier-mid,
  .card-bloom.tier-low,
  .card-bloom.tier-poor { background: none; }

  .card-dial-label {
    font-size: 10px;
    font-weight: 600;
    color: rgba(245,194,94,0.75);
    letter-spacing: 0.1px;
  }

  /* ── Skeleton placeholder cards ──────────────────────────────────── */
  /* Shown in two situations:
     1. After-sunset / no-sun-left empty state — passive placeholder.
     2. Time-slider scrub state — strong "list is being recomputed" signal,
        replaces the real cards while the user drags the slider.
     Layout mirrors the real .venue-card (card-row1 + card-meta + timeline
     placeholder) so the swap doesn't reshape the list height. */
  /* Skeleton card keeps the same opaque Delft box as a real card (footprint
     match — see the shared min-height below), but its blocks are cream-at-low-
     opacity (absence of content) and the loading motion is a horizontal sheen
     sweep, NOT a whole-card opacity pulse. Reduced-motion → static blocks. */
  .venue-card.skeleton {
    pointer-events: none;
    overflow: hidden;          /* clip the sweeping sheen to the card */
    animation: skeletonFadeIn 0.18s ease-out both;
  }
  @keyframes skeletonFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  .venue-card.skeleton::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Straight (non-diagonal) translucent sheen — diagonal would read as the
       shade stripe, which means shade, not loading. */
    background: linear-gradient(90deg, transparent 0%, var(--skel-sheen) 50%, transparent 100%);
    transform: translateX(-100%);
    animation: skeletonSheen 1.4s ease-in-out infinite;
  }
  @keyframes skeletonSheen {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
  }
  @media (prefers-reduced-motion: reduce) {
    .venue-card.skeleton::after { animation: none; opacity: 0; }
  }
  .skel {
    background: var(--skel-block);
    border-radius: var(--radius-none);
  }
  .skel-name {
    height: 14px;
    border-radius: var(--radius-none);
  }
  .skel-sub {
    height: 10px;
    border-radius: var(--radius-none);
  }
  .skel-duration {
    width: 46px;
    height: 13px;
    border-radius: var(--radius-none);
    flex-shrink: 0;
  }
  .skel-meta {
    height: 9px;
    border-radius: var(--radius-none);
  }
  .skel-timeline {
    width: 100%;
    height: 12px;
    margin-top: var(--space-sm);
    border-radius: var(--radius-none);
    background: var(--skel-block);
  }

  /* ── Arc frozen state (after sunset, today only) ──────────────────── */
  .arc-frozen #time-presets-row { pointer-events: none; opacity: 0.35; }

  /* ── Expanded section (visible when .selected) ─────────────────────── */
  .card-expanded {
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transition: max-height 0.22s ease, opacity 0.18s ease;
  }

  .venue-card.selected .card-expanded {
    max-height: 120px;
    opacity: 1;
  }

  .card-score-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-md);
    margin-bottom: var(--space-xs);
  }

  .score-detail-inline {
    font-size: var(--text-caption);
    color: var(--muted);
    opacity: 0.8;
  }

  .card-address {
    font-size: var(--text-caption);
    color: var(--muted);
    opacity: 0.7;
    margin-bottom: var(--space-md);
  }

  .card-actions {
    display: flex;
    gap: var(--space-md);
    margin-top: var(--space-xs);
    margin-bottom: var(--space-2xs);
  }

  .card-action-btn {
    font-size: var(--text-caption);
    font-weight: 600;
    padding: var(--space-xs) var(--space-lg);
    border-radius: var(--radius-sm);
    border: 1px solid rgba(156,189,231,0.22);
    background: rgba(156,189,231,0.1);
    color: var(--text);
    cursor: pointer;
    text-decoration: none;
    letter-spacing: 0.2px;
    transition: background 0.12s;
  }

  .card-action-btn:hover {
    background: rgba(156,189,231,0.2);
  }

  #hover-tooltip {
    position: absolute;
    z-index: 1100;
    background: var(--glass-action-bg);
    backdrop-filter: var(--glass-blur-action);
    -webkit-backdrop-filter: var(--glass-blur-action);
    border: var(--glass-border);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    pointer-events: none;
    display: none;
    width: 220px;
    box-shadow: 0 4px 24px rgba(0,0,0,0.5);
  }

  #hover-tooltip.visible { display: block; }

  .ht-name {
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 600;
    color: var(--accent);
    margin-bottom: var(--space-2xs);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .ht-meta {
    font-size: var(--text-caption);
    color: var(--muted);
    display: flex;
    gap: var(--space-xs);
    align-items: center;
  }

  .ht-status {
    font-size: var(--text-caption);
    font-weight: 600;
    margin-top: var(--space-xs);
    display: flex;
    gap: var(--space-sm);
    align-items: baseline;
  }
  .ht-status-main { white-space: nowrap; }
  .ht-status-sub  { font-weight: 500; opacity: 0.65; white-space: nowrap; }

  .ht-status.state-sun    { color: var(--accent); }
  .ht-status.state-shadow { color: rgba(165,170,178,0.9); }
  .ht-status.state-rain   { color: rgba(156,189,231,0.9); }
  .ht-status.state-done   { color: var(--muted); }
  .ht-status.state-closed { color: var(--muted); }

  .ht-timeline { margin-top: var(--space-md); }
  /* Card timeline reused inside tooltip */
  #hover-tooltip .card-timeline { margin-top: var(--space-sm); }

  .ht-tl-track {
    height: 5px;
    background: rgba(156,189,231,0.2);
    border-radius: var(--radius-none);
    position: relative;
    margin-bottom: var(--space-xs);
    overflow: visible;
  }

  .ht-tl-seg {
    position: absolute;
    top: 0;
    height: 100%;
    background: var(--accent);
    border-radius: var(--radius-none);
    opacity: 0.85;
  }

  .ht-tl-needle {
    position: absolute;
    top: -3px;
    width: 2px;
    height: 11px;
    background: #fff;
    border-radius: var(--radius-none);
    transform: translateX(-50%);
    box-shadow: 0 0 3px rgba(255,255,255,0.5);
  }

  .ht-tl-labels {
    display: flex;
    justify-content: space-between;
    font-size: 9px;
    color: var(--muted);
    opacity: 0.6;
    margin-top: var(--space-2xs);
  }

  #map-toast {
    position: absolute;
    top: 16px;
    left: 50%;
    transform: translateX(-50%) translateY(-8px);
    z-index: 950;
    background: var(--glass-action-bg);
    backdrop-filter: var(--glass-blur-action);
    -webkit-backdrop-filter: var(--glass-blur-action);
    border: var(--glass-border);
    border-radius: var(--radius-md);
    box-shadow: 0 4px 20px rgba(0,0,0,0.40);
    padding: var(--space-md) var(--space-lg);
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s, transform 0.2s;
    display: flex;
    align-items: center;
    gap: var(--space-md);
  }
  #map-toast.visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
  @keyframes toast-spin {
    to { transform: rotate(360deg); }
  }
  #map-toast::before {
    content: '';
    width: 12px;
    height: 12px;
    border: 1.5px solid rgba(156,189,231,0.2);
    border-top-color: rgba(245,194,94,0.7);
    border-radius: 50%;
    animation: toast-spin 0.8s linear infinite;
    flex-shrink: 0;
  }
  #map-toast.done::before {
    border: 1.5px solid rgba(100,255,180,0.5);
    animation: none;
  }

  /* ── Mobile — bottom sheet ────────────────────────────────────────── */
  @media (max-width: 639px) {
    /* Brand floats bottom-left just above the venue-list sheet, mirroring
       #locate-btn's sheet tracking (peek-h / fts-bottom + the drag handler).
       Fades out at fullscreen (map fully covered). */
    #floating-brand {
      top: auto;
      left: 12px;
      right: auto;
      bottom: calc(var(--peek-h, 252px) + 14px);
    }
    body.fts #floating-brand {
      bottom: calc(var(--fts-bottom, 160px) + 6px);
    }
    body:has(#panel.mobile-fullscreen) #floating-brand {
      opacity: 0;
    }

    #floating-search {
      left: 6px;
      right: 6px;
      width: auto;
      top: 12px;
    }

    /* Stage 4b-1: Profile panel uses the same bottom-sheet shape on mobile
       as on desktop. No mobile-specific position override needed — base
       rule handles it. */
    /* Stage 4b-1: Sub-view header bar. No solid background or backdrop
       blur — the sheet itself provides those; a second layer on top
       reads as a separate slab. Just the close/back button + title
       floating on the sheet glass. */
    .profile-panel-mobile-bar {
      position: sticky;
      top: 0;
      z-index: 1;
      display: flex;
      align-items: center;
      gap: var(--space-md);
      /* Top pad reserves the iOS safe area (notch / Dynamic Island)
         plus the visual breathing room above the back button. */
      padding: calc(env(safe-area-inset-top, 0px) + var(--space-lg)) var(--space-lg) var(--space-md);
      background: none;
      border-bottom: none;
    }
    .profile-panel-mobile-back {
      background: var(--glass-action-bg);
      border: var(--glass-border);
      box-shadow: none;
      border-radius: 50%;
      width: 34px; height: 34px;
      display: flex; align-items: center; justify-content: center;
      color: var(--text);
      cursor: pointer;
      flex-shrink: 0;
    }
    .profile-panel-mobile-back:hover { background: rgba(155,169,188,0.22); }
    .profile-panel-mobile-title {
      font-size: var(--text-body);
      font-weight: 600;
      color: var(--text);
      letter-spacing: -0.01em;
    }
    /* Hide the bottom qc-wrap and other floating chrome while the panel is up */
    body.profile-panel-open #qc-wrap,
    body.profile-panel-open #floating-search,
    body.profile-panel-open #floating-brand,
    body.profile-panel-open #top-strip,
    body.profile-panel-open #fts,
    body.profile-panel-open #panel,
    body.profile-panel-open #detail-panel {
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.25s ease;
    }

    /* Logged-out: mobile typography overrides. Layout (flex, padding) lives in
       the global #profile-panel.logged-out rules above. */
    #profile-panel.logged-out .login-slide-icon { width: 72px; height: 72px; margin-bottom: var(--space-xl); }
    #profile-panel.logged-out .login-slide-icon svg { width: 56px; height: 56px; }
    #profile-panel.logged-out .login-hero-title {
      font-size: var(--text-display); max-width: 320px; margin: 0 auto var(--space-2xl);
    }
    #profile-panel.logged-out .login-slide-title { font-size: 17px; margin-bottom: var(--space-md); }
    #profile-panel.logged-out .login-slide-body { max-width: 320px; font-size: 14px; }

    #zoom-debug {
      left: 6px;
      right: 6px;
      top: calc(12px + 46px + 8px + 50px);
      transform: none;
      width: auto;
      text-align: center;
    }

    /* Venue list — peek state position driven by --peek-h CSS var.
       Position-based animation (bottom + height) instead of transform.
       Transform-based slides on iOS WebKit caused the panel's
       backdrop-filter to drop for a few frames as the compositor layer
       recomposited mid-animation — the visible "transparency flash" the
       user reported. `bottom` and `height` are layout properties, not
       compositor ones, so backdrop-filter stays stable across the slide.
       Slightly more layout cost per frame but the slide is short and
       the glass look stays consistent end-to-end. */
    #panel {
      position: fixed;
      top: auto;
      left: 0;
      right: 0;
      width: 100%;
      /* 50% of the JS-tracked visible viewport — see --app-h. 50svh
         is the fallback until the visualViewport probe runs. */
      height: calc(var(--app-h, 100svh) * 0.5);
      /* Negative bottom in peek state: the panel's bottom edge sits
         (panel-height − peek-h) below the viewport bottom, so only
         peek-h is visible. Equivalent to the old translateY rule. */
      bottom: calc(var(--peek-h, 252px) - calc(var(--app-h, 100svh) * 0.5));
      border-radius: var(--radius-lg) var(--radius-lg) 0 0;
      border-left: none;
      border-right: none;
      border-bottom: none;
      transition:
        bottom 0.34s cubic-bezier(0.25, 0.9, 0.4, 1),
        height 0.34s cubic-bezier(0.25, 0.9, 0.4, 1);
    }

    /* Expanded state — bottom 0 so the full 50svh panel is in view. */
    #panel.mobile-expanded {
      bottom: 0;
      z-index: 910;  /* zoom-jog (900) sits behind list; locate-btn (920) stays on top */
    }

    /* Empty-state — when #venue-list contains only the .empty-state block, the
       50svh-tall panel leaves a tall dark band below the content.
       Shrink the panel to its natural content height so the map
       shows through above the home-indicator zone. iOS 16.4+ +
       Chrome 105+ have :has(); we already require those for push. */
    #panel.mobile-expanded:has(#venue-list > .empty-state:only-child) {
      height: auto;
      max-height: calc(var(--app-h, 100svh) * 0.5);
    }

    /* Fully hidden (covered by detail panel) — slides down behind the morphed card.
       Transition matches the panel morph (320ms) so list and detail move together. */
    #panel.mobile-hidden {
      /* Position-based slide-off (matches the new bottom-based animation
         scheme). Bottom set so the entire panel sits BELOW the viewport
         plus a 20 px buffer. */
      bottom: calc(-1 * calc(var(--app-h, 100svh) * 0.5) - 20px) !important;
      opacity: 0 !important;
      pointer-events: none !important;
      transition: bottom 0.32s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.22s ease !important;
    }

    #panel-toggle-icon { display: none; }

    /* FTS — Stage 2b: inside #panel, flow-positioned. Mobile override
       just resets any leftover fixed-position properties. */
    body.fts #fts {
      left: auto; right: auto; bottom: auto;
      width: auto;
      will-change: auto;
    }


    /* Locate button — sits ~6px above the panel top edge. FTS used to
       float ABOVE the panel and locate-btn stacked above it; now FTS
       lives inside the panel, so locate-btn just hugs the panel top.
       Restore the 40 px touch target on mobile (desktop default is 34). */
    #locate-btn {
      width: 40px;
      height: 40px;
      bottom: calc(var(--peek-h, 252px) + 14px);
      right: 12px;
    }
    #locate-btn .locate-icon { width: 20px; height: 20px; margin: -10px 0 0 -10px; }
    body.fts #locate-btn { bottom: calc(var(--fts-bottom, 160px) + 6px); right: 12px; transition: bottom 0.34s cubic-bezier(0.25, 0.9, 0.4, 1), opacity 0.25s ease; }

    /* Zoom jog — stacked 10px above the 40px locate button. */
    #zoom-jog { right: 12px; }
    #zoom-jog { bottom: calc(var(--peek-h, 252px) + 14px + 40px + 10px); }
    body.fts #zoom-jog { bottom: calc(var(--fts-bottom, 160px) + 6px + 40px + 10px); transition: bottom 0.34s cubic-bezier(0.25, 0.9, 0.4, 1), opacity 0.25s ease; }
    /* Hide the zoom-jog while the search keyboard is up — it was re-anchoring
       into the search bar as the viewport shrank. */
    body.search-active #zoom-jog { opacity: 0; pointer-events: none; }
    /* Mobile: restore the larger touch-friendly zoom-jog (40×108 with 30 px thumb). */
    .zj-track { width: 40px; height: 108px; border-radius: var(--radius-lg); }
    .zj-thumb { width: 30px; height: 30px; }

    /* FTS thumb sized up on mobile (was 32, tried 40 — settled on 36px).
       Balance: 36 is a comfortable touch target without feeling oversized
       relative to the 40px FTS track. */
    #fts-thumb { width: 36px; height: 36px; }

    /* Handle: grabber pill only — Google-Maps-tight spacing so the
       header content sits high in the panel.
       Was 16/0/4 (20px total); now 8/0/4 (12px) — reclaims 8px for
       the header content above. */
    #panel-handle {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: var(--space-md) 0 var(--space-xs);
      flex-shrink: 0;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }

    #panel-handle-pill {
      width: 36px;
      height: 4px;
      background: rgba(17,30,56,0.22);
      border-radius: var(--radius-none);
    }

    /* Venue peek: top of first card visible in collapsed state.
       12px padding-top so the card body sits below the panel chrome
       (handle + chip row above) instead of butting up against it.
       Bottom fade signals there's more content below. */
    #venue-peek {
      flex-shrink: 0;
      /* Top pad tightened — chip row that used to sit above is hidden;
         the action row directly above already provides spacing. */
      padding: var(--space-sm) var(--space-lg) 0;
      overflow: hidden;
      max-height: 104px;
      /* Fade only the very bottom edge as a soft "more below" hint —
         not the whole lower third (was 60→100). Keeps the card body
         solid + readable, just feathers the cut. */
      -webkit-mask-image: linear-gradient(to bottom, black 85%, transparent 100%);
      mask-image: linear-gradient(to bottom, black 85%, transparent 100%);
    }

    /* Hide peek when list is visible */
    #panel.mobile-expanded #venue-peek,
    #panel.mobile-fullscreen #venue-peek {
      display: none;
    }

    /* Venue card inside peek inherits list card styles — disable interaction
       so drag-to-expand works. The onclick won't fire thanks to pointer-events. */
    #venue-peek .venue-card {
      pointer-events: none;
      margin-bottom: 0;
    }

    /* Hide venue-count and the now-empty venue-header row on mobile */
    #venue-count { display: none; }
    #venue-header { display: none; }

    /* In peek mode, hide the in-list bucket section headers so the peek
       height stays as low as possible — the user just needs to see the
       first card or two. The headers re-appear once the panel expands. */
    /* Headers are collapsed in peek; the max-height transition (above) animates
       them open/closed as the panel slides between peek and expanded. The
       reveal rides WITH the slide (concurrent), matching the well-timed
       collapse — no is-sliding deferral, which made the reveal land late. The
       smooth max-height animation is what kills the old jitter (the instant
       display none→block toggle was the culprit), so the guard isn't needed. */
    #panel:not(.mobile-expanded):not(.mobile-fullscreen):not(.mobile-hidden) .venue-section-header {
      max-height: 0;
      opacity: 0;
      padding-top: 0;
      padding-bottom: 0;
    }

    /* The unified day-header is visible in every panel state — peek
       mode included. Horizontal padding matches the mobile #venue-list
       (16px) so the row 1 / row 2 content edges align with the venue
       cards' outer edges below. */
    #day-header { padding: var(--space-sm) var(--space-xl) var(--space-md) !important; }

    #panel-header { padding-top: 0; }

    #venue-list {
      /* Tight top padding so the gap above the first "In sun at X" header
         matches the ~8px header rhythm (was --space-lg/12px, which left a
         too-large gap below the filter pills). Bottom padding 88px so the
         FTS popup (above-thumb tooltip) doesn't cover the last card. */
      padding: var(--space-2xs) var(--space-xl) 88px;
      overscroll-behavior-y: none;
      /* No top mask — clean edge by default. A drop shadow under the sticky
         header fades in on scroll (see #panel.is-scrolled rule). */
    }
    /* Peek: the section headers are collapsed, so the first CARD sits under the
       filter pills. The tight 2px above is right for the expanded header, but
       too cramped for the card in peek — give it a touch more breathing room. */
    #panel:not(.mobile-expanded):not(.mobile-fullscreen):not(.mobile-hidden) #venue-list {
      padding-top: var(--space-sm);
    }

    .venue-card {
      padding: var(--space-lg) var(--space-xl);
      margin-bottom: var(--space-md);
      border-radius: var(--radius-md);
    }

    .card-badge   { font-size: var(--text-caption); padding: var(--space-xs) var(--space-lg); }
    .area-chip    { font-size: 12px; padding: var(--space-sm) var(--space-lg); }
    .sort-btn     { font-size: var(--text-body); padding: var(--space-lg) var(--space-xl); }

    .card-action-btn { font-size: 12px; padding: var(--space-md) var(--space-xl); }
    .card-actions    { gap: var(--space-md); }

    .card-expanded { max-height: 0; }
    .venue-card.selected .card-expanded { max-height: 160px; }

    /* Hide Mapbox zoom/compass controls — gesture-only on mobile */
    .mapboxgl-ctrl-bottom-right,
    .mapboxgl-ctrl-bottom-left,
    .mapboxgl-ctrl-top-right,
    .mapboxgl-ctrl-top-left,
    .mapboxgl-ctrl-group,
    .mapboxgl-ctrl-attrib { display: none !important; }

    /* Peek state: the visible portion (handle, sun-header, venue-peek) must
       capture touch for drag-to-expand. touch-action: none tells the browser
       not to handle pan gestures natively — our JS touchmove does it. */
    #panel:not(.mobile-expanded):not(.mobile-hidden):not(.mobile-fullscreen) {
      touch-action: none;
    }

    /* Fullscreen state: panel covers everything */
    #panel.mobile-fullscreen {
      bottom: 0;
      height: var(--app-h, 100svh);
      border-radius: 0;
      z-index: 910;
    }

    /* Kill expensive effects while the user is actively dragging the panel */
    #panel.panel-dragging {
      transition: none !important;
    }
    #panel.panel-dragging .venue-card {
      animation: none !important;
      transition: none !important;
    }

    /* Containment isolates the panel's paint+layout from the rest of
       the document so transforms don't trigger full-page repaints.
       Big win on iOS Chrome PWA where panel sliding was visibly
       laggy through backdrop-filter recompositing the map under it.

       NOTE: `paint` containment clips children to the panel's box,
       which crops the FTS scrub popup as the panel expands. Keep
       layout+style (still cuts most of the recomposite cost) and
       drop paint so the popup can hover above the panel top edge. */
    #panel { contain: layout style; }

    /* Drop backdrop-filter during slide animations. The blur effect
       is what makes the panel "look like glass"; re-blurring at 60fps
       while the underlying map scrolls is the actual cost. JS toggles
       .is-sliding around the transition.

       v2 swapped to a near-opaque rgba(17,30,56,0.95) fallback, which
       was much darker than the resting glass and read as a visible
       opacity *jump* at the start and end of the slide — the panel
       darkened, then lightened. The fallback now sits near the glass
       baseline (≈0.55 alpha) so the swap is imperceptible while still
       buying us the blur-recompute escape during the transform. */
    /* Keep the backdrop-filter (frosted glass) DURING slides. The old
       optimisation dropped it + showed a flat fallback bg, but the resting
       panel has an alpha-0 background — it's PURE blur — so no flat colour can
       stand in for it: navy read as a blue flash, cream read as transparent.
       Re-blurring the (static) map under the moving panel is cheap enough on
       modern devices; end-to-end visual consistency matters more. */
    #panel.is-sliding {
      /* intentionally empty — panel keeps its .glass-panel frosted look */
    }
  }

  /* ── Edit mode overlay — bottom-anchored sheet on every viewport ─────────── */
  #edit-overlay {
    position: fixed;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 920;
    display: none;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
    width: min(480px, calc(100vw - 32px));
    pointer-events: auto;
  }

  /* Cream surface (DESIGN.md "light/chrome panel") — opaque enough to read
     over bright satellite imagery, with INK text + light-chrome controls.
     The transparent frost was illegible over satellite. */
  #edit-banner {
    position: relative;
    background: rgba(255,242,235,0.50);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: 1px solid var(--line-l);
    border-radius: var(--radius-lg);
    padding: var(--space-2xs) var(--space-lg) var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    box-shadow: var(--shadow-2);
    width: 100%;
    color: var(--content-text);
  }

  /* Grabber doubles as a collapse toggle — a real button with a hit area. */
  .edit-grabber {
    width: 44px;
    height: 16px;
    padding: 0;
    border: none;
    background: transparent;
    margin: 0 auto;
    flex-shrink: 0;
    display: block;
    cursor: pointer;
    position: relative;
  }
  .edit-grabber::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 36px;
    height: 4px;
    border-radius: var(--radius-pill);
    background: var(--line-l-strong);
  }

  /* Collapsed: hide the controls row, keep grabber + primary action. */
  #edit-overlay.edit-collapsed #edit-controls { display: none; }

  /* Floating polygon-tool PILL — one vertical pill above the zoom jog, same
     34px width + radius, cream-frost. Glyphs sit inside like the zoom-jog
     labels (no per-icon squircles). Active vertex tool = honey glyph. */
  /* Exact clone of .zj-track (same width, radius, background, border, blur,
     shadow) so it stacks flush above the zoom jog. Glyphs sit inside like
     .zj-label — no per-button box/outline. */
  #edit-tools-float { display: none; }
  body.edit-mode #edit-tools-float {
    display: flex;
    flex-direction: column;
    align-items: center;
    position: fixed;
    right: 12px;                                       /* same as #zoom-jog */
    bottom: calc(var(--edit-zoom-bottom, 240px) + 92px + var(--space-sm));
    z-index: 921;
    width: 34px;
    padding: var(--space-sm) 0;
    background: rgba(255,242,235,0.50);                /* same as edit-mode .zj-track */
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: var(--glassctl-border);                    /* same border as the zoom jog */
    border-radius: var(--radius-lg);
    box-shadow: var(--panel-shadow);
    transition: bottom 0.22s ease;
  }
  .edit-tool-icon {
    width: 34px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 0;
    outline: none;
    -webkit-tap-highlight-color: transparent;
    color: var(--ink-muted);
    cursor: pointer;
    transition: color 0.12s, transform 0.1s;
  }
  .edit-tool-icon:hover  { color: var(--content-text); }
  .edit-tool-icon:active { transform: scale(0.9); }
  .edit-tool-icon.active { color: var(--accent); }
  .edit-tool-icon[hidden] { display: none; }
  .edit-tool-icon:disabled { opacity: 0.3; pointer-events: none; cursor: default; }
  /* Hide the whole float stack when neither tool is visible (e.g. courtyard
     with no override) so no empty frosted box floats over the work area. */
  body.edit-mode #edit-tools-float:not(:has(.edit-tool-icon:not([hidden]))) {
    display: none;
  }
  /* GL Draw is added with controls:{} (custom chrome drives editing), so its
     control container renders empty — hide the stray box in edit mode. */
  body.edit-mode .mapboxgl-ctrl-group:empty { display: none; }

  /* Desktop: lay the tool stack out HORIZONTALLY, centred and hovering just
     above the bottom edit panel. (Mobile keeps the vertical stack on the right,
     flush above the zoom jog.) --edit-zoom-bottom = panel top + gap, set by the
     edit-banner ResizeObserver. */
  @media (min-width: 640px) {
    body.edit-mode #edit-tools-float {
      flex-direction: row;
      right: auto;
      left: 50%;
      transform: translateX(-50%);
      bottom: calc(var(--edit-zoom-bottom, 240px) + var(--space-sm));
      width: auto;
      padding: var(--space-xs) var(--space-sm);
      gap: var(--space-xs);
    }
    body.edit-mode .edit-tool-icon { width: 32px; height: 32px; }
  }

  /* Tools row: AI helper chips + vertex tools. Lives IN-FLOW inside the edit
     sheet (no longer floats over the map work area). Hidden until the edit
     chips are populated; shown when any chip is visible. */
  #edit-tools-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    justify-content: flex-start;
    width: 100%;
  }

  /* Collapse the row when every chip inside is hidden (no tools available) so
     it doesn't leave an empty gap in the sheet. */
  #edit-tools-row:not(:has(.edit-chip:not([hidden]))) { display: none; }

  /* Light-chrome outline chip on the cream sheet: ink text, Delft silhouette;
     active fills Delft Blue + cream (DESIGN.md "controls step to Delft Blue"). */
  .edit-chip {
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    border-radius: var(--radius-pill);
    color: var(--content-text);
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 500;
    padding: var(--space-sm) var(--space-lg);
    cursor: pointer;
    transition: color 0.12s, border-color 0.12s, background 0.12s;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    white-space: nowrap;
    line-height: 1;
    height: 32px;
  }

  .edit-chip:hover { background: var(--line-l-faint); }

  /* Active = Delft fill + cream text (controls step to Delft Blue). */
  .edit-chip.active {
    color: var(--text);
    border-color: transparent;
    background: var(--surface-content);
  }

  .edit-chip[hidden] { display: none; }

  /* Type dropdown + map-mode toggle: both content-sized, type pinned left,
     toggle pinned right (matches the chip rhythm — no stretched-pill empty
     space). */
  #edit-controls {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    justify-content: space-between;
  }

  #edit-type-dropdown {
    position: relative;
    flex: 0 0 auto;
  }

  .edit-type-trigger {
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    border-radius: var(--radius-pill);
    color: var(--content-text);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 600;
    padding: 0 var(--space-lg);
    height: 36px;
    cursor: pointer;
    outline: none;
    display: inline-flex;
    align-items: center;
    gap: var(--space-md);
    transition: border-color 0.12s, background 0.12s;
  }

  .edit-type-trigger:hover { background: var(--line-l-faint); }
  .edit-type-trigger[aria-expanded="true"] {
    border-color: var(--line-l-strong);
    background: var(--line-l-faint);
  }
  .edit-type-trigger svg { color: var(--ink-muted); transition: transform 0.18s ease; }
  .edit-type-trigger[aria-expanded="true"] svg { transform: rotate(180deg); }

  .edit-type-list {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    width: max-content;
    min-width: 100%;
    background: var(--surface-raised);
    border: 1px solid var(--line-l);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
    margin: 0;
    padding: var(--space-xs);
    list-style: none;
    z-index: 5;
  }

  .edit-type-list li {
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 500;
    color: var(--content-text);
    padding: var(--space-md) var(--space-lg);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
    line-height: 1.2;
  }

  .edit-type-list li:hover { background: var(--line-l-faint); }
  .edit-type-list li.active { color: var(--text); background: var(--surface-content); }

  /* ── Map-mode segmented switch (3D-kart ↔ Satellitt) ─────────────────────── */
  .map-toggle {
    position: relative;
    flex: 0 0 auto;
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    border-radius: var(--radius-pill);
    height: 36px;
    padding: var(--space-2xs);
    cursor: pointer;
    display: inline-flex;
    align-items: stretch;
    overflow: hidden;
    transition: border-color 0.12s;
  }

  .map-toggle:hover { border-color: rgba(156,189,231,0.35); }

  .map-toggle .map-toggle-thumb {
    position: absolute;
    top: 3px;
    bottom: 3px;
    /* width set in JS based on the wider option so the thumb hugs the label */
    width: var(--map-toggle-thumb-w, 50%);
    left: var(--map-toggle-thumb-x, 3px);
    border-radius: var(--radius-pill);
    background: rgba(245,194,94,0.14);
    border: 1px solid rgba(245,194,94,0.45);
    box-shadow: inset 0 1px 0 rgba(255,242,235,0.18);
    transition: left 0.22s cubic-bezier(0.4, 0, 0.2, 1), width 0.22s ease;
    pointer-events: none;
    z-index: 0;
  }

  .map-toggle .map-toggle-option {
    position: relative;
    z-index: 1;
    padding: 0 var(--space-lg);
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 600;
    color: var(--ink-muted);
    white-space: nowrap;
    transition: color 0.18s ease;
    user-select: none;
  }

  .map-toggle[aria-checked="true"]  .map-toggle-option[data-option="satellite"],
  .map-toggle[aria-checked="false"] .map-toggle-option[data-option="map"] {
    color: var(--content-text);
  }

  /* ── Action row: vertical stack — primary pill on top, ghost link below ──── */
  /* Nav row: cancel (ghost, left) · back · skip (secondary glass, right). */
  /* Secondary nav buttons on the cream sheet: light-chrome outline + ink.
     Back is icon-only (square); skip carries a label. */
  .edit-nav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2xs);
    height: 48px;
    padding: 0 var(--space-lg);
    border-radius: var(--radius-pill);
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    font: 600 var(--text-label) 'Inter', sans-serif;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.12s, border-color 0.12s, transform 0.1s;
  }
  .edit-nav-btn:hover  { background: var(--line-l-faint); }
  .edit-nav-btn:active { transform: scale(0.96); }

  /* Flow row: back (icon) · primary (flex, honey) · skip. */
  #edit-action-row {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-sm);
  }
  #edit-action-row .primary-pill { flex: 1 1 auto; }
  #edit-action-row .edit-nav-btn:first-child { width: 48px; padding: 0; }

  /* Primary CTA — Tier 4 honey, solid + elevated (DESIGN.md button roles). */
  .primary-pill {
    width: 100%;
    height: 48px;
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: var(--accent-on);
    font-family: 'Inter', sans-serif;
    font-size: 15px;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.12s, opacity 0.12s;
    border: none;
    line-height: 1;
    box-shadow: var(--shadow-1);
  }

  .primary-pill:hover { background: var(--accent-hover); }
  .primary-pill:active { background: var(--accent-active); }

  .primary-pill[disabled] {
    opacity: 0.40;
    cursor: not-allowed;
  }

  /* No-changes state (non-audit only) — quiet light-chrome outline on cream. */
  .primary-pill.is-no-changes {
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    box-shadow: none;
  }

  .primary-pill.is-no-changes:hover {
    background: var(--line-l-faint);
  }

  /* Context (Feil bygning · Street View) + verdict (Usikker · Arkivér) rows —
     secondary light-chrome buttons; never the honey CTA. Active = Delft + cream
     (DESIGN.md selected-state rule). */
  #edit-context-row,
  #edit-verdict-row {
    display: flex;
    gap: var(--space-sm);
  }
  .edit-secondary-btn {
    flex: 1 1 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    height: 40px;
    padding: 0 var(--space-md);
    border-radius: var(--radius-pill);
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 600;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.1s;
  }
  .edit-secondary-btn svg { flex: 0 0 auto; }
  .edit-secondary-btn:hover  { background: var(--line-l-faint); }
  .edit-secondary-btn:active { transform: scale(0.97); }
  .edit-secondary-btn.active {
    background: var(--surface-content);
    border-color: var(--surface-content);
    color: var(--text);
  }

  /* Archive-reason chips — revealed under the verdict row when "Arkivér" is tapped. */
  #edit-archive-reasons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
  }
  #edit-archive-reasons[hidden] { display: none; }
  .edit-reason-chip {
    height: 32px;
    padding: 0 var(--space-md);
    border-radius: var(--radius-pill);
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 500;
    cursor: pointer;
    transition: background 0.12s;
  }
  .edit-reason-chip:hover { background: var(--line-l-faint); }

  /* Avbryt: tertiary ghost — never competes with the primary CTA. */
  .ghost-link {
    background: transparent;
    border: none;
    color: var(--text-secondary);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 500;
    height: 36px;
    cursor: pointer;
    padding: 0 var(--space-lg);
    align-self: center;
    transition: color 0.12s;
  }

  .ghost-link:hover { color: var(--text); }

  /* Mobile: bottom sheet flush to viewport edges */
  @media (max-width: 639px) {
    #edit-overlay {
      bottom: 0;
      left: 0;
      right: 0;
      transform: none;
      width: 100%;
      /* Lift the sheet clear of the home indicator (iOS PWA reports the gap
         via --app-bottom-inset; browsers via safe-area-inset). Was clipping
         the primary CTA at the bottom edge. */
      padding: 0;
      gap: var(--space-md);
    }
    #edit-banner {
      border-radius: var(--radius-lg) var(--radius-lg) 0 0;
      padding: var(--space-2xs) var(--space-xl)
               calc(max(env(safe-area-inset-bottom, 0px), var(--app-bottom-inset, 0px)) + var(--space-lg));
      border-left: none;
      border-right: none;
      border-bottom: none;
      gap: var(--space-md);
    }
    .edit-type-list { padding: var(--space-xs); }
  }

  /* Edit-mode: locate-btn is hidden. The zoom-jog sits at the same vertical
     anchor as the FTS pill / chips — i.e. FTS_GAP above the editor banner top —
     on every viewport. (--edit-zoom-bottom is set by the ResizeObserver to
     the banner top + FTS_GAP.) */
  body.edit-mode #zoom-jog {
    /* !important so the edit anchor wins over body.fts #zoom-jog (same
       specificity, +56px offset) — otherwise the jog floats above the sheet. */
    bottom: var(--edit-zoom-bottom, 240px) !important;
    transition: bottom 0.22s ease;
    opacity: 1 !important;
    pointer-events: auto !important;
  }

  /* FTS pill: sit just above the editor banner; hide entirely when satellite
     is on (no shadows to scrub anyway). Match the banner's horizontal extent. */
  body.edit-mode #fts {
    bottom: var(--fts-bottom, 240px);
    transition: bottom 0.22s ease, opacity 0.22s ease;
  }
  body.edit-mode.edit-satellite #fts {
    opacity: 0;
    pointer-events: none;
  }
  @media (max-width: 639px) {
    body.edit-mode #fts {
      left: 16px;
      right: 16px;
      width: auto;
    }
  }

  /* The brand lockup floats bottom-left over the map and collides with the
     terrace-type dropdown (which opens upward from the sheet). Hide it while
     editing. */
  body.edit-mode #floating-brand { display: none; }

  /* FTS scrubber in audit: shown only in Skygger (time-based shadow sim);
     hidden in Alle (sun ignored). High specificity so it beats body.fts #fts. */
  body.audit-mode:not(.audit-shadows):not(.edit-mode) #fts { display: none !important; }

  /* Hide notifications and toasts during edit + audit (single-task focus) */
  body.edit-mode #notif-toast-wrap,
  body.edit-mode #notif-toast,
  body.edit-mode #app-toast,
  body.audit-mode #notif-toast-wrap,
  body.audit-mode #notif-toast,
  body.audit-mode #app-toast {
    display: none !important;
  }

  /* ── Edit-mode top header: audit progress + venue bearings ──────────────────
     Only in audit edit mode. Replaces the audit bar at the top while editing
     so the reviewer always sees overall completion + which venue + where. */
  #edit-top { display: none; }
  body.edit-mode.audit-mode #audit-mode-indicator { display: none !important; }
  body.edit-mode.audit-mode #edit-top {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    position: fixed;
    top: calc(max(env(safe-area-inset-top, 0px) + 2px, 12px));
    left: 50%;
    transform: translateX(-50%);
    width: min(440px, calc(100vw - 2 * var(--space-lg)));
    z-index: 9000;
    padding: var(--space-md) var(--space-lg);
    /* Cream-frost (50% + 22px blur) + ink text — legible over satellite. */
    background: rgba(255,242,235,0.50);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: 1px solid var(--line-l);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-2);
    color: var(--content-text);
    font-family: 'Inter', sans-serif;
  }
  #edit-top-row1 {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    min-width: 0;
  }
  #edit-top-name {
    font-size: var(--text-title);
    font-weight: 700;
    color: var(--content-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1 1 auto;
    min-width: 0;
  }
  #edit-top-meta {
    font-size: var(--text-label);
    font-weight: 500;
    color: var(--ink-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  #edit-top-count {
    font-size: var(--text-label);
    font-weight: 700;
    color: var(--content-text);
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
  }
  #edit-close-btn {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    cursor: pointer;
    transition: background 0.12s;
  }
  #edit-close-btn:hover { background: var(--line-l-faint); }
  #edit-progress {
    height: 5px;
    border-radius: var(--radius-pill);
    background: var(--line-l);
    overflow: hidden;
  }
  #edit-progress-fill {
    display: block;
    height: 100%;
    width: 0%;
    border-radius: var(--radius-pill);
    background: var(--accent);
    transition: width 0.32s cubic-bezier(0.2, 0.8, 0.3, 1);
  }


  /* ── Profile panel additions ─────────────────────────────────────────────── */
  .profile-panel-section {
    border-top: 1px solid rgba(255,255,255,0.07);
    padding: var(--space-md) 0;
  }

  .profile-admin-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    background: none;
    border: none;
    color: var(--text);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    padding: var(--space-md) var(--space-xl);
    cursor: pointer;
    text-align: left;
    transition: background 0.12s;
  }

  .profile-admin-row:hover { background: rgba(255,255,255,0.05); }

  /* ── Admin audit mode (polygon walk-through) ──────────────────────────────
     Glass-lens surfaces on slate, single honey accent for active state.
     Tokens: --glass-panel-bg, --glass-border, --glass-inset, --accent,
             --accent-dim, --text, --muted, --red-500.
     Audit and Review modes share .review-chip (retuned below).
  ─────────────────────────────────────────────────────────────────────── */
  #audit-mode-indicator {
    position: fixed;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9000;
    color: var(--text);
    font-family: 'Inter', sans-serif;
  }
  /* Top bar — cream-frost (50% + 22px blur) so it reads over any base map;
     title + exit on top, progress + count below. Ink text. */
  .audit-indicator-row {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-lg);
    background: rgba(255,242,235,0.50);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: 1px solid var(--line-l);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-2);
    color: var(--content-text);
  }
  .audit-title-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
  }
  .audit-indicator-title {
    flex: 1 1 auto;
    font-size: var(--text-label);
    font-weight: 700;
    letter-spacing: 0.01em;
    color: var(--content-text);
  }
  #audit-exit-btn {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    cursor: pointer;
    transition: background 0.12s;
  }
  #audit-exit-btn:hover { background: var(--line-l-faint); }
  .audit-progress-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
  }
  #audit-progress {
    flex: 1 1 auto;
    height: 6px;
    border-radius: var(--radius-pill);
    background: var(--line-l);
    overflow: hidden;
  }
  #audit-progress-fill {
    display: block;
    height: 100%;
    width: 0%;
    border-radius: var(--radius-pill);
    background: var(--accent);
    transition: width 0.32s cubic-bezier(0.2, 0.8, 0.3, 1);
  }
  #audit-indicator-count {
    font-size: var(--text-label);
    font-weight: 700;
    color: var(--content-text);
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
  }

  /* Audit toolbar docked below the FTS (replaces the consumer Filtre/Sort row
     in audit mode). Holds every audit control. */
  body.audit-mode #panel-actions { display: none; }
  #audit-controls { display: none; }
  body.audit-mode #audit-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-lg) var(--space-md);
    flex-shrink: 0;
  }

  /* Mobile: span the viewport with gutters so the top bar never bleeds. */
  @media (max-width: 639px) {
    #audit-mode-indicator {
      left: var(--space-md);
      right: var(--space-md);
      transform: none;
      width: auto;
    }
  }

  /* Buttons + chips inside the indicator share the chip-pill rhythm
     (smaller height to keep the bar compact). */
  .audit-pill, .audit-submode-btn {
    height: 28px;
    padding: 0 var(--space-lg);
    border-radius: var(--radius-pill);
    font: 600 12px 'Inter', sans-serif;
    color: var(--text);
    background: var(--glass-action-bg);
    border: var(--glass-border);
    box-shadow: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    transition: border-color 120ms ease-out, background 120ms ease-out, color 120ms ease-out;
    white-space: nowrap;
  }
  .audit-pill:hover, .audit-submode-btn:hover { border-color: rgba(156,189,231,0.32); }
  .audit-pill.is-ghost {
    background: transparent;
    border-color: rgba(155,169,188,0.18);
    color: var(--muted);
    box-shadow: none;
  }
  .audit-pill.is-ghost:hover { color: var(--text); border-color: rgba(155,169,188,0.35); }

  /* Sub-mode segmented control — light-chrome on the panel: outline track,
     ink labels, active = Delft fill + cream (controls step to Delft Blue). */
  .audit-submode {
    display: inline-flex;
    gap: var(--space-2xs);
    padding: var(--space-2xs);
    border-radius: var(--radius-pill);
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
  }
  .audit-submode-btn {
    height: 26px;
    padding: 0 var(--space-md);
    border: none;
    border-radius: var(--radius-pill);
    background: transparent;
    box-shadow: none;
    color: var(--panel-text);
    font: 600 12px 'Inter', sans-serif;
    cursor: pointer;
  }
  .audit-submode-btn:hover { background: var(--line-l-faint); }
  .audit-submode-btn.active {
    background: var(--surface-content);
    color: var(--text);
  }

  /* Filter pill — honey-dim when filters deviate from default. */
  #audit-controls .panel-filter-pill.has-active {
    background: var(--accent-dim);
    border-color: var(--accent-border);
    color: var(--content-text);
  }
  .audit-filter-toggle-badge {
    display: inline-block;
    min-width: 16px;
    height: 16px;
    padding: 0 var(--space-xs);
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: var(--accent-on);
    font: 700 10px/16px 'Inter', sans-serif;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }
  .audit-filter-toggle-badge:empty { display: none; }

  /* Filter popover — cream-frost light surface (50% + 22px blur), ink text,
     light-chrome outline chips (Delft-fill active). Matches the dropdown
     family + the edit chrome. */
  .audit-filter-panel {
    margin-top: var(--space-sm);
    padding: var(--space-lg);
    width: 100%;
    background: rgba(255,242,235,0.50);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: 1px solid var(--line-l);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
    color: var(--content-text);
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
  }
  .audit-filter-group { display: flex; flex-direction: column; gap: var(--space-sm); }
  .audit-filter-label {
    font-size: 10px;
    font-weight: 700;
    color: var(--ink-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }
  .audit-chip-row { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
  .audit-chip {
    height: 28px;
    padding: 0 var(--space-md);
    border-radius: var(--radius-pill);
    font: 600 11px 'Inter', sans-serif;
    color: var(--content-text);
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    box-shadow: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    white-space: nowrap;
    transition: border-color 120ms ease-out, background 120ms ease-out, color 120ms ease-out;
  }
  .audit-chip:hover { background: var(--line-l-faint); }
  .audit-chip.active {
    background: var(--surface-content);
    border-color: transparent;
    color: var(--text);
  }
  .audit-chip-count {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    font-size: 10px;
    font-weight: 700;
  }
  .audit-chip.active .audit-chip-count { color: rgba(255,244,224,0.75); }
  .audit-filter-footer { display: flex; justify-content: flex-end; }

  /* Reviewed/archived state is shown by the action-row state badge, not a
     left accent bar (the bar wasn't a design-system element). Archived stays
     dimmed so done-and-hidden venues recede. */
  .venue-card.audit-archived { opacity: 0.6; }

  /* Card action row + badge — single line, intrinsic widths so 3 buttons
     fit on one row even in narrow cards. Smaller than indicator chips so
     they recede inside the card. */
  .audit-actions {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-sm);
    padding: var(--space-sm) 0 0;
    margin-top: var(--space-xs);
    align-items: center;
    border-top: 1px solid rgba(155,169,188,0.10);
  }

  /* Compact icon action buttons (audit tool). 32px touch targets; primary
     (Mark good) is the honey CTA, Archive is error-tinted, others neutral.
     Each carries its own title + aria-label so the glyph needs no text. */
  /* Light-chrome icon buttons on the cream audit list. Mark-good = honey CTA;
     archive = error outline; edit = neutral ink outline. */
  .audit-icon-btn {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-md);
    background: var(--surface-control);
    color: var(--content-text);
    border: 1px solid var(--line-l-strong);
    cursor: pointer;
    transition: background 120ms ease-out, color 120ms ease-out, border-color 120ms ease-out, transform 0.1s;
  }
  .audit-icon-btn:hover  { background: var(--line-l-faint); }
  .audit-icon-btn:active { transform: scale(0.94); }
  .audit-icon-btn.audit-good {
    background: var(--accent);
    color: var(--accent-on);
    border-color: transparent;
    box-shadow: var(--shadow-1);
  }
  .audit-icon-btn.audit-good:hover { background: var(--accent-hover); }
  .audit-icon-btn.audit-archive {
    color: var(--red-500);
    border-color: rgba(192,57,43,0.45);
  }
  .audit-icon-btn.audit-archive:hover { border-color: var(--red-500); background: rgba(192,57,43,0.08); }
  /* Edit (rightmost) pins to the card's right edge, spatially separate. */
  .audit-actions > .audit-icon-btn:last-child { margin-left: auto; }
  .audit-state-badge {
    flex: 0 0 auto;
    font: 700 10px 'Inter', sans-serif;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-pill);
    background: var(--accent-dim);
    color: var(--accent);
    white-space: nowrap;
  }
  .venue-card.audit-archived .audit-state-badge {
    background: rgba(255,107,107,0.18);
    color: var(--red-500);
  }
  /* Action buttons. Primary = honey; secondaries = glass; destructive =
     glass with error-tinted border. Edit-polygon is the rightmost item;
     it gets the .audit-edit modifier so it can pin-right without flexing. */
  .audit-action-btn {
    flex: 0 0 auto;
    height: 26px;
    padding: 0 var(--space-md);
    border-radius: var(--radius-pill);
    font: 700 11px 'Inter', sans-serif;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    background: var(--accent);
    color: var(--accent-on);
    border: none;
    box-shadow: 0 1px 4px rgba(0,0,0,0.28);
    transition: background 120ms ease-out, color 120ms ease-out, border-color 120ms ease-out;
    white-space: nowrap;
  }
  .audit-action-btn:hover { background: var(--accent-hover); }
  .audit-action-btn.audit-undo {
    background: var(--glass-action-bg);
    color: var(--muted);
    border: var(--glass-border);
    box-shadow: none;
  }
  .audit-action-btn.audit-undo:hover { color: var(--text); border-color: rgba(156,189,231,0.32); }
  .audit-action-btn.audit-archive {
    background: var(--glass-action-bg);
    color: var(--red-500);
    border: 1px solid rgba(255,107,107,0.28);
    box-shadow: none;
  }
  .audit-action-btn.audit-archive:hover { border-color: rgba(255,107,107,0.55); background: rgba(255,107,107,0.10); }
  /* Push the rightmost button to the right edge of the card so Edit feels
     spatially separate from the state action. */
  .audit-actions > .audit-action-btn:last-child { margin-left: auto; }

  /* Archive reason chooser — the audit-actions row morphs in place into
     a list of reason chips. Allow wrap so all presets stay visible on
     narrow cards; drop the right-edge margin so Cancel sits flush. */
  .audit-actions.is-choosing-reason { flex-wrap: wrap; gap: var(--space-xs); }
  .audit-actions.is-choosing-reason > .audit-action-btn:last-child { margin-left: auto; }
  .audit-actions.is-choosing-reason .audit-state-badge {
    color: var(--accent);
    background: var(--accent-dim);
    border: 1px solid var(--accent-border);
  }

  /* AI training-status chip — pinned next to the state badge so the admin
     can read "polygon-decision + training-status" in one glance. Subtle
     by design; the row's primary signal is still the action button. */
  .audit-train-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    height: 22px;
    padding: 0 var(--space-md);
    border-radius: var(--radius-pill);
    font: 600 10px 'Inter', sans-serif;
    letter-spacing: 0.02em;
    white-space: nowrap;
    border: 1px solid transparent;
  }
  .audit-train-chip.is-trained {
    color: var(--green-500);
    background: rgba(100,255,180,0.10);
    border-color: rgba(100,255,180,0.30);
  }
  .audit-train-chip.is-pending {
    color: var(--accent);
    background: var(--accent-dim);
    border-color: var(--accent-border);
  }

  .review-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    padding: var(--space-xs) 0 0;
  }
  .review-chip {
    display: inline-block;
    background: var(--accent-dim);
    color: var(--accent);
    border: 1px solid var(--accent-border);
    border-radius: var(--radius-pill);
    padding: var(--space-2xs) var(--space-sm);
    font-size: var(--text-caption);
    font-weight: 600;
    line-height: 1.25;
  }


  /* ── Unified cream-frost audit surface ─────────────────────────────────
     The whole audit experience is one cream-frost (50% + 22px blur) surface:
     the list panel, plus floating controls. Cards are transparent rows on it
     with a hairline divider, so there's a single background — no dark tiles. */
  body.audit-mode #panel {
    background: rgba(255,242,235,0.50);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
  }
  /* Floating controls → cream-frost too (icons are already ink). */
  body.audit-mode #locate-btn,
  body.audit-mode .zj-track, body.edit-mode .zj-track,
  body.audit-mode .zj-thumb, body.edit-mode .zj-thumb {
    background: rgba(255,242,235,0.50);
  }

  /* ── Audit card — a real OPAQUE cream tile with shadow, on the cream list ──
     name + icon actions (row 1), location · category (row 2), flags (row 3).
     Clicking focuses the venue on satellite — selection = honey ring. */
  .venue-card.audit-card {
    background: var(--surface-raised);             /* opaque cream */
    border: 1px solid var(--line-l);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
    padding: var(--space-lg);
    gap: var(--space-sm);
    margin-bottom: var(--space-md);
    cursor: pointer;
    transition: box-shadow 0.15s ease, transform 0.12s ease, border-color 0.15s ease;
  }
  @media (hover: hover) and (pointer: fine) {
    .venue-card.audit-card:hover { box-shadow: var(--shadow-2); border-color: var(--line-l-strong); }
  }
  .audit-card .ac-top {
    display: flex;
    align-items: center;
    gap: var(--space-md);
  }
  .audit-card .ac-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--text-body);
    font-weight: 700;
    color: var(--content-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* Actions sit inline on the name row — drop the standalone-row chrome and
     the last-child margin so the icons read as one tight group. */
  .audit-card .audit-actions {
    border-top: none;
    margin-top: 0;
    padding: 0;
    flex: 0 0 auto;
    gap: var(--space-xs);
  }
  .audit-card .audit-actions > .audit-icon-btn:last-child { margin-left: 0; }
  .audit-card .ac-meta {
    font-size: var(--text-caption);
    color: var(--ink-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .audit-card .review-chips { padding: var(--space-2xs) 0 0; gap: var(--space-xs); }
  /* Flags = neutral ink-outline chips on cream (NOT honey — honey is the sun
     signal, not a flag). Reads as informational tags. */
  .audit-card .review-chip {
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--content-text);
    padding: var(--space-2xs) var(--space-md);
  }
  /* AI-proposal badge — honey-dim, signals "AI picked this, not yet human-
     reviewed" with its confidence. Honey is OK here: it IS a discovery signal. */
  .audit-card .audit-ai-chip {
    display: inline-block;
    background: var(--accent-dim);
    border: 1px solid var(--accent-border);
    color: var(--content-text);
    border-radius: var(--radius-pill);
    padding: var(--space-2xs) var(--space-md);
    font-size: var(--text-caption);
    font-weight: 700;
    line-height: 1.25;
  }
  /* Soft edit-lock: another admin is editing this venue right now. */
  .venue-card.audit-card.audit-locked { opacity: 0.78; }
  .audit-card .ac-lock {
    font-size: var(--text-caption);
    font-weight: 600;
    color: #8A5A00;
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
  }
  .audit-card .ac-lock::before {
    content: '';
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #C0392B;
  }
  /* "Sist: <name>" — who last reviewed/edited. */
  .audit-card .ac-by {
    font-size: var(--text-caption);
    color: var(--ink-muted);
  }
  .venue-card.audit-card:active { transform: scale(0.99); }
  .venue-card.audit-card.audit-focus {
    border-color: transparent;
    box-shadow: 0 0 0 2px var(--accent), var(--shadow-2);
  }
  /* Status chips on the cream card need DARK ink colours — the bright
     --green-500 / --red-500 are tuned for dark surfaces and wash out here. */
  .audit-card .audit-train-chip.is-trained {
    color: #0F7A4F;
    background: rgba(15,122,79,0.10);
    border-color: rgba(15,122,79,0.30);
  }
  .audit-card .audit-train-chip.is-pending {
    color: #8A5A00;
    background: rgba(138,90,0,0.10);
    border-color: rgba(138,90,0,0.28);
  }
  .audit-card .audit-state-badge {
    background: rgba(15,122,79,0.12);
    color: #0F7A4F;
  }
  .venue-card.audit-archived.audit-card .audit-state-badge {
    background: rgba(192,57,43,0.10);
    color: #C0392B;
  }
  .audit-card .audit-icon-btn.audit-archive { color: #C0392B; }
  .profile-role-badge {
    display: inline-flex;
    align-items: center;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-sm);
    text-transform: uppercase;
    vertical-align: middle;
    margin-left: var(--space-sm);
  }

  .profile-role-badge.admin {
    background: rgba(245,194,94,0.18);
    border: 1px solid rgba(245,194,94,0.45);
    color: var(--accent);
  }

  .profile-role-badge.editor {
    background: rgba(156,189,231,0.18);
    border: 1px solid rgba(156,189,231,0.35);
    color: #9CBDE7;
  }

  .pending-badge {
    /* Honey-dim pill — matches the badge pattern across the redesign
       (count badges on bell, friend requests, pending suggestions). */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-dim);
    color: var(--accent);
    border: 1px solid var(--accent-border);
    border-radius: var(--radius-md);
    font-size: var(--text-caption);
    font-weight: 600;
    min-width: 22px;
    height: 22px;
    padding: 0 var(--space-sm);
    margin-left: auto;
  }

  /* ── Profile panel — settings + suggestions + privacy ───────────────────── */
  .profile-settings-section { padding: var(--space-md) 0; }
  .profile-section-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 0 var(--space-xl) var(--space-sm);
  }
  .profile-pref-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-xs) var(--space-xl);
    gap: var(--space-lg);
  }
  .profile-pref-label {
    font-size: var(--text-label);
    color: var(--text);
  }
  /* Segmented control — single container with inset background, the
     individual buttons sit inside without their own chrome. Active
     button gets the honey-dim treatment. */
  .profile-pref-pills {
    display: inline-flex;
    background: rgba(7,16,31,0.40);
    border: 1px solid rgba(155,169,188,0.20);
    border-radius: var(--radius-md);
    padding: var(--space-2xs);
    gap: 0;
  }
  .pref-pill {
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 600;
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-sm);
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    min-width: 32px;
    transition: background 0.12s, color 0.12s;
  }
  .pref-pill:hover { color: var(--text); }
  .pref-pill.active {
    background: var(--accent-dim);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent-border);
  }
  .profile-pref-desc {
    font-size: var(--text-caption);
    color: var(--muted);
    padding: 0 var(--space-xl) var(--space-xs);
    opacity: 0.7;
  }
  .checkin-vis-row { padding: var(--space-2xs) var(--space-xl); }
  .checkin-vis-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 26px;
    padding: 0;
  }
  .checkin-vis-toggle svg { stroke: currentColor; }
  .my-suggestion-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-xs) var(--space-xl);
    gap: var(--space-md);
  }
  .my-suggestion-info {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    min-width: 0;
  }
  .my-suggestion-name {
    font-size: var(--text-label);
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 140px;
  }
  .suggestion-status-badge {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-sm);
    white-space: nowrap;
    flex-shrink: 0;
  }
  .suggestion-status-badge.pending  { background: rgba(245,194,94,0.12); color: var(--accent); border: 1px solid rgba(245,194,94,0.3); }
  .suggestion-status-badge.approved { background: rgba(100,255,180,0.12); color: var(--color-success);       border: 1px solid rgba(100,255,180,0.3); }
  .suggestion-status-badge.rejected { background: rgba(255,107,107,0.12); color: var(--color-error);       border: 1px solid rgba(255,107,107,0.3); }
  .suggestion-withdraw-btn {
    font-family: 'Inter', sans-serif;
    font-size: var(--text-caption);
    font-weight: 600;
    color: var(--muted);
    background: transparent;
    border: 1px solid rgba(156,189,231,0.2);
    border-radius: var(--radius-sm);
    padding: var(--space-2xs) var(--space-md);
    cursor: pointer;
    flex-shrink: 0;
    transition: color 0.12s, border-color 0.12s;
  }
  .suggestion-withdraw-btn:hover { color: var(--color-error); border-color: rgba(255,107,107,0.4); }
  .profile-privacy-link {
    display: block;
    text-align: center;
    font-size: 12px;
    color: var(--muted);
    text-decoration: none;
    padding: var(--space-sm) 0 var(--space-2xs);
    transition: color 0.12s;
  }
  .profile-privacy-link:hover { color: var(--text); }

  /* ── Settings (grouped-list redesign) ────────────────────────────────────── */
  .settings-root {
    padding: var(--space-lg) var(--space-lg) calc(20px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: var(--space-xl);
  }
  .settings-group-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 0 var(--space-sm) var(--space-sm);
  }
  /* Stage 4b-2: settings-group as a Tier 2 lens object (was --card-bg
     legacy). Layered shadow gives it a card silhouette inside the
     sheet without competing with the panel's own glass. */
  .settings-group {
    /* Content container → canonical Delft surface + flat shadow. Was the legacy
       --glass-card-bg + two glossy inset sheens (retired, DESIGN.md principle 6). */
    background: var(--surface-content);
    border: 1px solid var(--line-d);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-1);
  }
  .settings-row {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
    width: 100%;
    background: none;
    border: none;
    color: var(--text);
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    text-align: left;
    padding: var(--space-lg) var(--space-lg);
    min-height: 46px;
    cursor: pointer;
    transition: background 0.12s;
    box-sizing: border-box;
  }
  .settings-row + .settings-row { border-top: 1px solid rgba(156,189,231,0.10); }
  .settings-row:hover { background: rgba(255,255,255,0.04); }
  .settings-row:disabled,
  .settings-row.is-disabled { cursor: default; opacity: 0.55; }
  .settings-row:disabled:hover,
  .settings-row.is-disabled:hover { background: none; }
  .settings-row__icon {
    flex-shrink: 0;
    color: var(--muted);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
  }
  .settings-row__label {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .settings-row__value {
    color: var(--muted);
    font-size: var(--text-label);
    flex-shrink: 0;
  }
  .settings-row__chevron {
    color: var(--muted);
    flex-shrink: 0;
    opacity: 0.7;
  }
  /* Stage 4b-2: destructive soft red (was bright #ff8a8a). Reads as
     "danger" without yelling. */
  .settings-row.destructive { color: #E47B7B; }
  .settings-row.destructive .settings-row__icon,
  .settings-row.destructive .settings-row__chevron { color: #E47B7B; }
  .settings-row.destructive:hover,
  .dprcv-cta-cancel:hover { background: rgba(228,123,123,0.06); }

  /* QR card host — sits inside the dark .settings-group below the
     Share / Add-friend rows. Centers the cream card child explicitly
     via flex so it doesn't drift to the left when the host stretches
     to the group's full width. */
  .qr-card-host {
    padding: var(--space-md);
    display: flex;
    justify-content: center;
  }
  /* Cream Tier-1 surface so the QR (dark modules on cream) reads with
     full contrast against the surrounding dark .settings-group, and so
     the dark-ink caption + url + Copy button text stays legible. */
  .qr-card {
    display: flex; flex-direction: column; align-items: center;
    gap: var(--space-sm);
    background: var(--content-bg);
    color: var(--panel-text);
    border-radius: var(--radius-lg);
    padding: var(--space-md);
    width: 100%; max-width: 240px; box-sizing: border-box;
  }
  /* SVG wrapper centers the SVG explicitly. qrcode-generator emits an
     <svg> with width/height attributes baked in; CSS width on the svg
     overrides them and scales the whole QR via its viewBox. */
  .qr-card-svg {
    line-height: 0;
    display: flex; justify-content: center; width: 100%;
  }
  .qr-card-svg svg {
    display: block; width: 168px; height: 168px;
    background: #fff; border-radius: var(--radius-sm);
  }
  .qr-card-caption {
    font-size: var(--text-caption); font-weight: 600;
    color: var(--panel-text); text-align: center;
  }
  .qr-card-url {
    font-size: var(--text-caption); color: var(--ink-muted);
    word-break: break-all; text-align: center; max-width: 100%;
    line-height: 1.3;
  }
  .qr-card-copy {
    font-family: 'Inter', sans-serif; font-size: var(--text-caption);
    font-weight: 600; line-height: 1;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-pill);
    background: var(--surface-control);
    border: 1px solid var(--line-l-strong);
    color: var(--panel-text);
    cursor: pointer;
    transition: background 120ms;
  }
  .qr-card-copy:hover { background: var(--line-l-faint); }
  .qr-card-loading,
  .qr-card-fallback { color: var(--panel-text); padding: var(--space-md); text-align: center; }

  /* Identity card variant — full bleed within group */
  /* Stage 4b-1: Hero identity — centered, 72px avatar, soft honey halo.
     The wrapping .settings-identity-group has its card chrome stripped so
     the hero floats free at the top of the sheet. */
  .settings-identity-group {
    background: none !important;
    border: none !important;
    box-shadow: none !important;
  }
  /* .settings-root is a column flex container with overflow-y:auto. By
     default flex items can shrink (flex-shrink:1); combined with
     .settings-group's overflow:hidden, the identity group can collapse
     to height:0 when total content exceeds the visible area — which
     hides the hero. The other groups have min-height:46px rows that
     keep them above 0. Lock children to their natural size and let the
     container scroll. */
  #profile-panel .settings-root > * { flex-shrink: 0; }
  .settings-identity {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0;
    padding: var(--space-2xl) var(--space-xl) var(--space-2xl);
    background: none;
    border: none;
    width: 100%;
    color: var(--text);
    cursor: default;
  }
  .settings-identity__avatar,
  .settings-identity__avatar-initials {
    width: 72px; height: 72px;
    border-radius: 50%;
    flex-shrink: 0;
    object-fit: cover;
    margin-bottom: var(--space-lg);
    /* "Sun on you" halo — subtle honey glow + soft elevation. */
    box-shadow:
      inset 0 1px 0 rgba(255,250,235,0.16),
      0 0 0 4px rgba(245,194,94,0.08),
      0 0 24px rgba(245,194,94,0.08),
      0 8px 24px rgba(0,0,0,0.40);
    border: 1.5px solid rgba(155,169,188,0.30);
  }
  .settings-identity__avatar-initials {
    background: var(--accent);
    display: flex; align-items: center; justify-content: center;
    font-size: 24px; font-weight: 700;
    color: var(--accent-on);
  }
  .settings-identity__info {
    min-width: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
  }
  .settings-identity__name {
    font-size: var(--text-subtitle);
    font-weight: 700;
    color: var(--text);
    display: inline-flex;
    align-items: center;
    gap: var(--space-md);
    justify-content: center;
    line-height: 1.2;
    max-width: 100%;
  }
  .settings-identity__email {
    font-size: 12px;
    color: var(--muted);
    line-height: 1.2;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Inline pref row (segmented controls inside a settings-group) */
  .settings-row.pref-row { cursor: default; }
  .settings-row.pref-row:hover { background: none; }

  /* Status pill — for "Coming soon" markers */
  .settings-status-pill {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-md);
    background: rgba(156,189,231,0.14);
    color: var(--muted);
    border: 1px solid rgba(156,189,231,0.22);
    flex-shrink: 0;
  }

  /* Toggle switch — replaces eye icons in the redesign */
  .toggle-switch {
    position: relative;
    width: 34px;
    height: 20px;
    border-radius: var(--radius-md);
    background: rgba(156,189,231,0.22);
    border: 1px solid rgba(156,189,231,0.28);
    padding: 0;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.16s ease, border-color 0.16s ease;
  }
  .toggle-switch::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #FFF2EB;
    transform: translateY(-50%);
    transition: left 0.16s ease, background 0.16s ease, transform 90ms ease-out;
    box-shadow: 0 1px 2px rgba(0,0,0,0.35);
  }
  .toggle-switch.is-on {
    background: var(--accent);
    border-color: rgba(245,194,94,0.7);
  }
  .toggle-switch.is-on::after {
    left: 17px;
    background: #111E38;
  }
  .toggle-switch:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  /* Activity sub-panel rows reuse .inbox-row styling already defined */
  .settings-subview {
    /* Stage 4b-4: sub-view becomes the scroll container so the sticky
       mobile bar stays in place at the top of the sheet while content
       scrolls beneath. Matches .settings-root behavior. */
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--space-lg) var(--space-lg) calc(20px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
  }
  .settings-subview__empty {
    text-align: center;
    color: var(--muted);
    font-size: var(--text-label);
    padding: var(--space-3xl) var(--space-xl);
    opacity: 0.7;
  }

  /* About row holds version text inline */
  .settings-row__sub {
    font-size: var(--text-caption);
    color: var(--muted);
    margin-top: var(--space-2xs);
  }

  /* ── Auth buttons ─────────────────────────────────────────────────────────── */
  .login-gate {
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; padding: 56px var(--space-2xl) var(--space-4xl); text-align: center; gap: var(--space-lg);
  }
  .login-gate-title {
    font-family: 'Newsreader', serif; font-size: var(--text-title);
    color: var(--text); font-weight: 400;
  }
  .login-gate-subtitle {
    color: var(--muted); font-size: var(--text-label); line-height: 1.6; max-width: 240px;
  }
  .login-gate-buttons {
    display: flex; flex-direction: column; gap: var(--space-md);
    width: 100%; max-width: 280px; margin-top: var(--space-md);
  }
  /* All auth controls (Google / Apple / honey submit / the email input) share
     one explicit 48px height so they read as a uniform stack — sizing by
     padding let font/border differences make them look uneven. */
  .auth-btn {
    display: flex; align-items: center; justify-content: center; gap: var(--space-md);
    width: 100%; height: 48px; border: none; border-radius: var(--radius-sm);
    padding: 0 var(--space-2xl); font-family: 'Inter', sans-serif;
    font-size: 14px; font-weight: 500; cursor: pointer;
    transition: opacity 0.12s;
  }
  .auth-btn:hover { opacity: 0.85; }
  .auth-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  /* Google/Apple keep their OAuth-mandated brand colours (white / black) and
     the auth modal keeps the full-width rectangular OAuth-button shape for the
     whole stack. The role is encoded by COLOUR: email = the honey Primary
     ("the decision"); the two brand buttons read as the providers. */
  .auth-btn-google { background: #fff; color: #1f1f1f; }
  .auth-btn-apple  { background: #000; color: #fff; }
  .auth-btn-apple svg { width: 18px; height: 18px; }
  .auth-btn-email  {
    background: var(--accent); color: var(--accent-on);
    font-weight: 700;
  }
  .auth-divider {
    display: flex; align-items: center; gap: var(--space-lg);
    color: var(--muted); font-size: 12px; margin: var(--space-2xs) 0;
  }
  .auth-divider::before, .auth-divider::after {
    content: ''; flex: 1; height: 1px;
    background: rgba(255,255,255,0.1);
  }
  .auth-magic-link-form { display: flex; flex-direction: column; gap: var(--space-md); }
  .auth-magic-link-input {
    width: 100%; box-sizing: border-box;
    background: rgba(255,255,255,0.07);
    border: 1px solid rgba(255,255,255,0.15); border-radius: var(--radius-sm);
    color: var(--text); font-family: 'Inter', sans-serif; font-size: 16px;
    /* 48px height matches the auth buttons exactly (uniform stack); 16px font
       blocks iOS zoom-on-focus. */
    height: 48px; padding: 0 var(--space-lg); outline: none;
  }
  .auth-magic-link-input:focus { border-color: rgba(156,189,231,0.5); }
  .auth-magic-link-input::placeholder { color: var(--muted); }
  /* Password input mirrors the email input visually; only visible when the
     user explicitly tabs into it (still occupies layout space for the hint
     line below). Optional — empty value means "use magic link". */
  .auth-password-input {
    width: 100%; box-sizing: border-box;
    background: rgba(255,255,255,0.07);
    border: 1px solid rgba(255,255,255,0.15); border-radius: var(--radius-sm);
    color: var(--text); font-family: 'Inter', sans-serif; font-size: 16px;
    height: 48px; padding: 0 var(--space-lg); outline: none;
  }
  .auth-password-input:focus { border-color: rgba(156,189,231,0.5); }
  .auth-password-input::placeholder { color: var(--muted); }
  .auth-password-hint {
    font-size: var(--text-caption); line-height: 1.4; color: var(--muted);
    text-align: center; padding: 0 var(--space-xs);
  }
  .auth-magic-link-status {
    font-size: 12px; line-height: 1.4; min-height: 17px; text-align: center;
  }
  .auth-magic-link-status.success { color: var(--color-success); }
  .auth-magic-link-status.error   { color: var(--color-error); }

  /* Logged-out profile panel: hero + benefits carousel */
  .login-hero { padding: var(--space-2xl) var(--space-xl) var(--space-md); text-align: center; }
  .login-hero-title {
    /* Display tier (DESIGN.md) — Inter 900 tight (the logo treatment), replacing
       the off-system Newsreader serif (the product is one font: Inter). */
    font-family: 'Inter', sans-serif; font-size: var(--text-display);
    font-weight: var(--fw-display); letter-spacing: var(--tracking-display);
    color: var(--text); line-height: 1.2; margin: 0 0 var(--space-xl);
  }
  .login-carousel { position: relative; overflow: hidden; }
  .login-carousel-track {
    display: flex;
    transition: transform 0.35s cubic-bezier(0.25, 0.9, 0.4, 1);
    will-change: transform; touch-action: pan-y;
  }
  .login-slide {
    flex: 0 0 100%; padding: var(--space-md) var(--space-xl) var(--space-lg); box-sizing: border-box;
    text-align: center;
  }
  .login-slide-icon {
    width: 48px; height: 48px; margin: 0 auto var(--space-lg); color: var(--accent);
    display: flex; align-items: center; justify-content: center;
  }
  .login-slide-icon svg { width: 36px; height: 36px; }
  .login-slide-title { font-size: var(--text-body); font-weight: 600; color: var(--text); margin-bottom: var(--space-sm); }
  .login-slide-body  {
    font-size: var(--text-label); line-height: 1.5; color: var(--muted);
    max-width: 240px; margin: 0 auto;
  }
  .login-carousel-dots {
    display: flex; gap: var(--space-sm); justify-content: center; padding: var(--space-lg) 0 var(--space-xs);
  }
  .login-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: rgba(255,255,255,0.2); border: none; padding: 0;
    cursor: pointer; transition: background 0.2s, width 0.2s;
  }
  .login-dot.active { background: var(--accent); width: 18px; border-radius: var(--radius-pill); }

  .login-auth-section { padding: var(--space-md) var(--space-xl) var(--space-lg); }
  .login-auth-section .login-gate-buttons { max-width: none; margin-top: 0; }
  .login-footer {
    border-top: 1px solid rgba(255,255,255,0.07);
    padding: var(--space-md) 0 var(--space-xs); margin-top: var(--space-md); text-align: center;
  }
  /* Close button base look. Positioning lives on the more-specific
     #profile-panel.logged-out .login-close-btn rule below so it always
     beats the parent flex layout. The parent panel has a transform set
     (translateX(0) / translateY(...)) for its slide animation, which
     creates a containing block for position:fixed descendants — so
     "fixed" effectively means "absolute relative to the panel". The
     panel itself spans the viewport, so top:12 / right:12 lands at the
     viewport top-right. */
  .login-close-btn {
    width: 32px; height: 32px; border-radius: 50%;
    background: rgba(255,255,255,0.06); border: none;
    color: var(--muted); font-size: var(--text-title); line-height: 1;
    cursor: pointer; display: none;
    align-items: center; justify-content: center;
  }
  .login-close-btn:hover { background: rgba(255,255,255,0.12); color: var(--text); }
  /* Higher-specificity positioning so flex layout on the parent can't
     drag the close button into the column flow. User-reported v1
     symptom: button rendered on the left mid-page, partially clipped. */
  #profile-panel.logged-out .login-close-btn {
    position: fixed;
    top: max(12px, env(safe-area-inset-top));
    right: max(12px, env(safe-area-inset-right));
    left: auto; bottom: auto;
    z-index: 10000;
    display: flex;
  }

  /* Candidate "Suggest venue" badge in search dropdown */
  /* Candidate badge ("Suggest venue" on a suggestable row). Was a honey pill —
     dies on the cream dropdown. Ink outline, matching .sd-suggest-btn. */
  .sd-candidate-btn {
    font-size: var(--text-caption);
    font-weight: 600;
    color: var(--panel-text);
    background: transparent;
    border: 1px solid var(--line-l-strong);
    border-radius: var(--radius-md);
    padding: var(--space-2xs) var(--space-md);
    white-space: nowrap;
    flex-shrink: 0;
    pointer-events: none; /* row handles click */
  }
  .sd-row-candidate:hover .sd-candidate-btn {
    background: var(--line-l-faint);
    border-color: var(--line-l-strong);
  }

  /* ── Venue selection modal (Google Places results) ──────────────────────────── */
  .suggest-result-row {
    padding: var(--space-lg) var(--space-xl);
    border-bottom: 1px solid rgba(156,189,231,0.1);
    cursor: pointer;
    transition: background 150ms ease;
  }
  .suggest-result-row:last-child { border-bottom: none; }
  .suggest-result-row:hover { background: rgba(245,194,94,0.08); }

  .suggest-result-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
    margin-bottom: var(--space-xs);
  }

  .suggest-result-address {
    font-size: 12px;
    color: var(--muted);
  }

  /* ── Candidate loading panel ──────────────────────────────────────────────── */
  .candidate-loading-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 280px;
    padding: var(--space-3xl) var(--space-2xl);
    text-align: center;
  }
  .candidate-loading-name {
    font-size: var(--text-subtitle);
    font-weight: 700;
    color: var(--text);
    margin-bottom: var(--space-2xl);
  }
  .candidate-loading-spinner {
    width: 36px;
    height: 36px;
    border: 3px solid rgba(245,194,94,0.15);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: candidate-spin 0.8s linear infinite;
    margin-bottom: var(--space-xl);
  }
  @keyframes candidate-spin {
    to { transform: rotate(360deg); }
  }
  .candidate-loading-status {
    font-size: var(--text-label);
    color: var(--muted);
    transition: opacity 0.2s;
  }

  /* ── Geo search pin (address marker on map) ──────────────────────────────── */
  .geo-pin {
    cursor: pointer;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
    animation: geo-pin-drop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  @keyframes geo-pin-drop {
    0%   { transform: translateY(-20px); opacity: 0; }
    100% { transform: translateY(0);     opacity: 1; }
  }

  /* ── Admin modals ─────────────────────────────────────────────────────────── */
  .admin-modal {
    position: fixed;
    inset: 0;
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    padding: var(--space-xl);
  }

  .admin-modal-inner {
    background: var(--panel-bg, #0d1f35);
    border: 1px solid rgba(156,189,231,0.22);
    border-radius: var(--radius-lg);
    width: 100%;
    max-width: 520px;
    max-height: calc(var(--app-h, 100svh) * 0.8);
    display: flex;
    flex-direction: column;
    box-shadow: 0 8px 40px rgba(0,0,0,0.7);
  }

  .admin-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-xl) var(--space-xl) var(--space-lg);
    border-bottom: 1px solid rgba(255,255,255,0.07);
    flex-shrink: 0;
  }

  .admin-modal-title {
    font-family: 'Inter', sans-serif;
    font-size: var(--text-body);
    font-weight: 700;
    color: var(--text);
  }

  .admin-modal-close {
    background: none;
    border: none;
    color: var(--muted);
    font-size: 16px;
    cursor: pointer;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-sm);
    transition: background 0.12s;
  }

  .admin-modal-close:hover { background: rgba(255,255,255,0.08); color: var(--text); }

  .admin-modal-body {
    padding: var(--space-xl) var(--space-xl);
    overflow-y: auto;
    flex: 1;
  }

  .admin-edit-card {
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(156,189,231,0.15);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
    margin-bottom: var(--space-lg);
    transition: opacity 0.3s;
  }

  .admin-edit-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: var(--space-md);
  }

  .admin-edit-venue {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
  }

  .admin-edit-meta {
    font-size: var(--text-caption);
    color: var(--muted);
  }

  .admin-edit-diff {
    margin-bottom: var(--space-md);
  }

  .admin-diff-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: 12px;
    padding: var(--space-2xs) 0;
    border-bottom: 1px solid rgba(255,255,255,0.04);
    flex-wrap: wrap;
  }

  .admin-diff-key   { color: var(--muted); min-width: 120px; }
  .admin-diff-before { color: #ff8080; font-family: monospace; }
  .admin-diff-arrow  { color: var(--muted); }
  .admin-diff-after  { color: var(--color-success); font-family: monospace; }

  .admin-edit-actions {
    display: flex;
    gap: var(--space-md);
  }

  .admin-approve-btn {
    background: rgba(100,255,180,0.12);
    border: 1px solid rgba(100,255,180,0.4);
    border-radius: var(--radius-sm);
    color: var(--color-success);
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 600;
    padding: var(--space-sm) var(--space-lg);
    cursor: pointer;
    transition: background 0.15s;
  }

  .admin-approve-btn:hover { background: rgba(100,255,180,0.22); }

  .admin-reject-btn {
    background: rgba(255,107,107,0.12);
    border: 1px solid rgba(255,107,107,0.35);
    border-radius: var(--radius-sm);
    color: var(--color-error);
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 600;
    padding: var(--space-sm) var(--space-lg);
    cursor: pointer;
    transition: background 0.15s;
  }

  .admin-reject-btn:hover { background: rgba(255,107,107,0.22); }

  /* (Edit banner mobile layout merged into the main #edit-overlay rules above.) */

  /* Timeline */
  .card-timeline {
    margin-top: var(--space-sm);
  }

  /* Now button */
  #now-btn {
    background: rgba(156,189,231,0.12);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--muted);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-caption);
    font-weight: 500;
    padding: var(--space-xs) var(--space-md);
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
    flex-shrink: 0;
  }
  #now-btn.active {
    background: var(--accent-dim);
    border-color: var(--accent);
    color: var(--accent);
  }
  #now-btn:hover:not(.active) {
    border-color: rgba(245,194,94,0.4);
    color: var(--text);
  }

  /* Dual range slider */
  .time-range-wrap {
    flex: 1;
    position: relative;
    height: 20px;
    display: flex;
    align-items: center;
    overflow: visible;
  }
  .time-range-track-bg {
    position: absolute;
    top: 8px;
    left: 0; right: 0; height: 4px;
    background: rgba(156,189,231,0.18);
    border-radius: var(--radius-none);
    pointer-events: none;
  }
  .time-range-wrap input[type=range] {
    position: absolute;
    width: 100%;
    margin: 0;
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    pointer-events: none;
    height: 20px;
    outline: none;
  }
  .time-range-wrap input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 18px; height: 18px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid #0a1830;
    cursor: pointer;
    pointer-events: all;
    box-shadow: 0 0 14px rgba(245,194,94,0.7);
  }
  .time-range-wrap input[type=range]::-moz-range-thumb {
    width: 18px; height: 18px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid #0a1830;
    cursor: pointer;
    pointer-events: all;
    box-shadow: 0 0 14px rgba(245,194,94,0.7);
  }
  .time-range-wrap input[type=range]:hover::-webkit-slider-thumb {
    background: #FFCBAA;
    box-shadow: 0 0 10px rgba(245,194,94,0.6);
  }
  .time-range-wrap.now-active input[type=range]::-webkit-slider-thumb {
    background: #0a1830;
    border-color: rgba(245,194,94,0.45);
    box-shadow: none;
    cursor: default;
  }
  .time-range-wrap.now-active input[type=range]:hover::-webkit-slider-thumb {
    background: #0d1f40;
    border-color: rgba(245,194,94,0.7);
    box-shadow: 0 0 6px rgba(245,194,94,0.15);
  }
  .time-range-sun-bg {
    position: absolute;
    top: 8px;
    height: 4px;
    background: rgba(245,194,94,0.13);
    border-radius: var(--radius-none);
    pointer-events: none;
  }
  .time-display-row {
    position: absolute;
    top: 22px;
    left: 0; right: 0;
    height: 12px;
  }
  .time-display-row span {
    position: absolute;
    transform: translateX(-50%);
    font-size: 9px;
    color: var(--muted);
    white-space: nowrap;
    transition: left 0.05s;
  }

  /* "Try tomorrow" banner in venue list */
  #no-sun-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: rgba(245,194,94,0.06);
    border: 1px solid rgba(245,194,94,0.18);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    margin-bottom: var(--space-md);
    font-size: 12px;
    color: var(--muted);
  }
  #no-sun-banner button {
    background: rgba(245,194,94,0.15);
    border: 1px solid rgba(245,194,94,0.35);
    border-radius: var(--radius-sm);
    color: var(--accent);
    font-family: 'Inter', sans-serif;
    font-size: var(--text-caption);
    font-weight: 600;
    padding: var(--space-xs) var(--space-lg);
    cursor: pointer;
    transition: background 0.12s;
  }
  #no-sun-banner button:hover { background: rgba(245,194,94,0.25); }

  /* ── Venue suggestion ────────────────────────────────────────────────────── */
  /* Suggest-flow buttons now use role primitives — empty-state "suggest this
     venue" = .s-pill (secondary), modal confirm = .p-pill (primary, hook class
     .suggest-submit for the loading state), Cancel = .g-rnd (ghost). The
     per-flow .suggest-btn / .suggest-btn-primary were retired in Phase 3. */
  .suggest-confirm {
    padding: var(--space-xl) var(--space-xl);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
  }
  .suggest-confirm-name {
    font-size: var(--text-body);
    font-weight: 500;
    color: var(--text);
  }
  .suggest-confirm-address {
    font-size: 12px;
    color: var(--muted);
    margin-bottom: var(--space-xs);
  }
  .suggest-confirm-prompt {
    font-size: var(--text-label);
    color: var(--muted);
    margin: var(--space-md) 0 var(--space-xs);
  }
  .suggest-confirm-actions {
    display: flex;
    gap: var(--space-md);
    flex-wrap: wrap;
  }

  .venue-card.closed-card {
    padding: var(--space-md) var(--space-lg);
    opacity: 0.5;
    cursor: pointer;
  }
  .venue-card.closed-card:hover { opacity: 0.75; }
  /* Hidden by default — closed-day venues were quiet noise at 0.4 opacity.
     The toggle at the bottom of the list reveals them on demand. */
  body:not(.show-closed) #venue-list .venue-card.closed-card { display: none; }

  #closed-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    width: 100%;
    margin: var(--space-md) 0 var(--space-2xl);
    padding: var(--space-md) var(--space-lg);
    background: transparent;
    border: 0;
    color: var(--muted);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    cursor: pointer;
    opacity: 0.65;
    transition: opacity 160ms ease, color 160ms ease;
    -webkit-tap-highlight-color: transparent;
  }
  #closed-toggle:hover { opacity: 1; color: var(--accent); }
  body.show-closed #closed-toggle { display: none; }
  .closed-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
  }
  .closed-name {
    font-size: 12px;
    color: var(--muted);
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ── RIGHT DETAIL PANEL ───────────────────────────────────────────── */
  /* ── Toast notice strip ─────────────────────────────────────────────── */
  #qc-wrap {
    position: absolute;
    left: 16px;
    top: 70px;     /* search top(16) + search height(46) + gap(8) */
    width: 380px;
    z-index: 820;
    pointer-events: none;
    transition: left 0.28s ease;
  }
  #qc-notice {
    font-size: var(--text-caption);
    color: var(--muted);
    text-align: center;
    padding: var(--space-2xs) var(--space-md) 0;
    opacity: 0;
    transition: opacity 0.4s;
    pointer-events: none;
  }
  #qc-notice.visible { opacity: 1; }


  /* ── Day header — unified 2-row composition.
     Row 1: date · weather (context, smaller, muted)
     Row 2: city-wide sun outlook (the answer, slightly larger, primary)
     Bottom hairline separates the header from the list below so the
     block reads as a contained title module, not text floating in space.
     8pt vertical rhythm; tight typography scale (13 → 16) so the two
     rows feel related, not jumbled. */
  #day-header {
    flex-shrink: 0;
    padding: var(--space-lg) var(--space-lg) var(--space-lg);
    border-bottom: 1px solid rgba(155,169,188,0.10);
  }

  /* Row 1 — flex row, plain text, sort pinned right. */
  #list-sun-header.dh-row1 {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    min-height: 24px;
    font-family: 'Inter', sans-serif;
    font-size: var(--text-label);
    font-weight: 500;
    color: rgba(255, 244, 224, 0.62);
    white-space: nowrap;
    letter-spacing: 0.005em;
  }
  .dh-sep { opacity: 0.35; flex-shrink: 0; font-weight: 400; }

  /* Date — tappable text. Calendar icon + label, transparent at rest
     so it flows with the composition. Three feedback states:
       hover (desktop):  bg + color shift to accent
       :active (any):    deeper bg flash for tap-down confirmation
       .active (state):  accent color while the date picker is open
     -webkit-tap-highlight stays default so iOS adds its native flash
     on top of our :active — belt and braces. */
  #header-date-chip.dh-date {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: var(--space-2xs) var(--space-xs);
    margin: calc(var(--space-2xs) * -1) calc(var(--space-xs) * -1);
    height: auto;
    width: auto;
    color: rgba(255, 244, 224, 0.85);
    font: inherit;
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: color 0.12s ease, background 0.12s ease;
  }
  #header-date-chip.dh-date:hover { color: var(--accent); background: rgba(255,242,235,0.05); }
  #header-date-chip.dh-date:hover .chip-cal-icon { color: var(--accent); }
  #header-date-chip.dh-date:active { background: rgba(255,242,235,0.12); color: var(--accent); }
  #header-date-chip.dh-date:active .chip-cal-icon { color: var(--accent); }
  #header-date-chip.dh-date.active { color: var(--accent); }
  #header-date-chip.dh-date.active .chip-cal-icon { color: var(--accent); }
  #header-date-chip.dh-date .chip-cal-icon {
    color: rgba(255, 244, 224, 0.55);
    flex-shrink: 0;
    transition: color 0.12s ease;
  }

  /* Weather inline group — icon + temp · wind. */
  #header-wx-chip.dh-weather {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    background: transparent;
    border: 0;
    padding: 0;
    color: inherit;
    font-weight: 500;
    flex: 1;
    min-width: 0;
  }
  #header-wx-chip.dh-weather #header-wx-icon {
    font-size: 14px;
    line-height: 1;
    flex-shrink: 0;
    opacity: 0.85;
    display: inline-flex;
    align-items: center;
  }
  #header-wx-chip.dh-weather #header-wx-temp {
    color: rgba(255, 244, 224, 0.85);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
  }
  #header-wx-chip.dh-weather #header-wx-wind { font-variant-numeric: tabular-nums; }
  #header-wx-chip.dh-weather .dh-sep-mid { opacity: 0.35; }
  /* Wind hidden from the header — surfaced per-venue in the card and
     detail panel where wind-shelter actually changes the recommendation.
     At the day level it was reading as noise. JS still writes to these
     spans (cheap, harmless); CSS hides them. */
  #header-wx-chip.dh-weather #header-wx-wind,
  #header-wx-chip.dh-weather .dh-sep-mid { display: none; }

  /* Sort icon — must override the legacy #sort-toggle-btn glass-button
     styling that's still in the cascade (specificity-wise the ID rule
     above wins over .sort-icon-btn, hence #sort-toggle-btn here). */
  .sort-icon-label-sr {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  #day-header #sort-toggle-btn,
  #sort-toggle-btn.sort-icon-btn {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: 0;
    margin: 0;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    border-radius: var(--radius-sm);
    flex-shrink: 0;
    transition: background 0.12s ease, color 0.12s ease;
  }
  #day-header #sort-toggle-btn:hover,
  #sort-toggle-btn.sort-icon-btn:hover  { background: rgba(255,242,235,0.06); border-color: transparent; }
  /* Tap-down feedback for mobile (and desktop click). Stronger than
     hover so the user sees their press register. */
  #day-header #sort-toggle-btn:active,
  #sort-toggle-btn.sort-icon-btn:active { background: rgba(255,242,235,0.14); }
  #day-header #sort-toggle-btn.open,
  #sort-toggle-btn.sort-icon-btn.open   { background: rgba(245,194,94,0.10); border-color: transparent; box-shadow: none; }
  .sort-icon { color: rgba(255, 244, 224, 0.55); opacity: 1; transition: color 0.12s ease; }
  #day-header #sort-toggle-btn:hover  .sort-icon { color: var(--accent); }
  #day-header #sort-toggle-btn:active .sort-icon { color: var(--accent); }
  #day-header #sort-toggle-btn.open   .sort-icon { color: var(--accent); }
  #header-wx-chip .chip-dot { opacity: 0.55; }

  /* Sort button — tappable, glass with chevron. Pinned to the right edge so
     the date+weather pair sits flush at the left in any state. */
  #list-sun-header #sort-toggle-btn {
    margin-left: auto;
    background: var(--glass-action-bg);
    border: var(--glass-border);
    border-radius: var(--radius-md);
    box-shadow: none;
    transition: color 0.12s, border-color 0.12s, background 0.12s;
  }
  #list-sun-header #sort-toggle-btn .sort-chevron {
    color: var(--muted);
    transition: transform 0.18s ease;
    flex-shrink: 0;
  }

  /* Very narrow viewports: drop the wind label so the chips don't crowd */
  @media (max-width: 360px) {
    #header-wx-chip .chip-dot,
    #header-wx-chip #header-wx-wind { display: none; }
  }

  /* ── Sun-section bar: "Sun now — N places in the sun" ─────────────────
     Two stacked text lines on a vertical translate track. JS toggles
     .later on the bar to slide the second line into view (calendar-style). */
  /* Sun outlook row — the "answer" line, plus the sort icon on the right.
     Flex row so the sentence flows in the available width and the sort
     button stays pinned right (its right edge aligns with the cards'
     outer edges below). Wraps naturally if too long. Always present. */
  #sun-section-bar.dh-outlook {
    padding: var(--space-xs) 0 0;
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
    min-width: 0;
  }
  #sun-section-bar.ssb-empty { display: none; }
  .ssb-text {
    flex: 1;
    min-width: 0;
    font-family: 'Inter', sans-serif;
    font-size: 16px;
    font-weight: 500;
    line-height: 1.35;
    color: var(--panel-text);
    letter-spacing: -0.005em;
    font-variant-numeric: tabular-nums;
    overflow-wrap: break-word;
    padding-top: var(--space-2xs);        /* baseline-align with the centered sort icon */
  }

  /* Sort icon sits in this row now — small, right-aligned via the grid's
     third column. Overrides the legacy #sort-toggle-btn glass-button
     styling that's still loaded from the old chip-row context. */
  #sun-section-bar #sort-toggle-btn {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: 0;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    gap: 0;
    justify-self: end;
  }
  #sun-section-bar #sort-toggle-btn:hover  { background: rgba(255,242,235,0.06); border-color: transparent; }
  #sun-section-bar #sort-toggle-btn.open   { background: rgba(245,194,94,0.10); border-color: transparent; box-shadow: none; }
  #sun-section-bar #sort-toggle-btn.open .sort-icon { color: var(--accent); opacity: 1; }

  /* Empty-state CTA in the outlook row — shown when body.day-no-sun is
     on (the day has no usable sun, so the header takes ownership of the
     "Pick another day" decision instead of the list body duplicating
     it). Replaces the sort button, since there's nothing to sort. */
  #sun-empty-cta {
    display: none;
    align-items: center;
    gap: var(--space-xs);
    background: var(--glass-action-bg);
    border: var(--glass-border);
    border-radius: var(--radius-pill);
    box-shadow: none;
    padding: var(--space-sm) var(--space-lg);
    font: 600 12px/1 'Inter', sans-serif;
    color: var(--accent);
    cursor: pointer;
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.12s, border-color 0.12s, transform 0.08s;
  }
  #sun-empty-cta:hover  { background: rgba(245,194,94,0.14); border-color: rgba(245,194,94,0.32); }
  #sun-empty-cta:active { transform: scale(0.97); }
  body.day-no-sun #sun-empty-cta   { display: inline-flex; }
  body.day-no-sun #sort-toggle-btn { display: none; }

  /* ── Calendar float — outside #panel, position: fixed so it escapes transforms ── */
  #ptb-cal-float {
    position: fixed;
    z-index: 1200;  /* Above detail panel (810 desktop / 920 mobile) and FTS (925) */
    display: none;
    /* desktop: position + width set by JS to match FTS */
    left: 16px;
    width: 336px;
    min-width: 300px;
    max-width: 480px;
    transition: left 0.28s ease;
  }
  #ptb-cal-float.open { display: block; }
  /* Header + close button — hidden on desktop, shown on mobile */
  #ptb-cal-header {
    display: none;
  }

  /* ── Mobile: calendar as bottom-sheet ──────────────────────────────────── */
  @media (max-width: 639px) {
    /* Override display:none with a transform-based show/hide so we can animate */
    #ptb-cal-float {
      display: block !important; /* always in layout; visibility via transform+opacity */
      left: 0;
      right: 0;
      width: auto;
      top: auto;
      bottom: 0;
      /* The calendar is one of the few sheets where a deliberate fixed
         beat (one month + peek of the next) matters more than content-fit.
         Keep the 56svh cap — svh = stable visible viewport, never clipped
         by browser chrome. */
      max-height: 56svh;
      /* Extra bottom room above var(--app-pad-b) (the home-indicator inset) so
         the bottom-most button doesn't crowd the OS home bar. */
      padding: 0 var(--space-xl) calc(var(--app-pad-b) + var(--space-lg));
      box-sizing: border-box;
      background: var(--glass-panel-bg);
      backdrop-filter: var(--glass-blur-panel);
      -webkit-backdrop-filter: var(--glass-blur-panel);
      border-radius: var(--radius-lg) var(--radius-lg) 0 0;
      border: 1px solid rgba(156,189,231,0.22);
      border-bottom: none;
      box-shadow: inset 0 1px 0 rgba(255,242,235,0.22), 0 -8px 40px rgba(0,0,0,0.50);
      transform: translateY(100%);
      opacity: 0;
      pointer-events: none;
      transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1), opacity 180ms ease-out;
      z-index: 1200;  /* Above the mobile detail panel (920) */
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
    }
    #ptb-cal-float.open {
      display: block !important;
      transform: translateY(0);
      opacity: 1;
      pointer-events: auto;
    }
    /* Header row: title + close button — visible on mobile */
    #ptb-cal-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: var(--space-xl) 0 var(--space-md);
      flex-shrink: 0;
    }
    #ptb-cal-title {
      font-family: 'Inter', sans-serif;
      font-size: var(--text-body);
      font-weight: 600;
      color: var(--text);
      /* The sheet background is transparent (blur-only), so over the light map
         the cream title had no contrast — "white on transparent". A soft dark
         halo (same technique as the map labels) restores legibility without
         changing the cream type colour. */
      text-shadow: 0 1px 3px rgba(17,30,56,0.55);
    }
    #ptb-cal-close {
      background: rgba(156,189,231,0.12);
      border: none;
      border-radius: 50%;
      width: 28px;
      height: 28px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      color: var(--muted);
      flex-shrink: 0;
      transition: background 0.12s, color 0.12s;
    }
    #ptb-cal-close:hover { background: rgba(156,189,231,0.22); color: var(--text); }
    /* Inner panel: no extra animation on mobile — the outer float handles reveal */
    #ptb-cal-float #qc-panel {
      overflow: visible;
      max-height: none !important;
      margin-top: 0;
      transition: none;
    }
    #ptb-cal-float #qc-panel.open { max-height: none !important; }
    #ptb-cal-float #qc-panel-inner {
      background: transparent;
      border: none;
      border-radius: 0;
      box-shadow: none;
      backdrop-filter: none;
      -webkit-backdrop-filter: none;
      overflow: visible;
      opacity: 1 !important;
      pointer-events: auto !important;
    }
    #ptb-cal-float #qc-date-section {
      padding: 0;
      overflow: visible;
      height: auto !important; /* override JS _syncQcPanelHeight inline height */
    }
    /* 10-day grid: 5 columns × 2 rows — all days visible at once, no scroll */
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      gap: var(--space-sm);
      overflow: visible;
    }
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) .dc-tile {
      justify-content: center;
      border-radius: var(--radius-md);
      gap: var(--space-2xs);
      padding: var(--space-md) var(--space-xs) var(--space-sm);
    }
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) .dc-day  { font-size: 9px; font-weight: 700; letter-spacing: 0.1em; }
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) .dc-num  { font-size: 17px; font-weight: 700; line-height: 1; }
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) .dc-icon { font-size: var(--text-body); line-height: 1.2; }
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) .dc-icon .wx-sky-icon { width: 18px; height: 18px; }
    #ptb-cal-float #qc-cal .dc-grid:not(.dc-month-grid) .dc-temp { font-size: 10px; font-weight: 600; color: rgba(156,189,231,0.72); }
    /* Month-view full calendar on mobile */
    #ptb-cal-float #qc-cal .dc-cal-scroll { height: calc(var(--app-h, 100svh) * 0.55); }
    #ptb-cal-float #qc-cal .dc-month-grid .dc-tile { height: 48px; }
    #ptb-cal-float #qc-cal .dc-month-grid .dc-tile.solar-only { height: 48px; min-height: 48px; }
  }

  #qc-presets {
    display: flex;
    gap: var(--space-2xs);
    padding: var(--space-2xs);
    background: rgba(156, 189, 231, 0.08);
    border: 1px solid rgba(156, 189, 231, 0.14);
    border-radius: var(--radius-md);
    position: relative;
  }
  .qc-preset-btn {
    flex: 1;
    background: rgba(17,30,56, 0.55);
    border: 1px solid rgba(156, 189, 231, 0.22);
    border-radius: var(--radius-sm);
    color: rgba(156, 189, 231, 0.65);
    font-family: 'Inter', sans-serif;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding: var(--space-xs) var(--space-xs);
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
    text-align: center;
    -webkit-tap-highlight-color: transparent;
    outline: none;
  }
  .qc-preset-btn:hover:not(.active) {
    color: rgba(156, 189, 231, 0.95);
    background: rgba(24, 52, 95, 0.8);
    border-color: rgba(245, 194, 94, 0.28);
  }
  .qc-preset-btn.active { background: var(--accent); color: #111E38; border-color: var(--accent); }

  /* Calendar section inside the calendar float */
  #qc-panel {
    overflow: hidden;
    max-height: 0;
    margin-top: var(--space-md);
    transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  }
  #qc-panel.open {
    max-height: var(--qc-panel-h, 290px);
  }
  #qc-panel-inner {
    width: 100%;
    box-sizing: border-box;
    background: var(--glass-panel-bg);
    backdrop-filter: var(--glass-blur-panel);
    -webkit-backdrop-filter: var(--glass-blur-panel);
    border: var(--glass-border);
    border-radius: var(--radius-md);
    box-shadow: 0 6px 24px rgba(0,0,0,0.50);
    overflow: hidden;
    opacity: 1;
    transition: opacity 0.25s ease;
  }
  #qc-panel:not(.open) #qc-panel-inner {
    opacity: 0;
    transition: opacity 0.2s ease;
    pointer-events: none;
  }
  #qc-date-section { display: none; padding: var(--space-md); box-sizing: border-box; width: 100%; overflow: hidden; }
  #qc-date-section.active { display: flex; flex-direction: column; }
  #qc-cal { flex: 1; min-height: 0; display: flex; flex-direction: column; width: 100%; box-sizing: border-box; overflow: clip; }
  #qc-panel.cal-expanded #qc-cal { overflow: clip; flex: 1; }
  #qc-cal .dc-cal-scroll {
    height: 390px; overflow-y: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Soft bottom fade hints at more content without eating into the last row of tiles. */
    mask-image: linear-gradient(to bottom, black 96%, transparent 100%);
    -webkit-mask-image: linear-gradient(to bottom, black 96%, transparent 100%);
  }
  #qc-cal .dc-cal-scroll::-webkit-scrollbar { display: none; }
  #qc-cal .dc-grid:not(.dc-month-grid) { display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: auto; gap: var(--space-2xs); width: 100%; box-sizing: border-box; }
  #qc-cal .dc-grid:not(.dc-month-grid) .dc-tile  { padding: var(--space-xs) var(--space-2xs); gap: var(--space-2xs); }
  #qc-cal .dc-grid:not(.dc-month-grid) .dc-day   { font-size: 8px; }
  #qc-cal .dc-grid:not(.dc-month-grid) .dc-num   { font-size: var(--text-body); }
  #qc-cal .dc-grid:not(.dc-month-grid) .dc-icon  { font-size: 17px; line-height: 1.1; }
  #qc-cal .dc-grid:not(.dc-month-grid) .dc-icon .wx-sky-icon { width: 20px; height: 20px; }
  #qc-cal .dc-month-grid .dc-icon .wx-sky-icon { width: 16px; height: 16px; }
  #qc-cal .dc-grid:not(.dc-month-grid) .dc-temp  { font-size: 12px; font-weight: 700; color: rgba(156,189,231,0.72); }
  /* Full-calendar month grid: 7 columns, 4px gap, 48px tiles */
  #qc-cal .dc-month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--space-xs); width: 100%; box-sizing: border-box; }
  #qc-cal .dc-month-grid .dc-tile { padding: var(--space-xs) var(--space-2xs); gap: var(--space-2xs); height: 44px; justify-content: center; }
  #qc-cal .dc-month-grid .dc-tile.solar-only { height: 44px; min-height: 44px; }
  #qc-cal .dc-month-grid .dc-num  { font-size: var(--text-body); font-weight: 600; }
  #qc-cal .dc-month-grid .dc-icon { font-size: 14px; line-height: 1.1; }
  /* Weekday row: 7 columns matching day grid, sticky at scroll-area top */
  #qc-cal .dc-weekday-row {
    display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--space-xs);
    height: 24px; align-items: center;
    position: sticky; top: 0; z-index: 2;
    background: rgba(17,30,56,0.85);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid rgba(156,189,231,0.12);
    margin-bottom: var(--space-2xs);
  }
  /* Month label: sticky just below weekday row, same backdrop */
  #qc-cal .dc-month-row-label {
    position: sticky; top: 26px; /* weekday row (24px) + bottom border (1px) + gap (1px) */
    z-index: 1;
    background: rgba(17,30,56,0.85);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
    padding: var(--space-xs) var(--space-2xs);
  }

  /* Suppress venue card enter animation on map-pan re-renders */
  #venue-list[data-no-anim] .venue-card { animation: none !important; }

  /* Mobile overrides */
  @media (max-width: 639px) {
    #qc-wrap {
      left: 12px;
      right: 12px;
      top: 68px;
      width: auto;
    }
  }

  #detail-panel {
    position: absolute;
    top: 70px;     /* align with list panel: 16 search-top + 46 search-h + 8 gap */
    left: 412px;   /* 16 + 380 + 16 */
    bottom: 16px;
    width: 380px;  /* match the venue-list panel width */
    border-radius: var(--radius-lg);
    /* Cream-frost content world (2026-05-27): the detail sheet is now pure-frost
       — transparent fill + heavy blur over the map, ink text — matching the
       venue-list panel + accept sheet. Cards inside step up as cream tiles
       (#detail-panel .dp-card rule below). No --panel-text/--ink-muted override
       any more: the shell's text falls back to the global ink values. */
    background: var(--glass-panel-bg);
    backdrop-filter: var(--glass-blur-frost);
    -webkit-backdrop-filter: var(--glass-blur-frost);
    border: var(--glass-border);
    box-shadow: var(--panel-shadow);
    color: var(--panel-text);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    z-index: 810;
    transform: translateX(-12px);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.26s ease, opacity 0.26s ease, left 0.28s ease;
    contain: layout;
  }
  /* Cream-frost tile on the frost sheet (2026-05-27). The card is a translucent
     cream tile lifted by a soft shadow. Flip the text tokens to INK within the
     card scope so every --text / --text-secondary / --muted consumer (name,
     meta, sub) reads dark; honey (--accent) stays honey and picks up a casing
     where it's a signal (rules below). */
  #detail-panel .dp-card,
  #detail-panel .venue-card,
  #detail-panel .dp-plans-block,
  #detail-panel .dp-social-card,
  #detail-panel .dp-info-card,
  #detail-panel .dp-shelter-section {
    background: var(--card-cream-bg);
    backdrop-filter: var(--card-cream-blur);
    -webkit-backdrop-filter: var(--card-cream-blur);
    border: 1px solid var(--card-cream-border);
    box-shadow: var(--card-cream-shadow);
    --text: #111E38;
    --text-secondary: rgba(17,30,56,0.64);
    --muted: rgba(17,30,56,0.50);
    color: #111E38;
    /* Tabular figures — detail times / sun-hours / temps don't twitch. */
    font-variant-numeric: tabular-nums;
  }
  #detail-panel.open {
    transform: translateX(0);
    opacity: 1;
    pointer-events: auto;
  }
  /* Desktop cal-open: don't slide the side panel away (it doesn't
     visually overlap the FTS float anyway), but drop its z-index so
     it can never sit in front of the calendar by accident. */
  body.cal-open #detail-panel.open {
    z-index: 700;
  }
  /* dp-content wraps the rendered content so dp-handle can sit above the scroll area */
  #dp-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 0;
  }
  /* ── Venue photo gallery ────────────────────────────────────────────────── */
  .dp-photos {
    display: flex;
    gap: var(--space-2xs);
    overflow-x: auto;
    scrollbar-width: none;
    margin: -14px calc(var(--space-xl) * -1) var(--space-lg);
    scroll-snap-type: x mandatory;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    overflow: hidden;         /* clip rounded corners */
    overflow-x: auto;         /* re-enable horizontal scroll */
    position: relative;
    /* Browser claims horizontal pans for native scroll, so the detail
       panel's drag handler never sees them (was sliding the panel up/
       down on any minor vertical drift during a swipe). */
    touch-action: pan-x;
  }
  /* Bevel: soft gradient at the top of the photo section, blending into the panel glass */
  .dp-photos::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 22px;
    background: linear-gradient(to bottom, rgba(14,26,52,0.55), transparent);
    z-index: 2;
    pointer-events: none;
  }
  .dp-photos::-webkit-scrollbar { display: none; }
  .dp-photo {
    width: 304px;  /* detail-panel width 336 - 32px horizontal padding */
    height: 190px;
    object-fit: cover;
    flex-shrink: 0;
    scroll-snap-align: start;
    background: rgba(17,30,56,0.6);
  }
  .dp-photo:first-child:last-child {
    width: 100%; /* single photo fills full width */
  }

  #dp-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: var(--space-lg) var(--space-xl) var(--space-2xl);
    scrollbar-width: thin;
    scrollbar-color: rgba(245,194,94,0.2) transparent;
    contain: layout style;
  }
  #dp-scroll::-webkit-scrollbar { width: 4px; }
  #dp-scroll::-webkit-scrollbar-track { background: transparent; }
  #dp-scroll::-webkit-scrollbar-thumb { background: rgba(245,194,94,0.2); border-radius: var(--radius-none); }
  .dp-header-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-md);
    margin-bottom: var(--space-xs);
  }
  .dp-venue-name {
    font-family: 'Inter', sans-serif;
    font-style: normal;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.02em;
    text-transform: none;
    color: var(--panel-text);
    line-height: 1.15;
    flex: 1;
    min-width: 0;
  }
  #dp-close-btn {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(17,30,56,0.08);
    border: 1px solid rgba(17,30,56,0.18);
    border-radius: var(--radius-sm);
    color: rgba(17,30,56,0.55);
    font-size: 14px;
    cursor: pointer;
    margin-top: var(--space-2xs);
    transition: background 0.12s, color 0.12s;
  }
  #dp-close-btn:hover { background: rgba(17,30,56,0.14); color: var(--panel-text); }
  .dp-close-back { display: none; }  /* shown on mobile only */
  .dp-meta {
    font-size: var(--text-caption);
    color: var(--muted);
    margin-bottom: var(--space-md);
    opacity: 0.8;
  }
  .dp-limited-solar {
    font-size: var(--text-caption);
    color: var(--muted);
    background: rgba(17,30,56,0.06);
    border: 1px solid rgba(17,30,56,0.12);
    border-radius: var(--radius-sm);
    padding: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-md);
    opacity: 0.8;
  }
  .dp-dial-wrap {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
    margin: var(--space-md) 0 var(--space-lg);
  }
  .dp-dial-svg {
    width: 100%;
    height: auto;
    max-height: 200px;
    filter: drop-shadow(0 2px 24px rgba(245,194,94,0.18));
  }
  .dp-sun-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-xl);
    border-radius: var(--radius-pill);
    font-size: var(--text-caption);
    font-weight: 700;
    letter-spacing: 0.08em;
  }
  .dp-sun-pill.sunny    { background: rgba(245,194,94,0.88); color: #111E38; }
  .dp-sun-pill.overcast { background: rgba(165,170,178,0.18); color: rgba(165,170,178,0.9); border: 1px solid rgba(165,170,178,0.28); }
  .dp-sun-pill.rainy    { background: rgba(156,189,231,0.18); color: rgba(156,189,231,0.9); border: 1px solid rgba(156,189,231,0.28); }
  .dp-sun-pill.neutral  { background: rgba(245,194,94,0.12); color: var(--accent); border: 1px solid rgba(245,194,94,0.28); }
  .dp-sun-pill.muted    { background: rgba(156,189,231,0.15); color: var(--muted); border: 1px solid rgba(156,189,231,0.22); }
  /* Experience Index */
  .dp-exp-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-top: var(--space-md);
    margin-bottom: var(--space-xs);
  }
  .dp-exp-label { font-size: 14px; font-weight: 400; color: #FFF2EB; }
  .dp-exp-hint  { font-size: 12px; font-weight: 400; color: #9CBDE7; margin-top: 1px; }
  .dp-exp-val   { font-family: 'Inter', sans-serif; font-style: italic; font-size: 16px; font-weight: 600; color: var(--accent); }
  .dp-dial-svg.dp-dial-locked circle:first-of-type { stroke: rgba(245,194,94,0.22); }
  .dp-exp-bar-wrap {
    height: 1px;
    background: rgba(156,189,231,0.15);
    border-radius: var(--radius-none);
    overflow: visible;
    margin-bottom: var(--space-md);
  }
  .dp-exp-bar-fill {
    height: 100%;
    background: var(--accent);
    border-radius: var(--radius-none);
  }
  .dp-score-row {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
    margin-bottom: var(--space-md);
  }
  .dp-score-num {
    font-family: 'Inter', sans-serif;
    font-size: 36px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    flex-shrink: 0;
  }
  .dp-score-num span {
    display: block;
    font-family: 'Inter', sans-serif;
    font-size: 8px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    opacity: 0.5;
    margin-top: var(--space-2xs);
  }
  .dp-score-num.tier-high { color: var(--accent); }
  .dp-score-num.tier-mid  { color: #7dd87d; }
  .dp-score-num.tier-low  { color: #f0b46a; }
  .dp-score-num.tier-poor { color: var(--muted); }
  .dp-score-breakdown {
    font-size: var(--text-caption);
    color: var(--muted);
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    opacity: 0.8;
  }
  .dp-section-label {
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: rgba(17,30,56,0.45);
    margin-bottom: var(--space-md);
    margin-top: var(--space-2xs);
  }
  .dp-divider {
    height: 1px;
    background: rgba(17,30,56,0.10);
    margin: var(--space-lg) 0;
  }
  .dp-address {
    font-size: var(--text-caption);
    color: var(--ink-muted);
    opacity: 1;
    margin-bottom: var(--space-lg);
  }
  .dp-actions {
    display: flex;
    gap: var(--space-sm);
  }

  .dp-busy-row {
    display: flex;
    align-items: baseline;
    gap: var(--space-xs);
    margin-bottom: var(--space-sm);
  }
  .dp-busy-now {
    font-size: 12px;
    font-weight: 600;
    color: var(--text);
  }
  .dp-busy-est {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: rgba(156,189,231,0.35);
  }
  .dp-busy-chart {
    width: 100%;
  }
  .dp-busy-labels {
    display: flex;
    justify-content: space-between;
    font-size: 9px;
    color: var(--muted);
    opacity: 0.5;
    margin-top: var(--space-xs);
  }

  /* Detail panel drag handle */
  #dp-handle {
    display: none; /* shown on mobile only */
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-xl) 0 var(--space-lg);
    flex-shrink: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    touch-action: none;
    position: relative;
  }
  #dp-handle-pill {
    width: 36px;
    height: 4px;
    background: rgba(17,30,56,0.22);
    border-radius: var(--radius-none);
  }

  /* Hide UI elements when detail panel is open on mobile */
  .mobile-ui-hidden { display: none !important; }

  /* ──── Detail panel — photo header with overlay ───────────────────────── */
  /* The photo block is the venue takeover: a single rectangular frame with
     the photo carousel filling it, a dark gradient over the bottom 55%, and
     the venue name + meta laid on top in cream. Friend chip lives top-right.
     Title moved here from the dp-card so identity reads with the photo. */
  .detail-new-photos {
    position: relative;
    /* Was a fixed 200px (cover-crop, height never varied by ratio). Shortened
       to 168px so the hero reads as a band, not a block. Aspect-aware height
       (let odd ratios "peek") needs JS per-image sizing — deferred; the
       carousel needs a uniform frame height anyway. */
    height: 168px;
    border-radius: var(--radius-md);
    margin-bottom: var(--space-lg);
    overflow: hidden;
    background: #2a3a5c;
    flex-shrink: 0;
    contain: layout style;
  }
  .detail-new-photos-scroll {
    position: absolute;
    inset: 0;
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
  }
  .detail-new-photos-scroll::-webkit-scrollbar { display: none; }
  .detail-new-photos-scroll img {
    height: 100%;
    min-width: 100%;
    max-width: 100%;
    object-fit: cover;
    flex-shrink: 0;
    scroll-snap-align: start;
  }

  /* Dark gradient over the bottom 55% so cream text stays readable on any
     photo. Top half stays clear so the photo's atmosphere reads. */
  .photo-overlay-grad {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
      to bottom,
      rgba(15,27,42,0) 0%,
      rgba(15,27,42,0) 45%,
      rgba(15,27,42,0.55) 78%,
      rgba(15,27,42,0.88) 100%
    );
  }

  /* Title + meta lockup, anchored to the bottom-left of the photo. */
  .photo-overlay-header {
    position: absolute;
    left: 16px;
    right: 16px;
    bottom: 14px;
    pointer-events: none;
  }
  .photo-overlay-title {
    color: var(--text);
    font-size: var(--text-title);
    font-weight: 700;
    line-height: 1.18;
    letter-spacing: -0.012em;
    text-shadow: 0 1px 4px rgba(0,0,0,0.45);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .photo-overlay-meta {
    color: rgba(255,244,224,0.78);
    font-size: 12.5px;
    font-weight: 500;
    margin-top: var(--space-xs);
    text-shadow: 0 1px 3px rgba(0,0,0,0.45);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Friend-here chip — top-right of the photo. Honey-tinted glass capsule
     with cream text. Compact (18px avatars + name + count). */
  .detail-new-photos .photo-overlay-chip {
    position: absolute;
    top: 12px;
    right: 12px;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-md) var(--space-xs) var(--space-xs);
    border-radius: var(--radius-pill);
    background: rgba(245,194,94,0.22);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid rgba(245,194,94,0.35);
    color: var(--text);
    font-size: 11.5px;
    font-weight: 600;
    box-shadow: 0 2px 6px rgba(0,0,0,0.30);
    pointer-events: auto;
  }
  .detail-new-photos .photo-overlay-chip .avatar-row { gap: calc(var(--space-xs) * -1); }

  /* No-photo empty state — same frame + overlay structure, with a soft
     slate base + warm honey radial wash + centered camera icon. The title
     overlay still renders so the venue still reads as primary. */
  .detail-new-photos-empty {
    background: rgba(17,30,56,0.85);
  }
  .detail-new-photos-empty-art {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--accent);
    opacity: 0.55;
    pointer-events: none;
  }

  /* Photo count indicator — top-left of the photo, only when multi-photo.
     Compact "1 / 8" pill on glass. Static (not scroll-tracked) for now. */
  .photo-overlay-count {
    position: absolute;
    top: 12px;
    left: 12px;
    padding: var(--space-2xs) var(--space-md);
    border-radius: var(--radius-pill);
    background: rgba(15,27,42,0.55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    color: rgba(255,244,224,0.92);
    font-size: var(--text-caption);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    pointer-events: none;
  }

  /* Friend chip — expand-on-tap. Default state shows the summary pill;
     when .expanded, a panel below the summary lists every friend with
     their check-in time. Scoped only to is-expandable (2+ friends). */
  .detail-new-photos .photo-overlay-chip.is-expandable { cursor: pointer; }
  .detail-new-photos .photo-overlay-chip-summary {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
  }
  .detail-new-photos .photo-overlay-chip-list {
    display: none;
    margin-top: var(--space-md);
    padding-top: var(--space-md);
    border-top: 1px solid rgba(255,244,224,0.18);
    flex-direction: column;
    gap: var(--space-sm);
  }
  .detail-new-photos .photo-overlay-chip.expanded {
    align-items: flex-start;
    padding: var(--space-md) var(--space-md) var(--space-md);
    border-radius: var(--radius-md);
  }
  .detail-new-photos .photo-overlay-chip.expanded .photo-overlay-chip-summary {
    width: 100%;
  }
  .detail-new-photos .photo-overlay-chip.expanded .photo-overlay-chip-list {
    display: flex;
  }
  .chip-list-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    font-size: 11.5px;
    color: var(--text);
  }
  .chip-list-avatar {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 9px;
    font-weight: 700;
  }
  .chip-list-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .chip-list-time { color: rgba(255,244,224,0.65); font-size: 10.5px; font-weight: 500; flex-shrink: 0; }

  /* ── Sun summary headline (the new dp-card content) ─────────────────────── */
  .dp-card .dp-sun-headline {
    /* Ink, not honey — no yellow fonts on the cream card (per user). The sun
       signal lives in the fills (timeline bar, sun pill), not in coloured text. */
    color: var(--text);
    font-size: var(--text-title);
    font-weight: 700;
    line-height: 1.18;
    letter-spacing: -0.012em;
  }
  .dp-card .dp-sun-sub {
    color: var(--text-secondary);  /* ink on the cream card (scoped token) */
    font-size: var(--text-label);
    font-weight: 500;
    margin-top: var(--space-2xs);
    letter-spacing: 0.005em;
  }
  /* Shadow + done states tone the headline back so it reads as muted info,
     not as a primary "sunny" cue. Ink (not honey) ⇒ no casing needed. */
  .dp-card.state-shadow .dp-sun-headline,
  .dp-card.state-rain   .dp-sun-headline {
    color: rgba(17,30,56,0.82);
    filter: none;
  }
  .dp-card.state-done .dp-sun-headline,
  .dp-card.state-closed .dp-sun-headline {
    color: rgba(17,30,56,0.55);
    filter: none;
  }
  /* Spacing between the headline block and the pills row inside the card.
     Was 12px — tightened to 8px so the card breathes less between rows. */
  .dp-card .dp-sun-headline + .dp-sun-sub + .card-pills,
  .dp-card .dp-sun-headline + .card-pills {
    margin-top: var(--space-sm);
  }
  /* Reserve the disruption-pill row height in the detail card even when there
     are no pills, so the card height (and the timeline's y) stays CONSTANT as
     the user scrubs — the pill set changes with time and was resizing the card,
     making the timeline jump under the finger. Single nowrap row → 26px. */
  .dp-card .dp-card-pills {
    min-height: 26px;
    flex-wrap: nowrap;
    overflow: hidden;
  }

  /* ── Section title (used by Avtaler + Le for vind) ─────────────────────── */
  .dp-section { margin-top: var(--space-lg); }
  .dp-section-title {
    color: var(--ink-muted);  /* ink — section title sits on the frost sheet */
    font-size: var(--text-caption);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: var(--space-md);
  }

  /* ── Plans block — cream tile on the frost sheet (redesign 2026-05-28) ────
     Surface (cream bg + border + shadow) + ink text tokens come from the
     #detail-panel .dp-plans-block rule above; here we own only the layout +
     padding + radius. */
  .dp-plans-block {
    padding: var(--space-lg) var(--space-lg);
    border-radius: var(--radius-md);
  }
  /* Plans stack vertically; each non-first plan gets a hairline ink divider. */
  .dp-plans-block .detail-plan-item:not(:first-of-type) {
    margin-top: var(--space-lg);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--line-l-faint);  /* ink divider on the cream plans tile */
  }
  /* WHEN row: calendar glyph + strong-ink date + a honey-dim time chip. */
  .dp-plans-block .plan-when-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
  }
  .dp-plans-block .plan-when-ico {
    display: inline-flex;
    color: var(--ink-muted);
    flex-shrink: 0;
  }
  .dp-plans-block .plan-when-date {
    font-size: var(--text-body);
    font-weight: 700;
    color: var(--text);              /* strong ink — the date reads first */
    letter-spacing: -0.005em;
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Time = the sun-adjacent reading → a honey-dim chip (deep amber on dim
     honey + honey border), like the list card's sun chip. Honey TEXT alone
     would wash out on cream, so it lives inside a chip. */
  .dp-plans-block .plan-when-chip {
    flex-shrink: 0;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-pill);
    background: var(--accent-dim);
    border: 1px solid var(--accent-border);
    color: var(--accent-on-light);   /* deeper amber — legible honey on cream */
    font-size: var(--text-label);
    font-weight: 700;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
  }
  /* Creator line — muted ink with a small user glyph. */
  .dp-plans-block .plan-creator {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-md);
    color: var(--text-secondary);    /* ink @ 0.64 in this scope */
    font-size: var(--text-label);
    font-weight: 500;
  }
  .dp-plans-block .plan-creator svg { color: var(--ink-muted); flex-shrink: 0; }
  .dp-plans-block .plan-creator span {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Optional plan message — quoted, secondary ink. */
  .dp-plans-block .plan-msg {
    margin-top: var(--space-sm);
    padding-left: var(--space-md);
    border-left: 2px solid var(--line-l);
    color: var(--text-secondary);
    font-size: var(--text-label);
    font-style: italic;
    line-height: 1.4;
  }
  .dp-plans-block .plan-invitees { margin-top: var(--space-md); }
  /* Foot row — Preview ghost on the left, status / RSVP actions on the right. */
  .dp-plans-block .plan-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-md);
    margin-top: var(--space-lg);
  }
  .dp-plans-block .plan-actions { gap: var(--space-md); }
  /* Status chip (answered invitee) — neutral ink, success/error tinted. */
  .dp-plans-block .plan-status {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2xs) var(--space-md);
    border-radius: var(--radius-pill);
    border: 1px solid var(--line-l);
    color: var(--ink-muted);
    font-size: var(--text-label);
    font-weight: 600;
  }
  .dp-plans-block .plan-status-accepted { color: var(--color-success); border-color: var(--color-success); }
  .dp-plans-block .plan-status-declined { color: var(--color-error);   border-color: var(--color-error); }

  /* ── Detail re-architecture (2026-05-28) ───────────────────────────────────
     Sun verdict chip on the photo (top-right) — the #1 answer made glanceable
     before any scroll. On the dark photo overlay, so honey text is legible
     (the cream-tile honey-on-light rule doesn't apply here). Synced on scrub by
     _populateDpCardSlot. */
  .dp-sun-chip {
    position: absolute; top: 12px; right: 12px;
    display: inline-flex; align-items: center; gap: var(--space-2xs);
    max-width: 62%;
    padding: var(--space-2xs) var(--space-md);
    border-radius: var(--radius-pill);
    background: rgba(15,27,42,0.55);
    -webkit-backdrop-filter: var(--blur-control);
    backdrop-filter: var(--blur-control);
    color: rgba(255,244,224,0.95);
    font-size: var(--text-caption); font-weight: 700; line-height: 1.2;
    pointer-events: none;
  }
  .dp-sun-chip svg { flex-shrink: 0; }
  .dp-sun-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dp-sun-chip.state-sun { color: var(--accent); }
  .dp-sun-chip.state-shadow,
  .dp-sun-chip.state-rain { color: rgba(180,205,235,0.95); }
  .dp-sun-chip.state-done,
  .dp-sun-chip.state-closed { color: rgba(255,244,224,0.70); }

  /* Hours paired with the sun panel — a quiet ink line right under the card so
     "is it sunny" + "is it open" are answered together. */
  .dp-hours-line {
    display: flex; align-items: center; gap: var(--space-sm);
    margin-top: var(--space-md);
    color: var(--ink-muted); font-size: var(--text-label); font-weight: 600;
  }
  .dp-hours-line svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--ink-muted); }

  /* Unified social zone — invite CTA + plans told as one story. The zone owns
     the spacing so children's own margins don't double up. */
  .dp-social-zone { display: flex; flex-direction: column; gap: var(--space-md); margin-bottom: var(--space-lg); }
  .dp-social-zone > * { margin: 0; }

  /* Plan card — prominent WHEN (day + big time), sun context for that moment,
     then who + one action. Replaces the old flat when-row stack. */
  .dp-plans-block .dp-plan-card:not(:first-of-type) {
    margin-top: var(--space-lg); padding-top: var(--space-lg);
    border-top: 1px solid var(--line-l-faint);
  }
  .dp-plan-top {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-md);
  }
  .dp-plan-when { display: flex; align-items: baseline; gap: var(--space-sm); min-width: 0; }
  .dp-plan-day { font-size: var(--text-body); font-weight: 600; color: var(--text-secondary); }
  .dp-plan-time {
    font-size: var(--text-subtitle); font-weight: 700; color: var(--text);
    letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
  }
  .dp-plan-sun {
    flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--space-2xs);
    padding: var(--space-2xs) var(--space-sm); border-radius: var(--radius-pill);
    font-size: var(--text-label); font-weight: 700; line-height: 1.2;
  }
  .dp-plan-sun svg { flex-shrink: 0; }
  .dp-plan-sun.in-sun { background: var(--accent-dim); border: 1px solid var(--accent-border); color: var(--accent-on-light); }
  .dp-plan-sun.no-sun { background: var(--line-l-faint); border: 1px solid var(--line-l); color: var(--ink-muted); }
  /* No yellow fonts on the cream tiles — honey TEXT goes ink; the sun signal
     stays in the fills (honey-dim pill bg/border, the timeline bar). */
  #detail-panel .dp-card .card-pill.pill-sol,
  #detail-panel .dp-card .card-pill.pill-overflow {
    color: var(--text);
  }

  /* ── Detail pass 2 (Claude-Design mocks, 2026-05-28) ───────────────────────
     Unified cream-frost cards; sun verdict left + extra pills top-right + tight
     timeline; VENNER zone (who's-here + plan cards); cream info rows. */
  /* Sun card: big "X igjen" on the left, the day's events as a pill column on
     the right. Both sides come from one computeSunWindows source so the numbers
     always agree (fixes the old left/right mismatch). */
  /* Stretch so the left column matches the pill column's height: the title
     pins to the top, the big verdict drops to the bottom — level with the
     lowest event pill (the "Sun conditions" mock). */
  .dp-card .dp-sun-row {
    display: flex; align-items: stretch; justify-content: space-between;
    gap: var(--space-md);
  }
  .dp-card .dp-sun-now {
    min-width: 0; display: flex; flex-direction: column;
    justify-content: space-between; gap: var(--space-sm);
  }
  .dp-card .dp-sun-title {
    font-size: var(--text-caption); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--ink-muted);
  }
  /* "[sun] 5h 35m left" — number big (display), "left" the lighter trailing
     word (inline, never a stacked sub-label). The wrapper's color drives the
     leading icon's currentColor — PR D v3 swapped it from honey
     --accent-on-light to dark --text per user feedback ("dark icons/glyphs
     to the left of the '2h 5m left' info"). Value + word inherit dark from
     their own rules below, so the swap only affects the icon. */
  .dp-card .dp-sun-now-main {
    /* Bumped gap from --space-2xs (no visible space — "7tigjen") to --space-xs
       so "igjen" breathes off the value. */
    display: flex; align-items: baseline; gap: var(--space-xs); flex-wrap: nowrap;
    color: var(--text);
  }
  .dp-card .dp-sun-now-main svg { flex-shrink: 0; align-self: center; }
  .dp-card .dp-sun-now-val {
    font-size: var(--text-display); font-weight: var(--fw-display);
    letter-spacing: var(--tracking-display); line-height: 1; color: var(--text);
    white-space: nowrap;
  }
  /* "igjen / left" — matches the value's size and ink so the phrase reads
     as one breath; weight drops to 500 so the number still carries the
     emphasis. User flagged the smaller-and-muted treatment as visually
     disjointed; sharing size + color closes that gap. */
  .dp-card .dp-sun-now-word {
    font-size: var(--text-display); font-weight: 500; white-space: nowrap;
    line-height: 1; color: var(--text);
    letter-spacing: var(--tracking-display);
  }
  /* PR D v8: match the .dp-sun-now-val display size + weight so verdicts
     like "Ingen sol" read at the same scale as "1h 50m". User flagged
     the previous --text-title (22px / 700) treatment as inconsistent. */
  .dp-card .dp-sun-now-verdict {
    font-size: var(--text-display); font-weight: var(--fw-display);
    letter-spacing: var(--tracking-display); line-height: 1; color: var(--text);
    white-space: nowrap;
  }
  .dp-card .dp-sun-now-label {
    margin-top: var(--space-2xs); font-size: var(--text-label);
    font-weight: 500; color: var(--ink-muted);
  }
  /* Event pills — one column, uniform height + text so it reads as a stack.
     Locked to a 2-pill height (PR D v5) so the card doesn't resize during
     FTS scrubs: 2 × 26px pill + 1 × gap = 60px. Pill count is capped to 2
     in the renderer; "+N" overflow pill replaces the 2nd slot when more
     events exist. min-height keeps the slot reserved even when there's
     only 1 pill at this hour. */
  .dp-card .dp-sun-events {
    flex-shrink: 0; display: flex; flex-direction: column; align-items: flex-end;
    justify-content: flex-start;
    gap: var(--space-xs); max-width: 60%;
    min-height: 60px;
  }
  /* +N overflow pill — same shape as the other event pills, with a tap
     affordance. Opens the dp-events popover on click. */
  .dp-card .dp-evt-more {
    cursor: pointer;
    position: relative;
    background: var(--line-l-faint);
    border: 1px solid var(--line-l-strong);
    color: var(--panel-text);
    font-family: 'Inter', sans-serif;
    transition: background 120ms, transform 90ms;
  }
  /* :hover background reuses the existing rgba(17,30,56,0.06) via
     .dprcv-cta-link:hover in components-overlays.css — combined below. */
  .dp-card .dp-evt-more:active { transform: scale(0.95); }
  /* PR C v6 — expand the tap target on the "+N" overflow pill to
     ~44px tall (iOS HIG min) without changing the 26px visual height. */
  .dp-card .dp-evt-more::after {
    content: '';
    position: absolute;
    top: -9px; bottom: -9px;
    left: -6px; right: -6px;
  }
  /* Click-to-expand popover — absolute over the card, doesn't reflow it.
     Positioned by JS relative to the +N pill that triggered it. */
  .dp-events-popover {
    position: fixed;
    z-index: 99999;
    background: var(--content-bg);
    color: var(--panel-text);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
    padding: var(--space-md);
    display: flex; flex-direction: column;
    gap: var(--space-xs);
    min-width: 180px;
    max-width: 260px;
    opacity: 0;
    transform: translateY(-4px);
    pointer-events: none;
    transition: opacity 160ms ease, transform 160ms ease;
  }
  .dp-events-popover.is-open {
    opacity: 1; transform: translateY(0); pointer-events: auto;
  }
  .dp-events-popover .dp-evt { width: 100%; justify-content: flex-start; }
  .dp-card .dp-evt {
    display: inline-flex; align-items: center; gap: var(--space-2xs);
    height: 26px; padding: 0 var(--space-md); border-radius: var(--radius-md);
    font-size: 12px; font-weight: 700; line-height: 1; white-space: nowrap;
  }
  .dp-card .dp-evt svg { flex-shrink: 0; }
  /* Shade end — jordy-blue, same recipe as the venue-list .pill-skygge. */
  .dp-card .dp-evt-shade {
    background: rgba(156,189,231,0.10); border: 1px solid rgba(156,189,231,0.22);
    color: var(--muted);
  }
  /* Bonus / later sun window — honey, same rim as .pill-overflow; text inks. */
  .dp-card .dp-evt-bonus {
    background: var(--accent-dim); border: 1px solid rgba(245,194,94,0.35);
    color: var(--accent-on-light);
  }
  /* Sundown — quiet neutral, the night cap on the column. */
  .dp-card .dp-evt-moon,
  .dp-card .dp-evt-clock {
    background: var(--line-l-faint); border: 1px solid var(--line-l);
    color: var(--ink-muted);
  }
  .dp-card .dp-sun-row + .card-timeline-block { margin-top: var(--space-md); }
  /* Drop the list-card's extra 16px side padding here — the .dp-card already
     pads 12px, so the bar was inset 28px while the row above was inset 12px.
     Zeroing it lines the timeline up flush with the sun-row content width. */
  .dp-card .card-timeline-block { padding-left: 0; padding-right: 0; }
  /* Slim the detail sun-card timeline well below the 48px accept-panel bar —
     it's a quiet day-shape strip now (pills carry the times). Still tappable +
     scrubbable; 28px stays >= 16 so the recessed channel (drawIndent) renders. */
  .dp-card .card-timeline { height: 28px; position: relative; }
  /* PR D v8: keep the second-row weather info (temp · wind) visible in
     the dp-card timeline scrubber popup at all times. User reported the
     "missing second row" — the legacy gating tied secondary to the
     fts-popup-expanded class which only fires while actively dragging.
     The detail-panel scrubber benefits from always-on weather context,
     so override display:none here. */
  .dp-card .dprcv-timeline-scrubber-label .fts-popup-secondary {
    display: flex;
  }
  /* Weather glyph overlay (dp-card only). Hidden by default on list/hover
     timelines — they're 16–20px tall and a 14px icon would dominate. The
     overlay is layered on top of the canvas; .dprcv-timeline-wx-icon dots
     are positioned by `left:%%` (set by _populateTimelineWeather). */
  .card-timeline .card-timeline-wx { display: none; }
  .dp-card .card-timeline .card-timeline-wx {
    display: block; position: absolute; inset: 0; pointer-events: none;
    z-index: 2;
  }
  .dp-card .card-timeline-wx .dprcv-timeline-wx-icon {
    width: 14px; height: 14px;
  }
  /* The scrubber marker spans the bar top-to-bottom; its base height is the
     48px accept-panel bar, so on the slim 28px detail bar the pill overshot
     20px below. Match it to the slim bar. */
  .dp-card .dprcv-timeline-scrubber { height: 28px; }

  /* Section dividers between zones. */
  .dp-divider { height: 1px; background: var(--line-l-faint); margin: var(--space-lg) 0; }

  /* VENNER zone. */
  .dp-social-zone { display: flex; flex-direction: column; gap: var(--space-md); }
  .dp-social-zone > * { margin: 0; }
  .dp-social-card { border-radius: var(--radius-md); padding: var(--space-lg); }

  /* Who's-here card. */
  .dp-here-card { display: flex; align-items: center; gap: var(--space-md); }
  .dp-here-avatars { display: flex; flex-shrink: 0; }
  .dp-here-avatars .avatar { margin-left: calc(var(--space-md) * -1); border: 2px solid var(--card-cream-bg); border-radius: 50%; }
  .dp-here-avatars .avatar:first-child { margin-left: 0; }
  .dp-here-label { flex: 1; min-width: 0; font-size: var(--text-label); font-weight: 600; color: var(--text); line-height: 1.3; }
  .dp-here-now {
    flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--space-2xs);
    padding: var(--space-2xs) var(--space-sm); border-radius: var(--radius-pill);
    background: var(--accent-dim); border: 1px solid var(--accent-border);
    color: var(--accent-on-light); font-size: var(--text-caption); font-weight: 700;
  }
  .dp-here-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-on-light); }

  /* ── Plan card (composer) ────────────────────────────────────────────────
     ONE rhythm: 12px card padding, 12px between stacked rows. Tight, no
     ad-hoc margins. Resting = cream-frost (from #detail-panel .dp-social-card);
     Active (body.nar-mode) steps to the top-bar control glass. See PANELS.md. */
  .dp-composer { display: flex; flex-direction: column; gap: var(--space-lg); padding: var(--space-lg); }
  body.nar-mode #detail-panel .dp-composer { background: var(--btn-glass-bg); border-color: var(--btn-glass-border); }
  /* "DEL PLAN" blue eyebrow (left) + the date (right), on one tight row. */
  .dp-composer-titlerow {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
  }
  .dp-composer-title {
    font-size: var(--text-caption); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--rain-ink);
  }
  /* Date: compact honey chip + pencil (Resting). Sized to the eyebrow scale
     (label, not subtitle) so it reads tight, like the mock. */
  .dp-composer-when {
    display: inline-flex; align-items: center; gap: var(--space-2xs); min-width: 0;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-pill);
    background: var(--card-cream-bg); border: 1px solid var(--card-cream-border);
    font-size: var(--text-caption); font-weight: 700; color: var(--rain-ink);
    cursor: pointer; text-align: left;
  }
  .dp-composer-when:active { transform: translateY(1px); }
  .dp-composer-sep { font-weight: 400; opacity: 0.6; }
  .dp-composer-chev { display: inline-flex; }
  /* ACTIVE: the date sheds its chip — plain blue label, no pill/pencil. */
  body.nar-mode .dp-composer-chev { display: none; }
  body.nar-mode .dp-composer-when {
    background: none; border-color: transparent; box-shadow: none; padding: 0;
    color: var(--rain-ink);
  }
  /* Chat message — your avatar bottom-left + a Delft-blue bubble (white text).
     Rounded like a modern chat bubble, but the bottom-left corner (toward the
     avatar) is tight, so it reads as "from you". */
  /* Avatar bottom-left + Delft bubble to its right, tight bottom-left corner
     (toward the avatar). */
  .dp-composer-msg { display: flex; align-items: flex-end; gap: var(--space-sm); }
  .dp-composer-msg-av { flex-shrink: 0; display: flex; }
  .dp-composer-msg-av .avatar { border-radius: 50%; }
  .dp-composer-bubble {
    flex: 1; min-width: 0;
    background: var(--bg); color: var(--ctl-icon);
    border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-xs);
    padding: var(--space-sm) var(--space-md);
    font-size: var(--text-label); line-height: 1.4;
  }
  /* Shared slot for the composer's CTA + hint — both absolute-positioned in
     the same 48px box, cross-fading via opacity. Keeps the composer card
     height stable across Resting / Active / Share so the card doesn't jump
     when state toggles. */
  /* Action-slot collapses entirely in nar/share modes (PR D v3) — the
     picker / share page own their own actions. Was a fixed 48px slot
     with both the Send button and an "adjust day/time" hint inside;
     hint removed (user feedback: just clutter), slot now only renders
     the Send in resting state and collapses to 0 elsewhere so the
     composer card shrinks. transition keeps the collapse smooth. */
  .dp-composer-action-slot {
    position: relative;
    height: 48px;
    overflow: hidden;
    transition: height 280ms var(--ease-decelerate);
  }
  body.nar-mode .dp-composer-action-slot,
  body.share-mode .dp-composer-action-slot {
    height: 0;
  }
  .dp-composer-send {
    position: absolute; top: 0; left: 0; right: 0; bottom: 0;
    transition: opacity 280ms var(--ease-decelerate),
                transform 280ms var(--ease-decelerate);
  }
  /* Honey "Send til venner" — the composer's one CTA. */
  .dp-composer-send {
    display: flex; align-items: center; justify-content: center; gap: var(--space-sm);
    height: 48px;
    border: none; border-radius: var(--radius-pill);
    background: var(--accent); color: var(--accent-on);
    font-family: 'Inter', sans-serif; font-size: 14px; font-weight: 700;
    cursor: pointer; box-shadow: var(--shadow-1);
    opacity: 1; transform: translateY(0);
  }
  .dp-composer-send svg { flex-shrink: 0; }
  .dp-composer-send:hover { box-shadow: var(--shadow-2); }
  .dp-composer-send:active { transform: translateY(1px); box-shadow: none;
                             transition: transform 60ms ease-out, box-shadow 60ms ease-out; }
  /* Active / Share: Send hidden — the action-slot itself collapses to 0
     (see above). The opacity tween here covers the brief moment between
     class toggle and height transition completing. */
  body.nar-mode .dp-composer-send,
  body.share-mode .dp-composer-send {
    opacity: 0; pointer-events: none;
  }
  /* Native button chrome reset that doesn't fight the chip styling above. */
  .dp-composer-when { margin: 0; font-family: 'Inter', sans-serif; }

  /* ── Action pager (FEATURE #11 v2, 2026-05-29) ─────────────────────────────
     The composer card (title row + chat bubble) is always visible at top. The
     pager sits below it and holds two pages side by side:
       Page A (.dp-action-page-compose): Send button + hint (Resting) OR the
         NÅR picker (Active — _openNarPanel appends it into the active-slot,
         CSS hides the Resting Send + hint while .nar-mode is on).
       Page B (.dp-action-page-share):   friends label + 4-col avatar grid +
         "eller" + share targets + honey Send overlay + cancel-row. The cancel
         row's margin-top:auto pushes it to the page bottom, so when JS sizes
         the pager to fill the panel content area the cancel sits a constant
         padding from the panel bottom regardless of friend count.
     body.share-mode → track translates -50% (page A slides left, page B
     slides in from the right). The staggered nth-child slide-in on the
     avatars + targets re-fires on every (re)open via the .is-open toggle. */
  body.nar-mode #dp-scroll > *:not(.dp-social-zone) { display: none; }
  body.share-mode #dp-scroll > *:not(.dp-social-zone) { display: none; }

  .dp-action-pager {
    position: relative;
    overflow: hidden;
    /* Resting: pager is invisible (Send button lives inside .dp-composer).
       nar-mode / share-mode set --dp-pager-h so the pager grows to the
       active page's content height. */
    height: var(--dp-pager-h, 0);
    transition: height 360ms var(--ease-decelerate);
  }
  /* Hide the pager entirely in Resting so it doesn't even contribute its
     .dp-social-zone flex gap below the composer card (no empty footprint,
     no big gap between composer and Directions). */
  body:not(.nar-mode):not(.share-mode) .dp-action-pager { display: none; }

  .dp-action-page {
    /* Pages are absolutely positioned so only the "active" page contributes
       to layout flow (via the pager's JS-controlled height). The exiting page
       still renders during the transition but doesn't push layout around. */
    position: absolute;
    top: 0; left: 0;
    width: 100%;
    /* Slide horizontally with iOS-style depth: the outgoing page also
       scales down slightly + dims (reads as receding), and the incoming
       scales up from 0.95 + opacity (reads as advancing). Avoids the flat
       "two sheets side by side" feel of a pure translate. */
    transition: transform 380ms var(--ease-decelerate),
                opacity   320ms var(--ease-decelerate);
    transform-origin: center center;
    will-change: transform, opacity;
  }
  .dp-action-page-compose { transform: translateX(0) scale(1); opacity: 1; }
  .dp-action-page-share   { transform: translateX(100%) scale(0.95); opacity: 0; pointer-events: none; }
  body.share-mode .dp-action-page-compose {
    transform: translateX(-100%) scale(0.95); opacity: 0; pointer-events: none;
  }
  body.share-mode .dp-action-page-share {
    transform: translateX(0) scale(1); opacity: 1; pointer-events: auto;
  }

  /* Both pages flex-fill the pager so cancel rows on each can bottom-anchor
     to the same position via margin-top:auto. */
  .dp-action-page-compose,
  .dp-action-page-share {
    display: flex; flex-direction: column; gap: var(--space-md);
    height: 100%;
  }
  /* The active slot fills the compose page so the picker inside it can also
     fill the height — its cancel row then bottom-anchors like the share page's. */
  .dp-action-active-slot {
    display: flex; flex-direction: column; gap: var(--space-md);
    flex: 1; min-height: 0;
  }
  /* (composer Send/hint visibility now handled by the .dp-composer-action-slot
     cross-fade above — opacity/transform, no display:none, so the card height
     stays stable across state changes.) */

  /* Picker becomes a flex column inside the slot. The NÅR card takes the
     natural space; the Send + Cancel bottom-anchor via margin-top:auto on
     the CTA row so the picker page and share page cancel-rows match. */
  body.nar-mode .nar-picker {
    flex: 1; min-height: 0;
  }
  .nar-picker .dpinvite-cta-row { margin-top: auto; flex-shrink: 0; }
  .nar-picker .dprcv-cta-row { flex-shrink: 0; padding-bottom: var(--space-md); }

  /* Share page: friends card (cream-frost) on top, bottom group (eller +
     targets + send overlay), cancel-row pinned to bottom. The friends card
     absorbs vertical slack (flex:1) so cancel sits at a constant padding
     from the panel bottom regardless of friend count. */
  .dp-share-bottom-group {
    display: flex; flex-direction: column; gap: var(--space-md);
    flex-shrink: 0;
  }
  .dp-share-cancel-row {
    margin-top: auto;
    padding-bottom: var(--space-md);
    flex-shrink: 0;
  }

  /* Friends card — same cream-frost chrome as .nar-card so the picker page
     and the share page mirror each other (eyebrow card up top, dock of
     actions below, cancel pinned at bottom). */
  .dp-share-friends-card {
    display: flex; flex-direction: column; gap: var(--space-lg);
    padding: var(--space-lg); border-radius: var(--radius-md);
    background: var(--card-cream-bg); border: 1px solid var(--card-cream-border);
    box-shadow: var(--card-cream-shadow); overflow: hidden;
    flex: 1; min-height: 0;
  }
  .dp-share-friends-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md); }
  .dp-share-friends-title {
    font-size: var(--text-caption); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--ink-muted);
  }

  /* 4-col avatar grid (was horizontal scroll in v1). Rows scale with friend
     count — 4 → 1 row, 8 → 2 rows, etc. Scrolls vertically inside the card
     when the list overflows. */
  .dp-share-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-md);
    justify-items: center;
    flex: 1; min-height: 0;
    overflow-y: auto;
    scrollbar-width: none;
    padding-right: var(--space-2xs);
  }
  .dp-share-grid::-webkit-scrollbar { display: none; }
  /* Avatar tiles fill their cell rather than their content-sized default, so
     the 4-col rhythm is even regardless of per-tile name-label width. */
  .dp-share-grid .dpinvite-avatar { width: 100%; }

  /* Staggered slide-in — same beat as the legacy invite sheet's .show-share
     page. Avatars first (110–350 ms), targets right behind them (250–330 ms).
     Kicked by the .is-open class which _dpShareOpen toggles off→on per open. */
  .dp-action-page-share.is-open .dpinvite-avatar,
  .dp-action-page-share.is-open .dpinvite-target {
    animation: dprcv-card-in 260ms var(--ease-spring) backwards;
  }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(1) { animation-delay: 110ms; }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(2) { animation-delay: 150ms; }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(3) { animation-delay: 190ms; }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(4) { animation-delay: 230ms; }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(5) { animation-delay: 270ms; }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(6) { animation-delay: 310ms; }
  .dp-action-page-share.is-open .dpinvite-avatar:nth-child(n+7) { animation-delay: 350ms; }
  .dp-action-page-share.is-open .dpinvite-target:nth-child(1) { animation-delay: 250ms; }
  .dp-action-page-share.is-open .dpinvite-target:nth-child(2) { animation-delay: 290ms; }
  .dp-action-page-share.is-open .dpinvite-target:nth-child(3) { animation-delay: 330ms; }
  .dp-action-page-share.is-open .dp-share-friends-card,
  .dp-action-page-share.is-open .dpinvite-or {
    animation: dprcv-card-in 220ms var(--ease-spring) 60ms backwards;
  }
  @media (prefers-reduced-motion: reduce) {
    .dp-action-page-share.is-open .dpinvite-avatar,
    .dp-action-page-share.is-open .dpinvite-target,
    .dp-action-page-share.is-open .dp-share-friends-card,
    .dp-action-page-share.is-open .dpinvite-or { animation: none; }
    .dp-action-page, body.share-mode .dp-action-page-compose,
    body.share-mode .dp-action-page-share { transition: none; }
  }

  /* Send confirmation beat — replaces the gleam-then-close of v1 with a
     proper "you made it" moment:
       0 ms    user taps honey Send → .is-confirming flips on, content
                dissolves (opacity 0 + scale 0.92).
       0–720ms honey gleam sweeps the page (dprcv-panel-sheen).
       320 ms  big check pops in center (dprcv-confirm-pop, spring).
       500 ms  "Invitasjon sendt" title fades in.
       620 ms  "Anna, Bob +2" caption fades in.
       1400 ms _inviteShowSent timer closes the page (pager collapses,
                composer Send cross-fades back in). */
  .dp-action-page-share.is-confirming::after {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(115deg, transparent 32%, var(--accent) 50%, transparent 68%);
    transform: translateX(-120%); opacity: 0; mix-blend-mode: screen;
    animation: dprcv-panel-sheen 720ms var(--ease-decelerate) forwards;
    z-index: 1;
  }
  /* Share-content dissolve — fades + scales out to clear the stage for the
     success overlay. */
  .dp-share-content {
    display: flex; flex-direction: column; gap: var(--space-md);
    height: 100%;
    transform-origin: center 30%;
    transition: opacity 200ms ease, transform 360ms var(--ease-decelerate);
  }
  .dp-action-page-share.is-confirming .dp-share-content {
    opacity: 0; transform: scale(0.94);
    pointer-events: none;
  }
  /* Success overlay — absolute over the share page, hidden by default.
     .is-confirming reveals after the content dissolve. */
  .dp-share-success {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--space-md);
    padding: var(--space-lg);
    opacity: 0; pointer-events: none;
    z-index: 2;
  }
  .dp-action-page-share.is-confirming .dp-share-success {
    opacity: 1; pointer-events: auto;
    transition: opacity 240ms ease 200ms;
  }
  /* Big honey check disc — springs in via dprcv-confirm-pop. */
  .dp-share-success-check {
    width: 96px; height: 96px; border-radius: 50%;
    background: var(--accent); color: var(--accent-on);
    display: flex; align-items: center; justify-content: center;
    box-shadow: var(--shadow-2);
    transform: scale(0);
  }
  .dp-action-page-share.is-confirming .dp-share-success-check {
    animation: dprcv-confirm-pop 600ms var(--ease-spring) 320ms both;
  }
  .dp-share-success-title {
    font-size: var(--text-title); font-weight: 700;
    color: var(--panel-text); text-align: center;
    opacity: 0; transform: translateY(8px);
  }
  .dp-action-page-share.is-confirming .dp-share-success-title {
    animation: dp-share-success-fade 420ms var(--ease-decelerate) 500ms forwards;
  }
  .dp-share-success-sub {
    font-size: var(--text-body); color: var(--ink-muted);
    text-align: center; min-height: 1.2em;
    opacity: 0; transform: translateY(8px);
  }
  .dp-action-page-share.is-confirming .dp-share-success-sub {
    animation: dp-share-success-fade 420ms var(--ease-decelerate) 620ms forwards;
  }
  @keyframes dp-share-success-fade {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  @media (prefers-reduced-motion: reduce) {
    .dp-share-content,
    .dp-share-success,
    .dp-share-success-check,
    .dp-share-success-title,
    .dp-share-success-sub { transition: none; animation: none; }
    .dp-action-page-share.is-confirming .dp-share-success-check { transform: scale(1); }
    .dp-action-page-share.is-confirming .dp-share-success-title,
    .dp-action-page-share.is-confirming .dp-share-success-sub { opacity: 1; transform: none; }
  }

  /* Honey Send overlay button — match the accent CTA in the rest of the app
     (the composer's Resting Send is honey too, per the user's request). */
  #dp-share-send-btn {
    background: var(--accent); color: var(--accent-on);
    border: none; box-shadow: var(--shadow-1);
  }
  #dp-share-send-btn:hover { background: var(--accent); box-shadow: var(--shadow-2); }
  #dp-share-send-btn:active { transform: translateY(1px); box-shadow: none; }
  #dp-share-send-btn:disabled {
    opacity: 0.55; cursor: not-allowed; box-shadow: none;
  }
  /* Zero the legacy margin on .dpinvite-or so the flex gap on the share
     page's bottom-group owns the spacing. */
  .dp-action-page-share .dpinvite-or { margin: 0; }

  /* ── NÅR picker — lives inside the action pager's compose page (in
     .dp-action-active-slot). max-height grows from 0 → 680 px when nar-mode
     is on so the slot's height tracks the picker; _dpPagerResize then drives
     the outer pager height. No outer dividers (the pager owns surrounding
     spacing); the picker still styles its own cream-frost nar-card. */
  .nar-picker {
    display: flex; flex-direction: column; gap: var(--space-md);
    max-height: 0; opacity: 0; overflow: hidden;
    /* Slight lift-in beat: rise 12px while the max-height + opacity transition
       fires so the picker feels like it floats up into place rather than
       sliding in flat. */
    transform: translateY(12px);
    transition: max-height 0.4s cubic-bezier(0.22, 0.61, 0.36, 1),
                opacity 0.28s ease,
                transform 0.36s var(--ease-decelerate);
  }
  body.nar-mode .nar-picker {
    max-height: 680px; opacity: 1; transform: translateY(0);
  }

  /* Date/time picker card — a cream-frost box with a sentence-case title.
     overflow:visible so the FTS scrub popup can pop up over the day tiles. */
  .nar-card {
    display: flex; flex-direction: column; gap: var(--space-lg);
    padding: var(--space-lg); border-radius: var(--radius-md);
    background: var(--card-cream-bg); border: 1px solid var(--card-cream-border);
    box-shadow: var(--card-cream-shadow); overflow: visible;
  }
  .nar-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md); }
  .nar-card-title {
    font-size: var(--text-caption); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--ink-muted);
  }
  /* Day-strip: one horizontal SCROLLING row of legible cream-frost tiles (the
     QC strip is a 5×2 grid of small dark over-map tiles by default). Hide its
     "Vis full kalender" — no full calendar here. */
  .nar-strip .dc-grid {
    display: flex; flex-wrap: nowrap; gap: var(--space-xs);
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;  /* hide the faint scrollbar that overlapped the tiles */
  }
  .nar-strip .dc-grid::-webkit-scrollbar { display: none; }
  .nar-strip .dc-expand-btn-wide { display: none; }
  .nar-strip .dc-tile {
    flex: 0 0 58px; padding: var(--space-sm) var(--space-2xs); gap: var(--space-2xs);
    background: var(--card-cream-bg); border: 1px solid var(--card-cream-border);
  }
  .nar-strip .dc-tile .dc-day  { font-size: var(--text-caption); color: var(--ink-muted); }
  .nar-strip .dc-tile .dc-num  { font-size: var(--text-subtitle); color: var(--panel-text); }
  .nar-strip .dc-tile .dc-temp { font-size: var(--text-caption); color: var(--ink-muted); }
  /* Weather glyph matches the top bar: ink fill, no cream halo. */
  .nar-strip .dc-tile .dc-icon .wx-sky-icon { width: 18px; height: 18px; color: var(--panel-text); filter: none; }
  /* No honey/yellow outlines on the tiles. Today + sun-tier borders go cream;
     the SELECTED day uses a Delft ring (selected = Delft, never honey). */
  .nar-strip .dc-tile.today,
  .nar-strip .dc-tile.sun-high,
  .nar-strip .dc-tile.sun-mid,
  .nar-strip .dc-tile.sun-low { border-color: var(--card-cream-border) !important; opacity: 1; }
  .nar-strip .dc-tile.today .dc-day { color: var(--ink-muted); }
  /* SELECTED day — solid Delft fill + cream text/glyph: an unambiguous "this is
     the picked day" (selected = Delft, never honey). */
  .nar-strip .dc-tile.selected {
    background: var(--bg) !important; border-color: var(--bg) !important; box-shadow: none !important;
  }
  .nar-strip .dc-tile.selected .dc-day,
  .nar-strip .dc-tile.selected .dc-num,
  .nar-strip .dc-tile.selected .dc-temp { color: var(--ctl-icon); }
  .nar-strip .dc-tile.selected .dc-icon .wx-sky-icon { color: var(--ctl-icon); }
  /* Host the reparented FTS: neutralise its fixed/floating positioning + force
     it visible (we don't flip the global body.fts state). Zero the LEFT/RIGHT
     margin (the body.fts rule adds a side inset) so the bar lines up with the
     day-strip edges; keep vertical room for the scrub popup. overflow:visible
     lets the expanded popup overlap the tiles above. */
  /* Extra room above the FTS (beyond the card's row gap) so the unexpanded
     scrub popup sits with breathing space between the day tiles and the bar. */
  .nar-fts-slot { position: relative; overflow: visible; margin-top: var(--space-lg); }
  .nar-fts-slot #fts {
    display: flex !important; position: relative;
    top: auto; bottom: auto; left: auto; right: auto;
    width: 100%; margin-left: 0 !important; margin-right: 0 !important;
    transform: none; opacity: 1;
  }
  /* Primary + cancel — identical layout to the invite panel (.dpinvite-cta-row
     + .dprcv-cta-row/.dprcv-cta-link). Both Send buttons (composer Resting +
     picker Send) are honey now — consistent CTA color across the flow
     regardless of which page the user is on. */
  .nar-send {
    display: flex; flex: 1; align-items: center; justify-content: center; gap: var(--space-sm);
    width: 100%; height: 48px; border: none; border-radius: var(--radius-pill);
    background: var(--accent); color: var(--accent-on);
    font-family: 'Inter', sans-serif; font-size: var(--text-body); font-weight: 700;
    cursor: pointer; box-shadow: var(--shadow-1);
    transition: background 120ms ease-out, box-shadow 120ms ease-out, transform 60ms ease-out;
  }
  .nar-send svg { flex-shrink: 0; }
  .nar-send:hover { box-shadow: var(--shadow-2); }
  .nar-send:active { transform: translateY(1px); box-shadow: none; }

  @media (max-width: 639px) {
    /* Panel fits its now-short content + grows UP as the picker expands
       (bottom-anchored sheet), pushing the plan card up. */
    body.nar-mode #detail-panel.open { height: auto; max-height: 90svh; }
    /* Let the flex children size to content (no forced fill) so the panel
       can shrink/grow with the picker. */
    body.nar-mode #detail-panel.open #dp-content { flex: 0 1 auto; min-height: 0; }
    body.nar-mode #detail-panel.open #dp-scroll { overflow: visible; }
    /* #dp-scroll's default 24px bottom padding left a big, lopsided gap under
       the cancel link in the picker — trim it to match the top rhythm. */
    body.nar-mode #detail-panel.open #dp-scroll { padding-bottom: var(--space-lg); }
  }

  /* Plan card. */
  .dp-plan-card { display: flex; flex-direction: column; gap: var(--space-md); }
  .dp-plan-card .dp-plan-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); }
  .dp-plan-card .dp-plan-when { display: flex; align-items: baseline; gap: var(--space-2xs); min-width: 0; }
  .dp-plan-card .dp-plan-day,
  .dp-plan-card .dp-plan-time { font-size: var(--text-subtitle); font-weight: 700; color: var(--text); }
  .dp-plan-card .dp-plan-time { font-variant-numeric: tabular-nums; }
  .dp-plan-going-av { display: flex; flex-shrink: 0; }
  .dp-plan-going-av .avatar { margin-left: calc(var(--space-md) * -1); border: 2px solid var(--card-cream-bg); border-radius: 50%; }
  .dp-plan-going-av .avatar:first-child { margin-left: 0; }
  .dp-plan-going { font-size: var(--text-label); color: var(--text-secondary); font-weight: 500; }
  /* Sun chip = full-width row (overrides the pass-1 inline pill). */
  .dp-plan-card .dp-plan-sun {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: var(--space-md); border-radius: var(--radius-md);
    background: var(--accent-dim); border: 1px solid var(--accent-border);
  }
  .dp-plan-card .dp-plan-sun.no-sun { background: var(--line-l-faint); border-color: var(--line-l); }
  .dp-plan-card .dp-plan-sun svg { flex-shrink: 0; color: var(--accent-on-light); }
  .dp-plan-card .dp-plan-sun.no-sun svg { color: var(--ink-muted); }
  .dp-plan-sun-txt { display: flex; flex-direction: column; min-width: 0; }
  .dp-plan-sun-main { font-size: var(--text-label); font-weight: 700; color: var(--accent-on-light); }
  .dp-plan-card .dp-plan-sun.no-sun .dp-plan-sun-main { color: var(--text); }
  .dp-plan-sun-sub { font-size: var(--text-caption); color: var(--text-secondary); }
  .dp-plan-actions { display: flex; gap: var(--space-md); align-items: center; }
  .dp-plan-actions .p-pill { flex: 1; height: 44px; }
  .dp-plan-maybe {
    flex: 1; height: 44px; border-radius: var(--radius-pill);
    background: transparent; border: 1px solid var(--line-l); color: var(--text);
    font-size: var(--text-label); font-weight: 600; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .dp-plan-maybe:active { transform: scale(0.97); }

  /* Info rows (mock 1). */
  .dp-info-card { border-radius: var(--radius-md); overflow: hidden; }
  .dp-info-row { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md) var(--space-lg); }
  .dp-info-row + .dp-info-row { border-top: 1px solid var(--line-l-faint); }
  .dp-info-icon { flex-shrink: 0; display: inline-flex; color: var(--ink-muted); }
  .dp-info-icon svg { width: 18px; height: 18px; }
  .dp-info-text { flex: 1; min-width: 0; font-size: var(--text-label); font-weight: 600; color: var(--text); }
  .dp-bars { flex-shrink: 0; display: inline-flex; align-items: flex-end; gap: var(--space-2xs); height: 16px; }
  .dp-bar { width: 4px; background: var(--line-l); }
  .dp-bar:nth-child(1) { height: 6px; }
  .dp-bar:nth-child(2) { height: 9px; }
  .dp-bar:nth-child(3) { height: 12px; }
  .dp-bar:nth-child(4) { height: 14px; }
  .dp-bar:nth-child(5) { height: 16px; }
  .dp-bar.on { background: var(--accent-on-light); }

  /* ── Wind shelter section ─────────────────────────────────────────────── */
  .dp-shelter-section {
    padding: var(--space-lg) var(--space-lg);
    background: var(--surface-content);
    border: 1px solid var(--line-d);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
  }
  .dp-shelter-canvas {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 9 / 5;
    border-radius: var(--radius-md);
  }

  /* ── Info row — adaptive: chips when ≤3 items, list when ≥4 ─────────── */
  .info-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin-top: var(--space-lg);
  }
  .info-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-pill);
    background: var(--glassctl-bg);
    border: var(--glassctl-border);
    box-shadow: var(--glassctl-raise);
    color: var(--panel-text);
    font-size: 12px;
    font-weight: 500;
    line-height: 1;
  }
  .info-chip-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-muted); /* ink — chip sits on the frost sheet */
    font-size: 14px;
  }
  .info-chip-text { font-variant-numeric: tabular-nums; }

  /* ── Footer toned down ─────────────────────────────────────────────────── */
  .dp-footer-quiet {
    margin-top: var(--space-xl);
    padding-top: var(--space-lg);
    /* PR D item #10 — generous bottom inset + safe-area so the last
       interactive elements (the feedback links) sit clear of the iOS
       home-indicator gesture zone. Was effectively just #dp-scroll's
       --space-2xl bottom padding; now the footer carries its own
       breathing room too. */
    padding-bottom: max(var(--space-2xl), env(safe-area-inset-bottom));
    border-top: 1px solid var(--line-l-faint);  /* ink divider on the frost sheet */
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-lg);
    /* Was opacity 0.55 (intentionally "quiet") — bumped so these stay
       discoverable. Visibility now comes from a clear link treatment, not full
       prominence. */
    opacity: 1;
  }
  .dp-footer-quiet .secondary-link {
    background: none;
    border: none;
    padding: var(--space-2xs) 0;
    color: var(--ink-muted);
    font-size: var(--text-label);
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: var(--line-l-strong);
    transition: color 0.12s, text-decoration-color 0.12s;
  }
  .dp-footer-quiet .secondary-link:hover {
    color: var(--panel-text);
    text-decoration-color: currentColor;
  }
  .dp-footer-sep { color: var(--ink-muted); font-size: var(--text-label); opacity: 0.5; }

  /* Merged header: back + title + fav/bell on one row */
  .detail-header-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
  }
  .detail-header-info {
    flex: 1;
    min-width: 0;
  }
  .detail-new-title {
    font-size: 20px;
    font-weight: 700;
    line-height: 1.2;
  }
  .detail-new-sub {
    font-size: 12px;
    color: var(--muted);
    margin-top: var(--space-2xs);
  }
  .detail-new-back {
    padding: var(--space-sm) var(--space-md);
    background: none;
    border: none;
    font-size: var(--text-subtitle);
    color: var(--muted);
    cursor: pointer;
    font-family: 'Inter', sans-serif;
    flex-shrink: 0;
    margin-top: var(--space-2xs);
  }
  .detail-new-back:hover { color: var(--text); }
  .detail-header-actions {
    display: flex;
    gap: var(--space-xs);
    flex-shrink: 0;
    margin-top: var(--space-2xs);
  }
  .dp-header-icon {
    background: none;
    border: none;
    cursor: pointer;
    padding: var(--space-sm);
    color: var(--muted);
    line-height: 0;
    opacity: 0.75;
    transition: opacity 0.15s;
  }
  .dp-header-icon:hover { opacity: 1; }
  .dp-header-icon.active { opacity: 1; }

  /* ── Detail panel action row (directions + utilities) ────────────────────── */
  /* Aligned with the Shades design system's "Secondary Square" spec:
     48×48 rounded squares (radius 12), glass-action fill, --muted at rest,
     --accent-dim background + --accent color when toggled on. The row
     spans the full panel width — the two most-used utilities (directions,
     share) flex to absorb extra space, while toggle-state squares
     (heart, bell, optional phone/website) stay 48×48. The result lines
     up flush with the Invite friends CTA above. */
  /* ── Detail action area — two-row layout ──────────────────────────────────
     Row 1: Directions = primary venue action, Tier 4 honey CTA full-width.
     Row 2: Share / Save / Alert = peer secondary toggles, Tier 3 glass with
            icon + small label so the user doesn't have to recognise glyphs
            cold. */
  .dp-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    margin-bottom: var(--space-lg);
  }

  /* Directions = the canonical Secondary role (.s-rnd) on a light surface
     (.on-light → ink outline). Invite-friends is the screen's only honey CTA;
     Directions reads lighter. This is just the layout modifier — full-width
     pill matching the action-row rhythm; appearance comes from .s-rnd.on-light. */
  /* Directions = Secondary on the dark Delft sheet: Jordy outline + cream
     (overrides .s-rnd's dark glass fill, which would blend on solid Delft).
     text-decoration:none — it's an <a>, not a <button>. */
  .dp-directions {
    /* Cream-frost now (honey moved to the plan card's "Send til venner"). */
    width: 100%; border-radius: var(--radius-pill);
    background: var(--card-cream-bg);
    border: 1px solid var(--card-cream-border);
    box-shadow: var(--card-cream-shadow);
    color: var(--panel-text);
    text-decoration: none;
  }
  .dp-directions:hover { background: var(--card-cream-bg-hover); }
  .dp-directions:active { transform: translateY(1px); box-shadow: none; }
  .dp-directions svg { width: 18px; height: 18px; flex-shrink: 0; }
  .dp-directions-label { overflow: hidden; text-overflow: ellipsis; }

  /* Tier 3 secondary toggles — equal-weight peers in a single row. Each is
     a glass surface with stacked icon + label. Active state (heart, bell)
     fills with honey for a decisive ON cue. */
  .dp-secondary-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-sm);
  }
  .dp-secondary-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2xs);
    height: 48px;
    padding: 0 var(--space-sm);
    border-radius: var(--radius-md);
    /* Cream-frost lens material — same surface as the cards (no box-in-box;
       these sit directly on the frost sheet). Ink text/icon. */
    background: var(--card-cream-bg);
    -webkit-backdrop-filter: var(--card-cream-blur);
    backdrop-filter: var(--card-cream-blur);
    border: 1px solid var(--card-cream-border);
    box-shadow: var(--card-cream-shadow);
    color: var(--panel-text);
    cursor: pointer;
    text-decoration: none;
    font-family: 'Inter', sans-serif;
    transition: all 120ms ease-out;
  }
  .dp-secondary-btn svg {
    width: 20px;
    height: 20px;
    stroke: currentColor;
    color: currentColor;
    flex-shrink: 0;
  }
  .dp-secondary-label {
    font-size: 10.5px;
    font-weight: 600;
    color: inherit;
    line-height: 1;
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
  }
  .dp-secondary-btn:hover {
    background: var(--card-cream-bg-hover);
    color: var(--panel-text);
  }
  .dp-secondary-btn:active { transform: translateY(1px); }
  /* Active toggle state (heart/bell ON) — brighter cream + the glyph fills
     honey-ink. The filled honey glyph carries the "on" signal (Directions is
     now the one honey CTA, so the toggle stays cream, not a honey fill). */
  .dp-secondary-btn.is-active {
    background: var(--card-cream-bg-hover);
    color: var(--accent-on-light);
  }
  .dp-secondary-btn.is-active svg { color: var(--accent-on-light); fill: currentColor; }
  .dp-secondary-btn.is-active:hover { background: var(--card-cream-bg-hover); }

  /* Legacy: keep .primary-action for backwards compat but hidden */
  .primary-action {
    display: flex;
    gap: var(--space-md);
    margin-bottom: var(--space-xl);
  }

  .btn-primary {
    flex: 1;
    background: var(--accent);
    color: #2a1a0a;
    padding: var(--space-lg);
    border-radius: var(--radius-md);
    font-weight: 700;
    font-size: var(--text-body);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    border: none;
    cursor: pointer;
    font-family: 'Inter', sans-serif;
    font-variant-numeric: tabular-nums;
  }

  .btn-primary:hover {
    opacity: 0.9;
  }

  .btn-icon-sec {
    width: 48px;
    height: 48px;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-subtitle);
    cursor: pointer;
    flex-shrink: 0;
    text-decoration: none;
  }

  .btn-icon-sec:hover {
    background: rgba(17,30,56,0.65);
  }

  .btn-icon-sec svg,
  .btn-primary svg {
    width: 20px;
    height: 20px;
    stroke: currentColor;
    color: var(--accent);
  }

  /* ── Photo gallery wrapper + heart/bell overlay ──────────────────────── */
  .detail-new-photos-wrap {
    position: relative;
  }
  .dp-photo-actions {
    position: absolute;
    top: 8px;
    right: 8px;
    display: flex;
    gap: var(--space-sm);
    z-index: 3;
  }
  .dp-photo-actions .dp-header-icon {
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: 50%;
    background: rgba(14,26,52,0.55);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(156,189,231,0.22);
    color: var(--text);
    opacity: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .dp-photo-actions .dp-header-icon svg { width: 18px; height: 18px; }

  /* ── First-photo "friends here" chip overlay ─────────────────────────── */
  /* Wrapper inherits the photo's flex-item geometry so the surrounding
     scroller layout is unchanged; the chip absolute-positions inside. */
  .dp-photo-wrap {
    position: relative;
    flex-shrink: 0;
    height: 100%;
    min-width: 240px;
    max-width: 280px;
    aspect-ratio: 4/3;
    border-radius: var(--radius-md);
    overflow: hidden;
  }
  .dp-photo-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    /* Cancel the scroller-flex sizing — wrapper carries it. */
    min-width: 0 !important;
    max-width: none !important;
    aspect-ratio: auto !important;
    border-radius: inherit;
  }
  .photo-overlay-chip {
    position: absolute;
    bottom: 8px;
    left: 8px;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-md) var(--space-xs) var(--space-xs);
    border-radius: var(--radius-pill);
    background: rgba(14, 26, 52, 0.78);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(156, 189, 231, 0.25);
    color: var(--text);
    font-size: var(--text-caption);
    font-weight: 600;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
    pointer-events: none;
  }
  .photo-overlay-chip span { white-space: nowrap; }

