/* home-discovery.css -- layout + visuals for the home page redesign */

/* --- Filter panel (wraps location row + time-bucket row) ----------- */

.filter-panel {
    margin: 0.5rem 0;
    padding: 0.75rem 1.5rem;
    background: rgba(216, 210, 196, 0.6);
    border-radius: var(--radius-md);
    /* Warm-shadow edge at 0.28 alpha matches the chip / popup-image
       vocabulary so the panel reads as a defined surface against the
       cream page background. Bumped from 0.10 (too faint to register
       as an edge). */
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.28);
    /* Positioning context for the absolutely-pinned Hide link in the
       upper-right corner (.filter-panel__header-actions). */
    position: relative;
    /* Animate the chrome (padding/margin/background/shadow) so when the
       panel collapses to its summary, the box visibly tightens around
       the new content rather than snapping. Body-height animation is on
       .filter-panel__body below. */
    transition: padding 280ms ease,
                margin 280ms ease,
                background 280ms ease,
                box-shadow 280ms ease;
}
/* The body wraps every part of the expanded view (header + location row
   + time row). JS sets an explicit pixel max-height on toggle so the
   transition runs across the exact content height (no animating against
   a fixed ceiling that eats the first slice of perceived motion).
   overflow stays visible while expanded so descendant tooltips (e.g.
   the time-bucket-pill ::before tooltips that render below the pill)
   can escape the body's box. expandFilterPanel sets overflow:hidden
   inline during the slide-down animation, then clears it on transition
   end. The .is-collapsed rule below keeps it clipped when collapsed. */
.filter-panel__body {
    transition: max-height 280ms ease;
}
.filter-panel__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}
/* Hide link pinned flush to the panel's upper-right corner. Absolute
   positioning (vs. living in the header flex row) tucks it deeper into
   the corner than the header's baseline alignment would allow. The
   panel itself is position: relative as the anchor. */
.filter-panel__header-actions {
    position: absolute;
    top: 0.4rem;
    right: 0.75rem;
    z-index: 1;
    display: flex;
    gap: 0.75rem;
    align-items: baseline;
}
.filter-panel__body > .near-me-row,
.filter-panel__body > .time-bucket-row {
    padding-left: 0;
    padding-right: 0;
}
.filter-panel__body > .near-me-row { padding-top: 0; padding-bottom: 0.5rem; }
.filter-panel__body > .time-bucket-row { padding-top: 0.25rem; padding-bottom: 0.3rem; }
/* Parallel styling to .persona-row__title so the two panels read as
   matched halves of the same "set your filters" workflow. */
.filter-panel__title {
    font-size: 1.25rem;
    margin: 0 0 0.5rem;
}

/* Summary bar shown when the filter panel is collapsed -- mirrors the
   persona-row summary pattern. display:none in expanded state so it
   doesn't render below the body; toggled to flex when .is-collapsed. */
.filter-panel__summary {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    cursor: pointer;
    padding: 0.4rem 0.85rem;
    /* Warm tint matches the expanded filter-panel bg and the persona-row
       below, so the collapsed summary reads as part of the same family
       rather than a stray white card. Outline ring instead of a solid
       border for the same reason. */
    background: rgba(216, 210, 196, 0.6);
    border: 0;
    /* Same defined edge as the expanded .filter-panel (0.28 alpha) so
       the collapsed bar reads as a real surface, not a faint smudge. */
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.28);
    border-radius: var(--radius-md);
    transition: background 120ms ease;
}
.filter-panel__summary:hover,
.filter-panel__summary:focus-visible {
    background: rgba(216, 210, 196, 0.85);
    outline: none;
}
.filter-panel__summary-text {
    flex: 1;
    color: var(--color-fg);
    font-size: 1rem;
    line-height: 1.3;
}
/* Key tokens (time bucket, village name) are wrapped in <strong> by
   buildFilterPanelSummaryNodes -- bump weight a touch above browser
   default 700 cap... actually 600 reads better against the muted
   connectors than full bold. */
.filter-panel__summary-text strong {
    font-weight: 600;
}
/* Radius is contextual detail, not the primary signal. Dim + shrink
   slightly so the eye lands on the bold tokens first. */
.filter-panel__summary-radius {
    color: var(--color-muted);
    font-size: 0.9rem;
}
/* Real-button treatment for the Edit affordance on both collapsed
   summary bars: a faint cream pill with a warm hairline border so it
   reads as actionable instead of as a passive chevron. Hover pop on
   the parent summary brightens this background a touch. Both
   .filter-panel__summary-edit and .persona-row__summary-edit pick
   up these rules so the two bars stay visually consistent. */
.filter-panel__summary-edit,
.persona-row__summary-edit {
    color: var(--color-accent);
    font-weight: 600;
    white-space: nowrap;
    background: rgba(255, 250, 242, 0.78);
    border: 1px solid rgba(70, 60, 40, 0.22);
    border-radius: var(--radius-md);
    padding: 0.25rem 0.75rem;
    box-shadow: 0 1px 2px rgba(70, 60, 40, 0.10);
    transition: background 120ms ease, box-shadow 120ms ease,
                transform 120ms ease;
}
.filter-panel__summary:hover .filter-panel__summary-edit,
.filter-panel__summary:focus-visible .filter-panel__summary-edit,
.persona-row__summary:hover .persona-row__summary-edit,
.persona-row__summary:focus-visible .persona-row__summary-edit {
    background: #fffaf2;
    box-shadow: 0 2px 6px rgba(70, 60, 40, 0.18);
    transform: translateY(-1px);
}
.filter-panel.is-collapsed {
    margin: 0 0 0.4rem;
    padding: 0.2rem 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
}
.filter-panel.is-collapsed .filter-panel__summary {
    display: flex;
}
/* Symmetry with .persona-row.is-collapsed (which display:none's its
   non-summary children): once the body has finished its max-height
   slide-up, flip it to visibility:hidden so chip descendants don't keep
   claiming a clickable bounding box at their natural Y. Without this,
   the chips' getBoundingClientRect still reports a rect inside what's
   now the event list area below the sticky bar -- a ghost target that
   trips up Playwright clicks and screen readers alike. The visibility
   transition is delay-only (0s duration, 280ms delay) so it lands when
   max-height hits 0; on expand the base rule has no visibility
   transition, so chip content reappears the instant the body opens. */
.filter-panel.is-collapsed .filter-panel__body {
    overflow: hidden;
    visibility: hidden;
    /* Static fallback so a server-rendered collapsed state has zero
       body height. The JS path animates via inline max-height; that
       inline style wins during the transition and ends at "0px",
       so this rule never fights the runtime collapse path. */
    max-height: 0;
    transition: max-height 280ms ease, visibility 0s 280ms;
}

/* Single sticky stack: when collapsed, both filter panels travel together
   as one pinned bar at the top of the viewport while events scroll behind. */
.filters-stack.is-collapsed {
    position: sticky;
    top: 0;
    z-index: 50;
    background: var(--color-bg);
    padding: 0.3rem 1rem;
    border-bottom: 1px solid var(--color-border);
    box-shadow: var(--shadow-1);
    margin: 0 0 0.75rem;
}

/* --- Time bucket row ------------------------------------------------ */

.time-bucket-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 1rem 1.5rem 0.75rem;
}
.time-bucket-row__label {
    color: var(--color-muted);
    margin-right: 0.25rem;
}
.time-bucket-pill {
    position: relative;     /* anchors the ::before explanation tooltip */
    border: 1px solid rgba(70, 60, 40, 0.28);
    background: var(--color-surface);
    color: var(--color-fg);
    padding: 0.5rem 1.25rem;
    min-height: 44px;
    /* radius-md (shared with all other toggleable chips) reads as a soft
       button rather than a pill. Press-affordance shadow/lift rules
       live in the shared chip block further down the file. */
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: 1rem;
    font-weight: 500;
    font-family: inherit;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease,
                transform 150ms ease, box-shadow 150ms ease;
    /* Grow proportionally from natural content width so the row's right
       edge aligns flush with the panel instead of leaving a jagged
       gap. Each pill picks up an equal share of the remaining space;
       "Next 3 hours" stays wider than "Sun", just both grown by the
       same delta. min-width: max-content prevents shrinking below the
       label so flex-wrap (rather than truncation) kicks in on narrow
       viewports. The label + hint at the row's left stay natural-width
       because they don't carry flex-grow. */
    flex: 1 1 auto;
    min-width: max-content;
}
/* Explanation tooltip: fades in after 100ms of hover (transition-delay
   only applies on hover, not mouseout, so it disappears immediately).
   Reads from data-tooltip so the template stays a single source of
   truth for the explanation text. */
/* Tooltip rendered on ::before so ::after is free for the .is-active
   checkmark glyph below. Matches the persona-chip[data-tooltip]::before
   pattern used elsewhere. */
.time-bucket-pill[data-tooltip]::before {
    content: attr(data-tooltip);
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 8px;
    padding: 0.4rem 0.7rem;
    background: #4a3f2e;
    color: var(--color-bg);
    font-size: 0.85rem;
    font-weight: 500;
    line-height: 1.25;
    border-radius: var(--radius-sm);
    white-space: nowrap;
    box-shadow:
        0 6px 12px rgba(40, 30, 20, 0.18),
        0 20px 40px rgba(40, 30, 20, 0.40);
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease;
    z-index: 5;
}
.time-bucket-pill[data-tooltip]:hover::before,
.time-bucket-pill[data-tooltip]:focus-visible::before {
    opacity: 1;
    transition-delay: 100ms;
}
.time-bucket-pill:hover { background: var(--color-bg); }
.time-bucket-pill.is-active {
    background: var(--color-primary);
    color: white;
    border-color: var(--color-primary);
}

/* --- Near me row ---------------------------------------------------- */

.near-me-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1.5rem 1.25rem;
}
.near-me-row__village,
.near-me-row__radius {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--color-muted);
    position: relative;     /* anchors the ::after explanation tooltip */
}
.near-me-row__village {
    display: flex;          /* overrides the shared inline-flex above */
    flex-wrap: wrap;        /* let pills wrap to a new line when narrow */
    align-items: center;
    flex: 1 1 100%;         /* claim the full row so .village-pills (flex: 1) has space to fill into */
}
.near-me-row__label {
    color: var(--color-muted);
    margin-right: 0.25rem;
}
/* Sub-line instructional copy that telegraphs multi-select / required
   behavior to less-tech-savvy users -- pills look like radio buttons
   at first glance, so the hint says "yes, you can tap several". */
.filter-row__hint {
    color: var(--color-muted);
    font-size: 1rem;
    margin-left: 0.25rem;
}

/* Cart-ride radius: hidden by default behind a disclosure. Collapsed
   state shows the current value as plain text + a "change" link; the
   chip row is revealed on click. Keeps the default UI clean for users
   who don't need to fiddle with distance. */
.near-me-row__radius-summary {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--color-muted);
    font-size: 1rem;
}
.near-me-row__radius-summary strong {
    color: var(--color-fg);
    font-weight: 600;
}
.near-me-row__radius-toggle {
    background: none;
    border: 0;
    padding: 0;
    color: var(--color-accent);
    text-decoration: underline;
    cursor: pointer;
    font-family: inherit;
    font-size: inherit;
}
.near-me-row__radius-toggle:hover { filter: brightness(0.9); }
.near-me-row__radius-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
}
.near-me-row__radius-chips[hidden] { display: none; }
.radius-chip {
    /* Mirror village-pill / time-bucket-pill so the row reads as one
       chip language across all three filters. Shared press-affordance
       shadow/lift lives in the shared chip block further down. */
    position: relative;
    border: 1px solid rgba(70, 60, 40, 0.28);
    background: var(--color-surface);
    color: var(--color-fg);
    padding: 0.4rem 1rem;
    min-height: 36px;
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: 500;
    font-family: inherit;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease,
                transform 150ms ease, box-shadow 150ms ease;
}
.radius-chip:hover { background: var(--color-bg); }
.radius-chip.is-active {
    background: var(--color-primary);
    color: white;
    border-color: var(--color-primary);
}
.village-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    /* Claim the remaining row width so the inner .village-pill flex-grow
       has space to actually grow into. Without this the wrapper would
       only take the natural width of its content and the row's right
       edge would stay jagged. */
    flex: 1 1 auto;
}
.village-pill {
    /* Mirror .time-bucket-pill so the two rows read as one chip language,
       including the proportional flex-grow that makes the row's right
       edge align flush with the panel. Shared press-affordance
       shadow/lift lives in the shared chip block further down. */
    flex: 1 1 auto;
    min-width: max-content;
    position: relative;
    border: 1px solid rgba(70, 60, 40, 0.28);
    background: var(--color-surface);
    color: var(--color-fg);
    padding: 0.5rem 1rem;
    min-height: 44px;
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: 1rem;
    font-weight: 500;
    font-family: inherit;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease,
                transform 150ms ease, box-shadow 150ms ease;
}
.village-pill:hover { background: var(--color-bg); }
.village-pill.is-active {
    background: var(--color-primary);
    color: white;
    border-color: var(--color-primary);
}
/* Checkmark glyph on selected chips: gives a non-color signal that
   complements the green fill. Trailing ✓ matches the persona-row chip
   convention ("Active Lifestyle ✓") so the two filter sections read the
   same way. The glyph is rendered (and its space reserved) at all
   times via the base rule below -- only its visibility flips on
   .is-active. Without this, toggling active state would change the
   chip's width and cause a jarring relayout. */
.village-pill::after,
.time-bucket-pill::after,
.radius-chip::after {
    content: " ✓";
    font-weight: 700;
    visibility: hidden;
}
.village-pill.is-active::after,
.time-bucket-pill.is-active::after,
.radius-chip.is-active::after {
    visibility: visible;
}
/* Same 100ms hover-delay tooltip pattern as the time-bucket pills.
   Difference: text wraps (with a max width) since these explanations
   are longer than the pill ones. */
.near-me-row__village[data-tooltip]::after,
.near-me-row__radius[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    top: 100%;
    left: 0;
    width: max-content;
    max-width: 320px;
    margin-top: 8px;
    padding: 0.5rem 0.75rem;
    background: #4a3f2e;
    color: var(--color-bg);
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.35;
    border-radius: var(--radius-sm);
    box-shadow:
        0 6px 12px rgba(40, 30, 20, 0.18),
        0 20px 40px rgba(40, 30, 20, 0.40);
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease;
    z-index: 5;
}
.near-me-row__village[data-tooltip]:hover::after,
.near-me-row__radius[data-tooltip]:hover::after,
.near-me-row__village[data-tooltip]:has(:focus-visible)::after,
.near-me-row__radius[data-tooltip]:has(:focus-visible)::after {
    opacity: 1;
    transition-delay: 100ms;
}
/* near-me.js adds .tooltip-suppressed on change and clears it on mouseleave,
   so the explanation hides immediately after a selection instead of lingering
   under the still-hovered label. */
.near-me-row__village.tooltip-suppressed[data-tooltip]::after,
.near-me-row__radius.tooltip-suppressed[data-tooltip]::after {
    opacity: 0;
    transition-delay: 0ms;
}
/* Explicit display:inline-flex above overrides the user-agent `[hidden]`
   rule. Reassert it so near-me.js can show/hide the Cart ride via the
   `hidden` attribute. */
.near-me-row__radius[hidden] { display: none; }
.near-me-row__radius select { min-width: 5rem; }
.near-me-row select { border-color: rgba(70, 60, 40, 0.28); }

/* --- Persona row ---------------------------------------------------- */

.persona-row {
    /* Warm tinted inset panel groups the "what interests you" choices
       (personas + moods + status) into one visual unit, distinct from
       the time/distance filters above. Tint is --color-border at low
       alpha + a warm-shadow outline ring (0.28, matches the chip /
       popup-image vocabulary) for edge definition. position: relative
       anchors the absolutely-pinned Hide link in the upper-right
       corner (.persona-row__header-actions). */
    position: relative;
    margin: 0 0 0.75rem;
    padding: 1rem 1.5rem 0.75rem;
    background: rgba(216, 210, 196, 0.6);
    border-radius: var(--radius-md);
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.28);
}
/* Hide link pinned flush to the persona-row's upper-right corner,
   mirroring .filter-panel__header-actions on the where/when panel. */
.persona-row__header-actions {
    position: absolute;
    top: 0.7rem;
    right: 0.75rem;
    z-index: 1;
    display: flex;
    gap: 0.75rem;
    align-items: baseline;
}

/* --- Find Events CTA banner + sticky collapsed summary -------------- */
/* CTA banner sits between the filter stack and the home-layout (drawer
   + list + map). Visible in both expanded and collapsed filter states
   so it functions as the persistent "command bar" linking the picker
   area above to the results below. Hides entirely when the match
   count drops to zero (the empty-state copy in the event list takes
   over). */
.cta-banner {
    display: flex;
    /* Tight to the filter stack above (0 top); generous gap below so
       the search row that follows reads as a distinct "now narrow
       further" step rather than crowding the banner. */
    margin: 0 0 0.85rem;
}
.cta-banner[hidden] { display: none; }

/* Top-of-viewport progress bar that fires during HTMX-driven filter
   re-fetches. Hidden until HTMX adds .htmx-request; opacity transition
   has a 150ms delay so quick (<150ms) fetches never flash, while the
   long ones (clearing the search pill on a 4,000-event window) get a
   clear "the system is working" signal. The inner ::after slides a
   primary-green wedge across to read as motion without claiming to
   measure actual progress. */
.filter-loading-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: rgba(125, 150, 112, 0.18);
    overflow: hidden;
    z-index: 200;
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease 150ms;
}
.filter-loading-bar.htmx-request {
    opacity: 1;
    transition-delay: 0s;
}
.filter-loading-bar::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 30%;
    background: var(--color-primary);
    animation: filter-loading-slide 1.2s ease-in-out infinite;
}
@keyframes filter-loading-slide {
    0%   { left: -30%; }
    50%  { left: 50%; }
    100% { left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
    .filter-loading-bar::after { animation: none; left: 0; width: 100%; }
}
/* When the filter stack is collapsed it gains 1rem horizontal padding
   to sit as a sticky-bar with its summaries inset. Mirror that inset on
   the CTA banner so the three rows (where/when summary, interests
   summary, CTA) line up at the same outer edge. */
body.is-filters-collapsed .cta-banner {
    margin: 0 1rem 0.85rem;
}
.find-events-btn {
    /* Full-width so it reads as the primary "next step" bar, not a
       polite centered pill. The trailing down arrow signals "click to
       jump down to the results below". border-box keeps the horizontal
       padding inside the 100% so the banner lines up with the filter-
       panel summaries above it. Color and type stay heavier than the
       chip rows above so the eye lands here after scanning the picker
       grids -- terracotta breaks out of the sage rhythm. Slimmed in
       Option A: padding cut ~40% vertically and the wisecrack moved
       out of the button (now lives above the event list), so this
       block reads as a tidy summary bar rather than a hero gate.
       position: relative anchors the absolutely-positioned arrow. */
    flex: 1;
    width: 100%;
    box-sizing: border-box;
    position: relative;
    padding: 0.55rem 1.5rem;
    /* Floor stays readable at narrow widths; cap trimmed from the old
       1.4rem to match the slimmer banner. */
    font-size: clamp(1rem, 1.8vw, 1.2rem);
    font-weight: 700;
    background: var(--color-accent);
    color: white;
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background 120ms ease, transform 150ms ease, box-shadow 150ms ease;
    min-height: var(--target-min);
}
.find-events-btn:hover { background: #9a6648; }
@media (prefers-reduced-motion: no-preference) {
    .find-events-btn:hover {
        transform: translateY(-2px);
        box-shadow: var(--shadow-2);
    }
}
/* Single-line CTA text: count + where + arrow. The wisecrack subtitle
   that used to live here has moved to .event-list__compliment above
   the event list. */
#find-events-btn-text {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1.2;
}
.find-events-btn .cta-main {
    display: inline-block;
}
/* Wisecrack tagline above the event list. Three children laid out as
   a flex row: the "Word to the wise:" prefix label, the italic body
   wisecrack (which wraps within its own column so a long line hangs
   under itself rather than under the prefix), and a ↻ refresh button
   pinned to the first line via baseline alignment. Hidden by JS when
   the event count is zero (matching CTA visibility).

   min-height reserves space for two lines so a refresh-click swap
   between a short and a long wisecrack doesn't visibly resize the
   row -- the user asked specifically for no jumpy reflow on ↻. */
.event-list__compliment {
    display: flex;
    align-items: baseline;
    column-gap: 0.4rem;
    margin: 0 0 0.6rem;
    text-align: left;
    line-height: 1.3;
    min-height: calc(1.3em * 2);
}
.event-list__compliment[hidden] { display: none; }

/* Empty-state forward CTA. When a bucket search has no upcoming matches,
   the fallback copy reports the activity's last occurrence and offers a
   link to the venue's live page. Styled as the site's standard warm-edge
   inline pill (cf. .persona-row__summary-edit) so it reads as a tappable
   "go here next" affordance rather than blending into the body copy. The
   arrow nudges right on hover/focus to reinforce the forward motion. */
.empty-fallback__forward {
    margin: 0.6rem 0 0;
}
.empty-fallback__cta {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--color-accent);
    font-weight: 600;
    text-decoration: none;
    background: rgba(255, 250, 242, 0.78);
    border: 1px solid rgba(70, 60, 40, 0.22);
    border-radius: var(--radius-md);
    padding: 0.3rem 0.8rem;
    box-shadow: 0 1px 2px rgba(70, 60, 40, 0.10);
    transition: background 120ms ease, box-shadow 120ms ease,
                transform 120ms ease;
}
.empty-fallback__cta:hover,
.empty-fallback__cta:focus-visible {
    background: #fffaf2;
    box-shadow: 0 2px 6px rgba(70, 60, 40, 0.18);
    transform: translateY(-1px);
}
.empty-fallback__arrow {
    transition: transform 120ms ease;
}
.empty-fallback__cta:hover .empty-fallback__arrow,
.empty-fallback__cta:focus-visible .empty-fallback__arrow {
    transform: translateX(2px);
}
.event-list__compliment-label {
    flex: 0 0 auto;        /* keep natural width; never shrink */
    color: var(--color-muted);
    font-weight: 600;
    font-size: 0.95rem;
}
.event-list__compliment-body {
    flex: 1 1 auto;        /* fill remaining row, wrap within column */
    min-width: 0;
    color: var(--color-muted);
    font-style: italic;
    font-size: 1rem;
}
/* Refresh affordance mirroring .site-testimonial__refresh -- same
   muted-circle pattern so the two refresh controls read as siblings.
   Spins 360° on click for click-registered feedback. */
.event-list__compliment-refresh {
    display: inline-block;
    margin-left: 0.45rem;
    padding: 0.1rem 0.35rem;
    background: none;
    border: none;
    cursor: pointer;
    color: #b8a888;
    font-size: 0.95rem;
    line-height: 1;
    border-radius: 999px;
    vertical-align: baseline;
    transition: color 150ms ease, background-color 150ms ease;
}
.event-list__compliment-refresh:hover,
.event-list__compliment-refresh:focus-visible {
    color: #6b5d44;
    background: rgba(70, 60, 40, 0.06);
    outline: none;
}
.event-list__compliment-refresh.is-spinning {
    animation: event-list-compliment-spin 380ms ease;
}
@keyframes event-list-compliment-spin {
    from { transform: rotate(0); }
    to   { transform: rotate(360deg); }
}
/* Trailing down-arrow pinned to the button's right edge. Sitting
   outside the text flow lets the two text lines center on each other
   alone -- without the arrow nudging the visual center to the right.
   Bigger glyph (2rem) + a bouncier ~12px drop on each round so the
   arrow reads as the eye-catching "go down here" cue, not a static
   chevron. The only motion on the page at rest, so it gets to play
   harder without competing. Respects prefers-reduced-motion. */
.find-events-btn .cta-arrow {
    position: absolute;
    right: 1.25rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 1.4rem;
    line-height: 1;
}
@media (prefers-reduced-motion: no-preference) {
    .find-events-btn .cta-arrow {
        animation: cta-arrow-bounce 1.8s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
    }
}
/* Drop + quick settle + brief lift past resting (the cubic-bezier's
   overshoot does the "boing"), then hold flat through the back half
   of the cycle so the bounce feels intentional, not jittery. */
@keyframes cta-arrow-bounce {
    0%        { transform: translateY(-50%); }
    35%       { transform: translateY(calc(-50% + 12px)); }
    55%       { transform: translateY(calc(-50% - 3px)); }
    70%, 100% { transform: translateY(-50%); }
}

/* --- Odometer (slot-machine number roll on the CTA count) ------------- */
.odo {
    display: inline-flex;
    vertical-align: baseline;
    font-variant-numeric: tabular-nums;
}
.odo__col {
    display: inline-block;
    width: 0.62em;
    height: 1em;
    line-height: 1em;
    overflow: hidden;
    text-align: center;
    vertical-align: baseline;
}
.odo__strip {
    display: block;
    transform: translateY(calc(var(--d, 0) * -1em));
    transition: transform 420ms cubic-bezier(0.2, 0.7, 0.2, 1);
    will-change: transform;
}
.odo__d {
    display: block;
    height: 1em;
    line-height: 1em;
}
@media (prefers-reduced-motion: reduce) {
    .odo__strip { transition: none; }
}

/* Summary bar appears ONLY in the collapsed state -- click it (or Enter
   when focused) to re-expand the full persona row. */
.persona-row__summary {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    cursor: pointer;
    padding: 0.4rem 0.85rem;
    /* Match .filter-panel__summary so both collapsed bars share the
       warm-tan panel-family color (no hard white box around it). */
    background: rgba(216, 210, 196, 0.6);
    border: 0;
    /* Same 0.28-alpha defined edge as the expanded .persona-row / the
       filter-panel summary above. */
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.28);
    border-radius: var(--radius-md);
    transition: background 120ms ease;
}
.persona-row__summary:hover,
.persona-row__summary:focus-visible {
    background: rgba(216, 210, 196, 0.85);
    outline: none;
}
.persona-row__summary-text {
    flex: 1;
    color: var(--color-fg);
    font-size: 1rem;
    line-height: 1.3;
}
/* Mirror the filter-panel summary's bold-token treatment so both
   collapsed bars share the same typographic hierarchy: each picked
   item renders bold; the "Moods:" / "Interests:" prefix stays
   regular weight. */
.persona-row__summary-text strong {
    font-weight: 600;
}
/* Shared with .filter-panel__summary-edit higher up in this file --
   the combined selector lives near the filter-panel block. */

/* Collapsed state: hide every child of persona-row except the summary.
   Sticky pinning is handled by the outer .filters-stack wrapper, which
   moves both panels together as one bar. */
.persona-row.is-collapsed {
    margin: 0 0 0.4rem;
    padding: 0.2rem 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
}
.persona-row.is-collapsed > *:not(.persona-row__summary) {
    display: none !important;
}
.persona-row.is-collapsed .persona-row__summary {
    display: flex;
}

/* Cold-load combined summary bar: one clickable line that replaces BOTH
   collapsed filter panels in the global is-filters-collapsed state. Reuses
   the summary-bar + cream Edit/Refine-pill vocabulary so it reads as part of
   the same family. Hidden outside that state. */
.filters-collapsed-summary {
    display: none;
    align-items: center;
    gap: 0.55rem;
    cursor: pointer;
    /* Tight top margin pulls the bar up close to the search box above
       (the two read as one input cluster); bottom margin sets it off the
       single separator below. */
    margin: 0.1rem 0 0.5rem;
    padding: 0.55rem 0.95rem;
    background: rgba(216, 210, 196, 0.85);
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.28);
    border-radius: var(--radius-md);
    color: #4a4537;
    font-size: 1rem;
    line-height: 1.3;
    transition: background 120ms ease;
}
.filters-collapsed-summary:hover,
.filters-collapsed-summary:focus-visible {
    background: rgba(216, 210, 196, 0.95);
    outline: none;
}
/* translateY lifts the compass off the text baseline so it sits visually
   centered with the cap height of the summary text. */
.filters-collapsed-summary__icon { font-size: 1.05rem; line-height: 1; transform: translateY(-2px); }
.filters-collapsed-summary__text strong { font-weight: 600; }
.filters-collapsed-summary__radius { color: var(--color-muted); font-size: 0.9rem; }
/* "Refine ›" pill -- shares the cream affordance treatment of the per-panel
   summary Edit pills (defined near .filter-panel__summary-edit). */
.filters-collapsed-summary__refine {
    margin-left: auto;
    color: var(--color-accent);
    font-weight: 600;
    white-space: nowrap;
    background: rgba(255, 250, 242, 0.78);
    border: 1px solid rgba(70, 60, 40, 0.22);
    border-radius: var(--radius-md);
    padding: 0.25rem 0.75rem;
    box-shadow: 0 1px 2px rgba(70, 60, 40, 0.10);
    transition: background 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
.filters-collapsed-summary:hover .filters-collapsed-summary__refine,
.filters-collapsed-summary:focus-visible .filters-collapsed-summary__refine {
    background: #fffaf2;
    box-shadow: 0 2px 6px rgba(70, 60, 40, 0.18);
    transform: translateY(-1px);
}
/* Show only in the global collapsed state; zero the now-redundant panel
   strips (kept in flow -- not display:none -- so expandFilterPanel can still
   measure scrollHeight for the slide-open animation). */
body.is-filters-collapsed .filters-collapsed-summary { display: flex; }
body.is-filters-collapsed #filters-stack > .filter-panel.is-collapsed,
body.is-filters-collapsed #filters-stack > .persona-row.is-collapsed {
    margin: 0;
    padding: 0;
}
/* One separator only: the collapsed filters-stack already draws its own
   bottom edge, so the standalone inputs/results divider below would be a
   second line. Hide it in this state. */
body.is-filters-collapsed .input-results-divider { display: none; }

.persona-row__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin: 0 0 0.5rem;
}
.persona-row__title-stack {
    /* Stacks the umbrella title + its tagline as one unit on the left
       so they read as a header block, while header-actions stays right. */
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.persona-row__title {
    font-size: 1.25rem;
    margin: 0;
}
.persona-row__header-actions {
    display: flex;
    gap: 0.75rem;
    align-items: baseline;
    flex: 0 0 auto;
}
.persona-row__link-btn,
.filter-panel__link-btn {
    background: none;
    border: 0;
    padding: 0.25rem 0.5rem;
    min-height: auto;
    /* Muted gray reads as a quiet utility action ("Clear", "Select all")
       rather than the clay-accent which had a destructive / warning
       register that worried less-confident users. Hover bumps to the
       deep sage so the affordance still feels live. */
    color: var(--color-muted);
    font-family: inherit;
    font-size: 1rem;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.persona-row__link-btn:hover,
.filter-panel__link-btn:hover { color: var(--color-primary-deep); }
/* The generic `button { display: inline-block }` in base.css beats the
   user-agent `[hidden] { display: none }` rule, so toggling the `hidden`
   attribute from JS has no visual effect without this explicit reassert. */
.persona-row__link-btn[hidden],
.filter-panel__link-btn[hidden] { display: none; }
.persona-row__hint {
    /* Now a tagline below the umbrella title rather than an inline
       parenthetical; applies to both "Pick by interest" and "Pick by
       mood" groups (the multi-select-with-union semantic). */
    margin: 0;
    color: var(--color-muted);
    font-size: 0.95rem;
    font-weight: normal;
}
.persona-row__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    padding: 0.85rem 1rem;
    border-radius: var(--radius-md);
    /* Both groups carry a subtle tint by default so they read as
       discrete grouping boxes (matches the where/when collapsed summary
       above). The darker active-group fill is layered on top when a
       chip in that group is selected. Warm-edge ring at 0.18 keeps
       the inner box demarcated without competing with the outer panel
       ring (0.28) or the chip borders (0.28) -- gives a soft hierarchy
       of edge weights (inner < outer == chip). */
    background: rgba(70, 60, 40, 0.08);
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.18);
    transition: background 180ms ease, box-shadow 180ms ease;
}
/* Quick Picks group: set apart from the bucket-level chip groups below
   it only by a lighter container background, so the eye reads it as a
   "shortcuts shelf". The chips themselves are dimensionally identical to
   the persona-bucket chips (same rounded-rect shape, size, icon scale,
   and hover/press animation) for a consistent button language. */
.persona-row__chips--quick-picks {
    background: rgba(70, 60, 40, 0.04);
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.14);
    padding: 0.6rem 1rem;
}
.quick-pick-chip {
    position: relative;
    border: 1px solid rgba(70, 60, 40, 0.28);
    background: var(--color-surface);
    color: var(--color-fg);
    padding: 0.5rem 1rem;
    min-height: 44px;
    /* radius-md + 44px min-height + 0.5rem/1rem padding so Quick Picks
       are dimensionally identical to the persona-bucket chips below --
       same rounded-rect button, not a smaller pill. */
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: 1rem;
    font-family: inherit;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Stretch to fill the row width, like the persona-bucket chips
       below. Both edges of every row land flush with the container,
       no jagged right margin. Icon + label stay centered together
       within the widened chip. */
    flex: 1 1 auto;
    transition: background 120ms ease, color 120ms ease,
                border-color 120ms ease, transform 150ms ease,
                box-shadow 150ms ease;
}
.quick-pick-chip:hover { background: var(--color-bg); }
.quick-pick-chip.is-active {
    background: var(--color-primary);
    color: #fff;
    border-color: var(--color-primary);
}
/* Trailing checkmark mirrors the persona-bucket chip convention
   (.persona-chip::after) so all selected chips on the page read the
   same "this is on" affordance. Glyph + space are reserved at rest
   via visibility:hidden so toggling doesn't shift layout. */
.quick-pick-chip::after {
    content: " \2713";
    font-weight: 700;
    visibility: hidden;
}
.quick-pick-chip.is-active::after {
    visibility: visible;
}
/* White halo around emoji glyphs when their chip is in the selected
   state (deep-green / clay-accent background). Without this, the
   multi-color emoji washes out against the dark fill -- the table-
   tennis paddle, billiards 8-ball, etc. become hard to read. Stacked
   drop-shadows simulate an outline more reliably than a single
   high-blur shadow. Applies to all three chip families (Quick Picks,
   persona buckets, mood / meta chips) so the contrast fix is
   consistent across the page. */
.quick-pick-chip.is-active .quick-pick-chip__icon,
.persona-chip.is-active .persona-chip__icon,
.persona-chip.is-active > span[aria-hidden="true"] {
    filter: drop-shadow(0 0 0.4px rgba(255, 255, 255, 0.75))
            drop-shadow(0 0 0.4px rgba(255, 255, 255, 0.75));
}
/* Active-group affordance: when one group is selected its container
   darkens further so the eye can tell which group is "on". JS toggles
   data-active-group on .persona-row between "" (no selection),
   "interests" (any persona chip on), or "mood". */
.persona-row[data-active-group="interests"] .persona-row__chips:not(.persona-row__chips--meta) {
    background: rgba(70, 60, 40, 0.18);
}
.persona-row[data-active-group="mood"] .persona-row__chips--meta {
    background: rgba(70, 60, 40, 0.18);
}
.persona-chip {
    position: relative;     /* anchors the ::before explanation tooltip */
    border: 1px solid rgba(70, 60, 40, 0.28);
    background: var(--color-surface);
    color: var(--color-fg);
    padding: 0.5rem 1rem;
    min-height: 44px;
    /* radius-md (shared with the where/when pills) so all toggleable
       chips on the page read as the same press-able button. Press-
       affordance shadow/lift lives in the shared chip block below. */
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: 1rem;
    font-family: inherit;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease,
                transform 150ms ease, box-shadow 150ms ease;
    /* Each chip grows to fill the row so chip boxes are flush against both
       left and right container edges. Content stays centered within the
       wider box. Last row may show wider chips when it carries fewer items
       than a full row -- accepted trade-off. */
    flex: 1 1 auto;
    text-align: center;
}
.persona-chip:hover { background: var(--color-bg); }
/* Icon bump on hover (the chip's own lift + shadow now comes from the
   shared chip-button block below, so this block is only for the icon
   transform). Gated behind reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
    /* Icon pops bigger AND lifts above the chip's own -3px hover lift,
       so the icon reads as jumping out of the chip rather than riding
       along with it. translate is added on top of the base scale-up
       so the icon ends up ~6-7px higher than rest when hovered. */
    .persona-chip:hover .persona-chip__icon,
    .persona-chip:hover > span[aria-hidden="true"],
    .quick-pick-chip:hover .quick-pick-chip__icon {
        transform: scale(1.85) translateY(-3px);
    }
    .persona-chip[data-persona-id="pickleball"]:hover .persona-chip__icon,
    .quick-pick-chip[data-persona-id="pickleball"]:hover .quick-pick-chip__icon {
        transform: scale(1.35) translateY(-3px);
    }
}
/* Visually larger icon without growing the chip's layout box: transform
   scales the rendered glyph but the element still occupies its 1x size
   in flow, so chips don't widen and the text next to it doesn't move.
   Selector covers both .persona-chip__icon (persona chips) and the bare
   aria-hidden emoji spans used by the mood chips. */
.persona-chip__icon,
.persona-chip > span[aria-hidden="true"],
.quick-pick-chip__icon {
    margin-right: 0.45rem;
    font-size: 1.1em;
    line-height: 1;
    display: inline-block;
    vertical-align: middle;
    transform: scale(1.55);
    transform-origin: center;
    transition: transform 150ms ease;
}
/* The yellow-disc glyph is solid, so it reads visually heavier than the
   line-art emojis on every other chip. Knock it down so the row balances. */
.persona-chip[data-persona-id="pickleball"] .persona-chip__icon,
.quick-pick-chip[data-persona-id="pickleball"] .quick-pick-chip__icon {
    transform: scale(1.15);
}
/* Explanation tooltip (uses ::before because ::after is the
   reserved-space checkmark slot). Same 100ms hover-delay pattern as
   the time-bucket and near-me tooltips. */
.persona-chip[data-tooltip]::before {
    content: attr(data-tooltip);
    position: absolute;
    top: 100%;
    left: 50%;
    /* width: max-content sizes the tooltip to its content first; without
       it, the absolute-positioned ::before is constrained by the chip's
       narrow padding box and wraps one word per line. max-width then
       caps long tooltips so they don't stretch ridiculously wide. */
    width: max-content;
    max-width: 320px;
    transform: translateX(-50%);
    margin-top: 8px;
    padding: 0.5rem 0.75rem;
    background: #4a3f2e;
    color: var(--color-bg);
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.35;
    border-radius: var(--radius-sm);
    box-shadow:
        0 6px 12px rgba(40, 30, 20, 0.18),
        0 20px 40px rgba(40, 30, 20, 0.40);
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease;
    z-index: 5;
}
.persona-chip[data-tooltip]:hover::before,
.persona-chip[data-tooltip]:focus-visible::before {
    opacity: 1;
    transition-delay: 100ms;
}
/* Elevate the chip itself on hover so its tooltip (rendered via
   ::before) paints ABOVE chips on subsequent flex-wrap rows. Without
   this bump, document order wins and row-N+1 chips cover the
   row-N tooltip. */
.persona-chip[data-tooltip]:hover,
.persona-chip[data-tooltip]:focus-visible,
.time-bucket-pill[data-tooltip]:hover,
.time-bucket-pill[data-tooltip]:focus-visible,
.near-me-row__village[data-tooltip]:hover,
.near-me-row__radius[data-tooltip]:hover,
.near-me-row__village[data-tooltip]:focus-within,
.near-me-row__radius[data-tooltip]:focus-within {
    z-index: 100;
}
.persona-chip.is-active {
    background: var(--color-primary);
    color: white;
    border-color: var(--color-primary);
}
/* Checkmark glyph on selected persona chips. ::before is already in
   use for the explanation tooltip, so the ✓ trails the label via
   ::after. Glyph + space are reserved at all times via the base rule;
   only visibility flips on .is-active so toggling doesn't cause a
   layout shift. Mood chips (--meta) opt out of the checkmark entirely
   via the override below. */
.persona-chip::after {
    content: " ✓";
    font-weight: 700;
    visibility: hidden;
}
.persona-chip.is-active::after {
    visibility: visible;
}

/* Mood chips: secondary "curated mix" path -- visually demoted vs the
   interest chips above so the user reads them as supplementary rather
   than a parallel filter section. Smaller padding + font + lighter
   border weight. Clay accent stays to distinguish them from persona
   chips, but at lower opacity so the row recedes. */
.persona-chip--meta {
    color: var(--color-accent);
    border-color: rgba(184, 125, 94, 0.35);
    /* Weight bumped 500 -> 600 so the clay-color label doesn't read
       as washed-out. Still demoted vs the interest chips via the
       lighter color + smaller font-size + lighter border. */
    font-weight: 600;
    /* Asymmetric padding -- emoji glyphs ride high above the text
       baseline, so equal top/bottom padding looks bottom-heavy. Add
       ~2px more on top to visually re-center the icon row. */
    padding: 0.65rem 0.85rem 0.5rem;
    min-height: 40px;
    font-size: 0.9rem;
}
.persona-chip--meta::after { content: none; }
.persona-chip--meta.is-active {
    background: var(--color-accent);
    color: white;
    border-color: var(--color-accent);
}

/* === SHARED CHIP-BUTTON PRESS AFFORDANCE ============================ */
/* All four toggleable filter affordances (village pills, time-bucket
   pills, radius chips, persona chips) read as the same kind of press-
   able button: a thin bottom shadow at rest implies a raised face,
   hover lifts the chip 1px with the shadow expanding, and the
   .is-active state inverts to a pressed-down look (no shadow, +1px).
   Shape (radius-md) and the green active fill are set on each chip's
   individual rule above; this block only adds the depth language.
   Color + size variations between families still differentiate
   primary/secondary/tertiary hierarchy. */
.village-pill,
.time-bucket-pill,
.radius-chip,
.persona-chip,
.quick-pick-chip {
    box-shadow: 0 1.5px 0 rgba(70, 60, 40, 0.18);
}
@media (prefers-reduced-motion: no-preference) {
    .village-pill:hover,
    .time-bucket-pill:hover,
    .radius-chip:hover,
    .persona-chip:hover,
    .quick-pick-chip:hover {
        /* -3px is enough lift to read as "the chip jumped at me"
           rather than the earlier -1px barely-noticeable shift. Paired
           with a two-stop shadow (tight ring + soft halo) so the
           depth grows with the lift. */
        transform: translateY(-3px);
        box-shadow:
            0 2px 0 rgba(70, 60, 40, 0.22),
            0 6px 12px rgba(40, 30, 20, 0.22);
    }
    .village-pill.is-active,
    .time-bucket-pill.is-active,
    .radius-chip.is-active,
    .persona-chip.is-active,
    .quick-pick-chip.is-active {
        transform: translateY(1px);
        box-shadow: none;
    }
}
/* Reduced-motion fallback: pressed-down state still clears the shadow
   (no transform) so the visual signal of "selected" is preserved. */
@media (prefers-reduced-motion: reduce) {
    .village-pill.is-active,
    .time-bucket-pill.is-active,
    .radius-chip.is-active,
    .persona-chip.is-active,
    .quick-pick-chip.is-active {
        box-shadow: none;
    }
}
/* Mood section header reads as a quieter "alternative path" -- same
   weight as the interest header above (600) so it doesn't feel
   detuned, but kept at 1rem (no demoted size) and recedes via the
   muted color from .persona-row__group-title above. */
.persona-row__chips--meta .persona-row__group-title {
    font-size: 1rem;
    font-weight: 600;
}

.persona-row__group-header {
    /* Wraps the group title + per-group action buttons. Sits as the
       first flex child of .persona-row__chips with flex-basis: 100% so
       the chips wrap below it. Title left, actions right. */
    flex-basis: 100%;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin: 0 0 0.4rem;
}
.persona-row__group-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-muted);
}
/* Inline title + hint stack. The title (h3, weight 600) anchors the
   left edge; the hint span trails on the same baseline. Toggling the
   hint's `hidden` only swaps text on the same line -- no vertical
   reflow of the chip grid below. */
.persona-row__group-title-stack {
    display: flex;
    align-items: baseline;
    column-gap: 0.5rem;
    flex-wrap: wrap;        /* allow wrap on narrow viewports without overflow */
    min-width: 0;
}
.persona-row__group-actions {
    display: flex;
    gap: 0.5rem;
    align-items: baseline;
    flex: 0 0 auto;
}
/* Empty-state hint that trails the group title on the same line when
   no interest chip is active ("Showing all events -- tap any interest
   to narrow."). Hides as soon as a chip becomes active. */
.persona-row__group-hint {
    color: var(--color-muted);
    font-size: 1rem;
    font-weight: normal;
    line-height: 1.3;
}
.persona-row__group-hint[hidden] { display: none; }

/* === SOUNDS-GOOD MODE SECTIONS ===================================== */
/* Each of the three filter modes (quick / activities / mix) is a
   collapsible section. Body animates max-height like .filter-panel.
   Active+collapsed -> green summary bar. Inactive+collapsed -> dim
   thin header row. */
.sounds-mode { margin-bottom: 0.4rem; }
.sounds-mode__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}
/* The Edit affordance in the header only shows when the section is
   collapsed; expanded sections use the Select all / Clear buttons. */
.sounds-mode__edit { display: none; }
.sounds-mode__body {
    overflow: hidden;
    transition: max-height 260ms ease, opacity 200ms ease,
        padding 260ms ease, margin 260ms ease, overflow 0s;
}
/* overflow:hidden above is only needed to clip the max-height collapse
   animation. When expanded it clips the last row's hover tooltip (it
   has nowhere to go below the box), so let an open section overflow.
   The overflow flip to visible is delayed until the expand transition
   finishes (overflow 0s 260ms) so expand still animates cleanly; the
   base rule above flips it back to hidden immediately on collapse. */
.sounds-mode:not(.is-collapsed) .sounds-mode__body {
    overflow: visible;
    transition: max-height 260ms ease, opacity 200ms ease,
        padding 260ms ease, margin 260ms ease, overflow 0s 260ms;
}
.sounds-mode__summary {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    /* Tight gap to the section title above -- the three collapsed rows
       are a compact summary state, so they don't need much vertical air. */
    margin-top: 0.2rem;
    padding: 0.4rem 1rem;
    min-height: 38px;
    border-radius: var(--radius-md);
    cursor: pointer;
    border: 1px solid rgba(70, 60, 40, 0.28);
}

/* Collapsed: hide the body + the expanded header actions, show summary.
   The body carries .persona-row__chips (content-box padding + margin),
   so max-height:0 alone leaves a ~22px padded strip -- zero the padding
   and margin too so the section fully compresses to its summary bar. */
.sounds-mode.is-collapsed .sounds-mode__body {
    max-height: 0;
    opacity: 0;
    padding-top: 0;
    padding-bottom: 0;
    margin-bottom: 0;
}
/* The group title doubles as the section's collapse/expand toggle, so a
   user can minimize the OPEN section (and see all three summaries) without
   having to open a different one. The caret rotates to show open/closed
   state; the hover/focus underline signals it's clickable. Touch users get
   no hover, so the persistent caret is their affordance. */
.sounds-mode .persona-row__group-title {
    cursor: pointer;
    user-select: none;
}
.sounds-mode .persona-row__group-title::before {
    content: '\203A';   /* › */
    display: inline-block;
    margin-right: 0.35rem;
    color: rgba(70, 60, 40, 0.5);
    transition: transform 120ms ease;
}
.sounds-mode:not(.is-collapsed) .persona-row__group-title::before {
    transform: rotate(90deg);
}
.sounds-mode .persona-row__group-title:hover,
.sounds-mode .persona-row__group-title:focus-visible {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.sounds-mode.is-collapsed .persona-row__group-actions
    > .persona-row__link-btn:not(.sounds-mode__edit) { display: none; }
.sounds-mode.is-collapsed .sounds-mode__summary { display: flex; }
/* The per-persona "Narrow X / tap to refine" panel is a SIBLING of
   .sounds-mode__body (not inside it), so the body-collapse rule above
   doesn't catch it. Without this, a collapsed "What activities are you
   into?" still spills its full narrow list out under Quick picks. Hide
   it with the section: the collapsed activities mode is just its
   "Activities - All / Edit" summary, and expanding it reveals the
   personas + their refine rows. */
.sounds-mode.is-collapsed .persona-narrow,
.sounds-mode.is-collapsed .persona-narrow-toggle { display: none; }

/* Active + collapsed: full green bar (matches selected-chip fill). */
.sounds-mode.is-active.is-collapsed .sounds-mode__summary {
    background: var(--color-primary);
    color: #fff;
    border-color: var(--color-primary);
    box-shadow: 0 1.5px 0 rgba(70, 60, 40, 0.18);
}
/* Inactive + collapsed: dim thin header row, muted summary text. */
.sounds-mode:not(.is-active).is-collapsed .sounds-mode__summary {
    background: rgba(70, 60, 40, 0.04);
    color: var(--color-fg-muted, rgba(70, 60, 40, 0.7));
    min-height: 32px;
}
.sounds-mode:not(.is-active).is-collapsed .sounds-mode__summary-text { opacity: 0.7; }

@media (prefers-reduced-motion: reduce) {
    .sounds-mode__body { transition: none; }
}

.link-btn {
    background: none;
    border: none;
    color: var(--color-accent);
    text-decoration: underline;
    cursor: pointer;
    min-height: 32px;
    min-width: auto;
    padding: 0.25rem 0.5rem;
    font-size: 0.95rem;
    font-family: inherit;
}

/* --- Home layout (drawer + side-by-side list/map) -------------------- */

/* The layout is a 2-col grid: filter drawer on the left + content on the
   right. The content is itself a flex row of [list, map]. Both panes fill
   the available height and scroll independently. Closing the drawer
   collapses the left column to 0 width via the .is-drawer-closed modifier;
   the grid-template-columns transition handles the slide. */
/* The discovery page is map+list dense and wants the full viewport width;
   1200px (base.css default) leaves a lot of dead margin on wide screens.
   Scoped via body.page-home so event detail / venue detail keep the
   readable narrower column. site-header + site-footer follow main's
   width so the page isn't a narrow-wide-narrow sandwich. */
body.page-home main,
body.page-home .site-header,
body.page-home .site-footer {
    max-width: none;
}

/* Make the form a vertical flex container so .home-layout can flex:1 to
   fill the space below the chip rows. main itself is flexed in base.css,
   so the chain is body -> main -> form -> .home-layout -> panes. */
#filter-form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
.home-layout {
    --drawer-w: 230px;
    display: flex;
    flex-direction: row;
    gap: 1.5rem;
    padding-bottom: 1rem;
    width: 100%;
    /* A DEFINITE viewport-relative height is required: the drawer's content
       (11 expanded persona groups) is ~3700px tall, and without a cap it
       forces the whole .home-layout (and the body) to that size. Internal
       containers (.drawer .filters, .scrollable-list, #home-map) then
       handle their own scroll within this fixed box. */
    height: 70vh;
    min-height: 500px;
}
/* The map-under-bar scroll guard lives in persona-chips.js as a
   scroll-event clamp. position:sticky on .home-layout didn't engage
   reliably inside the flex parents (#filter-form -> main -> body, all
   flex-col), so we just intercept scroll directly. See the
   "Scroll-clamp" block in persona-chips.js. */
/* The drawer is a fixed-width flex item; transitioning flex-basis to 0
   collapses it cleanly while keeping the content clipped during the
   animation. */
.drawer {
    flex: 0 0 var(--drawer-w);
    overflow: hidden;
    height: 100%;
    transition: flex-basis 250ms ease;
}
.home-layout.is-drawer-closed .drawer {
    flex-basis: 0;
}
/* The collapsed drawer is still a flex item, so the row gap is left
   behind as dead space on the left of the cards. Drop it when closed so
   the content split slides flush against the full-width bars above. */
.home-layout.is-drawer-closed {
    gap: 0;
}
.drawer .filters {
    width: var(--drawer-w);
    height: 100%;
    overflow-y: auto;
    /* Symmetric horizontal padding so the focus outline on the search
       input (3px outline + 2px offset = 5px reach) has room on the left
       and isn't clipped against the drawer's edge. */
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    box-sizing: border-box;
}
/* Search input + any future text fields inside the drawer: full width
   of the drawer so they don't overflow past the right edge (where the
   focus ring would otherwise get clipped). The 0.5rem right padding on
   the drawer keeps the input shy of the scrollbar. */
.drawer .filters input[type="text"] {
    width: 100%;
    box-sizing: border-box;
}
/* Search input + inline X clear. Used both in the drawer (legacy) and
   in the home-page .home-search-row -- styles are scoped to
   .search-input-wrap (not the drawer) so they apply wherever the
   wrapper lives. The label is the positioning container; the button
   overlays the input's right edge so a single click wipes the q value.
   Hidden when the input is empty so an idle search box doesn't carry
   a dead control. */
.search-input-wrap {
    position: relative;
    display: block;
}
.search-input-wrap input[type="text"] {
    padding-right: 2rem;
}
.search-clear-btn {
    position: absolute;
    right: 0.45rem;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: 0;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 1.2rem;
    line-height: 1;
    padding: 0.25rem 0.4rem;
    min-width: auto;
    min-height: auto;
    font-family: inherit;
}
.search-clear-btn:hover { color: var(--color-cancelled); }
.search-clear-btn[hidden] { display: none; }

/* Home-page search row: lives between the persona/mood chip pickers
   and the CTA banner. The leading magnifying-glass glyph plus the
   wider placeholder ("Search events by title or description") make
   the input self-explanatory; the inline X (shared .search-clear-btn
   above) wipes the value with one click. */
.home-search-row {
    margin: 0 0 0.85rem;
}
/* In the collapsed state the search row sits INSIDE .filters-stack.is-
   collapsed, which already supplies the 1rem horizontal inset via its
   padding -- so no horizontal margin (it would double-inset). The bottom
   margin (0.35rem) plus the stack's 0.3rem bottom padding = 0.65rem below the
   search box -- a touch more than the 0.5rem the combined summary bar sets
   ABOVE it, so the search row sits with a hair more breathing room below
   before the cluster's bottom edge. */
body.is-filters-collapsed .home-search-row {
    margin: 0 0 0.35rem;
}
.home-search-input {
    position: relative;
    display: block;
}
.home-search-input input[type="text"] {
    width: 100%;
    box-sizing: border-box;
    padding-left: 2.4rem;
    /* Right padding leaves enough room for the X to sit further
       inward (right: 1.2rem on the home-page override below) without
       text running under it. Vertical alignment of this X with the
       search-info X below depends on these matching offsets. */
    padding-right: 3rem;
    font-size: 1rem;
}

/* Home-page override of the shared .search-clear-btn position so the
   X visually lines up with the search-info X below (which is held away
   from the right edge by the pill's rounded corner). The shared
   .search-clear-btn rule used by the drawer search keeps its tighter
   default. */
.home-search-input .search-clear-btn {
    right: 1.2rem;
    font-size: 1.4rem;
}
.home-search-icon {
    position: absolute;
    left: 0.7rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 1.1rem;
    color: var(--color-muted);
    pointer-events: none;
    line-height: 1;
}

.content-split {
    /* Grow to fill the home-layout space the drawer doesn't claim. Without
       flex:1, this collapses to its content width and the map disappears. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: row;
    gap: 1rem;
    height: 100%;
    min-width: 0;
}
.list-pane {
    /* Even 50/50 split with the map. Cards got taller / busier (bigger
       activity glyph, full-height venue thumb, FAB + chip ↗ + admin
       pencil) and were crowding their content at the prior 42% width.
       Map at 50% still works as the "where is this" spatial confirmer
       even if fewer venue pins fit at the default zoom. */
    flex: 0 0 50%;
    display: flex;
    flex-direction: column;
    height: 100%;
    min-width: 0;
    min-height: 0;
}
/* Drawer-toggle row -- sits between the CTA banner and the home-layout
   so the cards/map area is clean (the button used to float over the
   top-left of the event list and obscure the first card). Mirrors the
   collapsed-state 1rem horizontal inset on .cta-banner so the toggle
   lines up with the bars above. */
.drawer-toggle-bar {
    display: flex;
    margin: 0 0 0.5rem;
}
body.is-filters-collapsed .drawer-toggle-bar {
    margin: 0 1rem 0.5rem;
}
.drawer-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    /* Warm-tan pill backdrop mirrors the collapsed summary bars above
       (and is readable over the fading list edge). Subtle shadow lifts
       it off the cards beneath. */
    background: rgba(216, 210, 196, 0.92);
    color: var(--color-accent);
    border: 0;
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.15), 0 2px 6px rgba(70, 60, 40, 0.18);
    padding: 0.35rem 0.7rem;
    border-radius: var(--radius-pill);
    cursor: pointer;
    font-size: 0.9rem;
    font-weight: 600;
    font-family: inherit;
    min-height: auto;
    min-width: auto;
}
.drawer-toggle:hover { background: rgba(216, 210, 196, 1); }
.drawer-toggle__chevron {
    width: 0;
    height: 0;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    border-right: 6px solid currentColor;
    transition: transform 200ms ease;
}
/* Chevron flips to point right when the drawer is closed (the .drawer-
   toggle now lives in its own bar between the CTA banner and the
   home-layout, so .home-layout.is-drawer-closed no longer contains it
   -- home-drawer.js mirrors the flag onto <body> for cases like this). */
body.is-drawer-closed .drawer-toggle__chevron {
    transform: rotate(180deg);
}
.map-pane {
    flex: 1 1 50%;
    min-width: 0;
    height: 100%;
    /* Stack the discovery hint above the actual map area. The map
       itself takes the remaining height via flex:1 below. */
    display: flex;
    flex-direction: column;
}
.map-pane__hint {
    margin: 0 0 0.4rem;
    color: var(--color-muted);
    font-size: 1rem;
    line-height: 1.25;
    flex: 0 0 auto;
}
.map-pane #home-map {
    flex: 1 1 auto;
    min-height: 0;
}


/* Bump Leaflet's stock zoom +/- buttons up to the site's --target-min
   (44px, the touch-target minimum we use everywhere else for a11y).
   ID-prefix raises specificity above Leaflet's own `.leaflet-touch
   .leaflet-bar a` rule, which is loaded later in the page and would
   otherwise win on tie. */
#home-map .leaflet-control-zoom-in,
#home-map .leaflet-control-zoom-out {
    width: var(--target-min);
    height: var(--target-min);
    line-height: var(--target-min);
    font-size: 1.5rem;
}

/* Desaturate OpenStreetMap tiles so the bright orange roads + saturated
   land colors don't visually fight the muted Sage & Clay chrome palette.
   Saturation 0.7 keeps area types (parks, water, residential) clearly
   differentiated but tones the whole map into the page's color space.
   Note: Leaflet's own CSS (loaded after this one) sets
   `.leaflet-tile { filter: inherit; }`, which would override a rule
   placed directly on `.leaflet-tile`. We set the filter on the tile
   container -- the tile's direct parent -- so the tile's `inherit`
   resolves to our value. */
.leaflet-tile-container {
    filter: saturate(0.7) contrast(0.95);
}
.leaflet-tile {
    filter: saturate(0.7) contrast(0.95);
}

#home-map {
    height: 100%;
    border-radius: 6px;
    overflow: hidden;
    /* Soft fade-to-transparent on all four edges. Two linear masks crossed
       with `intersect` give a uniform inset fade instead of a vignette. */
    --map-fade: 20px;
    -webkit-mask-image:
        linear-gradient(to right, transparent, #000 var(--map-fade), #000 calc(100% - var(--map-fade)), transparent),
        linear-gradient(to bottom, transparent, #000 var(--map-fade), #000 calc(100% - var(--map-fade)), transparent);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to right, transparent, #000 var(--map-fade), #000 calc(100% - var(--map-fade)), transparent),
        linear-gradient(to bottom, transparent, #000 var(--map-fade), #000 calc(100% - var(--map-fade)), transparent);
    mask-composite: intersect;
}
.scrollable-list {
    flex: 1 1 auto;
    overflow-y: auto;
    border: 1px solid #eee;
    border-radius: 6px;
    /* Horizontal padding is generous so the hover scale-up (1.03) on
       cards doesn't clip against the list edges. No TOP padding: it would
       sit above the sticky feed band header's pin point (top:0 lands at
       the padding edge), leaving a strip where cards peek over the label.
       Bottom padding stays so the last card clears the fade. */
    padding: 0 1rem 0.5rem;
    min-height: 0;
    --list-fade: 20px;
    /* Bottom-only fade ("more content below"). The old top fade dated from
       the floating-Filters-button era (Phase-2 shell replaced it) and made
       the sticky band header's top edge translucent, so cards sliding behind
       it bled through. Opaque from the top so the pinned header fully
       occludes the cards passing under it. */
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--list-fade)), transparent);
    mask-image: linear-gradient(to bottom, #000 calc(100% - var(--list-fade)), transparent);
}
/* With the drawer closed there's no left column, so pull the list box
   left by its own padding to land the cards flush with the bars above.
   The padding stays (negative margin only shifts the box) so the card
   hover scale-up still has its left-edge headroom. */
body.is-drawer-closed .scrollable-list {
    margin-left: -1rem;
}

/* --- Persona-aware sidebar ------------------------------------------ */

.interests-header {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-bottom: 0.25rem;
}
/* "Reset all" sits alone on the left now that the close-X has been
   removed. flex-start keeps the layout left-aligned even if more
   actions get added later. */
.interests-header__actions {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: flex-start;
    gap: 0.25rem;
}
/* .drawer-close-btn removed -- the "Filters" pill at the top is the
   single drawer toggle. */

/* Dismissible search-filter pill above the event list. Renders only
   when the q (text search) param is active; provides the loud,
   unmissable indicator of an in-effect search now that the search
   input itself clears on submit. */
.search-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.6rem 0.4rem 0.8rem;
    margin: 0 0 0.75rem;
    background: rgba(184, 125, 94, 0.14);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-pill);
    color: var(--color-fg);
    font-size: 0.9rem;
    line-height: 1.2;
}
.search-pill__label { color: var(--color-fg); }
.search-pill__label strong {
    color: var(--color-accent);
    font-weight: 700;
}
.search-pill__close {
    background: transparent;
    border: 0;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 1.2rem;
    line-height: 1;
    padding: 0 0.25rem;
    min-width: auto;
    min-height: auto;
    font-family: inherit;
}
.search-pill__close:hover { color: var(--color-cancelled); }

.persona-group {
    margin-bottom: 0.5rem;
    /* --stripe-color is set by the global .persona-X rules in base.css
       (one per persona). Deliberately NOT defaulted to transparent here
       -- that would tie the cascade and win over the .persona-X classes,
       leaving every header stripe invisible. */
}
.persona-group summary {
    cursor: pointer;
    padding: 0.5rem;
    background: var(--color-bg);
    color: var(--color-fg);
    border-radius: var(--radius-sm);
    font-weight: 600;
    /* Hide the browser default disclosure marker so we can lay out the
       row as dot -> custom chevron -> label, instead of the default
       marker-first ordering. */
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.persona-group summary::-webkit-details-marker { display: none; }

/* Persona color dot. Order 1 puts it first, ahead of both the chevron
   and the label. Warm-edge ring matches the chip/card family so the
   dot reads as a defined object on the cream background. */
.persona-group summary::before {
    content: "";
    order: 1;
    flex-shrink: 0;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 50%;
    background: var(--stripe-color);
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.22);
}
/* Custom expand/collapse chevron. Sits between the dot and the label
   so it reads paired with the name it controls. Rotates from
   right-pointing (closed) to down-pointing (open). */
.persona-group summary::after {
    content: "\25BC";
    order: 2;
    font-size: 0.7em;
    color: var(--color-muted);
    transition: transform 120ms ease;
}
.persona-group:not([open]) summary::after {
    transform: rotate(-90deg);
}
.persona-group__label { order: 3; }
/* Per-group select-all / clear shortcut row, shown only when the group
   has subcategories. Sits directly under the summary so the user sees
   the shortcut before scrolling through individual checkboxes. */
.persona-group__actions {
    display: flex;
    gap: 0.5rem;
    padding: 0.15rem 0.5rem 0.25rem 1.5rem;
}
.persona-group__action-btn {
    font-size: 0.8rem;
    padding: 0.1rem 0.4rem;
    min-height: auto;
    color: var(--color-muted);
    text-decoration: none;
}
.persona-group__action-btn:hover {
    color: var(--color-primary-deep);
    text-decoration: underline;
}
.persona-group .subcat-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    /* Left padding (1.5rem) intentionally exceeds the summary's
       (0.85rem) so the subcategory list sits visibly indented beneath
       its persona header -- the visual cue that "these belong to that". */
    padding: 0.15rem 0.5rem 0.15rem 1.5rem;
    line-height: 1.25;
    border-bottom: 1px solid var(--color-border);
}
.persona-group .subcat-row:hover {
    background: var(--color-bg);
}
/* Custom checkbox visual so the checkmark can be animated on the
   programmatic uncheck path (native checkboxes can't be transitioned).
   Default-state sage fill matches the rest of the warm-earth palette;
   manual user clicks still toggle as normal. */
.persona-group input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 18px;
    height: 18px;
    margin: 0;
    border: 1.5px solid rgba(70, 60, 40, 0.35);
    border-radius: 4px;
    background: var(--color-surface);
    position: relative;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 140ms ease, border-color 140ms ease;
}
.persona-group input[type="checkbox"]:hover {
    border-color: var(--color-primary);
}
.persona-group input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}
.persona-group input[type="checkbox"]:checked {
    background: var(--color-primary);
    border-color: var(--color-primary);
}
.persona-group input[type="checkbox"]:checked::after {
    content: "";
    position: absolute;
    left: 5px;
    top: 1px;
    width: 5px;
    height: 10px;
    border: solid white;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
    transform-origin: center;
}

/* Programmatic uncheck animation: while .is-unchecking is on a still-
   :checked box, the checkmark scales up (a deliberate "wave goodbye"
   beat) then falls out and fades, while the box itself emits a sage
   glow ring. After the animation, JS sets checked=false and removes
   the class. */
.persona-group input[type="checkbox"].is-unchecking {
    animation: checkbox-uncheck-glow 480ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
.persona-group input[type="checkbox"].is-unchecking::after {
    animation: checkmark-uncheck 420ms cubic-bezier(0.4, 0.0, 0.6, 1) forwards;
}
@keyframes checkmark-uncheck {
    0%   { opacity: 1; transform: rotate(45deg) scale(1) translateY(0); }
    35%  { opacity: 1; transform: rotate(45deg) scale(1.35) translateY(-2px); }
    100% { opacity: 0; transform: rotate(45deg) scale(0.15) translateY(7px); }
}
@keyframes checkbox-uncheck-glow {
    0%   { box-shadow: 0 0 0 0 rgba(125, 150, 112, 0.55); }
    40%  { box-shadow: 0 0 0 7px rgba(125, 150, 112, 0.38); }
    100% { box-shadow: 0 0 0 14px rgba(125, 150, 112, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .persona-group input[type="checkbox"].is-unchecking,
    .persona-group input[type="checkbox"].is-unchecking::after {
        animation: none;
    }
}
/* Row-level "this is the one" attention pulse fired by
   revealSubInFilter when a card-side tag pill is clicked. Triple-
   pulses sage so the user unmistakably sees which filter row
   corresponds to the chip they clicked; they then choose whether
   to uncheck. Position relative so the inset glow doesn't escape
   the row. */
.persona-group .subcat-row.is-emphasizing {
    position: relative;
    animation: subcat-row-emphasize 2200ms cubic-bezier(0.2, 0.7, 0.2, 1);
    z-index: 1;
}
@keyframes subcat-row-emphasize {
    /* Four sage pulses over ~2.2s, deliberately slower than a typical
       UI flash so an older user has time to register each beat. Reads
       as "that one, that one, that one, that one" rather than a brief
       blink that's easy to miss. */
    0%, 19%, 44%, 69%, 94%, 100% {
        background: transparent;
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(125, 150, 112, 0);
    }
    7%, 32%, 57%, 82% {
        background: rgba(125, 150, 112, 0.32);
        transform: scale(1.035);
        box-shadow: 0 0 0 5px rgba(125, 150, 112, 0.42);
    }
}
@media (prefers-reduced-motion: reduce) {
    .persona-group .subcat-row.is-emphasizing { animation: none; }
}

/* --- Event card with current-card cue ------------------------------- */

.event-card.is-current {
    /* Deeper-sage ring + a soft warm shadow lift make the focused card
       unmistakable next to the hover state (which uses the lighter
       primary sage). The inset stripe persists so the persona color
       stays visible when the scroll-sync ring is on. */
    box-shadow:
        0 0 0 3px var(--color-primary-deep),
        0 8px 18px rgba(70, 60, 40, 0.16),
        inset 7px 0 0 0 var(--stripe-color);
}

/* --- Map pin icons (Leaflet DivIcon) -------------------------------- */

.home-pin {
    background: transparent !important;
    border: 0;
    /* Center the visible .pin-dot within the larger transparent hit area
       (60x60 in JS) so seniors don't need precise aim to click a pin. */
    display: flex;
    align-items: center;
    justify-content: center;
}
.pin-dot {
    display: block;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 2px solid white;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.4);
    /* Anchor for the .pin-label so the label hangs off the dot's
       actual edge, not the 60x60 invisible hit-area parent. */
    position: relative;
}
/* Short venue name that hangs directly under each pin. Anchored off
   the dot/ring (not the larger hit-area parent), so the gap is the
   2px margin-top regardless of how big the hit area is. Hidden while
   this pin's own popup is open -- the popup already names the venue. */
.pin-label {
    position: absolute;
    top: 100%;
    left: 50%;
    margin-top: 6px;
    transform: translateX(-50%);
    background: var(--color-bg);
    color: var(--color-fg);
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1;
    padding: 2px 6px;
    border-radius: var(--radius-pill);
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.18);
    white-space: nowrap;
    pointer-events: none;
    /* CSS-side truncation: full venue name is rendered in HTML; we
       cap the visible width here and let the zoom tier (set on
       #home-map by home-map.js:updateMapZoomClass) bump the cap as
       the user zooms in and pins spread out. */
    max-width: 90px;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: opacity 140ms ease, max-width 200ms ease;
}
#home-map.pin-zoom-low  .pin-label { max-width: 80px; }
#home-map.pin-zoom-mid  .pin-label { max-width: 120px; }
#home-map.pin-zoom-high .pin-label { max-width: 180px; }
#home-map.pin-zoom-max  .pin-label { max-width: 260px; }
.home-pin.has-popup-open .pin-label,
.home-pin-ring.has-popup-open .pin-label {
    opacity: 0;
}
.home-pin-highlight { background: transparent !important; border: 0; }
.pin-highlight {
    /* Bigger head so the persona icon (emoji) reads at glance distance. */
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 34px;
    height: 34px;
    margin: 0 auto;
    border-radius: 50%;
    border: 3px solid white;
    box-shadow: 0 0 0 2px #111, 0 2px 6px rgba(0,0,0,0.3);
}
.pin-icon {
    font-size: 18px;
    line-height: 1;
    /* Emoji glyphs inherit the parent background's text color, but the
       glyph itself is colored. drop-shadow gives a hint of separation
       against bright persona backgrounds. */
    filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.25));
}
.pin-highlight::after {
    /* Spike that drops from the bottom of the head onto the event location. */
    content: '';
    position: absolute;
    top: calc(100% - 4px);
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 14px solid #111;
    filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}
/* --lift-y is set by home-map.js per-transition, computed from the pin's
   container-Y position so every pin lifts/falls "just enough to clear
   the map" regardless of where in the viewport it sits. Falls back to
   260px if unset (e.g., when the variable isn't applied yet). */
.drop-in .pin-highlight {
    animation: drop-fall 480ms ease-in;
}
@keyframes drop-fall {
    0%   { transform: translate(0, calc(-1 * var(--lift-y, 260px))); opacity: 0; }
    15%  { transform: translate(0, calc(-1 * var(--lift-y, 260px))); opacity: 0.4; }
    100% { transform: translate(0, 0);                                opacity: 1; }
}

/* Lift-out: pin shoots straight up by --lift-y and fades out over 500ms
   (ease-in so it accelerates like an object pushing off gravity).
   LIFT_MS in home-map.js must match. */
.lift-out .pin-highlight {
    animation: lift-out 350ms ease-in forwards;
}
@keyframes lift-out {
    0%   { transform: translate(0, 0);                                opacity: 1; }
    100% { transform: translate(0, calc(-1 * var(--lift-y, 260px))); opacity: 0; }
}

/* Anvil-drop dust: a radial-gradient ellipse spawned at the spike tip
   when the dropping pin lands (~75% of drop-bounce). Sized + colored
   for visibility against OpenStreetMap's gray-on-gray tile backgrounds:
   darker base, higher peak opacity, larger footprint, and a subtle
   drop-shadow outline for contrast on light areas. */
.pin-impact {
    position: absolute;
    top: 100%;
    left: 50%;
    margin-top: 8px;
    margin-left: -45px;
    width: 90px;
    height: 26px;
    border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(ellipse at center,
        rgba(60, 60, 60, 0.85)  0%,
        rgba(60, 60, 60, 0.55) 30%,
        rgba(60, 60, 60, 0.18) 55%,
        transparent 78%);
    filter: drop-shadow(0 1px 1px rgba(255, 255, 255, 0.5));
    animation: pin-impact 750ms ease-out forwards;
}
@keyframes pin-impact {
    0%   { transform: scale(0.15, 0.25); opacity: 0; }
    14%  { transform: scale(1, 1);       opacity: 1; }
    100% { transform: scale(2.4, 1.8);   opacity: 0; }
}

/* --- Cluster pin popup (venue summary) ------------------------------ */

/* Hide Leaflet's default tail/arrow so the popup is a free-floating
   rounded rectangle. Without the tail there's no pointer trying to land
   on the pin, so the offset can be approximate. */
.leaflet-popup-tip-container,
.leaflet-popup-tip {
    display: none;
}
/* Replace Leaflet's stock chrome with the project's design tokens: cream
   surface, warmer border, generous rounded corners, two-stop warm
   shadow matching the elevation language used by the hover tooltips so
   the popup reads as clearly floating above the map. */
.leaflet-popup-content-wrapper {
    background: var(--color-bg);
    color: var(--color-fg);
    /* Harder warm edge than the chip/image vocabulary (which is 0.28).
       The map's underlying noise (street lines, terrain shading, pin
       labels) competes with a soft border, so the popup needs a more
       defined transition to read as a clear floating surface above
       the map. Drop shadow stays for elevation; the border does the
       demarcation. */
    border: 1.5px solid rgba(70, 60, 40, 0.5);
    border-radius: var(--radius-md);
    box-shadow:
        0 6px 12px rgba(40, 30, 20, 0.18),
        0 20px 40px rgba(40, 30, 20, 0.40);
    padding: 0;
    /* Clip the corner-bled .popup-image to the rounded top-right corner.
       The close button lives outside this wrapper, so it isn't clipped. */
    overflow: hidden;
}
/* Descendant selector (not bare .leaflet-popup-content) so this beats
   Leaflet's own .leaflet-popup-content rule, which is lazy-loaded by
   home-map.js AFTER this file and would otherwise win the cascade (same
   specificity, later load) -- pinning the inset at Leaflet's 24px instead
   of our 1rem. The .popup-image bleed below cancels exactly 1rem/0.85rem,
   so this MUST resolve to those values or a right-edge gap appears. */
.leaflet-popup-content-wrapper .leaflet-popup-content {
    margin: 0.85rem 1rem;
    line-height: 1.4;
    /* min-width is the floor that Leaflet's content auto-sizing
       respects. Bumped 220 -> 280 so event titles like "Line Dance
       for Exercise" and "Pattern Partner - Basic Plus" have room to
       sit on one line. Pair with the maxWidth 360 -> 400 ceiling in
       home-map.js so longer titles can stretch further when needed. */
    min-width: 280px;
}
/* Venue thumbnail, corner-bled into the popup's top-right to echo the
   event card's full-bleed photo (treatment C). Still float:right so the
   name/address wrap to its left and the ul's clear:both drops the divider
   below it -- but negative top/right margins pull it flush to the wrapper's
   inner top + right edges (escaping .leaflet-popup-content's 0.85rem/1rem
   inset). Only the outer (top-right) corner is rounded; a 1px inset seam on
   the inner (left + bottom) edges keeps a defined edge where photo meets
   body. The wrapper's overflow:hidden clips the bleed to the rounded
   corner. The restyled close button lives OUTSIDE the wrapper and overhangs
   this corner (~3/4 outside the popup), so a sliver sits over the photo --
   the white badge holds its own contrast, no on-photo X needed. */
.popup-image {
    float: right;
    width: 112px;
    height: 96px;
    object-fit: cover;
    margin: -0.85rem -1rem 0.4rem 0.6rem;
    border: 0;
    border-radius: 0 var(--radius-md) 0 0;
    box-shadow:
        inset 1px 0 0 0 rgba(70, 60, 40, 0.30),
        inset 0 -1px 0 0 rgba(70, 60, 40, 0.30);
}
/* Venue name -- promote it visually so it reads as the popup's heading. */
.popup-venue {
    display: block;
    font-size: 1.05rem;
    color: var(--color-primary-deep);
    margin-bottom: 0.1rem;
    font-weight: 700;
}
/* When the name is a link to the venue page (the default for live venues),
   suppress the default underline so it still reads as a heading; underline
   on hover is the linkability hint. The .leaflet-popup-content prefix is
   needed to beat Leaflet's `.leaflet-container a { color: #0078A8 }`. */
.leaflet-popup-content a.popup-venue {
    color: var(--color-primary-deep);
    text-decoration: none;
}
.leaflet-popup-content a.popup-venue:hover { text-decoration: underline; }
/* Soft "go to" affordance: muted next to the name, slides right on hover.
   margin-left negative-trims the default space so the chevron sits close. */
.popup-venue__chevron {
    display: inline-block;
    margin-left: 0.35rem;
    color: var(--color-muted);
    /* Sized in em (not rem) so the chevron scales with whichever
       text it sits next to. Bumped from a default-size glyph to
       1.3em so the "leads somewhere" affordance is unmistakable
       at arm's length -- this audience is 55+. */
    font-size: 1.3em;
    font-weight: 600;
    line-height: 1;
    transition: transform 120ms ease, color 120ms ease;
}
.leaflet-popup-content a.popup-venue:hover .popup-venue__chevron {
    color: var(--color-primary-deep);
    transform: translateX(2px);
}
/* "No events match" message shown when a catalog-only (ring) venue is
   clicked. Subtle and italicized so it reads as a passive note. */
.popup-empty {
    margin-top: 0.5rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--color-border);
    color: var(--color-muted);
    font-size: 0.9rem;
    font-style: italic;
}
/* Hollow ring marker for the catalog layer -- shows "a venue is here, but
   it has no events matching the current filter." Smaller and subtler than
   the filled gray dots so attention stays on filter-relevant venues. */
.home-pin-ring {
    display: flex;
    align-items: center;
    justify-content: center;
}
.pin-ring {
    /* display: block so the inline span actually respects width/height
       (matches .pin-dot's pattern -- without this it renders as a
       vertical smudge because inline elements ignore explicit sizing). */
    display: block;
    width: 14px;
    height: 14px;
    border: 2.5px solid var(--color-fg);
    background: rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    opacity: 0.8;
    /* Outer halo gives the ring contrast on both light and dark map
       tile patches. */
    box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.9);
    /* Anchor for the .pin-label so it hangs off the ring's actual
       edge, not the 30x30 hit-area parent. */
    position: relative;
}
.home-pin-ring:hover .pin-ring { opacity: 0.9; }
.popup-time {
    font-weight: 600;
    color: var(--color-accent);
    white-space: nowrap;
}
/* Title column inside the 2-column grid (.popup-event-row). Wraps within
   its own column so long titles hang-indent under themselves instead of
   underrunning the time column. */
.popup-title {
    min-width: 0;
    overflow-wrap: anywhere;
}
.popup-address {
    color: var(--color-muted);
    font-size: 0.85rem;
    margin-top: 0.1rem;
    margin-bottom: 0.4rem;
}
/* Default browser <ul> padding-left + disc markers push the event list
   ~40px in, making the popup feel narrow. The bold time stamps already
   delineate each entry, so the bullets aren't earning their indent.
   Two-column grid lives HERE (not on each .popup-event-row) so the
   "auto" first column sizes to the widest time across every row. With
   subgrid on the rows below, "Tue 7:00 AM" and "Tue 10:00 AM" both
   land in a column sized to the wider of the two -- the title column
   then starts at the same x in every row instead of jagging in or out
   by one digit's width. */
.leaflet-popup-content ul {
    list-style: none;
    /* Negative side margins bleed the border-top divider edge-to-edge
       (out to the wrapper's inner border, where the photo also lands),
       while the matching 1rem side padding keeps the event rows inset.
       Echoes the full-bleed photo so the header/list split reads as
       deliberate. The wrapper's overflow:hidden clips the bleed. */
    margin: 0.5rem -1rem 0.4rem;
    padding: 0.4rem 1rem 0;
    border-top: 1px solid var(--color-border);
    /* Clear the floated .popup-image so the divider always sits below
       the image, not through it. Without this, short venue
       name + address text leaves the ul anchored beside the image and
       the border-top cuts across the thumbnail. */
    clear: both;
    display: grid;
    /* Third (auto) column holds the trailing chevron on each event row.
       Subgrid on .popup-event-row inherits this 3-column track so the
       chevrons align in their own column on the right. */
    grid-template-columns: auto 1fr auto;
    column-gap: 0.6rem;
    row-gap: 0.25rem;
}
.leaflet-popup-content li {
    /* Subgrid: this li's track sizes come from the parent ul, so the
       time + title spans inside the button land in the same columns
       the ul defined. grid-column: 1 / -1 makes the li span both
       parent columns. */
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    margin: 0;
    font-size: 0.92rem;
}
/* Popup event lines are <button>s so they're keyboard-accessible. Strip
   native button chrome; on hover/focus a soft tint indicates "click this
   to jump to the event card in the list and pulse-highlight it." */
.popup-event-row {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0.15rem 0.35rem;
    margin: 0 -0.35rem;
    width: calc(100% + 0.7rem);
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background-color 120ms ease;
    min-height: 0;
    min-width: 0;
    /* Subgrid the button into the parent <ul>'s columns so its time
       and title spans align to the SAME first-column width as every
       other row, not just the widest content in its own row. The
       parent's column-gap (0.6rem) is inherited automatically. */
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    align-items: baseline;
}
.popup-event-row:hover,
.popup-event-row:focus-visible {
    background: rgba(125, 150, 112, 0.12);
    outline: none;
}
/* "Also here" rows: the event is at this venue but outside the active
   filters, so there's no feed card to scroll to -- clicking opens its
   detail popup instead. Dim it slightly so the distinction from the
   "in your filters" rows reads as intentional, not broken. It lifts back
   to full strength on hover/focus to confirm it's still interactive. */
.popup-event-row--other {
    opacity: 0.62;
}
.popup-event-row--other:hover,
.popup-event-row--other:focus-visible {
    opacity: 1;
}
/* "Goes somewhere" chevron at the end of each event row. Mirrors the
   .popup-venue__chevron treatment so the popup speaks one visual
   language: muted by default, primary-deep + slight slide on hover. */
.popup-event-row__chevron {
    color: var(--color-muted);
    /* Larger + bolder than the surrounding row text so it reads
       clearly as a "leads somewhere" affordance at arm's length.
       em-based so it tracks the row's font scale. */
    font-size: 1.3em;
    font-weight: 600;
    line-height: 1;
    transition: transform 120ms ease, color 120ms ease;
    align-self: center;
}
.popup-event-row:hover .popup-event-row__chevron,
.popup-event-row:focus-visible .popup-event-row__chevron {
    color: var(--color-primary-deep);
    transform: translateX(2px);
}
.leaflet-popup-content .popup-more {
    display: inline-block;
    margin-top: 0.55rem;
    padding: 0.25rem 0.6rem;
    color: var(--color-bg);
    background: var(--color-primary);
    font-weight: 600;
    font-size: 0.85rem;
    text-decoration: none;
    border-radius: var(--radius-sm);
    transition: background 120ms ease;
}
.leaflet-popup-content .popup-more:hover {
    background: var(--color-primary-deep);
    text-decoration: none;
}
/* Restyle Leaflet's default close: a larger X centered in a circular
   surface that overflows the popup's upper-right corner. The overflow
   floats it against the map tiles instead of melting into the popup,
   and the bigger glyph reads as an actual control rather than a ghost
   character. Warm rgba(70,60,40,0.28) edge matches the chip vocabulary
   used elsewhere. The × is Leaflet's own text content, just resized. */
.leaflet-popup-close-button {
    position: absolute !important;
    /* 30px button shifted out so its center lands exactly on the
       popup wrapper's top-right corner (button half-width = 15px,
       hence -15px offsets on both axes). */
    top: -15px !important;
    right: -15px !important;
    width: 30px !important;
    height: 30px !important;
    padding: 0 !important;
    /* Border weight matches the popup wrapper's edge (1.5px at 0.5)
       so the badge holds its own against the busy map tiles behind
       it -- the old 1px / 0.28 dissolved into terrain shading and
       road lines. Soft elevation shadow kept from the original. */
    border: 1.5px solid rgba(70, 60, 40, 0.5) !important;
    border-radius: 50% !important;
    background: var(--color-surface) !important;
    color: var(--color-fg) !important;
    font-size: 1.4rem !important;
    font-family: inherit !important;
    font-weight: 400 !important;
    line-height: 1 !important;
    text-decoration: none !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    box-shadow: 0 2px 6px rgba(70, 60, 40, 0.25);
    transition: background 120ms ease, border-color 120ms ease,
                transform 120ms ease;
}
.leaflet-popup-close-button:hover {
    background: var(--color-bg) !important;
    border-color: rgba(70, 60, 40, 0.45) !important;
    text-decoration: none !important;
    transform: scale(1.08);
}
/* Optical centering of the × glyph inside the circular close button.
   Leaflet 1.9.4 wraps the × in <span aria-hidden="true">. The parent's
   flex align-items/justify-content centers the span's line-box, but
   Atkinson Hyperlegible draws the × resting on the baseline -- which
   sits well below the line-box's geometric midline once you account
   for the descender region -- so the glyph reads as several pixels
   low inside the circle. Lifting the inner span lands the glyph's
   visual center on the circle's center. */
.leaflet-popup-close-button > span {
    display: inline-block;
    transform: translateY(-3px);
}

/* No special rules for the post-drop hint popup's close button --
   it now uses Leaflet's built-in closeButton (same as the click-on-
   marker popup), so the .leaflet-popup-close-button rule above
   styles both consistently. */

/* --- Persona chip activation celebration ---------------------------- */

/* When a persona chip is clicked into the active state, its icon
   bounces a few times in place -- a "kid jumping up and down because
   they got picked" effect. Three jumps of decreasing amplitude with a
   tiny settle bounce. Base scale stays 1.55 (matches the always-on
   icon scale rule) so the icon returns to rest at the same size it
   started. Gated behind prefers-reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
    .persona-chip.is-celebrating .persona-chip__icon,
    .persona-chip.is-celebrating > span[aria-hidden="true"] {
        animation: chip-icon-celebrate 1700ms cubic-bezier(0.34, 1.56, 0.64, 1);
        transform-origin: center;
    }
}
@keyframes chip-icon-celebrate {
    0%   { transform: scale(1.55) translateY(0); }
    12%  { transform: scale(2.05) translateY(-16px); }
    24%  { transform: scale(1.55) translateY(0); }
    38%  { transform: scale(1.9)  translateY(-11px); }
    50%  { transform: scale(1.55) translateY(0); }
    62%  { transform: scale(1.75) translateY(-6px); }
    74%  { transform: scale(1.55) translateY(0); }
    86%  { transform: scale(1.65) translateY(-2px); }
    100% { transform: scale(1.55) translateY(0); }
}

/* --- Event card page-level modal ------------------------------------ */

/* Sits above Leaflet (which uses z-index up to ~700 for its top pane). */
.event-popover-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(42, 42, 38, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1500;
    padding: 1.5rem;
}
.event-popover-panel {
    background: var(--color-surface);
    border-radius: var(--radius-lg);
    /* Defined warm edge so the panel reads as a distinct surface against the
       busy map -- especially where the hero photo bleeds to the top edge.
       Pronounced (0.38) per the defined-edge convention on popup surfaces. */
    border: 1.5px solid rgba(70, 60, 40, 0.38);
    box-shadow:
        0 6px 14px rgba(40, 30, 20, 0.32),
        0 28px 56px rgba(40, 30, 20, 0.45),
        0 60px 120px rgba(20, 15, 10, 0.55);
    overflow: hidden;
    width: 720px;
    max-width: 95vw;
    max-height: 92vh;
    overflow-y: auto;
    position: relative;
}

/* --- Warm scrollbar, scoped to bounded inner scroll containers --------
   The event feed (#home-list.scrollable-list), the desktop filter drawer,
   and the event popover each scroll INDEPENDENTLY with a bounded height,
   so a styled bar is visible and on-brand there. The root WINDOW scrollbar
   is deliberately left native -- styling it would force an always-visible
   bar app-wide plus reserve a layout-shifting gutter on every page. Mobile
   isn't targeted: those surfaces either stack into the window scroll or use
   the OS overlay bar. Trade-off here: macOS shows an always-visible bar in
   these containers instead of the auto-hiding overlay -- intended, and
   limited to these few surfaces. scrollbar-width/color is the cross-browser
   standard; ::-webkit gives Chrome/Safari finer control. */
.event-popover-panel,
.scrollable-list,
.drawer .filters {
    /* "auto" (not "thin") gives a wider, easier-to-grab bar in Firefox --
       a drag target for mouse users, esp. seniors, not just a flick hint. */
    scrollbar-width: auto;
    scrollbar-color: rgba(70, 60, 40, 0.35) transparent;
}
.event-popover-panel::-webkit-scrollbar,
.scrollable-list::-webkit-scrollbar,
.drawer .filters::-webkit-scrollbar {
    /* 16px track -> ~12px visible thumb after the 2px inset border each side.
       Wider so it's an easy mouse-drag target for our 55+ audience. */
    width: 16px;
}
.event-popover-panel::-webkit-scrollbar-track,
.scrollable-list::-webkit-scrollbar-track,
.drawer .filters::-webkit-scrollbar-track {
    background: transparent;
}
.event-popover-panel::-webkit-scrollbar-thumb,
.scrollable-list::-webkit-scrollbar-thumb,
.drawer .filters::-webkit-scrollbar-thumb {
    background: rgba(70, 60, 40, 0.35);
    border-radius: 999px;
    /* Transparent border + padding-box clip insets the thumb so it floats
       off the container's rounded edge instead of hugging it. */
    border: 2px solid transparent;
    background-clip: padding-box;
}
.event-popover-panel::-webkit-scrollbar-thumb:hover,
.scrollable-list::-webkit-scrollbar-thumb:hover,
.drawer .filters::-webkit-scrollbar-thumb:hover {
    background: rgba(70, 60, 40, 0.55);
    background-clip: padding-box;
}
.event-popover-close {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 36px;
    height: 36px;
    min-width: 0;
    min-height: 0;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Dark bounding ring + offset shadow gives the button a clear
       circular silhouette on any hero photo -- white-on-white skies
       used to make it disappear into the image. */
    background: white;
    border: 1.5px solid rgba(40, 30, 20, 0.85);
    border-radius: 50%;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    z-index: 2;
    color: var(--color-fg);
    box-shadow:
        0 2px 6px rgba(40, 30, 20, 0.45),
        0 0 0 2px rgba(255, 255, 255, 0.6);
    font-family: inherit;
}
.event-popover-close:hover,
.event-popover-close:focus-visible {
    background: var(--color-bg);
    box-shadow:
        0 3px 8px rgba(40, 30, 20, 0.55),
        0 0 0 2px rgba(255, 255, 255, 0.75);
    outline: none;
}
.event-popover-mute {
    position: absolute;
    top: 8px;
    right: 52px;
    width: 34px;
    height: 34px;
    min-width: 0;
    min-height: 0;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: rgba(255, 255, 255, 0.95);
    border-radius: 50%;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    z-index: 2;
    color: var(--color-fg);
    box-shadow: var(--shadow-1);
    font-family: inherit;
}
.event-popover-mute:hover { background: white; }
.event-popover-panel .event-popup { width: auto; max-width: none; }
body.event-popover-open { overflow: hidden; }

/* --- Swingers gag popup -------------------------------------------- */
/* Reuses .event-popover-backdrop + .event-popover-panel chrome; this  */
/* rule set styles the interior of the gag message (no Continue path -- */
/* the whole chip is a joke, Cancel is the only option).               */
/* Override the default white panel with the warm site cream so the    */
/* gag feels in-world rather than browser-default. Accent-tinted       */
/* border gives it personality without losing contrast (text is still  */
/* --color-fg on cream, well within AA).                               */
/* Paper-edge look: SVG turbulence displaces a rect so the panel's outline
   becomes a soft, organic deckle. The mask MUST live on the inner body,
   not the outer panel: filter:drop-shadow and mask-image on the same
   element clip the shadow to the masked shape, so the shadow disappears
   outside the deckle. By splitting them -- mask on .swingers-gag-body,
   drop-shadow on .swingers-gag-panel -- the parent's filter sees the
   child's deckled silhouette and casts the shadow around it, unclipped.
   preserveAspectRatio='none' lets the mask fill the body at any size;
   the noise gets mildly stretched, which is invisible because it's
   noise. */
.swingers-gag-panel {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    /* Bold values because the 55% dark backdrop swallows subtle shadows.
       Wide outer halo cuts through the wash; tighter warm sepia sells
       the "paper on a table" feel. */
    filter: drop-shadow(0 30px 50px rgba(0, 0, 0, 0.55))
            drop-shadow(0 6px 14px rgba(60, 40, 20, 0.45));
}
.swingers-gag-body {
    background: var(--color-bg);
    padding: 2.5rem 2rem 1.75rem;
    text-align: center;
    max-width: 24rem;
    /* Center the visible paper inside the 720px .event-popover-panel.
       Without this, max-width pins the body to the panel's left edge
       and the popover looks offset ~168px left of viewport center. */
    margin: 0 auto;
    --paper-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none' viewBox='0 0 200 150'><defs><filter id='r' x='-5%25' y='-5%25' width='110%25' height='110%25'><feTurbulence type='fractalNoise' baseFrequency='0.08' numOctaves='2' seed='3'/><feDisplacementMap in='SourceGraphic' scale='5'/></filter></defs><rect x='5' y='5' width='190' height='140' fill='white' filter='url(%23r)'/></svg>");
    -webkit-mask-image: var(--paper-mask);
    mask-image: var(--paper-mask);
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}
.swingers-gag-icon {
    font-size: 5rem;
    line-height: 1;
    margin: 0 0 1.5rem;
    letter-spacing: 0.15em;  /* breathing room between the two dancers */
}
.swingers-gag-message {
    margin: 0 0 1.75rem;
    font-size: 1.375rem;
    line-height: 1.4;
    color: var(--color-fg);
}
.swingers-gag-cancel {
    background: var(--color-surface);
    color: var(--color-fg);
    border: 1px solid var(--color-border);
    padding: 0.5rem 1.75rem;
    min-height: 44px;
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 1rem;
    cursor: pointer;
}
/* On the cream gag panel the button now sits on a tinted surround, so   */
/* hover swaps to the accent tint instead of cream-on-cream (invisible). */
.swingers-gag-cancel:hover { background: #f0e8de; }

/* --- Swingers fake-stats split-flap counter ------------------------- */
/* Solari/Kayak airport-board styling. Sits above the dancer icon as a   */
/* mock "people clicked" tally. Number is regenerated client-side per   */
/* popup open -- not a real counter. The visible reroll on hover/click  */
/* (and the auto-play on mount) is what sells the gag.                  */
.swingers-flap-board {
    /* No outer chrome -- the dark per-cell flaps sit directly on the
       cream paper like an odometer strip. */
    margin: 0 0 2.5rem;
    cursor: pointer;
    user-select: none;
    text-align: center;
}
.swingers-flap-row {
    display: inline-flex;
    gap: 1px;
    margin: 0.25rem auto 0.4rem;
    align-items: flex-end;
}
.swingers-flap-sep {
    color: var(--color-fg);
    font-size: 2.25rem;
    line-height: 0.8;
    padding: 0;
    /* Pull the comma left toward the preceding digit and squeeze
       the next group in on its right so the comma reads as numeric
       punctuation glued to the group it follows. */
    margin: 0 -4px 0 -5px;
    transform: translateY(0.35rem);
    font-family: "Courier New", monospace;
    font-weight: 700;
}
.swingers-flap {
    position: relative;
    display: inline-block;
    width: 1.55rem;
    height: 2.25rem;
    background: #1a1410;
    color: #fff8dc;
    font-family: "Courier New", monospace;
    font-weight: 700;
    font-size: 1.95rem;
    line-height: 2.25rem;
    text-align: center;
    overflow: hidden;
    border-radius: 2px;
    box-shadow: inset 0 -1px 0 rgba(255,255,255,0.08);
}
/* Horizontal seam across each cell sells the split-flap look. */
.swingers-flap::after {
    content: "";
    position: absolute;
    inset: 50% 0 auto 0;
    height: 1px;
    background: rgba(0,0,0,0.85);
    pointer-events: none;
}
.swingers-flap.is-flipping {
    animation: swingers-flap-flip 130ms linear;
}
@keyframes swingers-flap-flip {
    0%   { transform: perspective(120px) rotateX(0deg); }
    50%  { transform: perspective(120px) rotateX(-90deg); }
    100% { transform: perspective(120px) rotateX(0deg); }
}
.swingers-flap-label {
    color: var(--color-fg);
    font-size: 1.15rem;
    font-weight: 700;
    margin: 0;
    text-align: center;
}
.swingers-flap-hint {
    color: #7a6a55;
    font-style: italic;
    font-size: 1.05rem;
    text-align: center;
    margin: 0 0 0;
}
@media (prefers-reduced-motion: reduce) {
    .swingers-flap.is-flipping { animation: none; }
}

/* --- Ken Burns popup ------------------------------------------------ */

.kb-stage {
    position: relative;
    width: 100%;
    /* 400px == the 600px popup width at the photos' native 3:2 ratio, so
       object-fit:cover shows the WHOLE image (no top/bottom trim) at rest.
       A taller band would instead start cropping the sides. */
    height: 400px;
    overflow: hidden;
    border-radius: 6px 6px 0 0;
    background: #000;
}
/* Standalone default hero on the venue page: the base .kb-stage rounds only
   its top (it normally sits above a card body), but here it's a freestanding
   hero, so give it full radius + the venue hero's shadow and spacing. */
.kb-stage--static.venue-detail__hero-kb {
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-2);
    margin-bottom: 1.5rem;
}
.kb-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 2000ms ease;
    /* Resting transform = the image's pan/zoom end-state (set per-image
       via CSS vars in ken-burns.js). The .is-current animation overrides
       this while playing; once the class is removed, the transform stays
       put rather than snapping back to identity, so the outgoing image
       fades out at its final zoomed/panned position. */
    transform:
        translate(var(--kb-end-x, 0%), var(--kb-end-y, 0%))
        scale(var(--kb-end-scale, 1));
}
.kb-image.is-current {
    opacity: 1;
    /* ease-out makes the motion slow as it approaches the end, giving a
       natural "settle" feel before the crossfade starts. `forwards` keeps
       the end transform during the dwell window between animation end
       and the next image's crossfade. Duration is set per-image via the
       --kb-duration CSS var (randomized 3-5s in ken-burns.js) so the
       cadence reads as a human camera operator, not a metronome. The
       fallback 4000ms only applies if the JS hasn't run yet. */
    animation-name: kb-pan-zoom;
    animation-duration: var(--kb-duration, 4000ms);
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}
/* End transform values are randomized per image in ken-burns.js by
   setting these CSS custom properties on the <img>, so every image
   pans/zooms in a different direction. Defaults match the prior fixed
   behavior in case the JS hasn't run yet.

   Order matters: translate THEN scale means the translate distance is
   measured in unscaled pixels, which makes the pan-vs-zoom edge math
   in ken-burns.js straightforward. */
@keyframes kb-pan-zoom {
    from {
        transform:
            translate(var(--kb-start-x, 0%), var(--kb-start-y, 0%))
            scale(var(--kb-start-scale, 1.0));
    }
    to   {
        transform:
            translate(var(--kb-end-x, -2%), var(--kb-end-y, -2%))
            scale(var(--kb-end-scale, 1.15));
    }
}
.event-popup {
    width: 600px;
    max-width: 90vw;
}
/* Static hero image at the top of the popup. Same 400px height the
   .kb-stage carousel occupies, so the popup geometry stays identical --
   only the imagery source swapped (venue.image_url with the generic
   Florida default as the fallback). */
.event-popup__hero {
    display: block;
    width: 100%;
    height: 400px;
    object-fit: cover;
    border-radius: 6px 6px 0 0;
    background: #000;
}
.event-popup__body { padding: 1.25rem 1.5rem 1.5rem; }
/* Title row: emoji glyph + h3 side-by-side. Glyph carries the same
   subtle drop-shadow the event card uses so the two surfaces feel like
   they're from the same family. */
.event-popup__title-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.4rem 1rem;
    flex-wrap: wrap;
    margin: 0 0 0.5rem;
}
/* Title group (icon + name) on the left of the title row. */
.event-popup__title-main {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    min-width: 0;  /* let a long title wrap instead of shoving the pills off */
}
.event-popup__icon {
    flex: 0 0 auto;
    font-size: 2.2rem;
    line-height: 1.05;
    filter: drop-shadow(0 2px 3px rgba(40, 30, 20, 0.32));
    cursor: pointer;  /* Easter egg: click replays the glyph's bounce */
}
/* Image variant: same visual weight as the emoji span (matches the
   title-row line-height) but rendered as a real <img>. */
.event-popup__icon--img {
    width: 2.4rem;
    height: 2.4rem;
    object-fit: contain;
    font-size: 0;
}
/* Popover icon "answer": when the event popover opens (it's freshly injected,
   so the animation auto-plays), its icon replays the SAME persona-specific
   motion the event card uses on hover -- for two iterations, then rests. Ties
   the clicked card to the popover and anchors the eye at the title before
   attention fans out to the details. Plays on all devices (open, not hover),
   gated only on reduced-motion; the ~250ms lead-in lets the panel settle
   first. Paired with the .event-card glyph map in base.css (~1280-1320) and
   reuses its keyframes -- keep the two in sync. Source order mirrors the card:
   default, then persona variants, then bucket-movie, then glyph-ball last so a
   ball subcategory wins regardless of persona. */
@media (prefers-reduced-motion: no-preference) {
    .event-popup .event-popup__icon {
        animation: event-card-glyph-bounce 540ms ease-out 250ms 2;
    }
    .event-popup.persona-dance .event-popup__icon {
        animation: event-card-glyph-sway 700ms ease-in-out 250ms 2;
    }
    .event-popup.persona-music_lovers .event-popup__icon,
    .event-popup.persona-live_entertainment .event-popup__icon {
        animation: event-card-glyph-pulse 540ms ease-out 250ms 2;
    }
    .event-popup.persona-arts_crafts .event-popup__icon {
        animation: event-card-glyph-tilt 620ms ease-out 250ms 2;
    }
    .event-popup.persona-water_sports .event-popup__icon {
        animation: event-card-glyph-wave 720ms ease-in-out 250ms 2;
    }
    .event-popup.persona-active_lifestyle .event-popup__icon {
        animation: event-card-glyph-jump 620ms ease-out 250ms 2;
    }
    .event-popup.persona-lawn_games .event-popup__icon {
        animation: event-card-glyph-toss-arc 660ms ease-out 250ms 2;
    }
    .event-popup.persona-lifelong_learning .event-popup__icon {
        animation: event-card-glyph-nod 620ms ease-in-out 250ms 2;
    }
    .event-popup.persona-game_room .event-popup__icon {
        animation: event-card-glyph-wiggle 500ms ease-out 250ms 2;
    }
    .event-popup.bucket-movie .event-popup__icon {
        animation: event-card-glyph-zoom-pulse 580ms ease-out 250ms 2;
    }
    .event-popup.glyph-ball .event-popup__icon {
        animation: event-card-glyph-ball-bounce 900ms ease-out 250ms 2;
    }
}
.event-popup__body h3 {
    margin: 0;
    font-size: 1.5rem;
    line-height: 1.25;
    color: var(--color-fg);
    letter-spacing: -0.005em;
}
.event-popup__body .venue {
    margin: 0 0 0.625rem;
    font-size: 1.0625rem;
}
.event-popup__body .time {
    margin: 0;
    color: var(--color-muted);
    font-size: 0.9375rem;
    letter-spacing: 0.01em;
}
.event-popup__body .description {
    margin-top: 0.875rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--color-border);
    color: var(--color-fg);
    line-height: 1.55;
}

/* --- /club/<slug>/ page --------------------------------------------- */
.club-page {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 1.25rem 1rem 2rem;
    min-height: 70vh;
}
/* Top bar holds the back affordance, sized + centered to match the card so
   the link's left edge lines up with the card's. */
.club-page__topbar {
    width: 720px;
    max-width: 95vw;
    margin: 0 0 0.6rem;
}
/* Page-appropriate back affordance (not a popover X). Clay accent echoes the
   club card's eyebrow so the card -> page reads as one family. */
.club-page__back {
    display: inline-block;
    color: #a3673f;
    font-size: 1.25rem;
    font-weight: 700;
    text-decoration: none;
}
.club-page__back::first-letter { font-size: 1.15em; }  /* nudge the arrow larger */
.club-page__back:hover { color: var(--color-primary-deep, #7a3a1a); text-decoration: underline; }
.club-page__card {
    /* Warm parchment + stronger warm edge -- echoes the club result cards so
       the detail page feels like the card you tapped, expanded. */
    background: #faf6ec;
    border: 1px solid rgba(70, 60, 40, 0.30);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: 0 4px 11px rgba(70, 60, 40, 0.34),
                0 2px 4px rgba(70, 60, 40, 0.22);
    width: 720px;
    max-width: 95vw;
}
/* Eyebrow on the detail page, mirroring the card's kicker. */
.club-page__eyebrow {
    display: block;
    font-size: 0.66rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: #a3673f;
    margin-bottom: 0.2rem;
}
.club-page__eyebrow::before {
    content: "\25C6";
    margin-right: 0.4em;
    opacity: 0.75;
}
/* Disclaimer size lives here (moved off the shared partial's inline style) so
   the club PAGE can show it larger while the event popup keeps the small size. */
.club-disclaimer { font-size: 0.8rem; }
.page-club .club-disclaimer { font-size: 1rem; }
.club-page__description {
    color: var(--color-muted);
    margin: 0 0 0.75rem;
}
.club-page__schedule {
    list-style: disc;
    padding-left: 1.25rem;
    margin: 0.5rem 0 1rem;
}
.event-popup__club {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid #eee;
}
.event-popup__cta {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid #eee;
    font-style: italic;
}
.event-popup__cta-link {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-style: inherit;
    color: var(--color-accent);
    text-decoration: underline;
    cursor: pointer;
    text-align: left;
}
.event-popup__cta-link:hover { color: var(--color-primary-deep); }

/* --- Intake guide (body-swap with the event popup) ----------------- */
.event-popup__intake {
    padding: 1.25rem 1.5rem 1.5rem;
}
.event-popup__intake-back {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--color-accent);
    cursor: pointer;
    margin-bottom: 0.6rem;
}
.event-popup__intake-back:hover { color: var(--color-primary-deep); }
.event-popup__intake-heading {
    margin: 0 0 0.4rem;
    font-size: 1.4rem;
    line-height: 1.25;
    color: var(--color-fg);
}
.event-popup__intake-lede {
    margin: 0 0 1rem;
    color: var(--color-muted);
    line-height: 1.5;
}
.event-popup__intake-section {
    margin: 0 0 1rem;
}
.event-popup__intake-section h4 {
    margin: 0 0 0.4rem;
    font-size: 0.85rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-muted);
}
.event-popup__intake-section ul {
    margin: 0;
    padding-left: 1.2rem;
    line-height: 1.55;
}
.event-popup__intake-section li { margin-bottom: 0.2rem; }
.event-popup__intake-section p {
    margin: 0;
    line-height: 1.55;
}
/* Styled mock of the real club card so the organizer sees what the
   submission will produce. Warm-tan ring + soft tint distinguishes it
   from the rest of the intake content as a "preview". */
.event-popup__intake-example {
    margin: 1.25rem 0;
}
.event-popup__intake-example-label {
    margin: 0 0 0.5rem;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-muted);
    font-weight: 700;
}
.event-popup__intake-example-card {
    padding: 0.85rem 1rem;
    background: rgba(216, 210, 196, 0.25);
    border: 1px solid rgba(70, 60, 40, 0.20);
    border-radius: var(--radius-md);
}
.event-popup__intake-example-card h5 {
    margin: 0 0 0.35rem;
    font-size: 1rem;
    font-weight: 600;
}
.event-popup__intake-example-card h5 span {
    color: var(--color-accent);
}
.event-popup__intake-example-desc {
    margin: 0 0 0.5rem;
    line-height: 1.45;
    color: var(--color-fg);
}
.event-popup__intake-example-card ul {
    list-style: none;
    margin: 0 0 0.5rem;
    padding: 0;
    line-height: 1.5;
}
.event-popup__intake-example-card li { padding: 0; }
.event-popup__intake-example-notes,
.event-popup__intake-example-contact {
    margin: 0.35rem 0 0;
    color: var(--color-muted);
    font-size: 0.92rem;
}
.event-popup__intake-actions {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
    align-items: center;
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border);
}
.event-popup__intake-send {
    display: inline-flex;
    align-items: center;
    padding: 0.55rem 1.1rem;
    background: var(--color-primary);
    color: white;
    border-radius: var(--radius-pill);
    font-weight: 600;
    text-decoration: none;
    transition: background-color 120ms ease, transform 120ms ease,
                box-shadow 120ms ease;
    box-shadow: 0 2px 4px rgba(40, 30, 20, 0.18);
}
.event-popup__intake-send:hover,
.event-popup__intake-send:focus-visible {
    background: var(--color-primary-deep);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(40, 30, 20, 0.24);
    outline: none;
}
.event-popup__intake-cancel {
    background: none;
    border: 1px solid var(--color-border);
    padding: 0.5rem 0.95rem;
    border-radius: var(--radius-pill);
    color: var(--color-muted);
    font-weight: 600;
    cursor: pointer;
    font: inherit;
    font-weight: 600;
}
.event-popup__intake-cancel:hover { background: var(--color-bg); }

/* Primary action button right under the date/time row. Visually
   prominent (brand green, white text) so it reads as the call-to-
   action; the .ics download itself is a stock anchor with the
   `download` attribute -- the user's calendar app intercepts. */
/* Date/time + calendar button on one row (time left, button right). Wraps on
   narrow widths so the button drops below the time rather than overflowing. */
.event-popup__when-row {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.6rem;
}
.event-popup__when-row .time { margin: 0; }
/* Zero the buttons' standalone top margin inside the row so the flex
   align-items:center lines the time + both buttons up on one center line. */
.event-popup__when-row .event-popup__ics,
.event-popup__when-row .event-popup__share { margin: 0; }
.event-popup__ics {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.75rem 0 0;
    padding: 0.55rem 1rem;
    background: var(--color-primary);
    color: white;
    border-radius: var(--radius-pill);
    font-weight: 600;
    font-size: 0.95rem;
    text-decoration: none;
    transition: background-color 120ms ease, transform 120ms ease,
                box-shadow 120ms ease;
    box-shadow: 0 2px 4px rgba(40, 30, 20, 0.18);
}
.event-popup__ics:hover,
.event-popup__ics:focus-visible {
    background: var(--color-primary-deep);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(40, 30, 20, 0.24);
    outline: none;
}
.event-popup__ics-icon {
    font-size: 1.05rem;
    line-height: 1;
}
/* Calendar + Share sit together on the right of the when-row. */
.event-popup__actions {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
/* Share: secondary (outline) pill so it reads as distinct from the primary
   green calendar button. Native share sheet on mobile, copy-link on desktop. */
.event-popup__share {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0.75rem 0 0;
    padding: 0.55rem 1rem;
    background: var(--color-bg);
    color: var(--color-primary-deep);
    border: 1px solid rgba(70, 60, 40, 0.28);
    border-radius: var(--radius-pill);
    font-weight: 600;
    font-size: 0.95rem;
    cursor: pointer;
    transition: background-color 120ms ease, transform 120ms ease,
                box-shadow 120ms ease;
}
.event-popup__share:hover,
.event-popup__share:focus-visible {
    background: rgba(125, 150, 112, 0.14);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(40, 30, 20, 0.18);
    outline: none;
}
.event-popup__share-icon {
    font-size: 1.05rem;
    line-height: 1;
}
.event-popup__share.is-copied {
    color: var(--color-primary-deep);
    border-color: var(--color-primary);
    background: rgba(125, 150, 112, 0.18);
}

/* Taxonomy breadcrumb under the venue: persona pill > specific bucket(s),
   so people can see how the event is grouped. The persona pill tints to the
   card's stripe color via the .persona-<id> class (--stripe-color); dark text
   keeps it legible on every persona hue. */
/* Taxonomy pills (persona pill > bucket chip(s)) on the title row. The row's
   justify-content:space-between right-aligns them beside the title when they
   fit, and (since there's no margin-left:auto) left-aligns them when they wrap
   to their own line below a long title. flex-shrink:0 keeps the pills intact. */
.event-popup__taxonomy {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0.3rem 0 0;
    flex: 0 0 auto;
}
.event-popup__persona-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.2rem 0.6rem;
    border-radius: var(--radius-pill);
    background: #fff;  /* fallback for browsers without color-mix() */
    background: color-mix(in srgb, var(--stripe-color, #8a7a5e) 14%, #fff);
    border: 1px solid var(--stripe-color, rgba(70, 60, 40, 0.28));
    color: var(--color-fg);
    font-weight: 700;
    font-size: 0.82rem;
    line-height: 1.2;
}
.event-popup__persona-pill-icon {
    font-size: 0.95em;
    line-height: 1;
}
.event-popup__tax-sep {
    color: var(--color-muted);
    font-weight: 700;
}
.event-popup__bucket-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.55rem;
    border-radius: var(--radius-pill);
    background: rgba(70, 60, 40, 0.06);
    border: 1px solid rgba(70, 60, 40, 0.22);
    color: var(--color-muted);
    font-weight: 600;
    font-size: 0.8rem;
    line-height: 1.2;
}

/* --- Related sections at the bottom of the popover ----------------- */
/* "Also offered at" (same activity, different venues) + "You might  */
/* also like" (same persona, different bucket). Each row is a link   */
/* that home-popup.js intercepts to swap the popover content -- so   */
/* users can chain through related events without leaving the modal. */
.event-popup__related {
    margin-top: 1.1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border);
}
.event-popup__related-section + .event-popup__related-section {
    margin-top: 1rem;
}
.event-popup__related-heading {
    margin: 0 0 0.5rem;
    font-size: 0.85rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-muted);
}
.event-popup__related-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.event-popup__related-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.4rem 0.55rem;
    border-radius: var(--radius-sm);
    color: var(--color-fg);
    text-decoration: none;
    transition: background-color 120ms ease;
}
.event-popup__related-row:hover,
.event-popup__related-row:focus-visible {
    background: var(--color-bg);
    outline: none;
}
.event-popup__related-name {
    font-weight: 600;
    flex: 1 1 auto;
    min-width: 0;
    /* Long venue / event titles get clamped to one line so the row stays
       scannable; tooltip falls back to the link's underlying href in
       browser UI for the truncated case. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.event-popup__related-meta {
    color: var(--color-muted);
    font-size: 0.9rem;
    white-space: nowrap;
    flex: 0 0 auto;
}

/* --- "By the numbers" CTA card (sits at the bottom of the event list,
   below the map/list split, links to /pulse/). Lives here rather than
   in stats.css so the home page doesn't need to load the whole stats
   stylesheet just for this card. Dark band echoes the /pulse/ page
   headline so the visual language matches the destination. */
.home-stats-cta {
  /* Horizontal margin 0 so the CTA aligns flush with the map + event-card
     layout above (which sits at <main>'s gutter), rather than carrying an
     extra inset. Keeps the whole column on one left/right edge. */
  margin: 18px 0 8px;
}
/* Home page only: the .home-stats-cta CTA card is already a chunky    */
/* visual terminator, so the global 3rem footer margin-top (base.css)  */
/* stacks into an awkward dead band above the footer. Zero it out here */
/* and let the cta's own margin-bottom + footer border-top do the work.*/
body.page-home .site-footer {
  margin-top: 0;
}
.home-stats-cta__link {
  display: flex;
  align-items: center;
  gap: 20px;
  /* Warm tan-brown -- bold enough to anchor the eye at the bottom of the
     list, light enough to sit at the same visual weight as the cream bg
     instead of dominating it. Earlier #6b5435 read as a heavy slab. */
  background: #8a6e47;
  color: #f8efde;
  border-radius: 14px;
  padding: 16px 28px;
  text-decoration: none;
  /* Layered shadow: a soft wide one for the "lift" + a tight close one
     for the contact patch, so the card reads as permanently floating.
     Alphas tuned down from .28/.18 once the bg lightened -- the heavier
     shadow against the lighter slab read as a second source of weight. */
  box-shadow: 0 10px 26px rgba(70,60,40,0.20),
              0 2px 6px  rgba(70,60,40,0.13),
              inset 0 1px 0 rgba(255,255,255,0.08);
  transition: transform 140ms ease, box-shadow 140ms ease, background-color 140ms ease;
}
.home-stats-cta__link:hover {
  background: #9a7c51;
  transform: translateY(-2px);
  box-shadow: 0 14px 32px rgba(70,60,40,0.24),
              0 3px 8px   rgba(70,60,40,0.16),
              inset 0 1px 0 rgba(255,255,255,0.10);
}
.home-stats-cta__copy {
  flex: 1;
  min-width: 0;
}
.home-stats-cta__eyebrow {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 700;
  color: #f0c75a;
  margin: 0 0 4px;
}
/* Title + subtitle share a single baseline-aligned row so the card
   stops being top-heavy / bottom-heavy with stacked lines. Wraps to a
   second line when there isn't room (narrow viewports), with the gap
   shorthand handling both axes. */
.home-stats-cta__headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 16px;
}
.home-stats-cta__title {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #f8efde;
  margin: 0;
  line-height: 1.15;
}
.home-stats-cta__sub {
  font-size: 15px;
  color: rgba(248,239,222,0.78);
  margin: 0;
  line-height: 1.4;
}
.home-stats-cta__arrow {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  border-radius: 999px;
  background: rgba(248,239,222,0.10);
  display: grid;
  place-items: center;
  font-size: 22px;
  color: #f8efde;
  transition: transform 120ms ease, background-color 120ms ease;
}
/* Decorative brain emoji sitting between the copy and the arrow.
   Non-interactive thematic hint ("analytical/insights page"), matching
   the same brain glyph used by the "for nerds" toggle on /pulse/. */
.home-stats-cta__brain {
  flex: 0 0 auto;
  font-size: 56px;
  line-height: 1;
  /* Keep the emoji at full color but slightly dimmed so it doesn't
     compete with the arrow as the primary affordance. */
  opacity: 0.85;
  transition: opacity 140ms ease, transform 140ms ease;
}
.home-stats-cta__link:hover .home-stats-cta__brain {
  opacity: 1;
  transform: rotate(-6deg);
}
.home-stats-cta__link:hover .home-stats-cta__arrow {
  background: #c9722a;
  transform: translateX(3px);
}
@media (max-width: 640px) {
  /* Tighten the stats CTA box itself + the white space around it.
     Vertical real estate is scarce on phones - the CTA is decorative,
     not the main event, so it should occupy a single tight band. */
  .home-stats-cta {
    margin: 10px 0 12px;
  }
  .home-stats-cta__link {
    padding: 12px 16px;
    gap: 12px;
    border-radius: 12px;
  }
  .home-stats-cta__eyebrow {
    font-size: 11px;
    letter-spacing: 0.12em;
    margin: 0 0 4px;
  }
  .home-stats-cta__title { font-size: 19px; line-height: 1.1; }
  .home-stats-cta__sub   {
    font-size: 13px;
    line-height: 1.3;
  }
  /* On phones the headline wraps to two lines (title above sub) because
     the row can't fit "By the Numbers Every rec center..." side by side.
     Let the flex gap drive the line-break; no explicit margin needed. */
  .home-stats-cta__arrow {
    width: 34px;
    height: 34px;
    font-size: 16px;
  }
  .home-stats-cta__brain {
    font-size: 44px;
  }
}

/* ===== Mobile viewport (iPhone-width) ===== */
/* All mobile-specific overrides live below. Keep this block contiguous
   so the @media query gate is easy to audit. */
@media (max-width: 640px) {
  /* Stack the list+map split - drop the map entirely. */
  .home-layout {
    display: block;
  }
  .home-layout .drawer { display: none; }
  .home-layout .content-split {
    display: block;
    grid-template-columns: none;
  }
  .list-pane {
    width: 100%;
    max-width: none;
  }
  .map-pane {
    display: none;
  }
  /* Drawer toggle bar is also dead weight on mobile. */
  .drawer-toggle-bar {
    display: none;
  }

  /* Desktop filter chrome - content moves into the bottom sheet
     in a later task. Hidden here so the mobile view is just cards. */
  /* Hide the filter-panel container entirely (not just its body/summary)
     so its beige background + padding don't render as an empty box
     between the summary bar and the CTA banner. */
  .filter-panel { display: none; }
  .filter-panel__body { display: none; }
  .filter-panel__summary { display: none; }
  .persona-row { display: none; }
  .home-search-row { display: none; }

  /* Event card - compact mobile layout: glyph upper-right, thumb +
     ICS "+" lower-right, text fills the left without glyph overlap. */
  .event-card {
    /* Left padding indents text well clear of the colored stripe.
       Right padding reserves space for the 48px icon/thumb column at
       its new 1rem offset (48 + 16 + ~8 buffer = ~72px). */
    padding: 10px 72px 10px 22px;
    margin-bottom: 8px;
  }
  .event-card.has-thumb {
    /* Override desktop's calc(110px + 1.5rem) thumb reservation. */
    padding-right: 72px;
  }
  .event-card h3 {
    font-size: 16px;
    line-height: 1.25;
    margin: 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .event-card .time,
  .event-card .venue {
    font-size: 13px;
  }
  .event-card .tags {
    margin-top: 4px;
  }
  .event-card .tag {
    font-size: 11px;
    padding: 3px 7px;
  }
  .event-card__glyph {
    /* Match the thumb's width + right offset so glyph and thumb form
       a vertically-aligned right column. Top offset is smaller than the
       thumb's bottom offset so the icon hugs the top edge and leaves a
       bit more breathing room before the thumb beneath it. */
    left: auto;
    right: 1rem;
    top: 0.2rem;
    width: 48px;
    height: 48px;
  }
  .event-card__glyph--emoji {
    font-size: 36px;
    line-height: 48px;
  }
  /* Image-variant glyphs (custom racquet-sport icons) fill their slot
     edge-to-edge so they touch the thumb beneath them. Knock the img
     down ~10% with a transform so the visual gap stays clean without
     having to ship a separate mobile asset. */
  .event-card__glyph--img {
    transform: scale(0.9);
    transform-origin: top right;
  }
  .event-card__thumb {
    top: auto;
    bottom: 0.5rem;
    right: 1rem;
    width: 48px;
    height: 48px;
    /* Pin the compact corner tile's radius + lift here so the desktop
       A/C thumb treatment (full-bleed vs inset) never leaks into mobile. */
    border-radius: 6px;
    box-shadow:
        0 0 0 1px rgba(70, 60, 40, 0.18),
        0 2px 4px rgba(40, 30, 20, 0.30),
        0 10px 22px rgba(40, 30, 20, 0.45);
  }
  /* The compact 48px mobile thumb is too small for the "Details ->" text
     label (it wraps to two lines), so collapse the cue to just the arrow
     glyph here. font-size:0 hides the text node; ::after re-adds a sized
     arrow. Applies to any <=640px viewport (the 48px thumb exists with or
     without hover), so a narrow desktop window gets the arrow too. */
  .event-card__details-cue {
    font-size: 0;
    bottom: 0.3rem;
  }
  .event-card__details-cue::after {
    content: "\2192";
    font-size: 15px;
    font-weight: 800;
  }
  /* Mobile inherits the inline-in-time-row treatment from the desktop
     base rule. No override needed -- a 22px pill is already tuned for
     the compact card layout. */

  .bottom-sheet {
    position: fixed;
    inset: 0;
    z-index: 9000;
    pointer-events: none;
  }
  .bottom-sheet[aria-hidden="false"] {
    pointer-events: auto;
  }
  .bottom-sheet__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(20, 16, 10, 0.0);
    transition: background 200ms ease;
  }
  .bottom-sheet[aria-hidden="false"] .bottom-sheet__backdrop {
    background: rgba(20, 16, 10, 0.45);
  }
  .bottom-sheet__panel {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 70vh;
    max-height: 70vh;
    background: #f8efde;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -6px 18px rgba(70, 60, 40, 0.28);
    transform: translateY(100%);
    transition: transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .bottom-sheet[aria-hidden="false"] .bottom-sheet__panel {
    transform: translateY(0);
  }
  .bottom-sheet__handle {
    align-self: center;
    width: 36px;
    height: 4px;
    border-radius: 999px;
    background: rgba(70, 60, 40, 0.30);
    border: none;
    margin: 10px 0 6px;
    padding: 0;
    cursor: grab;
    touch-action: none;
  }
  .bottom-sheet__body {
    flex: 1;
    overflow-y: auto;
    padding: 8px 14px 16px;
  }
  .bottom-sheet__done {
    border: none;
    background: #c9722a;
    color: #fff;
    font-weight: 600;
    font-size: 14px;
    padding: 12px 16px;
    margin: 0 14px 14px;
    border-radius: 12px;
    cursor: pointer;
  }

  .mobile-sheet-section {
    margin-bottom: 18px;
  }
  .mobile-sheet-section__title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: rgba(43, 34, 24, 0.65);
    margin: 0 0 8px;
  }
  /* Override the display:none rules from Task 3 so partials render
     inside the sheet body. */
  .bottom-sheet__body .filter-panel__body,
  .bottom-sheet__body .persona-row,
  .bottom-sheet__body .home-search-row,
  .bottom-sheet__body .time-bucket-row,
  .bottom-sheet__body .near-me-row,
  .bottom-sheet__body aside.filters {
    display: block !important;
  }

  .mobile-summary-bar {
    position: sticky;
    top: 0;
    z-index: 50;
    background: #fff;
    border-bottom: 1px solid rgba(70, 60, 40, 0.18);
    box-shadow: 0 1px 4px rgba(70, 60, 40, 0.10);
    padding: 8px 12px;
    /* Negative margin matches <main>'s reduced 0.5rem horizontal padding
       so the bar still bleeds edge-to-edge. */
    margin: 0 -0.5rem 8px;
  }
  .mobile-summary-bar[aria-hidden="false"] { display: block; }
  .mobile-summary-bar[aria-hidden="true"]  { display: none; }
  .mobile-summary-bar__chips {
    display: flex;
    gap: 6px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .mobile-summary-bar__chips::-webkit-scrollbar { display: none; }
  .summary-chip {
    flex: 0 0 auto;
    font-size: 12px;
    padding: 6px 10px;
    border-radius: 999px;
    background: rgba(70, 60, 40, 0.08);
    border: 1px solid rgba(70, 60, 40, 0.22);
    color: #2b2218;
    cursor: pointer;
    white-space: nowrap;
  }
  .summary-chip--active {
    background: #c9722a;
    color: #fff;
    border-color: transparent;
  }
  .summary-chip--add {
    background: rgba(70, 60, 40, 0.04);
    border-style: dashed;
  }
  .summary-chip__remove {
    margin-left: 4px;
    font-weight: 600;
    opacity: 0.8;
  }

  /* Popover takes full viewport on mobile. */
  .event-popover-panel,
  .event-popover {
    position: fixed !important;
    inset: 0 !important;
    width: 100vw !important;
    height: 100vh !important;
    max-width: none !important;
    max-height: none !important;
    border-radius: 0 !important;
    margin: 0 !important;
  }
  .event-popover-panel .event-popup,
  .event-popover .event-popup {
    width: 100% !important;
    max-width: none !important;
    height: 100%;
    overflow-y: auto;
    border-radius: 0;
    padding-top: 56px;
  }
  /* Hide the X close button on mobile - the Back pill replaces it. */
  .event-popover-close {
    display: none !important;
  }
  .event-popover-back-mobile {
    position: fixed;
    top: 10px;
    left: 10px;
    z-index: 10;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: rgba(20, 16, 10, 0.78);
    color: #fff;
    border: none;
    border-radius: 999px;
    padding: 8px 14px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
  }

  .cta-banner {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .cta-banner__map-link {
    flex: 0 0 auto;
    font-size: 13px;
    color: #c9722a;
    font-weight: 600;
    text-decoration: none;
    padding: 8px 6px;
    white-space: nowrap;
  }

  /* Hide CTA flourishes on mobile - the gag tagline (now lives above
     the event list), the down arrow, the "near X, Y" location
     qualifier, and the leading "Show " word are screen-real-estate
     luxuries kept for desktop only. */
  .event-list__compliment { display: none; }
  .cta-arrow              { display: none; }
  .cta-where              { display: none; }
  .cta-show-prefix        { display: none; }
  /* Tighten the button so the remaining single line fits without wrap. */
  .find-events-btn {
    font-size: 16px;
    padding: 0.7rem 0.9rem;
    white-space: nowrap;
  }
  .find-events-btn .cta-main { white-space: nowrap; }
}

/* Hide the sheet entirely on desktop - it's mobile-only chrome. */
@media (min-width: 641px) {
  #mobile-bottom-sheet { display: none; }
  #mobile-summary-bar { display: none; }
  .event-popover-back-mobile { display: none; }
  .cta-banner__map-link { display: none; }
}

/* ----- Search redesign: input-results separation --------------- */

/* Horizontal divider that separates the input zone (search + filters)
   from the results zone (CTA banner + event list + map). Combined with
   the data-state dim below, this makes the mutual-exclusivity of
   search vs filters visually obvious without explanatory copy. */
/* .results-extras-divider mirrors the input/results divider's hairline but
   sits BELOW the event-card + map layout to set it off from the "By the
   Numbers" CTA + Latest Reads -- a third page band. Shares the style; unlike
   .input-results-divider it is NOT hidden in the collapsed-filters state. */
.input-results-divider,
.results-extras-divider {
    border: 0;
    border-top: 1px solid rgba(70, 60, 40, 0.18);
    margin: 14px 0 16px;
    width: 100%;
}

/* When search is active, dim the filter strip so the user sees the
   chips/dates/villages are paused. pointer-events:none prevents
   accidental interaction; the delegated listener in persona-chips.js
   catches clicks at the wrapper level (capture phase) to clear the
   search instead. */
/* Dim the FILTER content (not .filters-stack itself) as a "filters paused"
   cue while search is active. The search box now lives inside .filters-stack
   too, and CSS opacity on an ancestor can't be undone by a child -- so the
   dim sits on the filter children only, leaving the search row bright and
   interactive. */
.filters-stack[data-state="searching"] > .filters-collapsed-summary,
.filters-stack[data-state="searching"] > .filter-panel,
.persona-row[data-state="searching"] {
    opacity: 0.4;
    transition: opacity 120ms ease-out;
}

.filters-stack[data-state="searching"] > .filters-collapsed-summary,
.filters-stack[data-state="searching"] > .filter-panel,
.filters-stack[data-state="searching"] > .filter-panel *,
.persona-row[data-state="searching"],
.persona-row[data-state="searching"] * {
    pointer-events: none;
}

/* Search-mode info line shown directly below the search input.
   Replaces the old "Interpreted as: X bucket" chip with plain-English
   copy that ALSO surfaces the "every location, every time" callout
   (essential because search overrides the user's filter selections).
   The trailing X is a shortcut for "exit search entirely" -- same
   effect as clicking the search bar's own X. */
.search-info {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.5rem;
    /* Breathing room before the first event card -- the banner now    */
    /* lives inside #event-list and would otherwise butt up against    */
    /* the card's top edge.                                             */
    margin-bottom: 1rem;
    /* Right padding leaves room for the absolute-positioned X (see
       .search-info__clear below) so it sits at the same offset from
       the row's right edge as the search input's own X. */
    padding: 0.5rem 2.4rem 0.5rem 0.95rem;
    border: 1px solid rgba(70, 60, 40, 0.22);
    /* Soft-pill / stadium-tending shape: large enough to read as an
       active applied state (closer to a chip than a form field) but
       short of full --radius-pill since the message is sentence-length
       rather than a 1-3-word tag. */
    border-radius: 1.25rem;
    /* Soft warm tint (vs solid white) so the info box reads as a
       callout band rather than a floating card on top of the page bg. */
    background: rgba(70, 60, 40, 0.05);
    box-shadow: 0 1px 2px rgba(70, 60, 40, 0.18);
}
/* Author display:flex above shares specificity with the browser's
   default [hidden] rule, and "later origin wins" gives display:flex
   the upper hand -- which would defeat JS setting hidden=true to
   dismiss the box. Make hidden authoritative. */
.search-info[hidden] {
    display: none;
}

.search-info__label {
    flex: 1 1 auto;
    color: #2a2a2a;
    font-size: 0.95rem;
    line-height: 1.35;
}

.search-info__clear {
    /* Absolute-positioned so the X lines up with the search input's
       own X above (both at right: 0.45rem from their parent's edge),
       giving the two clear buttons a tidy vertical alignment. */
    position: absolute;
    right: 0.45rem;
    top: 50%;
    transform: translateY(-50%);
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0.25rem 0.4rem;
    color: rgba(70, 60, 40, 0.75);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    /* Pill matches the parent's stadium-tending shape so hover/focus
       state doesn't feel like a square sitting inside a soft container. */
    border-radius: var(--radius-pill);
    transition: color 120ms ease, background-color 120ms ease;
}

.search-info__clear:hover,
.search-info__clear:focus-visible {
    color: var(--color-primary-deep);
    background: rgba(70, 60, 40, 0.06);
    outline: none;
}

/* ===== Latest Reads (home) + blog post cards ===== */
/* The .home-reads section and .blog-card component shipped with no CSS, so
   the blog teasers fell back to raw browser defaults. Theme them with the
   Sage & Clay card language (white surface, warm-tinted edge, soft lift) so
   they sit in the same family as the event cards and the stats CTA. The
   .blog-card rules are shared with the full /blog/ index page. */
.home-reads {
    /* Flush with the .home-stats-cta and the map/event-card layout above:
       no extra horizontal inset, so the page shares one left/right gutter. */
    margin: 18px 0 8px;
}
.home-reads__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}
.home-reads__title {
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-fg);
    margin: 0;
    line-height: 1.15;
}
.home-reads__more {
    font-size: 15px;
    font-weight: 700;
    color: var(--color-accent);
    text-decoration: none;
    white-space: nowrap;
    transition: color 120ms ease;
}
.home-reads__more:hover,
.home-reads__more:focus-visible {
    color: #a06544;          /* deeper clay on hover */
    text-decoration: underline;
}

.blog-list {
    display: grid;
    gap: 14px;
}
.blog-list--compact {
    gap: 12px;
}
/* The standalone /blog/ index has room for a 2-up grid; the home teaser
   stays single-column (it only ever shows the two newest posts). */
@media (min-width: 760px) {
    .page-blog-index .blog-list {
        grid-template-columns: repeat(2, 1fr);
        gap: 18px;
    }
}

.blog-card {
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    /* Warm-tinted edge: a soft wide lift + a tight close contact shadow so
       the card reads as a defined object on the cream bg, not a smudge. */
    box-shadow: 0 4px 14px rgba(70, 60, 40, 0.10),
                0 1px 3px  rgba(70, 60, 40, 0.18);
    transition: transform 140ms ease, box-shadow 140ms ease;
}
.blog-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(70, 60, 40, 0.16),
                0 2px 6px   rgba(70, 60, 40, 0.22);
}
/* Make the whole card clickable, not just the title -- the home teaser and
   the /blog/ index both use this component. A stretched pseudo-link on the
   existing title anchor covers the card, so a click anywhere lands on the
   post without nesting <a> tags. */
.blog-card {
    position: relative;
}
.blog-card__title a::after {
    content: "";
    position: absolute;
    inset: 0;
}
.blog-card__cover {
    display: block;
    line-height: 0;
}
.blog-card__cover img {
    width: 100%;
    height: 180px;
    object-fit: cover;
    display: block;
}
.blog-card__body {
    padding: 16px 20px 18px;
}
.blog-card__title {
    font-size: 19px;
    font-weight: 700;
    line-height: 1.25;
    margin: 0 0 6px;
}
.blog-card__title a {
    color: var(--color-primary-deep);
    text-decoration: none;
}
.blog-card__title a:hover,
.blog-card__title a:focus-visible {
    text-decoration: underline;
}
.blog-card__date {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-muted);
    margin: 0 0 8px;
}
.blog-card__summary {
    font-size: 15px;
    color: var(--color-muted);
    line-height: 1.45;
    margin: 0;
}

/* Home "Latest Reads" teaser cards take the deep-sage fill (the /blog/ index
   cards keep the white surface) so the foot of the home page echoes the sage
   used in the header + active states -- more oomph than white, and better
   top-to-bottom balance. Text flips to cream for contrast on the dark green
   (the title was itself --color-primary-deep, so it would vanish otherwise). */
.home-reads .blog-card {
    background: var(--color-primary-deep);   /* #5d7a52 */
    border-color: #4a6241;                   /* a shade darker than the fill, for a defined edge */
}
.home-reads .blog-card:hover {
    background: #547046;                      /* slightly deeper green on hover */
}
.home-reads .blog-card__title a {
    color: #f4f1ea;                           /* soft cream title link */
}
.home-reads .blog-card__date {
    color: rgba(246, 243, 237, 0.78);        /* pale cream date label */
}
.home-reads .blog-card__summary {
    color: rgba(246, 243, 237, 0.9);         /* soft cream summary */
}

@media (max-width: 640px) {
    .home-reads {
        margin: 10px 0 12px;
    }
    .home-reads__title { font-size: 20px; }
    .blog-card__body   { padding: 14px 16px 16px; }
    .blog-card__title  { font-size: 17px; }
}

/* --- Time-anchored feed bands ---------------------------------------- */
.feed-band {
    margin-bottom: 1.5rem;
}
.feed-band__header {
    position: sticky;
    top: 0;
    /* Above .event-card:hover's z-index:2 so a hovered top card (which
       lifts itself to sit over its neighbors) still slides UNDER the
       pinned band rather than popping in front of it. */
    z-index: 3;
    /* Negative side margins pull the header out over the scroll container's
       1rem side padding so its background spans edge-to-edge and fully
       occludes cards sliding behind it (the container's top padding was
       removed so the header pins flush to the top). Padding restores the
       label's position inside the widened background. */
    /* Bottom margin gives the first card breathing room so it isn't tucked
       under the band's edge + drop shadow at rest, and a little extra so
       the elevated band reads as clearly separated from the cards below. */
    margin: 0 -1rem 0.7rem;
    padding: 0.5rem 1rem 0.35rem;
    /* Cream page background (not white) so the band blends with the page
       instead of reading as a white box; still fully opaque, so it occludes
       cards sliding under it. */
    background: var(--color-bg, #f6f3ed);
    /* Defined edge + a deeper drop shadow so the band reads as ELEVATED,
       floating above the cards that scroll beneath it: the warm-shadow
       hairline ring, a soft ambient shadow, and a tight contact shadow. */
    box-shadow: 0 0 0 1px rgba(70, 60, 40, 0.20),
                0 5px 12px rgba(70, 60, 40, 0.16),
                0 1px 3px rgba(70, 60, 40, 0.12);
}
.feed-band__label {
    margin: 0;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-muted, #8a8275);
}
.feed-band__see-all {
    display: inline-block;
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.9rem;
    border: 1px dashed rgba(70, 60, 40, 0.28);
    border-radius: 16px;
    font-size: 0.9rem;
    color: var(--color-accent, #8a6d4a);
    text-decoration: none;
}
.feed-band__see-all:hover {
    background: rgba(70, 60, 40, 0.06);
}

/* --- Phase 2: cold-load control bar --------------------------------- */
.shell-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    /* Small top margin nudges the whole bar (location pill + "next 3 hrs"
       pill) down a hair off the top edge. Tight bottom margin (0.1rem) +
       the collapsed cluster's ~0.4rem of internal top space ~= 0.5rem, so
       the 2-hubs -> Today bar gap matches the Today bar -> search gap. */
    margin: 0.5rem 0 0.1rem;
}
/* Match the 1rem horizontal inset that .filters-stack.is-collapsed gives the
   Today-summary + search boxes below it, so the shell-bar's outer pills line
   up with those boxes. The two are siblings: the stack's padding insets ITS
   content, the bar's doesn't, so without this the pills jut ~1rem past the
   boxes left and right. Collapsed cold-load only -- expanded filters sit
   flush to the content edge, and so does the bar. */
body.is-filters-collapsed .shell-bar {
    padding-left: 1rem;
    padding-right: 1rem;
}
.shell-bar__pill,
.shell-bar__btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 6px 12px;
    border-radius: 16px;
    font: inherit;
    font-size: 0.9rem;
    cursor: pointer;
    background: var(--color-surface, #fff);
    border: 1px solid rgba(70, 60, 40, 0.22);
    color: var(--color-ink, #3a352c);
}
.shell-bar__pill { background: #f3f7fa; border-color: #9ab; color: #38566a; }
.shell-bar__btn--map[aria-pressed="false"] { border-style: dashed; }
/* The Map toggle only earns its place on mobile, where the map pane is
   hidden by default and this button reveals it. On desktop the pane shows
   beside the list, so the toggle is redundant -- hide it above the mobile
   breakpoint. */
@media (min-width: 641px) {
    .shell-bar__btn--map { display: none; }
}
/* The single "next 3 hours" CTA: promoted from a plain text link to the
   solid pill that used to live in the hero (the hero copy was removed to
   de-dupe the CTA and tighten the header). Sized to the other shell-bar
   buttons. */
.shell-bar__pulse {
    position: relative;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    padding: 6px 14px;
    /* Warm clay rather than the hero's deep rust (#7a3a1a) -- still a solid
       CTA, but lighter so it doesn't read heavy/overbearing in the bar. */
    background: #a3673f;
    border: 1px solid #8c5733;
    border-radius: 999px;
    color: #fbf7ef;
    font-size: 0.9rem;
    font-weight: 700;
    line-height: 1.1;
    text-decoration: none;
    box-shadow: 0 2px 5px rgba(70, 60, 40, 0.18),
                0 1px 2px rgba(70, 60, 40, 0.12);
    transition: background-color 140ms ease, box-shadow 140ms ease,
                transform 140ms ease;
}
/* Keep the slim "light" pill (the comment above), but stretch the hit
   area UP to the 44px Apple-HIG minimum via an invisible overlay anchored
   to the pill's bottom. Upward is the safe direction: the pill is right-
   aligned (margin-left:auto) while the row above is left-aligned, so the
   12px of extra reach lands in empty gutter, stealing no neighbour taps. */
.shell-bar__pulse::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 44px;
}
.shell-bar__pulse:hover {
    background: #b3744a;
    box-shadow: 0 3px 8px rgba(70, 60, 40, 0.22);
    transform: translateY(-1px);
    text-decoration: none;
}
/* Map pane toggled off (desktop). */
body.is-map-hidden .map-pane { display: none; }
body.is-map-hidden .content-split .list-pane { width: 100%; }
/* Mobile: map hidden by default (feed full-width); Map button reveals it. */
@media (max-width: 640px) {
    .map-pane { display: none; }
    body.is-map-shown .map-pane { display: block; }
}
/* Cold-load shell: the control bar is the sole collapsed-state filter
   surface. Hide the legacy collapsed summary line + count banner so they
   don't duplicate (and, in feed mode, contradict -- a single "Today"
   vs. the multi-band feed) the shell bar above them. */
body.is-filters-collapsed .filter-panel.is-collapsed .filter-panel__summary,
body.is-filters-collapsed .persona-row.is-collapsed .persona-row__summary,
body.is-filters-collapsed .cta-banner {
    display: none;
}

/* --- Phase 3: per-persona narrow drill-down ------------------------- */
/* Progressive-disclosure toggle that reveals the whole narrow panel.
   Reads as a quiet text affordance (not a chip) so picking a persona
   stays the obvious primary action; the caret rotates open like the
   per-persona rows and the section titles. */
.persona-narrow-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--color-primary-deep);
    font-weight: 600;
    font-size: 0.95rem;
}
/* The toggle + its refine panel now live INSIDE the chips box, which is a
   flex-wrap row -- force each onto its own full-width row at the bottom so
   they stack under the chips rather than flowing inline beside them. */
.persona-row__chips > .persona-narrow-toggle,
.persona-row__chips > .persona-narrow { flex: 0 0 100%; }
/* Explicit display:inline-flex above overrides the user-agent
   `[hidden] { display: none }`, so restore it when JS hides the toggle. */
.persona-narrow-toggle[hidden] { display: none; }
.persona-narrow-toggle__caret {
    display: inline-block;
    transition: transform 120ms ease;
}
.persona-narrow-toggle.is-open .persona-narrow-toggle__caret {
    transform: rotate(90deg);
}
.persona-narrow-toggle:hover .persona-narrow-toggle__label,
.persona-narrow-toggle:focus-visible .persona-narrow-toggle__label {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.persona-narrow {
    /* No margin-top: the chips box's row-gap already separates it from the
       toggle above (it's now a flex row inside that box). */
    padding: 0.5rem 0.6rem;
    border: 1px dashed rgba(70, 60, 40, 0.28);
    border-radius: 10px;
    background: rgba(70, 60, 40, 0.03);
}
.persona-narrow__group {
    margin-bottom: 0.4rem;
    border: 1px solid rgba(70, 60, 40, 0.16);
    border-radius: 10px;
    background: var(--color-surface, #fff);
    overflow: hidden;
}
.persona-narrow__group:last-child { margin-bottom: 0; }
/* Collapsed-by-default toggle: the affordance that says "there's more to
   refine here." The caret rotates a quarter-turn when the group opens. */
.persona-narrow__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    width: 100%;
    padding: 0.5rem 0.7rem;
    background: none;
    border: 0;
    cursor: pointer;
    font: inherit;
    text-align: left;
    color: var(--color-primary-deep, #4f6a45);
    font-weight: 600;
}
.persona-narrow__toggle-label { display: inline-flex; align-items: baseline; }
.persona-narrow__caret {
    display: inline-block;
    font-size: 1.05rem;
    color: var(--color-primary, #6f8c5f);
    transition: transform 0.18s ease;
}
.persona-narrow__group.is-open .persona-narrow__caret { transform: rotate(90deg); }
.persona-narrow__count {
    font-weight: 400;
    font-size: 0.8rem;
    color: var(--color-muted, #8a8275);
    margin-left: 0.35rem;
}
.persona-narrow__hint {
    font-weight: 400;
    font-size: 0.78rem;
    color: var(--color-muted, #8a8275);
    white-space: nowrap;
}
.persona-narrow__body {
    display: none;
    flex-wrap: wrap;
    padding: 0.1rem 0.7rem 0.6rem;
}
.persona-narrow__group.is-open .persona-narrow__body { display: flex; }
.persona-narrow__chip {
    display: inline-flex;
    align-items: center;
    margin: 0 4px 4px 0;
    padding: 3px 10px;
    border-radius: 13px;
    font: inherit;
    font-size: 0.82rem;
    cursor: pointer;
    border: 1px solid rgba(70, 60, 40, 0.22);
    background: var(--color-surface, #fff);
    color: var(--color-muted, #8a8275);
}
.persona-narrow__chip.is-active {
    background: var(--color-accent, #b87d5e);
    border-color: var(--color-accent, #b87d5e);
    color: #fff;
}

/* --- Discussion Groups browse page ---------------------------------- */

.dg-page {
    max-width: 720px;
    margin: 2rem auto;
    padding: 0 1rem;
}

.dg-page__head {
    margin-bottom: 1.5rem;
}

.dg-page__head h1 {
    margin: 0 0 0.4rem;
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--color-text, #2c2920);
}

.dg-page__intro {
    margin: 0;
    font-size: 1rem;
    color: var(--color-muted, #8a8275);
}

.dg-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.75rem;
}

.dg-card {
    border-radius: var(--radius-md, 8px);
    background: var(--color-surface, #fff);
    /* Stronger edge + a real drop shadow so each card reads as a distinct,
       liftable surface. overflow:hidden clips the right thumbnail to the
       rounded corners. */
    border: 1px solid rgba(70, 60, 40, 0.28);
    box-shadow: 0 2px 6px rgba(70, 60, 40, 0.18);
    overflow: hidden;
    transition: box-shadow 180ms ease, transform 180ms ease, border-color 180ms ease;
}

/* Lift + warm the edge on hover/focus so the whole card reads as one big,
   obviously-clickable target (the entire card is the link). */
.dg-card:hover,
.dg-card:focus-within {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(70, 60, 40, 0.30);
    border-color: rgba(70, 60, 40, 0.40);
}
/* Keyboard focus ring on the whole card (outline on the overflow:hidden box
   itself isn't clipped, unlike one on the inner link). */
.dg-card:focus-within {
    outline: 2px solid var(--color-primary-deep, #5d7a52);
    outline-offset: 2px;
}

/* Horizontal layout: text body left, square club thumbnail flush to the right
   edge -- mirrors the event-card thumbnail treatment. */
.dg-card__link {
    display: flex;
    align-items: stretch;
    text-decoration: none;
    color: inherit;
}

.dg-card__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.85rem 1.1rem;
}

.dg-card__name {
    font-weight: 700;
    font-size: 1rem;
    color: var(--color-text, #2c2920);
    transition: color 180ms ease;
}
.dg-card:hover .dg-card__name,
.dg-card:focus-within .dg-card__name {
    color: var(--color-accent, #b87d5e);
}

.dg-card__meta {
    font-size: 0.88rem;
    color: var(--color-muted, #8a8275);
    line-height: 1.4;
}

/* CTA cue at the foot of the text (the top-right is the thumbnail now): an
   accent "Read more ->" whose arrow nudges right on hover/focus. */
.dg-card__cue {
    margin-top: 0.1rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-accent, #b87d5e);
}
.dg-card__arrow {
    display: inline-block;
    transition: transform 180ms ease;
}
.dg-card:hover .dg-card__arrow,
.dg-card:focus-within .dg-card__arrow {
    transform: translateX(3px);
}

/* Square-ish thumbnail strip flush to the card's right edge, full card height. */
.dg-card__thumb {
    flex: 0 0 auto;
    width: 96px;
    align-self: stretch;
    background: var(--color-bg, #f6f3ed);
}
.dg-card__thumb img {
    display: block;
    width: 96px;
    height: 100%;
    min-height: 96px;
    object-fit: cover;
}

/* --- Club search results section ----------------------------------------- */

.club-results {
    margin: 0 0 1rem;
}
.club-results__heading {
    font-size: 0.95rem;
    margin: 0 0 0.5rem;
    color: var(--color-text, #2c2418);
}
.club-results__list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 0.5rem;
}
.club-results__card {
    border: 1px solid rgba(70, 60, 40, 0.30);
    box-shadow: 0 4px 11px rgba(70, 60, 40, 0.34),
                0 2px 4px rgba(70, 60, 40, 0.22);
    border-radius: var(--radius-md, 10px);
    /* Warm parchment tint (not the event cards' pure white) so a run of club
       cards reads as its own "groups to join" band -- clubs are evergreen,
       events are moments. */
    background: #faf6ec;
}
.club-results__card:hover {
    box-shadow: 0 6px 15px rgba(70, 60, 40, 0.38),
                0 2px 4px rgba(70, 60, 40, 0.24);
}
.club-results__link {
    display: flex;
    align-items: stretch;
    text-decoration: none;
    color: inherit;
    overflow: hidden;                 /* clip the thumb to the card radius */
    border-radius: var(--radius-md, 10px);
}
.club-results__text {
    flex: 1 1 auto;
    min-width: 0;                     /* let text shrink instead of shoving the thumb */
    padding: 0.6rem 0.75rem;
}
/* Small uppercase kicker that signals "this is a group, not an event". */
.club-results__eyebrow {
    display: block;
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: #a3673f;
    margin-bottom: 0.15rem;
}
.club-results__eyebrow::before {
    content: "\25C6";   /* diamond accent mark */
    margin-right: 0.4em;
    opacity: 0.75;
}
.club-results__name {
    display: block;
    font-weight: 700;
}
/* Meeting-cadence footer strip -- info events don't carry. Sits under the
   text only (the thumb stays full-height beside it). Hidden when the club
   has no fixed cadence. */
.club-results__meta {
    display: block;
    margin-top: 0.45rem;
    padding-top: 0.4rem;
    border-top: 1px solid rgba(70, 60, 40, 0.14);
    font-size: 0.78rem;
    font-weight: 600;
    color: #7a5a3a;
}
.club-results__desc {
    display: block;
    margin-top: 0.2rem;
    font-size: 0.85rem;
    color: var(--color-muted, #6b6151);
}
/* Right-side thumbnail -- mirrors the event-card thumb so the two card types
   read as one family. Warm defined edge per the chip/card edge convention. */
.club-results__thumb {
    flex: 0 0 auto;
    width: 96px;
    align-self: stretch;
    border-left: 1px solid rgba(70, 60, 40, 0.18);
    background: var(--color-bg, #efe9dd);
    overflow: hidden;
}
.club-results__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
