/* ============================================================
   Tokens
   ============================================================ */
:root {
    --bg: #0f172a;
    --surface: rgba(30, 41, 59, 0.7);
    --surface-strong: #1e293b;
    --text: #f8fafc;
    --text-muted: #94a3b8;
    --accent: #3b82f6;
    --accent-hover: #60a5fa;
    --price: #10b981;
    --hairline: rgba(255, 255, 255, 0.08);
    --hairline-strong: rgba(255, 255, 255, 0.14);

    /* 8px spacing scale */
    --s-1: 4px;
    --s-2: 8px;
    --s-3: 16px;
    --s-4: 24px;
    --s-5: 32px;
    --s-6: 48px;

    --r-sm: 8px;
    --r-md: 12px;
    --r-lg: 16px;
    --r-pill: 999px;
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    font-family: 'Inter', system-ui, -apple-system, sans-serif;
}

body {
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
}

.is-hidden { display: none !important; }

/* ============================================================
   App splash (radar sweep on startup)
   ============================================================ */
.app-splash {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--s-4);
    background: radial-gradient(circle at 50% 40%, #14253f 0%, var(--bg) 70%);
    transition: opacity 0.4s ease, visibility 0.4s ease;
}

.app-splash.is-dismissed {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

html.no-splash .app-splash { display: none; }

.app-splash-radar {
    width: min(60vw, 220px);
    aspect-ratio: 1 / 1;
    filter: drop-shadow(0 0 24px rgba(59, 130, 246, 0.35));
}

.app-splash-svg {
    width: 100%;
    height: 100%;
    display: block;
}

.app-splash-sweep {
    transform-origin: 100px 100px;
    animation: app-splash-sweep 1.8s linear infinite;
}

@keyframes app-splash-sweep {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

.app-splash-blip {
    opacity: 0;
    transform-origin: 100px 100px;
    animation: app-splash-blip 1.8s linear infinite;
}

.app-splash-blip--a { animation-delay: 0.15s; }
.app-splash-blip--b { animation-delay: 0.85s; }
.app-splash-blip--c { animation-delay: 1.35s; }

@keyframes app-splash-blip {
    0%   { opacity: 0; }
    8%   { opacity: 1; }
    60%  { opacity: 0.6; }
    100% { opacity: 0; }
}

.app-splash-text {
    text-align: center;
    color: var(--text);
}

.app-splash-title {
    font-size: clamp(22px, 5vw, 30px);
    font-weight: 700;
    letter-spacing: 0.2px;
}

.app-splash-tag {
    margin-top: 6px;
    color: var(--text-muted);
    font-size: 14px;
}

@media (prefers-reduced-motion: reduce) {
    .app-splash-sweep,
    .app-splash-blip {
        animation: none;
    }
    .app-splash-blip { opacity: 1; }
}

/* ============================================================
   Layout
   ============================================================ */
.app-container {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--s-5) var(--s-4);
}

/* ============================================================
   Header
   ============================================================ */
header {
    margin-bottom: var(--s-5);
}

.header-main {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--s-3);
    margin-bottom: var(--s-1);
}

/* Top-right bell button — entry point to the Deals Alert signup modal
   (.da-overlay). Matches the muted translucent treatment used by other
   header/card buttons (.share-btn, chip-style controls) rather than a
   high-contrast white pill. */
.notif-wrap {
    position: relative;
    flex-shrink: 0;
}

.notif-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Matches .brand-mark (32px) so the bell sits visually balanced
       opposite the radar logo. */
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--hairline-strong);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.04);
    cursor: pointer;
    color: var(--accent);
    position: relative;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.notif-btn svg {
    width: 18px;
    height: 18px;
}

.notif-btn:hover,
.notif-btn:focus-visible {
    background: rgba(96, 165, 250, 0.12);
    border-color: var(--accent);
    color: #93c5fd;
    outline: none;
}

.notif-badge {
    position: absolute;
    top: -2px;
    right: -2px;
    width: 8px;
    height: 8px;
    background: #ef4444;
    border-radius: 50%;
    border: 2px solid var(--bg, #0f172a);
}

header h1 {
    font-size: 2.25rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--text);
    margin: 0;
}

.brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.brand-mark {
    width: 64px;
    height: 64px;
    flex: 0 0 auto;
    filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.25));
}

.tagline {
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.5;
}

/* Slim "how it works" hint line under the tagline. Always present (not
   first-visit-gated) — gives a newcomer the 3-beat mental model without a
   modal or banner. Reads as a hint, not a hero: muted, one line, wraps on
   narrow viewports. "Scan" rhymes with the radar brand mark + splash copy.
   Accent-tinted middots separate the beats. */
.home-hero-steps {
    margin: var(--s-2) 0 0;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--text-muted);
}
.hero-sep {
    margin: 0 0.6ch;
    color: var(--accent);
    opacity: 0.6;
}

.install-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--s-3);
    padding: 8px 14px;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: var(--r-pill);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.05s ease;
}

.install-btn:hover { background: var(--accent-hover); }
.install-btn:active { transform: translateY(1px); }

.ios-install-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--s-3);
}

.ios-install-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(2px);
}

.ios-install-modal-content {
    position: relative;
    background: var(--surface-strong);
    color: var(--text);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-lg);
    padding: var(--s-4);
    max-width: 360px;
    width: 100%;
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
}

.ios-install-modal-content h2 {
    font-size: 1.15rem;
    margin-bottom: var(--s-2);
}

.ios-install-modal-content p {
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    margin-bottom: var(--s-3);
}

.ios-install-modal-content ol {
    margin: 0 0 var(--s-3) var(--s-3);
    padding-left: var(--s-3);
    font-size: 0.9rem;
    line-height: 1.7;
}

.ios-install-modal-content ol li { margin-bottom: 4px; }

.ios-share-icon {
    display: inline-block;
    padding: 0 6px;
    background: rgba(59, 130, 246, 0.18);
    border-radius: 4px;
    font-weight: 700;
    color: var(--accent-hover);
}

.ios-install-modal-note {
    font-size: 0.8rem !important;
    color: var(--text-muted);
    border-top: 1px solid var(--hairline);
    padding-top: var(--s-2);
    margin-top: var(--s-2);
    margin-bottom: 0 !important;
}

.ios-install-modal-close {
    position: absolute;
    top: 8px;
    right: 12px;
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
}

.ios-install-modal-close:hover { color: var(--text); }

.install-toast {
    position: fixed;
    left: 50%;
    bottom: var(--s-4);
    transform: translate(-50%, 16px);
    z-index: 1001;
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    max-width: calc(100vw - var(--s-4) * 2);
    padding: 10px 16px;
    background: var(--surface-strong);
    color: var(--text);
    border: 1px solid rgba(16, 185, 129, 0.4);
    border-radius: var(--r-pill);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
    font-size: 0.9rem;
    font-weight: 500;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
}

.install-toast.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}

.install-toast-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--price);
    color: #052e22;
    font-size: 0.85rem;
    font-weight: 800;
    flex: 0 0 auto;
}

.install-toast-text { line-height: 1.3; }

.install-reminder {
    position: fixed;
    left: var(--s-3);
    right: var(--s-3);
    bottom: var(--s-3);
    z-index: 999;
    margin: 0 auto;
    max-width: 480px;
    display: flex;
    align-items: center;
    gap: var(--s-3);
    padding: 12px 14px;
    background: var(--surface-strong);
    color: var(--text);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-md);
    box-shadow: 0 14px 32px rgba(0, 0, 0, 0.5);
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.3s ease, transform 0.3s ease;
}

.install-reminder.is-visible {
    opacity: 1;
    transform: translateY(0);
}

.install-reminder-icon { flex: 0 0 auto; }
.install-reminder-icon img {
    display: block;
    width: 40px;
    height: 40px;
    border-radius: 8px;
}

.install-reminder-text {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.3;
}

.install-reminder-text strong {
    font-size: 0.95rem;
    font-weight: 600;
}

.install-reminder-text span {
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-top: 2px;
}

.install-reminder-actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 2px;
}

.install-reminder-btn {
    background: var(--accent);
    color: #fff;
    border: none;
    padding: 8px 14px;
    border-radius: var(--r-pill);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease;
}

.install-reminder-btn:hover { background: var(--accent-hover); }

.install-reminder-close {
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
}

.install-reminder-close:hover { color: var(--text); }

@media (max-width: 480px) {
    .install-reminder { padding: 10px 12px; gap: var(--s-2); }
    .install-reminder-icon img { width: 32px; height: 32px; }
    .install-reminder-text strong { font-size: 0.85rem; }
    .install-reminder-text span { font-size: 0.75rem; }
    .install-reminder-btn { padding: 6px 12px; font-size: 0.8rem; }
}

/* ============================================================
   Top picks nearby — at-a-glance grouping by restaurant. Each row
   is its own card mirroring the deal-card header (name + distance,
   rating/count/price/open-now), with the strongest matching deal
   underneath. Multi-deal restaurants expand inline.
   Hierarchy is layered:
     • restaurant name  — 1.05rem / 700 / text
     • meta line        — 0.85rem / 400 / muted (rating, price, open)
     • deal title       — 0.95rem / 600 / text
     • deal when/price  — 0.82rem / 400 / muted
   Color carries the muted-vs-prominent distinction; weight separates
   restaurant-name from deal-title without making them the same shape.
   ============================================================ */
.top-summary {
    margin: 0 0 var(--s-4) 0;
}

.top-summary.is-hidden { display: none; }

/* Dev-only "top picks only" mode (toggle via localStorage 'mealradar:topOnlyMode:v1'
   or ?topOnly=1). Hides the image-card list so the top-picks section can be
   evaluated standalone with its own paginated "Show more". */
body.is-top-only #dealsContainer,
body.is-top-only #truncationBanner { display: none !important; }
/* Top Picks came up empty (e.g. search matched only 'everyday' deals that
   fail the top-pick eligibility gate). Fall back to the regular list so the
   user isn't staring at a blank screen — set by renderTopSummary. */
body.is-top-only.top-picks-empty #dealsContainer { display: block !important; }
.top-summary__more-btn { margin-top: 12px; }

/* ============================================================
   Holiday banner — calm advisory above the deal list when a
   public holiday falls inside the active region's near-term
   window. Amber palette (matches sponsored) reads as "heads-up"
   without being alarming. Dismissable per-holiday.
   ============================================================ */
.holiday-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--s-2);
    margin: 0 0 var(--s-3) 0;
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-md, 8px);
    background: rgba(245, 158, 11, 0.08);
    border: 1px solid rgba(245, 158, 11, 0.28);
    font-size: 0.85rem;
    line-height: 1.4;
}

.holiday-banner.is-hidden { display: none; }

/* Inline "Request <town>?" link inside the nearby-town banner. Styled as a
   quiet text link (not a CTA button) so it reads as an offer, not a demand —
   matches the friendly, non-sales-y voice. */
.nearby-banner-request {
    background: transparent;
    border: none;
    padding: 0;
    color: var(--accent, #4f9dff);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

.nearby-banner-request:hover {
    color: var(--text);
}

.holiday-banner-icon {
    color: #f59e0b;
    font-weight: 700;
    line-height: 1.4;
    flex: 0 0 auto;
}

.holiday-banner-body {
    flex: 1 1 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s-2);
    color: var(--text);
}

.holiday-banner-item {
    color: var(--text);
}

.holiday-banner-item + .holiday-banner-item::before {
    content: '·';
    margin-right: var(--s-2);
    color: var(--text-muted);
}

.holiday-banner-note {
    color: var(--text-muted);
    font-size: 0.8rem;
}

.holiday-banner-dismiss {
    flex: 0 0 auto;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: var(--r-sm, 6px);
    transition: background 0.15s, color 0.15s;
}

.holiday-banner-dismiss:hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
}

/* ============================================================
   Verify tip — evergreen "double-check the link" nudge that
   teaches the gold-dot system. Tinted gold to echo the verified
   dot it explains. Resurfaces a few times then retires (app.js).
   ============================================================ */
.verify-tip {
    display: flex;
    align-items: flex-start;
    gap: var(--s-2);
    margin: 0 0 var(--s-3) 0;
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-md, 8px);
    background: rgba(212, 160, 23, 0.08);
    border: 1px solid rgba(212, 160, 23, 0.28);
    font-size: 0.85rem;
    line-height: 1.4;
}

.verify-tip.is-hidden { display: none; }

.verify-tip-icon {
    line-height: 1.4;
    flex: 0 0 auto;
}

.verify-tip-body {
    flex: 1 1 auto;
    color: var(--text);
}

/* The inline dot is a <span> here (not the tappable card <button>); keep it
   centered against the text baseline. */
.verify-tip-body .mr-dot {
    margin: 0 3px;
    pointer-events: none;
}

/* Mini preview of how a confirmed name looks on a card — a subtle chip so the
   "next to the name" instruction is shown, not just described. */
.verify-tip-example {
    display: inline-flex;
    align-items: center;
    gap: 0;
    margin: 0 2px;
    padding: 1px 7px;
    border-radius: var(--r-sm, 6px);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.12);
    font-weight: 600;
    white-space: nowrap;
}

.verify-tip-example .mr-dot {
    margin: 0 0 0 5px;
}

.verify-tip-dismiss {
    flex: 0 0 auto;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: var(--r-sm, 6px);
    transition: background 0.15s, color 0.15s;
}

.verify-tip-dismiss:hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
}

/* ============================================================
   Truncation banner — surfaces when the /api/deals D1 response
   returned the LIMIT-capped slice (per d1_catalog_plan.md §Why
   LIMIT 1500). Calm informational tone matching the holiday
   banner; only renders when source=d1 AND envelope.truncated.
   ============================================================ */
.truncation-banner {
    margin: 0 0 var(--s-3) 0;
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-md, 8px);
    background: rgba(99, 102, 241, 0.08);
    border: 1px solid rgba(99, 102, 241, 0.28);
    color: var(--text);
    font-size: 0.85rem;
    line-height: 1.4;
}

.truncation-banner.is-hidden { display: none; }

/* Day chip dot: appears when the next occurrence of that weekday
   falls on a holiday in the active region. Quiet visual — a small
   amber pip in the upper-right corner. Persistent reminder that
   complements the dismissable banner above. */
.chip-filter-btn.has-holiday {
    position: relative;
}

.chip-filter-btn.has-holiday::after {
    content: '';
    position: absolute;
    top: 3px;
    right: 4px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #f59e0b;
    box-shadow: 0 0 0 1.5px var(--surface, #0f172a);
}

/* Asterisk on the Open/Closed pill when today is a holiday — the
   verdict is from regular hours, so flag the uncertainty inline
   rather than silently mislead. */
.open-now-badge--holiday {
    cursor: help;
}

.open-now-badge-mark {
    margin-left: 3px;
    opacity: 0.85;
    font-size: 0.9em;
}

.top-summary__heading {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 10px;
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--text);
    letter-spacing: 0.01em;
}

.top-summary__heading::before {
    content: "";
    display: inline-block;
    width: 3px;
    height: 14px;
    border-radius: 2px;
    background: var(--accent);
}

.top-summary__count {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.82rem;
    font-weight: 500;
    letter-spacing: 0;
    white-space: nowrap;
}

.top-summary__count:empty { display: none; }

/* Weather Day/Night-style rows. Stacked vertically; each row is internally
   horizontal — hero block (icon + big price) on the left, content column
   (restaurant name, deal title, meta strip) on the right. */
.top-summary__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 18px;
}

.top-summary__row {
    position: relative;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: stretch;
    /* Row-gap doubles as the vertical space between the photo/emoji/price top
       zone and the name/deal text body below — keep it tight so the deal text
       reads as close to its emoji. */
    gap: 10px;
    padding: 16px 20px;
    background: linear-gradient(135deg, rgba(59, 130, 246, 0.14) 0%, rgba(59, 130, 246, 0.04) 100%);
    border: 1px solid rgba(96, 165, 250, 0.22);
    border-radius: 16px;
    line-height: 1.35;
    /* The card body isn't clickable — only the "+N more" toggle is — so no
       pointer cursor or hover-lift that would imply the whole card is a button. */
    cursor: default;
    transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
}
.top-summary__row:hover {
    border-color: rgba(96, 165, 250, 0.5);
}
.top-summary__row.is-expanded {
    border-color: var(--accent);
    box-shadow: 0 6px 16px -8px rgba(59, 130, 246, 0.55);
}

/* Top zone of each row: a large restaurant photo on the left with the deal
   emoji + price to its right. The text info (name, deal, meta, address) sits
   full-width below it (.top-summary__body has flex-basis 100% so it wraps to
   its own line). */
.top-summary__top {
    flex: 1 1 100%;
    min-width: 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 16px;
}

/* Share + flag (report) buttons, pinned to the top-right of the row on the
   same line as the photo and emoji. */
.top-summary__top-actions {
    /* Pinned to the far right whether or not the promo slot is present — when
       there's no ad, the photo sits left and the actions hug the right edge. */
    margin-left: auto;
    /* Stretch to the photo's height and push share to the top, flag to the
       bottom — so the flag aligns with the bottom edge of the photo. */
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 8px;
}

/* Ad / promo slot — sits directly to the right of the photo, in the space the
   old deal emoji + price hero used to occupy. Only rendered (by topPickPromoHtml)
   for sponsored restaurants that carry creative; collapses entirely otherwise so
   organic cards give the full width back to the photo + actions. Always carries
   a "Promoted" label (disclosure is mandatory). */
.top-summary__promo {
    flex: 1 1 auto;
    min-width: 0;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    padding: 8px 10px;
    border: 1px solid rgba(251, 191, 36, 0.35);
    background: rgba(251, 191, 36, 0.08);
    border-radius: 12px;
    overflow: hidden;
}
.top-summary__promo-label {
    align-self: flex-start;
    flex: 0 0 auto;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: #fbbf24;
    background: rgba(251, 191, 36, 0.16);
    border-radius: 999px;
    padding: 1px 7px;
}
.top-summary__promo-img {
    width: 100%;
    max-height: 92px;
    object-fit: cover;
    border-radius: 8px;
}
.top-summary__promo-text {
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.3;
    color: var(--text);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Large restaurant photo — the focal point of the row. Hides itself on image
   error so rows without a photo fall back to just the emoji + price. */
.top-summary__photo {
    margin: 0;
    flex: 0 0 auto;
    width: 225px;
    line-height: 0;
}
.top-summary__photo img {
    width: 100%;
    height: 112px;
    object-fit: cover;
    border-radius: 12px;
    border: 1px solid var(--hairline);
}
.top-summary__photo.no-photo { display: none; }

/* Per-deal list — one line per deal (small type emoji + price + text, with the
   day/time beneath), sitting at the bottom of the card body below the address.
   Only the first VISIBLE_DEAL_LINES show; the rest collapse behind "+N more". */
.top-summary__deals {
    list-style: none;
    /* Clear separation between the restaurant header (name/meta/address) and the
       first deal line. */
    margin: 14px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.top-summary__deal-line {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}
.top-summary__deal-icon {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--accent-hover);
}
.top-summary__deal-icon .deal-emoji { width: 100%; height: 100%; }
.top-summary__deal-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.top-summary__deal-headline {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
}
.top-summary__deal-price {
    font-weight: 800;
    color: #fbbf24;
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
}
.top-summary__deal-text {
    color: var(--text);
    font-size: 0.92rem;
    line-height: 1.3;
}
.top-summary__deal-when {
    font-size: 0.76rem;
    color: var(--text-muted);
}

/* Extra deal lines + the dropped-deals label stay hidden until the card is
   expanded (whole-row click, or the "+N more" toggle). */
.top-summary__deal-line--extra,
.top-summary__deals-label { display: none; }
.top-summary__row.is-expanded .top-summary__deal-line--extra { display: flex; }
.top-summary__row.is-expanded .top-summary__deals-label { display: block; }
/* A restaurant with many deals would otherwise grow the card taller than the
   phone viewport when expanded — cap the list and let it scroll in place, then
   chain to the page once it hits the top/bottom (default overscroll behaviour;
   do NOT use `overscroll-behavior: contain`, which traps the scroll in the
   list). `min-height: 0` is required: the list is a flex item (parent is a flex
   column), whose default `min-height: auto` resolves to the full content height
   and overrides `max-height`, so without it the list never becomes scrollable.
   Scoped to the main list — the map popup has its own scroll container. */
.top-summary__list:not(.top-summary__list--popup) .top-summary__row.is-expanded .top-summary__deals {
    min-height: 0;
    max-height: 50vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* room so the last line / scrollbar doesn't kiss the card edge */
    padding-right: 4px;
}
.top-summary__deal-line--hidden { opacity: 0.6; }
/* Divider that marks the boundary between the deals matching the active filters
   (above) and the ones a filter dropped (below) — a full-width line plus a
   clearer label so the out-of-filter section is easy to notice. */
.top-summary__deals-label {
    margin-top: 6px;
    padding-top: 12px;
    border-top: 1px solid var(--hairline-strong);
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

/* "+N more / Show fewer" toggle, styled as another deal line. */
.top-summary__more-toggle {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--accent-hover);
    font-weight: 700;
    font-size: 0.82rem;
}
.top-summary__more-hide { display: none; }
.top-summary__row.is-expanded .top-summary__more-show { display: none; }
.top-summary__row.is-expanded .top-summary__more-hide { display: inline; }

/* Content column: restaurant name, deal title, meta strip.
   `flex-basis: 0` (not auto) so the row's flex-wrap doesn't kick the body
   below the hero when the foot's max-content (e.g. long meta + "+N more")
   exceeds available width. With basis 0, body just fills the remainder
   and its children shrink/wrap inside via min-width:0 + ellipsis. */
.top-summary__body {
    /* Full-width line below the photo/emoji top zone. */
    flex: 1 1 100%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
}

.top-summary__head {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 22px;
}

.top-summary__place {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    letter-spacing: 0.005em;
    /* Long names wrap to a second line instead of getting truncated.
       Matches the .top-summary__deal treatment one row below. The
       confidence dot is an inline child and rides along inside the
       webkit-box. Full name available via the title= tooltip. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-word;
    line-height: 1.25;
}
/* When the name links to the restaurant's /r/ SEO page, suppress the default
   link chrome and only hint interactivity on hover/focus. */
a.top-summary__place-link {
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}
a.top-summary__place-link:hover,
a.top-summary__place-link:focus-visible {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Photo wrapped in a link to the /r/ SEO page — keep it a tight box around the
   figure (no extra line-height gap) and give a subtle press affordance. */
.top-summary__photo-link {
    display: block;
    flex: 0 0 auto;
    line-height: 0;
    border-radius: 12px;
}
.top-summary__photo-link .top-summary__photo { margin: 0; }
/* Photo failed to load → figure self-hides; collapse the empty link too so it
   doesn't sit in the layout (or tab order) as a clickable blank. */
.top-summary__photo-link:has(.no-photo) { display: none; }
.top-summary__photo-link:hover img,
.top-summary__photo-link:focus-visible img {
    filter: brightness(0.92);
}

.top-summary__sponsor-pin {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    font-size: 0.68rem;
    font-weight: 700;
    color: #fbbf24;
    background: rgba(251, 191, 36, 0.14);
    border: 1px solid rgba(251, 191, 36, 0.32);
    padding: 1px 8px;
    border-radius: 999px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    line-height: 1.4;
}

.top-summary__share {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    background: transparent;
    border: 1px solid rgba(96, 165, 250, 0.32);
    border-radius: 999px;
    color: var(--accent-hover, #60a5fa);
    cursor: pointer;
    transition: background-color 150ms ease, border-color 150ms ease;
}
.top-summary__share svg {
    width: 13px;
    height: 13px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    display: block;
}
.top-summary__share:hover,
.top-summary__share:focus-visible {
    background: rgba(59, 130, 246, 0.22);
    border-color: rgba(96, 165, 250, 0.65);
    outline: none;
}

/* Report-a-problem (flag) button — sits in .top-summary__top-actions at the
   top-right of the row, next to the share button. */
.top-summary__report {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    background: transparent;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    color: var(--text-muted);
    cursor: pointer;
    transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}
.top-summary__report svg {
    width: 13px;
    height: 13px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    display: block;
}
.top-summary__report:hover,
.top-summary__report:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    border-color: var(--hairline-strong);
    color: var(--text);
    outline: none;
}

/* "Show on map" button — in-app pan-to-pin, sits between share and report in
   .top-summary__top-actions. Same accent treatment as share (it's an action,
   not the muted report flag). */
.top-summary__map {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    background: transparent;
    border: 1px solid rgba(96, 165, 250, 0.32);
    border-radius: 999px;
    color: var(--accent-hover, #60a5fa);
    cursor: pointer;
    transition: background-color 150ms ease, border-color 150ms ease;
}
.top-summary__map svg {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    display: block;
}
.top-summary__map:hover,
.top-summary__map:focus-visible {
    background: rgba(59, 130, 246, 0.22);
    border-color: rgba(96, 165, 250, 0.65);
    outline: none;
}

.top-summary__deal {
    font-size: 0.92rem;
    font-weight: 500;
    color: var(--text);
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-word;
}

/* Meta strip — the "P.O.P. · Wind · Humidity" pattern. Dot-separated. */
.top-summary__foot {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 0.8rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.top-summary__meta-item {
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    gap: 3px;
}
.top-summary__meta-item small { font-size: 0.85em; opacity: 0.8; margin-left: 1px; }
.top-summary__rating-star {
    color: #fbbf24;
    font-size: 0.95em;
    line-height: 1;
}
.top-summary__dot { opacity: 0.5; }
/* Atmosphere tags + "+N more" on one row: tags left, "+N more" pushed right
   and vertically centered with them. */
.top-summary__tagline {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 4px;
    /* Breathing room below the tags / "+N more" row before the card edge. */
    margin-bottom: 8px;
}
.top-summary__tagline .atmosphere-pills { margin: 0; }

.top-summary__more {
    flex: 0 0 auto;
    margin-left: auto;
    padding: 2px 8px;
    background: rgba(96, 165, 250, 0.18);
    color: var(--accent-hover);
    border-radius: 999px;
    font-weight: 700;
    font-size: 0.72rem;
}

/* Address line in the Top Picks body, linked to Google Maps directions. */
.top-summary__address {
    display: inline-flex;
    align-self: flex-start;   /* don't stretch to full row width in the flex column */
    max-width: 100%;
    align-items: baseline;
    gap: 4px;
    font-size: 0.78rem;
    line-height: 1.3;
    color: var(--text-muted);
    text-decoration: none;
}
.top-summary__address:hover,
.top-summary__address:focus-visible {
    color: var(--text);
    text-decoration: underline;
}

@media (max-width: 500px) {
    .top-summary__row { padding: 14px 14px; gap: 10px; }
    .top-summary__top { gap: 12px; }
    /* Narrower photo on phones so the ad slot (when present) and actions still
       fit beside it. */
    .top-summary__photo { width: 160px; }
    .top-summary__photo img { height: 96px; }
    .top-summary__place { font-size: 1rem; }
    .top-summary__deal-text { font-size: 0.88rem; }
    .top-summary__foot { font-size: 0.76rem; }
}

/* Expansion panel shown when a top-picks card is clicked. Now anchored
   below the horizontal strip (sibling of the <ul>) rather than inside
   a card, since cards are flex children that can't host a full-width
   panel without breaking the row layout. */
.top-pick-overlay {
    margin-top: 12px;
    background: var(--surface-strong);
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    overflow: hidden;
    animation: top-pick-overlay-pop 140ms ease-out;
}


@keyframes top-pick-overlay-pop {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.data-source-container {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    background: rgba(255, 255, 255, 0.05);
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-pill);
    border: 1px solid var(--hairline);
}

.data-source-container label {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.data-source-container select,
.data-source-container input {
    background: transparent;
    border: none;
    color: var(--text);
    font-family: inherit;
    font-size: 0.875rem;
    outline: none;
    cursor: pointer;
    font-weight: 600;
}

.data-source-container select option { background: var(--bg); }

.data-source-container input {
    border-bottom: 1px solid var(--accent);
    padding: 2px 4px;
    width: 150px;
}

/* ============================================================
   Map
   ============================================================ */
.map-section {
    position: relative;
    width: 100%;
    height: 200px;
    margin-bottom: var(--s-4);
    border-radius: var(--r-lg);
    overflow: hidden;
    border: 1px solid var(--hairline);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
    transition: height 0.45s ease, margin-bottom 0.45s ease;
    /* Isolate the map onto its own GPU layer so scrolling the page
       doesn't force the (very expensive) map tile repaint on each frame. */
    transform: translateZ(0);
    will-change: transform;
    contain: layout paint style;
}

.map-section.is-expanded {
    height: 420px;
    margin-bottom: var(--s-5);
}

.map-section.is-collapsed {
    /* !important so "hidden" always wins over .is-expanded, regardless of source
       order. Without it the later max-width:768px `.map-section.is-expanded`
       rule (height:55vh; min-height:320px) overrides height:0 when the map is
       hidden while still expanded — so on mobile the Hide-map icon left the map
       open at ~320px. (Same precedence trick .is-fullscreen uses below; that
       rule is later in source, so full-screen still wins over collapsed.) */
    height: 0 !important;
    min-height: 0 !important;
    max-height: none !important;
    margin-bottom: 0;
    border-width: 0;
    box-shadow: none;
    pointer-events: none;
}

/* Full-screen map — CSS overlay (not the native Fullscreen API, which no-ops
   on iOS Safari for non-video elements). The map section pins to the whole
   viewport. 100dvh (not 100vh) so the iOS URL bar doesn't crop the bottom.
   Opaque dark background — the map fills it, so no translucent scrim. Sits at
   the modal z-index tier (.da-overlay is 900); above app chrome, below toast
   (1200) and splash (9999). Composes with is-expanded/is-collapsed: the
   fixed/inset overrides win regardless of those states. */
.map-section.is-fullscreen {
    /* !important so the overlay geometry always wins over .is-expanded — incl.
       the later max-width:768px `.map-section.is-expanded { height: 55vh }` rule,
       which (equal specificity, later in source) would otherwise shrink the
       "full-screen" map to 55vh when entered from the expanded state. */
    position: fixed !important;
    inset: 0 !important;
    width: 100vw !important;
    height: 100vh !important;   /* fallback for no-dvh browsers */
    height: 100dvh !important;
    /* The mobile `.is-expanded` rule also sets min/max-height (320/600px), which
       would clamp the full-screen height — override both, not just height. */
    min-height: 0 !important;
    max-height: none !important;
    margin: 0 !important;
    /* Drop the GPU-layer transform/will-change in full-screen so the fixed
       overlay can't be trapped by its own self-transform on any engine. */
    transform: none !important;
    will-change: auto !important;
    border-width: 0;
    border-radius: 0;
    box-shadow: none;
    background: #0b1120;
    z-index: 905;
    pointer-events: auto;
    /* The page behind is scroll-locked; don't animate the height jump. */
    transition: none;
    contain: none;
}

#map {
    width: 100%;
    height: 100%;
    z-index: 1;
}

/* Radar sweep overlay — plays when the map first reveals from collapsed. */
.map-radar-sweep {
    position: absolute;
    inset: 0;
    z-index: 10;
    pointer-events: none;
    overflow: hidden;
    opacity: 0;
    transition: opacity 0.5s ease;
}

.map-radar-sweep.is-active { opacity: 1; }

.map-radar-beam {
    position: absolute;
    width: 200%;
    height: 200%;
    top: -50%;
    left: -50%;
    background: conic-gradient(
        from 0deg at 50% 50%,
        transparent 0deg,
        rgba(59, 130, 246, 0.04) 10deg,
        rgba(59, 130, 246, 0.28) 32deg,
        rgba(59, 130, 246, 0.04) 55deg,
        transparent 60deg 360deg
    );
    animation: radarBeam 1.4s linear infinite;
    transform-origin: 50% 50%;
}

@keyframes radarBeam {
    to { transform: rotate(360deg); }
}

.map-radar-rings {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.map-radar-ring {
    position: absolute;
    border-radius: 50%;
    border: 1px solid rgba(59, 130, 246, 0.18);
}

.map-radar-ring:nth-child(1) { width:  90px; height:  90px; }
.map-radar-ring:nth-child(2) { width: 190px; height: 190px; }
.map-radar-ring:nth-child(3) { width: 310px; height: 310px; }
.map-radar-ring:nth-child(4) { width: 460px; height: 460px; }

@media (prefers-reduced-motion: reduce) {
    .map-radar-beam { animation: none; }
}

.map-overlay-controls {
    position: absolute;
    bottom: var(--s-3);
    left: var(--s-3);
    right: var(--s-3);
    z-index: 400;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--s-2);
    pointer-events: none;
}

.map-overlay-controls > * { pointer-events: auto; }

.map-btn,
.radius-control {
    background: rgba(15, 23, 42, 0.85);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: var(--text);
    border: 1px solid var(--hairline);
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-pill);
    font-size: 0.875rem;
    font-weight: 600;
}

.map-btn {
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, opacity 0.15s;
}

.map-btn:hover {
    background: var(--accent);
    border-color: var(--accent);
}

.map-btn:disabled {
    opacity: 0.6;
    cursor: wait;
}

/* Icon-only map button (full-screen toggle). Square-ish so the SVG centres. */
.map-btn--icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--s-2);
    line-height: 0;
}

.map-btn__icon { display: block; }

/* Default state: show the expand glyph, hide the collapse glyph. The
   .is-fullscreen state (set on .map-section) swaps them. */
.map-btn__icon--collapse { display: none; }

.map-section.is-fullscreen .map-btn--icon .map-btn__icon--expand { display: none; }
.map-section.is-fullscreen .map-btn--icon .map-btn__icon--collapse { display: block; }

.radius-control {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    margin: var(--s-3) 0 var(--s-4) 0;
    padding: var(--s-2) var(--s-3);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--hairline);
    border-radius: var(--r-pill);
    color: var(--text);
    font-weight: 600;
}

/* Location bar — first decision on the page. "Use my location" sits next to
   the city search so the most important orientation control isn't buried as
   a map overlay. The label below names the city the map is currently centred
   on, derived from the nearest COVERAGE entry. */
.location-bar {
    margin: 0 0 var(--s-3) 0;
}

.location-bar-controls {
    display: flex;
    align-items: stretch;
    gap: var(--s-2);
    flex-wrap: wrap;
}

.location-btn {
    background: rgba(15, 23, 42, 0.85);
    color: var(--text);
    border: 1px solid var(--hairline);
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-pill);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    transition: background 0.15s, border-color 0.15s, opacity 0.15s;
}

/* Subtle hover for the dark/inactive state so blue is reserved as the
   signal that the button is "on" (map open / primary action). Without this,
   hovering the closed map-reveal button turned it full --accent blue, which
   read as a second blue button next to the always-blue geolocate primary. */
.location-btn:hover {
    background: rgba(30, 41, 59, 0.95);
    border-color: var(--hairline-strong);
}

.location-btn:disabled { opacity: 0.6; cursor: wait; }

/* While the geolocate request is in flight (click → success / error), pulse
   the button so users see something is happening even though the icon is
   stationary and the map hasn't transitioned in yet. Opacity-only so the
   button stays its resting dark colour — no implied "located" signal. */
@keyframes locate-blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}
.location-btn.is-locating {
    animation: locate-blink 0.9s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
    .location-btn.is-locating { animation: none; opacity: 0.7; }
}

/* Map reveal button stays blue while the map is open (partial or expanded),
   so the toolbar reflects whether the map is currently showing. JS toggles
   .is-active on #mapRevealBtn whenever mapRevealed flips. */
.location-btn.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.location-btn.is-active:hover {
    background: #2563eb;
    border-color: #2563eb;
}

/* Icon-only variant: square button that stretches to the row's cross-size
   (set by the taller .city-search pill), keeping all three controls flush.
   Label is hidden visually but kept in DOM so dynamic label updates
   (e.g. "Locating…") still reach screen readers. */
.location-btn--icon {
    padding: 0;
    aspect-ratio: 1;
    align-self: stretch;
    min-width: 38px;
    justify-content: center;
    flex: 0 0 auto;
}

.location-btn--icon .location-btn-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Stay on a single row at common phone widths (≥360px); only wrap on very
   narrow screens. The city search is supporting, not primary — it should
   share the row with "Use my location", not double the vertical chrome. */
@media (max-width: 500px) {
    .location-btn:not(.location-btn--icon) {
        padding: 6px 12px;
        font-size: 0.82rem;
    }
    .city-search {
        flex: 1 1 140px;
        padding: 4px 10px;
    }
    .city-search-input { font-size: 0.85rem; }
    .city-search-go { padding: 5px 10px; font-size: 0.78rem; }
}

/* City search — typed queries either fly the map to a covered city or open
   the request-city modal pre-filled. Sits inside .location-bar-controls so it
   shares the row with "Use my location". */
.city-search {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    flex: 1 1 200px;
    min-width: 0;
    padding: var(--s-2) var(--s-3);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--hairline);
    border-radius: var(--r-pill);
    position: relative;  /* anchor for .city-suggestions popover */
}

/* First-run cue: a fresh visitor with no city chosen sees the locate button and
   city search gently pulse so it's obvious where to start. Cleared (class
   removed from <body>) once a location loads or the field is engaged. */
body.is-firstrun #geolocateBtn,
body.is-firstrun .city-search {
    border-color: var(--accent);
    animation: firstrun-pulse 1.8s ease-in-out infinite;
}
@keyframes firstrun-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
    50%      { box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.30); }
}
@media (prefers-reduced-motion: reduce) {
    body.is-firstrun #geolocateBtn,
    body.is-firstrun .city-search {
        animation: none;
        box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.30);
    }
}

/* ── First-run guided walkthrough (coachmark tour) ──────────────────────────
   docs/onboarding_walkthrough_plan.md. Spotlight = a positioned "hole" whose
   huge box-shadow dims the rest of the screen, plus a tooltip. Sits above app
   chrome (toast 1200, legend popover 1250) but below the splash (9999) — the
   tour only ever starts after the splash dismisses. */
/* The root is click-through (pointer-events:none) so the spotlit controls stay
   USABLE — step 1 requires the user to actually pick a location. Only the
   tooltip (and the centered-mode backdrop) re-enable pointer events. Dimming for
   anchored steps comes entirely from the hole's box-shadow. */
.tour-root { position: fixed; inset: 0; z-index: 1300; pointer-events: none; }
.tour-backdrop { position: fixed; inset: 0; z-index: 1300; }   /* transparent + non-blocking for anchored steps */
/* Centered mode (no anchor — e.g. "reading a deal" before any card exists):
   hide the spotlight, dim + block the whole screen via the backdrop, center the card. */
.tour-root.is-centered .tour-hole { display: none; }
.tour-root.is-centered .tour-backdrop { background: rgba(8, 15, 30, 0.66); pointer-events: auto; }
.tour-root.is-centered .tour-tooltip {
    left: 50%; top: 50%; transform: translate(-50%, -50%);
}
.tour-hole {
    position: fixed; z-index: 1301; border-radius: 10px;
    box-shadow: 0 0 0 9999px rgba(8, 15, 30, 0.66);
    pointer-events: none;
    transition: left 0.25s ease, top 0.25s ease, width 0.25s ease, height 0.25s ease;
}
.tour-tooltip {
    position: fixed; z-index: 1302; width: min(340px, calc(100vw - 24px));
    pointer-events: auto;
    background: var(--surface-strong); color: var(--text);
    border: 1px solid rgba(255, 255, 255, 0.10); border-radius: 14px;
    padding: 18px 18px 14px; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}
.tour-close {
    position: absolute; top: 8px; right: 10px; background: none; border: none;
    color: inherit; font-size: 20px; line-height: 1; opacity: 0.6; cursor: pointer;
}
.tour-close:hover { opacity: 1; }
.tour-title { margin: 0 0 6px; font-size: 1.05rem; }
.tour-body { margin: 0 0 14px; font-size: 0.9rem; line-height: 1.45; color: var(--text-muted); }
.tour-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tour-progress { font-size: 0.75rem; color: var(--text-muted); }
.tour-btns { display: flex; gap: 8px; }
.tour-btn {
    border-radius: 9px; padding: 8px 14px; font-size: 0.85rem; cursor: pointer;
    border: 1px solid transparent;
}
.tour-btn--ghost { background: none; color: var(--text); border-color: rgba(255, 255, 255, 0.18); opacity: 0.85; }
.tour-btn--ghost:hover { opacity: 1; }
.tour-btn--primary { background: var(--accent); color: #fff; border: none; font-weight: 600; }
.tour-btn--primary:hover { background: var(--accent-hover); }
/* While the browser permission prompt is up and we're waiting on a fix. */
.tour-btn.is-locating { opacity: 0.75; cursor: progress; }
@media (prefers-reduced-motion: reduce) {
    .tour-hole { transition: none; }
}

/* Focus-popover listing the COVERAGE cities. Opens on input focus so users
   discover which cities have data without typing. Picking a row skips the
   geocode round-trip entirely — see selectSuggestion() in app.js. */
.city-suggestions {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    margin: 0;
    padding: 6px 0;
    list-style: none;
    z-index: 1000;
    max-height: 320px;
    overflow-y: auto;
    background: var(--surface-strong);
    border: 1px solid var(--hairline);
    border-radius: 12px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
}

.city-suggestions[hidden] { display: none; }

.city-suggestions-header {
    padding: 6px 14px 4px;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    pointer-events: none;
}

.city-suggestion {
    padding: 8px 14px;
    font-size: 0.9rem;
    color: var(--text);
    cursor: pointer;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-3);
}

.city-suggestion:hover,
.city-suggestion.is-active {
    background: rgba(59, 130, 246, 0.14);
}

.city-suggestion-province {
    font-size: 0.78rem;
    color: var(--text-muted);
}

.city-suggestion-empty {
    padding: 10px 14px;
    font-size: 0.85rem;
    color: var(--text-muted);
}

/* Discovery helper line shown when the input is empty/short (<2 chars) and
   focused. Replaces the focus-dump popover — single static row, presentation-only. */
.city-suggestions-helper {
    padding: 10px 14px;
    font-size: 0.85rem;
    color: var(--text-muted);
    line-height: 1.4;
    pointer-events: none;
}

.city-search-icon {
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1;
}

/* Wraps the input so the aria-live region can announce field updates without
   the input itself being a live region (which would announce every keystroke). */
.city-search-input-wrap {
    display: flex;
    flex: 1;
    min-width: 0;
}

.city-search-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: 0;
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
    padding: 4px 0;
    outline: none;
    /* Long addresses ("123 King Street West, Kitchener, ON") would overflow
       on 320-375px viewports. Truncate with ellipsis when unfocused; native
       input scrolls horizontally on focus as the cursor moves. */
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
}

.city-search-input::placeholder {
    color: var(--text-muted);
}

/* Muted modifier — applied for "Near <city>" and "Custom location" so users
   can tell at a glance that the value was derived from map state, not typed. */
.city-search-input.is-muted {
    color: var(--text-muted);
    font-style: italic;
}

/* Clear (×) button — flex sibling of the input. Hidden via the `hidden` attr
   when the field is empty; JS toggles visibility via setCityFieldValue. */
.city-search-clear {
    flex: 0 0 auto;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.05rem;
    line-height: 1;
    padding: 4px 6px;
    margin: 0;
    cursor: pointer;
    border-radius: 999px;
    transition: color 120ms ease, background 120ms ease;
}

.city-search-clear:hover,
.city-search-clear:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}

.city-search-clear[hidden] { display: none; }

/* Browser autofill (Chrome/Safari) paints its own white background on
   inputs that match a saved entry — including from <datalist> selection
   in some versions. The :-webkit-autofill pseudoclass can't be styled
   with `background-color` directly, but a giant inset box-shadow wins,
   and the 5000s transition trick prevents the brief white flash. */
.city-search-input:-webkit-autofill,
.city-search-input:-webkit-autofill:hover,
.city-search-input:-webkit-autofill:focus,
.city-search-input:-webkit-autofill:active {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.04) inset;
    box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.04) inset;
    transition: background-color 5000s ease-in-out 0s;
    caret-color: var(--text);
}

.city-search-go {
    flex: 0 0 auto;
    padding: 6px 14px;
    font-size: 0.8rem;
}

.city-search.is-loading .city-search-go {
    opacity: 0.6;
    pointer-events: none;
}

.radius-control label {
    font-size: 0.85rem;
    white-space: nowrap;
    color: var(--text-muted);
}

.radius-control label span {
    color: var(--text);
}

.radius-control input[type="range"] {
    flex: 1;
    min-width: 0;
    cursor: pointer;
    accent-color: var(--accent);
}

/* Result count: now rendered inline beside the "Top picks nearby" heading
   (see .top-summary__count). Kept as a hidden live-region fallback so screen
   readers still hear the count when topSummary is collapsed. */
.map-stats {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Inline toast for geolocate + other transient map feedback. */
.map-toast {
    position: absolute;
    top: var(--s-2);
    left: var(--s-3);
    right: var(--s-3);
    z-index: 500;
    background: rgba(15, 23, 42, 0.95);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: var(--text);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-md);
    padding: var(--s-2) var(--s-3);
    font-size: 0.875rem;
    line-height: 1.4;
    pointer-events: none;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.map-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* Map markers */
.center-marker-icon,
.deal-marker-icon {
    display: flex;
    justify-content: center;
    align-items: center;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4));
}

.center-pin {
    font-size: 28px;
    cursor: grab;
    transform-origin: 50% 100%;
}

.center-pin.is-hinting {
    animation: pinPulse 2.2s ease-in-out infinite;
}

@keyframes pinPulse {
    0%, 100% { transform: scale(1) translateY(0); }
    50% { transform: scale(1.12) translateY(-2px); }
}

.deal-pin {
    border-radius: 50%;
    color: white;
    font-weight: 700;
    text-align: center;
    border: 2px solid white;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
}

/* Leaflet popup theming */
.dark-tooltip {
    background: var(--surface-strong);
    border: 1px solid var(--hairline);
    color: var(--text);
    border-radius: var(--r-sm);
    padding: 4px 8px;
    font-weight: 600;
}

.dark-popup .leaflet-popup-content-wrapper {
    background: var(--surface-strong);
    color: var(--text);
    border-radius: var(--r-md);
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--hairline);
}

.dark-popup .leaflet-popup-tip { background: var(--surface-strong); }

/* MapLibre GL popup chrome (Path B, ?map=gl) — GL wraps content in
   .maplibregl-popup-content (white by default) with a separate per-anchor tip;
   mirror the Leaflet .dark-popup styling above so GL popups aren't white. */
.dark-popup .maplibregl-popup-content {
    background: var(--surface-strong);
    color: var(--text);
    border-radius: var(--r-md);
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--hairline);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}
.dark-popup .maplibregl-popup-anchor-top .maplibregl-popup-tip,
.dark-popup .maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
.dark-popup .maplibregl-popup-anchor-top-right .maplibregl-popup-tip { border-bottom-color: var(--surface-strong); }
.dark-popup .maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.dark-popup .maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
.dark-popup .maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip { border-top-color: var(--surface-strong); }
.dark-popup .maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--surface-strong); }
.dark-popup .maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--surface-strong); }
.dark-popup .maplibregl-popup-close-button {
    color: var(--text-dim, #9aa);
    font-size: 20px;
    line-height: 1;
    padding: 2px 8px;
}
.dark-popup .maplibregl-popup-close-button:hover { background: transparent; color: var(--text); }

/* When the popup hosts a Top-Picks card (GL map): the same card, just constrained
   to the popup width (it reflows — not transform-scaled). Fill the bubble, scroll
   if tall, and strip the list's section spacing so the single row sits flush. */
/* Unlike the in-page card list, the popup's scroll must NOT chain at its
   extents — chaining here leaks into the map and pans/zooms it, which reads as a
   broken scroll. `contain` keeps the gesture inside the popup. */
.dark-popup--toppick .maplibregl-popup-content {
    max-height: 75vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}
.dark-popup--toppick .top-summary__list { margin: 0; padding: 0; list-style: none; }
.dark-popup--toppick .top-summary__row { margin: 0; }
.dark-popup--toppick .maplibregl-popup-close-button { z-index: 2; }

/* In full-screen the map already fills the viewport, so the expand/collapse
   toggle is redundant — hide it (the full-screen exit button stays). */
.map-section.is-fullscreen #mapToggleBtn { display: none; }

.popup-content { min-width: 240px; }

.popup-header {
    background: rgba(0, 0, 0, 0.2);
    padding: var(--s-2) var(--s-3);
    border-bottom: 1px solid var(--hairline);
}

.popup-name {
    font-weight: 700;
    font-size: 1rem;
    margin-bottom: 2px;
}

.popup-info {
    font-size: 0.75rem;
    color: var(--text-muted);
}

.popup-deals-list {
    padding: var(--s-2) var(--s-3);
    max-height: 200px;
    overflow-y: auto;
}

.popup-deals-section-label {
    padding: var(--s-2) var(--s-3) 0;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.popup-deals-list--hidden { opacity: 0.85; }

.popup-deal {
    padding: var(--s-2) 0;
    border-bottom: 1px solid var(--hairline);
}

.popup-deal:last-child { border-bottom: none; }

.popup-deal-title {
    font-weight: 600;
    font-size: 0.875rem;
    margin-bottom: 2px;
}

.popup-deal-meta {
    font-size: 0.75rem;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s-2);
}

/* Long time ranges (e.g. "everyday 3-5pm & 7-9pm") must wrap inside the popup
   instead of being locked to one line by the card pill's nowrap/no-shrink. */
.popup-deal-meta .deal-day {
    white-space: normal;
    flex-shrink: 1;
}

.popup-deal-price {
    color: var(--price);
    font-weight: 700;
}

.popup-website-btn {
    display: block;
    text-align: center;
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    text-decoration: none;
    padding: var(--s-2);
    font-weight: 600;
    font-size: 0.85rem;
    transition: background 0.15s;
}

.popup-website-btn:hover { background: var(--accent); }

/* Mini popup — shown on first marker tap; tap to expand to full popup. */
.mini-popup .leaflet-popup-content {
    margin: 0;
    min-width: 180px;
}

.mini-popup-trigger {
    padding: var(--s-2) var(--s-3);
    color: var(--text);
    cursor: pointer;
}
.mini-popup-trigger:hover,
.mini-popup-trigger:focus-visible {
    background: rgba(255, 255, 255, 0.05);
    outline: none;
}
.mini-popup-name {
    font-weight: 700;
    font-size: 0.95rem;
    margin-bottom: 2px;
    display: flex;
    align-items: center;
    gap: var(--s-2);
}
.mini-popup-meta {
    font-size: 0.75rem;
    color: var(--text-muted);
}
.mini-popup-sponsor { flex-shrink: 0; }

/* ============================================================
   Controls
   ============================================================ */
.controls {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
    margin-top: var(--s-3);
    margin-bottom: var(--s-5);
}

.search-bar {
    display: flex;
    align-items: center;
    gap: var(--s-3);
}

.search-bar input {
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--s-3) var(--s-4);
    border-radius: var(--r-pill);
    border: 1px solid var(--hairline);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    font-size: 1rem;
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.search-bar input::placeholder { color: var(--text-muted); }

.search-bar input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
}

.refine-toggle {
    position: relative;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: var(--r-pill);
    border: 1px solid var(--hairline);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.refine-toggle:hover { border-color: var(--accent); }

.refine-toggle[aria-expanded="true"] {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.refine-toggle__icon {
    display: block;
    width: 20px;
    height: 20px;
}

.refine-toggle__count {
    position: absolute;
    top: -4px;
    right: -4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4em;
    height: 1.4em;
    padding: 0 0.4em;
    border-radius: 999px;
    background: var(--accent);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
    box-shadow: 0 0 0 2px var(--bg, #000);
}

.refine-toggle[aria-expanded="true"] .refine-toggle__count {
    background: #fff;
    color: var(--accent);
}

.refine-panel {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
    margin-top: var(--s-4);
    padding: var(--s-4);
    border-radius: var(--r-lg);
    border: 1px solid var(--hairline);
    background: rgba(255, 255, 255, 0.03);
}

.refine-panel[hidden] { display: none; }

.filters {
    display: flex;
    gap: var(--s-3);
    flex-wrap: wrap;
    align-items: center;
}

.filters--top { margin-bottom: var(--s-5); }

.filters select {
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
    border: 1px solid transparent;
    cursor: pointer;
    font-size: 0.875rem;
    outline: none;
    transition: background 0.15s, border-color 0.15s;
}

.filters select:hover { background: rgba(255, 255, 255, 0.12); }
.filters select:focus { border-color: var(--accent); }
.filters select option { background: var(--bg); }

/* Chip-style segmented filter (used by day-of-week). Single-select pill
   group with one active state — fits alongside the <select> pills. */
.chip-filter {
    display: inline-flex;
    align-items: stretch;
    gap: 2px;
    padding: 3px;
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid transparent;
    transition: background 0.15s, border-color 0.15s;
    flex-wrap: wrap;
}

.chip-filter:hover { background: rgba(255, 255, 255, 0.12); }
.chip-filter:focus-within { border-color: var(--accent); }

.chip-filter-btn {
    border: none;
    background: transparent;
    color: var(--text-muted);
    padding: 4px 10px;
    border-radius: var(--r-pill);
    cursor: pointer;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1;
    outline: none;
    transition: background 0.15s, color 0.15s;
}

.chip-filter-btn:hover { color: var(--text); }
.chip-filter-btn:focus-visible { box-shadow: 0 0 0 2px var(--accent); }

.chip-filter-btn.is-active {
    background: var(--accent);
    color: white;
}

/* Sort-order icons each get their own circular .map-btn-style chip
   (dark slate, hairline, backdrop-blur) so the sort control reads as a
   row of map-tier chrome buttons rather than a unified pill. Scoped to
   #sortFilter so .chip-filter.day-forecast and other reuses keep their
   own look. */
#sortFilter.chip-filter {
    background: transparent;
    border: none;
    padding: 0;
    gap: var(--s-2);
    transition: none;
}

#sortFilter.chip-filter:hover { background: transparent; }
#sortFilter.chip-filter:focus-within { border-color: transparent; }

#sortFilter .chip-filter-btn {
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: rgba(15, 23, 42, 0.85);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid var(--hairline);
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

#sortFilter .chip-filter-btn:focus-visible { box-shadow: 0 0 0 2px var(--accent); }

#sortFilter .chip-filter-btn__icon {
    display: block;
}

#sortFilter .chip-filter-btn:hover:not(.is-active) {
    background: rgba(15, 23, 42, 0.92);
    color: var(--text);
}

#sortFilter .chip-filter-btn.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: white;
}

/* ------------------------------------------------------------------ */
/* Day-of-week "forecast" row.
   .day-forecast overrides the .chip-filter pill chrome so each child
   button can render as a vertically-stacked card (label / icons / price)
   rather than a flat chip. We keep .chip-filter-btn on each card so
   wireMultiChipFilter() in app.js stays unmodified; the visuals come
   from .day-card. Card content is filled by renderDayForecast(). */
/* Stacked: a compact "Any day" chip on top, the scrolling day strip below.
   Block container so the strip behaves like the original single-element
   scroller (a block child whose width = container, content overflowing into
   an internal scroll). */
.chip-filter.day-forecast {
    display: block;
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 4px 2px 6px;
    /* #dayFilter is a flex item of .filters--top (display:flex). Pin min-width:0
       so it's constrained to the container and the strip scrolls internally
       instead of blowing the page width out. */
    min-width: 0;
}
.chip-filter.day-forecast:hover,
.chip-filter.day-forecast:focus-within { background: transparent; border-color: transparent; }

/* Horizontal scroller holding the 7 day cards (the "Any day" chip sits above
   it). renderDayForecast() sets `order` on each card and this is their flex
   container, so today-first ordering still works. */
.day-forecast-row {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: var(--s-3);
    /* overflow-x:auto forces overflow-y to clip, so without vertical padding
       the cards' top/bottom outline + the active-state ring/lift get shaved
       at the scroller edges. A few px of inset gives them room. */
    padding: 4px 1px;
}
.day-forecast-row::-webkit-scrollbar { display: none; }

/* Full-width "Any day" button on its own line above the strip — spans the
   container (phone width on mobile; the day-row width on desktop). The
   clear/default state (no day filter). data-value="all" routes through
   wireMultiChipFilter's clear path; the days.sync() in filters.js marks it
   active when no day is selected. */
.day-any {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    margin-bottom: var(--s-2);
    padding: 7px 12px;
    border-radius: 10px;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.06);
    color: var(--text-muted);
}
.day-any:hover { color: var(--text); }
.chip-filter-btn.day-any.is-active {
    background: linear-gradient(180deg, rgba(251, 191, 36, 0.22) 0%, rgba(251, 191, 36, 0.08) 100%);
    border-color: var(--accent);
    color: var(--text);
}

/* Weather-forecast card. Vertical stack: day label → big hero glyph →
   price (the "temperature") → muted "N deals" sub-stat (the "low").
   renderDayForecast() sets `style.order` on each card so the row reads
   today → +6 days, like a weather app's daily strip. */
.day-card {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    min-width: 74px;
    padding: 10px 8px;
    border-radius: 14px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.09) 0%, rgba(255, 255, 255, 0.03) 100%);
    border: 1px solid rgba(255, 255, 255, 0.06);
    color: var(--text-muted);
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.12s, box-shadow 0.15s;
}

.day-card:hover {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0.06) 100%);
    color: var(--text);
}

/* Active card: lift + accent ring. The base .chip-filter-btn.is-active
   rule also fires (kept so wireMultiChipFilter still toggles it), so
   we override its solid accent fill with the lifted-card treatment. */
.chip-filter-btn.day-card.is-active {
    background: linear-gradient(180deg, rgba(251, 191, 36, 0.22) 0%, rgba(251, 191, 36, 0.08) 100%);
    border-color: var(--accent);
    color: var(--text);
    transform: translateY(-1px);
    box-shadow: 0 6px 14px -8px rgba(251, 191, 36, 0.6);
}

/* Today is marked by its accent-colored label only (see below) — NOT a ring.
   An amber inset ring read as a selection outline (same hue as the active
   state), so it looked like "today" stayed selected after picking "Any day". */

/* Empty state: no deals matched current filters on this day. */
.day-card.is-empty { opacity: 0.55; }
.day-card.is-empty .day-card-icons { opacity: 0.5; }

.day-card-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--text-muted);
}
/* Today: accent label as a non-outline "you are here" cue. Active wins (listed
   after) so a selected today card gets the normal --text label, not accent. */
.day-card.is-today .day-card-label { color: var(--accent); }
.day-card.is-active .day-card-label { color: var(--text); }

/* "Game day": the event-triggered sports card (no fixed weekday). A persistent
   cool tint + the longer two-word label set it apart from the amber weekday
   strip; keep the label on one line since "Game day" is wider than "Mon". */
.day-card.day-gameday {
    background: linear-gradient(180deg, rgba(56, 189, 248, 0.12) 0%, rgba(56, 189, 248, 0.04) 100%);
    border-color: rgba(56, 189, 248, 0.28);
}
.day-card.day-gameday:hover {
    background: linear-gradient(180deg, rgba(56, 189, 248, 0.2) 0%, rgba(56, 189, 248, 0.08) 100%);
}
.day-card.day-gameday.is-active {
    background: linear-gradient(180deg, rgba(56, 189, 248, 0.28) 0%, rgba(56, 189, 248, 0.1) 100%);
    border-color: rgb(56, 189, 248);
    box-shadow: 0 6px 14px -8px rgba(56, 189, 248, 0.6);
}
.day-gameday .day-card-label {
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* Hero glyph — the "weather icon" for the day. One big SVG, centered.
   renderDayForecast() injects exactly one <svg> here (the #1 deal). */
.day-card-icons {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 34px;
    margin: 2px 0;
}
.day-card-icons .deal-emoji { width: 30px; height: 30px; }

/* Price + support glyphs are hidden on the simplified day strip — the cards
   are a glanceable "what's good that day" label-over-icon row; actual prices
   live in the list/map below. renderDayForecast() still populates these nodes
   (logic untouched); they just don't render. Restore by removing `display:none`. */
.day-card-price {
    display: none;
    font-size: 1.2rem;
    font-weight: 800;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    min-height: 1em;
    /* Long price_text values (e.g. "1/2 off appetizers") truncate so
       the card width stays stable — full text lives in the list view. */
    max-width: 8ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.day-card.is-empty .day-card-price { color: var(--text-muted); }
.day-card.is-active .day-card-price { color: #fbbf24; }

/* Supporting glyphs — the "night" sub-icons under the headline price.
   Smaller and dimmer than the hero so the eye still lands on the hero first. */
.day-card-support {
    display: none;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 22px;
    opacity: 0.85;
}
.day-card-support .deal-emoji { width: 20px; height: 20px; }
.day-card.is-active .day-card-support { opacity: 1; }
.day-card.is-empty .day-card-support { opacity: 0.5; }

/* Holiday pip from .chip-filter-btn.has-holiday::after — re-anchor to
   the card's top-right corner so it doesn't collide with the label. */
.day-card.has-holiday::after { top: 4px; right: 5px; }
/* ------------------------------------------------------------------ */

/* Slider-style filter (used by rating + price).
   Layout: [muted prefix]  [slider]  [live value, prominent, right of thumb] */
.slider-filter {
    display: inline-flex;
    align-items: center;
    gap: var(--s-3);
    padding: 4px 16px;
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, 0.08);
    border: none;
    min-width: 240px;
    transition: background 0.15s;
}

.slider-filter:hover { background: rgba(255, 255, 255, 0.12); }

.slider-filter-prefix {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    white-space: nowrap;
    flex: 0 0 auto;
}

.slider-filter-input {
    flex: 1;
    min-width: 80px;
    accent-color: var(--accent);
    cursor: pointer;
    margin: 0;
}

.slider-filter-value {
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--accent-hover);
    white-space: nowrap;
    text-align: right;
    flex: 0 0 auto;
    min-width: 70px;
    font-variant-numeric: tabular-nums;
}

.tags-group {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    flex-wrap: wrap;
}

.tags-group + .tags-group { margin-top: var(--s-2); }

.tags-group-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    flex: 0 0 auto;
    min-width: 44px;
}

.tags {
    display: flex;
    gap: var(--s-2);
    flex-wrap: wrap;
}

.tag-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
    background: transparent;
    border: 1px solid var(--hairline-strong);
    color: var(--text-muted);
    padding: 5px 12px 5px 9px;
    border-radius: var(--r-pill);
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
    font-weight: 600;
    font-size: 0.82rem;
    line-height: 1;
    height: 30px;
}

.tag-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    line-height: 1;
    width: 18px;
    flex-shrink: 0;
}

.tag-flag {
    width: 20px;
    height: 15px;
    border-radius: 2px;
    object-fit: cover;
    flex-shrink: 0;
    display: block;
}

.tag-btn:hover {
    color: var(--text);
    border-color: var(--text-muted);
}

.tag-btn.is-active {
    background: var(--accent);
    color: white;
    border-color: var(--accent);
}

/* ============================================================
   What-tag scroller: 3 stacked rows (Food / Cuisine / Occasion) that
   share one horizontal scrollbar. As the user swipes, all three rows
   shift in lockstep — the inner content sizes to the widest row, and
   the outer container clips and scrolls. Row labels are sticky-left so
   they stay visible while chips slide under them.
   ============================================================ */
/* The outer scroller is now just a vertical stack of rows — each row owns
   its own horizontal scrollbar so users can swipe through one section
   without affecting the others. */
.what-scroller {
    margin-top: var(--s-2);
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.what-row {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    padding: 3px 0;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: var(--hairline-strong) transparent;
    position: relative;
}

/* Right-edge "more →" affordance: only shown on rows whose chip content
   actually overflows the visible width (set by JS in renderWhatTags).
   Fades the rightmost chips out so the user sees content trailing off.
   A static fade applied to every row was misleading — it just looked
   like wasted padding on rows that already fit. */
.what-row.is-overflow {
    mask-image: linear-gradient(to right, black 0, black calc(100% - 40px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 40px), transparent 100%);
}

.what-row::-webkit-scrollbar { height: 3px; }
.what-row::-webkit-scrollbar-track { background: transparent; }
.what-row::-webkit-scrollbar-thumb { background: var(--hairline-strong); border-radius: 2px; }

/* Fixed label column width so chips start at the same X across all rows. */
.what-row .tags-group-label {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--bg);
    min-width: 80px;
    padding-right: var(--s-2);
    flex-shrink: 0;
}

.what-row .tags {
    display: flex;
    gap: var(--s-2);
    flex-wrap: nowrap;
}

/* Cuisine has ~20 chips — lay them out in a 3-row sub-grid inside its own
   horizontal scroller so the cuisine row stays compact vertically while
   still letting the user swipe through all of them. */
.what-row .tags[data-what-group="cuisine"] {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(3, auto);
    grid-auto-columns: max-content;
    column-gap: var(--s-2);
    row-gap: 4px;
}

/* On mobile, label-on-top so chips get the full row width. Each row still
   owns its own scroll; the sticky-left label keeps the section name
   anchored as the chips slide under it. */
@media (max-width: 640px) {
    .what-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 4px;
    }
    .what-row .tags-group-label {
        min-width: 0;
        padding-right: 0;
    }
}

/* ============================================================
   Deal cards
   ============================================================ */
#dealsContainer {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--s-4);
    align-items: start;
}

/* "Show more" button at the bottom of the deal list. Secondary visual weight
   on purpose — it's a navigation aid, not a primary CTA. Spans all grid
   columns so it sits as a single full-width footer under the cards. */
.list-load-more {
    grid-column: 1 / -1;
    width: 100%;
    min-height: 44px;
    padding: 12px 22px;
    background: transparent;
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-md);
    color: var(--text-muted);
    font-weight: 600;
    font-size: 0.9rem;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.list-load-more:hover {
    color: var(--text);
    border-color: rgba(255, 255, 255, 0.3);
}
.list-load-more:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Visually-hidden utility for aria-live announcers and other SR-only text. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Cards stagger in during the radar sweep when the map first reveals. */
@keyframes radarPop {
    from { opacity: 0; transform: translateY(10px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0)    scale(1);    }
}

.deal-card.radar-reveal {
    opacity: 0;
    animation: radarPop 0.3s ease-out forwards;
    animation-delay: calc(var(--reveal-i, 0) * 90ms);
}

@media (prefers-reduced-motion: reduce) {
    .deal-card.radar-reveal { animation: none; opacity: 1; }
}

.deal-card {
    background: var(--surface);
    border-radius: var(--r-lg);
    border: 1px solid var(--hairline);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.deal-img {
    width: 100%;
    height: 160px;
    object-fit: cover;
    border-bottom: 1px solid var(--hairline);
}

.deal-content {
    padding: var(--s-3);
    display: flex;
    flex-direction: column;
    flex-grow: 1;
}

.deal-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: var(--s-3);
}

.restaurant-name {
    font-size: 1rem;
    color: var(--text);
    font-weight: 700;
    letter-spacing: -0.01em;
}

.restaurant-name small {
    color: var(--text-muted);
    font-weight: 400;
}

/* Address line under the restaurant name. Links to Google Maps directions.
   Muted so it reads as secondary chrome, not as the headline. */
.restaurant-address {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    margin-top: 2px;
    font-size: 0.8rem;
    line-height: 1.3;
    color: var(--text-muted);
    text-decoration: none;
}

.restaurant-address:hover,
.restaurant-address:focus-visible {
    color: var(--text);
    text-decoration: underline;
}

/* Gap 3 (docs/pipeline_ops_plan.md §3): data-confidence dot in the card
   chrome. Subtle by design — 8 px disk that sits beside the restaurant
   name without competing for attention with the deal title or rating. The
   hover/focus tooltip is the load-bearing affordance, not the color.
   See web/app.js renderConfidenceDot() for the state -> class mapping. */
.mr-dot {
    /* Rendered as <button> so taps work cross-platform. Reset button chrome. */
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    vertical-align: middle;
    margin-left: 6px;
    padding: 0;
    border: 0;
    cursor: pointer;
    /* Expand the tap target invisibly so mobile taps don't miss the 8 px
       visible dot. The visual stays 8 px; the hit area is ~24 px square. */
    position: relative;
    outline-offset: 2px;
}
.mr-dot::before {
    content: "";
    position: absolute;
    inset: -8px;
}
.mr-dot:focus-visible {
    outline: 2px solid var(--accent-hover);
}
/* Two-state palette (the PWA shows users only `verified` vs `detected`):
   - detected     muted grey; the default state for any listing MealRadar
                  detected without owner confirmation. Tells the user
                  to double-check via the restaurant's link.
   - verified     gold for owner-submitted/paid listings; "no need to
                  double-check" trust signal.
   Legacy class names (.mr-dot-fresh/aging/older) are kept as aliases
   that render the same grey — in case cached HTML still carries them.
   Backend still computes 4-state granularity for ranking / admin /
   future re-expansion. */
.mr-dot-detected,
.mr-dot-fresh,
.mr-dot-aging,
.mr-dot-older    { background: rgba(148, 163, 184, 0.6); }  /* text-muted at 60% */
.mr-dot-verified { background: #d4a017; }

/* Confidence-legend popover — shown on tap of any .mr-dot. Position matches
   the existing .toast (centered, bottom-anchored); slightly larger so the
   four-row legend fits. Dismissed via the Got-it button, outside tap, or
   Escape. See web/app.js showConfidenceLegend() + attachConfidenceLegendHandler(). */
.mr-legend-popup {
    position: fixed;
    left: 50%;
    bottom: 24px;
    transform: translate(-50%, 12px);
    z-index: 1250;
    width: min(320px, calc(100vw - 32px));
    background: rgba(15, 23, 42, 0.96);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: var(--text);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-md);
    padding: 14px 16px 12px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
}
.mr-legend-popup.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
    pointer-events: auto;
}
.mr-legend-popup-title {
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: 8px;
    opacity: 0.85;
}
.mr-legend-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 0;
    font-size: 0.85rem;
    opacity: 0.65;
}
.mr-legend-row.is-current {
    opacity: 1;
    font-weight: 500;
}
.mr-legend-row .mr-dot {
    margin-left: 0;
    cursor: default;
    flex-shrink: 0;
}
.mr-legend-row .mr-dot::before {
    /* No oversized hitbox inside the legend itself. */
    content: none;
}
.mr-legend-text {
    line-height: 1.35;
}
.mr-legend-close {
    margin-top: 10px;
    width: 100%;
    padding: 8px 12px;
    background: transparent;
    color: var(--text);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-sm);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s ease;
}
.mr-legend-close:hover,
.mr-legend-close:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}

.deal-rating {
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-top: 2px;
}

.deal-item { margin-bottom: var(--s-3); }
.deal-item:last-child { margin-bottom: 0; }

.deal-item-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--s-2);
    margin-bottom: 4px;
}

.deal-divider {
    border: 0;
    height: 1px;
    background: var(--hairline);
    margin: var(--s-3) 0;
}

.deals-list { margin-bottom: var(--s-3); }

.show-all-deals-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    padding: 6px 8px;
    margin: 0 0 var(--s-3);
    background: transparent;
    border: 0;
    border-top: 1px dashed var(--hairline);
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition: color 0.15s;
}
.show-all-deals-btn:hover,
.show-all-deals-btn:focus-visible {
    color: var(--accent-hover);
    outline: none;
}
.show-all-deals-btn::after {
    content: '▾';
    font-size: 0.7em;
    transition: transform 0.2s ease;
}
.show-all-deals-btn[aria-expanded="true"]::after { transform: rotate(180deg); }

.deals-list--hidden { opacity: 0.65; }
.deals-list--hidden .deal-title { font-weight: 500; }

.deal-day {
    background: rgba(255, 255, 255, 0.08);
    padding: 2px 8px;
    border-radius: var(--r-sm);
    font-size: 0.7rem;
    font-weight: 600;
    white-space: nowrap;
    color: var(--text-muted);
    flex-shrink: 0;
}

.deal-title {
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: 2px;
    line-height: 1.35;
}

.deal-link,
.popup-deal-link {
    color: var(--text);
    text-decoration: none;
    transition: color 0.15s;
}

.deal-link::after,
.popup-deal-link::after {
    content: "↗";
    display: inline-block;
    margin-left: 5px;
    font-size: 1.1em;
    font-weight: 700;
    color: var(--accent-hover);
    vertical-align: baseline;
    transition: transform 0.15s ease;
}

.deal-link:hover,
.deal-link:active,
.popup-deal-link:hover,
.popup-deal-link:active {
    color: var(--accent-hover);
}

.deal-link:hover::after,
.popup-deal-link:hover::after {
    transform: translate(1px, -1px);
}

.deal-price {
    font-size: 0.95rem;
    color: var(--price);
    font-weight: 700;
    margin-bottom: var(--s-2);
}

.deal-keywords {
    margin-top: auto;
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    margin-bottom: var(--s-3);
}

.keyword {
    font-size: 0.7rem;
    background: rgba(0, 0, 0, 0.25);
    padding: 2px 6px;
    border-radius: var(--r-sm);
    color: var(--text-muted);
}

.visit-btn {
    text-align: center;
    display: block;
    width: 100%;
    padding: var(--s-2);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    text-decoration: none;
    border-radius: var(--r-sm);
    font-weight: 600;
    font-size: 0.875rem;
    transition: background 0.15s;
}

.visit-btn:hover { background: var(--accent); }

.call-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-left: var(--s-2);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    text-decoration: none;
    border-radius: var(--r-sm);
    font-size: 0.95rem;
    line-height: 1;
    transition: background 0.15s;
}

.call-btn:hover,
.call-btn:active { background: var(--accent); }

.price-badge {
    margin-left: 6px;
    color: var(--text);
    font-weight: 600;
}

.open-now-badge {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.open-now-badge--open {
    background: rgba(34, 197, 94, 0.18);
    color: #4ade80;
}

.open-now-badge--closed {
    background: rgba(248, 113, 113, 0.18);
    color: #f87171;
}

.atmosphere-pills {
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    margin: var(--s-1) 0 var(--s-2) 0;
}

.atmo-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    background: rgba(255, 255, 255, 0.06);
    border-radius: 999px;
    font-size: 0.72rem;
    color: var(--text-muted);
}

/* Clear filters — sits in .search-bar next to .refine-toggle, mirrors its
   40×40 round chrome but tinted amber so it reads as a reset action rather
   than another filter control. countActiveFilters() drives visibility and
   the corner-badge count. */
.clear-filters-btn {
    position: relative;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: var(--r-pill);
    border: 1px solid rgba(251, 191, 36, 0.35);
    background: rgba(251, 191, 36, 0.08);
    color: #fbbf24;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.clear-filters-btn:hover {
    color: #fde68a;
    background: rgba(251, 191, 36, 0.16);
    border-color: rgba(251, 191, 36, 0.55);
}

/* Nothing to clear — stays in place but reads as unavailable. */
.clear-filters-btn.is-inactive {
    opacity: 0.4;
    cursor: default;
}
.clear-filters-btn.is-inactive:hover {
    color: #fbbf24;
    background: rgba(251, 191, 36, 0.08);
    border-color: rgba(251, 191, 36, 0.35);
}

.clear-filters-btn__icon {
    display: block;
    width: 20px;
    height: 20px;
}

.clear-filters-btn__count {
    position: absolute;
    top: -4px;
    right: -4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4em;
    height: 1.4em;
    padding: 0 0.4em;
    border-radius: 999px;
    background: #fbbf24;
    color: #1a1a1a;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
    box-shadow: 0 0 0 2px var(--bg, #000);
}

.clear-filters-btn__count:empty { display: none; }

/* ============================================================
   States
   ============================================================ */
.loader,
.no-results {
    grid-column: 1 / -1;
    text-align: center;
    padding: var(--s-6);
    color: var(--text-muted);
    font-size: 1rem;
}

.no-results h3 {
    color: var(--text);
    font-weight: 600;
    margin-bottom: var(--s-2);
    font-size: 1.2rem;
}

.no-results p {
    max-width: 480px;
    margin: 0 auto var(--s-4);
    line-height: 1.5;
}

.no-results__day-suggest {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-2);
    margin: 0 auto var(--s-4);
    max-width: 480px;
}

.no-results__day-suggest-label {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.no-results__day-suggest-chips {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--s-2);
}

.no-results__day-chip {
    display: inline-flex;
    align-items: baseline;
    gap: var(--s-2);
    background: var(--surface, #fff);
    color: var(--text);
    border: 1px solid var(--hairline);
    border-radius: var(--r-pill);
    padding: 6px 14px;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s, transform 0.1s;
}

.no-results__day-chip:hover { border-color: var(--accent); }
.no-results__day-chip:active { transform: scale(0.97); }
.no-results__day-chip-price { color: var(--price); font-weight: 700; }

.request-city-btn {
    background: var(--accent);
    color: white;
    border: none;
    padding: 10px 22px;
    border-radius: var(--r-pill);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
}

.request-city-btn:hover {
    background: var(--accent-hover);
}

.request-overlay .prefs-modal h3 {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--text-muted);
    margin-bottom: var(--s-2);
    font-weight: 700;
}

.request-status {
    margin-top: var(--s-3);
    color: var(--accent-hover);
    font-size: 0.9rem;
    font-weight: 600;
}

/* ============================================================
   Interstitials
   ============================================================ */
.interstitial {
    grid-column: 1 / -1;
    border-radius: var(--r-lg);
    padding: var(--s-3) var(--s-4);
    border: 1px solid var(--hairline-strong);
    background: linear-gradient(135deg, rgba(59, 130, 246, 0.12), rgba(59, 130, 246, 0.04));
    position: relative;
}

.interstitial-mark {
    color: var(--accent-hover);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 700;
    margin-bottom: var(--s-1);
}

.interstitial-text {
    font-size: 1.05rem;
    line-height: 1.45;
    color: var(--text);
    margin: 0;
}

.interstitial--in-feed .interstitial-text {
    font-size: 0.95rem;
    color: var(--text);
    padding-right: var(--s-4);
}

.interstitial-dismiss {
    position: absolute;
    top: var(--s-2);
    right: var(--s-2);
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.4rem;
    cursor: pointer;
    line-height: 1;
    padding: 4px 8px;
    border-radius: var(--r-sm);
    transition: background 0.15s, color 0.15s;
}

.interstitial-dismiss:hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
}

.interstitial-actions {
    display: flex;
    gap: var(--s-2);
    margin-top: var(--s-3);
}

.prefs-open-btn,
.prefs-skip-btn {
    border: 1px solid var(--hairline-strong);
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
    padding: 8px 14px;
    border-radius: var(--r-pill);
    cursor: pointer;
    font-weight: 600;
    font-size: 0.875rem;
    transition: background 0.15s, border-color 0.15s;
}

.prefs-open-btn {
    background: var(--accent);
    border-color: var(--accent);
}

.prefs-open-btn:hover { background: var(--accent-hover); }
.prefs-skip-btn:hover { background: rgba(255, 255, 255, 0.12); }

/* ============================================================
   Sponsored variant + Photo attribution + Favorites
   ============================================================ */
.deal-card--sponsored {
    border-color: rgba(245, 158, 11, 0.4);
    box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.2);
}

.sponsor-badge {
    display: inline-block;
    margin-left: var(--s-1);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #f59e0b;
    border: 1px solid rgba(245, 158, 11, 0.5);
    padding: 1px 6px;
    border-radius: var(--r-sm);
    font-weight: 700;
    vertical-align: middle;
}

/* Time-boxed "New" badge for a freshly-posted owner deal (portal new-deal
   priority). Green to distinguish from the amber Promoted/Sponsored badge. */
.new-badge {
    display: inline-block;
    margin-left: var(--s-1);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #22c55e;
    border: 1px solid rgba(34, 197, 94, 0.5);
    padding: 1px 6px;
    border-radius: var(--r-sm);
    font-weight: 700;
    vertical-align: middle;
}

.popup-sponsor-badge {
    background: rgba(245, 158, 11, 0.1);
}

.deal-photo {
    position: relative;
    margin: 0;
}

.deal-photo.no-photo { display: none; }

.photo-attribution {
    position: absolute;
    bottom: 4px;
    right: 6px;
    background: rgba(15, 23, 42, 0.78);
    padding: 2px 6px;
    border-radius: var(--r-sm);
    font-size: 0.65rem;
    color: var(--text-muted);
    line-height: 1.4;
}

.photo-attribution a {
    color: var(--text-muted);
    text-decoration: none;
}

.photo-attribution a:hover { color: var(--text); }

.deal-title-wrap {
    flex: 1;
    min-width: 0;
}

.fav-btn {
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.4rem;
    cursor: pointer;
    line-height: 1;
    padding: 2px 6px;
    border-radius: var(--r-sm);
    transition: color 0.15s, transform 0.15s, background 0.15s;
    flex-shrink: 0;
}

.fav-btn:hover { background: rgba(255, 255, 255, 0.08); color: var(--text); }
.fav-btn.is-fav { color: #ef4444; }
.fav-btn.is-fav:hover { color: #f87171; }

/* ============================================================
   Prefs modal
   ============================================================ */
.prefs-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.7);
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--s-3);
    overflow-y: auto;
}

.prefs-modal {
    background: var(--surface-strong);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-lg);
    width: 100%;
    max-width: 480px;
    padding: var(--s-5);
    position: relative;
    max-height: 92vh;
    overflow-y: auto;
}

.prefs-close {
    position: absolute;
    top: var(--s-2);
    right: var(--s-2);
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.6rem;
    cursor: pointer;
    padding: 4px 10px;
    border-radius: var(--r-sm);
    line-height: 1;
}

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

.prefs-progress {
    display: flex;
    gap: 6px;
    margin-bottom: var(--s-4);
}

.prefs-progress span {
    flex: 1;
    height: 3px;
    background: var(--hairline);
    border-radius: 2px;
}

.prefs-progress span.is-current { background: var(--accent); }

.prefs-eyebrow {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--text-muted);
    margin-bottom: var(--s-2);
    font-weight: 700;
}

.prefs-headline {
    font-size: 1.2rem;
    font-weight: 700;
    margin-bottom: var(--s-2);
    line-height: 1.3;
    color: var(--text);
}

.prefs-sub {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin-bottom: var(--s-3);
    line-height: 1.5;
}

/* Scope to .prefs-modal (not .prefs-step) — the city-request modal puts inputs
   and the country <select> directly inside .prefs-modal, so a .prefs-step
   selector silently skipped them and they rendered as unstyled white boxes. */
.prefs-modal label {
    display: block;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: var(--s-4);
}

.prefs-modal .optional {
    color: var(--text-muted);
    font-weight: 400;
}

.prefs-modal input[type="email"],
.prefs-modal input[type="text"],
.prefs-modal select {
    width: 100%;
    padding: 14px 16px;
    min-height: 48px;
    border-radius: var(--r-sm);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--hairline-strong);
    color: var(--text);
    font-size: 1rem;
    font-family: inherit;
    line-height: 1.3;
    margin-top: 6px;
    outline: none;
    appearance: none;
    -webkit-appearance: none;
}

.prefs-modal input::placeholder {
    color: var(--text-muted);
    opacity: 0.7;
}

/* Autofill override — see .da-input rule for details. */
.prefs-modal input:-webkit-autofill,
.prefs-modal input:-webkit-autofill:hover,
.prefs-modal input:-webkit-autofill:focus,
.prefs-modal input:-webkit-autofill:active {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px #1a2236 inset;
    caret-color: var(--text);
    transition: background-color 9999s ease-in-out 0s, color 9999s ease-in-out 0s;
}

/* Native dropdown chrome on dark inputs is jarring across browsers — paint our
   own caret so the select matches the inputs. */
.prefs-modal select {
    background-image:
        linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
        linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
    background-position:
        calc(100% - 20px) 22px,
        calc(100% - 15px) 22px;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    padding-right: 40px;
    cursor: pointer;
}

.prefs-modal select option {
    background: var(--surface-strong);
    color: var(--text);
}

.prefs-modal input:focus,
.prefs-modal select:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
}

.prefs-group-label {
    color: var(--text-muted);
    font-size: 0.8rem;
    font-weight: 600;
    margin-bottom: var(--s-2);
    margin-top: var(--s-3);
}

.chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: var(--s-3);
}

.chip {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--hairline-strong);
    color: var(--text);
    padding: 6px 12px;
    border-radius: var(--r-pill);
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 600;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}

.chip:hover { background: rgba(255, 255, 255, 0.10); border-color: var(--text-muted); }
.chip.is-selected {
    background: var(--accent);
    color: white;
    border-color: var(--accent);
}

/* "Other…" chip — visually marked as a free-text gateway so users don't
   confuse it with a normal preset. */
.chip--other {
    border-style: dashed;
}
.chip--other.is-selected {
    border-style: solid;
}

/* Free-text companion shown when an "Other…" chip is selected. */
.chip-other {
    margin-top: calc(-1 * var(--s-2));
    margin-bottom: var(--s-3);
    display: flex;
    align-items: center;
    gap: var(--s-2);
}

.chip-other input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 10px 12px;
    min-height: 40px;
    border-radius: var(--r-sm);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--hairline-strong);
    color: var(--text);
    font-size: 0.95rem;
    font-family: inherit;
    line-height: 1.3;
    outline: none;
    appearance: none;
    -webkit-appearance: none;
}

.chip-other input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
}

.chip-other input::placeholder {
    color: var(--text-muted);
    opacity: 0.7;
}

/* Notes textarea (step 3 free-form). */
.notes-wrap {
    position: relative;
    margin-bottom: var(--s-3);
}

.prefs-notes {
    width: 100%;
    padding: 12px 14px;
    border-radius: var(--r-sm);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--hairline-strong);
    color: var(--text);
    font-size: 0.95rem;
    font-family: inherit;
    line-height: 1.4;
    resize: vertical;
    min-height: 76px;
    outline: none;
}

.prefs-notes:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
}

.prefs-notes::placeholder {
    color: var(--text-muted);
    opacity: 0.7;
}

/* Shared character counter for Other inputs and notes. Right-aligned, muted
   until close to the cap. */
.char-count {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.notes-wrap .char-count {
    display: block;
    text-align: right;
    margin-top: 4px;
}

.char-count.is-near-limit { color: #fbbf24; }
.char-count.is-at-limit { color: #f87171; }

.consent-text {
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.5;
    margin-top: var(--s-3);
    margin-bottom: var(--s-2);
}

.prefs-status {
    margin-top: var(--s-2);
    margin-bottom: var(--s-1);
    color: #f87171;
    font-size: 0.85rem;
    font-weight: 500;
    line-height: 1.4;
}

.prefs-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--s-2);
    margin-top: var(--s-4);
}

.prefs-next,
.prefs-submit,
.request-overlay .prefs-submit,
.request-overlay .prefs-skip {
    padding: 10px 18px;
    border-radius: var(--r-pill);
    cursor: pointer;
    font-weight: 600;
    font-size: 0.9rem;
    border: 1px solid var(--hairline-strong);
}

.prefs-next,
.prefs-submit,
.request-overlay .prefs-submit {
    background: var(--accent);
    color: white;
    border-color: var(--accent);
}

.prefs-next:hover,
.prefs-submit:hover,
.request-overlay .prefs-submit:hover { background: var(--accent-hover); }

.prefs-next:disabled,
.prefs-submit:disabled,
.request-overlay .prefs-submit:disabled {
    opacity: 0.6;
    cursor: wait;
}

/* City-request modal keeps a secondary Cancel button alongside Send. */
.request-overlay .prefs-skip {
    background: transparent;
    color: var(--text-muted);
}
.request-overlay .prefs-skip:hover { background: rgba(255, 255, 255, 0.06); color: var(--text); }

.prefs-success {
    text-align: center;
    padding: var(--s-4) var(--s-2);
}

.prefs-success-mark {
    width: 56px;
    height: 56px;
    margin: 0 auto var(--s-3);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: rgba(16, 185, 129, 0.18);
    color: var(--price);
    font-size: 1.8rem;
    font-weight: 800;
}

.prefs-success-title {
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--text);
    margin-bottom: var(--s-2);
}

.prefs-success-text {
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.5;
}

/* Turnstile widget renders its own iframe; just give it breathing room. */
.cf-turnstile { margin-top: var(--s-2); margin-bottom: var(--s-2); }

/* ============================================================
   Footer
   ============================================================ */
.site-footer {
    position: relative;
    overflow: hidden;
    background: #080f1e;
    border-top: 1px solid rgba(59, 130, 246, 0.18);
    margin-top: var(--s-6);
}

/* Radar ring background decoration */
.footer-radar-bg {
    position: absolute;
    top: -100px;
    right: -140px;
    width: 520px;
    height: 520px;
    pointer-events: none;
    user-select: none;
}
.footer-radar-bg svg {
    width: 100%;
    height: 100%;
}

.footer-inner {
    position: relative;
    max-width: 1200px;
    margin: 0 auto;
    padding: 60px 24px 40px;
}

/* CTA section */
.footer-cta-section {
    max-width: 560px;
    margin-bottom: var(--s-6);
}

.footer-brand-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: var(--s-4);
}

/* Pulsing radar blip */
.footer-ping {
    position: relative;
    display: inline-block;
    width: 10px;
    height: 10px;
    flex: 0 0 10px;
}
.footer-ping::before,
.footer-ping::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
}
.footer-ping::before {
    background: var(--accent);
}
.footer-ping::after {
    background: transparent;
    border: 1.5px solid var(--accent);
    animation: footer-ping 2s ease-out infinite;
}
.footer-ping--sm {
    width: 7px;
    height: 7px;
    flex: 0 0 7px;
}

@keyframes footer-ping {
    0%   { transform: scale(1);   opacity: 0.7; }
    100% { transform: scale(2.6); opacity: 0;   }
}

@media (prefers-reduced-motion: reduce) {
    .footer-ping::after { animation: none; }
}

.footer-brand-name {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.01em;
}

.footer-cta-eyebrow {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--accent);
    margin-bottom: var(--s-3);
}

.footer-headline {
    font-size: clamp(1.8rem, 4vw, 2.6rem);
    font-weight: 700;
    letter-spacing: -0.03em;
    color: var(--text);
    line-height: 1.15;
    margin-bottom: var(--s-3);
}

.footer-headline-alt {
    display: block;
    color: var(--text-muted);
    font-weight: 600;
    font-style: italic;
    opacity: 0.85;
}

.footer-sub {
    color: var(--text-muted);
    font-size: 0.975rem;
    line-height: 1.65;
    margin-bottom: var(--s-5);
    max-width: 460px;
}

.footer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
}

.footer-btn-primary {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 13px 24px;
    background: var(--accent);
    color: #fff;
    font-weight: 700;
    font-size: 0.95rem;
    font-family: inherit;
    letter-spacing: 0.01em;
    border: none;
    border-radius: var(--r-pill);
    text-decoration: none;
    cursor: pointer;
    box-shadow: 0 6px 22px rgba(59, 130, 246, 0.3);
    transition: background 0.15s, transform 0.15s, box-shadow 0.15s;
}
.footer-btn-primary:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
    box-shadow: 0 10px 28px rgba(59, 130, 246, 0.45);
}
.footer-btn-primary:hover .footer-btn-arrow {
    transform: translateX(3px);
}

.footer-btn-arrow {
    display: inline-block;
    transition: transform 0.18s ease;
    font-weight: 600;
}

.footer-btn-secondary {
    display: inline-flex;
    align-items: center;
    padding: 11px 22px;
    border: 1px solid var(--hairline-strong);
    color: var(--text-muted);
    font-weight: 600;
    font-size: 0.9rem;
    border-radius: var(--r-pill);
    text-decoration: none;
    transition: border-color 0.15s, color 0.15s;
}
.footer-btn-secondary:hover {
    color: var(--text);
    border-color: rgba(255, 255, 255, 0.3);
}

.footer-cta-aside {
    margin: var(--s-3) 0 0;
    font-size: 0.85rem;
    color: var(--text-muted);
}
.footer-cta-aside a {
    color: var(--text-muted);
    text-decoration: none;
    border-bottom: 1px solid var(--hairline-strong);
    transition: color 0.15s, border-color 0.15s;
}
.footer-cta-aside a:hover {
    color: var(--text);
    border-bottom-color: var(--accent);
}

.footer-rule {
    height: 1px;
    background: var(--hairline);
    margin: var(--s-5) 0;
}

/* Navigation columns */
.footer-nav {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--s-5) var(--s-4);
}

.footer-col-head {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-muted);
    margin-bottom: var(--s-3);
}

.footer-col ul {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.footer-col a {
    color: #7f92aa;
    text-decoration: none;
    font-size: 0.9rem;
    transition: color 0.12s;
}
.footer-col a:hover {
    color: var(--text);
}

/* "Send feedback" sits in the footer nav list but opens a modal, so it's a
   <button>. Reset the native chrome so it reads as a footer link. */
.footer-link-btn {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: #7f92aa;
    font-size: 0.9rem;
    cursor: pointer;
    text-align: left;
    transition: color 0.12s;
}
.footer-link-btn:hover {
    color: var(--text);
}

.footer-portal {
    color: var(--accent) !important;
    font-weight: 600;
}

/* Bottom legal bar */
.footer-bottom {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--s-3) var(--s-5);
}

.footer-ai-tag {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--accent);
    letter-spacing: 0.02em;
}

.footer-legal {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-4);
    font-size: 0.78rem;
    color: #5c7189;
}

.footer-legal a {
    color: #5c7189;
    text-decoration: none;
    transition: color 0.12s;
}
.footer-legal a:hover {
    color: var(--text-muted);
}

@media (max-width: 720px) {
    .footer-nav {
        grid-template-columns: repeat(2, 1fr);
    }
    .footer-radar-bg {
        opacity: 0.5;
        right: -180px;
        top: -60px;
    }
}

@media (max-width: 480px) {
    .footer-inner {
        padding: 48px 20px 32px;
    }
    .footer-nav {
        grid-template-columns: 1fr;
    }
    .footer-bottom {
        flex-direction: column;
        align-items: flex-start;
    }
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 768px) {
    .app-container { padding: var(--s-3); }

    header h1 { font-size: 1.75rem; }

    .map-section { height: 180px; }
    .map-section.is-expanded { height: 55vh; min-height: 320px; max-height: 600px; }

    .map-overlay-controls {
        bottom: var(--s-2);
        left: var(--s-2);
        right: var(--s-2);
        gap: var(--s-2);
    }

    .map-btn {
        padding: 8px 12px;
        font-size: 0.8rem;
    }

    .map-stats {
        font-size: 0.875rem;
        margin-left: var(--s-2);
    }

    #dealsContainer { gap: var(--s-3); }
}

/* ============================================================
   Share button (next to favorite, inside deal-item)
   ============================================================ */
.share-btn {
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    line-height: 1;
    padding: 4px 6px;
    border-radius: var(--r-sm);
    transition: color 0.15s, background 0.15s;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.share-btn svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    display: block;
}
.share-btn:hover,
.share-btn:focus-visible { background: rgba(255, 255, 255, 0.08); color: var(--text); outline: none; }

/* ============================================================
   Deal detail view (deep-link / share landing)
   ============================================================ */
.deal-detail-view { padding: 0 var(--s-3) var(--s-4); }

.deal-detail {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--r-lg);
    overflow: hidden;
    max-width: 720px;
    margin: var(--s-4) auto 0;
    padding: 0 0 var(--s-4);
}

.deal-detail-photo {
    position: relative;
    margin: 0;
    border-bottom: 1px solid var(--hairline);
}
.deal-detail-photo.no-photo { display: none; }
.deal-detail-img {
    width: 100%;
    height: 200px;
    object-fit: cover;
    display: block;
}
@media (min-width: 720px) {
    .deal-detail-img { height: 280px; }
}

.deal-detail-restaurant {
    margin: var(--s-3) var(--s-3) 0;
    font-size: 0.95rem;
    color: var(--text-muted);
    font-weight: 600;
    letter-spacing: 0.01em;
}

.deal-detail-title {
    margin: 4px var(--s-3) var(--s-2);
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--text);
    line-height: 1.25;
}

.deal-detail-meta {
    margin: 0 var(--s-3);
    color: var(--text-muted);
    font-size: 0.9rem;
}

.deal-detail-price {
    margin: 6px var(--s-3) 0;
    color: var(--text);
    font-weight: 600;
}

.deal-detail-more-toggle {
    display: block;
    width: calc(100% - 2 * var(--s-3));
    margin: var(--s-3) var(--s-3) 0;
    padding: 10px 12px;
    background: transparent;
    color: var(--text-muted);
    border: 1px dashed var(--hairline);
    border-radius: var(--r-md);
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.deal-detail-more-toggle:hover,
.deal-detail-more-toggle:focus-visible { color: var(--text); border-color: var(--hairline-strong); outline: none; }

.deal-detail-more-list {
    margin: var(--s-2) var(--s-3) 0;
    padding: var(--s-3);
    background: rgba(255, 255, 255, 0.03);
    border-radius: var(--r-md);
}
.deal-detail-more-list.is-hidden { display: none; }

.deal-detail-explore {
    display: block;
    width: calc(100% - 2 * var(--s-3));
    margin: var(--s-4) var(--s-3) 0;
    padding: 12px 16px;
    background: var(--accent, #3b82f6);
    color: #fff;
    border: none;
    border-radius: var(--r-md);
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
}
.deal-detail-explore:hover,
.deal-detail-explore:focus-visible { background: var(--accent-hover, #2563eb); outline: none; }

/* ============================================================
   Report-a-problem entry point on each deal card.
   Bottom-right flag icon — same circle treatment as .share-btn.
   ============================================================ */
.deal-card-footer {
    display: flex;
    justify-content: flex-end;
    margin-top: var(--s-2);
    padding-top: var(--s-2);
    border-top: 1px solid var(--hairline);
}
.report-btn {
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    line-height: 1;
    padding: 4px 6px;
    border-radius: var(--r-sm);
    transition: color 0.15s, background 0.15s;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.report-btn svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    display: block;
}
.report-btn:hover,
.report-btn:focus-visible {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
    outline: none;
}

/* ============================================================
   Report-a-problem modal (opens from .report-btn).
   Reuses .prefs-overlay / .prefs-modal chrome.
   ============================================================ */
.report-form {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
}
.report-options {
    border: 0;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.report-legend {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 4px;
    padding: 0;
}
.report-option {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 0.9rem;
    color: var(--text);
    cursor: pointer;
    line-height: 1.4;
}
.report-option input[type="radio"] { margin-top: 4px; flex-shrink: 0; }

.report-note-label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 0.8rem;
    color: var(--text-muted);
}
.report-note {
    width: 100%;
    padding: 8px;
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    border: 1px solid var(--hairline);
    border-radius: var(--r-sm, 6px);
    font: inherit;
    font-size: 0.9rem;
    resize: vertical;
    min-height: 3em;
    box-sizing: border-box;
}
.report-note:focus { outline: none; border-color: var(--hairline-strong); }

.report-actions {
    display: flex;
    gap: 8px;
    margin-top: var(--s-1);
}
.report-submit,
.report-cancel {
    padding: 10px 16px;
    border-radius: var(--r-md);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
}
.report-submit {
    background: var(--accent, #3b82f6);
    color: #fff;
}
.report-submit:hover:not(:disabled),
.report-submit:focus-visible:not(:disabled) {
    background: var(--accent-hover, #2563eb);
    outline: none;
}
.report-submit:disabled { opacity: 0.6; cursor: default; }
.report-cancel {
    background: transparent;
    color: var(--text-muted);
    border-color: var(--hairline);
}
.report-cancel:hover,
.report-cancel:focus-visible {
    color: var(--text);
    border-color: var(--hairline-strong);
    outline: none;
}
.report-status {
    font-size: 0.85rem;
    color: var(--text-muted);
    min-height: 1em;
}
.report-status--error { color: var(--danger, #ef4444); }

/* ============================================================
   Page-level toast (share fallback + deep-link not-found)
   ============================================================ */
.toast {
    position: fixed;
    left: 50%;
    bottom: 24px;
    transform: translate(-50%, 8px);
    z-index: 1200;
    max-width: calc(100vw - 32px);
    background: rgba(15, 23, 42, 0.95);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: var(--text);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-md);
    padding: 10px 14px;
    font-size: 0.875rem;
    line-height: 1.4;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

.brand[role="button"] { cursor: pointer; }
.brand[role="button"]:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; border-radius: var(--r-sm); }



/* ========================================================================
   Deal-icon glyphs — Twemoji color SVGs in web/icons/emoji/, rendered as
   <img>. Saturate+brightness filter mutes the colors a touch so they read
   as recognisable food without the candy-bright Twemoji palette. Active
   cards drop the filter so the icon pops back to full vibrance.
   ======================================================================== */
.deal-emoji {
    display: inline-block;
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
    pointer-events: none;
    filter: saturate(0.22) brightness(0.72);
    transition: filter 0.15s;
}
.day-card.is-active .deal-emoji,
.chip-filter-btn.is-active .deal-emoji { filter: saturate(0.45) brightness(0.85); }
.day-card.is-empty .deal-emoji { filter: saturate(0.1) brightness(0.55); }


/* ============================================================
   Deals Alert modal — opened by the top-right .notif-btn.
   Single-form prefs_signup capture (email/name/city/postal).
   Posts to /api/forms with type='prefs_signup' — same backend
   as the 3-step interstitial flow in .prefs-overlay.
   ============================================================ */
.da-overlay {
    position: fixed;
    inset: 0;
    z-index: 900;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}

.da-overlay[hidden] { display: none; }

.da-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(5, 10, 20, 0.75);
    backdrop-filter: blur(3px);
}

.da-modal {
    position: relative;
    background: var(--surface-strong);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-lg);
    padding: 28px 28px 24px;
    width: 100%;
    max-width: 440px;
    max-height: 92vh;
    overflow-y: auto;
    box-shadow: 0 32px 80px rgba(0, 0, 0, 0.6);
    animation: daSlideUp 0.2s cubic-bezier(0.34, 1.1, 0.64, 1);
    background-image: radial-gradient(ellipse 220px 160px at 110% -10%, rgba(59,130,246,0.12) 0%, transparent 70%);
}

@keyframes daSlideUp {
    from { opacity: 0; transform: translateY(16px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0)    scale(1); }
}

.da-close {
    position: absolute;
    top: 12px;
    right: 14px;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
    border-radius: var(--r-sm);
    transition: background 0.15s, color 0.15s;
}

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

.da-hero {
    display: flex;
    justify-content: center;
    margin-bottom: 18px;
}

.da-hero-mark {
    width: 52px;
    height: 52px;
    filter: drop-shadow(0 0 12px rgba(59,130,246,0.35));
}

@keyframes daRadarSpin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

.da-hero-mark--pulse {
    filter: drop-shadow(0 0 14px rgba(16,185,129,0.45));
    animation: daRadarSpin 4s linear infinite;
}

.da-eyebrow {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--accent);
    text-align: center;
    margin: 0 0 6px;
}

.da-eyebrow--green { color: var(--price); }

.da-title {
    font-size: 1.5rem;
    font-weight: 800;
    color: var(--text);
    line-height: 1.2;
    margin: 0 0 8px;
    text-align: center;
    letter-spacing: -0.02em;
}

.da-desc {
    font-size: 0.875rem;
    color: var(--text-muted);
    line-height: 1.6;
    margin: 0 0 22px;
    text-align: center;
}

.da-form { display: flex; flex-direction: column; gap: 14px; }

.da-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }

.da-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.da-required { color: var(--accent); margin-left: 2px; }
.da-optional { color: #7f92aa; font-weight: 400; font-size: 0.85em; }

.da-input-wrap {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-sm);
    padding: 0 12px;
    transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}

.da-input-wrap:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
    background: rgba(59, 130, 246, 0.04);
}

.da-input-icon {
    color: var(--text-muted);
    flex-shrink: 0;
    opacity: 0.7;
}

.da-input {
    flex: 1;
    border: none;
    background: transparent;
    font-size: 0.95rem;
    font-family: inherit;
    color: var(--text);
    padding: 12px 0;
    outline: none;
    min-width: 0;
}

.da-input::placeholder { color: var(--text-muted); opacity: 0.55; }

/* Override iOS Safari + Chrome autofill, which forces a light yellow/blue
   background and dark text on the input itself — invisible on a dark theme.
   The `box-shadow` inset paints over the autofill background; `text-fill-color`
   overrides the autofilled text color; the long `transition-delay` on
   background-color stops the browser fading its own background back in. */
.da-input:-webkit-autofill,
.da-input:-webkit-autofill:hover,
.da-input:-webkit-autofill:focus,
.da-input:-webkit-autofill:active {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px #1a2236 inset;
    caret-color: var(--text);
    transition: background-color 9999s ease-in-out 0s, color 9999s ease-in-out 0s;
}

/* Textarea variant — same chrome as .da-input-wrap but vertically padded
   and no left icon, so the textarea breathes. */
.da-textarea-wrap {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-sm);
    padding: 8px 12px;
    transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}

.da-textarea-wrap:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
    background: rgba(59, 130, 246, 0.04);
}

.da-textarea {
    width: 100%;
    padding: 4px 0;
    resize: vertical;
    min-height: 64px;
    line-height: 1.4;
}

.da-help {
    margin: 0;
    text-align: right;
    font-size: 0.72rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.da-notes-count.is-near-limit { color: #fbbf24; }
.da-notes-count.is-at-limit { color: #f87171; }

.rs-message-count {
    text-align: right;
    font-size: 0.72rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.rs-message-count.is-near-limit { color: #fbbf24; }
.rs-message-count.is-at-limit { color: #f87171; }

/* Intent picker — segmented two-card switch above the restaurant form. */
.rs-intent {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin: 4px 0 14px;
}

.rs-intent-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
    gap: 4px;
    padding: 12px 14px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    color: var(--text-muted);
    cursor: pointer;
    font-family: inherit;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.rs-intent-card:hover {
    border-color: var(--hairline-strong);
    color: var(--text);
}

.rs-intent-card.is-selected {
    border-color: var(--accent);
    background: rgba(59, 130, 246, 0.12);
    color: var(--text);
    box-shadow: 0 0 0 1px var(--accent) inset;
}

.rs-intent-title {
    font-weight: 700;
    font-size: 0.88rem;
    letter-spacing: 0.01em;
}

.rs-intent-sub {
    font-size: 0.75rem;
    color: var(--text-muted);
    line-height: 1.35;
}

.rs-intent-card.is-selected .rs-intent-sub { color: var(--text); opacity: 0.8; }

/* Perk blurb shown only when claim_listing is the active intent. */
.rs-perks {
    margin: -4px 0 14px;
    padding: 12px 14px;
    background: rgba(16, 185, 129, 0.08);
    border: 1px solid rgba(16, 185, 129, 0.3);
    border-radius: var(--r-md);
}

.rs-perks-head {
    margin: 0 0 6px;
    font-size: 0.82rem;
    font-weight: 700;
    color: #10b981;
    letter-spacing: 0.01em;
}

.rs-perks-list {
    margin: 0;
    padding-left: 18px;
    font-size: 0.82rem;
    color: var(--text);
    line-height: 1.45;
}

.rs-perks-list li { margin: 2px 0; }

.da-label-optional {
    font-weight: 400;
    color: var(--text-muted);
    opacity: 0.7;
    font-size: 0.78rem;
}

@media (max-width: 480px) {
    .rs-intent { grid-template-columns: 1fr; }
}

.da-row {
    display: grid;
    /* Postal codes fit "N2P 2C2" / "12345" — narrower than city names like
       "Kitchener-Waterloo" — so weight the split toward city. */
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: 10px;
}

.da-field-error {
    font-size: 0.78rem;
    color: #f87171;
    margin: 0;
}

.da-turnstile { margin-top: 2px; }

.da-submit-error {
    font-size: 0.82rem;
    color: #f87171;
    text-align: center;
    margin: 0;
}

.da-form::before {
    content: '';
    display: block;
    height: 1px;
    background: var(--hairline);
    margin-top: 4px;
}

.da-submit {
    width: 100%;
    padding: 13px;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: var(--r-pill);
    font-size: 0.95rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    letter-spacing: 0.01em;
    transition: background 0.15s, box-shadow 0.15s, transform 0.08s;
    box-shadow: 0 4px 18px rgba(59, 130, 246, 0.35);
}

.da-submit:hover:not(:disabled) {
    background: var(--accent-hover);
    box-shadow: 0 6px 22px rgba(59, 130, 246, 0.45);
}

.da-submit:active:not(:disabled) { transform: translateY(1px); }
.da-submit:disabled { opacity: 0.55; cursor: not-allowed; box-shadow: none; }

.da-submit--ghost {
    background: transparent;
    border: 1px solid var(--hairline-strong);
    color: var(--text-muted);
    box-shadow: none;
}

.da-submit--ghost:hover { background: rgba(255,255,255,0.06); color: var(--text); box-shadow: none; }

.da-footer-note {
    text-align: center;
    font-size: 0.75rem;
    color: var(--text-muted);
    opacity: 0.6;
    margin: 2px 0 0;
}

@media (max-width: 480px) {
    .da-modal { padding: 24px 18px 20px; }
    .da-row   { grid-template-columns: 1fr; }
    .da-title { font-size: 1.3rem; }
}

/* ============================================================
   DESKTOP SPLIT VIEW (>= 1024px) — map + list
   ------------------------------------------------------------
   Mobile/tablet (< 1024px) keeps the existing stacked layout
   untouched: .home-split is `display:contents`, so its children
   (map, controls, banners, deal list) behave exactly as direct
   children of #home-view — zero layout change below the cutoff.

   At >= 1024px the region becomes two panes:
     col 1  scrolling controls + banners + deal-card grid
     col 2  persistent map, sticky to the viewport
   ============================================================ */
/* INVARIANT (S12): these base rules must stay OUTSIDE the @media block below, so
   the mobile/tablet `display:contents` always applies under the cutoff — the
   wrappers vanish from layout and children flow exactly as before (mobile
   byte-identical). Only the >=1024px block below ever makes them real boxes. */
.home-split,
.home-list-col,
.home-list-sticky { display: contents; }

/* INVARIANT (S12): this @media block (incl. the bare `#dealsContainer` 2-up
   override) must stay at END of style.css, after the base grid/list rules, so
   its desktop overrides win on source order. */
@media (min-width: 1024px) {
    /* Use more of a wide desktop. The global cap is 1200px (good for the mobile
       column); on the split we widen it so the list can grow to a 3-up grid and
       the map gets more room. Capped at 1600px so line length / map stay sane on
       ultrawide; the centered margins only appear past ~1600px. */
    .app-container { max-width: 1600px; }

    .home-split {
        display: grid;
        /* Map gets the larger share on desktop (Zillow/Yelp style). Wider than
           the list once there's room; min 460px so it stays usable at 1024px. */
        grid-template-columns: minmax(0, 1fr) clamp(460px, 46vw, 960px);
        column-gap: var(--s-5);
        align-items: start;
    }

    /* Left pane: ALL non-map content in one cell so it never shares the map's
       (tall) grid row. Its children flow as a normal block column inside. */
    .home-split > .home-list-col {
        display: block;        /* override the base display:contents on desktop */
        grid-column: 1;
        min-width: 0;          /* let the inner card grid shrink, no overflow */
    }

    /* Sticky filter header: range + filters pin at the same line as the map
       (shared top: var(--s-4)); the deal list scrolls UP underneath them. The
       solid bg + bottom padding keep cards from showing through the gap.
       #refinePanel lives inside and grows the box in place. */
    .home-list-col > .home-list-sticky {
        display: block;        /* override the base display:contents on desktop */
        position: sticky;
        top: var(--s-4);
        z-index: 5;
        background: var(--bg);
        padding-bottom: var(--s-3);
        /* Solid bg extended upward covers the var(--s-4) gap between the viewport
           top and the pinned header, so cards can't peek above the controls. */
        box-shadow: 0 calc(var(--s-4) * -1) 0 var(--bg);
    }

    /* Right column: persistent map pinned to the viewport. Sticky travel is
       bounded by .home-split's box (tall, driven by the left column), so the
       map stays pinned the whole scroll. align-self:start keeps it from
       stretching to the grid row. No grid-row span — that would inflate the
       grid with empty implicit rows. */
    .home-split > #mapSection {
        grid-column: 2;
        grid-row: 1;
        align-self: start;
        position: sticky;
        top: var(--s-4);
        /* A comfortable panel, not a full-viewport slab — less vertical, calmer
           scroll. Sticky still pins it while the list scrolls past. */
        height: clamp(420px, 72vh, 760px);
        height: clamp(420px, 72dvh, 760px);
        margin-bottom: 0;
    }
    /* Force the map visible on desktop even if the mobile collapse state is
       still set on first paint (JS also calls setMapRevealed(true); this is
       the no-flash guard). Overrides the height:0 !important collapse rule. */
    .home-split > #mapSection.is-collapsed {
        height: clamp(420px, 72vh, 760px) !important;   /* fallback for no-dvh browsers */
        height: clamp(420px, 72dvh, 760px) !important;
        min-height: 0 !important;
        max-height: none !important;
        border-width: 1px !important;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
        pointer-events: auto;
    }
    .home-split > #mapSection.is-expanded {
        height: clamp(420px, 72vh, 760px);   /* fallback for no-dvh browsers */
        height: clamp(420px, 72dvh, 760px);
    }

    /* The reveal / expand / fullscreen toggles are meaningless when the map is
       always present in its own pane. */
    #mapRevealBtn,
    .map-section #mapToggleBtn,
    .map-section #mapFullscreenBtn { display: none; }

    /* Deal list packs into the narrower left column as a 2-up card grid. */
    #dealsContainer {
        grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    }

    /* Hover affordance pairs with the pin highlight (card<->pin sync). */
    .deal-card {
        transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
    }
    .deal-card:hover,
    .deal-card:focus-within {
        border-color: rgba(255, 59, 48, 0.6);   /* matches the pin's red hover ring */
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
        transform: translateY(-2px);
    }
    /* Respect reduced-motion: keep the border/shadow affordance, drop the lift +
       its transition (matches the codebase's reduced-motion posture elsewhere). */
    @media (prefers-reduced-motion: reduce) {
        .deal-card { transition: border-color 0.15s ease, box-shadow 0.15s ease; }
        .deal-card:hover,
        .deal-card:focus-within { transform: none; }
    }

    /* 5a no-coverage collapse: when the viewport is outside any covered city the
       map is useless, so collapse to a single centered column and hide the map.
       JS toggles .home-split--no-coverage on the true no-coverage branch only
       (in-coverage zero / no-match states keep the split — map still useful). */
    .home-split.home-split--no-coverage {
        grid-template-columns: 1fr;
        max-width: 780px;
        margin-inline: auto;
    }
    .home-split.home-split--no-coverage > #mapSection { display: none; }
}

/* Unified restaurant claim/signup modal — public catalog search results.
   The owner types their name, picks their listing (→ claim), or adds a new one. */
/* Cap at ~4 result rows (~64px each incl. gap) so typing 2 letters doesn't
   balloon the modal — the rest scroll within this list, not by growing it. */
#rsSearchResults { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 14px; max-height: 264px; overflow-y: auto; }
.rs-result {
    display: flex; flex-direction: column; gap: 2px; text-align: left;
    padding: 10px 12px; border: 1px solid rgba(148,163,184,0.28); border-radius: 10px;
    background: rgba(148,163,184,0.06); color: inherit; cursor: pointer; font: inherit;
    transition: border-color .12s, background .12s;
}
.rs-result:hover, .rs-result:focus-visible { border-color: #3b82f6; background: rgba(59,130,246,0.10); outline: none; }
.rs-result-name { font-weight: 600; }
.rs-result-addr { font-size: 0.82em; color: #94a3b8; }
.rs-noresults { color: #94a3b8; font-size: 0.9em; margin: 4px 2px 14px; }
.da-back-link {
    background: none; border: 0; color: #94a3b8; cursor: pointer; font: inherit;
    padding: 2px 0; margin-bottom: 10px; text-align: left;
}
.da-back-link:hover { color: #cbd5e1; text-decoration: underline; }
