/*
base.css
Role: shared foundation styles.
Responsibility: theme tokens, document defaults, and topbar controls.
Note: Modify only from the latest local file.
*/
:root {
    color-scheme: light dark;
    --radius: 4px;
    --radius-sm: 2px;
    --ticker-h: 31px;
    --widget-h: 40px;   /* shared fixed height for topbar widgets (weather, radio) — compact 2-row */
    --widget-w: 250px;  /* shared fixed width for topbar widgets (weather, radio) */
    /* Topbar shell margin-bottom — single source of truth so .status-strip can
       compute the tape's real top from it (see .status-strip). Overridden <=820px. */
    --topbar-mb: var(--space-sm);
    --space-sm: 10px;
    --space-md: 14px;
    --text-soft: var(--muted);
    /* type scale */
    --text-xs:    0.68rem;
    --text-sm:    0.72rem;
    --text-meta:  0.82rem;
    --text-base:  0.84rem;
    --text-ui:    0.90rem;
    --text-body:  1rem;
    --text-panel: 1.02rem;
    --text-lg:    1.5rem;
    /* letter-spacing */
    --tracking-ui:    0.01em;
    --tracking-label: 0.07em;
    --tracking-wide:  0.15em;
    /* line-height */
    --leading-tight:   1.15;
    --leading-heading: 1.3;
    --leading-body:    1.35;
    --leading-prose:   1.5;
    --bg: #f5f7fb;
    --text: #222;
    --muted: #555;
    --muted-2: #666;
    --panel: #ffffff;
    --panel-soft: #f1f3f4;
    --border-accent: #0b57d0;
    /* Topbar accent: the header's decorative bottom line AND the active nav-pill's 3-sided border
       share this one color (theme-independent — it sits over the header photo in both themes). */
    --topbar-accent: rgba(45, 112, 192, 0.7);
    --border-local: #188038;
    --link-bg: #e8f0fe;
    --link-bg-hover: #dce7fd;
    --link-text: #0b57d0;
    --button-bg: #0b57d0;
    --button-bg-hover: #0842a0;
    --button-text: #ffffff;
    --shadow: 0 8px 24px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.06);
    /* THE single shared shadow for ALL floating popovers (settings, contact, nav dropdown,
       weather, radio). Heavy 3-layer drop + a crisp 1px ring so they read as clearly lifted.
       Every popover uses var(--shadow-float) — do NOT hardcode a popover shadow anywhere. */
    --shadow-float: 0 24px 64px rgba(0,0,0,0.22), 0 8px 24px rgba(0,0,0,0.14), 0 2px 6px rgba(0,0,0,0.08), 0 0 0 1px rgba(0,0,0,0.06);
    --shadow-sm:  0 2px 6px rgba(0,0,0,0.07), 0 1px 2px rgba(0,0,0,0.04);
    --shadow-btn: 0 4px 12px rgba(11,87,208,0.22), 0 1px 3px rgba(0,0,0,0.08);
    --shadow-header: 2px 4px 6px rgba(0,0,0,0.45), 1px 2px 3px rgba(0,0,0,0.3);
    --shadow-pill:   1px 3px 5px rgba(0,0,0,0.35), 1px 1px 2px rgba(0,0,0,0.2);
    --drop-shadow-icon:  drop-shadow(1px 3px 4px rgba(0,0,0,1)) drop-shadow(2px 5px 10px rgba(0,0,0,0.9));
    --drop-shadow-chart: drop-shadow(2px 5px 8px rgba(0,0,0,0.65));
    --text-shadow-btn:     0 1px 3px rgba(0,0,0,0.30), 0 2px 6px rgba(0,0,0,0.12);
    --text-shadow-heading: 0 1px 1px rgba(0,0,0,0.06);
    --text-shadow-header: 0 -1px 1px rgba(255,255,255,0.4), 1px 2px 4px rgba(0,0,0,0.5);
    --text-shadow-pill:   0 -1px 1px rgba(255,255,255,0.12), 2px 4px 6px rgba(0,0,0,0.6);
    /* Topbar title + subtitle sit over the header photo — heavier than the rest for
       legibility against the image, but softened from the old alpha-1.0 triple shadow. */
    --text-shadow-title:  0 -1px 1px rgba(255,255,255,0.25), 2px 5px 8px rgba(0,0,0,0.65), 1px 2px 3px rgba(0,0,0,0.55);
    --debug: #4b5563;
    --image-bg: #d9dee5;
    --border: #d7dce3;
    --warning-bg: #fff5d6;
    --warning-text: #7a5a00;
    --danger-bg: #fde7e9;
    --danger-text: #a12622;
    --ok-bg: #e6f4ea;
    --ok-text: #137333;
    --radio-accent: #104b8c;
    --radio-accent-soft: #e6f0fb;
    --radio-chip: #d9e7fb;
    /* spacing scale — all layout gaps/padding reference these so density overrides work */
    --space-xs: 6px;
    /* --space-sm: 10px  already declared above */
    /* --space-md: 14px  already declared above */
    --space-lg: 20px;
    --space-xl: 28px;
    /* extended type scale */
    --text-mini: 0.62rem;   /* 9–10 px badges, tiny labels */
    /* semantic accent colors */
    --lean-left:   #4a7bd1; /* left-leaning source indicator */
    --lean-right:  #d16464; /* right-leaning source indicator */
    --market-up:   #15803d; /* positive market movement */
    --market-down: #b42318; /* negative market movement */
    --chart-line:  #4a7bd1; /* primary chart line / data visualization */
    --market-events-notable: #6b7280; /* historical event annotation */
}

html[data-theme="dark"] {
    color-scheme: dark;
    --bg: #0f141a;
    --text: #e6edf3;
    --muted: #a9b4c0;
    --muted-2: #97a3af;
    --panel: #18202a;
    --panel-soft: #243041;
    --border-accent: #5aa2ff;
    --border-local: #4ecb71;
    --link-bg: #243041;
    --link-bg-hover: #2d3b4f;
    --link-text: #8ab4ff;
    --button-bg: #2b6fd6;
    --button-bg-hover: #245db3;
    --button-text: #ffffff;
    --shadow: 0 10px 28px rgba(0,0,0,0.32), 0 2px 8px rgba(0,0,0,0.18);
    --shadow-float: 0 24px 64px rgba(0,0,0,0.55), 0 8px 24px rgba(0,0,0,0.40), 0 2px 6px rgba(0,0,0,0.22), 0 0 0 1px rgba(255,255,255,0.06);
    --shadow-sm:  0 2px 6px rgba(0,0,0,0.30), 0 1px 2px rgba(0,0,0,0.20);
    --shadow-btn: 0 4px 12px rgba(90,162,255,0.22), 0 1px 3px rgba(0,0,0,0.28);
    --text-shadow-heading: 0 1px 3px rgba(0,0,0,0.35), 0 2px 8px rgba(0,0,0,0.15);
    --debug: #a0acb8;
    --image-bg: #243041;
    --border: #314154;
    --warning-bg: #473a12;
    --warning-text: #f7d774;
    --danger-bg: #4a1f24;
    --danger-text: #ffb3ba;
    --ok-bg: #173626;
    --ok-text: #8be0a6;
    --radio-accent: #8ab4ff;
    --radio-accent-soft: #203249;
    --radio-chip: #28415c;
}

@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] {
        color-scheme: dark;
        --bg: #0f141a;
        --text: #e6edf3;
        --muted: #a9b4c0;
        --muted-2: #97a3af;
        --panel: #18202a;
        --panel-soft: #243041;
        --border-accent: #5aa2ff;
        --border-local: #4ecb71;
        --link-bg: #243041;
        --link-bg-hover: #2d3b4f;
        --link-text: #8ab4ff;
        --button-bg: #2b6fd6;
        --button-bg-hover: #245db3;
        --button-text: #ffffff;
        --shadow: 0 10px 28px rgba(0,0,0,0.32), 0 2px 8px rgba(0,0,0,0.18);
    --shadow-float: 0 24px 64px rgba(0,0,0,0.55), 0 8px 24px rgba(0,0,0,0.40), 0 2px 6px rgba(0,0,0,0.22), 0 0 0 1px rgba(255,255,255,0.06);
        --shadow-sm:  0 2px 6px rgba(0,0,0,0.30), 0 1px 2px rgba(0,0,0,0.20);
        --shadow-btn: 0 4px 12px rgba(90,162,255,0.22), 0 1px 3px rgba(0,0,0,0.28);
        --text-shadow-heading: 0 1px 3px rgba(0,0,0,0.35), 0 2px 8px rgba(0,0,0,0.15);
        --debug: #a0acb8;
        --image-bg: #243041;
        --border: #314154;
        --warning-bg: #473a12;
        --warning-text: #f7d774;
        --danger-bg: #4a1f24;
        --danger-text: #ffb3ba;
        --ok-bg: #173626;
        --ok-text: #8be0a6;
        --radio-accent: #8ab4ff;
        --radio-accent-soft: #203249;
        --radio-chip: #28415c;
    }
}

html,
body {
    height: 100%;
}

body {
    font-family: Arial, sans-serif;
    margin: 0;
    background: var(--bg);
    color: var(--text);
    overflow: hidden;
}

body.modal-open {
    overflow: hidden;
}

.wrap {
    margin: 0 auto;
}

.app-wrap {
    max-width: 1680px;
    height: 100vh;
    padding: 0;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.debug-wrap {
    max-width: 1260px;
    padding: 24px;
}

.debug-app-wrap {
    max-width: 1680px;
}

.debug-app-wrap .debug-content {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 24px;
    box-sizing: border-box;
}

h1 {
    margin-top: 0;
    margin-bottom: 0;
}

h2 {
    margin-top: 32px;
    margin-bottom: var(--space-md);
    font-size: 1.2rem;
}

.sub {
    color: var(--muted);
    margin-bottom: 24px;
}


.topbar-shell {
    flex: 0 0 auto;
    position: sticky;
    top: 0;
    z-index: 1100;
    margin-bottom: var(--topbar-mb);
    padding-bottom: 0;
    background: transparent;
    overflow: visible;
    isolation: isolate;
}

html[data-theme="dark"] .topbar-shell {
    background: transparent;
}

@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] .topbar-shell {
        background: transparent;
    }
}

/* Phone: make the header OPAQUE (same color as the page behind it → visually identical, but no
   see-through). On iOS, focusing the Contact/Search inputs raises the keyboard and shifts the
   visual viewport, which slides the relative `.signal-ticker-tape` up UNDER the sticky header;
   a transparent header let the ticker show THROUGH it ("ticker rendering on the header"). The
   opaque fill (header z-index 1100 > the ticker) hides it cleanly. Placed after the theme rules
   so it wins for dark/auto too (equal specificity, later source). */
html[data-phone-mode] .topbar-shell {
    background: var(--bg);
}


.topbar-shell::before {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0.4;
    pointer-events: none;
    z-index: 0;
    background: url("/static/img/ant-header1.png");
    background-position: center;
    background-size: cover;
    -webkit-mask-image:
        linear-gradient(to bottom, transparent 0%, #000 16%, #000 100%),
        linear-gradient(to right, transparent 0%, #000 16%, #000 84%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to bottom, transparent 0%, #000 16%, #000 100%),
        linear-gradient(to right, transparent 0%, #000 16%, #000 84%, transparent 100%);
    mask-composite: intersect;
}

.topbar-shell::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--topbar-accent), transparent);
    pointer-events: none;
    z-index: 1;
}

/* ── Status bar (below-header app status messages) ───────────────────────── */

.status-strip {
    position: fixed;
    /* Sit directly below the header. --topbar-h is the shell's measured height
       (kept current by shared_ui.js) and the shell is sticky at top:0, so this IS
       the header's bottom edge — no margin/tape-offset math. */
    top: var(--topbar-h, 0px);
    left: 0;
    right: 0;
    height: var(--ticker-h);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    padding: 0 var(--space-md);
    background: color-mix(in srgb, var(--panel) 92%, var(--border) 8%);
    border-bottom: 1px solid var(--border);
    font-size: var(--text-ui);
    color: var(--text-soft);
    z-index: 1099;
    pointer-events: none;
    transform: translateY(-100%);
    opacity: 0;
    transition: transform 0.18s ease, opacity 0.18s ease;
}

.status-strip.is-visible {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
}

.status-strip.is-visible::before {
    content: '';
    display: inline-block;
    width: 10px;
    height: 10px;
    border: 2px solid var(--border);
    border-top-color: var(--muted);
    border-radius: 50%;
    animation: chart-spin 0.7s linear infinite;
    flex-shrink: 0;
}

.status-strip-row + .status-strip-row::before {
    content: "•";
    margin-right: var(--space-xs);
    opacity: 0.45;
}

/* ── Activity bar (indeterminate progress indicator) ──────────────────────── */

@keyframes activity-sweep {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(500%); }
}

.activity-bar {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 3px;
    overflow: hidden;
    opacity: 0;
    pointer-events: none;
    z-index: 3;
    transition: opacity 0.2s;
}

.activity-bar.is-active {
    opacity: 1;
}

.activity-bar::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 25%;
    height: 100%;
    background: linear-gradient(90deg, transparent, var(--button-bg) 50%, transparent);
    animation: activity-sweep 1.5s ease-in-out infinite;
}

.topbar {
    position: relative;
    z-index: 2;
    display: grid;
    grid-template-rows: auto auto;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-sm) 0 var(--space-sm);
    margin-bottom: 0;
    overflow: visible;
}

/* Two stacked full-width rows: row-main (title + widgets) over the pills row.
   Replaces the old 3-col grid where the radio spanned both rows and starved the
   pills of width (causing them to wrap early). */
.topbar {
    display: flex;
    flex-direction: column;
}
.topbar-row-main {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    width: 100%;
}
.topbar-row-main .topbar-primary { flex: 1 1 auto; min-width: 0; }   /* title block grows */
.topbar-row-main .topbar-middle,
.topbar-row-main .radio-player-shell { flex: 0 0 auto; }             /* widgets sit right, content-width */
.topbar-secondary { width: 100%; }

.topbar-primary {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    min-width: 0;
}

.topbar-secondary {
    grid-column: 1;
    grid-row: 2;
    display: flex;
    align-items: flex-end;
    gap: var(--space-sm);
    min-height: 0;
    overflow: visible;
}

.topbar-title-block {
    flex: 0 0 auto;          /* hold natural width so icons stay put; widgets hide instead of title compressing */
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.topbar-subtitle {
    font-size: calc(var(--text-xs) * 1.5);
    color: var(--muted);
    letter-spacing: 0.04em;
    padding: 0 var(--space-md);
    line-height: 1;
    opacity: 0.75;
    text-shadow: var(--text-shadow-title);
}

.topbar-title-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    min-width: 0;
}

/* Phone view (html[data-phone-mode], set by a fixed 500px media query in
   shared_ui.js): the title block is mostly wasted eye-candy on a tiny screen, so
   hide BOTH the title and the subtitle and let the icon group sit at the LEFT
   (it's the only remaining child of the row, which is flex/row by default).
   Reclaims the vertical space the old column-restack consumed. Layout only — no
   font scaling. Trigger is a fixed viewport width this can't perturb → no
   oscillation. (Later: a small logo could replace the hidden title.) */
/* HEADER phone layout — triggered at <=780 (NOT the 500px data-phone-mode used by Signal's
   phone feed + the ticker badge, which stay at 500). At 780 the nav pills already collapse to
   the burger, so hiding the title/subtitle + slimming the radio makes the whole header one
   compact row. Fixed-width @media (not geometric) → no oscillation, per the topbar contract. */
/* Header collapses to ONE compact row at <=780: hide title/subtitle (pills already collapse to
   the burger at this width), drop the topbar top padding so the row sits flush, and stretch the
   radio shell to fill the row height. The radio's compact internals are now the DEFAULT (base
   rules) at all widths — only these header-layout bits are phone-specific. Fixed-width @media
   (not geometric) → no oscillation, per the topbar contract. */
@media (max-width: 780px) {
    .topbar-subtitle,
    .topbar-title-row h1 {
        display: none;
    }
    html .topbar {
        padding-top: 0;
        padding-right: 0;   /* radio widget rests flush to the screen's right edge */
    }
    /* Narrow both widgets together via the shared token; stretch radio to fill the row height. */
    :root {
        --widget-w: 215px;
    }
    /* Radio may SHRINK horizontally when the row is crowded (e.g. large OS text size inflates the
       icons) so it never overlaps the icons — backs off until it clears them, down to a floor.
       The icon group holds its size so the radio is the one that yields, not the icons. */
    html .radio-player-shell {
        align-self: stretch;
        flex: 0 1 auto;
        min-width: 120px;
    }
    html .radio-player-card {
        height: 100%;
    }
    /* Icon group: vertically center in the (radio-height) row, and DON'T shrink (radio yields). */
    html .topbar-primary {
        align-self: stretch;
        display: flex;
        align-items: center;
        flex: 0 0 auto;
        min-width: 0;
    }
}

/* True phone (<=500): narrow the radio further so it clears the icons even with large OS text.
   The 500-780 range keeps the wider 215px (more room there). */
@media (max-width: 500px) {
    :root { --widget-w: 150px; }
}

/* ── Radio widget: three compact buttons (all screen sizes) ──
   The radio widget is ALWAYS three fixed 30px square buttons with 5px gaps all around + between:
     [ Play/Pause ] [ Now-Playing (wave chrome) ] [ Station picker (tuner icon) ].
   Tapping the middle (now-playing) button toggles the Now-Playing popup (full station/song text +
   art); the right button opens the station picker. The old marquee + volume center was retired —
   the now-playing info lives in the popup. No screen-size variant: this is the single widget at
   every width (it's just 3 buttons, so it never needs to shrink/hide). The widget sizes to its
   content (5+30+5+30+5+30+5 = 110px) — no dead space. */
/* Radio mode sizes to its 3-button content (110px). NOT applied in podcast mode — the podcast
   marquee/seek center needs the original wider --widget-w (its flexible child would blow a
   max-content parent off-screen). :not(.is-podcast) scopes the shrink to radio only. */
.radio-player-shell:not(.is-podcast),
.radio-player-shell:not(.is-podcast) .radio-player-card {
    width: max-content;
    min-width: 0;
}
.radio-player-shell:not(.is-podcast) .radio-player-content {
    flex: 0 0 auto;
    gap: 5px;
    padding: 5px;
    justify-content: flex-start;
}
.radio-player-toggle,
.radio-player-np-btn,
.radio-player-wave-btn {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
}
/* The old marquee/volume center is retired — never shown. */
/* Right button shows the tuner icon (station picker); its wave-row chrome moved to the middle.
   (The marquee/volume center is hidden by default on .radio-player-center itself — see its rule
   below — and only re-shown in podcast mode.) */
.radio-player-wave-btn .radio-player-wave-row { display: none; }
.radio-player-wave-btn .radio-player-tuner-icon { display: block; }

/* ── Now-Playing popup — a small standalone popover, SEPARATE from the station picker. Anchored
   to the radio shell like .radio-player-menu (top:100%+10px; right:0), content-height (it's small),
   shared --shadow-float shadow. radio.js toggles the [hidden] attribute to show/hide. Shown at all
   widths (the middle now-playing button opens it everywhere). */
/* The explicit display:flex below would otherwise BEAT the bare [hidden] attribute (whose
   display:none is lowest-specificity) → the popup would show permanently. Re-assert hidden. */
.radio-player-np-popup[hidden] { display: none; }
.radio-player-np-popup {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    left: auto;
    width: min(300px, calc(100vw - 24px));
    z-index: 1000;
    box-sizing: border-box;
    padding: var(--space-md);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: rgba(255, 255, 255, 0.97);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow: var(--shadow-float);   /* shared popover shadow (theme-aware via the token) */
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}
html[data-theme="dark"] .radio-player-np-popup {
    background: rgba(18, 24, 34, 0.97);
}
@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] .radio-player-np-popup {
        background: rgba(18, 24, 34, 0.97);
    }
}
/* Phone: anchored absolute inside the radio widget like the picker — switch to viewport-fixed and
   make it a full-width sheet flush to the header's bottom edge (matches settings/contact/picker).
   Keyed off html[data-phone-mode] (NOT a px @media) so the phone breakpoint has ONE home — the
   setPhoneMode matchMedia in shared_ui.js. */
html[data-phone-mode] .radio-player-np-popup {
    position: fixed;
    top: var(--topbar-h, 56px);
    left: 0;
    right: 0;
    width: auto;
    border-radius: 0 0 var(--radius) var(--radius);
}
/* Song art on the left — fixed square, shown only when an art URL exists (JS clears [hidden]);
   no reserved gap when absent (it's display:none, and the flex gap collapses with one child). */
.radio-player-np-art {
    flex: 0 0 auto;
    width: 100px;
    height: 100px;
    border-radius: var(--radius-sm);
    object-fit: cover;
}
.radio-player-np-art[hidden] { display: none; }
.radio-player-np-text { min-width: 0; }   /* let the text column shrink so word-break works */
.radio-player-np-station {
    font-size: var(--text-meta);
    font-weight: 700;
    color: var(--radio-accent);
    word-break: break-word;
}
.radio-player-np-track {
    font-size: var(--text-sm);        /* ≤ station size */
    font-weight: 400;                 /* regular, not bold */
    color: var(--text);
    word-break: break-word;
    margin-top: 4px;
}
.radio-player-np-detail {
    font-size: var(--text-sm);
    color: var(--muted);
    word-break: break-word;
    margin-top: 2px;
}
.radio-player-np-detail:empty { display: none; }

.topbar-title-row h1 {
    min-width: 0;
    flex: 0 0 auto;          /* title holds its width; icon-group can't slide left into it */
}

.topbar-title-text {
    display: inline-flex;
    align-items: center;
    padding: 8px var(--space-md);
    font-family: "Palatino Linotype", "Book Antiqua", Georgia, serif;
    font-size: clamp(1.72rem, 1.45rem + 0.82vw, 2.28rem);
    font-weight: 700;
    letter-spacing: var(--tracking-ui);
    line-height: 1.05;
    background: transparent;
    box-sizing: border-box;
    color: var(--muted);
    text-shadow: var(--text-shadow-title);
}

.topbar-title-row h1 { color: var(--muted); }
.topbar-initial-n { color: var(--lean-left); }
.topbar-word-news { padding: 0 var(--space-sm); color: #fff; }
.topbar-initial-v { color: var(--lean-right); }

.topbar-mode-pills {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: var(--space-xs);
    flex-wrap: nowrap;
    min-width: 0;
}

.topbar-mode-pills-bottom {
    align-self: end;
}

.mode-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius) var(--radius) 0 0;
    border: 1px solid transparent;
    background: rgba(0,0,0,0.18);
    color: var(--muted);
    text-decoration: none;
    font-family: inherit;
    font-size: var(--text-body);
    font-weight: 600;
    letter-spacing: var(--tracking-ui);
    white-space: nowrap;
    box-sizing: border-box;
    transition: background 0.12s, color 0.12s, border-color 0.12s, box-shadow 0.12s;
    /* No box-shadow: --shadow-pill casts downward (positive Y) and bled over the status bar
       below the topbar. Pills rely on border + background for definition instead. */
    text-shadow: var(--text-shadow-pill);
}

.mode-pill.is-active {
    background: var(--bg);
    /* 3-sided selected border on LEFT/TOP/RIGHT only; bottom transparent so the tab connects into the
       content below. Uses --topbar-accent — the SAME color as the header's decorative bottom line — so
       the selected pill and the header rule read as one accent. No glow, no clip-path, no cover pseudo:
       those fought the header's bottom line at the seam (glow/clip/cover deadlock). */
    border-color: var(--topbar-accent);
    border-bottom-color: transparent;
    color: var(--link-text);
    text-shadow: var(--text-shadow-pill);
}

.mode-pill:not(.is-active):not(:disabled):hover {
    background: rgba(0,0,0,0.04);
    border-color: var(--border);
    border-bottom-color: transparent;
    color: var(--text);
}

.mode-pill:disabled {
    opacity: 0.82;
    cursor: default;
}

.topbar-middle {
    /* Flex child of .topbar-row-main (the grid-column/grid-row/align-self:stretch here were dead
       leftovers from the old grid topbar). NO padding-bottom: it added 10px so a loaded 77px weather
       widget made this 87px and pushed the whole topbar (and the status bar) taller than the 77px
       radio. The row's `gap` already spaces it from the pills row below. Matches radio (padding 0). */
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    min-width: 0;
}

.radio-player-shell {
    width: var(--widget-w);   /* shared fixed widget width */
    min-width: 0;
    position: relative;
    overflow: visible;
    z-index: 20;
    align-self: flex-start;
    padding-bottom: 0;
}

.radio-player-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    min-width: 0;
    height: var(--widget-h);   /* fixed — identical to weather widget */
    box-sizing: border-box;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background:
        radial-gradient(circle at top left, rgba(16, 75, 140, 0.16), transparent 40%),
        linear-gradient(145deg, rgba(255,255,255,0.88), rgba(241,243,244,0.78));
    box-shadow: 2px 5px 8px rgba(0,0,0,0.65), 1px 2px 3px rgba(0,0,0,0.5);
    backdrop-filter: blur(12px);
    overflow: visible;
}

/* ── Chrome-less radio widget: strip the card so the buttons read as standalone icons
   sitting directly on the header image. Scoped :not(.is-podcast) so podcast mode keeps its
   card chrome. Each icon casts its own drop-shadow (drop-shadow follows the clip-path/SVG
   glyph shape; text-shadow wouldn't apply to these). ── */
.radio-player-shell:not(.is-podcast) .radio-player-card {
    border: none;
    background: none;
    box-shadow: none;
    backdrop-filter: none;
}
/* Toggle shadow goes on the (transparent) button, not the icon span: the play glyph is a
   clip-path triangle, and clip-path + drop-shadow on the SAME element clips the shadow away.
   The button has no fill, so only the icon glyph casts the shadow. Pause/wave/tuner are
   unclipped, so the filter sits on them directly. */
.radio-player-shell:not(.is-podcast) .radio-player-toggle,
.radio-player-shell:not(.is-podcast) .radio-player-wave-row,
.radio-player-shell:not(.is-podcast) .radio-player-tuner-icon {
    filter: drop-shadow(1px 2px 2px rgba(0, 0, 0, 0.9));
}

.radio-player-hd {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 5px var(--space-sm);
    border-bottom: 1px solid var(--border);
    border-radius: var(--radius) var(--radius) 0 0;
    flex: 0 0 auto;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--muted);
    background: color-mix(in srgb, var(--border) 55%, var(--panel-soft) 45%);
    text-shadow: var(--text-shadow-header);
    box-shadow: var(--shadow-header);
    min-width: 0;
}

.radio-player-content {
    flex: 1 1 auto;
    display: flex;
    align-items: center;   /* center the square buttons in the compact row */
    gap: 5px;
    padding: 5px;
    min-height: 0;
    min-width: 0;
    overflow: visible;
}

/* 3-zone radio layout: [play] [center: marquee over volume] [wave-button]. The two side
   buttons are square, full content-height; the center column stacks marquee (top) + volume
   (bottom). */
.radio-player-center {
    /* Hidden in radio mode (the 3-button widget has no marquee/volume); re-shown as flex in
       podcast mode via .radio-player-shell.is-podcast .radio-player-center. The flex layout
       properties below apply when it's re-shown. */
    display: none;
    flex: 1 1 auto;
    min-width: 0;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    overflow: visible;
}
.radio-player-now-playing { min-height: 0; }
.radio-player-vol-row { min-height: 0; line-height: 1; }

.radio-player-vol-row {
    display: flex;
    align-items: center;
}
/* Volume fills the center width in the new layout (override the old 44px inline size). */
.radio-player-center .radio-player-vol {
    width: 100%;
    flex: 1 1 auto;
}
/* Podcast mode swaps volume → seek (handled by the .is-podcast rules below). */
.radio-player-shell.is-podcast .radio-player-vol-row { display: none; }

html[data-theme="dark"] .radio-player-card {
    background:
        radial-gradient(circle at top left, rgba(138, 180, 255, 0.16), transparent 40%),
        linear-gradient(145deg, rgba(24,32,42,0.84), rgba(36,48,65,0.74));
}

@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] .radio-player-card {
        background:
            radial-gradient(circle at top left, rgba(138, 180, 255, 0.16), transparent 40%),
            linear-gradient(145deg, rgba(24,32,42,0.84), rgba(36,48,65,0.74));
    }
}


.radio-player-artwork-shell {
    position: relative;
    flex: 0 0 calc(var(--widget-h) - 20px);
    width: calc(var(--widget-h) - 20px);
    max-height: calc(var(--widget-h) - 20px);
    align-self: center;
    aspect-ratio: 1;
    border-radius: var(--radius);
    overflow: hidden;
    background: linear-gradient(145deg, var(--radio-chip), var(--panel-soft));
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.18);
}

.radio-player-artwork {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
}

.radio-player-artwork-fallback {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    background:
        radial-gradient(circle at 30% 28%, rgba(255,255,255,0.4), transparent 28%),
        linear-gradient(145deg, rgba(16,75,140,0.88), rgba(77,126,191,0.78));
}
.radio-player-artwork-fallback[hidden] { display: none !important; }
.radio-player-artwork[hidden] { display: none !important; }

.radio-player-artwork-glyph {
    position: relative;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    border: 2px solid rgba(255,255,255,0.95);
}

.radio-player-artwork-glyph::before,
.radio-player-artwork-glyph::after {
    content: "";
    position: absolute;
    inset: 50%;
    transform: translate(-50%, -50%);
    border-radius: 999px;
}

.radio-player-artwork-glyph::before {
    width: 8px;
    height: 8px;
    background: rgba(255,255,255,0.95);
}

.radio-player-artwork-glyph::after {
    width: 18px;
    height: 18px;
    border: 2px solid rgba(255,255,255,0.42);
}

.radio-player-body-column {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;   /* stack tight from top, no space-between spread */
    gap: 3px;
    overflow: hidden;
}

/* Wave lives INSIDE the square wave-button. Explicit height so the bars' per-bar % heights
   compute; 1px gap + bar min-width so 12 bars fit the small square without starving to 0 width. */
.radio-player-wave-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1px;
    align-self: center;
    width: 100%;
    height: 18px;
}

.radio-player-wave-bar {
    flex: 1 1 0;
    min-width: 1px;   /* 12 bars in the small square otherwise starve to 0 width */
    border-radius: 999px;
    background: currentColor;   /* matches the button's icon color (accent → --bg on hover) */
    transform-origin: 50% 50%;
    opacity: 0.7;
}

.radio-player-wave-bar:nth-child(5n+1) { height: 30%; }
.radio-player-wave-bar:nth-child(5n+2) { height: 85%; }
.radio-player-wave-bar:nth-child(5n+3) { height: 55%; }
.radio-player-wave-bar:nth-child(5n+4) { height: 100%; }
.radio-player-wave-bar:nth-child(5n)   { height: 45%; }

.radio-player-shell.is-playing .radio-player-wave-bar {
    opacity: 1;
    animation: radio-wave-bounce 0.75s ease-in-out infinite alternate;
}

.radio-player-shell.is-playing .radio-player-wave-bar:nth-child(5n+1) { animation-duration: 0.70s; animation-delay: 0.00s; }
.radio-player-shell.is-playing .radio-player-wave-bar:nth-child(5n+2) { animation-duration: 0.85s; animation-delay: 0.12s; }
.radio-player-shell.is-playing .radio-player-wave-bar:nth-child(5n+3) { animation-duration: 0.65s; animation-delay: 0.05s; }
.radio-player-shell.is-playing .radio-player-wave-bar:nth-child(5n+4) { animation-duration: 0.90s; animation-delay: 0.20s; }
.radio-player-shell.is-playing .radio-player-wave-bar:nth-child(5n)   { animation-duration: 0.78s; animation-delay: 0.08s; }

@keyframes radio-wave-bounce {
    from { transform: scaleY(0.18); }
    to   { transform: scaleY(1); }
}

.radio-player-seek-row {
    display: none;
    align-items: center;
    gap: var(--space-sm);
    height: 12px;
    flex: 0 0 12px;
}
/* Mode gating:
   RADIO mode → 3 buttons (play · now-playing · station-picker); center/seek/skip hidden.
   PODCAST mode (.is-podcast) → the radio-only NOW-PLAYING button hides (its popup is station/song
   specific) and the marquee/seek center reappears with seek-row + skip buttons. The STATION-PICKER
   button stays visible — it's the only way back to radio (selecting a station clears podcast mode).
   (Podcasts still play through this shared widget via window.TopbarPlayer; a dedicated podcast
   player is a future build.) */
.radio-player-shell.is-podcast .radio-player-np-btn { display: none; }
.radio-player-shell.is-podcast .radio-player-center { display: flex; }
/* (Podcast art is kept hidden by radio.js — no compact-widget art in either mode — so no CSS art
   gate is needed here; it must NOT reappear on the switch back to radio. See updateNowPlaying /
   updatePodcastMode.) */
.radio-player-seek-row { display: none; }
.radio-player-shell.is-podcast .radio-player-seek-row { display: flex; }
.radio-player-skip-btn {
    display: none;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 0;
}
.radio-player-shell.is-podcast .radio-player-skip-btn { display: flex; }

/* Skip-back icon: | ◀ */
#radio-player-skip-back::before {
    content: "";
    flex: 0 0 auto;
    width: 2px;
    height: 12px;
    background: currentColor;
}
#radio-player-skip-back::after {
    content: "";
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border-top: 6px solid transparent;
    border-bottom: 6px solid transparent;
    border-right: 9px solid currentColor;
}

/* Skip-fwd icon: ▶ | */
#radio-player-skip-fwd::before {
    content: "";
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border-top: 6px solid transparent;
    border-bottom: 6px solid transparent;
    border-left: 9px solid currentColor;
}
#radio-player-skip-fwd::after {
    content: "";
    flex: 0 0 auto;
    width: 2px;
    height: 12px;
    background: currentColor;
}

.radio-player-seek-time {
    font-size: var(--text-mini);
    color: var(--muted);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
}

.radio-player-seek {
    flex: 1 1 auto;
    min-width: 0;
    height: 3px;
    accent-color: var(--radio-accent, #3b82f6);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.radio-player-vol {
    width: 100%;
    height: 5px;
    accent-color: var(--radio-accent, #3b82f6);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    flex: 1 1 auto;
}
/* Compact slider thumb (the native thumb is large and overflowed the slim track + tight center
   column, clipping at the card edge). 12px, centered on the 5px track. */
.radio-player-vol::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--radio-accent, #3b82f6);
    cursor: pointer;
    margin-top: -3.5px;
}
.radio-player-vol::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border: none;
    border-radius: 50%;
    background: var(--radio-accent, #3b82f6);
    cursor: pointer;
}

.radio-player-controls {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    flex: 0 0 auto;
    margin-top: auto;   /* bottom-align controls in the body column */
}

/* Play/pause + now-playing + wave buttons: square, full content-height, flanking the center column.
   Light link bg, accent-fill on hover/open — no resting border. */
.radio-player-toggle,
.radio-player-np-btn,
.radio-player-wave-btn {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    align-self: center;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;   /* no fill — just the icon/chrome; selected state re-fills with accent */
    color: var(--link-text);
    cursor: pointer;
    padding: 6px;   /* icon fills the square minus padding, auto-scaling with the button */
    box-sizing: border-box;
}
/* Tuner icon (station picker) fills the square like the other glyphs. */
.radio-player-tuner-icon { width: 100%; height: 100%; }
.radio-player-skip-btn {
    width: 20px;
    height: 20px;
    border: none;
    border-radius: var(--radius-sm);
    background: var(--link-bg);
    color: var(--link-text);
    cursor: pointer;
    flex: 0 0 auto;
}

/* Hover affordance only on hover-capable pointers, so it never sticks after a click/tap. The
   PLAY button intentionally has NO filled state — it only swaps its icon (play↔pause). The WAVE
   button's filled "selected" look is driven solely by its open state, NOT by hover, so it's
   filled iff the popup is open and reverts the instant it closes. */
@media (hover: hover) {
    .radio-player-skip-btn:hover {
        background: var(--border-accent);
        color: var(--bg);
    }
}

/* Selected (popup-open) fill — the station-picker and now-playing buttons light up blue while
   their popup is open, reverting when it closes. [aria-expanded] (0,2,0) out-specifies the bare
   transparent rule (0,1,0). */
.radio-player-wave-btn[aria-expanded="true"],
.radio-player-np-btn[aria-expanded="true"] {
    background: var(--border-accent);
    color: var(--bg);
}
/* Slightly less padding than play, so the 12 wave bars have room in the square. */
.radio-player-wave-btn,
.radio-player-np-btn { padding: 5px; }

.radio-player-toggle:disabled,
.radio-player-np-btn:disabled,
.radio-player-wave-btn:disabled,
.radio-player-skip-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

/* Icons FILL the button's content box (button − 10px padding) and auto-scale with it: the
   shapes are drawn via clip-path/background on a 100%×100% block, so no fixed px to maintain. */
.radio-player-toggle-icon {
    width: 100%;
    height: 100%;
}

.radio-player-toggle-icon-play {
    background: currentColor;
    clip-path: polygon(0 0, 100% 50%, 0 100%);   /* right-pointing triangle */
}

.radio-player-toggle-icon-pause {
    display: none;
    background:
        linear-gradient(to right, currentColor 0 38%, transparent 38% 62%, currentColor 62% 100%);
}

.radio-player-shell.is-playing .radio-player-toggle-icon-play {
    display: none;
}

.radio-player-shell.is-playing .radio-player-toggle-icon-pause {
    display: block;
}

.radio-player-menu-icon,
.radio-player-menu-icon::before,
.radio-player-menu-icon::after {
    display: block;
    width: 10px;
    height: 1.5px;
    border-radius: 999px;
    background: currentColor;
    content: "";
}

.radio-player-menu-icon {
    position: relative;
    margin: 0 auto;
}

.radio-player-menu-icon::before {
    position: absolute;
    top: -3px;
}

.radio-player-menu-icon::after {
    position: absolute;
    top: 3px;
}

.radio-player-menu {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    left: auto;
    width: min(420px, calc(100vw - 24px));   /* single nav rail; caps to screen on phone */
    max-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 0 solid var(--border);
    border-radius: var(--radius);
    background: rgba(255, 255, 255, 0.97);
    z-index: 1000;
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-sizing: border-box;
}

.radio-player-menu.is-open {
    max-height: calc(100vh - 180px);
    border-width: 1px;
    box-shadow: var(--shadow-float);   /* shared popover shadow (theme-aware via the token) */
}

html[data-theme="dark"] .radio-player-menu {
    background: rgba(18, 24, 34, 0.97);
}

@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] .radio-player-menu {
        background: rgba(18, 24, 34, 0.97);
    }
}

/* Phone: the picker is anchored absolute INSIDE the radio widget (far right of the header), so
   left:0 there would mean the widget's edge, not the screen's. Switch to viewport-fixed and make
   it a full-screen sheet flush to the header's bottom edge (matches settings/contact/now-playing)
   that expands all the way to the bottom of the phone — top anchored to --topbar-h, open height
   fills the rest of the viewport (the inner .radio-genre-rail scrolls). Square corners since it
   meets all four screen edges. Keyed off html[data-phone-mode] so the phone breakpoint has ONE
   home (the setPhoneMode matchMedia in shared_ui.js). */
html[data-phone-mode] .radio-player-menu {
    position: fixed;
    top: var(--topbar-h, 56px);
    left: 0;
    right: 0;
    width: auto;
    border-radius: 0;
}
html[data-phone-mode] .radio-player-menu.is-open {
    max-height: calc(100svh - var(--topbar-h, 56px));
}


/* Station nav rail — single collapsible-accordion column (genres as collapsible groups,
   stations nested). Replaces the old two-pane genre-list + station-list layout. */

.radio-genre-rail {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    min-width: 0;
}

.radio-genre-rail .section-header:first-child {
    margin-top: 0;
}

.radio-player-now-playing {
    min-width: 0;
    min-height: 12px;   /* reserve the marquee/seek slot so layout doesn't shift when idle */
    display: grid;
    gap: 2px;
    overflow: hidden;
    align-self: flex-start;   /* top-align the marquee in the body column */
    align-content: start;
}

.radio-player-marquee {
    position: relative;
    overflow: hidden;
    min-width: 0;
    white-space: nowrap;
    padding-bottom: 0;
}

.radio-player-marquee-track {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: max-content;
    transform: translateX(0);
    line-height: 1;   /* hug the top — no extra leading above the text */
}

.radio-player-marquee.is-overflowing .radio-player-marquee-track {
    animation: radio-marquee-scroll var(--radio-marquee-duration, 22s) linear infinite;
}

.radio-player-station-line {
    font-size: var(--text-sm);
    font-weight: 700;
    line-height: 1;
    color: var(--radio-accent);
    white-space: nowrap;
    flex: 0 0 auto;   /* lead span inside the marquee track — scrolls, no ellipsis */
}

/* Separator after the name when followed by track text (mirrors detail-line) */
.radio-player-track-line:not(:empty)::before {
    content: "•";
    margin-right: 8px;
    color: var(--muted);
}

.radio-player-track-line {
    font-size: var(--text-sm);
    font-weight: 700;
}

.radio-player-track-line,
.radio-player-detail-line {
    flex: 0 0 auto;
}

.radio-player-detail-line::before {
    content: "•";
    margin-right: 8px;
}

.radio-player-detail-line:empty::before {
    content: "";
    margin-right: 0;
}

.radio-player-detail-line {
    font-size: var(--text-sm);
    color: var(--muted);
}


.top-search-wrap {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    flex: 1 1 auto;
}

/* ── Title-row icon group (Settings · Mail · Search) ── */
.topbar-icon-group {
    display: flex;
    align-items: center;
    gap: 4px;            /* tight spacing between the 3 icons */
    flex: 0 0 auto;
}

/* Burger menu (shown only when nav collapses to a dropdown). 3-line icon.
   .settings-btn sets display:inline-flex, which beats the bare [hidden] attribute —
   so the hidden state needs an explicit display:none to win. */
.topbar-burger { position: relative; }
.topbar-burger[hidden] { display: none; }
.topbar-burger-icon,
.topbar-burger-icon::before,
.topbar-burger-icon::after {
    content: "";
    display: block;
    position: absolute;
    left: 50%;
    width: 14px;
    height: 2px;
    border-radius: 999px;
    background: currentColor;
    transform: translateX(-50%);
}
.topbar-burger-icon { top: 50%; transform: translate(-50%, -50%); }
.topbar-burger-icon::before { top: -5px; }
.topbar-burger-icon::after  { top: 5px; }

/* Nav collapsed: hide the pills row; burger becomes the nav. */
.topbar.nav-collapsed .topbar-secondary {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 60;
    display: none;
    width: auto;
    min-width: 0;   /* size to the widest label, not a fixed 200px floor */
    padding: var(--space-xs) 0;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-float);
    /* Cap to the space below the header and scroll — otherwise the pills run off the bottom on a
       short (landscape phone) viewport and the last items get clipped. --topbar-h is live-measured. */
    max-height: calc(100dvh - var(--topbar-h, 56px) - 12px);
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
}
.topbar.nav-collapsed.nav-open .topbar-secondary { display: block; }
.topbar.nav-collapsed .topbar-mode-pills {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
}
/* Pills as rail-style nav items inside the dropdown (match .rail-item app-wide).
   Reset all the pill-specific chrome (shadow, tint, rounded top, centering). */
.topbar.nav-collapsed .mode-pill {
    display: block;
    width: 100%;
    padding: 6px 10px;
    border: none;
    border-left: 2px solid transparent;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    text-shadow: none;
    color: var(--text);
    text-align: left;
    justify-content: flex-start;
    font-size: var(--text-ui);
    font-weight: 500;
}
.topbar.nav-collapsed .mode-pill:hover {
    background: var(--link-bg);
    color: var(--link-text);
}
.topbar.nav-collapsed .mode-pill.is-active {
    background: color-mix(in srgb, var(--link-bg) 55%, transparent 45%);
    color: var(--link-text);
    border-left-color: var(--border-accent);
}
/* Phone only: in the collapsed (burger) dropdown, show just the modes with a phone-optimized view.
   MUST be scoped to .nav-collapsed: shared_ui.js updateNavCollapse() measures pill overflow with
   nav-collapsed temporarily REMOVED (inline layout), so the 5 hidden pills have to still be present
   during that measurement — otherwise the shorter row wouldn't overflow and the burger would never
   trigger (the trigger point must stay the full pill set hitting the edge, NOT the thinned set).
   Add `mode-pill--phone-ready` to a pill (topbar.html) as each remaining mode gets its phone pass.
   Tablet (nav-collapsed but not phone-mode) keeps every mode in its dropdown. */
html[data-phone-mode] .topbar.nav-collapsed .topbar-mode-pills .mode-pill:not(.mode-pill--phone-ready) {
    display: none;
}
/* topbar needs positioning context for the absolute dropdown */
.topbar { position: relative; }

/* Collapsible search: icon + input that expands rightward on toggle. */
.topbar-search {
    display: flex;
    align-items: center;
    min-width: 0;
}
.topbar-search { position: relative; }
.topbar-search .top-search-wrap {
    width: 0;
    overflow: hidden;
}
/* Expanded search always drops DOWN. Default: centered under the icon. */
.topbar-search.is-expanded .top-search-wrap {
    position: absolute;
    top: calc(100% + 4px);
    left: 50%;
    transform: translateX(-50%);
    width: min(190px, calc(100vw - 32px));   /* fit the "Search current page" placeholder */
    overflow: visible;
    z-index: 60;
}
/* Near the right edge → anchor to the right (drops down-and-left). */
.topbar-search.is-expanded.is-down-left .top-search-wrap {
    left: auto;
    right: 0;
    transform: none;
}
/* Phone: top-align the expanded search with the header's bottom edge (like the burger menu) —
   anchored absolute under the small centered search icon, it otherwise sits higher than the
   header bottom. Viewport-fixed, full-width sheet flush to --topbar-h (matches the other popups).
   Both selectors so it beats the .is-down-left variant too. Keyed off html[data-phone-mode] so the
   phone breakpoint has ONE home (the setPhoneMode matchMedia in shared_ui.js). */
html[data-phone-mode] .topbar-search.is-expanded .top-search-wrap,
html[data-phone-mode] .topbar-search.is-expanded.is-down-left .top-search-wrap {
    position: fixed;
    top: var(--topbar-h, 56px);
    left: 0;
    right: 0;
    width: auto;
    transform: none;
}
/* Compact inline input (override the old standalone-box padding/shadow). */
.topbar-search .top-search-input {
    padding: 4px 26px 4px 10px;   /* +~6px height vs the 1px compact default */
    box-shadow: var(--shadow);    /* standard light shadow for small objects (matches its size) */
    font-size: var(--text-sm);
    min-width: 0;
    box-sizing: border-box;   /* keep padding INSIDE the wrap width so the placeholder isn't clipped */
}
/* Phone: 16px font on the search input prevents iOS auto-zoom-on-focus (which never restores the
   zoom on blur — the user is left zoomed in). 16px is the exact iOS no-zoom threshold. Keyed off
   html[data-phone-mode] so the phone breakpoint has ONE home (shared_ui.js setPhoneMode). */
html[data-phone-mode] .topbar-search .top-search-input { font-size: 16px; }

.topbar-search-slot {
    /* Flexible so the top row can shrink instead of forcing a page-wide minimum
       (fixed 240px + nowrap title row was creating a right-side gap ~730px down).
       Shrinks from 240 toward 120 as space tightens. */
    flex: 1 1 auto;
    display: flex;
    min-width: 120px;
    width: auto;
    max-width: 240px;
    margin-left: auto;
}

/* ── Weather widget + popup ───────────────────────────────────────────── */

.topbar-weather[hidden] { display: none !important; }

.topbar-weather {
    position: relative;
    display: flex;
    align-items: stretch;
    width: auto;   /* content-sized — narrower than radio; only shares the HEIGHT token */
    min-width: 0;
}

/* ── Compact pill widget ── */

.weather-widget-btn {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    gap: 1px;
    width: auto;               /* content-sized */
    height: var(--widget-h);   /* fixed height — identical to radio card */
    box-sizing: border-box;
    padding: 4px 5px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background:
        radial-gradient(circle at top left, rgba(16, 75, 140, 0.16), transparent 40%),
        linear-gradient(145deg, rgba(255,255,255,0.88), rgba(241,243,244,0.78));
    cursor: pointer;
    color: var(--text);
    font-family: inherit;
    overflow: visible;
    transition: border-color 0.15s;
    box-shadow: 2px 5px 8px rgba(0,0,0,0.65), 1px 2px 3px rgba(0,0,0,0.5);
    backdrop-filter: blur(12px);
}

html[data-theme="dark"] .weather-widget-btn {
    background:
        radial-gradient(circle at top left, rgba(138, 180, 255, 0.16), transparent 40%),
        linear-gradient(145deg, rgba(24,32,42,0.84), rgba(36,48,65,0.74));
}

@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] .weather-widget-btn {
        background:
            radial-gradient(circle at top left, rgba(138, 180, 255, 0.16), transparent 40%),
            linear-gradient(145deg, rgba(24,32,42,0.84), rgba(36,48,65,0.74));
    }
}


.weather-widget-btn:hover {
    border-color: var(--border-accent);
}

.weather-widget-hd {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: 5px var(--space-sm);
    border-bottom: 1px solid var(--border);
    border-radius: var(--radius) var(--radius) 0 0;
    flex: 0 0 auto;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--muted);
    background: color-mix(in srgb, var(--border) 55%, var(--panel-soft) 45%);
    text-shadow: var(--text-shadow-header);
    box-shadow: var(--shadow-header);
}

.weather-widget-time {
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    color: #fff;
    opacity: 0.7;
    line-height: 1;
    flex: 0 0 auto;
}

/* Compact 2-row widget rows: city|icon (top), temp|rain% (bottom). Centered. */
.weather-widget-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-width: 0;
    line-height: 1;
}

.weather-widget-city {
    font-size: var(--text-sm);
    font-weight: 700;
    line-height: 1;
    color: var(--radio-accent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.weather-widget-emoji {
    font-size: 1.05rem;
    line-height: 1;
    flex: 0 0 auto;
}

.weather-widget-wind-arrow {
    font-size: 1.1rem;
    line-height: 0;
    vertical-align: middle;
    color: #5ba3f5;
}

.weather-widget-icon-row {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    flex: 0 0 auto;
}

.weather-widget-icon {
    width: 38px;
    height: 38px;
    flex: 0 0 auto;
}

.weather-widget-precip {
    font-size: var(--text-xs);
    font-weight: 600;
    color: #5ba3f5;
    line-height: 1;
}

.weather-widget-body {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--space-sm);
    gap: var(--space-sm);
}

.weather-widget-left {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    justify-content: center;
    padding-right: var(--space-sm);
    border-right: 1px solid rgba(255, 255, 255, 0.12);
}

.weather-widget-temp {
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
}

.weather-widget-cond {
    font-size: var(--text-sm);
    opacity: 0.78;
    line-height: 1;
}

.weather-widget-range {
    font-size: var(--text-sm);
    opacity: 0.65;
    line-height: 1;
}

.weather-widget-right {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    min-width: 0;
}

.weather-widget-stat {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-xs);
}

.weather-widget-stat-label {
    font-size: var(--text-xs);
    opacity: 0.6;
    flex: 0 0 auto;
}

.weather-widget-stat-val {
    font-size: var(--text-sm);
    font-weight: 600;
    text-align: right;
    white-space: nowrap;
}


/* ── Fallback "set location" (no data state) ── */

.weather-display {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    font-size: var(--text-sm);
    color: rgba(255, 255, 255, 0.78);
    white-space: nowrap;
}

.weather-city-btn {
    font: inherit;
    font-size: var(--text-sm);
    background: none;
    border: none;
    cursor: pointer;
    color: rgba(255, 255, 255, 0.78);
    padding: 0;
    text-decoration: underline;
    text-underline-offset: 2px;
    text-decoration-color: rgba(255, 255, 255, 0.28);
    transition: color 0.12s;
}

.weather-city-btn:hover { color: rgba(255, 255, 255, 1); }

.weather-locating,
.weather-error {
    font-size: var(--text-xs);
    color: rgba(255, 255, 255, 0.55);
    padding: 2px 0;
    text-align: center;
}

/* Fallback inline editor (no-data state only) */
.weather-editor {
    position: absolute;
    top: 0;
    /* Open to the LEFT: right-align to the widget's right edge so the ~220px form grows leftward
       instead of centering — centering pushed its right half over the radio widget (which sits
       directly to the right of weather). */
    right: 0;
    z-index: 70;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px var(--space-sm);
    border-radius: var(--radius);
    background: rgba(10, 16, 26, 0.88);
    border: 1px solid rgba(255, 255, 255, 0.18);
    backdrop-filter: blur(10px);
    white-space: nowrap;
}

.weather-editor-input {
    width: 140px;
    padding: 4px 8px;
    border-radius: var(--radius-sm);
    border: 1px solid rgba(255, 255, 255, 0.35);
    background: rgba(255, 255, 255, 0.12);
    color: rgba(255, 255, 255, 0.95);
    font: inherit;
    font-size: var(--text-xs);
    outline: none;
}

.weather-editor-input::placeholder { color: rgba(255, 255, 255, 0.42); }
.weather-editor-input:focus { border-color: rgba(255, 255, 255, 0.6); background: rgba(255, 255, 255, 0.18); }

.weather-editor-go,
.weather-editor-cancel {
    font: inherit;
    font-size: var(--text-xs);
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: var(--radius-sm);
    color: rgba(255, 255, 255, 0.85);
    cursor: pointer;
    padding: 4px 9px;
    transition: background 0.12s;
}

.weather-editor-go:hover,
.weather-editor-cancel:hover { background: rgba(255, 255, 255, 0.24); }

/* ── Weather popup ── */

.weather-popup {
    position: fixed;
    inset: 0;
    z-index: 1150;
    pointer-events: none;
}

.weather-popup-backdrop {
    position: absolute;
    inset: 0;
    pointer-events: auto;
}

.weather-popup-panel {
    position: absolute;
    pointer-events: auto;
    width: max-content;
    min-width: 380px;
    max-width: calc(100vw - 24px);
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.97);
    box-shadow: var(--shadow-float);   /* shared popover shadow (theme-aware via the token) */
    backdrop-filter: blur(20px);
    overflow: hidden;
    color: var(--text);
    max-height: 0;
}

.weather-popup-panel.is-open {
    max-height: 900px;
}

html[data-theme="dark"] .weather-popup-panel {
    background: rgba(18, 24, 34, 0.97);
}

@media (prefers-color-scheme: dark) {
    html[data-theme="auto"] .weather-popup-panel {
        background: rgba(18, 24, 34, 0.97);
    }
}

.weather-popup-hd {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-md) var(--space-sm);
    border-bottom: 1px solid var(--border);
}

/* "Now" details — today's stats relocated out of the compact widget. Two-column
   grid of label/value rows (.weather-widget-stat reused), above the forecast. */
.weather-popup-now {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-xs) var(--space-lg);
    padding: var(--space-sm) var(--space-md);
    border-bottom: 1px solid var(--border);
}

.weather-popup-loc-actions {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 0 0 auto;
}

.weather-popup-city {
    font-size: var(--text-ui);
    font-weight: 700;
    line-height: 1.2;
}

.weather-popup-change-btn,
.weather-popup-auto-btn {
    font: inherit;
    font-size: var(--text-xs);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    color: var(--link-text);
    text-align: left;
    text-decoration: underline;
    text-underline-offset: 2px;
    transition: opacity 0.12s;
}

.weather-popup-change-btn:hover,
.weather-popup-auto-btn:hover { opacity: 0.7; }

.weather-popup-auto-btn { color: var(--muted); }

.weather-popup-close {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 999px;
    border: none;
    background: var(--panel-soft);
    color: var(--muted);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s;
}

.weather-popup-close:hover { background: var(--link-bg); color: var(--link-text); }

/* Inline editor inside popup */
.weather-popup-editor {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: var(--space-sm) var(--space-md);
    border-bottom: 1px solid var(--border);
}

.weather-popup-editor[hidden] { display: none; }

.weather-popup-editor-input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 7px var(--space-sm);
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--panel-soft);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
    outline: none;
}

.weather-popup-editor-input:focus {
    border-color: var(--border-accent);
}

.weather-popup-editor-go,
.weather-popup-editor-cancel {
    flex: 0 0 auto;
    padding: 7px 11px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--link-bg);
    color: var(--link-text);
    font: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
    transition: background 0.12s;
}

.weather-popup-editor-go:hover,
.weather-popup-editor-cancel:hover { background: var(--link-bg-hover); }

/* Current conditions */
.weather-popup-current {
    padding: var(--space-md);
    display: flex;
    gap: var(--space-md);
    align-items: flex-start;
    border-bottom: 1px solid var(--border);
}

.weather-popup-big {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

.weather-popup-emoji-big {
    font-size: 2.8rem;
    line-height: 1;
}

.weather-popup-temps {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.weather-popup-temp-big {
    font-size: 2.0rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
}

.weather-popup-condition {
    font-size: var(--text-sm);
    color: var(--muted);
    line-height: 1;
}

.weather-detail-grid {
    flex: 1 1 auto;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px var(--space-sm);
    min-width: 0;
}

.weather-detail-item {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.weather-detail-label {
    font-size: var(--text-xs);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
}

.weather-detail-value {
    font-size: var(--text-sm);
    font-weight: 600;
}

/* 7-day forecast strip */
.weather-fc-strip {
    display: flex;
    padding: var(--space-sm) var(--space-md);
    gap: 4px;
}

.weather-fc-day {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 8px var(--space-sm);
    border-radius: var(--radius);
    background: var(--panel-soft);
    min-width: 64px;
    text-align: center;
}

.weather-fc-day:first-child {
    background: var(--link-bg);
}

.weather-fc-name {
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--muted);
}

.weather-fc-day:first-child .weather-fc-name { color: var(--link-text); }

.weather-fc-emoji {
    font-size: 1.4rem;
    line-height: 1;
}

.weather-fc-cond {
    font-size: var(--text-xs);
    color: var(--muted);
    line-height: 1.2;
    text-align: center;
}

.weather-fc-precip {
    font-size: var(--text-xs);
    color: #4a90d9;
    line-height: 1;
    min-height: 1em;
}

.weather-fc-range {
    display: flex;
    gap: 4px;
    align-items: baseline;
}

.weather-fc-hi {
    font-size: var(--text-sm);
    font-weight: 700;
}

.weather-fc-lo {
    font-size: var(--text-xs);
    color: var(--muted);
}

/* ── End weather ──────────────────────────────────────────────────────── */

.top-search-input {
    width: 100%;
    padding: 12px 58px 12px 14px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: rgba(255,255,255,0.9);
    color: #10233f;
    caret-color: #10233f;
    -webkit-text-fill-color: #10233f;
    box-shadow: var(--shadow);   /* tokenized (was a hardcoded literal); topbar context restates it */
    font-size: var(--text-ui);
    backdrop-filter: blur(10px);
    opacity: 1;
}

.top-search-input::placeholder {
    color: rgba(16, 35, 63, 0.62);
    -webkit-text-fill-color: rgba(16, 35, 63, 0.62);
}

/* X clear button — sits left of the spyglass icon */
.top-search-clear[hidden] {
    display: none;
}

.top-search-clear {
    position: absolute;
    right: 5px;
    top: 50%;
    transform: translateY(-50%);
    width: 16px;
    height: 16px;
    border: none;
    border-radius: 999px;
    background: rgba(16, 35, 63, 0.12);
    color: rgba(16, 35, 63, 0.7);
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    transition: background 0.12s, color 0.12s;
}

.top-search-clear:hover {
    background: var(--link-bg);
    color: var(--link-text);
}

.top-search-icon {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(16, 35, 63, 0.62);
    pointer-events: none;
    z-index: 1;
}

/* Search text highlight — applied by shared_ui.js across all modes */
mark.search-hl {
    background: rgba(249, 115, 22, 0.60);
    color: inherit;
    border-radius: 3px;
    box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.42);
}

/* ── Shared application tooltip — cursor-following, driven by shared_ui.js ──
   Use data-tooltip="..." on any element. No CSS overrides anywhere else. */
#app-tooltip {
    position: fixed;
    z-index: 9999;
    pointer-events: none;
    display: none;
    font-family: Arial, sans-serif;
    font-size: var(--text-sm);
    font-weight: 400;
    line-height: 1.45;
    color: var(--text);
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
    padding: var(--space-xs) var(--space-sm);
    max-width: 300px;
    white-space: pre-line;   /* wraps normally AND honors explicit \n (for multi-line tooltips) */
    word-wrap: break-word;
    text-align: left;
}
#app-tooltip.is-visible {
    display: block;
}

/* ── Autoplay notice banner ── */
@keyframes autoplay-notice-slide-up {
    from { transform: translateY(100%); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
}

@keyframes autoplay-notice-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(181, 136, 0, 0.5); }
    50%       { box-shadow: 0 0 0 6px rgba(181, 136, 0, 0); }
}

.autoplay-notice {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 800;
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    background: var(--panel);
    border: 2px solid #b58800;
    box-shadow: 0 -4px 16px rgba(0,0,0,0.18);
    font-size: var(--text-meta);
    animation: autoplay-notice-slide-up 0.35s ease-out;
}

.autoplay-notice-text {
    flex: 1;
    min-width: 0;
    line-height: var(--leading-body);
}

.autoplay-notice-steps {
    color: var(--muted);
    margin-top: 2px;
}

.autoplay-notice-dismiss {
    flex: 0 0 auto;
    padding: 6px var(--space-sm);
    border: 1px solid #b58800;
    border-radius: var(--radius);
    background: var(--panel-soft);
    color: #b58800;
    font-size: var(--text-meta);
    font-family: inherit;
    cursor: pointer;
    white-space: nowrap;
    font-weight: 700;
    animation: autoplay-notice-pulse 1.8s ease-in-out infinite;
}

.autoplay-notice-dismiss:hover {
    background: #b58800;
    color: #fff;
    border-color: #b58800;
    animation: none;
}

.topbar-secondary-tools {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 0 0 auto;
}

.topbar-secondary-tools:empty {
    display: none;
}

.theme-picker {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

.theme-label {
    font-size: var(--text-meta);
    color: var(--muted);
    font-weight: 600;
}


.settings-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: 0;
    border: none;
    background: transparent;
    color: var(--muted);
    box-shadow: none;
    cursor: pointer;
    backdrop-filter: none;
    filter: var(--drop-shadow-icon);
}

@keyframes radio-marquee-scroll {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(calc(-1 * var(--radio-marquee-distance, 0px)));
    }
}

.settings-btn:hover {
    background: transparent;
    color: var(--link-text);
}

.settings-btn-inline {
    margin-left: 2px;
}

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

/* Shared popover WIDTH (settings + contact use the same width; everything else stays per-panel). */
.settings-panel,
.contact-modal-panel {
    width: min(365px, calc(100vw - 24px));
}

.settings-panel {
    position: fixed;
    top: var(--settings-panel-top, 86px);
    left: var(--settings-panel-left, 22px);
    z-index: 1200;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-float);
    overflow: hidden;
}

/* On phones, the settings popover is a full-width sheet flush to the header's bottom edge (the
   same edge the burger menu drops from): full viewport width, top anchored to the live-measured
   --topbar-h, top corners squared (they meet the screen edges), bottom corners rounded so the
   bottom shadow still reads. Overrides the JS-set anchor top/left + the shared min(365px) width.
   Desktop keeps the anchored-under-icon look. (The contact panel gets the SAME treatment in
   contact-modal.css — its `left` lives in that file, which loads after base, so the override
   must be there too.) Keyed off html[data-phone-mode] so the phone breakpoint has ONE home (the
   setPhoneMode matchMedia in shared_ui.js). */
html[data-phone-mode] .settings-panel {
    top: var(--topbar-h, 56px);
    left: 0;
    right: 0;
    width: auto;
    transform: none;
    border-radius: 0 0 var(--radius) var(--radius);
}

.settings-panel .section-header {
    cursor: default;
}

.settings-panel .section-header:hover {
    color: var(--muted);
    background: color-mix(in srgb, var(--border) 55%, var(--panel-soft) 45%);
}

.settings-pill-row {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm);
}

.settings-row-label {
    flex-shrink: 0;
    font-size: var(--text-sm);
    font-weight: normal;
    color: var(--muted);
    margin-right: var(--space-xs);
}

.settings-pill {
    flex: 1;
    padding: var(--space-xs) 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--panel-soft);
    color: var(--muted);
    font: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
    text-decoration: none;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    text-align: center;
}

.settings-pill:hover {
    background: var(--link-bg);
    color: var(--link-text);
    border-color: transparent;
}

.settings-pill.is-active {
    background: var(--link-bg);
    color: var(--link-text);
    border-color: transparent;
    font-weight: 600;
}

.settings-debug-row {
    border-top: 1px solid var(--border);
}

.settings-font-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-sm);
}

.settings-font-label {
    flex-shrink: 0;
    color: var(--muted);
    line-height: 1;
    user-select: none;
}

.settings-font-label--sm { font-size: 10px; }
.settings-font-label--lg { font-size: 18px; }

.settings-font-slider {
    flex: 1;
    min-width: 0;
    cursor: pointer;
    accent-color: var(--link-text);
}

.nav-link {
    display: inline-block;
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius);
    background: var(--link-bg);
    color: var(--link-text);
    text-decoration: none;
    font-size: var(--text-ui);
    font-weight: 600;
    flex: 0 0 auto;
    box-shadow: var(--shadow-sm);
    transition: background 0.12s, color 0.12s, box-shadow 0.15s, transform 0.15s;
}

.nav-link:hover {
    background: var(--link-bg-hover);
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: 0 6px 16px rgba(11,87,208,0.24), 0 2px 4px rgba(0,0,0,0.08);
}

.nav-link.active {
    background: var(--button-bg);
    color: var(--button-text);
    box-shadow: var(--shadow-btn);
    text-shadow: var(--text-shadow-btn);
}


.refresh-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius);
    border: none;
    background: var(--button-bg);
    color: var(--button-text);
    text-decoration: none;
    font-size: var(--text-ui);
    font-weight: 600;
    flex: 0 0 auto;
    cursor: pointer;
    box-shadow: var(--shadow-btn);
    text-shadow: var(--text-shadow-btn);
    transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease, opacity 0.2s ease;
}

.refresh-btn:hover {
    background: var(--button-bg-hover);
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: 0 8px 20px rgba(11,87,208,0.30), 0 2px 5px rgba(0,0,0,0.10);
}

.refresh-btn.has-updates {
    animation: refresh-button-glow 1.75s ease-in-out infinite;
}

.refresh-btn.is-refreshing {
    animation: refresh-button-working 1.1s linear infinite;
    cursor: progress;
}

.refresh-btn:disabled {
    opacity: 0.94;
}

@keyframes refresh-button-glow {
    0%, 100% {
        box-shadow: 0 10px 26px rgba(11,87,208,0.16), 0 0 0 0 rgba(11,87,208,0.0);
        transform: translateY(0);
        filter: brightness(1);
    }
    50% {
        box-shadow: 0 14px 32px rgba(11,87,208,0.34), 0 0 0 4px rgba(11,87,208,0.12);
        transform: translateY(-1px);
        filter: brightness(1.09);
    }
}

@keyframes refresh-button-working {
    0% {
        box-shadow: 0 10px 26px rgba(11,87,208,0.22), 0 0 0 0 rgba(11,87,208,0.0);
    }
    50% {
        box-shadow: 0 14px 30px rgba(11,87,208,0.30), 0 0 0 6px rgba(11,87,208,0.10);
    }
    100% {
        box-shadow: 0 10px 26px rgba(11,87,208,0.22), 0 0 0 0 rgba(11,87,208,0.0);
    }
}

.title-refresh-btn {
    padding: 9px 13px;
}

.section-sub {
    color: var(--muted-2);
    margin-top: -4px;
    margin-bottom: var(--space-md);
    font-size: var(--text-ui);
}

/* ── Layout density ─────────────────────────────────────────
   Apply [data-density="compact"] or [data-density="relaxed"] to <html>.
   All spacing and type scale variables are overridden here so every
   component that references them scales consistently with zero
   per-component changes.
─────────────────────────────────────────────────────────── */
[data-density="compact"] {
    --space-xs:   4px;
    --space-sm:   8px;
    --space-md:   11px;
    --space-lg:   16px;
    --space-xl:   22px;
    --text-mini:  0.58rem;
    --text-xs:    0.62rem;
    --text-sm:    0.68rem;
    --text-meta:  0.76rem;
    --text-base:  0.80rem;
    --text-ui:    0.85rem;
    --text-body:  0.93rem;
    --text-panel: 0.96rem;
}

[data-density="relaxed"] {
    --space-xs:   8px;
    --space-sm:   12px;
    --space-md:   18px;
    --space-lg:   26px;
    --space-xl:   36px;
    --text-mini:  0.66rem;
    --text-xs:    0.74rem;
    --text-sm:    0.78rem;
    --text-meta:  0.88rem;
    --text-base:  0.90rem;
    --text-ui:    0.96rem;
    --text-body:  1.06rem;
    --text-panel: 1.10rem;
}

@keyframes chart-spin {
    to { transform: rotate(360deg); }
}

