  :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; }
  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%;
  }
  .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); }
  .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: 20;
  }
  .suggestions-dropdown.open { display: block; }
  .suggestion-item {
    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); }
  .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-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;
  }
  .expand-all-btn {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--shade-900);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    text-decoration: underline;
    padding: 0;
  }
  .expand-all-btn:hover { color: var(--sun-700); }
  .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;
    max-width: 640px;
    margin: 12px auto 0;
    padding: 0 20px;
  }
  .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 */
  }

  .map-popup-name { font-weight: 600; margin-bottom: 2px; }
  .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%; }
  #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 { 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(--shade-900); }
  .lumo-here-pulse { position: absolute; inset: 0; border-radius: 50%; background: color-mix(in srgb, var(--shade-900) 30%, transparent); animation: lumo-pulse 2.6s ease-out infinite; }
  @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;
  }

  .card {
    background: white;
    border: 1px solid var(--card-border);
    border-radius: 16px;
    padding: 14px;
    cursor: pointer;
  }
  .card.sunny { border-color: #F5D9B3; background: #FFFBF3; }

  .card-header {
    display: flex;
    gap: 14px;
    align-items: 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; }

  /* 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;
  }

  .card-body { flex: 1; min-width: 0; }
  .card-top {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    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; }
  .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); }
  .favorite-btn.favorited { color: var(--sun-700); }
  .favorite-btn.favorited svg path { fill: currentColor; }
  .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; }

  /* 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;
  }
  /* 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; }
  .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;
  }

  .split-toggle-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: none;
    border: none;
    color: var(--muted);
    font-size: 11px;
    cursor: pointer;
    padding: 4px 0;
    margin-top: 4px;
  }
  .split-toggle-btn:hover { color: var(--shade-900); }
  .split-toggle-btn .chevron-mini {
    display: inline-block;
    transition: transform 0.15s ease;
    font-size: 9px;
  }
  .split-toggle-btn.open .chevron-mini { transform: rotate(180deg); }
  .split-view {
    display: none;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px dashed var(--card-border);
  }
  .split-view.open { display: block; }
  .split-sub-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 3px;
  }
  .split-sub { margin-bottom: 10px; }
  .split-sub:last-child { margin-bottom: 0; }

  .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);
  }
  .loading-more {
    max-width: 640px;
    margin: 16px auto;
    padding: 0 20px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
  }
  .error { color: #B03A2E; }

  @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; }
  }
  @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_cloudy"] { --st: #F7C08A; }
  .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_cloudy { fill: #F7C08A; }
  .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); }

  .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);
  }
  /* 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;
  }

  /* "Get this badge for your website": under each ranked row, a <details> with
     both sizes of the venue's medal, each shown at its real size with its
     embed code, a copy button and a download link (see badge_kit_html() in
     render_terrace_leaderboard_page.py; the copy button and opening one from a
     #badge-... link are leaderboard.js). Closed by default, so it costs a
     list a single quiet line per row. */
  .lb-kit { grid-column: 1 / -1; margin-top: 12px; }
  .lb-kit > summary {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 2px 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--lb-muted, var(--muted));
    list-style: none;
    cursor: pointer;
  }
  .lb-kit > summary::-webkit-details-marker { display: none; }
  .lb-kit > summary::after {
    content: "";
    width: 6px;
    height: 6px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translateY(-2px);
    transition: transform 0.2s ease;
  }
  .lb-kit[open] > summary::after { transform: rotate(-135deg) translateY(-1px); }
  .lb-kit > summary:hover { color: var(--shade-900); }
  .lb-kit > summary:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 3px; border-radius: 6px; }
  .lb-kit-body {
    margin-top: 10px;
    padding: 14px;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.72);
  }
  .content-page-inner .lb-kit-intro { margin: 0 0 14px; font-size: 13px; line-height: 1.5; color: var(--lb-muted, var(--muted)); }
  .lb-kit-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
  .lb-kit-option { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
  .lb-kit-preview {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    min-height: 116px;
    padding: 12px;
    border: 1px dashed var(--card-border);
    border-radius: 10px;
    background: var(--cream);
  }
  .content-page-inner .lb-kit-preview img { display: block; border: 0; border-radius: 0; }
  .lb-kit-label { font-size: 13px; font-weight: 700; color: var(--shade-900); }
  .lb-kit-label span { font-weight: 400; color: var(--muted); }
  .lb-kit textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 9px 10px;
    border: 1px solid var(--card-border);
    border-radius: 10px;
    background: white;
    color: var(--ink);
    font: 12px/1.5 ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
    resize: vertical;
  }
  .lb-kit textarea:focus { outline: 2px solid var(--sun-500); }
  .lb-kit-actions { display: flex; align-items: center; gap: 14px; }
  .lb-kit-copy {
    padding: 8px 14px;
    border: 0;
    border-radius: 10px;
    background: var(--shade-900);
    color: white;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
  }
  .lb-kit-copy:hover { background: var(--shade-700); }
  .lb-kit-copy:focus-visible { outline: 2px solid var(--sun-500); outline-offset: 2px; }
  .lb-kit-copy.is-copied { background: var(--sun-500); }
  .content-page-inner .lb-kit-actions a { font-size: 13px; font-weight: 600; color: var(--shade-900); text-decoration: underline; }
  .content-page-inner .lb-kit-actions a:hover { color: var(--shade-700); }
  .leaderboard-row.is-first .lb-kit-body { border-color: #E2B24A; }

  /* 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); }
  /* 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; } }

  /* 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. */
  .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;
  }
