  :root {
    --shade-900: #122A38;
    --shade-700: #25526C;
    --sun-500: #F2994A;
    --sun-300: #FFD27A;
    --sun-600: #E67E22;
    /* This was #9E571A for one session - a darkened step meant to carry
       text/icon/focus-ring roles at a real WCAG AA contrast (5.1-5.5:1
       vs. #E67E22/#F2994A's 2.2-2.9:1 in those same roles). Reverted on
       direct request: it read as muddy brown next to the bright orange
       already used elsewhere (the search-radius slider's native
       accent-color, still #F2994A) - matching that look is the priority
       here, not the contrast gain. --sun-700 now equals --sun-500
       exactly, kept as its own name only so every selector already
       written against "the foreground register" doesn't need touching
       again if this gets revisited. Text/icon/focus-ring contrast in
       every role that uses this is back down to ~2.2-2.9:1 against white
       and cream - a known, deliberate tradeoff, not an oversight. */
    --sun-700: #F2994A;
    --cream: #FBF7EF;
    --header-bg: #F0E9D8;
    --ink: #16303F;
    /* Was #6B7B85 - measured at 4.4:1 on white/cream, just under the
       4.5:1 WCAG AA minimum for real text (this is used for labels,
       meta rows, and footer links, not just decoration). #62717A is the
       same hue, darkened just enough to clear 4.5:1 on white and 4.72:1
       on cream - a change too small to be visually noticeable on its
       own, but it's the one sitewide token this project has that was
       still short of AA. */
    --muted: #62717A;
    --card-border: #E7E1D4;
  }
  * { box-sizing: border-box; }
  /* No blue flash over whatever is tapped (mobile browsers and the app's web view). The keyboard focus
     outline is separate and stays. */
  html { -webkit-tap-highlight-color: transparent; }
  body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    font-family: -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
    background: var(--cream);
    color: var(--ink);
  }
  header {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: space-between; /* keeps nav-menu-wrap pushed to the
       far right even without .search-bar present as a middle element to
       flex-grow and do that job implicitly - how-it-works.html,
       contact.html, and privacy.html only have two header children
       (brand + nav-menu-wrap, no search bar), so without this they'd
       just sit side-by-side on the left. Doesn't change index.html's own
       layout at all - .search-bar's own flex:1 already consumes all the
       available space there, leaving nothing left for this to
       redistribute differently. */
    gap: 16px;
    width: 100%;
    background: var(--header-bg);
    padding: 14px 24px;
    box-sizing: border-box;
  }
  .header-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    order: 1;
    flex-shrink: 0;
    cursor: pointer;
    text-decoration: none; /* .header-brand is an <a> on how-it-works.html/
       contact.html (a real link to "/", replacing index.html's own
       onclick-based div) - without this, the browser's default link
       styling (blue, underlined) would visibly leak into the "Lumo"
       text, which has no color of its own set elsewhere (unlike the
       "Spot" part, styled separately below via h1 b). Harmless no-op on
       index.html, where this class is still just a plain div. */
    color: inherit;
  }
  .header-brand img { height: 44px; width: 44px; }
  .header-brand h1 { font-size: 22px; margin: 0; letter-spacing: -0.5px; white-space: nowrap; }
  .header-brand h1 b { color: var(--sun-600); font-weight: 700; }

  #homeView {
    flex: 1;
    display: flex;
    flex-direction: column;
    width: 100%;
  }
  /* Plain flex passthrough below 1060px (matches how .content-column
     used to sit directly in #homeView, before it gained this wrapper for
     the desktop sidebar grid) - without this, .content-column's own
     flex:1 below has no effect, since it would no longer have a flex/grid
     parent to grow within, and its white background stops at its content
     instead of reaching the footer. */
  .home-layout {
    display: flex;
    flex-direction: column;
    flex: 1;
    width: 100%;
  }
  /* Mobile/tablet only (never desktop, where .home-layout's own gaps -
     between the sidebar and the content column, or the three split-view
     panes - are deliberately cream, showing each as its own separate
     card): in map view, .map-view's 12px top margin below .content-column
     otherwise exposes the page's cream background as a thin line between
     the two white surfaces, since .map-view sits outside .content-column
     now (a sibling, not nested - see index.html) rather than sharing its
     parent's own white background the way it used to. */
  @media (max-width: 1059px) {
    .home-layout { background: white; }
  }
  .content-column {
    flex: 1;
    max-width: 640px;
    margin: 0 auto;
    width: 100%;
    background: white;
    /* Prevents margin-collapse: without this, .datetime-bar's own
       margin-top below would "leak" through this wrapper and push the
       whole white area down instead of creating a gap inside it. */
    padding-top: 1px;
  }

  .datetime-bar {
    max-width: 640px;
    margin: 16px auto 0;
    padding: 0 20px;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .datetime-bar label {
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
  }
  .datetime-bar input[type="date"], .datetime-bar input[type="time"] {
    padding: 10px 12px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    font-size: 14px;
    background: white;
    color: var(--ink);
    font-family: inherit;
  }
  .datetime-bar input[type="date"] { width: 150px; }
  .datetime-bar input[type="time"] { width: 100px; }
  /* --sun-700, not --sun-500: 2.23:1 on white, under the 3:1 WCAG
     minimum for a focus indicator. */
  .datetime-bar input:focus { outline: 2px solid var(--sun-700); }
  .datetime-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 9px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    cursor: pointer;
    flex-shrink: 0;
  }
  .datetime-icon-btn:hover { background: var(--card-border); }
  .datetime-reset {
    padding: 10px 12px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: none;
    color: var(--shade-900);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
  }
  .datetime-reset:hover { background: var(--card-border); }

  .search-bar {
    order: 2;
    flex: 1 1 280px;
    min-width: 0;
    display: flex;
    gap: 8px;
  }
  .search-input-wrap {
    flex: 1;
    position: relative;
  }
  .search-bar input {
    width: 100%;
    padding: 12px 16px;
    border-radius: 12px;
    border: 1px solid var(--card-border);
    font-size: 15px;
    background: white;
    color: var(--ink);
  }
  .search-bar input:focus { outline: 2px solid var(--sun-700); }
  /* The ✕ that empties the search box (index.html's searchClearBtn), inside its right edge */
  .search-bar input { padding-right: 44px; }
  .search-bar .search-clear-btn {          /* .search-bar: over the bar's own dark button style */
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    width: 32px; height: 32px; min-width: 0; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
    display: flex; align-items: center; justify-content: center; background: none; color: var(--muted);
  }
  .search-bar .search-clear-btn[hidden] { display: none; }
  .search-bar .search-clear-btn:hover { background: var(--card-border); color: var(--shade-900); }
  /* Phones: a smaller placeholder so the example fits, and no magnifying glass - Enter (the keyboard's
     "Search" key, enterkeyhint on the input) or a suggestion runs the search. Only the placeholder shrinks:
     the typed text stays at 15px+, and iOS zooms the page in on focusing a field under 16px anyway. */
  @media (max-width: 767px) {
    .search-bar input::placeholder { font-size: 13px; }
    #searchBtn { display: none; }
  }
  .suggestions-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: white;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    box-shadow: 0 8px 20px rgba(22, 48, 63, 0.12);
    overflow: hidden;
    z-index: 1110; /* same Leaflet-clearance reasoning as .filters-drawer above (was 20 - rendered
       behind the filters drawer, whose own z-index:1101 is already deliberately far above the
       normal stacking range to clear Leaflet's map controls) - kept safely above it, matching
       .nav-menu-dropdown's own identical fix */
  }
  .suggestions-dropdown.open { display: block; }
  .suggestion-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    font-size: 14px;
    color: var(--ink);
    cursor: pointer;
    border-bottom: 1px solid var(--card-border);
  }
  .suggestion-item:last-child { border-bottom: none; }
  .suggestion-item:hover, .suggestion-item.active { background: var(--cream); }
  /* Both icons share this sizing/color - venueTypeIconSvg()'s own
     .venue-type-icon class (venue-card.js) and this suggestion-specific
     one for the address pin - so a venue and an address suggestion read
     as visually equivalent rows, just with a different leading glyph. */
  .suggestion-item .venue-type-icon, .suggestion-item .suggestion-icon {
    flex-shrink: 0;
    color: var(--muted);
  }
  .suggestion-item-venue { font-weight: 600; }
  .search-bar button {
    padding: 12px 16px;
    border-radius: 12px;
    border: none;
    background: var(--shade-900);
    color: white;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
  }
  .search-bar button:hover { background: var(--shade-700); }
  .locate-btn {
    background: none !important;
    color: var(--shade-900) !important;
    border: 1px solid var(--card-border) !important;
    padding: 10px !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .locate-btn:hover { background: var(--card-border) !important; }

  .filters-toggle-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 12px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
  }
  .filters-toggle-btn:hover { background: var(--card-border); }

  .favorites-only-toggle-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 7px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    cursor: pointer;
  }
  .favorites-only-toggle-btn:hover { background: var(--card-border); }
  .favorites-only-toggle-btn.active {
    /* --sun-700, not --sun-600: white-on-#E67E22 measures 2.85:1, under
       the 3:1 WCAG minimum for an icon/graphical object. --sun-700 gives
       the same white heart icon 5.48:1. */
    background: var(--sun-700);
    border-color: var(--sun-700);
    color: white;
  }
  .favorites-only-toggle-btn.active svg path { fill: currentColor; }

  .include-weather-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--muted);
    cursor: pointer;
    white-space: nowrap;
  }
  .include-weather-toggle .weather-short-label { display: none; } /* shown
     only at true mobile widths instead - see that media query - a
     separate, dedicated short label rather than hiding half of the full
     one, since which half is safely droppable depends on word order,
     and that differs by language ("Include weather" vs "Weer meenemen"). */
  .include-weather-toggle input[type="checkbox"] {
    /* Visually hidden but still a real, focusable, keyboard-operable
       checkbox - .toggle-track below is purely a styled visual stand-in
       driven by this input's :checked state, not a replacement for it. */
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
  }
  .toggle-track {
    position: relative;
    display: inline-block;
    width: 34px;
    height: 20px;
    background: #C9CDD3;
    border-radius: 999px;
    flex-shrink: 0;
    transition: background 0.2s ease;
  }
  .toggle-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: white;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: transform 0.2s ease;
  }
  .include-weather-toggle input[type="checkbox"]:checked ~ .toggle-track {
    /* --sun-700: the white thumb sitting on --sun-500 measures only
       2.23:1, under the 3:1 WCAG minimum for a UI component against its
       background - --sun-700 gives the same white thumb 5.48:1. */
    background: var(--sun-700);
  }
  .include-weather-toggle input[type="checkbox"]:checked ~ .toggle-track .toggle-thumb {
    transform: translateX(14px);
  }
  .include-weather-toggle input[type="checkbox"]:focus-visible ~ .toggle-track {
    outline: 2px solid var(--sun-700);
    outline-offset: 2px;
  }

  .filters-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
    z-index: 1100;
    /* Leaflet's own internal elements (map panes, and especially its
       zoom control) carry their own z-index values, up to around 800-
       1000 depending on version - see .leaflet-control/.leaflet-top in
       leaflet.css. .map-view itself has no position/z-index of its own,
       so it doesn't cap those values within its own stacking context -
       they compare directly against whatever's used here. Without a
       comfortable margin above Leaflet's own maximum, the map's zoom
       control renders ON TOP of this drawer instead of being covered by
       it, specifically when Map view is open on a narrow screen. */
  }
  .filters-backdrop.open { opacity: 1; pointer-events: auto; }

  .filters-drawer {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    width: min(320px, 85vw);
    background: var(--cream);
    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    z-index: 1101;
    overflow-y: auto;
  }
  .filters-drawer.open { transform: translateX(0); }

  .filters-drawer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 20px;
    border-bottom: 1px solid var(--card-border);
  }
  .filters-drawer-header h2 {
    margin: 0;
    font-size: 16px;
    color: var(--shade-900);
  }
  .filters-close-btn {
    background: none;
    border: none;
    font-size: 22px;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    padding: 4px;
  }
  .filters-close-btn:hover { color: var(--ink); }

  .filters-drawer-body {
    padding: 20px;
    display: flex;
    flex-direction: column;
    gap: 22px;
  }
  .filter-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .filter-group label {
    font-size: 12px;
    color: var(--muted);
    font-weight: 600;
  }
  .filter-group input[type="range"] {
    accent-color: var(--sun-500);
  }
  .filter-group select {
    padding: 8px 10px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--ink);
    font-size: 14px;
  }
  /* "Venue cloud coverage": the sky slider. Its track is the status palette itself, cloudy to sunny, so the control previews
     the colours the timelines use. Skies that count are lit; the ones left out sit under a cream wash, their icons
     greyed. A native range underneath does the work - keyboard, touch, screen readers - and is styled, not replaced. */
  /* The filters' sliders (.lumo-range: search radius, "Venue cloud coverage"): a 10px pill track and a white 24px thumb
     ringed in colour, over a native range that keeps keyboard, touch and screen readers. The radius lights its track
     up to the thumb in Sun Bright (--range-f, set in index.html); the sky slider paints its own track below. */
  .lumo-range {
    --thumb: 24px; --range-f: 0; --ring: #F2994A;
    -webkit-appearance: none; appearance: none; display: block; width: 100%; height: var(--thumb); margin: 0;
    background: transparent; cursor: pointer; touch-action: pan-y;
  }
  .lumo-range::-webkit-slider-runnable-track {
    height: 10px; border-radius: 999px; box-shadow: inset 0 0 0 1px rgba(22, 48, 63, 0.08);
    background: linear-gradient(to right, #FFD27A, #F2994A) 0 0 / calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--range-f)) 100% no-repeat, #EDE6D6;
  }
  .lumo-range::-moz-range-track {
    height: 10px; border-radius: 999px; box-shadow: inset 0 0 0 1px rgba(22, 48, 63, 0.08);
    background: linear-gradient(to right, #FFD27A, #F2994A) 0 0 / calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--range-f)) 100% no-repeat, #EDE6D6;
  }
  .lumo-range::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none; width: var(--thumb); height: var(--thumb); margin-top: calc((10px - var(--thumb)) / 2);
    border-radius: 50%; background: white; border: 3px solid var(--ring); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: border-color 0.25s ease;
  }
  .lumo-range::-moz-range-thumb {
    width: var(--thumb); height: var(--thumb); box-sizing: border-box; border-radius: 50%; background: white;
    border: 3px solid var(--ring); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  }
  .lumo-range:focus { outline: none; }
  .lumo-range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  .lumo-range:focus-visible::-moz-range-thumb { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  @media (prefers-reduced-motion: reduce) { .lumo-range::-webkit-slider-thumb { transition: none; } }

  .sky-scale { --sky-f: 0.3333; --sky-ink: #7A6450; --thumb: 24px; position: relative; padding: 2px 0 0; }
  .sky-stops { position: relative; height: 30px; margin: 0 0 4px; }
  .sky-stop {
    position: absolute; top: 0; left: calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--k) / 3);
    transform: translateX(-50%) scale(0.92); width: 34px; height: 30px; padding: 0; border: 0; background: none;
    font-size: 19px; line-height: 30px; cursor: pointer; border-radius: 8px;
    filter: grayscale(1); opacity: 0.38;
    transition: filter 0.25s ease, opacity 0.25s ease, transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .sky-stop[data-stop="0"] { --k: 0; } .sky-stop[data-stop="1"] { --k: 1; }
  .sky-stop[data-stop="2"] { --k: 2; } .sky-stop[data-stop="3"] { --k: 3; }
  .sky-stop.is-in { filter: none; opacity: 1; }
  .sky-stop.is-chosen { transform: translateX(-50%) scale(1.14); }
  .sky-stop:hover:not(.is-chosen) { opacity: 0.75; }
  .sky-stop.is-in:hover { opacity: 1; }
  .sky-range { --ring: var(--sky-ink); }
  .sky-range::-webkit-slider-runnable-track {
    background:
      linear-gradient(to right, rgba(251, 247, 239, 0.78) 0 calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--sky-f)), transparent 0),
      linear-gradient(to right, #CBCFD4, #D8C1A8 33.3%, #F7C08A 66.6%, #F2994A);
  }
  .sky-range::-moz-range-track {
    background:
      linear-gradient(to right, rgba(251, 247, 239, 0.78) 0 calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--sky-f)), transparent 0),
      linear-gradient(to right, #CBCFD4, #D8C1A8 33.3%, #F7C08A 66.6%, #F2994A);
  }
  .sky-readout { margin: 2px 0 0; font-size: 13px; font-weight: 600; color: var(--sky-ink, var(--ink)); min-height: 18px; }
  .sky-filter .sky-readout { color: var(--ink); }
  /* With the weather off the sky decides nothing: the whole scale rests, greyed, and says why. */
  .sky-filter.is-resting .sky-scale { filter: grayscale(1); opacity: 0.45; }
  .sky-filter.is-resting .sky-range, .sky-filter.is-resting .sky-stop { cursor: default; pointer-events: none; }
  .sky-filter.is-resting .sky-readout { color: var(--muted); font-weight: 400; }
  @media (prefers-reduced-motion: reduce) {
    .sky-stop { transition: none; }
  }
  /* "Sort by": a button that opens into a small menu (index.html's sort picker). Same white, hairline and radius as
     the drawer's other controls; the menu is a dropdown, so it carries the system's dropdown shadow. The button and
     the menu share one view-transition-name while it opens or closes, so one morphs into the other. */
  .sort-picker { position: relative; }
  .sort-picker.is-morphing .sort-picker-btn[aria-expanded="false"],
  .sort-picker.is-morphing .sort-picker-menu:not([hidden]) { view-transition-name: sort-picker; }
  .sort-picker-btn {
    display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; font: inherit; font-size: 14px;
    color: var(--ink); background: white; border: 1px solid var(--card-border); border-radius: 12px; cursor: pointer;
    text-align: left; transition: border-color 0.2s ease, background 0.2s ease;
  }
  .sort-picker-btn:hover { background: var(--cream); }
  .sort-picker-btn:focus-visible { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  .sort-picker.is-open .sort-picker-btn { border-color: #F5D9B3; }
  .sort-picker-icon { display: inline-flex; width: 20px; height: 20px; flex-shrink: 0; color: var(--shade-900); }
  .sort-picker svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
  /* One line always: the sidebar is narrower than the phone drawer, and a wrapped label doubled the button's height. */
  .sort-picker-text { flex: 1; min-width: 0; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .sort-picker-chevron { display: inline-flex; width: 18px; height: 18px; color: var(--muted); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
  .sort-picker.is-open .sort-picker-chevron { transform: rotate(180deg); }
  .sort-picker-menu {
    position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 6px); margin: 0; padding: 5px; list-style: none;
    background: white; border: 1px solid var(--card-border); border-radius: 14px;
    box-shadow: 0 8px 20px rgba(22, 48, 63, 0.12); outline: none; transform-origin: top center;
  }
  /* Without View Transitions: a short drop instead of the morph (never both). */
  .sort-picker-menu.is-dropping { animation: sort-menu-drop 0.22s cubic-bezier(0.16, 1, 0.3, 1); }
  @keyframes sort-menu-drop { from { opacity: 0; transform: translateY(-4px) scaleY(0.96); } }
  .sort-option {
    display: flex; align-items: center; gap: 10px; min-height: 42px; padding: 0 10px; border-radius: 10px;
    font-size: 14px; color: var(--ink); cursor: pointer;
  }
  .sort-option.is-active { background: var(--cream); }
  .sort-picker-menu:focus-visible:not(.is-pointer) .sort-option.is-active { box-shadow: inset 0 0 0 2px var(--sun-700); }
  .sort-option[aria-selected="true"] { font-weight: 600; }
  .sort-option-text { flex: 1; }
  .sort-option-check { display: inline-flex; width: 18px; height: 18px; color: var(--sun-700); }
  ::view-transition-group(sort-picker) { animation-duration: 0.26s; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  @media (prefers-reduced-motion: reduce) {
    .sort-picker-menu.is-dropping { animation: none; }
    .sort-picker-chevron { transition: none; }
  }

  /* Switches that belong together (shade, open, confirmed seating) share one group, as close as the venue-type ones. */
  .filter-switches { gap: 10px; }
  .venue-type-filter-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .venue-type-toggle {
    text-transform: capitalize;
  }
  .venue-type-icon {
    color: var(--muted);
    flex-shrink: 0;
  }

  .status-line {
    max-width: 640px;
    margin: 12px auto 0;
    padding: 0 20px;
    font-size: 13px;
    color: var(--muted);
  }
  .controls-row {
    max-width: 640px;
    margin: 10px auto 0;
    padding: 0 20px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 8px 12px;
  }
  .controls-row-right {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 8px 12px;
    /* Pushes this group to the row's far right regardless of how many
       elements precede it (filtersToggleBtn alone, or now also
       favoritesOnlyToggleBtn next to it) - .controls-row's own
       justify-content:space-between (see that rule above) was written
       back when this row only ever had two direct children, so a third
       one (the heart toggle) got spread into the middle of the row
       instead of staying next to Filters - same underlying bug, same
       fix, as .auth-wrap's own margin-left:auto in the header (see that
       rule's own comment for the fuller explanation and how this was
       actually confirmed via a real screenshot, not just reasoned about). */
    margin-left: auto;
  }
  .map-toggle-btn {
    flex-shrink: 0;
    background: var(--shade-900);
    color: white;
    border: none;
    border-radius: 8px;
    padding: 4px 10px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
  }
  .map-toggle-btn:hover { background: var(--shade-700); }

  .map-view {
    display: none;
    position: relative;
    max-width: 640px;
    /* Explicit, not left to the default auto-width a stretched flex item
       (#homeView > .home-layout is flex-direction:column below 1060px)
       would otherwise resolve to - matches .content-column's own
       width:100% for the same reason, next to it. */
    width: 100%;
    margin: 12px auto 0;
    padding: 0 20px;
    /* Explicit, not inherited from sitting inside .content-column: now a
       sibling of it in the DOM (see index.html), so it needs its own
       background to match rather than showing the page's cream through
       its own horizontal padding. */
    background: white;
  }
  .map-view.open { display: block; }
  #mapContainer {
    height: clamp(440px, 68vh, 680px);
    border-radius: 16px;
    border: 1px solid var(--card-border);
    overflow: hidden;
    background: #F5EFE1; /* the map style's own ground, so the pane never flashes white while tiles load */
  }
  /* Only shown in the desktop 3-pane split (see the :has() block at the
     end of this file) - elsewhere the existing "Map view"/"List view"
     toggle button already closes it, and duplicating a second close
     control there would just be noise. */
  .map-close-btn {
    display: none;
    position: absolute;
    top: 16px;
    right: 36px; /* 16px + .map-view's own 20px side padding, so it sits flush with #mapContainer's edge */
    z-index: 10;
    align-items: center;
    gap: 6px;
    padding: 9px 14px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    font-size: 13px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(22, 48, 63, 0.12);
  }
  .map-close-btn:hover { background: var(--card-border); }

  /* Desktop only, and only while dragging the map away from the original
     search center at a close-enough zoom - see venue-map.js's own
     visibility logic (MAP_SPLIT_MIN_WIDTH, moveend/zoomend), which sets
     inline display directly rather than through a CSS state class, since
     "is the query center still in view" isn't something CSS can express.
     Mirrors .map-close-btn's own card treatment, top-left instead of
     top-right so the two never collide. */
  .map-search-area-btn {
    position: absolute;
    top: 16px;
    left: 36px; /* 16px + .map-view's own 20px side padding, so it sits flush with #mapContainer's edge */
    z-index: 10;
    align-items: center;
    gap: 6px;
    padding: 9px 14px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    font-size: 13px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(22, 48, 63, 0.12);
  }
  .map-search-area-btn:hover { background: var(--card-border); }

  /* Same card treatment again, bottom-left this time - clear of the zoom
     control and attribution, which both sit on the right (see
     .map-search-area-btn's own comment for why this is inline-display-
     driven rather than a CSS state class). Radius searches only - see
     venue-map.js's own visibility logic and placeHere()'s comment on why
     a bbox search has no "here" point to recenter on. */
  /* The map's own search running (venue-map.js: setAreaSearching()) */
  .map-searching-pill {
    position: absolute; z-index: 6; left: 50%; top: 12px; transform: translate(-50%, -6px);
    display: flex; align-items: center; gap: 8px; padding: 7px 14px; border-radius: 999px;
    background: #FFFBF3; color: var(--shade-900); font-size: 13px; font-weight: 600; white-space: nowrap;
    box-shadow: 0 6px 18px rgba(22, 48, 63, 0.18); opacity: 0; pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
  }
  .map-searching-pill.is-on { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.15s; }
  .map-searching-dot {
    width: 10px; height: 10px; border-radius: 50%; background: var(--sun-500);
    animation: map-searching-pulse 1s ease-in-out infinite alternate;
  }
  @keyframes map-searching-pulse { from { transform: scale(0.7); opacity: 0.55; } to { transform: scale(1.1); opacity: 1; } }
  @media (prefers-reduced-motion: reduce) { .map-searching-dot { animation: none; } }
  .map-recenter-btn {
    position: absolute;
    bottom: 16px;
    left: 36px; /* 16px + .map-view's own 20px side padding, so it sits flush with #mapContainer's edge */
    z-index: 10;
    align-items: center;
    gap: 6px;
    padding: 9px 14px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    font-size: 13px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(22, 48, 63, 0.12);
  }
  .map-recenter-btn:hover { background: var(--card-border); }

  /* The heart in front of the name, as on the cards; the name (with its type icon) wraps beside it, its first line
     level with the heart's middle. */
  .map-popup-name { display: flex; align-items: flex-start; gap: 8px; font-weight: 600; margin-bottom: 8px; padding-right: 18px; }
  .map-popup-name > .favorite-btn { flex: none; }
  .map-popup-name-text { flex: 1; min-width: 0; padding-top: 7px; }
  /* The venue's type as an icon in front of its name (the list card's icon; the type's name is its tooltip) */
  .map-popup-type-icon { display: inline-flex; vertical-align: -2px; margin-right: 5px; color: var(--muted); }
  .map-popup-status.sunny { color: var(--sun-700); font-weight: 600; }
  .map-popup-status.shaded { color: var(--muted); }
  .map-popup-total-badge {
    display: inline-block;
    background: var(--sun-300);
    /* --shade-900, not an orange: even --sun-700 only reaches 3.85:1 on
       this light --sun-300 background, under the 4.5:1 this 13px bold
       text needs. Navy-on-amber keeps the badge legible; the background
       alone still carries the "sun" association. */
    color: var(--shade-900);
    font-weight: 700;
    font-size: 13px;
    padding: 3px 10px;
    border-radius: 10px;
    margin: 2px 0 6px;
  }
  .map-popup-link {
    display: inline-block;
    margin-top: 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--shade-900);
    text-decoration: none;
    border-bottom: 1px solid var(--shade-900);
  }
  .map-popup-link:hover { color: var(--sun-700); border-color: var(--sun-700); }

  /* Vector map (see venue-map.js): MapLibre's own chrome re-skinned to the
     design system - flat, rounded, hairline-bordered, warm. Scoped to
     #mapContainer because MapLibre's stylesheet loads after this one (lazily,
     the first time the map opens), so plain selectors would lose to it. */
  #mapContainer.maplibregl-map { font-family: inherit; }
  #mapContainer .maplibregl-ctrl-group:not(:empty) {
    overflow: hidden;
    border: 1px solid #F5D9B3;
    border-radius: 12px;
    background: #FFFBF3;
    box-shadow: none;
  }
  #mapContainer .maplibregl-ctrl-group button { width: 34px; height: 34px; }
  #mapContainer .maplibregl-ctrl-group button + button { border-top: 1px solid #F5D9B3; }
  #mapContainer .maplibregl-ctrl-group button:not(:disabled):hover { background: #FFF3D6; }
  #mapContainer .maplibregl-ctrl button:focus-visible { outline: 2px solid var(--sun-500); outline-offset: -2px; }
  #mapContainer .maplibregl-ctrl-attrib.maplibregl-compact,
  #mapContainer .maplibregl-ctrl-attrib {
    background: rgba(251, 247, 239, 0.9);
    border-radius: 10px 0 0 0;
    font-size: 11px;
    color: var(--muted);
  }
  #mapContainer .maplibregl-ctrl-attrib a { color: var(--muted); }
  #mapContainer .maplibregl-ctrl-attrib-button { border-radius: 50%; }
  /* A venue's popup is one fixed width: its contents (the time, the temperature, the reset button) change as the sun
     is dragged, and a popup sized to its contents grew and shrank with them. */
  #mapContainer .lumo-venue-popup .maplibregl-popup-content { width: 260px; max-width: calc(100vw - 32px); box-sizing: border-box; }
  #mapContainer .lumo-popup .maplibregl-popup-content {
    padding: 12px 14px;
    border: 1px solid #F5D9B3;
    border-radius: 14px;
    background: #FFFBF3;
    /* A popup floats over the map like a dropdown does over a page - the one
       kind of surface DESIGN.md lets carry a shadow. */
    box-shadow: 0 8px 24px rgba(22, 48, 63, 0.14);
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    /* The card is tall (timeline + window list); venue-map.js sets --lumo-popup-max to what fits
       inside the map, and anything beyond that scrolls rather than running off the pane. */
    max-height: var(--lumo-popup-max, 380px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  #mapContainer .lumo-popup .maplibregl-popup-close-button {
    padding: 2px 8px 4px;
    border-radius: 0 12px 0 10px;
    font-size: 18px;
    color: var(--muted);
  }
  #mapContainer .lumo-popup .maplibregl-popup-close-button:hover { background: #FFF3D6; color: var(--shade-900); }
  #mapContainer .lumo-popup.maplibregl-popup-anchor-top .maplibregl-popup-tip,
  #mapContainer .lumo-popup.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
  #mapContainer .lumo-popup.maplibregl-popup-anchor-top-right .maplibregl-popup-tip { border-bottom-color: #FFFBF3; }
  #mapContainer .lumo-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
  #mapContainer .lumo-popup.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
  #mapContainer .lumo-popup.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip { border-top-color: #FFFBF3; }
  #mapContainer .lumo-popup.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: #FFFBF3; }
  #mapContainer .lumo-popup.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: #FFFBF3; }
  /* "You are here": Ink Navy, so it can't be mistaken for a venue pin. */
  .lumo-here { --here: #1A73E8; --beam: 26, 115, 232; position: relative; width: 22px; height: 22px; cursor: pointer; }
  .lumo-here-dot { position: absolute; inset: 4px; box-sizing: border-box; border: 2.5px solid #fff; border-radius: 50%; background: var(--here); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); }
  .lumo-here-pulse { position: absolute; inset: 0; border-radius: 50%; background: color-mix(in srgb, var(--here) 30%, transparent); animation: lumo-pulse 2.6s ease-out infinite; }
  /* The compass beam (venue-map.js's setHeading()): a soft cone ahead of the dot, the marker turned to the heading */
  .lumo-here-beam {
    display: none; position: absolute; left: 50%; bottom: 50%; width: 76px; height: 64px; transform: translateX(-50%);
    clip-path: polygon(50% 100%, 6% 0, 94% 0);
    background: radial-gradient(ellipse 60% 100% at 50% 100%, rgba(var(--beam), 0.55), rgba(var(--beam), 0.18) 60%, rgba(var(--beam), 0) 100%);
  }
  .lumo-here.has-heading .lumo-here-beam { display: block; }
  @keyframes lumo-pulse { from { transform: scale(0.55); opacity: 0.9; } to { transform: scale(2.1); opacity: 0; } }
  @media (prefers-reduced-motion: reduce) { .lumo-here-pulse { animation: none; opacity: 0.3; } }

  .feed {
    max-width: 640px;
    margin: 0 auto;
    padding: 12px 20px 60px;
    display: flex;
    flex-direction: column;
    gap: 12px;
  }

  /* Living light (venue-card.js: applyCardLight): the list is lit by the real sun for the moment on screen.
     --lit / --shade are registered so a card can fade between sunny and shaded; the rest (--shx/--shy the
     shadow direction, --shl its length, --lx/--ly where the light comes from, --ray, --edge, --warm,
     --sunup) are plain values set per card. Painted on the card's own background, so there are no extra
     layers to fight the tooltips or the expand animation. Only cards with .has-light get any of it. */
  @property --lit { syntax: '<number>'; inherits: true; initial-value: 0; }
  @property --shade { syntax: '<number>'; inherits: true; initial-value: 0; }

  .card {
    background: white;
    border: 1px solid var(--card-border);
    border-radius: 16px;
    padding: 14px;
    cursor: pointer;
  }
  .card.sunny { border-color: #F5D9B3; background: #FFFBF3; }
  /* The desktop 3-pane split's pin -> card link (the reverse of card-hover
     -> pin ring): set by window.selectCardForPin() in index.html when a
     pin's popup opens, cleared when it closes. Same 2px Sun Bright outline
     every other focus/selection state in this system already uses (see
     Inputs/Fields), rather than a new treatment just for this. An outline,
     not a border, so it doesn't shift the card's own layout or its
     neighbours. */
  .card.card-pin-selected { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  #mapContainer .lumo-popup .maplibregl-popup-content.has-light {
    --muted: #4F5D66;
    background-image:
      radial-gradient(calc(110% + var(--warm) * 70%) calc(90% + var(--warm) * 50%) at var(--lx) var(--ly),
        color-mix(in srgb, rgb(255 210 122 / calc(0.34 * var(--lit) * var(--sunup))) calc((1 - var(--warm)) * 100%),
                           rgb(242 153 74 / calc(0.30 * var(--lit) * var(--sunup)))) 0%,
        transparent 64%),
      linear-gradient(calc(var(--ray) * 1deg),
        rgb(37 82 108 / calc(0.062 * var(--shade) * var(--sunup))) 0,
        rgb(37 82 108 / calc(0.05 * var(--shade) * var(--sunup))) calc(var(--edge) * 1%),
        transparent calc(var(--edge) * 1% + 1.6%));
  }

  .card.has-light {
    /* The small secondary text sits where the light lands, so inside a lit card it uses the darker slate
       (the same #4F5D66 the leaderboard hero uses): above 5:1 on the strongest wash and on the deepest shadow. */
    --muted: #4F5D66;
    /* a warm wash from the sun's side (wider and deeper near the horizon), or, in shade, a cool
       building-shadow edge whose length is the real shadow ratio */
    background-image:
      radial-gradient(calc(110% + var(--warm) * 70%) calc(90% + var(--warm) * 50%) at var(--lx) var(--ly),
        color-mix(in srgb, rgb(255 210 122 / calc(0.34 * var(--lit) * var(--sunup))) calc((1 - var(--warm)) * 100%),
                           rgb(242 153 74 / calc(0.30 * var(--lit) * var(--sunup)))) 0%,
        transparent 64%),
      linear-gradient(calc(var(--ray) * 1deg),
        rgb(37 82 108 / calc(0.062 * var(--shade) * var(--sunup))) 0,
        rgb(37 82 108 / calc(0.05 * var(--shade) * var(--sunup))) calc(var(--edge) * 1%),
        transparent calc(var(--edge) * 1% + 1.6%));
    /* every card casts its shadow the way the sun says: warm when lit, cool in shade */
    box-shadow: calc(var(--shx) * var(--shl) * 1px) calc(var(--shy) * var(--shl) * 1px) 14px -5px
      color-mix(in srgb, rgb(160 92 24 / 0.17) calc(var(--lit) * 100%), rgb(18 42 56 / calc(0.05 + 0.06 * var(--sunup))));
  }

  /* The name row (name, badges, heart | sun exposure) spans the whole card, above the thumbnail; the
     thumbnail, the rest of the details and the chevron share the row below it. */
  .card-header { display: block; }
  .card-main {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    margin-top: 10px;
  }
  /* The thumbnail lines up with the timeline (its middle 30 px down: the type line, then the 12 px timeline), not
     with the middle of everything below the name; the chevron stays in the middle of the block. */
  .card-main > .avatar { margin-top: 2px; }
  .card-main > .chevron { align-self: center; }

  .avatar {
    flex-shrink: 0;
    width: 56px;
    height: 56px;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 20px;
    color: white;
    overflow: hidden;
  }
  .avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: pointer; }
  /* The terrace picture (terrace-thumb.js) laid over the letter, which stays underneath until it is drawn */
  .avatar { position: relative; }
  .avatar-thumb { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
  /* Fades in only when its file has just arrived; a card rebuilt from memory (dragging the sun) shows it at once,
     so the letter underneath never flashes through */
  .avatar-thumb--arrived { animation: avatar-thumb-in 0.35s ease-out; }
  /* A light hairline frame over the picture's edge, so the pale ground doesn't melt into the card. Drawn by
     ::after over the canvas (it replaces the letter tile's light overlay). */
  .avatar--thumb::after,
  .card.has-light .avatar--thumb::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
    background: none; box-shadow: inset 0 0 0 1px rgb(18 42 56 / 0.14);
  }
  @keyframes avatar-thumb-in { from { opacity: 0; } }
  @media (prefers-reduced-motion: reduce) { .avatar-thumb--arrived { animation: none; } }
  /* The letter tile is a small sun/shade dial: lit from the sun's side, its letter casting a soft shadow. */
  .card.has-light .avatar--letter {
    position: relative;
    text-shadow: calc(var(--shx) * var(--ash) * 1px) calc(var(--shy) * var(--ash) * 1px) 1.5px rgb(18 42 56 / 0.32);
  }
  .card.has-light .avatar--letter::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
      radial-gradient(95% 95% at var(--lx) var(--ly), rgb(255 255 255 / calc(0.34 * var(--lit) * var(--sunup))), transparent 72%),
      linear-gradient(calc(var(--ray) * 1deg), rgb(18 42 56 / calc(0.20 * var(--shade) * var(--sunup))), transparent 70%);
  }

  /* Full-size photo lightbox - opened by clicking a venue's avatar photo
     (see venue-card.js: openPhotoLightbox()). One shared element reused
     for every card rather than one per card, created lazily on first
     use - same fixed/inset/fade-transition shape as .filters-backdrop
     above, for the same "dim everything else, show one thing on top"
     purpose, just centering an image instead of a side drawer. */
  .photo-lightbox {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.75);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
    z-index: 1200;
  }
  .photo-lightbox.open { opacity: 1; pointer-events: auto; }
  .photo-lightbox-img {
    max-width: 100%;
    max-height: 100%;
    border-radius: 8px;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.4);
    cursor: default;
  }
  .photo-lightbox-close {
    position: fixed;
    top: 16px;
    right: 20px;
    background: none;
    border: none;
    color: white;
    font-size: 32px;
    line-height: 1;
    cursor: pointer;
    padding: 8px;
  }

  /* A visitor photo's credit under the photo in the lightbox */
  .photo-lightbox-figure {
    margin: 0; max-width: 100%; max-height: 100%;
    display: flex; flex-direction: column; align-items: center; gap: 10px;
  }
  .photo-lightbox-figure .photo-lightbox-img { max-height: calc(100vh - 110px); }
  .photo-lightbox-credit { color: rgb(255 255 255 / 0.86); font-size: 13px; letter-spacing: 0.01em; }

  /* Visitor photos in an expanded card (venue-photos.js): the gallery strip and "Add a photo" */
  .vp-slot { margin: 2px 0 12px; }
  .vp-slot:empty { display: none; }
  .vp-strip {
    display: flex; gap: 8px; overflow-x: auto; padding-bottom: 8px; margin-bottom: 4px;
    scrollbar-width: thin; scroll-snap-type: x proximity;
  }
  .vp-thumb {
    flex: 0 0 auto; width: 76px; height: 76px; padding: 0; border: 0; border-radius: 12px; overflow: hidden;
    background: var(--header-bg); cursor: zoom-in; scroll-snap-align: start;
    box-shadow: inset 0 0 0 1px rgb(18 42 56 / 0.10);
  }
  .vp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .vp-thumb:focus-visible { outline: 2px solid var(--shade-700); outline-offset: 2px; }
  .vp-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
  .vp-add {
    display: inline-flex; align-items: center; gap: 6px; min-height: 34px; padding: 6px 12px;
    border: 1px solid var(--card-border); border-radius: 999px; background: white; color: var(--ink);
    font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
  }
  .vp-add:hover { border-color: var(--sun-500); background: #FFFBF3; }
  .vp-add:focus-visible { outline: 2px solid var(--shade-700); outline-offset: 2px; }
  .vp-pending { font-size: 12px; color: var(--muted); }

  /* The upload dialog */
  .vp-backdrop {
    position: fixed; inset: 0; z-index: 1250; background: rgb(18 42 56 / 0.45);
    display: flex; align-items: center; justify-content: center; padding: 16px;
  }
  .vp-backdrop[hidden] { display: none; }
  .vp-dialog {
    position: relative; width: min(420px, 100%); max-height: calc(100vh - 32px); overflow-y: auto;
    background: var(--cream); color: var(--ink); border-radius: 18px; padding: 20px 20px 18px;
    box-shadow: 0 18px 50px rgb(18 42 56 / 0.28); display: grid; gap: 12px;
  }
  .vp-title { margin: 0 28px 0 0; font-size: 17px; line-height: 1.3; }
  .vp-close {
    position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; border: 0; border-radius: 50%;
    background: none; color: var(--muted); font-size: 24px; line-height: 1; cursor: pointer;
  }
  .vp-close:hover { background: var(--header-bg); }
  .vp-drop {
    position: relative; display: grid; place-items: center; gap: 8px; min-height: 150px; padding: 12px;
    border: 1.5px dashed #D8CDB6; border-radius: 14px; background: white; cursor: pointer; text-align: center;
    color: var(--muted); font-size: 14px; overflow: hidden;
  }
  .vp-drop:hover, .vp-drop:focus-within { border-color: var(--sun-500); }
  .vp-file { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
  .vp-preview { max-width: 100%; max-height: min(220px, 30vh); border-radius: 10px; display: block; }
  .vp-check { display: flex; gap: 8px; align-items: flex-start; font-size: 14px; line-height: 1.4; cursor: pointer; }
  .vp-check input { margin: 3px 0 0; accent-color: var(--sun-600); width: 16px; height: 16px; flex: 0 0 auto; }
  .vp-check a { color: var(--shade-700); }
  .vp-note { margin: 0; font-size: 12.5px; color: var(--muted); }
  .vp-msg { margin: 0; font-size: 14px; min-height: 0; }
  .vp-msg:empty { display: none; }
  .vp-msg.is-ok { color: #2E7D4F; font-weight: 600; }
  .vp-msg.is-error { color: #B3412E; }
  .vp-actions { display: flex; justify-content: flex-end; gap: 8px; }
  .vp-actions button {
    min-height: 40px; padding: 8px 16px; border-radius: 999px; font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  }
  .vp-cancel { border: 1px solid var(--card-border); background: white; color: var(--ink); }
  .vp-send { border: 0; background: var(--shade-900); color: white; }
  .vp-send:disabled { opacity: 0.4; cursor: default; }
  .vp-dialog.is-done .vp-drop, .vp-dialog.is-done .vp-check, .vp-dialog.is-done .vp-note,
  .vp-dialog.is-done .vp-cancel { display: none; }

  .card-body { flex: 1; min-width: 0; }
  .card-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 8px;
  }
  .sun-exposure-block {
    flex-shrink: 0;
    text-align: right;
    line-height: 1.35;
  }
  .sun-exposure-label {
    font-size: 9px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .sun-exposure-line {
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
  }
  .sun-exposure-line b {
    /* Orange (reusing --sun-700, the same accent already used for
       "Sunny until..." status text elsewhere on this card) is the one
       change kept from an earlier iteration that also made this bigger
       and bolder - that turned out too large in practice, reverted back
       to the original size/weight, keeping only the color change.
       --sun-700 rather than --sun-600: the latter measures 2.85:1 on
       this card's white/#FFFBF3 background, under the 4.5:1 this bold
       number needs as real text. */
    color: var(--sun-700);
    font-weight: 600;
  }
  .card-name { font-size: 16px; font-weight: 600; margin: 0; line-height: 1.45; flex: 1; min-width: 0; padding-top: 7px; }
  /* The heart sits in front of the name, as its own item (venue-card.js), so a long name wraps beside it
     instead of running into it; padding-top above lines the name's first line up with the heart's middle. */
  .card-top > .card-actions { flex: none; }
  .favorite-btn {
    /* Inline content INSIDE .card-name's own text now (see
       buildVenueCardEl in venue-card.js), not a separate flexbox
       sibling in a wrapper row - a long name (e.g. "De Engelbewaarder")
       wrapping to two lines could otherwise overflow that row's own
       width and push this out into the sun-exposure column next to it,
       confirmed happening from a real screenshot on mobile. As part of
       the name's own inline text flow instead, this heart is just the
       trailing "word" after the name - the browser's normal text
       wrapping handles it exactly like a trailing icon or emoji would,
       correctly flowing to whichever line has room, for a name of any
       length, without this needing to know anything about how long any
       particular name is.
       display:inline-flex (not inline-block) is what still lets the
       svg inside center itself via align-items below - inline-flex
       still participates in the surrounding inline/text flow exactly
       like inline-block does, it's only the CONTENTS of this element
       that use flexbox layout, not its own participation in the
       paragraph's line-wrapping. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    margin-left: 4px;
    padding: 2px;
    background: none;
    border: none;
    color: var(--muted);
    cursor: pointer;
  }
  .favorite-btn:hover { color: var(--sun-700); }

  /* The venue's actions - heart, walking route, Google Maps - as framed buttons, so they read as tappable
     (card and map popup alike; the popup's are a little smaller). */
  .favorite-btn, .route-btn,
  .place-row .maps-link, .place-row .map-popup-link {
    box-sizing: border-box;
    width: 38px;
    height: 38px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid rgb(18 42 56 / 0.14);
    border-radius: 11px;
    background: rgb(255 255 255 / 0.72);
    box-shadow: 0 1px 2px rgb(18 42 56 / 0.08);
    text-decoration: none;
    transition: background 0.15s ease, border-color 0.15s ease;
  }
  .favorite-btn:hover, .route-btn:hover,
  .place-row .maps-link:hover, .place-row .map-popup-link:hover {
    background: white;
    border-color: rgb(18 42 56 / 0.24);
  }
  .favorite-btn.favorited, .route-btn.in-route { border-color: rgb(196 112 24 / 0.45); background: #FFF4E2; }
  .maplibregl-popup .favorite-btn, .maplibregl-popup .route-btn,
  .maplibregl-popup .place-row .map-popup-link { width: 34px; height: 34px; border-radius: 10px; }
  .favorite-btn.favorited { color: var(--sun-700); }
  .favorite-btn.favorited svg path { fill: currentColor; }

  /* Walking route (route-planner.js): the button beside the heart, the bar that appears once a stop is
     picked, and the plan panel. */
  .card-actions { display: inline-flex; align-items: center; white-space: nowrap; vertical-align: middle; }
  .route-btn {
    display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
    color: var(--muted); cursor: pointer;   /* frame, size and padding: the framed-button rule above */
  }
  /* Going there (card and map popup): how far, the walking-route button in the middle, Google Maps on the right */
  /* The distance on the left, the route and Google Maps buttons side by side on the right */
  .place-row, .meta-row.place-row { display: grid; grid-template-columns: 1fr auto auto; column-gap: 8px; align-items: center; }
  .place-row .distance { justify-self: start; }
  .place-row .maps-link, .place-row .map-popup-link { justify-self: end; margin: 0; }
  .place-row img { display: block; width: 24px; height: 24px; }
  .map-popup-actions { margin-top: 6px; }
  .route-btn:hover, .route-btn.in-route { color: var(--sun-700); }
  .route-btn.in-route .route-dot { fill: currentColor; }
  .route-bar {
    position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
    z-index: 950; display: flex; align-items: center; gap: 10px; max-width: calc(100vw - 24px);
    padding: 8px 8px 8px 14px; border-radius: 999px; background: var(--shade-900); color: white;
    box-shadow: 0 8px 24px rgba(22, 48, 63, 0.3); font-size: 14px; font-weight: 600;
  }
  .route-bar[hidden] { display: none; }
  body.has-route-bar { padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }

  /* Floating Filters (left) and Map (right) on phones/tablets (Map alone on wide screens) once the list's controls row has scrolled
     away (index.html's updateFloatingListActions()). Above the walking-route bar when that's showing. */
  .floating-list-actions:not([hidden]) {
    position: fixed; left: 16px; right: 16px; z-index: 940;
    /* --dock-lift: how far the end of the page has scrolled into view (index.html's updateFloatingListActions()), so
       the buttons stop just above it */
    bottom: calc(16px + env(safe-area-inset-bottom, 0px) + var(--dock-lift, 0px));
    display: flex; justify-content: space-between; pointer-events: none;
  }
  body.has-route-bar .floating-list-actions { bottom: calc(76px + env(safe-area-inset-bottom, 0px) + var(--dock-lift, 0px)); }
  .floating-list-actions.map-only { justify-content: center; }   /* wide screens, map closed: Map alone */
  .float-btn {
    pointer-events: auto; display: inline-flex; align-items: center; gap: 7px;
    font: inherit; font-size: 14px; font-weight: 700; cursor: pointer; padding: 10px 16px; border-radius: 999px;
    box-shadow: 0 6px 18px rgba(22, 48, 63, 0.22);
  }
  .float-btn--filters { background: white; color: var(--shade-900); border: 1px solid var(--card-border); }
  .float-btn--map { background: var(--shade-900); color: white; border: 1px solid var(--shade-900); }
  .float-btn[hidden] { display: none; }
  .route-bar-icon { display: flex; color: var(--sun-500); }
  .route-bar-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .route-bar button { font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; border-radius: 999px; padding: 7px 12px; white-space: nowrap; }
  .route-bar-clear { background: none; border: 1px solid rgba(255,255,255,0.35); color: white; }
  .route-bar-plan { background: var(--sun-500); border: 1px solid var(--sun-500); color: var(--shade-900); }
  .route-toast {
    position: fixed; left: 50%; bottom: calc(76px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
    z-index: 960; max-width: calc(100vw - 32px); padding: 10px 14px; border-radius: 10px; background: white; color: var(--ink);
    box-shadow: 0 8px 24px rgba(22, 48, 63, 0.2); font-size: 13px; opacity: 0; pointer-events: none; transition: opacity 0.2s ease;
  }
  .route-toast.is-on { opacity: 1; }
  .route-panel-backdrop:not([hidden]) {
    position: fixed; inset: 0; z-index: 970; background: rgba(18, 42, 56, 0.45);
    display: flex; align-items: flex-end; justify-content: center;
  }
  .route-panel {
    position: relative; width: min(460px, 100%); max-height: 85vh; overflow-y: auto; background: white;
    border-radius: 16px 16px 0 0; padding: 20px 20px calc(20px + env(safe-area-inset-bottom, 0px));
  }
  @media (min-width: 700px) {
    .route-panel-backdrop:not([hidden]) { align-items: center; }
    .route-panel { border-radius: 16px; }
  }
  .route-panel h2 { margin: 0 30px 6px 0; font-size: 18px; color: var(--shade-900); }
  .route-panel-close { position: absolute; top: 12px; right: 14px; background: none; border: none; font-size: 24px; line-height: 1; color: var(--muted); cursor: pointer; padding: 4px; }
  .route-note { margin: 0 0 12px; font-size: 13px; color: var(--muted); line-height: 1.5; }
  .route-stops { list-style: none; margin: 0; padding: 0; }
  .route-stop { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-top: 1px solid var(--card-border); }
  .route-stop:first-child { border-top: none; }
  .route-stop-n {
    flex: none; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    background: var(--sun-500); color: var(--shade-900); font-size: 13px; font-weight: 700;
  }
  .route-stop-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .route-stop-name { font-size: 15px; font-weight: 600; color: var(--shade-900); }
  .route-stop-leg { font-size: 12px; color: var(--muted); }
  .route-stop-remove { flex: none; background: none; border: none; font-size: 20px; line-height: 1; color: var(--muted); cursor: pointer; padding: 4px 6px; }
  .route-total { margin: 12px 0 14px; font-size: 13px; color: var(--ink); font-weight: 600; }
  .route-google {
    display: block; text-align: center; text-decoration: none; padding: 12px 16px; border-radius: 12px;
    background: var(--shade-900); color: white; font-weight: 700; font-size: 15px;
  }
  .route-google:hover { background: var(--shade-700); }
  .outdoor-seating-badge {
    /* Pill-with-visible-text version - used in the FILTERS DRAWER only
       (the toggle's own label, always shown), where full text is
       expected the same way "Only open venues" is. See
       .outdoor-seating-badge-icon just below for the card's own
       icon-only variant. --sun-700, not --sun-600: the icon+text pair
       measures 2.85:1 on white here, under the 4.5:1 real text needs. */
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--sun-700);
    font-weight: 600;
  }
  .outdoor-seating-badge-icon {
    /* Card version - icon only, same inline-flex-inside-the-name-text
       approach as .favorite-btn just above and for the same reason
       (needs to flow as part of card-name's own wrapping text). The
       full "Outdoor seating confirmed" text lives in the title
       attribute (desktop hover) and the .estimated-tooltip-popup child
       (mobile tap, via the shared toggleEstimatedTooltip() - see
       venue-card.js) - a helper, not always-visible label, so a row of
       cards doesn't repeat that whole phrase on every single one.
       position:relative anchors that popup's own position:absolute to
       THIS badge specifically, not some more distant ancestor.
       --sun-700, not --sun-600: this icon sits directly on the card's
       white/#FFFBF3 surface and measures 2.85:1 there, under the 3:1
       WCAG minimum for a graphical/icon object. */
    display: inline-flex;
    align-items: center;
    position: relative;
    vertical-align: middle;
    margin-left: 4px;
    color: var(--sun-700);
    cursor: pointer;
  }
  .claimed-badge-icon {
    /* Same icon-only, inline-flex-inside-the-name-text shape as
       .outdoor-seating-badge-icon just above (same tooltip mechanism
       too - toggleEstimatedTooltip(), see venue-card.js), styled green
       rather than that one's orange specifically so the two read as
       distinct badges at a glance when a venue carries both. */
    display: inline-flex;
    align-items: center;
    position: relative;
    vertical-align: middle;
    margin-left: 4px;
    color: #1E7A3D;
    cursor: pointer;
  }
  .opening-hours-mini-icon {
    position: absolute;
    top: 2px;
    right: 0;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background: var(--muted);
    color: white;
    font-size: 9px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
  }
  .estimated-tooltip-popup {
    display: none;
    position: absolute;
    top: 18px;
    right: 0;
    z-index: 3;
    background: var(--shade-900, #16303F);
    color: white;
    font-size: 11px;
    line-height: 1.4;
    padding: 6px 9px;
    border-radius: 6px;
    width: 200px;
    text-align: left;
    box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  }
  .estimated-tooltip-popup.visible { display: block; }
  .estimated-tooltip-popup a { color: var(--sun-300, #FFD27A); text-decoration: underline; }
  /* The building-precision mark beside "Sun exposure" (precisionMarkHtml() in venue-card.js): a warning triangle
     with a crosshair, sun yellow for medium and coral for low - the SVGs carry their own colours. Same helper
     bubble as the badges. */
  .precision-mark {
    display: inline-flex;
    align-items: center;
    position: relative;
    vertical-align: -2px;
    margin-left: 4px;
    cursor: pointer;
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
  }


  /* Timeline tap tooltip - the mobile-friendly equivalent of a timeline
     segment's native hover title (see showTimelineTooltip() in
     venue-card.js). position:fixed + JS-set top/left, same convention as
     .auth-floater, since this can anchor to any segment anywhere in the
     timeline rather than one fixed spot the way .estimated-tooltip-popup
     itself is always anchored relative to its own badge. */
  .timeline-tap-tooltip {
    position: fixed;
    z-index: 1120;
    background: var(--shade-900, #16303F);
    color: white;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    padding: 7px 11px;
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.25);
    white-space: nowrap;
  }

  .card-type {
    font-size: 12px;
    color: var(--muted);
    text-transform: capitalize;
    margin: 2px 0 6px;
    display: flex;
    align-items: center;
    gap: 5px;
  }
  /* Heater / table-count chips after the venue type: the owner's own terrace facts. A hairline divider sets each
     one off from the type. */
  .terrace-fact {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    text-transform: none;
    font-variant-numeric: tabular-nums;
  }
  .terrace-fact::before {
    content: "";
    width: 1px;
    height: 11px;
    margin-right: 3px;
    background: var(--border, rgba(0,0,0,0.15));
  }
  .terrace-fact b { font-weight: 600; font-size: 13px; color: var(--ink); }
  .terrace-fact svg { flex: none; }
  .terrace-fact.heater svg { color: #E07B1F; }
  /* Always-visible preview of the venue's real, computed sun/shade day -
     replaces the flat 4px accent border every card used to carry (a
     single "sunny/not" flag with no shape to it, and the specific
     pattern craft review calls out as the most recognizable AI-slop
     tell on a card). Reuses renderTimeline() itself (showLegend=false)
     rather than a separate simplified reimplementation, so this is
     ALWAYS exactly the same real data as the full timeline inside the
     expanded card, never a second source of truth to keep in sync by
     hand. Only the axis labels, legend, and window list get hidden here
     (all unconditional in renderTimeline()'s own markup, so hidden via
     CSS rather than a second render-time flag) - the colored bar itself,
     its closed-hours hatching, and the "now" marker all render exactly
     as they do at full size, just compressed into a thin strip. */
  .mini-timeline { margin: 2px 0 8px; }
  .mini-timeline .timeline-wrap { padding-top: 0; }
  /* 12px, not the original 7px - at 7px the color segments and the
     closed-hours hatching compressed into an indistinct blur (confirmed
     directly against a real screenshot); 12px is the smallest height
     that still reads as an actual pattern rather than a thin line. */
  .mini-timeline .timeline-svg { height: 12px; border-radius: 4px; overflow: hidden; }
  .mini-timeline .timeline-hours,
  .mini-timeline .timeline-legend,
  .mini-timeline .window-list-section { display: none; }
  /* The hour gridlines (thin white ticks - see buildHourGridlines()) read
     as fine texture at full timeline height; compressed to 7px they just
     muddy the color read, so they're the one sub-element hidden by
     attribute selector rather than kept. The "now" marker uses a
     different stroke (--shade-900, #16303F) and stays. */
  .mini-timeline .timeline-svg line[stroke="white"] { display: none; }
  .next-good-line { font-size: 11px; color: var(--muted); margin-top: 2px; }
  .wind-line { display: flex; align-items: center; gap: 5px; margin-top: 3px; font-size: 12px; color: var(--muted); }
  .wind-line svg { flex: none; }
  .sun-line { font-size: 14px; font-weight: 600; }
  /* --sun-700, not --sun-600: this is the app's single most important
     line of text (the actual sunny/shaded answer) and #E67E22 measures
     only 2.85:1 on the card's white/#FFFBF3 background, under the 4.5:1
     WCAG minimum for text this size. */
  .sun-line.sunny { color: var(--sun-700); }
  .sun-line.shaded { color: var(--muted); }
  .meta-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 8px;
  }
  .distance { font-size: 12px; color: var(--muted); }
  .maps-link {
    font-size: 12px;
    font-weight: 600;
    color: var(--shade-900);
    text-decoration: none;
    border-bottom: 1px solid var(--shade-900);
  }
  .card-footer-actions {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--card-border);
    font-size: 11px;
    color: var(--muted);
  }
  .listing-action-link {
    color: var(--muted);
    text-decoration: underline;
  }
  .listing-action-link.reported {
    color: var(--sun-700);
    text-decoration: none;
    cursor: default;
  }
  .footer-dot { margin: 0 6px; }
  .maps-link:hover { color: var(--sun-700); border-color: var(--sun-700); }

  .chevron {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--cream);
    color: var(--shade-900);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.2s ease, background 0.15s ease;
    font-size: 13px;
  }
  .card:hover .chevron { background: var(--card-border); }
  .card.expanded .chevron { transform: rotate(180deg); }

  /* Expand / collapse: a one-row grid whose row animates 0fr -> 1fr, so the
     details open to their real height (no max-height ceiling to guess at or
     to clip a long window list against) and only grid-template-rows is
     animated, not height/margin. The clip wrapper has no padding of its own
     so it can collapse to nothing; the 14px that used to be margin-top sits
     inside the body, where the clip hides it along with everything else.
     Collapsed content is also visibility:hidden (after the collapse has
     played out), so links inside a closed card aren't reachable by keyboard.
     Browsers without grid-row interpolation just snap open, as they'd
     snap without any transition. */
  .card-details {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .card.expanded .card-details { grid-template-rows: 1fr; }
  .card-details-clip {
    min-height: 0;
    overflow: hidden;
    visibility: hidden;
    transition: visibility 0s linear 0.3s;
  }
  .card.expanded .card-details-clip { visibility: visible; transition-delay: 0s; }
  .card-details-body { padding-top: 14px; }
  @media (prefers-reduced-motion: reduce) {
    .card-details, .card-details-clip { transition: none; }
  }

  .timeline-wrap { padding-top: 4px; }
  .timeline-with-badge { position: relative; }
  .timeline-svg { width: 100%; height: 40px; display: block; }
  .timeline-hours {
    position: relative;
    height: 12px;
    font-size: 10px;
    color: var(--muted);
    margin-top: 2px;
  }
  /* Each label is placed at its own exact time-proportional %, matching
     buildHourGridlines()'s own math in venue-card.js, rather than
     flexbox space-between (which spaces N labels evenly BY COUNT,
     not by the actual time they represent - only equivalent when the
     bar's own start/end happen to divide evenly by the label count,
     which is exactly the 06:00-22:00/4-hour-step case here). */
  .timeline-hours span { position: absolute; top: 0; white-space: nowrap; }
  .window-list {
    margin-top: 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .window-list-section {
    margin-top: 10px;
  }
  .window-list-section .window-list {
    margin-top: 4px;
  }
  .window-list-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .window-list-item {
    font-size: 12px;
    color: var(--ink);
    display: flex;
    align-items: center;
    gap: 6px;
  }
  .window-list-item .dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--sun-700);
    flex-shrink: 0;
  }

  .timeline-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 12px;
    margin-top: 8px;
  }
  .legend-item {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
  }
  .legend-swatch {
    width: 9px;
    height: 9px;
    border-radius: 2px;
    flex-shrink: 0;
  }

  /* The day instrument (day-instrument.js): the expanded card's picture of the day. */
  .dayi { margin-top: 2px; --ease: cubic-bezier(0.16, 1, 0.3, 1); }
  /* Compact (the map popup): the same instrument, smaller and without the parts a small card has no room
     for (split, shadow view, legend, chips, sunrise/sunset - none are built in compact mode). */
  .dayi--compact { margin-top: 0; }
  .dayi--compact .dayi-time { font-size: 22px; }
  .dayi--compact .dayi-state { font-size: 13px; }
  .dayi--compact .dayi-plot { margin-top: 8px; }
  .dayi--compact .dayi-reset { font-size: 11px; padding: 3px 8px; }
  .dayi-readout { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 12px; row-gap: 2px; }
  .dayi-time {
    font-size: 30px; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums; color: var(--shade-900);
  }
  .dayi-state { display: inline-flex; align-items: center; gap: 6px; font-size: 15px; font-weight: 600; }
  .dayi-temp { color: var(--shade-900); font-variant-numeric: tabular-nums; }
  .dayi-reset {
    margin-left: auto; align-self: center; font: inherit; font-size: 12px; font-weight: 600; color: var(--shade-900);
    background: white; border: 1px solid var(--card-border); border-radius: 999px; padding: 5px 11px; cursor: pointer;
  }
  .dayi-reset { display: inline-flex; align-items: center; gap: 4px; font-variant-numeric: tabular-nums; }
  .dayi-reset.is-off { visibility: hidden; }
  .dayi-reset:hover { background: var(--card-border); }
  .dayi-reset:focus-visible, .dayi-chip:focus-visible, .dayi-split:focus-visible { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  .dayi-facts { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 2px 14px; font-size: 12px; color: var(--muted); min-height: 18px; }
  .dayi-fact { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }

  /* The plot: a narrow gutter for the lane icons, then the stage. The time axis and sunrise / sunset line up with the
     stage. Every size comes from variables set in day-instrument.js, so the JS and the CSS can't drift apart. */
  .dayi-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 8px; margin-top: 8px; }
  .dayi-head .dayi-split { margin-left: auto; }
  .dayi-head:not(:has(> button:not([hidden]))) { display: none; }      /* nothing to show in it: no empty row */
  .dayi-plot { position: relative; margin-top: 2px; padding-left: 24px; }
  .dayi-gutter { position: absolute; left: 0; top: 0; width: 20px; height: var(--h1); pointer-events: none; }
  .dayi-stage {
    --x: 50%;
    position: relative; height: var(--h1); border-radius: 10px;
    cursor: ew-resize; touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent;
  }
  /* Splitting needs --shift px more room: the wind lane slides down into it, and this spacer opens to make it
     (grid-template-rows, not height, so only what's below reflows and nothing animates layout properties). */
  .dayi-spacer { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.55s var(--ease); }
  .dayi-spacer > div { min-height: 0; overflow: hidden; }
  .dayi-spacer > div > div { height: var(--shift); }
  .dayi-plot.is-split .dayi-spacer { grid-template-rows: 1fr; }
  .dayi-stage:focus-visible { outline: 2px solid var(--sun-700); outline-offset: 4px; }

  /* Lanes. Merged, shade and sky share one spot; splitting slides the sky lane (and the wind lane under it) down and
     cross-fades merged into shade + sky, merging slides them back together and cross-fades the other way. */
  .dayi-lane { position: absolute; left: 0; width: 100%; top: var(--handle); height: var(--lane); display: block; overflow: visible; }
  .dayi-lane--wind { top: var(--wind-top); height: var(--wind-h); transition: transform 0.55s var(--ease); }
  .dayi-lane--merged { opacity: 1; transition: opacity 0.3s ease 0.28s; }
  .dayi-lane--shade { opacity: 0; transition: opacity 0.3s ease 0.28s; }
  .dayi-lane--sky { opacity: 0; transition: transform 0.55s var(--ease), opacity 0.3s ease 0.28s; }
  .dayi-plot.is-split .dayi-lane--merged { opacity: 0; transition-delay: 0s; transition-duration: 0.18s; }
  .dayi-plot.is-split .dayi-lane--shade { opacity: 1; transition-delay: 0s; transition-duration: 0.2s; }
  .dayi-plot.is-split .dayi-lane--sky { opacity: 1; transform: translateY(var(--shift)); transition: transform 0.55s var(--ease), opacity 0.2s ease 0s; }
  .dayi-plot.is-split .dayi-lane--wind { transform: translateY(var(--shift)); }
  .dayi-wind rect { fill: #8898A3; fill-opacity: 0.5; transition: fill-opacity 0.15s ease, fill 0.15s ease; }
  .dayi-wind rect.is-on { fill: var(--shade-900); fill-opacity: 0.8; }

  /* Lane icons follow their lanes (same transforms), and cross-fade with them. */
  .dayi-lane-icon { position: absolute; left: 0; display: flex; color: var(--muted); pointer-events: auto; cursor: help; transform: translateY(-50%); }
  .dayi-lane-icon svg { display: block; }
  .dayi-lane-icon--merged { opacity: 1; transition: opacity 0.3s ease 0.28s; }
  .dayi-lane-icon--shade { opacity: 0; transition: opacity 0.3s ease 0.28s; }
  .dayi-lane-icon--sky { opacity: 0; transition: transform 0.55s var(--ease), opacity 0.3s ease 0.28s; }
  .dayi-lane-icon--wind { transition: transform 0.55s var(--ease); }
  .dayi-plot.is-split .dayi-lane-icon--merged { opacity: 0; transition-delay: 0s; transition-duration: 0.18s; }
  .dayi-plot.is-split .dayi-lane-icon--shade { opacity: 1; transition-delay: 0s; transition-duration: 0.2s; }
  .dayi-plot.is-split .dayi-lane-icon--sky { opacity: 1; transform: translateY(calc(-50% + var(--shift))); transition: transform 0.55s var(--ease), opacity 0.2s ease 0s; }
  .dayi-plot.is-split .dayi-lane-icon--wind { transform: translateY(calc(-50% + var(--shift))); }
  .dayi-lane-icon--merged, .dayi-lane-icon--shade, .dayi-lane-icon--sky { pointer-events: none; }
  .dayi-plot:not(.is-split) .dayi-lane-icon--merged, .dayi-plot.is-split .dayi-lane-icon--shade,
  .dayi-plot.is-split .dayi-lane-icon--sky { pointer-events: auto; }

  /* The crosshair, with a small sun-or-shade handle on top. */
  .dayi-cross {
    position: absolute; top: 8px; height: calc(var(--h1) - 8px); left: var(--x); width: 1px; pointer-events: none;
    transform: translateX(-0.5px); transform-origin: 0 0; transition: transform 0.55s var(--ease);
    background: color-mix(in srgb, var(--shade-900) 50%, transparent);
  }
  .dayi-base {
    position: absolute; left: var(--base, 0); top: 8px; height: calc(var(--h1) - 8px); width: 0; pointer-events: none;
    transform-origin: 0 0; transition: transform 0.55s var(--ease);
    border-left: 1px dashed color-mix(in srgb, var(--shade-900) 35%, transparent);
  }
  .dayi-plot.is-split .dayi-cross { transform: translateX(-0.5px) scaleY(var(--cross-scale, 1)); }
  .dayi-plot.is-split .dayi-base { transform: scaleY(var(--cross-scale, 1)); }
  .dayi-orb {
    --orb: var(--sun-500);
    position: absolute; left: var(--x); top: 8px; width: 16px; height: 16px; box-sizing: border-box;
    border: 3px solid white; border-radius: 50%; background: var(--orb); transform: translate(-50%, -50%); pointer-events: none;
    box-shadow: 0 1px 4px rgb(18 42 56 / 0.35);
    transition: background-color 0.2s ease, box-shadow 0.2s ease;
  }
  .dayi-orb[hidden] { display: none; }
  /* lit is told by the handle's colour and a solid ring around it, with a neutral cast shadow - no glow */
  .dayi-orb.is-lit { outline: 3px solid color-mix(in srgb, var(--orb) 32%, transparent); box-shadow: 0 3px 6px rgb(18 42 56 / 0.28); }

  /* The shadow theatre (shadow-theatre.js): a top-down view of the buildings around the terrace. */
  .dayi-theatre-wrap { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 0.5s var(--ease); }
  .dayi-theatre-wrap.is-collapsed { grid-template-rows: 0fr; }
  .dayi-theatre-wrap > div { min-height: 0; overflow: hidden; }
  .dayi-theatre-slot { padding-top: 10px; }
  .theatre { position: relative; height: 200px; border-radius: 14px; overflow: hidden; border: 1px solid var(--card-border); background: #F1E8D6; }
  @media (min-width: 560px) { .theatre { height: 240px; } }
  .theatre-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
  .theatre.is-dragging .theatre-canvas { cursor: grabbing; }
  .theatre-rain { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
  .theatre-hud {
    position: absolute; left: 10px; top: 10px; max-width: calc(100% - 56px); display: flex; align-items: center; gap: 7px;
    padding: 6px 11px 6px 9px; border-radius: 14px; background: rgb(255 255 255 / 0.9); pointer-events: none;
    font-size: 12px; font-weight: 600; line-height: 1.25; color: var(--shade-900);
  }
  .theatre-hud:empty { display: none; }
  .theatre-close {
    position: absolute; top: 10px; right: 10px; width: 28px; height: 28px; display: grid; place-items: center; padding: 0;
    border: 0; border-radius: 50%; background: rgb(255 255 255 / 0.9); color: var(--shade-900); cursor: pointer;
  }
  .theatre-close:hover { background: white; }
  /* The map popup's shadow view: same component, a shorter and tighter version of it. */
  .dayi--compact .dayi-theatre-slot { padding-top: 8px; }
  .dayi--compact .theatre { height: 132px; border-radius: 10px; }
  .dayi--compact .theatre-hud { left: 6px; top: 6px; max-width: calc(100% - 44px); padding: 4px 9px 4px 7px; font-size: 11px; }
  .dayi--compact .theatre-close { top: 6px; right: 6px; width: 24px; height: 24px; }
  .dayi--compact .theatre-hint { display: none; }
  .theatre-close:focus-visible { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  .theatre-hud::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--sun-500); }
  .theatre-hud[data-tone="shade"]::before { background: var(--shade-900); }
  .theatre-hud[data-tone="sky"]::before { background: #8FA3B5; }   /* open to the sky, but no sun getting through */
  .theatre-hint {
    position: absolute; right: 10px; bottom: 8px; padding: 2px 9px; border-radius: 999px; background: rgb(255 255 255 / 0.85);
    font-size: 11px; color: var(--shade-900); pointer-events: none;
  }
  .theatre-hint[hidden] { display: none; }
  .theatre-loading {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 0 20px; text-align: center; font-size: 12px; color: var(--muted);
    background: linear-gradient(100deg, #F1E8D6 30%, #F8F2E6 50%, #F1E8D6 70%) 0 0 / 300% 100%;
  }
  .theatre:not(.is-loading) .theatre-loading { display: none; }
  @media (prefers-reduced-motion: no-preference) {
    .theatre.is-loading .theatre-loading { animation: theatre-shimmer 1.6s linear infinite; }
  }
  @keyframes theatre-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }

  .dayi .timeline-hours { margin-top: 4px; margin-left: 24px; }
  .dayi-suntimes { display: flex; justify-content: space-between; margin: 4px 0 0 24px; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

  /* Split / merge, top-right above the timeline. Two icons share one spot and cross-fade: triangles pointing
     outward = split, inward = merge. */
  .dayi-split, .dayi-theatre-toggle {
    display: inline-flex; align-items: center; gap: 7px; padding: 4px 11px 4px 6px; font: inherit; font-size: 12px; font-weight: 600;
    color: var(--muted); background: white; border: 1px solid var(--card-border); border-radius: 999px; cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
  }
  .dayi-split:hover, .dayi-theatre-toggle:hover { color: var(--shade-900); border-color: color-mix(in srgb, var(--shade-900) 30%, var(--card-border)); }
  .dayi-theatre-toggle[hidden] { display: none; }
  .dayi-pill-icon { display: block; width: 18px; height: 18px; }
  .dayi-pill-icon svg { display: block; }
  .dayi-theatre-toggle:focus-visible { outline: 2px solid var(--sun-700); outline-offset: 2px; }
  .dayi-split-icons { position: relative; display: block; width: 18px; height: 18px; }
  .dayi-split-icons > span { position: absolute; inset: 0; display: block; transition: opacity 0.25s ease, transform 0.4s var(--ease); }
  .dayi-split-icons svg { display: block; }
  .dayi-split-icons .is-in { opacity: 0; transform: scale(0.7); }
  .dayi-split[aria-expanded="true"] .dayi-split-icons .is-out { opacity: 0; transform: scale(0.7); }
  .dayi-split[aria-expanded="true"] .dayi-split-icons .is-in { opacity: 1; transform: none; }

  .dayi .timeline-legend { margin-top: 6px; }
  .legend-swatch.dayi-hatch {
    background: repeating-linear-gradient(135deg, rgb(22 48 63 / 0.55) 0 1.5px, transparent 1.5px 4px); border: 1px solid rgb(22 48 63 / 0.35);
  }
  .dayi-chips { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
  .dayi-chip-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
  /* Which rows show follows the timeline: merged reads one combined row, split reads unshaded + sky. */
  .dayi:not(.dayi--split) .dayi-chip-row--split,
  .dayi--split .dayi-chip-row--merged { display: none; }
  .dayi-chip-label { flex-basis: 100%; font-size: 11px; color: var(--muted); }
  .dayi-chip-empty { font-size: 12px; color: var(--muted); }
  .dayi-chip {
    font: inherit; font-size: 12px; color: var(--shade-900); display: inline-flex; align-items: center; gap: 6px;
    background: white; border: 1px solid var(--card-border); border-radius: 999px; padding: 5px 10px 5px 8px; cursor: pointer;
    font-variant-numeric: tabular-nums; transition: background-color 0.15s ease, border-color 0.15s ease;
  }
  .dayi-chip:hover { background: color-mix(in srgb, var(--sun-500) 8%, white); }
  .dayi-chip[aria-pressed="true"] { border-color: var(--sun-500); background: color-mix(in srgb, var(--sun-500) 14%, white); }
  .dayi-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sun-500); flex-shrink: 0; }
  .dayi-chip b { font-weight: 600; }
  .dayi-chip b::before { content: "\00b7"; margin-right: 6px; color: var(--muted); font-weight: 400; }
  /* First open: the timeline draws in from the left while the crosshair travels from sunrise (day-instrument.js). */
  @media (prefers-reduced-motion: no-preference) {
    .dayi--enter .dayi-lane { animation: dayi-draw 0.95s var(--ease) both; }
  }
  @media (prefers-reduced-motion: reduce) {
    .dayi-spacer, .dayi-cross, .dayi-base, .dayi-lane, .dayi-lane-icon, .dayi-split-icons > span, .dayi-theatre-wrap { transition: none !important; }
  }
  @keyframes dayi-draw { from { clip-path: inset(-6px 100% -6px -6px); } to { clip-path: inset(-6px -6px -6px -6px); } }

  .empty, .loading, .error {
    max-width: 640px;
    margin: 40px auto;
    padding: 0 20px;
    text-align: center;
    color: var(--muted);
    font-size: 14px;
  }
  .empty-action-link {
    /* --sun-700, not --sun-600: 2.85:1 on white/cream, under 4.5:1. */
    color: var(--sun-700);
    font-weight: 600;
    text-decoration: none;
    border-bottom: 1px solid var(--sun-700);
  }
  .feed-more { display: flex; justify-content: center; padding: 6px 0 18px; }
  .feed-more-btn {
    font: inherit; font-size: 14px; font-weight: 600; color: var(--shade-900); cursor: pointer;
    padding: 9px 18px; border-radius: 999px; border: 1px solid var(--card-border); background: white;
  }
  .feed-more-btn:hover { background: var(--cream); }
  .loading-more {
    max-width: 640px;
    margin: 16px auto;
    padding: 0 20px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
  }
  .error { color: #B03A2E; }
  /* Nothing could load (index.html's showLoadError()): offline, or LumoSpot unreachable - calm, with a way back */
  .load-error { color: var(--shade-900); display: flex; flex-direction: column; align-items: center; gap: 8px; }
  .load-error strong { font-size: 16px; }
  .load-error-icon { width: 96px; height: auto; }
  .load-error p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; max-width: 320px; }
  .load-error-retry {
    margin-top: 6px; padding: 10px 22px; border-radius: 999px; border: 0; cursor: pointer;
    background: var(--sun-700); color: white; font: inherit; font-size: 14px; font-weight: 700;
  }
  .load-error-detail { margin-top: 6px; font-size: 12px; color: #62717A; word-break: break-word; }

  @media (max-width: 900px) {
    /* "Compressed" - narrower than a full laptop window but not yet
       mobile. Hides the "LumoSpot" wordmark (keeps just the logo icon)
       specifically to give the search bar, now sharing the header row
       with the logo and menu button, more breathing room. */
    .header-brand h1 { display: none; }
    /* the search bar shrinks instead of pushing the account and menu buttons onto a second row (with the product
       switcher beside the logo, its 280 px start no longer fitted a tablet held upright, 768-900 px) */
    .search-bar { flex-basis: 140px; }
    header { gap: 12px; }
  }
  @media (max-width: 480px) {
    /* True mobile - previously reverted to a stacked layout here (search
       bar dropping to its own row, wordmark reappearing since nothing
       else competed for the space). Now instead extends the ≤900px
       "compressed" behavior all the way down: wordmark stays hidden,
       search bar stays in the header row alongside the logo and menu
       button - just needs to be allowed to shrink much smaller than its
       normal 280px minimum to actually fit at true phone widths. */
    .search-bar { flex: 1 1 100px; min-width: 0; }
    /* Focusing the search input (see the matching focus/blur listeners
       in index.html) hides the search/locate buttons so the input - and
       the suggestions dropdown anchored to it, which shares its width -
       gets the search bar's FULL width instead of splitting it with two
       buttons that together eat a large chunk of an already-narrow
       phone-width row. Scoped to true-mobile only: at wider widths the
       search bar already has enough room that this isn't needed. */
    .search-bar.search-focused .locate-btn { display: none; }
    .datetime-bar { width: 100%; flex-wrap: wrap; }
    .datetime-bar label { display: none; } /* "Viewing:" - the date/time
       fields plus both toggle buttons already fill this row tightly at
       true phone widths; the label's own width was crowding them
       further, pushing the date field narrower than it needed to be.
       Dropped here specifically (not at any wider breakpoint) since
       there's enough room to keep it everywhere else. */
    .datetime-bar input[type="date"] { flex: 1; width: auto; min-width: 120px; }
    /* Compacts the filters/weather/expand/map row enough to fit on one
       line at true phone widths, rather than wrapping to two - "Filters"
       and the full "Include weather"/"Weer meenemen" label are dropped
       in favor of just the icon and the short "weather"/"weer" label
       respectively (see .weather-short-label above) - the icon alone
       still reads as a filters button; the short label alone still
       reads fine next to its own toggle, in either language. */
    .filters-toggle-btn .btn-label,
    .include-weather-toggle .btn-label { display: none; }
    .include-weather-toggle .weather-short-label { display: inline; }
    .filters-toggle-btn { padding: 7px 9px; }
  }
  .nav-menu-wrap {
    position: relative;
    order: 4;
    flex-shrink: 0;
  }
  .nav-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 9px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: white;
    color: var(--shade-900);
    cursor: pointer;
  }
  .nav-menu-btn:hover { background: var(--card-border); }

  /* Auth (Google sign-in) - sits between the search bar and the
     hamburger menu. order:2.5 would have been a clean way to slot this
     in without renumbering .search-bar/.nav-menu-wrap's own order
     values - except CSS's order property is specifically typed as an
     <integer>, not a general number, so a fractional value is invalid
     and silently falls back to order's own initial value of 0 (which,
     confirmed directly via a real screenshot, put this element before
     .header-brand instead of after .search-bar). Renumbered cleanly
     instead: .nav-menu-wrap moved from order:3 to order:4 (see its own
     rule below), freeing up order:3 for this element. */
  .auth-wrap {
    position: relative;
    order: 3;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    /* Pushes this element (and .nav-menu-wrap, order:4, right after it)
       to the header's far right regardless of how many elements
       precede it - on index.html that's .header-brand + .search-bar;
       on the pages with no search bar (favorites.html, how-it-works.html,
       contact.html, privacy.html) it's .header-brand alone. Without
       this, header's own justify-content:space-between (see header's
       own rule and its comment, written back when this row only ever
       had two children) spreads a variable-length middle group evenly
       instead - confirmed via screenshot landing the avatar dead
       center on a two-child page instead of pinned to the right. auto
       margins on a flex child override the parent's justify-content
       for that child, so this works regardless of that other rule. */
    margin-left: auto;
  }
  #authSignedOut { display: flex; align-items: center; }
  .auth-user-btn {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: 1px solid var(--card-border);
    padding: 0;
    background: none;
    cursor: pointer;
    overflow: hidden;
    flex-shrink: 0;
  }
  .auth-avatar {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 15px;
    color: white;
  }
  .auth-avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .nav-menu-dropdown.auth-user-dropdown {
    right: 0;
    min-width: 220px;
  }
  .auth-user-info {
    padding: 8px 10px 10px;
    border-bottom: 1px solid var(--card-border);
    margin-bottom: 6px;
  }
  .auth-user-name { font-weight: 700; font-size: 14px; color: var(--shade-900); }
  .auth-user-email {
    font-size: 12px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .auth-danger { color: #c0392b !important; }
  .auth-danger:hover { background: #fdecea !important; }

  .nav-menu-dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    background: white;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    min-width: 180px;
    padding: 6px;
    display: none;
    flex-direction: column;
    z-index: 1110; /* same Leaflet-clearance reasoning as .filters-drawer above - kept safely above it too, matching its original relative order */
  }
  .nav-menu-dropdown.open { display: flex; }
  .nav-menu-item {
    display: block;
    width: 100%;
    text-align: left;
    padding: 10px 12px;
    border-radius: 8px;
    border: none;
    background: none;
    color: var(--ink);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none; /* nav-menu-item is an <a> on how-it-works.html/
       contact.html (real links) - color is already overridden above so
       that alone was safe either way, but the underline isn't covered by
       color, so this stops the browser's default link underline from
       showing. Harmless no-op on index.html, where these are still
       plain buttons. */
  }
  .nav-menu-item:hover { background: var(--cream); }
  .nav-menu-item.active { color: var(--sun-700); }

  .content-page {
    max-width: 640px;
    margin: 0 auto;
  }
  .content-page-inner {
    padding: 24px 20px 60px;
  }
  .content-page-inner h2 {
    font-size: 22px;
    color: var(--shade-900);
    margin: 0 0 12px;
  }
  .content-page-inner h3 {
    font-size: 16px;
    color: var(--shade-900);
    margin: 24px 0 8px;
  }
  .content-page-inner p {
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink);
    margin: 0 0 4px;
  }
  .how-it-works-gif {
    display: block;
    max-width: 100%;
    margin: 16px auto 8px;
    border-radius: 12px;
    border: 1px solid var(--card-border);
  }
  .content-page-inner img {
    display: block;
    max-width: 100%;
    border-radius: 12px;
    border: 1px solid var(--card-border);
  }
  .favorite-venue-list {
    display: flex;
    flex-direction: column;
    gap: 20px;
    margin: 16px 0 20px;
  }
  .favorite-venue img {
    margin: 0 0 10px;
  }
  .favorite-venue h4 {
    font-size: 15px;
    color: var(--shade-900);
    margin: 0 0 6px;
  }
  .favorite-venue p {
    margin: 0;
  }
  .coverage-note {
    background: #FFFBF3;
    border: 1px solid #F5D9B3;
    border-radius: 10px;
    padding: 10px 14px;
    font-size: 14px;
    color: var(--ink);
    margin: 4px 0 20px;
  }

  .blog-meta {
    font-size: 13px;
    color: var(--muted);
    margin: -4px 0 20px;
  }
  .content-page-inner ul {
    margin: 0 0 16px;
    padding-left: 20px;
  }
  .content-page-inner li {
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink);
    margin-bottom: 6px;
  }
  .content-page-inner a {
    /* --sun-700 (== Sun Bright), not --sun-600 (Sun Core, wordmark-only
       per the One-Orange Rule) - this rule predates that decision and
       was never updated, which is what let every leaderboard venue
       name/hours/link below render in the wrong orange despite each
       having its own, correct color declaration: `.content-page-inner
       a`'s type+class specificity (0,1,1) was silently beating
       `.leaderboard-name`'s class-only specificity (0,1,0). Fixed at
       both ends: this value, and a.leaderboard-name/a.leaderboard-claim-
       link below matching this rule's own specificity so source order
       (they come later) decides it instead. */
    color: var(--sun-700);
    font-weight: 600;
  }
  .content-page-inner a:hover {
    text-decoration: none;
  }

  .blog-post-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 20px;
  }
  .content-page-inner a.blog-post-card {
    display: block;
    padding: 16px;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    background: white;
    color: var(--ink);
    font-weight: normal;
    text-decoration: none;
  }
  .content-page-inner a.blog-post-card:hover {
    border-color: var(--sun-500);
  }
  .blog-post-card h3 {
    margin: 0 0 6px;
    font-size: 16px;
    color: var(--shade-900);
  }
  .blog-post-card p {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink);
  }

  /* How-it-works "shadow lab" (see hiw-lab.js): one sticky top-down figure
     the five explanations scroll past and drive. Map colors are all derived
     from the existing tokens; the status colors below mirror venue-card.js's
     STATUS_COLORS (sunny / partly / cloudy / rainy / shaded-and-night) so
     the lab reads in the same language as the timelines it explains. */
  .content-page.hiw-page { max-width: 1080px; }
  .hiw-page .content-page-inner > h2,
  .hiw-page .content-page-inner > p,
  .hiw-page .content-page-inner > .coverage-note { max-width: 640px; }
  .hiw-story { margin-top: 16px; }
  .hiw-stage {
    --hiw-ground: color-mix(in srgb, var(--header-bg) 62%, var(--cream));
    --hiw-building: color-mix(in srgb, white 74%, var(--cream));
    --hiw-edge: color-mix(in srgb, var(--shade-900) 24%, transparent);
    --hiw-shade: color-mix(in srgb, var(--shade-900) 24%, transparent);
    position: sticky;
    top: 8px;
    z-index: 5;
    margin: 0 0 12px;
    padding: 10px;
    border: 1px solid #F5D9B3;
    border-radius: 16px;
    background: #FFFBF3;
  }
  .hiw-stage [data-status="sunny"] { --st: #F2994A; }
  .hiw-stage [data-status="partially_sunny"] { --st: #F7C08A; }
  .hiw-stage [data-status="partially_cloudy"] { --st: #D8C1A8; }
  .hiw-stage [data-status="cloudy"] { --st: #CBCFD4; }
  .hiw-stage [data-status="rainy"] { --st: #A8C8EA; }
  .hiw-stage [data-status="not_sunny"],
  .hiw-stage [data-status="night"] { --st: #55606B; }
  .hiw-fallback[hidden], .hiw-lab[hidden], .hiw-caption[hidden] { display: none; }
  .hiw-visually-hidden {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .hiw-map-wrap {
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    background: var(--hiw-ground);
  }
  .hiw-map { display: block; width: 100%; height: 100%; cursor: grab; touch-action: pan-y; user-select: none; }
  .hiw-stage.is-dragging .hiw-map { cursor: grabbing; }
  .hiw-ground { fill: var(--hiw-ground); }
  .hiw-canal { fill: color-mix(in srgb, #A8C8EA 52%, var(--cream)); }
  .hiw-shadow { fill: var(--hiw-shade); }
  .hiw-b {
    fill: var(--hiw-building);
    stroke: var(--hiw-edge);
    stroke-width: 1px;
    vector-effect: non-scaling-stroke;
    transition: opacity 0.7s ease, stroke 0.7s ease;
  }
  .hiw-b.is-venue { fill: #FFE5B2; stroke: var(--sun-500); }
  .hiw-stage.has-ring .hiw-b.is-out { opacity: 0.4; }
  .hiw-stage.has-ring .hiw-b.is-in:not(.is-venue) { stroke: color-mix(in srgb, var(--sun-500) 70%, transparent); }
  .hiw-terrace {
    fill: #FFE5B2;
    stroke: var(--sun-500);
    stroke-width: 1.2px;
    vector-effect: non-scaling-stroke;
  }
  .hiw-ring-fill { fill: var(--sun-500); opacity: 0; transition: opacity 0.9s ease 0.5s; }
  .hiw-stage.has-ring .hiw-ring-fill { opacity: 0.07; }
  .hiw-ring {
    fill: none;
    stroke: var(--sun-500);
    stroke-linecap: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    opacity: 0;
  }
  .hiw-stage.has-ring .hiw-ring {
    stroke-dashoffset: 0;
    opacity: 1;
    transition: stroke-dashoffset 1.7s cubic-bezier(0.16, 1, 0.3, 1) 0.3s, opacity 0.2s ease 0.3s;
  }
  .hiw-orbit { fill: none; stroke: color-mix(in srgb, var(--sun-500) 45%, transparent); }
  .hiw-ray { stroke: var(--sun-500); stroke-linecap: round; opacity: 0.75; transition: opacity 0.3s ease; }
  .hiw-ray[data-status="not_sunny"] { stroke: var(--shade-900); opacity: 0.3; }
  .hiw-stage.is-night .hiw-ray { opacity: 0; }
  .hiw-night { fill: var(--shade-900); opacity: 0; pointer-events: none; transition: opacity 0.5s ease; }
  .hiw-stage.is-night .hiw-night { opacity: 0.32; }
  .hiw-sun-ring { fill: var(--sun-300); }
  .hiw-sun-core { fill: var(--sun-500); }
  .hiw-sun { transition: opacity 0.3s ease; }
  .hiw-stage.is-night .hiw-sun { opacity: 0; }
  .hiw-pin-halo { fill: white; stroke: var(--hiw-edge); stroke-width: 1px; }
  .hiw-pin-dot { fill: var(--st, #55606B); transition: fill 0.3s ease; }
  .hiw-clouds, .hiw-rain { opacity: 0; transition: opacity 0.6s ease; }
  .hiw-stage.has-weather.weather-on .hiw-clouds,
  .hiw-stage.has-weather.weather-on .hiw-rain.is-on { opacity: 1; }
  .hiw-cloud { opacity: 0; transition: opacity 0.8s ease; }
  .hiw-cloud.is-on { opacity: 0.94; }
  .hiw-cloud-body { fill: color-mix(in srgb, #CBCFD4 40%, white); animation: hiw-drift 24s ease-in-out infinite alternate; }
  @keyframes hiw-drift { from { transform: translateX(-10px); } to { transform: translateX(10px); } }
  .hiw-rain line { stroke: #4576A3; stroke-width: 1px; opacity: 0.55; vector-effect: non-scaling-stroke; }
  .hiw-rain > g { animation: hiw-fall 0.8s linear infinite; }
  @keyframes hiw-fall { to { transform: translate(-4px, 14px); } }
  .hiw-chip { opacity: 0; transition: opacity 0.5s ease; pointer-events: none; }
  .hiw-chip-terrace { opacity: 1; }
  .hiw-stage.has-src .hiw-chip-terrace { opacity: 0; }
  .hiw-stage.has-ring .hiw-chip-ring { opacity: 1; transition-delay: 1s; }
  .hiw-stage.has-src .hiw-chip-src { opacity: 1; }
  .hiw-stage.has-src .hiw-chip-src-weather { transition-delay: 0.15s; }
  .hiw-stage.has-src .hiw-chip-src-osm { transition-delay: 0.3s; }
  .hiw-chip-body rect { fill: var(--shade-900); }
  .hiw-chip-body text { fill: var(--cream); font-size: 12px; font-weight: 600; font-family: inherit; }
  .hiw-leader { stroke: var(--shade-900); opacity: 0.55; stroke-linecap: round; }
  .hiw-north path { fill: var(--shade-900); opacity: 0.5; }
  .hiw-north text { fill: var(--shade-900); opacity: 0.6; font-size: 11px; font-weight: 700; font-family: inherit; }

  .hiw-row { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
  .hiw-time { flex: 1; min-width: 0; display: block; }
  .hiw-range {
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: 100%;
    height: 26px;
    margin: 0;
    background: transparent;
    cursor: pointer;
    --p: 0%;
  }
  .hiw-range::-webkit-slider-runnable-track {
    height: 6px;
    border-radius: 999px;
    background: linear-gradient(to right, var(--sun-500) var(--p), #E4DDCC var(--p));
  }
  .hiw-range::-moz-range-track {
    height: 6px;
    border-radius: 999px;
    background: linear-gradient(to right, var(--sun-500) var(--p), #E4DDCC var(--p));
  }
  .hiw-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 22px;
    height: 22px;
    margin-top: -8px;
    border: 3px solid var(--sun-500);
    border-radius: 50%;
    background: white;
  }
  .hiw-range::-moz-range-thumb {
    box-sizing: border-box;
    width: 22px;
    height: 22px;
    border: 3px solid var(--sun-500);
    border-radius: 50%;
    background: white;
  }
  .hiw-range:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 4px; border-radius: 999px; }
  .hiw-time-out {
    min-width: 3.3em;
    text-align: right;
    font-size: 15px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--shade-900);
  }
  .hiw-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px 5px 10px;
    border: 1px solid var(--card-border);
    border-radius: 999px;
    background: white;
    font-size: 13px;
    font-weight: 600;
    color: var(--shade-900);
    white-space: nowrap;
  }
  .hiw-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--st, #55606B); transition: background 0.3s ease; }
  .hiw-weather-toggle { margin-left: auto; font-size: 13px; color: var(--ink); visibility: hidden; opacity: 0; transition: opacity 0.4s ease; }
  .hiw-stage.has-weather .hiw-weather-toggle { visibility: visible; opacity: 1; }
  .hiw-day { margin-top: 12px; opacity: 0; transform: translateY(6px); pointer-events: none; transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); }
  .hiw-stage.has-day .hiw-day { opacity: 1; transform: none; pointer-events: auto; }
  .hiw-strip-wrap { position: relative; height: 18px; }
  .hiw-strip { display: block; width: 100%; height: 100%; border-radius: 6px; overflow: hidden; }
  .hiw-strip .c-sunny { fill: #F2994A; }
  .hiw-strip .c-partially_sunny { fill: #F7C08A; }
  .hiw-strip .c-partially_cloudy { fill: #D8C1A8; }
  .hiw-strip .c-cloudy { fill: #CBCFD4; }
  .hiw-strip .c-rainy { fill: #A8C8EA; }
  .hiw-strip .c-not_sunny, .hiw-strip .c-night { fill: #55606B; }
  .hiw-strip-past { fill: var(--cream); opacity: 0.55; }
  .hiw-now { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--shade-900); }
  .hiw-ticks { position: relative; height: 16px; margin-top: 4px; font-size: 11px; line-height: 16px; color: var(--muted); font-variant-numeric: tabular-nums; }
  .hiw-ticks span { position: absolute; transform: translateX(-50%); }
  .hiw-ticks span:nth-child(1) { left: 5.88%; }
  .hiw-ticks span:nth-child(2) { left: 23.53%; }
  .hiw-ticks span:nth-child(3) { left: 41.18%; }
  .hiw-ticks span:nth-child(4) { left: 58.82%; }
  .hiw-ticks span:nth-child(5) { left: 76.47%; }
  .hiw-ticks span:nth-child(6) { left: 94.12%; }
  .hiw-stats { display: flex; justify-content: space-between; gap: 12px; margin-top: 6px; font-size: 13px; color: var(--muted); }
  .hiw-stats b { color: var(--shade-900); font-variant-numeric: tabular-nums; }
  .hiw-caption { margin: 10px 2px 0; font-size: 12px; line-height: 1.4; color: var(--muted); }

  /* Precision scenes (6-8, see hiw-lab.js). Uncertainty has one look everywhere: hatching - on the map the band a
     shadow could also reach, on the day strip the 15 minutes where the terrace's answer could flip. Mapped heights
     wear the medium mark's amber, guessed ones the low mark's coral, as dotted and dashed edges. */
  .hiw-fringe { opacity: 0; transition: opacity 0.6s ease; }
  .hiw-stage[data-prec="medium"] .hiw-fringe,
  .hiw-stage[data-prec="low"] .hiw-fringe { opacity: 1; }
  .hiw-hatch-line { fill: color-mix(in srgb, var(--shade-900) 50%, transparent); }
  .hiw-stage.has-prec .hiw-b.is-in:not(.is-venue) { stroke: var(--hiw-edge); }
  .hiw-stage.has-prec .hiw-b.is-mapped { stroke: #C98418; stroke-width: 1.6px; stroke-dasharray: 1.6 2.6; }
  .hiw-stage.has-prec .hiw-b.is-guessed {
    fill: color-mix(in srgb, white 40%, var(--hiw-ground));
    stroke: #C9452F;
    stroke-width: 1.6px;
    stroke-dasharray: 5 3.5;
  }
  .hiw-heights { opacity: 0; transition: opacity 0.5s ease; pointer-events: none; }
  .hiw-stage.has-prec .hiw-heights { opacity: 1; }
  .hiw-h {
    font: 700 10.5px/1 -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
    fill: var(--shade-900);
    dominant-baseline: central;
    font-variant-numeric: tabular-nums;
    paint-order: stroke;
    stroke: var(--hiw-building);
    stroke-width: 3px;
    stroke-linejoin: round;
  }
  .hiw-h.is-medium { fill: #8A5A0E; }
  .hiw-h.is-low { fill: #A8402E; }
  .hiw-h.is-out { display: none; }
  .hiw-pin.is-uncertain .hiw-pin-halo { stroke: var(--shade-900); stroke-width: 1.5px; stroke-dasharray: 3 2.3; }
  .hiw-pill-note { display: none; font-weight: 500; color: var(--muted); }
  .hiw-pill.is-uncertain .hiw-pill-note { display: inline; }
  .hiw-pill.is-uncertain .hiw-pill-note::before { content: "\00B7"; margin-right: 6px; }
  .hiw-pill.is-uncertain .hiw-dot { background: repeating-linear-gradient(135deg, var(--st, #55606B) 0 2px, white 2px 3.4px); box-shadow: 0 0 0 1px var(--st, #55606B); }
  .hiw-row-state { flex-wrap: wrap; row-gap: 8px; }
  .hiw-prec {
    position: absolute;
    top: 10px;
    right: 10px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 11px 4px 9px;
    border: 1px solid var(--card-border);
    border-radius: 999px;
    background: color-mix(in srgb, white 90%, transparent);
    font-size: 12px;
    font-weight: 600;
    color: var(--shade-900);
    pointer-events: none;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .hiw-stage.has-prec .hiw-prec { opacity: 1; transform: none; }
  .hiw-prec-icon:empty { display: none; }
  .hiw-prec-icon svg { display: block; }
  .hiw-strip rect[fill="url(#hiwStripHatch)"] { opacity: 0; transition: opacity 0.3s ease; }
  .hiw-strip rect.is-on { opacity: 1; }
  .hiw-strip-hatch { fill: none; stroke: color-mix(in srgb, white 82%, transparent); stroke-width: 0.11; }
  .hiw-legend { display: none; align-items: center; gap: 7px; margin-top: 6px; font-size: 12px; color: var(--muted); }
  .hiw-stage[data-prec="medium"] .hiw-legend,
  .hiw-stage[data-prec="low"] .hiw-legend { display: flex; }
  .hiw-legend-hatch {
    width: 18px;
    height: 10px;
    border-radius: 3px;
    background: repeating-linear-gradient(135deg, #55606B 0 1.6px, color-mix(in srgb, white 82%, #55606B) 1.6px 3.6px);
  }
  .hiw-step-precision { scroll-margin-top: 90px; }
  .content-page-inner .hiw-step-precision h3 { display: flex; align-items: center; gap: 10px; }
  .hiw-step-precision h3 .precision-mark { margin: 0; cursor: default; }
  .hiw-step-precision h3 .precision-mark svg { width: 29px; height: 21px; }
  .content-page-inner .hiw-step .hiw-step-aside { margin-top: 14px; font-size: 14px; color: var(--muted); }
  .hiw-step { padding: 0 0 8px; }
  .hiw-step + .hiw-step { margin-top: 0; }
  .content-page-inner .hiw-step h3 { margin: 0 0 10px; font-size: 22px; line-height: 1.2; }
  .content-page-inner .hiw-step p { margin: 0; max-width: 34em; font-size: 16px; line-height: 1.65; }
  .hiw-stage:not(.is-live) ~ .hiw-steps .hiw-step { min-height: 0; }
  .hiw-stage.is-live ~ .hiw-steps .hiw-step { min-height: 38vh; }
  @media (min-width: 900px) {
    .hiw-story { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 480px); column-gap: 56px; align-items: start; }
    .hiw-stage { grid-column: 2; grid-row: 1; top: 20px; margin: 0; }
    .hiw-steps { grid-column: 1; grid-row: 1; padding-left: 26px; }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step { min-height: 64vh; display: flex; flex-direction: column; justify-content: center; }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step:first-child { min-height: 52vh; }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step:last-child { min-height: 52vh; }
    .hiw-step { position: relative; }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step::before {
      content: "";
      position: absolute;
      left: -26px;
      top: calc(50% - 1.1em - 7px);
      width: 10px;
      height: 10px;
      border: 2px solid var(--sun-300);
      border-radius: 50%;
      transition: background 0.4s ease, border-color 0.4s ease;
    }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step.is-active::before { background: var(--sun-500); border-color: var(--sun-500); }
  }
  @media (max-width: 899px) {
    .hiw-map-wrap { aspect-ratio: 16 / 10; }
    /* Text scrolls under the pinned stage; this masks the sliver of it that
       would otherwise show in the 8px gap above the stage. */
    .hiw-stage::before { content: ""; position: absolute; left: -1px; right: -1px; top: -9px; height: 9px; background: var(--cream); }
    .hiw-stage { padding: 8px; }
    .hiw-row { margin-top: 6px; }
    .hiw-day { margin-top: 8px; }
    .hiw-ticks { display: none; }
    .hiw-stats { margin-top: 4px; }
    .hiw-caption { display: none; }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step { min-height: 36vh; }
    .hiw-stage.is-live ~ .hiw-steps .hiw-step:last-child { min-height: 30vh; }
  }
  /* Reduced motion: the lab still works, it just never moves on its own. */
  html:not(.hiw-anim) .hiw-stage * { transition: none !important; animation: none !important; }

  /* Sunniest-terraces page header (see the .lb-hero markup and skyline_svg()
     in render_terrace_leaderboard_page.py, and leaderboard.js): a canal-house
     skyline with the day's sun riding an arc above it and one long building
     shadow that the sun pushes off the page. The product's own idea (shade
     moves with the sun) shown literally.
     Everything is driven by one number, --s: the sun's place in the day, 0 =
     06:00 to 1 = 21:00. The sun's left/top, the shadow's edge, and (via
     leaderboard.js) the time readout all derive from it. --s is a registered
     property so the load animation below can interpolate it; without support
     it just jumps to the finished state. Without JS or under
     prefers-reduced-motion the header simply renders at its resting --s
     (late afternoon, the shade tucked against the right edge). The sun is
     also a keyboard-operable slider, so the scrub isn't pointer-only. */
  @property --s { syntax: "<number>"; inherits: true; initial-value: 0.76; }
  .lb-hero {
    --s: 0.76;
    --sky-h: 96px;
    --arch: 150px;
    --d: 56px;
    /* Where the sun is, shared by the sun and its time chip. Parabola: at
       s = 0 / 1 the sun sits on the roofline, at 0.5 it tops out --arch
       above it. */
    --sun-x: calc(var(--s) * 100%);
    --sun-y: calc(100% - var(--sky-h) + 8px - var(--arch) * 4 * var(--s) * (1 - var(--s)));
    --lb-hero-muted: #4F5D66;
    position: relative;
    isolation: isolate;
    overflow: hidden;
    margin: 0 0 20px;
    padding: 28px 24px calc(var(--sky-h) + 30px);
    border: 1px solid #F5D9B3;
    border-radius: 16px;
    background: linear-gradient(180deg, #FFF6E5 0%, #FFE5B2 100%);
    touch-action: pan-y;
  }
  .lb-hero-shade {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: color-mix(in srgb, var(--shade-900) 13%, transparent);
    /* Everything right of a slanted edge that trails the sun. */
    clip-path: polygon(
      calc(var(--s) * 100% + 14%) 0, 100% 0, 100% 100%, calc(var(--s) * 100% - 10%) 100%);
  }
  .lb-sun {
    position: absolute;
    z-index: 2;
    box-sizing: border-box;
    width: var(--d);
    height: var(--d);
    left: var(--sun-x);
    top: var(--sun-y);
    transform: translate(-50%, -50%);
    border: 9px solid var(--sun-300);
    border-radius: 50%;
    background: var(--sun-500);
    cursor: grab;
    touch-action: none;
  }
  .lb-hero.is-scrubbing .lb-sun { cursor: grabbing; }
  .lb-sun:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 4px; }
  /* A sibling of the sun, not a child: the sun sits under the roofline and
     the title, but this chip has to read over both. */
  .lb-sun-time {
    position: absolute;
    z-index: 5;
    left: var(--sun-x);
    top: var(--sun-y);
    transform: translate(-50%, calc(-100% - var(--d) / 2 - 8px));
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--shade-900);
    color: var(--cream);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.18s ease;
  }
  .lb-hero.is-scrubbing .lb-sun-time,
  .lb-sun:focus-visible + .lb-sun-time { opacity: 1; }
  .lb-skyline {
    position: absolute;
    left: 0;
    bottom: 0;
    z-index: 3;
    display: block;
    width: 100%;
    height: var(--sky-h);
    pointer-events: none;
  }
  .lb-skyline .bk { fill: color-mix(in srgb, var(--shade-900) 22%, transparent); }
  .lb-skyline .ft { fill: var(--shade-900); }
  .lb-skyline .w { fill: color-mix(in srgb, var(--cream) 14%, transparent); }
  .lb-skyline .w.on { fill: color-mix(in srgb, var(--sun-300) 62%, transparent); }
  .lb-hero-copy { position: relative; z-index: 4; }
  .content-page-inner h2.lb-hero-title {
    max-width: 11em;
    margin: 0 0 10px;
    font-size: clamp(28px, 7vw, 40px);
    line-height: 1.08;
    letter-spacing: -0.02em;
    font-weight: 700;
    text-wrap: balance;
  }
  .content-page-inner p.lb-hero-sub {
    max-width: 28em;
    margin: 0;
    font-size: 15px;
    line-height: 1.5;
    color: var(--lb-hero-muted);
  }
  /* A city's ranking page links back to the hub; the hub lists each city under its own heading. */
  .content-page-inner p.lb-crumb { margin: 16px 0 12px; font-size: 14px; }
  .lb-city { margin-top: 28px; }
  .content-page-inner h3.lb-city-title { margin: 0; font-size: 20px; }
  .lb-city .blog-post-list { margin-top: 12px; }
  /* The one authored moment: the sun climbs out from behind the roofs and
     drags the shade off the page. Exponential ease-out; only the header's
     own --s is animated. Gated on .lb-js (see the inline <head> script), so
     reduced-motion and no-JS visitors get the finished state. */
  @keyframes lb-sunrise { from { --s: 0.04; } }
  .lb-js .lb-hero { animation: lb-sunrise 3.4s cubic-bezier(0.22, 1, 0.36, 1) 0.2s both; }
  /* Once, after it lands, a ring says "this is grabbable"; the first touch
     retires it. */
  .lb-sun::after {
    content: "";
    position: absolute;
    inset: -9px;
    border: 2px solid var(--sun-500);
    border-radius: 50%;
    opacity: 0;
    pointer-events: none;
  }
  .lb-js .lb-hero:not(.is-touched) .lb-sun::after { animation: lb-sun-ping 1.5s cubic-bezier(0.16, 1, 0.3, 1) 3.4s 2 both; }
  @keyframes lb-sun-ping { from { transform: scale(0.9); opacity: 0.75; } to { transform: scale(1.75); opacity: 0; } }
  /* Narrow screens: the text spans the full width, so the sun can't ride in
     the open sky beside it. Leave a clear band between the copy and the
     roofs and flatten the arc so the sun crosses that band instead. */
  @media (max-width: 560px) {
    .lb-hero { --sky-h: 68px; --arch: 60px; --d: 44px; padding: 20px 16px calc(var(--sky-h) + 78px); }
    .lb-sun { border-width: 7px; }
    .lb-sun::after { inset: -7px; }
  }

  /* Sunniest-terraces leaderboard (see render_terrace_leaderboard_page.py) -
     one CARD per venue: its earned medal, its name, the year's total sun
     hours, and a 12-month "sun strip" - the shape of that venue's whole
     year (see sun_year_html() there), not just one total. Motion is one
     choreographed reveal (cards rise, medals settle, bars grow month by
     month, the hours count up) driven by leaderboard.js and gated behind
     the .lb-js class it's added to <html> by an inline script in <head> -
     without JS (or with prefers-reduced-motion, where the class is never
     added at all) everything below simply renders at its final state. */
  .leaderboard-jumpnav {
    font-size: 13px;
    color: var(--muted);
  }
  .leaderboard-jumpnav a {
    color: var(--sun-700);
    font-weight: 600;
    text-decoration: none;
  }
  .leaderboard-jumpnav a:hover { text-decoration: underline; }
  .leaderboard-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 12px 0 28px;
  }
  .leaderboard-row {
    position: relative;
    display: grid;
    grid-template-columns: 72px minmax(0, 1fr);
    column-gap: 16px;
    align-items: start;
    padding: 14px 16px 12px;
    border: 1px solid var(--card-border);
    border-radius: 16px;
    background: white;
    overflow: hidden;
  }
  /* Every row in the Overall list is a real winner (the closing
     coverage-note promises a badge to all of them, not just a top 3) -
     was `:nth-child(-n+3)`, which tinted only the first three rows
     regardless of the list, silently contradicting that copy. Scoped to
     `.leaderboard-list.overall` (see render_terrace_leaderboard_page.py's
     render_section()) so the per-category lists below it - which are
     ranked WITHIN a category, not "the winners" the copy refers to -
     stay untinted. */
  .leaderboard-list.overall .leaderboard-row { border-color: #F5D9B3; background: #FFFBF3; }
  /* The #1 card of every list (Overall and each category) is gold, to
     belong to its own gold 1st-place medal. Text on it steps darker than
     the site's usual muted/orange tones (#4F5D66 / #8A5A00 vs #62717A /
     #F2994A) - both measure well under AA on the gold surface, so the
     one card meant to stand out is never the hardest to read. */
  .leaderboard-list .leaderboard-row.is-first {
    --lb-muted: #4F5D66;
    border-color: #E2B24A;
    background: linear-gradient(135deg, #FFF7DC 0%, #FFE8A3 100%);
  }
  /* Every ranked venue's own earned medal (see badge_tier()/
     badge_img_html() in render_terrace_leaderboard_page.py) replaces
     what used to be a plain numbered rank circle - real, tier-accurate
     artwork (frontend/assets/badges/medal-<tier>-<category>.png) a venue
     can actually screenshot and use. The exact numeric rank is still on
     the element as a title tooltip, since a shared tier badge (e.g. "Top
     10") doesn't by itself say whether a venue is #4 or #9. */
  .leaderboard-badge-wrap { display: flex; }
  /* `.leaderboard-row .leaderboard-badge-mini`, not the bare class:
     `.content-page-inner img` (blog post images) puts a hairline border
     and 12px corners on every <img> in this page's content column, which
     was silently framing each medal in a faint rounded square - a medal
     is artwork with its own silhouette, not a photo to be boxed. */
  .leaderboard-row .leaderboard-badge-mini {
    display: block;
    width: 72px;
    height: 72px;
    border: 0;
    border-radius: 0;
  }
  .leaderboard-main { min-width: 0; }
  .leaderboard-headline {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
  }
  /* `a.leaderboard-name`, not `.leaderboard-name` - see the comment on
     .content-page-inner a above for why the type qualifier is load-
     bearing here, not stylistic: without it, that rule's higher
     specificity silently won regardless of source order. */
  a.leaderboard-name {
    min-width: 0;
    font-weight: 600;
    font-size: 17px;
    line-height: 1.25;
    color: var(--shade-900);
    text-decoration: none;
    /* Links to that one venue's own card on the homepage (see
       venue_deep_link() in render_terrace_leaderboard_page.py) - kept
       looking like plain text until hovered, same as .blog-post-card's
       own name-as-link treatment, rather than looking like a normal
       blue/underlined link the whole time. */
  }
  a.leaderboard-name:hover { text-decoration: underline; color: var(--sun-700); }
  .leaderboard-row.is-first a.leaderboard-name:hover { color: #8A5A00; }
  .leaderboard-hours {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    line-height: 1;
    color: var(--sun-700);
    font-variant-numeric: tabular-nums;
  }
  .leaderboard-row.is-first .leaderboard-hours { color: #8A5A00; }
  .lb-hours-num { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
  .lb-hours-unit {
    margin-top: 3px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--lb-muted, var(--muted));
  }
  .leaderboard-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px 12px;
    margin-top: 2px;
  }
  .leaderboard-type {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: 400;
    color: var(--lb-muted, var(--muted));
    font-size: 12px;
    white-space: nowrap;
  }
  a.leaderboard-claim-link {
    font-size: 12px;
    color: var(--lb-muted, var(--muted));
    text-decoration: underline;
    white-space: nowrap;
  }
  a.leaderboard-claim-link:hover { color: var(--sun-700); }
  .leaderboard-row.is-first a.leaderboard-claim-link:hover { color: #8A5A00; }

  /* The sun strip: 12 bars, one per calendar month, height = that month's
     sun hours against the tallest month on the WHOLE page (not each
     card's own peak) - so cards are honestly comparable at a glance: a
     lower-ranked venue's bars are visibly shorter, not just reshaped. The
     peak month is full-strength, the rest a tint of the same orange; while
     scrubbing, everything but the bar under the pointer dims. */
  .sun-year { margin: 12px 0 0; }
  .sun-year-bars {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 5px;
    height: 52px;
    align-items: end;
    border-bottom: 1px solid var(--card-border);
    touch-action: pan-y;
    cursor: crosshair;
  }
  .leaderboard-row.is-first .sun-year-bars { border-bottom-color: #E2B24A; }
  .sun-year-bar {
    display: block;
    height: calc(max(var(--h), 0.05) * 100%);
    border-radius: 4px 4px 1px 1px;
    background: var(--sun-500);
    opacity: 0.6;
    transform-origin: bottom;
    transition: opacity 0.18s ease;
  }
  .leaderboard-row.is-first .sun-year-bar { background: linear-gradient(to top, #B87A0A, #F0B93E); }
  .sun-year-bar.is-peak { opacity: 1; }
  .sun-year-bars.is-scrubbing .sun-year-bar { opacity: 0.3; }
  .sun-year-bars.is-scrubbing .sun-year-bar.is-active { opacity: 1; }
  .sun-year-months {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 5px;
    margin-top: 5px;
    font-size: 11px;
    line-height: 1;
    text-align: center;
    color: var(--lb-muted, var(--muted));
  }
  .sun-year-months span.is-active { color: var(--shade-900); font-weight: 700; }
  .sun-year-readout {
    margin-top: 8px;
    font-size: 12px;
    color: var(--lb-muted, var(--muted));
    font-variant-numeric: tabular-nums;
  }

  /* A venue's embedded badge links to its own row (#<list>-<rank>-<name>): the row it lands on gets a
     Sun Bright ring so the eye finds it, with a little room above for the page's top controls. */
  .leaderboard-row { scroll-margin-top: 72px; }
  .leaderboard-row:target { outline: 3px solid var(--sun-500); outline-offset: 3px; }

  /* "On this page" menu of the yearly ranking - the same rail as the terms page (see .lg-toc): a sticky
     column with a sun that slides to what you're reading on wide screens, a collapsed "On this page" line
     on narrow ones. Each list (Overall, Cafes, ...) with its venues under it, every one a link to its row;
     leaderboard.js opens only the list being read, so 90 names never show at once. */
  .content-page.leaderboard-page { max-width: 1000px; }
  .lb-body { min-width: 0; max-width: 640px; }
  .lb-body > h3 { scroll-margin-top: 72px; }
  /* "Out of 142 cafes considered", right under a list's heading. */
  .content-page-inner p.lb-count { margin: -6px 0 12px; font-size: 13px; line-height: 1.4; color: var(--lb-muted, var(--muted)); }
  .content-page-inner p.lb-count b { color: var(--ink); }
  .lb-toc { margin: 0 0 24px; }
  .lb-toc-details > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid #F5D9B3;
    border-radius: 12px;
    background: #FFFBF3;
    color: var(--shade-900);
    font-size: 14px;
    font-weight: 600;
    list-style: none;
    cursor: pointer;
  }
  .lb-toc-details > summary::-webkit-details-marker { display: none; }
  .lb-toc-details > summary:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 2px; }
  .lb-toc-current {
    min-width: 0;
    overflow: hidden;
    font-weight: 400;
    color: #4F5D66;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .lb-toc-list { position: relative; margin: 8px 0 0; padding: 0; list-style: none; max-height: 60vh; overflow-y: auto; }
  .lb-toc-venues { margin: 0; padding: 0; list-style: none; }
  .content-page-inner .lb-toc li { margin: 0; font-size: 14px; }
  .content-page-inner .lb-toc a {
    display: flex;
    align-items: baseline;
    gap: 8px;
    color: var(--ink);
    text-decoration: none;
  }
  .content-page-inner .lb-toc a:hover { color: var(--shade-700); text-decoration: underline; }
  .content-page-inner .lb-toc a:focus-visible { outline: 2px solid var(--sun-500); outline-offset: -2px; border-radius: 6px; }
  .content-page-inner .lb-toc a.lb-toc-cat {
    gap: 6px;
    padding: 8px 12px;
    color: var(--shade-900);
    font-size: 14px;
    font-weight: 700;
    line-height: 1.35;
  }
  .content-page-inner .lb-toc-venues a { padding: 5px 12px 5px 14px; font-size: 13px; line-height: 1.35; }
  .lb-toc-rank { flex: none; min-width: 1.7em; color: var(--muted); font-variant-numeric: tabular-nums; text-align: right; }
  .lb-toc-name { min-width: 0; overflow-wrap: anywhere; }
  .content-page-inner .lb-toc a.is-active { color: var(--shade-900); font-weight: 700; }
  .content-page-inner .lb-toc a.is-active .lb-toc-rank { color: var(--shade-900); }
  .lb-toc.is-collapsible .lb-toc-group:not(.is-current) > .lb-toc-venues { display: none; }
  .content-page-inner .lb-toc-list li.lb-toc-sun { display: none; }
  @media (min-width: 900px) {
    .lb-layout {
      display: grid;
      grid-template-columns: 220px minmax(0, 640px);
      column-gap: 56px;
      align-items: start;
    }
    .lb-toc {
      position: sticky;
      top: 64px;
      margin: 0;
      max-height: calc(100vh - 84px);
      overflow-y: auto;
      overscroll-behavior: contain;
      scrollbar-width: thin;
    }
    .lb-toc-details > summary { display: none; }
    .lb-toc-list { margin: 0; padding-left: 20px; max-height: none; overflow: visible; }
    .lb-toc-list::before { content: ""; position: absolute; left: 5px; top: 0; bottom: 0; width: 1px; background: #F5D9B3; }
    .content-page-inner .lb-toc-list li.lb-toc-sun {
      display: block;
      position: absolute;
      left: 0;
      top: 0;
      box-sizing: border-box;
      width: 11px;
      height: 11px;
      margin: -5.5px 0 0;
      border: 3px solid var(--sun-300);
      border-radius: 50%;
      background: var(--sun-500);
      transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .lb-toc-sun { transition: none !important; }
  }

  /* Reveal choreography (only ever applied once .lb-js is on <html>, so a
     JS failure or a JS-less visitor sees every card at its final state).
     Exponential ease-out throughout - nothing linear, nothing bouncy. */
  .lb-js .leaderboard-row { opacity: 0; transform: translateY(14px); }
  .lb-js .leaderboard-row.is-in {
    opacity: 1;
    transform: none;
    transition: opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: var(--row-delay, 0ms);
  }
  .lb-js .leaderboard-badge-mini { transform: scale(0.86); }
  .lb-js .leaderboard-row.is-in .leaderboard-badge-mini {
    transform: none;
    transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: calc(var(--row-delay, 0ms) + 120ms);
  }
  .lb-js .sun-year-bar { transform: scaleY(0); }
  .lb-js .leaderboard-row.is-in .sun-year-bar {
    transform: scaleY(1);
    transition: transform 0.85s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.18s ease;
    transition-delay: calc(var(--row-delay, 0ms) + 220ms + var(--i) * 45ms), 0ms;
  }
  /* One pass of light across the gold card as it lands - the gold card
     only, so the winner is the single thing on the page that catches it. */
  .leaderboard-row.is-first::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, 0.6) 50%, transparent 62%);
    transform: translateX(-130%);
  }
  .lb-js .leaderboard-row.is-first.is-in::after {
    animation: lb-sheen 1.5s cubic-bezier(0.16, 1, 0.3, 1) 0.55s 1 both;
  }
  @keyframes lb-sheen { to { transform: translateX(130%); } }
  @media (max-width: 480px) {
    .leaderboard-row { grid-template-columns: 56px minmax(0, 1fr); column-gap: 12px; padding: 12px; }
    .leaderboard-row .leaderboard-badge-mini { width: 56px; height: 56px; }
    a.leaderboard-name { font-size: 15px; }
    .lb-hours-num { font-size: 20px; }
    .sun-year-bars, .sun-year-months { gap: 3px; }
    .sun-year-bars { height: 44px; }
  }

  .contact-form {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 20px;
  }
  .contact-form label {
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    margin-top: 10px;
  }
  .contact-form input,
  .contact-form textarea {
    padding: 10px 12px;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    font-size: 14px;
    font-family: inherit;
    color: var(--ink);
    background: white;
    resize: vertical;
  }
  .contact-form input:focus,
  .contact-form textarea:focus {
    outline: 2px solid var(--sun-500);
  }
  .contact-form button {
    margin-top: 14px;
    padding: 12px 16px;
    border-radius: 12px;
    border: none;
    background: var(--shade-900);
    color: white;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
  }
  .contact-form button:hover { background: var(--shade-700); }
  .contact-form button:disabled { opacity: 0.6; cursor: not-allowed; }
  .contact-status {
    font-size: 13px;
    margin-top: 10px;
  }
  .contact-status.success { color: #1e8449; }
  .contact-status.error { color: #B03A2E; }
  .contact-response-time { font-size: 12px; color: var(--muted); margin: 4px 0 0; }
  .contact-direct-email-note { font-size: 12px; color: var(--muted); margin: 6px 0 0; }
  .contact-direct-email-note a { color: var(--sun-700); }
  /* Inline replacement for the browser's own native validation bubble
     ("Please fill out this field") - unstyled, not localized, and
     visibly off-system against this page's rounded/warm inputs. The
     form itself carries `novalidate` (see contact.html's own script) so
     the native bubble never appears at all; this renders in its place,
     right under the field it belongs to. Empty and non-interactive when
     a field is valid - no reserved height, so it doesn't push the next
     label down until there's actually something to say. */
  .field-error {
    display: block;
    font-size: 12px;
    color: #B03A2E;
    margin-top: 3px;
  }
  .field-error:empty { display: none; }
  .contact-form input.invalid,
  .contact-form textarea.invalid {
    border-color: #B03A2E;
  }
  /* The claim flow's locked venue identity - see contact.html's own
     script for when this shows. Deliberately not part of the editable
     textarea: an owner correcting/adding context to their claim request
     could otherwise edit away the one piece of text (the venue name)
     the human operator on the other end actually needs to process it -
     see main.py's own claim-flow docstring for why that operator step
     is manual in the first place. */
  .claim-chip {
    display: flex;
    align-items: center;
    gap: 8px;
    background: #FFFBF3;
    border: 1px solid #F5D9B3;
    border-radius: 12px;
    padding: 10px 14px;
    margin: 16px 0 4px;
  }
  .claim-chip-label {
    /* 11px, not the app's usual 9px eyebrow-label size - this one is
       functional text a claimant actually has to read to confirm which
       venue they're claiming, not a decorative caption. */
    font-size: 11px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .claim-chip-name { font-size: 15px; font-weight: 700; color: var(--shade-900); }

  /* Contact page header: the leaderboard's skyline-and-sun (.lb-hero above)
     with one difference - nobody grabs the sun here, the form moves it.
     contact.html's updateSun() sets --s (0 = the shade covers the sky, 1 =
     clear) from how many required fields are valid; --s is a registered
     property (see @property --s above), so it eases between steps. Sending
     pushes the sun to the top of its arc, clears the shade and lights the
     windows. With no JS the panel just shows the leaderboard's resting sky. */
  .ct-hero, .bl-hero, .bl-end, .lg-hero { --arch: 50px; padding-bottom: calc(var(--sky-h) + 76px); }
  .ct-js .ct-hero { --s: 0.14; transition: --s 1.1s cubic-bezier(0.16, 1, 0.3, 1); }
  .ct-hero .lb-sun { cursor: default; pointer-events: none; touch-action: auto; }
  .ct-hero .lb-hero-shade { transition: opacity 1s ease 0.3s; }
  .ct-hero.is-sent .lb-hero-shade { opacity: 0; }
  .ct-hero .lb-skyline .w { transition: fill 0.9s ease 0.4s; }
  .ct-hero.is-sent .lb-skyline .w { fill: color-mix(in srgb, var(--sun-300) 62%, transparent); }
  /* The claim chip, promoted to the venue's sign on the roofline. */
  .ct-sign {
    position: absolute;
    z-index: 6;
    right: 14px;
    bottom: calc(var(--sky-h) - 8px);
    margin: 0;
    padding: 8px 14px;
  }
  .content-page-inner .ct-hero .lb-hero-title { max-width: 12em; }
  @media (max-width: 560px) {
    /* Shorter panel than the leaderboard's - people arrive here to write. */
    .ct-hero, .bl-hero, .bl-end, .lg-hero { --sky-h: 56px; --arch: 34px; padding-bottom: calc(var(--sky-h) + 54px); }
    .ct-sign { position: relative; z-index: 4; right: auto; bottom: auto; width: fit-content; margin-top: 12px; }
  }
  @media (prefers-reduced-motion: reduce) {
    .ct-hero, .ct-hero * { transition: none !important; }
  }
  .contact-form[hidden] { display: none; }
  .ct-sent {
    margin-top: 20px;
    padding: 20px 20px 18px;
    border: 1px solid #F5D9B3;
    border-radius: 16px;
    background: #FFFBF3;
  }
  .ct-sent[hidden] { display: none; }
  .content-page-inner p.ct-sent-title {
    margin: 0 0 16px;
    font-size: 18px;
    line-height: 1.35;
    font-weight: 700;
    color: var(--shade-900);
    outline: none;
  }
  .ct-steps { list-style: none; margin: 0 0 6px; padding: 0; }
  .content-page-inner .ct-steps li {
    position: relative;
    margin: 0;
    padding: 0 0 16px 28px;
    font-size: 14px;
    line-height: 1.5;
  }
  .ct-steps li:last-child { padding-bottom: 0; }
  .ct-steps li[hidden] { display: none; }
  .ct-steps li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.3em;
    box-sizing: border-box;
    width: 11px;
    height: 11px;
    border: 2px solid var(--sun-300);
    border-radius: 50%;
    background: #FFFBF3;
  }
  .ct-steps li.is-done::before { background: var(--sun-500); border-color: var(--sun-500); }
  .ct-steps li:not(:last-child)::after {
    content: "";
    position: absolute;
    left: 5px;
    top: calc(0.3em + 13px);
    bottom: 2px;
    width: 1px;
    background: #F5D9B3;
  }
  .ct-steps li:has(+ li[hidden])::after { display: none; }
  .ct-again {
    margin-top: 16px;
    padding: 10px 16px;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    background: white;
    color: var(--shade-900);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
  }
  .ct-again:hover { background: var(--cream); }
  .ct-again:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 2px; }
  @media (prefers-reduced-motion: no-preference) {
    .ct-sent:not([hidden]) { animation: ct-rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) both; }
    .ct-sent:not([hidden]) .ct-steps li { animation: ct-rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) both; animation-delay: calc(0.35s + var(--i) * 0.14s); }
    @keyframes ct-rise { from { opacity: 0; transform: translateY(10px); } }
  }

  /* ---- Homepage day scrubber (see day-scrub.js) -------------------------
     A slim panel above the list: a ribbon of how many listed spots are
     sunny (and open) at every moment of the day, with a sun you drag along
     it. Flat, hairline-bordered like the other warm panels; the sun is the
     site's one sun (Sun Bright core on a Sun Pale ring). Hidden until there
     is data to scrub. */
  .day-scrub { max-width: 640px; margin: 12px auto 0; padding: 0 20px; }
  .day-scrub[hidden] { display: none; }
  .ds-panel { padding: 10px 14px 6px; border: 1px solid #F5D9B3; border-radius: 14px; background: #FFFBF3; }
  .day-scrub.is-live .ds-panel { animation: ds-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) both; }
  @keyframes ds-in { from { opacity: 0; transform: translateY(8px); } }
  .ds-head { display: flex; align-items: baseline; gap: 10px; }
  .ds-time { min-width: 3.1em; font-size: 20px; font-weight: 700; line-height: 1; color: var(--shade-900); font-variant-numeric: tabular-nums; }
  .ds-count { font-size: 13px; font-weight: 600; color: var(--ink); }
  .ds-caption { margin-left: auto; font-size: 12px; color: var(--muted); text-align: right; }
  .ds-track { position: relative; height: 56px; margin-top: 8px; padding: 0 11px; cursor: grab; touch-action: pan-y; user-select: none; }
  .day-scrub.is-dragging .ds-track { cursor: grabbing; }
  .ds-rail { --f: 0.5; position: relative; height: 100%; }
  .ds-bars { position: absolute; left: 0; right: 0; bottom: 0; height: 36px; display: grid; grid-template-columns: repeat(68, minmax(0, 1fr)); gap: 1px; align-items: end; }
  .ds-bar {
    display: block;
    height: 100%;
    border-radius: 2px 2px 0 0;
    background: var(--sun-500);
    opacity: 0.85;
    transform-origin: bottom;
    transform: scaleY(var(--h, 0));
    transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i, 0) * 8ms), opacity 0.3s ease;
  }
  .ds-bar.is-past { opacity: 0.26; }
  .ds-now { position: absolute; bottom: 0; width: 1px; height: 40px; background: color-mix(in srgb, var(--shade-900) 38%, transparent); pointer-events: none; }
  .ds-now[hidden] { display: none; }
  .ds-guide { position: absolute; left: calc(var(--f) * 100%); top: 22px; bottom: 0; width: 1px; background: var(--sun-500); opacity: 0.55; pointer-events: none; }
  .ds-sun {
    position: absolute;
    left: calc(var(--f) * 100%);
    top: 12px;
    box-sizing: border-box;
    width: 22px;
    height: 22px;
    border: 4px solid var(--sun-300);
    border-radius: 50%;
    background: var(--sun-500);
    transform: translate(-50%, -50%);
    cursor: inherit;
    touch-action: none;
  }
  .ds-rail.is-gliding .ds-sun, .ds-rail.is-gliding .ds-guide { transition: left 0.9s cubic-bezier(0.16, 1, 0.3, 1); }
  .ds-sun:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 4px; }
  .ds-sun::after { content: ""; position: absolute; inset: -4px; border: 2px solid var(--sun-500); border-radius: 50%; opacity: 0; pointer-events: none; }
  @media (prefers-reduced-motion: no-preference) {
    .day-scrub.is-live:not(.is-touched) .ds-sun::after { animation: lb-sun-ping 1.5s cubic-bezier(0.16, 1, 0.3, 1) 1.4s 2 both; }
  }
  /* Whole-day mode: no single moment is being viewed, so the sun is hollow
     and the readout quiet until it's grabbed. */
  .day-scrub.is-parked .ds-sun { background: transparent; }
  .day-scrub.is-parked .ds-guide { opacity: 0.2; }
  .day-scrub.is-parked .ds-time, .day-scrub.is-parked .ds-count { visibility: hidden; }
  .ds-ticks { position: relative; height: 16px; margin: 2px 11px 0; font-size: 11px; line-height: 16px; color: var(--muted); font-variant-numeric: tabular-nums; }
  .ds-ticks span { position: absolute; transform: translateX(-50%); }
  .ds-ticks span:nth-child(1) { left: 5.88%; }
  .ds-ticks span:nth-child(2) { left: 23.53%; }
  .ds-ticks span:nth-child(3) { left: 41.18%; }
  .ds-ticks span:nth-child(4) { left: 58.82%; }
  .ds-ticks span:nth-child(5) { left: 76.47%; }
  .ds-ticks span:nth-child(6) { left: 94.12%; }
  @media (max-width: 560px) {
    .ds-caption { display: none; }
    .ds-track { height: 48px; }
    .ds-bars { height: 30px; }
    .ds-now { height: 34px; }
  }
  @media (prefers-reduced-motion: reduce) {
    .day-scrub *, .day-scrub { transition: none !important; animation: none !important; }
  }

  /* ---- Blog ------------------------------------------------------------
     Index (blog/index.html): the skyline-and-sun header panel (.lb-hero, with
     the sun-rides-in-a-band rule shared with the contact page above), the
     newest post as a large featured card and the rest in a two-column grid.
     Cards keep the exact markup generate_blog_post.py emits and edits by
     regex (<a class="blog-post-card"> holding img/h3/p); everything below is
     styling on top of it. Posts: see .bl-sky / .bl-end (blog.js). Scroll
     reveals are CSS scroll-driven animations - no JS, and browsers without
     them (or with reduced motion) just show the finished page. */
  .content-page.blog-index { max-width: 900px; }
  .bl-hero { --s: 0.76; }
  @media (prefers-reduced-motion: no-preference) {
    .bl-js .bl-hero { animation: lb-sunrise 3.4s cubic-bezier(0.22, 1, 0.36, 1) 0.2s both; }
  }
  .bl-hero .lb-sun, .bl-end .lb-sun { cursor: default; pointer-events: none; touch-action: auto; }
  .blog-index .blog-post-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    margin-top: 0;
  }
  .blog-index .content-page-inner a.blog-post-card {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    border-radius: 16px;
    background: white;
  }
  .blog-index .content-page-inner a.blog-post-card:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 3px; }
  .content-page-inner a.blog-post-card .blog-card-img {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border: 0;
    border-radius: 0;
    background: #FFE5B2;
    transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .blog-index .blog-post-card h3 { margin: 16px 18px 6px; font-size: 17px; line-height: 1.3; }
  .blog-index .blog-post-card p { margin: 0 18px 18px; color: var(--lb-hero-muted, #4F5D66); }
  .blog-index .blog-post-card .blog-card-date { display: block; margin-bottom: 4px; font-size: 12px; letter-spacing: 0.02em; color: var(--muted); }
  /* The sun that rises over a card's image on hover / focus. */
  .content-page-inner a.blog-post-card::after {
    content: "";
    position: absolute;
    top: 14px;
    left: 14px;
    box-sizing: border-box;
    width: 30px;
    height: 30px;
    border: 6px solid var(--sun-300);
    border-radius: 50%;
    background: var(--sun-500);
    opacity: 0;
    transform: translateY(44px);
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease;
    pointer-events: none;
  }
  .content-page-inner a.blog-post-card:hover::after,
  .content-page-inner a.blog-post-card:focus-visible::after { opacity: 1; transform: none; }
  .content-page-inner a.blog-post-card:hover .blog-card-img,
  .content-page-inner a.blog-post-card:focus-visible .blog-card-img { transform: scale(1.04); }
  /* The newest post: full width, image beside the text. */
  .blog-index .content-page-inner a.blog-post-card:first-child { grid-column: 1 / -1; }
  @media (min-width: 720px) {
    .blog-index .content-page-inner a.blog-post-card:first-child:has(.blog-card-img) {
      display: grid;
      grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
      grid-template-rows: 1fr 1fr;
    }
    .blog-index a.blog-post-card:first-child .blog-card-img { grid-column: 1; grid-row: 1 / span 2; height: 100%; aspect-ratio: auto; min-height: 280px; }
    .blog-index .blog-post-card:first-child h3 { grid-column: 2; grid-row: 1; align-self: end; margin: 24px 24px 8px; font-size: 25px; line-height: 1.2; letter-spacing: -0.01em; text-wrap: balance; }
    .blog-index .blog-post-card:first-child p { grid-column: 2; grid-row: 2; align-self: start; margin: 0 24px 24px; font-size: 15px; line-height: 1.6; }
  }
  @media (max-width: 719px) {
    .blog-index .blog-post-list { grid-template-columns: minmax(0, 1fr); }
    .blog-index .blog-post-card:first-child h3 { font-size: 21px; }
  }
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .blog-index a.blog-post-card { animation: bl-rise linear both; animation-timeline: view(); animation-range: entry 0% entry 40%; }
      .blog-post .content-page-inner img { animation: bl-rise linear both; animation-timeline: view(); animation-range: entry 0% entry 45%; }
      .bl-end { animation: bl-sunset linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
    }
  }
  @keyframes bl-rise { from { opacity: 0; transform: translateY(26px); } }
  @keyframes bl-sunset { from { --s: 0.3; } }

  /* A post (and an index card) holds its text in both languages, each in an element
     marked class="bl-lang" lang="en|nl"; the one that doesn't match <html lang> is
     hidden. shared.js sets <html lang> from the EN/NL toggle, and an inline script in
     each page's <head> sets it before first paint from the same stored choice.
     Content with no bl-lang (a post that exists in one language only) always shows. */
  html[lang="nl"] .bl-lang[lang="en"],
  html:not([lang="nl"]) .bl-lang[lang="nl"] { display: none !important; }

  /* Posts: comfortable long-form measure, and the reading-sun bar. */
  .blog-post .content-page-inner h2 {
    margin: 0 0 10px;
    font-size: clamp(27px, 5.4vw, 36px);
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .blog-post .content-page-inner h3 { margin: 32px 0 10px; font-size: 22px; line-height: 1.25; }
  .blog-post .content-page-inner p,
  .blog-post .content-page-inner li { font-size: 16px; line-height: 1.7; }
  .blog-post .content-page-inner p { margin: 0 0 14px; }
  .blog-post .content-page-inner img { margin: 22px 0; }
  .bl-sky { display: none; }
  .bl-js .bl-sky {
    --p: 0;
    display: block;
    position: sticky;
    top: 0;
    z-index: 30;
    height: 40px;
    overflow: hidden;
    border-bottom: 1px solid #F5D9B3;
    background: linear-gradient(180deg, #FFF6E5 0%, #FFE5B2 100%);
  }
  .bl-sky-shade {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--shade-900) 13%, transparent);
    /* Everything ahead of the sun - what's still to read - is in shade. */
    clip-path: polygon(
      calc(12px + var(--p) * (100% - 24px) + 14px) 0, 100% 0, 100% 100%, calc(12px + var(--p) * (100% - 24px) - 10px) 100%);
  }
  .bl-sky-sun {
    position: absolute;
    left: calc(12px + var(--p) * (100% - 24px));
    top: calc(22px - 7px * 4 * var(--p) * (1 - var(--p)));
    box-sizing: border-box;
    width: 22px;
    height: 22px;
    border: 4px solid var(--sun-300);
    border-radius: 50%;
    background: var(--sun-500);
    transform: translate(-50%, -50%);
  }
  .bl-sky-inner {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    max-width: 640px;
    height: 100%;
    margin: 0 auto;
    padding: 0 38px 0 20px; /* clear of the sun when it reaches the right edge */
  }
  .bl-sky-label { font-size: 12px; font-weight: 600; color: var(--shade-900); font-variant-numeric: tabular-nums; }
  /* The closing block: the same skyline, late in the day, with the other posts. */
  .bl-end { --s: 0.94; margin-top: 44px; }
  .content-page-inner .bl-end .lb-hero-title { margin: 0 0 12px; font-size: clamp(24px, 5vw, 30px); }
  .content-page-inner .bl-more { list-style: none; margin: 0 0 14px; padding: 0; max-width: 26em; }
  .content-page-inner .bl-more:empty { display: none; }
  .content-page-inner .bl-more li { margin: 0 0 8px; font-size: 15px; line-height: 1.4; }
  .content-page-inner .bl-more a { color: var(--shade-900); font-weight: 600; text-decoration: underline; text-decoration-color: var(--sun-500); text-underline-offset: 3px; }
  .content-page-inner .bl-more a:hover { color: var(--shade-700); }
  .content-page-inner a.bl-all {
    display: inline-block;
    padding: 8px 16px;
    border-radius: 999px;
    background: var(--shade-900);
    color: var(--cream);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
  }
  .content-page-inner a.bl-all:hover { background: var(--shade-700); color: var(--cream); }

  /* ---- Privacy and terms (see legal.js) -----------------------------------
     A long legal text made easy to move around in, without touching a word
     of it: the skyline-and-sun header panel, a section index (a sticky rail
     on wide screens with a sun that slides to the section being read, a
     collapsed "On this page" menu on narrow ones), a copy-link anchor beside
     every heading, and - privacy page only - a live consent switch. The
     reading-sun bar is the blog's (.bl-sky). Every enhancement is layered on
     the plain section markup, so the text reads fine without any of it. */
  .content-page.legal-page { max-width: 1000px; }
  .lg-hero { --s: 0.76; }
  @media (prefers-reduced-motion: no-preference) {
    .bl-js .lg-hero { animation: lb-sunrise 3.4s cubic-bezier(0.22, 1, 0.36, 1) 0.2s both; }
  }
  .lg-hero .lb-sun { cursor: default; pointer-events: none; touch-action: auto; }
  .lg-toc[hidden] { display: none; }
  .lg-body { min-width: 0; max-width: 640px; }
  .lg-section { position: relative; scroll-margin-top: 64px; padding-bottom: 6px; outline: none; }
  .lg-section + .lg-section { margin-top: 26px; }
  .content-page-inner .lg-section h3 { margin: 0 0 10px; font-size: 22px; line-height: 1.25; }
  .content-page-inner .lg-section p { margin: 0; font-size: 16px; line-height: 1.7; }
  .lg-toc { margin: 0 0 24px; }
  .lg-toc-details > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid #F5D9B3;
    border-radius: 12px;
    background: #FFFBF3;
    color: var(--shade-900);
    font-size: 14px;
    font-weight: 600;
    list-style: none;
    cursor: pointer;
  }
  .lg-toc-details > summary::-webkit-details-marker { display: none; }
  .lg-toc-details > summary:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 2px; }
  .lg-toc-current {
    min-width: 0;
    overflow: hidden;
    font-weight: 400;
    color: #4F5D66;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .lg-toc-list { position: relative; margin: 8px 0 0; padding: 0; list-style: none; }
  .content-page-inner .lg-toc-list li { margin: 0; font-size: 14px; }
  .lg-toc-list a {
    display: block;
    padding: 8px 12px;
    color: var(--ink);
    font-size: 14px;
    line-height: 1.35;
    text-decoration: none;
  }
  .content-page-inner .lg-toc-list a { color: var(--ink); }
  .content-page-inner .lg-toc-list a:hover { color: var(--shade-700); text-decoration: underline; }
  .content-page-inner .lg-toc-list a.is-active { color: var(--shade-900); font-weight: 700; }
  .lg-toc-list a:focus-visible { outline: 2px solid var(--sun-500); outline-offset: -2px; border-radius: 6px; }
  .content-page-inner .lg-toc-list li.lg-toc-sun { display: none; }
  .lg-anchor {
    position: absolute;
    top: 2px;
    right: 0;
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
    opacity: 0.6;
    cursor: pointer;
    transition: opacity 0.2s ease, color 0.2s ease;
  }
  .lg-anchor:hover { color: var(--shade-900); opacity: 1; }
  .lg-anchor:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 2px; opacity: 1; }
  .lg-copied {
    position: absolute;
    top: -22px;
    right: 0;
    z-index: 2;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--shade-900);
    color: var(--cream);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 0.2s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
    pointer-events: none;
  }
  .lg-copied.is-on { opacity: 1; transform: none; }
  @media (min-width: 900px) {
    .lg-layout:has(> .lg-toc:not([hidden])) {
      display: grid;
      grid-template-columns: 220px minmax(0, 640px);
      column-gap: 56px;
      align-items: start;
    }
    .lg-toc { position: sticky; top: 64px; margin: 0; }
    .lg-toc-details > summary { display: none; }
    .lg-toc-list { margin: 0; padding-left: 20px; }
    .lg-toc-list::before { content: ""; position: absolute; left: 5px; top: 0; bottom: 0; width: 1px; background: #F5D9B3; }
    .content-page-inner .lg-toc-list li.lg-toc-sun {
      display: block;
      position: absolute;
      left: 0;
      top: 0;
      box-sizing: border-box;
      width: 11px;
      height: 11px;
      margin: -5.5px 0 0;
      border: 3px solid var(--sun-300);
      border-radius: 50%;
      background: var(--sun-500);
      transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
    }
    .lg-anchor { right: auto; left: -34px; opacity: 0; }
    .lg-section:hover .lg-anchor, .lg-section:focus-within .lg-anchor { opacity: 1; }
    .lg-copied { right: auto; left: -34px; }
  }
  @media (prefers-reduced-motion: reduce) {
    .lg-toc-sun, .lg-copied, .lg-anchor { transition: none !important; }
  }
  /* Privacy page: the live analytics choice, bound to the cookie banner's consent. */
  .lg-consent {
    margin-top: 16px;
    padding: 16px 18px;
    border: 1px solid #F5D9B3;
    border-radius: 14px;
    background: #FFFBF3;
  }
  .lg-consent-title { margin-bottom: 10px; font-size: 13px; font-weight: 700; color: var(--shade-900); }
  .lg-consent-toggle { font-size: 15px; color: var(--ink); }
  .lg-consent-status { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 14px; color: #4F5D66; }
  .lg-consent-dot { flex-shrink: 0; box-sizing: border-box; width: 10px; height: 10px; border-radius: 50%; background: #55606B; transition: background 0.3s ease; }
  .lg-consent-status[data-choice="accepted"] .lg-consent-dot { background: var(--sun-500); }
  .lg-consent-status[data-choice="none"] .lg-consent-dot { background: transparent; border: 2px solid var(--sun-300); }

  /* Cookie consent banner - deliberately NOT a full-screen modal/overlay
     (no "cookie wall" - Dutch AP guidance is explicit that access to a
     site can't be blocked pending a cookie choice), just a fixed bar at
     the bottom that sits above the rest of the page without covering or
     disabling it. Both buttons share identical size/weight/border-radius
     - only the color differs - specifically so neither reads as more
     prominent or more "the right choice" than the other (equal button
     prominence is one of the AP's own explicit banner requirements). */
  .cookie-consent-banner {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 500;
    background: var(--cream);
    border-top: 1px solid var(--header-bg);
    box-shadow: 0 -4px 16px rgba(0,0,0,0.08);
    padding: 16px 20px;
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
  }
  .cookie-consent-text {
    flex: 1 1 320px;
    margin: 0;
    font-size: 13px;
    color: var(--shade-900);
    line-height: 1.5;
  }
  .cookie-consent-actions {
    display: flex;
    gap: 10px;
    flex: 0 0 auto;
  }
  .cookie-consent-btn {
    padding: 10px 20px;
    min-width: 110px; /* explicit equal width, not just "naturally similar" -
       "Reject" and "Accept" render a few px apart at identical padding
       purely from font-rendering, this removes any ambiguity about
       whether the two are genuinely equally prominent */
    border-radius: 10px;
    border: none;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
  }
  .cookie-consent-reject { background: var(--shade-900); color: white; }
  .cookie-consent-reject:hover { background: var(--shade-700); }
  /* --sun-700, not --sun-600: white text on #E67E22 measures 2.85:1,
     under the 4.5:1 real button text needs - and this button is shown to
     every new visitor on every page. */
  .cookie-consent-accept { background: var(--sun-700); color: white; }
  .cookie-consent-accept:hover { opacity: 0.9; }
  @media (max-width: 480px) {
    .cookie-consent-banner { padding: 14px 16px; }
    .cookie-consent-actions { width: 100%; }
    .cookie-consent-btn { flex: 1; } /* full-width, equal-width buttons on mobile too */
  }

  /* Auth floater - a single, shared sign-in prompt repositioned via JS
     (see openAuthFloater() in shared.js) to appear near whichever
     trigger was actually clicked - the header's own login icon, or any
     venue card's favorite heart. position:fixed + JS-set top/left,
     same reasoning as .cookie-consent-banner above using fixed
     positioning, but at an explicit point rather than pinned to a
     screen edge. z-index above .nav-menu-dropdown's own 1110 (see that
     rule's own comment on why it needs to clear Leaflet at all) - this
     can be triggered from anywhere on the page, including over the map
     view, and should never end up hidden behind it. */
  .auth-floater {
    position: fixed;
    z-index: 1120;
    width: 260px;
    background: white;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    padding: 14px;
  }
  .auth-floater-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--shade-900);
    margin-bottom: 10px;
    line-height: 1.4;
    text-align: center;
  }
  #googleSignInContainer { display: flex; justify-content: center; }
  .auth-floater-terms {
    margin-top: 10px;
    font-size: 11px;
    line-height: 1.4;
    color: var(--muted);
    text-align: center;
  }
  .auth-floater-terms a { color: var(--muted); text-decoration: underline; }
  .auth-floater-terms a:hover { color: var(--sun-700); }

  /* Homepage footer - small, deliberately understated, since these are
     legal/settings links a visitor occasionally needs, not something
     that should compete for attention with the app itself. Moved out of
     the hamburger menu specifically because it was cluttering a menu
     meant for actual page navigation. */
  /* What LumoSpot is (index.html), above the popular places: real text for search engines, kept quiet for people */
  .home-about { max-width: 640px; margin: 32px auto 0; padding: 0 20px; text-align: center; }
  .home-about-title { margin: 0 0 8px; font-size: 17px; font-weight: 700; color: var(--shade-900); }
  .home-about p { margin: 0 0 8px; font-size: 14px; line-height: 1.6; color: var(--muted); }
  /* Popular places above the footer (index.html) - quiet: muted, small, centred like the footer */
  .home-places { max-width: 720px; margin: 8px auto 0; padding: 18px 20px 0; text-align: center; }
  .home-places-title { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--muted); }
  .home-places-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 14px; font-size: 13px; }
  .home-places-links a { color: var(--shade-700); text-decoration: none; padding: 4px 0; }
  .home-places-links a:hover { text-decoration: underline; }
  .home-places-links a.home-places-all { color: var(--muted); text-decoration: underline; }
  .home-footer {
    text-align: center;
    padding: 24px 20px 16px;
    font-size: 12px;
    color: var(--muted);
  }
  .home-footer-link {
    background: none;
    border: none;
    padding: 0;
    font-size: 12px;
    color: var(--muted);
    text-decoration: underline;
    cursor: pointer;
  }
  .home-footer-link:hover { color: var(--shade-900); }
  .home-footer-separator { margin: 0 8px; color: var(--muted); }
  .home-footer-lang { display: inline-flex; align-items: center; gap: 4px; }
  .home-footer-lang-sep { color: var(--muted); }
  .home-footer-lang-btn {
    background: none;
    border: none;
    padding: 0;
    font-size: 12px;
    color: var(--muted);
    text-decoration: underline;
    cursor: pointer;
  }
  .home-footer-lang-btn:hover { color: var(--shade-900); }
  .home-footer-lang-btn.active {
    color: var(--shade-900);
    font-weight: 700;
    text-decoration: none;
    cursor: default;
  }

  /* Desktop: filters live as a permanent left-hand sidebar instead of an
     overlay drawer, and the center column widens to actually use the
     freed-up space - below this width there isn't room for a 280px
     sidebar plus a content column at least as wide as the mobile one
     (640px) without the two fighting each other, so the drawer/toggle
     behavior below stays exactly as it was. Grid (not flex/order) so
     .filters-drawer can sit visually first without moving it in the DOM
     - it's still reached after the results in tab order, same tradeoff
     the leaderboard/legal pages' own sticky rail makes (see .lb-toc).
     Placed at the end of the file (not near .home-layout's own markup
     above) so these overrides land after every base rule they touch
     (.feed, .status-line, etc. are each declared once, later, elsewhere
     in this file) - same cascade-order requirement as any other override
     here, media query or not. */
  @media (min-width: 1060px) {
    .home-layout {
      display: grid;
      grid-template-columns: 280px minmax(640px, 1fr);
      grid-template-areas: "sidebar main";
      column-gap: 24px;
      /* No align-items here (default stretch): .content-column's white
         background should reach the bottom of the page even with an
         empty/short result set, the way the single mobile column always
         has via its own flex:1 on #homeView. flex:1 below gives this grid
         container that same full-height sizing to stretch into;
         .filters-drawer opts back out with its own align-self so it
         keeps its natural (sticky, capped) height instead of stretching
         to match. */
      flex: 1;
      max-width: 1160px;
      margin: 0 auto;
      width: 100%;
      padding: 0 24px;
    }
    .filters-toggle-btn { display: none; }
    .filters-backdrop { display: none; }
    .filters-drawer {
      grid-area: sidebar;
      align-self: start;
      position: sticky;
      top: 88px;
      left: auto;
      height: auto;
      max-height: calc(100vh - 108px);
      width: auto;
      background: white;
      border: 1px solid var(--card-border);
      border-radius: 16px;
      box-shadow: none;
      transform: none;
      overflow-y: auto;
      scrollbar-width: thin;
    }
    .filters-close-btn { display: none; }
    /* The sidebar is meant to fit a laptop screen without its own scrollbar: tighter rhythm than the phone drawer
       (whose taller layout scrolls naturally), same controls. */
    .filters-drawer-header { padding-top: 12px; padding-bottom: 12px; }
    .filters-drawer-body { padding: 16px 18px 18px; gap: 16px; }
    .sky-stops { height: 24px; margin-bottom: 2px; }
    .sky-stop { height: 24px; line-height: 24px; font-size: 17px; }
    .sort-picker-btn { padding: 8px 12px; }
    .content-column {
      grid-area: main;
      max-width: none;
      margin: 0;
      /* the same frame as the filters panel beside it */
      border: 1px solid var(--card-border);
      border-radius: 16px;
    }
    .datetime-bar, .status-line, .controls-row, .map-view, .feed, .day-scrub {
      max-width: none;
    }
    .datetime-bar { margin: 16px 0 0; }
    .status-line { margin: 12px 0 0; }
    .controls-row { margin: 10px 0 0; }
    .map-view { margin: 12px 0 0; }
    .feed { margin: 0; }
    .day-scrub { margin: 12px 0 0; }
  }

  /* The 3-pane "map view" split (filters | results list | map) - desktop/
     tablet only, and only while the map is actually open: :has() keys
     this off .map-view's own existing open/closed state instead of a
     second, separately-managed class that could drift out of sync with
     it. Below 1060px (including tablet portrait), map view stays exactly
     as it's always been - full width, replacing the list - untouched.
     .home-layout's own height here is NOT set explicitly - it already
     resolves to exactly the viewport height minus the header and footer
     via the flex:1 two rules above, the same distribution the no-results
     fix relies on; .content-column just needs min-height:0 to accept
     that height (shrink and scroll) instead of forcing the whole page
     taller the way 100+ stacked cards normally would. */
  @media (min-width: 1060px) {
    .home-layout:has(.map-view.open) {
      grid-template-columns: 280px minmax(360px, 420px) minmax(0, 1fr);
      grid-template-areas: "sidebar list map";
      max-width: 1600px;
      /* An explicit height, not just flex:1 above. flex-basis:auto
         (overriding the inherited flex:1 shorthand's own 0%) matters just
         as much as the height itself: with flex-basis:0% (flex:1's
         default), the flex algorithm sizes this item from flex-grow
         alone and ignores the height property entirely per spec - height
         only feeds the flex algorithm when flex-basis is auto. Confirmed
         empirically: height alone, without this, left .home-layout (and
         so .content-column below it) sized to fit all 100+ stacked cards
         regardless. 72px is the header's own height. */
      flex-basis: auto;
      height: calc(100vh - 72px);
      min-height: 0;
    }
    .home-layout:has(.map-view.open) .content-column {
      grid-area: list;
      min-height: 0;
      overflow-y: auto;
      scrollbar-width: thin;
      border: 1px solid var(--card-border);
      border-radius: 16px;
    }
    .home-layout:has(.map-view.open) .map-view {
      grid-area: map;
      max-width: none;
      margin: 0;
      padding: 0;
    }
    .home-layout:has(.map-view.open) #mapContainer {
      height: 100%;
    }
    .home-layout:has(.map-view.open) .map-close-btn {
      display: flex;
      right: 16px; /* .map-view has no side padding here, unlike its 20px default elsewhere */
    }
  }

  /* Narrower split view (1060-1399px, map open): the 3-pane rules just
     above already give the list its own max width (420px) and don't let
     the map go below its own comfortable minimum - so on a narrower
     screen, the thing that has to give is the permanent filters sidebar,
     not the map shrinking past usefulness. Below this range the sidebar
     was never on anyway (plain mobile/tablet toggle+drawer); at/above
     1400px there's room for all three - see the 3-pane block above, which
     this only overrides inside the gap between them. Reverts .filters-
     drawer to the exact same off-canvas overlay it is below 1060px
     (rather than a new third visual treatment) and brings the "Filters"
     toggle button and backdrop back so there's a way to open it. */
  @media (min-width: 1060px) and (max-width: 1399.98px) {
    .home-layout:has(.map-view.open) {
      grid-template-columns: minmax(360px, 420px) minmax(0, 1fr);
      grid-template-areas: "list map";
    }
    .home-layout:has(.map-view.open) .filters-toggle-btn {
      display: inline-flex;
    }
    .home-layout:has(.map-view.open) .filters-backdrop {
      display: block;
    }
    .home-layout:has(.map-view.open) .filters-drawer {
      position: fixed;
      grid-area: unset;
      align-self: auto;
      top: 0;
      left: 0;
      height: 100vh;
      max-height: none;
      width: min(320px, 85vw);
      background: var(--cream);
      border: none;
      border-radius: 0;
      box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
      transform: translateX(-100%);
      z-index: 1101;
    }
    .home-layout:has(.map-view.open) .filters-drawer.open {
      transform: translateX(0);
    }
    .home-layout:has(.map-view.open) .filters-close-btn {
      display: block;
    }
  }

/* ?embed=card (with ?venue=<id>): only the venue's card and the date/time controls - the admin page's "Sun & shadows"
   view shows the card exactly as visitors see it, inside a frame. Set by index.html's <head> switch, which then
   leaves app mode alone. */
html.embed-card body > header, html.embed-card .day-scrub, html.embed-card .status-line, html.embed-card .controls-row,
html.embed-card .floating-list-actions, html.embed-card .map-view, html.embed-card .filters-drawer,
html.embed-card .filters-backdrop, html.embed-card .home-footer, html.embed-card .cookie-consent-banner,
html.embed-card .admin-preview-note, html.embed-card .route-bar { display: none !important; }
html.embed-card .home-layout { display: block; max-width: none; padding: 0; margin: 0; }
html.embed-card .content-column { max-width: none; padding: 10px; margin: 0; }
html.embed-card body { padding-bottom: 0; }

/* Shown only to a signed-in admin when a city isn't launched yet (shared.js: showAdminPreviewNote()).
   Bottom-left, clear of the header and search; a tap hides it for the rest of the visit. */
.admin-preview-note {
  position: fixed;
  left: 8px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
  z-index: 1000;      /* under banners, drawers and dialogs (1100+), over the page and map */
  max-width: calc(100vw - 16px);
  padding: 3px 10px;
  border: 0;
  border-radius: 999px;
  background: rgba(40, 32, 20, 0.72);
  color: #FFFBF3;
  font: inherit;
  font-size: 11px;
  line-height: 1.4;
  cursor: pointer;
}
html.app-shell .admin-preview-note,
html.app-shell-page .admin-preview-note {
  bottom: calc(var(--shell-tabs-h) + 8px);   /* above the app's tab bar */
}

  /* --- City pages (/terrassen/<place>, build_city_pages.py): a place's terraces, today's sunniest, the live map --- */
  .city-page { max-width: 720px; }
  .city-page .cp-hero {
    background: linear-gradient(160deg, #FFF6E6 0%, #FFFBF3 55%, var(--cream) 100%);
    border: 1px solid #F5D9B3; border-radius: 18px; padding: 22px 20px 20px; margin: 4px 0 24px;
  }
  .city-page .cp-title { font-size: clamp(24px, 5vw, 30px); line-height: 1.2; margin: 0 0 10px; letter-spacing: -0.01em; }
  .city-page .cp-intro { font-size: 15px; line-height: 1.6; margin: 0 0 14px; max-width: 60ch; }
  .city-page .cp-stats { list-style: none; padding: 0; margin: 0 0 18px; display: flex; flex-wrap: wrap; gap: 8px; }
  .city-page .cp-stats li {
    margin: 0; padding: 4px 12px; border-radius: 999px; background: white; border: 1px solid var(--card-border);
    font-size: 13px; color: var(--muted);
  }
  .city-page .cp-stats b { color: var(--shade-900); font-weight: 700; }
  .city-page a.cp-cta {
    display: inline-flex; align-items: center; gap: 4px; min-height: 44px; padding: 10px 18px; border-radius: 999px;
    background: var(--shade-900); color: white; font-size: 15px; font-weight: 600; text-decoration: none;
    transition: background-color 0.15s ease;
  }
  .city-page a.cp-cta:hover { background: var(--shade-700); }
  .city-page a.cp-cta:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 3px; }
  .city-page .cp-today h3, .city-page .cp-about h3, .city-page .cp-nearby h3 { font-size: 18px; margin: 28px 0 4px; }
  .city-page .cp-sub { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
  .city-page .cp-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }
  .city-page .cp-list li { margin: 0; }
  .city-page a.cp-row {
    display: grid; grid-template-columns: 28px 1fr auto; grid-template-areas: "rank what sun" "rank strip strip";
    gap: 6px 12px; align-items: center; padding: 12px 14px; border-radius: 14px; background: white;
    border: 1px solid var(--card-border); color: var(--ink); font-weight: 400; text-decoration: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
  }
  .city-page a.cp-row:hover { border-color: #F5D9B3; box-shadow: 0 4px 14px rgb(18 42 56 / 0.06); }
  .city-page a.cp-row:focus-visible { outline: 2px solid var(--shade-700); outline-offset: 2px; }
  .city-page .cp-rank {
    grid-area: rank; align-self: start; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
    background: #FFF1DD; color: #8A4B12; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums;
  }
  .city-page .cp-what { grid-area: what; min-width: 0; display: flex; flex-direction: column; }
  .city-page .cp-name { font-weight: 600; color: var(--shade-900); font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .city-page .cp-type { font-size: 12px; color: var(--muted); text-transform: capitalize; }
  .city-page .cp-sun { grid-area: sun; font-size: 13px; font-weight: 600; color: #A26020; white-space: nowrap; }
  .city-page .cp-strip {
    grid-area: strip; position: relative; height: 8px; border-radius: 4px; background: #55606B; overflow: hidden;
  }
  .city-page .cp-strip span { position: absolute; top: 0; bottom: 0; background: var(--sun-500); }
  .city-page .cp-axis {
    display: flex; justify-content: space-between; margin: 6px 14px 0 54px; font-size: 11px; color: var(--muted);
    font-variant-numeric: tabular-nums;
  }
  .city-page .cp-empty { color: var(--muted); }
  .city-page .cp-faq { border-bottom: 1px solid var(--card-border); padding: 4px 0; }
  .city-page .cp-faq summary {
    cursor: pointer; padding: 10px 0; font-size: 15px; font-weight: 600; color: var(--shade-900); list-style-position: inside;
  }
  .city-page .cp-faq p { margin: 0 0 12px; }
  .city-page .cp-nearby ul, .city-page ul.cp-index { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
  .city-page .cp-nearby li, .city-page .cp-index li { margin: 0; }
  .city-page .cp-nearby a, .city-page .cp-index a {
    display: inline-flex; align-items: baseline; gap: 8px; padding: 8px 14px; border-radius: 999px; background: white;
    border: 1px solid var(--card-border); color: var(--shade-900); text-decoration: none; font-weight: 600; font-size: 14px;
  }
  .city-page .cp-nearby a:hover, .city-page .cp-index a:hover { border-color: var(--sun-500); }
  .city-page .cp-index small { color: var(--muted); font-weight: 400; font-size: 12px; }
  .city-page .cp-updated { margin-top: 28px; font-size: 12px; color: var(--muted); }
  .city-page .cp-ranking { margin: 24px 0 0; padding: 14px 16px; border-radius: 14px; background: white; border: 1px solid var(--card-border); font-size: 15px; }
  .city-page .cp-ranking a { display: inline-block; font-weight: 700; color: var(--shade-900); }
  @media (max-width: 420px) {
    .city-page a.cp-row { grid-template-columns: 28px 1fr; grid-template-areas: "rank what" "rank sun" "rank strip"; }
  }
  /* a part-of-the-day list: that part of the day outlined on each strip */
  .city-page .cp-strip i {
    position: absolute; top: 0; bottom: 0; z-index: 1; border: 1.5px solid var(--shade-900); border-radius: 4px;
    background: rgb(255 255 255 / 0.08); pointer-events: none;
  }
  /* The place index (/terrassen): a card per place - its name, how many terraces, today's sunniest */
  .city-page ul.cp-index { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
  .city-page .cp-index a.cp-place {
    display: grid; gap: 2px; padding: 12px 14px; border-radius: 14px; background: white;
    border: 1px solid var(--card-border); color: var(--ink); font-weight: 400; text-decoration: none; height: 100%;
    box-sizing: border-box; transition: border-color 0.15s ease, box-shadow 0.15s ease;
  }
  .city-page .cp-index a.cp-place:hover { border-color: #F5D9B3; box-shadow: 0 4px 14px rgb(18 42 56 / 0.06); }
  .city-page .cp-index a.cp-place:focus-visible { outline: 2px solid var(--shade-700); outline-offset: 2px; }
  .city-page .cp-place-name { font-size: 15px; font-weight: 700; color: var(--shade-900); }
  .city-page .cp-place-count { font-size: 12px; color: var(--muted); }
  .city-page .cp-place-top { font-size: 12.5px; color: #A26020; margin-top: 4px; line-height: 1.4; }
  .city-page .cp-index-about p { color: var(--ink); margin: 0 0 10px; }
  .city-page .cp-index-h { font-size: 18px; margin: 24px 0 10px; }

  /* --- My profile (profile.html): the name and picture from Google, links to favorites and sun reports, the
     visitor's photos (my-photos.js, also in the app's Profile tab), and deleting the account small at the foot --- */
  .profile-who { display: flex; align-items: center; gap: 16px; margin: 8px 0 22px; }
  .profile-avatar {
    width: 64px; height: 64px; border-radius: 50%; overflow: hidden; flex-shrink: 0; font-size: 26px;
    box-shadow: 0 0 0 3px white, 0 0 0 4px var(--card-border);
  }
  .profile-name { font-size: 20px; font-weight: 700; color: var(--shade-900); }
  .profile-links {
    display: flex; flex-direction: column; background: white; border: 1px solid var(--card-border);
    border-radius: 14px; overflow: hidden;
  }
  .content-page-inner a.profile-link {
    display: flex; align-items: center; gap: 14px; padding: 14px 16px; color: var(--ink); text-decoration: none;
  }
  .content-page-inner a.profile-link + a.profile-link { border-top: 1px solid var(--card-border); }
  .content-page-inner a.profile-link:hover { background: var(--cream); text-decoration: none; }
  .profile-link-icon {
    display: flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%;
    background: #FDF0DF; color: var(--sun-600); flex-shrink: 0;
  }
  .profile-link-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
  .profile-link-text b { color: var(--shade-900); font-size: 15px; }
  .profile-link-text small { color: var(--muted); font-size: 12.5px; }
  .profile-link-arrow { color: var(--muted); font-size: 24px; line-height: 1; }
  .profile-section { margin-top: 28px; }
  .content-page-inner .profile-section h3 { margin: 0 0 12px; }
  .profile-signed-out p { margin-bottom: 14px; }
  .profile-sign-in {
    font: inherit; font-size: 15px; font-weight: 700; padding: 10px 18px; border-radius: 12px; cursor: pointer;
    background: var(--shade-900); color: white; border: 1px solid var(--shade-900);
  }
  .profile-danger-zone { margin-top: 56px; text-align: center; }
  .profile-delete {
    background: none; border: 0; padding: 4px; font: inherit; font-size: 12px; color: var(--muted);
    text-decoration: underline; cursor: pointer;
  }
  .profile-delete:hover { color: #B03A2E; }

  .mp-note { color: var(--muted); font-size: 14px; line-height: 1.5; margin: 0; }
  .mp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; width: 100%; }
  .mp-item {
    margin: 0; background: white; border: 1px solid var(--card-border); border-radius: 12px; overflow: hidden;
    display: flex; flex-direction: column;
  }
  .mp-thumb { display: block; padding: 0; border: 0; background: var(--cream); cursor: zoom-in; aspect-ratio: 4 / 3; }
  .mp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .mp-item figcaption { display: flex; flex-direction: column; gap: 3px; padding: 8px 10px 10px; font-size: 12.5px; }
  .mp-item a.mp-venue {
    font-weight: 700; color: var(--shade-900); text-decoration: none; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
  }
  .mp-item a.mp-venue:hover { text-decoration: underline; }
  .mp-status { color: var(--muted); }
  .mp-status--pending { color: #A26020; }
  .mp-remove {
    align-self: flex-start; background: none; border: 0; padding: 0; margin-top: 2px; font: inherit; font-size: 12px;
    color: var(--muted); text-decoration: underline; cursor: pointer;
  }
  .mp-remove:hover { color: #B03A2E; }
  .mp-box { width: 100%; }

  /* --- My Favorites (favorites.html): the app's Favorites cards (app-shell.css .shell-fav-*) at the page's width, a
     way back to the profile under the heading, and each card's round arrow to the venue on the map --- */
  .content-page-inner a.fav-back {
    display: inline-flex; align-items: center; gap: 6px; margin-top: 2px; padding: 7px 14px 7px 10px;
    border: 1px solid var(--card-border); border-radius: 999px; background: white; color: var(--shade-900);
    font-size: 13.5px; font-weight: 600; text-decoration: none;
  }
  .content-page-inner a.fav-back:hover { border-color: #F5D9B3; background: var(--cream); text-decoration: none; }
  .content-page:has(.fav-back) .content-page-inner { padding-bottom: 8px; }
  /* One column at the cards' own width on every screen: the homepage's desktop layout lifts .feed's width limit
     (the media query further up), which would otherwise stretch these cards across the whole window */
  .feed.fav-feed { width: 100%; box-sizing: border-box; max-width: 640px; margin: 0 auto; }
  .fav-card { position: relative; }
  .fav-go {
    position: absolute; bottom: 15px; left: 15px; z-index: 2;   /* the card's 14 px padding + its border */
    width: 38px; height: 38px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    border: 1px solid #F5D9B3; background: white; color: var(--sun-700); box-shadow: 0 2px 8px rgba(22, 48, 63, 0.12);
  }
  .fav-go:hover { background: var(--cream); }

  /* --- The two products beside the logo (setupProductSwitcher() in shared.js): Terraces and Sun report, the
     page's own one raised in white. Labels give way to icons where the header gets tight (the homepage shares it
     with the search bar), the "New" tag then a dot; gone on phones and in app mode, where the app's bars stand in --- */
  .lumo-products {
    order: 1; flex-shrink: 0; display: flex; align-items: center; gap: 2px; padding: 3px;
    border-radius: 12px; background: rgba(255, 255, 255, 0.5); border: 1px solid var(--card-border);
  }
  .lumo-product {
    position: relative; display: flex; align-items: center; gap: 7px; padding: 7px 12px; border-radius: 9px;
    color: var(--muted); font-size: 14px; font-weight: 600; text-decoration: none; white-space: nowrap;
    transition: background 0.15s ease, color 0.15s ease;
  }
  .lumo-product:hover { color: var(--shade-900); background: rgba(255, 255, 255, 0.7); }
  .lumo-product:focus-visible { outline: 2px solid var(--shade-700); outline-offset: 1px; }
  .lumo-product.is-on { background: white; color: var(--shade-900); box-shadow: 0 1px 3px rgba(18, 42, 56, 0.12); }
  .lumo-product.is-on svg { color: var(--sun-600); }
  .lumo-product-new {
    padding: 2px 6px; border-radius: 999px; background: var(--sun-500); color: white;
    font-size: 10px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1.3;
  }
  /* icons only: beside the homepage's search bar on a laptop, and on every page below 900 px */
  @media (max-width: 1180px) {
    header:has(.search-bar) .lumo-product-label { display: none; }
    header:has(.search-bar) .lumo-product { padding: 8px 10px; }
    header:has(.search-bar) .lumo-product-new { position: absolute; top: 3px; right: 3px; width: 7px; height: 7px; padding: 0; font-size: 0; }
  }
  @media (max-width: 900px) {
    .lumo-product-label { display: none; }
    .lumo-product { padding: 8px 10px; }
    .lumo-product-new { position: absolute; top: 3px; right: 3px; width: 7px; height: 7px; padding: 0; font-size: 0; }
  }
  @media (max-width: 480px) { .lumo-products { display: none; } }
  html.app-shell .lumo-products, html.app-shell-page .lumo-products { display: none; }

  /* --- The sun report's card on the homepage (index.html .home-reports): its own warm card above the quiet
     "about" text, with the one call to action of the page's second product --- */
  .home-reports {
    display: flex; align-items: center; gap: 20px; max-width: 640px; margin: 36px auto 0; padding: 22px 24px;
    box-sizing: border-box; border-radius: 18px; border: 1px solid #F5D9B3;
    background: linear-gradient(135deg, #FFF6E8 0%, #FFFFFF 70%);
    box-shadow: 0 6px 22px rgba(242, 153, 74, 0.12);
  }
  .home-reports-art { flex: none; }
  .home-reports-body { flex: 1; min-width: 0; }
  .home-reports-tag { margin: 0 0 4px; font-size: 11.5px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: #A26020; }
  .home-reports-title { margin: 0 0 6px; font-size: 20px; line-height: 1.25; font-weight: 700; color: var(--shade-900); }
  .home-reports-text { margin: 0 0 14px; font-size: 14px; line-height: 1.55; color: var(--ink); }
  .home-reports-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; }
  .home-reports-cta {
    display: inline-block; padding: 10px 18px; border-radius: 12px; background: var(--shade-900); color: white;
    font-size: 14.5px; font-weight: 700; text-decoration: none;
  }
  .home-reports-cta:hover { background: var(--shade-700); }
  .home-reports-sample { font-size: 14px; font-weight: 600; color: var(--shade-700); }
  @media (max-width: 560px) {
    .home-reports { flex-direction: column; align-items: flex-start; margin: 28px 16px 0; padding: 18px; gap: 12px; }
    .home-reports-art { width: 48px; height: 48px; }
  }
  .profile-role-lead { font-size: 13.5px; color: var(--muted); margin: 0 0 10px; }
  .profile-role-row { display: flex; align-items: center; gap: 12px; }
  .profile-role-select { font: inherit; font-size: 14px; padding: 8px 12px; border-radius: 10px; border: 1px solid var(--card-border);
    background: white; color: var(--ink); max-width: 100%; }
  .profile-role-saved { font-size: 13px; color: var(--muted); }
