/* SafeTrace component styles. Built entirely on the custom properties in tokens.css. */

*, *::before, *::after { box-sizing: border-box; }

html {
    -webkit-text-size-adjust: 100%;
    /* Also set on body below for the same 100vw-vs-scrollbar reason, but html needs its own copy
       too: a third-party-injected element (Google Auto Ads picks its own placement/creative
       automatically -- see App.razor -- so a video/rich-media unit wider than the viewport is
       outside this codebase's direct control) can otherwise still widen the document's scrollable
       area even when only body clips its own box, since body-only overflow-x:hidden doesn't
       contain content positioned relative to the initial containing block. */
    overflow-x: hidden;
}

body {
    margin: 0;
    background: var(--st-color-bg);
    color: var(--st-color-text);
    font-family: var(--st-font-family);
    font-size: var(--st-font-size-base);
    line-height: var(--st-line-height-base);
    /* Full-bleed elements (.st-hero, .st-full-bleed) size themselves off 100vw, which on most
       browsers includes the vertical scrollbar's width -- without this, that mismatch reintroduces
       its own few-pixel horizontal scrollbar on every page. */
    overflow-x: hidden;
}

/* Google Auto Ads (App.razor) injects its own ad units directly into the page with no container
   this codebase controls -- constrain whatever it inserts to the viewport width regardless of a
   creative's own native/intrinsic size, rather than letting one wide ad unit widen the whole page
   on mobile. */
ins.adsbygoogle, .google-auto-placed { max-width: 100% !important; overflow: hidden !important; }

h1, h2, h3, h4 { line-height: var(--st-line-height-tight); }
h1 { font-size: var(--st-font-size-3xl); margin: 0 0 var(--st-space-4); }
h2 { font-size: var(--st-font-size-2xl); margin: var(--st-space-6) 0 var(--st-space-3); }
h3 { font-size: var(--st-font-size-xl); margin: var(--st-space-5) 0 var(--st-space-2); }

a { color: var(--st-color-link); }
a:hover { color: var(--st-color-link-hover); }

/* Focus states -- visible everywhere, never suppressed */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, label:focus-visible, [tabindex]:focus-visible {
    outline: var(--st-focus-ring-width) solid var(--st-color-focus-ring);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* Skip link */
.skip-to-content-link {
    position: absolute;
    left: var(--st-space-2);
    top: -3rem;
    z-index: 1000;
    background: var(--st-color-primary);
    color: var(--st-color-text-inverse);
    padding: var(--st-space-2) var(--st-space-4);
    border-radius: var(--st-radius-sm);
    transition: top 0.15s ease-in-out;
}
.skip-to-content-link:focus { top: var(--st-space-2); }

/* App shell */
.safetrace-app { display: flex; flex-direction: column; min-height: 100vh; }
.safetrace-main { flex: 1; max-width: 1200px; margin: 0 auto; width: 100%; padding: var(--st-space-6) var(--st-space-4); }
.safetrace-main:focus { outline: none; }

/* Header -- bright, not a solid brand-color fill. Navy/teal is carried by the logo, the nav's
   hover/active state, and the "Get Alerts" CTA rather than by painting the whole bar. */
.safetrace-header {
    background: var(--st-color-surface-raised); color: var(--st-color-text);
    border-bottom: 1px solid var(--st-color-border);
    position: sticky; top: 0; z-index: 100;
}
.safetrace-header-inner {
    max-width: 1200px; margin: 0 auto; padding: var(--st-space-3) var(--st-space-4);
    min-height: 5.5rem;
    display: flex; align-items: center; justify-content: space-between; gap: var(--st-space-5);
}
.safetrace-brand { color: var(--st-color-text); font-weight: 700; font-size: var(--st-font-size-lg); text-decoration: none; }
.safetrace-brand-with-logo { display: inline-flex; align-items: center; }
.safetrace-brand-logo-chip { display: inline-flex; align-items: center; gap: var(--st-space-3); }
/* Mobile-first: smaller than the desktop mark (restored at 960px+ below) so the brand mark,
   nav toggle, and header padding all fit inside a phone-width viewport without overflowing --
   the header CTA is hidden at this width instead (see .safetrace-header-cta below) rather than
   shrunk, since it's already duplicated inside the mobile drawer (MobileNavigation.razor). */
.safetrace-brand-logo { display: block; height: 2rem; width: auto; }
/* The icon mark alone doesn't carry the wordmark (unlike the old logo image, which baked "Safe
   Trace" text into the PNG itself) -- this renders the site name as real text beside it instead,
   which also scales/wraps/localizes better than baking a three-word name into a raster image. */
.safetrace-brand-text { font-weight: 700; font-size: var(--st-font-size-xl); color: var(--st-color-text); white-space: nowrap; }

.safetrace-header-actions { display: flex; align-items: center; gap: var(--st-space-4); margin-left: auto; }
.safetrace-primary-nav { display: none; }
.safetrace-nav-list { list-style: none; display: flex; gap: var(--st-space-6); margin: 0; padding: 0; }
/* flex-shrink:0 on the item, white-space:nowrap on the link -- without both, a flex item holding
   multi-word text (e.g. "Health Alerts", "Near Me") has an automatic minimum width equal to just
   its LONGEST WORD, not the full phrase (a flex/grid-specific quirk: text that CAN wrap at a
   space is treated as shrinkable down to that word, unlike a single unbreakable word such as
   "Diseases", which can't shrink past its own full width and so never wraps). The desktop nav's
   items only need to be a few px narrower than their natural total width before this silently
   wraps exactly the two-word items onto a second line while every single-word item stays put --
   reported live, reproduced at every desktop width since .safetrace-header-inner's own 1200px cap
   means the available row width never actually grows past that regardless of window size. */
.safetrace-primary-nav .safetrace-nav-list > li { flex-shrink: 0; }
.safetrace-nav-list a {
    color: var(--st-color-text); text-decoration: none; font-size: var(--st-font-size-base);
    font-weight: 600; padding: var(--st-space-2) var(--st-space-1); display: inline-block;
    white-space: nowrap;
    border-bottom: 2px solid transparent; transition: border-color var(--st-transition-fast), color var(--st-transition-fast);
}
.safetrace-nav-list a:hover { color: var(--st-color-accent); border-bottom-color: var(--st-color-accent); text-decoration: none; }
.safetrace-nav-list a:focus-visible {
    outline: var(--st-focus-ring-width) solid var(--st-color-focus-ring); outline-offset: 2px; border-radius: var(--st-radius-sm);
}
/* Hidden in the collapsed mobile header -- it's already offered inside the mobile nav drawer
   (see MobileNavigation.razor's own "Get Alerts" link), and keeping both forced the header row
   wider than a phone viewport, pushing the hamburger toggle off-screen. Restored at 960px+
   below, once the drawer/toggle are themselves hidden in favor of the full desktop nav.
   Selector is the two classes together (0,0,2,0) rather than ".safetrace-header-cta" alone
   (0,0,1,0) -- the single-class version has the SAME specificity as .st-btn's own
   "display: inline-flex" rule further down this file, so that later, equal-specificity rule
   was silently winning and keeping the button visible (confirmed live: computed display stayed
   "flex" at a 360px viewport). Same category of bug as .st-hero-overlap's own comment above. */
.safetrace-header-cta.st-btn {
    display: none;
    flex-shrink: 0; padding: var(--st-space-3) var(--st-space-6);
    box-shadow: 0 2px 6px rgba(22, 50, 79, 0.25);
}

/* "Resources" dropdown: a native <details>/<summary> disclosure, not a hover-only menu -- it opens
   and closes with a click/Enter/Space and no JavaScript, same philosophy as the mobile drawer's
   checkbox toggle. Positioned as a popup only within the desktop header nav (guarded by the
   ".safetrace-primary-nav" ancestor selector); inside the mobile drawer it stays a plain
   in-flow disclosure, matching how the rest of that drawer already renders. */
.safetrace-nav-dropdown { position: relative; list-style: none; }
.safetrace-nav-dropdown summary {
    color: var(--st-color-text); font-size: var(--st-font-size-base); font-weight: 600; cursor: pointer;
    padding: var(--st-space-2) var(--st-space-1); list-style: none; white-space: nowrap;
}
.safetrace-nav-dropdown summary::-webkit-details-marker { display: none; }
.safetrace-nav-dropdown summary::after { content: " \25BE"; font-size: 0.7em; }
.safetrace-nav-dropdown summary:hover { color: var(--st-color-accent); }
.safetrace-nav-dropdown summary:focus-visible {
    outline: var(--st-focus-ring-width) solid var(--st-color-focus-ring); outline-offset: 2px; border-radius: var(--st-radius-sm);
}
.safetrace-nav-dropdown-menu { list-style: none; margin: var(--st-space-2) 0 0; padding: 0; }
.safetrace-nav-dropdown-menu a {
    display: block; padding: var(--st-space-2) var(--st-space-3); color: var(--st-color-text); text-decoration: none;
    font-size: var(--st-font-size-sm); font-weight: 500; border-radius: var(--st-radius-sm);
}
.safetrace-nav-dropdown-menu a:hover { background: var(--st-color-surface); color: var(--st-color-accent); }

/* Nested "Data Explorer" submenu (United States/Global) inside the Explore dropdown -- a second,
   plain <details>/<summary> disclosure, same JS-free philosophy as .safetrace-nav-dropdown above.
   Base (mobile/narrow) styling expands it in place, indented one level -- a side flyout has
   nowhere to go on a narrow drawer. The desktop media query below repositions it as a flyout to
   the right of "Data Explorer" instead, matching the outer Explore/About dropdown's own flyout
   treatment rather than pushing States/Companies/Brands down the list. */
.safetrace-nav-submenu { position: relative; }
.safetrace-nav-submenu > details > summary {
    color: var(--st-color-text); font-size: var(--st-font-size-sm); font-weight: 600; cursor: pointer;
    padding: var(--st-space-2) var(--st-space-3); list-style: none;
}
.safetrace-nav-submenu > details > summary::-webkit-details-marker { display: none; }
.safetrace-nav-submenu > details > summary::after { content: " \25BE"; font-size: 0.7em; }
.safetrace-nav-submenu > details > summary:hover { color: var(--st-color-accent); }
.safetrace-nav-submenu > details > ul { list-style: none; margin: 0; padding: 0 0 0 var(--st-space-3); }
.safetrace-nav-submenu > details > ul a {
    display: block; padding: var(--st-space-2) var(--st-space-3); color: var(--st-color-text); text-decoration: none;
    font-size: var(--st-font-size-sm); font-weight: 500; border-radius: var(--st-radius-sm);
}
.safetrace-nav-submenu > details > ul a:hover { background: var(--st-color-surface); color: var(--st-color-accent); }

@media (min-width: 960px) {
    .safetrace-primary-nav .safetrace-nav-dropdown-menu {
        position: absolute; top: 100%; left: 0; min-width: 12rem; margin: var(--st-space-1) 0 0;
        background: var(--st-color-surface-raised); border: 1px solid var(--st-color-border);
        border-radius: var(--st-radius-md); box-shadow: var(--st-shadow-card-hover); padding: var(--st-space-2);
    }

    .safetrace-primary-nav .safetrace-nav-submenu > details > ul {
        position: absolute; top: 0; left: 100%; min-width: 10rem; margin: 0 0 0 var(--st-space-1);
        background: var(--st-color-surface-raised); border: 1px solid var(--st-color-border);
        border-radius: var(--st-radius-md); box-shadow: var(--st-shadow-card-hover); padding: var(--st-space-2);
    }
}

.safetrace-mobile-nav-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2.75rem; height: 2.75rem; cursor: pointer; border-radius: var(--st-radius-sm);
}
.safetrace-mobile-nav-toggle-icon, .safetrace-mobile-nav-toggle-icon::before, .safetrace-mobile-nav-toggle-icon::after {
    display: block; width: 1.5rem; height: 2px; background: var(--st-color-text); position: relative;
}
.safetrace-mobile-nav-toggle-icon::before { content: ""; position: absolute; top: -6px; width: 1.5rem; }
.safetrace-mobile-nav-toggle-icon::after { content: ""; position: absolute; top: 6px; width: 1.5rem; }

/* Mobile nav drawer (checkbox-driven, works with zero JS) */
.safetrace-mobile-nav-checkbox { position: absolute; opacity: 0; pointer-events: none; }
.safetrace-mobile-nav-backdrop {
    position: fixed; inset: 0; background: rgba(10, 15, 20, 0.5);
    opacity: 0; pointer-events: none; transition: opacity 0.15s ease-in-out; z-index: 998;
}
.safetrace-mobile-nav-drawer {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(85vw, 320px);
    background: var(--st-color-surface-raised); box-shadow: var(--st-shadow-card);
    transform: translateX(100%); transition: transform 0.2s ease-in-out; z-index: 999;
    padding: var(--st-space-4); overflow-y: auto;
}
.safetrace-mobile-nav-checkbox:checked ~ .safetrace-mobile-nav-backdrop { opacity: 1; pointer-events: auto; }
.safetrace-mobile-nav-checkbox:checked ~ .safetrace-mobile-nav-drawer { transform: translateX(0); }
.safetrace-mobile-nav-drawer-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--st-space-4); }
.safetrace-mobile-nav-close { cursor: pointer; font-size: var(--st-font-size-xl); padding: var(--st-space-1) var(--st-space-2); }
.safetrace-mobile-nav-drawer .safetrace-nav-list { flex-direction: column; gap: var(--st-space-3); }
.safetrace-mobile-nav-drawer .safetrace-nav-list a { color: var(--st-color-text); }

/* Legal acknowledgment gate (LegalAcknowledgmentBanner.razor) -- server-rendered on every page,
   hidden here by default; legal-acknowledgment.js adds .st-legal-gate-visible after checking
   localStorage. Never gated behind display:none at the .st-legal-gate-backdrop/-panel level in a
   way that would also hide the real page content underneath -- only these two elements are
   affected, so a visitor (or a search engine) with JavaScript disabled, or before the check runs,
   still sees the actual page, just without this overlay on top of it. */
.st-legal-gate-backdrop {
    position: fixed; inset: 0; background: rgba(10, 15, 20, 0.6);
    opacity: 0; pointer-events: none; transition: opacity 0.15s ease-in-out; z-index: 1000;
}
.st-legal-gate {
    position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
    padding: var(--st-space-4); opacity: 0; pointer-events: none; z-index: 1001;
}
.st-legal-gate-backdrop.st-legal-gate-visible { opacity: 1; pointer-events: auto; }
.st-legal-gate.st-legal-gate-visible { opacity: 1; pointer-events: auto; }
.st-legal-gate:focus-visible { outline: none; } /* focus is moved here programmatically, not via keyboard nav -- the panel's own contents keep normal focus rings */
body.st-legal-gate-open { overflow: hidden; }

.st-legal-gate-panel {
    width: min(92vw, 560px); max-height: min(88vh, 720px); overflow-y: auto;
    background: var(--st-color-surface-raised); border: 1px solid var(--st-color-border);
    border-radius: var(--st-radius-lg); box-shadow: var(--st-shadow-card-hover);
    padding: var(--st-space-6); display: flex; flex-direction: column; gap: var(--st-space-3);
}
.st-legal-gate-panel h2 { margin: 0; font-size: var(--st-font-size-xl); }
.st-legal-gate-panel > p { margin: 0; color: var(--st-color-text); line-height: var(--st-line-height-relaxed); }

.st-legal-gate-links { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--st-space-2) var(--st-space-4); }
.st-legal-gate-links a { font-weight: 600; }

.st-legal-gate-version { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); margin: 0; }

.st-legal-gate-actions { display: flex; gap: var(--st-space-3); flex-wrap: wrap; margin-top: var(--st-space-2); }
.st-legal-gate-actions .st-btn { flex: 1 1 10rem; }

.st-legal-gate-footer {
    font-size: var(--st-font-size-xs); color: var(--st-color-text-muted); line-height: var(--st-line-height-relaxed);
    margin: 0; padding-top: var(--st-space-3); border-top: 1px solid var(--st-color-border);
}

@media (prefers-reduced-motion: reduce) {
    .st-legal-gate-backdrop, .st-legal-gate { transition: none; }
}

/* Full-page navigation-in-progress overlay (App.razor, nav-loading-indicator.js) -- shown the
   instant an internal link is clicked and hidden once Blazor's enhanced navigation finishes
   patching in the new page. z-index sits above the legal gate (1001) since it needs to cover
   absolutely everything, including that gate, on the rare chance both are ever visible at once. */
.st-nav-loading-overlay {
    position: fixed; inset: 0; z-index: 1100; display: flex; align-items: center; justify-content: center;
    background: var(--st-color-bg); opacity: 0; pointer-events: none; transition: opacity 0.15s ease-in-out;
}
.st-nav-loading-overlay.st-nav-loading-visible { opacity: 1; pointer-events: auto; }
.st-nav-loading-overlay img { width: 7.5rem; height: 7.5rem; }

@media (prefers-reduced-motion: reduce) {
    .st-nav-loading-overlay { transition: none; }
}

/* Footer */
.safetrace-footer { border-top: 1px solid var(--st-color-border); padding: var(--st-space-10) var(--st-space-4) var(--st-space-6); background: var(--st-color-surface); }
/* No max-width cap, matching the homepage's own .st-full-bleed-inner convention: the footer
   already sits outside .safetrace-main (see SafeTraceMainLayout.razor) so its background was
   always edge-to-edge -- this makes the content genuinely fill that width too, instead of
   centering a narrower 1200px column inside it. .safetrace-footer's own padding already handles
   the horizontal gutter, so nothing extra is needed here. */
.safetrace-footer-brand { display: flex; flex-direction: column; align-items: flex-start; gap: var(--st-space-3); margin-bottom: var(--st-space-8); }
.safetrace-footer-brand .safetrace-brand-logo { height: 2.25rem; }
.safetrace-footer-brand .safetrace-brand-text { font-size: var(--st-font-size-lg); }
.safetrace-footer-brand-text { margin: 0; max-width: 42ch; color: var(--st-color-text-muted); font-size: var(--st-font-size-base); line-height: var(--st-line-height-relaxed); }
.safetrace-footer-columns {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--st-space-6) var(--st-space-5);
    margin-bottom: var(--st-space-8);
}
.safetrace-footer-column h2 {
    font-size: var(--st-font-size-sm); text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--st-color-text-muted); margin: 0 0 var(--st-space-3); font-weight: 700;
}
.safetrace-footer-column ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--st-space-3); }
.safetrace-footer-column a { color: var(--st-color-text); font-size: var(--st-font-size-base); text-decoration: none; }
.safetrace-footer-column a:hover { color: var(--st-color-accent); text-decoration: underline; }
.safetrace-footer-disclaimer, .safetrace-footer-copyright {
    color: var(--st-color-text-muted); font-size: var(--st-font-size-sm); line-height: var(--st-line-height-relaxed);
    max-width: 60ch; margin-left: auto; margin-right: auto; text-align: center;
}
.safetrace-footer-disclaimer { margin-top: 0; margin-bottom: var(--st-space-4); padding-top: var(--st-space-6); border-top: 1px solid var(--st-color-border); }
.safetrace-footer-copyright { font-size: var(--st-font-size-xs); }

@media (min-width: 600px) {
    .safetrace-footer-columns { grid-template-columns: repeat(4, 1fr); }
}

/* Cards */
.st-card {
    background: var(--st-color-surface-raised); border: 1px solid var(--st-color-border);
    border-radius: var(--st-radius-lg); box-shadow: var(--st-shadow-card); padding: var(--st-space-5);
    display: flex; flex-direction: column; gap: var(--st-space-2);
    position: relative;
}
.st-card-grid { display: grid; grid-template-columns: 1fr; gap: var(--st-space-5); align-items: stretch; }
.st-card-grid > * { height: 100%; }
/* Shared across every listing page's card grid (recalls, outbreaks, foods, diseases, companies,
   brands, states) -- single column at mobile widths, 2 once there's room to breathe, 3 once wide
   enough that a listing-page sidebar (e.g. the recall filter drawer) no longer crowds it. */
@media (min-width: 700px) {
    .st-card-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1300px) {
    .st-card-grid { grid-template-columns: repeat(3, 1fr); }
}

/* A record-type badge ("Recall", "Outbreak") distinct from StatusBadge's status labels --
   identifies what kind of card this is at a glance, before reading the title. */
.st-card-type-badge {
    display: inline-block; font-size: var(--st-font-size-xs); font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--st-color-primary); margin-bottom: var(--st-space-1);
}
/* Shared by RecallCard and OutbreakCard (both render their title as an h3 inside .st-card), so a
   smaller title here shortens cards on both the recall list and the Global outbreak-notice tab
   at once. Was --st-font-size-xl (1.375rem) -- long recall/outbreak titles wrapped to 3-4 lines
   at that size, making every card in a row as tall as its longest title. */
.st-card h3 { font-size: var(--st-font-size-base); font-weight: 600; margin: 0; color: var(--st-color-text); line-height: var(--st-line-height-relaxed); }
/* Caps a long government recall title/reason at N lines instead of stretching the card to fit
   it -- the full, untruncated text is always still available on the recall detail page. */
.st-card-title-clamp { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.st-card-reason-clamp { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
/* Content-based card tints -- almost imperceptible against the default white card, just enough
   to let a reader subconsciously tell a recall from an outbreak from an educational card while
   scanning a mixed section, without relying on strong color or heavy borders. Educational cards
   (foods, diseases) stay pure white, i.e. no modifier class at all. */
.st-card-recall { background: var(--st-color-card-recall); }
.st-card-outbreak { background: var(--st-color-card-outbreak); }
/* Active vs. Historical outbreak-card accents (Outbreaks page redesign) -- a left-edge stripe,
   colored to match the same badge tokens the record's own StatusBadge already uses (red for an
   active CDC/FDA investigation, blue for an NNDSS-derived monitoring signal, neutral grey for a
   NORS archive record), so the card border and its status badge never disagree with each other. */
.st-card-outbreak-active { border-left: 4px solid var(--st-color-alert-active-border); }
.st-card-outbreak-monitoring { border-left: 4px solid var(--st-color-provisional-border); }
.st-card-outbreak-historical { border-left: 4px solid var(--st-color-border); }
/* Compact secondary line under the title, e.g. a recall's Brand -- smaller than body text. */
.st-card-meta { font-size: var(--st-font-size-meta); color: var(--st-color-text-muted); margin: 0; }
/* Pins the CTA row to the bottom of every card in a grid row, regardless of how much summary
   text sits above it, so a row of cards lines up even when content length differs. */
.st-card-cta-row { margin-top: auto; padding-top: var(--st-space-2); position: relative; z-index: 1; }
/* The whole card is clickable via one stretched link: the CTA is the card's only real <a>
   (its visible text + a unique aria-label are unchanged), and this pseudo-element extends its
   clickable/tappable area to cover the entire card. This is deliberately a single link per
   card, not an extra overlay link, so screen-reader/keyboard users get one tab stop, not two.
   Any other genuinely-independent link inside a card (e.g. OutbreakCard's DataSourceBadge,
   which must keep pointing at the source, not the detail page) is lifted above the overlay
   via the :where() rule below, so it stays independently clickable. */
.st-card-cta-row a::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.st-card-cta-row a { position: relative; z-index: 2; }
.st-card :where(a, button):not(.st-card-cta-row a) { position: relative; z-index: 2; }
/* Keyboard focus on that one link elevates the whole card the same way mouse hover does, so
   the two interaction modes feel consistent. Progressive enhancement: browsers without :has()
   support still show the link's own native focus ring -- nothing breaks, it's just less showy. */
.st-card:has(.st-card-cta-row a:focus-visible) { box-shadow: var(--st-shadow-card-hover); }

/* Buttons */
.st-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--st-space-2);
    border-radius: var(--st-radius-sm); border: 1px solid transparent; cursor: pointer;
    font-size: var(--st-font-size-sm); font-weight: 600; padding: var(--st-space-3) var(--st-space-5);
    text-decoration: none; line-height: 1; min-height: 2.75rem;
}
/* Primary action buttons are teal (the site's "make it happen" color) sitewide -- navy stays
   reserved for the hero gradient and other brand-identity accents (timeline dots, pagination,
   metric values) that this palette pass didn't touch. .st-btn-accent is kept as an alias so
   existing markup referencing either class renders identically. */
.st-btn-primary, .st-btn-accent { background: var(--st-color-accent); color: var(--st-color-text-inverse); box-shadow: var(--st-shadow-glow-accent); }
.st-btn-primary:hover, .st-btn-accent:hover, .st-btn-primary:focus-visible, .st-btn-accent:focus-visible {
    background: var(--st-color-accent-hover); color: var(--st-color-text-inverse); box-shadow: var(--st-shadow-glow-accent-strong);
}
.st-btn-secondary { background: var(--st-color-surface-raised); color: var(--st-color-text); border-color: var(--st-color-border); box-shadow: var(--st-shadow-glow-primary); }
.st-btn-secondary:hover, .st-btn-secondary:focus-visible { background: var(--st-color-surface); border-color: var(--st-color-primary); box-shadow: var(--st-shadow-glow-primary-strong); }
/* No resting glow -- a link-styled button has no background/border to glow around, so a
   permanent halo would just look like a rendering artifact on plain text. The glow only appears
   once it's actually acting like a button (hover/focus). */
.st-btn-link { background: transparent; color: var(--st-color-link); padding: 0; min-height: 0; }
.st-btn-link:hover, .st-btn-link:focus-visible { text-shadow: 0 0 8px rgba(11, 99, 206, 0.5); }
.st-btn-sm { padding: var(--st-space-2) var(--st-space-4); min-height: 2.25rem; font-size: var(--st-font-size-xs); }
.st-btn-lg { padding: var(--st-space-4) var(--st-space-8); min-height: 3.25rem; font-size: var(--st-font-size-base); }

/* Forms */
.st-field { display: flex; flex-direction: column; gap: var(--st-space-1); margin-bottom: var(--st-space-4); }
.st-field label { font-size: var(--st-font-size-sm); font-weight: 600; }
.st-field input, .st-field select, .st-field textarea {
    font: inherit; padding: var(--st-space-2) var(--st-space-3); border: 1px solid var(--st-color-border);
    border-radius: var(--st-radius-sm); background: var(--st-color-bg); color: var(--st-color-text);
}
.st-field-error { color: var(--st-color-alert-active-text); font-size: var(--st-font-size-xs); }

/* Badges */
.st-badge {
    display: inline-flex; align-items: center; gap: var(--st-space-1); border-radius: var(--st-radius-pill);
    padding: var(--st-space-1) var(--st-space-3); font-size: var(--st-font-size-xs); font-weight: 600;
    border: 1px solid transparent;
}
/* Colored by status, not severity -- a "Recall Issued"/active alert and an "Under Investigation"
   outbreak both look busy/attention-worthy but neither implies a health-risk level; "Resolved"
   (an outbreak with an end date, or a terminated/closed recall) reads as Archived -- a closed,
   inactive record -- so it deliberately uses the same muted/neutral treatment as .st-badge-neutral
   instead of the site's teal accent color, which would otherwise read as a positive/celebratory
   status rather than "this is done and filed away." */
.st-badge-active { background: var(--st-color-alert-active-bg); border-color: var(--st-color-alert-active-border); color: var(--st-color-alert-active-text); }
.st-badge-investigation { background: var(--st-color-status-investigation-bg); border-color: var(--st-color-status-investigation-border); color: var(--st-color-status-investigation-text); }
.st-badge-resolved { background: var(--st-color-surface); border-color: var(--st-color-border); color: var(--st-color-text-muted); }
.st-badge-provisional { background: var(--st-color-provisional-bg); border-color: var(--st-color-provisional-border); color: var(--st-color-provisional-text); }
.st-badge-neutral { background: var(--st-color-surface); border-color: var(--st-color-border); color: var(--st-color-text-muted); }
/* Monitoring: CDC-NNDSS-derived elevated-activity signal -- never a confirmed outbreak, so it
   deliberately reuses the same informational-blue tokens as .st-alert-info rather than the
   amber Investigation or red Active tokens, which would misread a surveillance trend as a
   confirmed active investigation. */
.st-badge-monitoring { background: var(--st-color-provisional-bg); border-color: var(--st-color-provisional-border); color: var(--st-color-provisional-text); }

/* AQI category badge (AqiCategoryBadge.razor) -- larger than a normal inline .st-badge since it's
   the hero element on the Air Quality detail page. Colored via the site's own --st-color-aqi-*
   tokens (tokens.css), not AirNow.gov's raw saturated hex -- Good/Moderate/Unhealthy additionally
   glow like the site's own buttons (border doubles as a light ambient border, not just a shadow),
   while Unhealthy for Sensitive Groups/Very Unhealthy/Hazardous stay solid: rising severity should
   read as more serious, never prettier. */
.st-aqi-badge { font-size: var(--st-font-size-lg); padding: var(--st-space-2) var(--st-space-4); gap: var(--st-space-2); border-width: 1px; border-style: solid; }
.st-aqi-badge-value { font-size: var(--st-font-size-xl); font-weight: 700; line-height: 1; }
.st-aqi-badge-label { font-weight: 600; }

.st-aqi-good { background: var(--st-color-aqi-good-bg); border-color: var(--st-color-aqi-good-border); color: var(--st-color-aqi-good-text); box-shadow: var(--st-shadow-glow-aqi-good); }
.st-aqi-moderate { background: var(--st-color-aqi-moderate-bg); border-color: var(--st-color-aqi-moderate-border); color: var(--st-color-aqi-moderate-text); box-shadow: var(--st-shadow-glow-aqi-moderate); }
.st-aqi-usg { background: var(--st-color-aqi-usg-bg); border-color: var(--st-color-aqi-usg-border); color: var(--st-color-aqi-usg-text); }
.st-aqi-unhealthy { background: var(--st-color-aqi-unhealthy-bg); border-color: var(--st-color-aqi-unhealthy-border); color: var(--st-color-aqi-unhealthy-text); box-shadow: var(--st-shadow-glow-aqi-unhealthy); }
.st-aqi-very-unhealthy { background: var(--st-color-aqi-very-unhealthy-bg); border-color: var(--st-color-aqi-very-unhealthy-bg); color: var(--st-color-aqi-very-unhealthy-text); }
.st-aqi-hazardous { background: var(--st-color-aqi-hazardous-bg); border-color: var(--st-color-aqi-hazardous-bg); color: var(--st-color-aqi-hazardous-text); }
.st-aqi-unknown { background: var(--st-color-aqi-unknown-bg); border-color: var(--st-color-aqi-unknown-border); color: var(--st-color-aqi-unknown-text); }

/* AQI gauge (AqiGauge.razor) -- an AirNow.gov-style semicircular dial, sitting directly above the
   existing .st-aqi-badge on the detail page's "Current observation" section rather than replacing
   it, since the badge's plain text remains the more screen-reader- and print-friendly value. */
.st-aqi-gauge { display: flex; flex-direction: column; align-items: center; gap: var(--st-space-1); margin: 0 0 var(--st-space-4); max-width: 20rem; }
.st-aqi-gauge-plot { position: relative; width: 100%; }
.st-aqi-gauge-svg { display: block; width: 100%; height: auto; }
.st-aqi-gauge-tick { position: absolute; transform: translate(-50%, -50%); font-size: 0.6875rem; font-variant-numeric: tabular-nums; color: var(--st-color-text-muted); }
.st-aqi-gauge-caption { display: flex; flex-direction: column; align-items: center; gap: var(--st-space-1); }
.st-aqi-gauge-value { font-size: var(--st-font-size-xl); font-weight: 700; line-height: 1; color: var(--st-color-text); }
.st-aqi-gauge-label { font-size: var(--st-font-size-sm); font-weight: 600; color: var(--st-color-text-muted); }

/* Recall-class severity badges (RecallCard, RecallSummary) -- reuses the exact same
   alert/investigation/resolved tokens already used for recall *status* elsewhere, so Class I
   (most severe) reads as alert, Class II as investigation/amber, Class III (least severe) as
   resolved/muted-green, rather than every class rendering as identical neutral gray. See
   RecallClassificationText.BadgeCssClass, the one place this mapping lives. */
.st-badge-recall-class-i { background: var(--st-color-alert-active-bg); border-color: var(--st-color-alert-active-border); color: var(--st-color-alert-active-text); }
.st-badge-recall-class-ii { background: var(--st-color-status-investigation-bg); border-color: var(--st-color-status-investigation-border); color: var(--st-color-status-investigation-text); }
.st-badge-recall-class-iii { background: var(--st-color-status-resolved-bg); border-color: var(--st-color-status-resolved-border); color: var(--st-color-status-resolved-text); }

/* Status Dot -- reuses the exact same status tokens as the badges above (New shares the site's
   own accent teal, the closest "green" in this palette, rather than introducing a new hue) so
   the two status-coding conventions never visually disagree with each other. */
.st-status-dot-group { display: inline-flex; align-items: center; gap: var(--st-space-2); font-size: var(--st-font-size-sm); font-weight: 600; color: var(--st-color-text-muted); }
.st-status-dot { display: inline-block; width: 0.6rem; height: 0.6rem; border-radius: 50%; flex-shrink: 0; }
.st-status-dot-new { background: var(--st-color-accent); }
.st-status-dot-updated { background: var(--st-color-provisional-border); }
.st-status-dot-archived { background: var(--st-color-text-muted); }
.st-status-dot-underinvestigation { background: var(--st-color-status-investigation-border); }
.st-status-dot-recallissued { background: var(--st-color-alert-active-border); }

/* Alerts */
.st-alert { border-radius: var(--st-radius-md); border: 1px solid; padding: var(--st-space-4); margin-bottom: var(--st-space-4); }
.st-alert-info { background: var(--st-color-provisional-bg); border-color: var(--st-color-provisional-border); color: var(--st-color-provisional-text); }
.st-alert-warning { background: var(--st-color-status-investigation-bg); border-color: var(--st-color-status-investigation-border); color: var(--st-color-status-investigation-text); }
.st-alert-danger { background: var(--st-color-alert-active-bg); border-color: var(--st-color-alert-active-border); color: var(--st-color-alert-active-text); }

/* Tables */
.st-table { width: 100%; border-collapse: collapse; font-size: var(--st-font-size-sm); }
.st-table caption { text-align: left; font-weight: 600; margin-bottom: var(--st-space-2); }
.st-table th, .st-table td { text-align: left; padding: var(--st-space-2) var(--st-space-3); border-bottom: 1px solid var(--st-color-border); }
.st-table th { color: var(--st-color-text-muted); font-weight: 600; }
.st-responsive-table-wrapper { overflow-x: auto; }

/* Metric cards */
.st-metric-card { text-align: left; }
a.st-metric-card { text-decoration: none; color: inherit; }
.st-metric-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--st-space-3); }
.st-metric-card .st-metric-value { font-size: var(--st-font-size-3xl); font-weight: 700; display: block; color: var(--st-color-primary); }
.st-metric-card .st-metric-label { color: var(--st-color-text-muted); font-size: var(--st-font-size-base); font-weight: 600; }
.st-metric-card .st-metric-context { color: var(--st-color-text-muted); font-size: var(--st-font-size-xs); }
/* Reuses the same up/down meaning as .st-trend-summary-change-up/-down: for recall counts, "up"
   (more recalls) is colored as alert, "down" (fewer) as resolved -- the opposite of a typical
   financial dashboard, deliberately, since more recalls is the worse outcome here. */
.st-metric-card .st-metric-card-trend { display: block; font-size: var(--st-font-size-sm); font-weight: 600; margin-top: var(--st-space-1); }
.st-metric-card .st-metric-card-trend-up { color: var(--st-color-alert-active-text); }
.st-metric-card .st-metric-card-trend-down { color: var(--st-color-status-resolved-text); }
.st-metric-card .st-metric-card-trend-flat { color: var(--st-color-text-muted); }
/* Click-to-expand explanation of what the trend figures actually compare (e.g. the exact date
   windows) -- native <details>, same no-JS-required disclosure pattern as .st-map-table-disclosure
   and .st-recall-faq-item, just styled as a small inline info toggle instead of a block section. */
.st-metric-trend-info { display: inline-block; margin-left: var(--st-space-1); vertical-align: middle; }
.st-metric-trend-info summary {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1rem; height: 1rem; border-radius: 50%; border: 1px solid currentColor;
    font-size: var(--st-font-size-xs); font-style: italic; font-weight: 700; line-height: 1;
    cursor: pointer; list-style: none; color: var(--st-color-text-muted);
}
.st-metric-trend-info summary::-webkit-details-marker { display: none; }
.st-metric-trend-info summary:hover, .st-metric-trend-info[open] summary { color: var(--st-color-accent); border-color: var(--st-color-accent); }
.st-metric-trend-info p {
    display: block; margin: var(--st-space-2) 0 0; font-weight: 400; font-size: var(--st-font-size-xs);
    color: var(--st-color-text-muted); white-space: normal;
}
.st-metric-card-icon {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 2.25rem; height: 2.25rem; border-radius: 50%;
}
.st-metric-card-icon .st-icon { width: 1.1rem; height: 1.1rem; }
.st-metric-card-icon-alert { background: var(--st-color-alert-active-bg); color: var(--st-color-alert-active-text); }
.st-metric-card-icon-neutral { background: var(--st-color-success-bg); color: var(--st-color-accent); }

/* Compact icon + name + count tile (FoodCard's Compact mode) -- a row layout, unlike the base
   .st-card's column layout, so this overrides flex-direction/gap/padding specifically. */
a.st-compact-card {
    flex-direction: row; align-items: center; gap: var(--st-space-3); padding: var(--st-space-3) var(--st-space-4);
    text-decoration: none; color: inherit;
}
.st-compact-card-icon {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 2.25rem; height: 2.25rem; border-radius: 50%;
    background: var(--st-color-success-bg); color: var(--st-color-accent);
}
.st-compact-card-icon .st-icon { width: 1.1rem; height: 1.1rem; }
.st-compact-card-text { display: flex; flex-direction: column; min-width: 0; }
.st-compact-card-name { font-weight: 600; }
.st-compact-card-meta { color: var(--st-color-text-muted); font-size: var(--st-font-size-sm); }
.st-compact-card-grid { gap: var(--st-space-3); }

/* Loading / empty / error states */
.st-state-panel { padding: var(--st-space-6) var(--st-space-4); text-align: center; color: var(--st-color-text-muted); }

/* Pagination */
.st-pagination { display: flex; gap: var(--st-space-2); list-style: none; padding: 0; margin: var(--st-space-5) 0; flex-wrap: wrap; }
.st-pagination a, .st-pagination span {
    display: inline-flex; min-width: 2.25rem; height: 2.25rem; align-items: center; justify-content: center;
    border-radius: var(--st-radius-sm); border: 1px solid var(--st-color-border); text-decoration: none; padding: 0 var(--st-space-2);
}
.st-pagination [aria-current="page"] { background: var(--st-color-primary); color: var(--st-color-text-inverse); border-color: var(--st-color-primary); }

/* Filter chips */
.st-filter-chips { display: flex; flex-wrap: wrap; gap: var(--st-space-2); margin-bottom: var(--st-space-4); }
.st-filter-chip {
    display: inline-flex; align-items: center; gap: var(--st-space-2); background: var(--st-color-surface);
    border: 1px solid var(--st-color-border); border-radius: var(--st-radius-pill); padding: var(--st-space-1) var(--st-space-3);
    font-size: var(--st-font-size-xs);
}
.st-filter-chip button { border: none; background: none; cursor: pointer; color: var(--st-color-text-muted); font-size: var(--st-font-size-sm); line-height: 1; padding: 0; }

/* Breadcrumbs */
.st-breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--st-space-1); padding: 0; margin: 0 0 var(--st-space-4); font-size: var(--st-font-size-sm); }
.st-breadcrumbs li:not(:last-child)::after { content: "/"; margin-left: var(--st-space-1); color: var(--st-color-text-muted); }
.st-breadcrumbs [aria-current="page"] { color: var(--st-color-text-muted); }

/* Source references, last-updated, disclaimers, share links, pagination summary */
.st-source-reference-list { list-style: none; padding: 0; margin: var(--st-space-2) 0; display: flex; flex-wrap: wrap; gap: var(--st-space-3); }
.st-last-updated { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); }
.st-data-disclaimer { font-size: var(--st-font-size-xs); color: var(--st-color-text-muted); }
.st-reference-code { font-size: var(--st-font-size-xs); }
.st-share-links { list-style: none; display: flex; gap: var(--st-space-3); padding: 0; margin: var(--st-space-2) 0; }
.st-pagination-summary { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); }

/* Visually hidden (screen-reader only) */
.st-visually-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Filters */
.st-filter-panel { display: flex; flex-direction: column; gap: var(--st-space-2); }
.st-filter-panel-actions { display: flex; gap: var(--st-space-2); margin-top: var(--st-space-2); }
.st-date-range-inputs { display: flex; gap: var(--st-space-4); flex-wrap: wrap; }
.st-date-range-inputs label { display: flex; flex-direction: column; gap: var(--st-space-1); font-size: var(--st-font-size-sm); font-weight: 600; }

/* Every fieldset inside the filter panel (both the explicit .st-filter-group wrappers below and
   DateRangeFilter's own pre-existing <fieldset>) gets the same reset -- browsers otherwise draw a
   default border/padding/min-width on <fieldset> that looks inconsistent with every other filter
   here rendering as a plain, borderless .st-field. The legend becomes a small, uppercase section
   label instead, so related filters (recall details / location / date range / sort) read as
   distinct, scannable groups without boxing each one off. */
.st-filter-panel fieldset { border: 0; padding: 0; margin: 0 0 var(--st-space-5); min-width: 0; }
.st-filter-panel fieldset:last-child { margin-bottom: 0; }
.st-filter-panel legend {
    padding: 0; margin-bottom: var(--st-space-2); font-size: var(--st-font-size-xs); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em; color: var(--st-color-text-muted);
}

/* Global search */
.st-global-search-box { display: flex; gap: var(--st-space-2); max-width: 640px; }
.st-global-search-box input { flex: 1; font: inherit; padding: var(--st-space-2) var(--st-space-3); border: 1px solid var(--st-color-border); border-radius: var(--st-radius-sm); }
.st-global-search-box-lg input { padding: var(--st-space-4); border-radius: var(--st-radius-md); font-size: var(--st-font-size-lg); min-height: 3.5rem; }
.st-global-search-box-lg .st-btn { border-radius: var(--st-radius-md); padding: var(--st-space-3) var(--st-space-6); min-height: 3.5rem; font-size: var(--st-font-size-base); }
.st-search-suggestions { display: flex; flex-wrap: wrap; gap: var(--st-space-2); align-items: center; margin-top: var(--st-space-2); font-size: var(--st-font-size-sm); }
.st-search-suggestions-label { color: var(--st-color-text-muted); }
.st-search-results-list { list-style: none; padding: 0; }
.st-search-result-card { padding: var(--st-space-3) 0; border-bottom: 1px solid var(--st-color-border); }
.st-search-related-updates { margin-top: var(--st-space-2); font-size: var(--st-font-size-sm); }
.st-search-related-updates summary { cursor: pointer; color: var(--st-color-text-muted); }
.st-search-related-updates ul { list-style: none; padding: var(--st-space-2) 0 0 var(--st-space-4); margin: 0; }
/* Side-by-side columns, one per entity type -- auto-fit so 1 column on narrow viewports and as
   many as comfortably fit (typically 3-4) on a wide one, without a hardcoded column count. Items
   are align-start (not stretch) since each column's own card count varies -- columns are never
   forced to equal height. */
.st-search-highlights { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--st-space-4); align-items: start; margin-bottom: var(--st-space-6); }
.st-search-highlight-group h2 { font-size: var(--st-font-size-base); margin: 0 0 var(--st-space-2); }
.st-search-highlight-group .st-search-results-list { border: 1px solid var(--st-color-border); border-radius: var(--st-radius-sm); padding: 0 var(--st-space-3); }
.st-search-highlight-group .st-search-result-card:last-child { border-bottom: none; }

/* Definition grids, inline lists, timelines, related-content lists */
/* minmax(0, 1fr), not a bare 1fr -- a grid item's default min-width is its content's own
   min-content size, so a bare 1fr column still refuses to shrink below a long unwrapped value
   (e.g. an AqiCategoryBadge reading "Unhealthy for Sensitive Groups"), pushing the whole grid wider
   than the viewport on mobile. minmax(0, 1fr) lets the column -- and the text inside it -- actually
   wrap at the real container width instead. */
.st-definition-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--st-space-2) var(--st-space-4); margin: var(--st-space-3) 0; }
.st-definition-grid dt { font-weight: 600; color: var(--st-color-text-muted); }
.st-definition-grid dd { margin: 0; }
.st-inline-list { list-style: none; padding: 0; margin: var(--st-space-2) 0; display: flex; flex-wrap: wrap; gap: var(--st-space-2); }
.st-inline-list li { background: var(--st-color-surface); border-radius: var(--st-radius-sm); padding: var(--st-space-1) var(--st-space-2); font-size: var(--st-font-size-sm); }
.st-faq-list { margin: var(--st-space-3) 0; }
.st-faq-list dt { font-weight: 600; font-size: var(--st-font-size-lg); margin-top: var(--st-space-5); }
.st-faq-list dt:first-child { margin-top: 0; }
.st-faq-list dd { margin: var(--st-space-1) 0 0; color: var(--st-color-text-muted); }
.st-timeline { list-style: none; padding: 0; margin: var(--st-space-3) 0; border-left: 2px solid var(--st-color-border); }
.st-timeline li { padding: 0 0 var(--st-space-3) var(--st-space-4); position: relative; }
.st-timeline li::before { content: ""; position: absolute; left: -5px; top: 4px; width: 8px; height: 8px; border-radius: 50%; background: var(--st-color-primary); }
.st-related-list { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: var(--st-space-2); }
.st-related-list li { background: var(--st-color-surface); border: 1px solid var(--st-color-border); border-radius: var(--st-radius-pill); padding: var(--st-space-1) var(--st-space-3); font-size: var(--st-font-size-sm); }
.st-trend-summary-list { list-style: none; padding: 0; margin: var(--st-space-3) 0; display: flex; flex-direction: column; gap: var(--st-space-2); }
.st-trend-summary-list li { padding: var(--st-space-2) var(--st-space-3); background: var(--st-color-surface); border-radius: var(--st-radius-sm); font-size: var(--st-font-size-sm); }

/* Trend Explorer -- the selector row (Metric/Range/Grouping dropdowns + the View segmented
   control) stacks on mobile, sits in one row on wider screens. */
.st-trend-explorer-controls { display: flex; flex-direction: column; gap: var(--st-space-4); margin-bottom: var(--st-space-5); }
@media (min-width: 760px) {
    .st-trend-explorer-controls { flex-direction: row; align-items: flex-end; flex-wrap: wrap; }
    .st-trend-explorer-controls .st-field { flex: 1 1 10rem; margin-bottom: 0; }
    .st-trend-explorer-controls .st-trend-view-selector { flex: 0 0 auto; }
}

/* State Explore Map -- a single metric tab group above the map/list row, sized to its own
   content (three buttons) rather than stretching full-width like a multi-field control row. */
.st-state-explore-controls { max-width: max-content; margin-bottom: var(--st-space-4); }

/* Coverage Selector bar -- a full-width band (same .st-full-bleed 100vw trick as every other
   homepage section) so it reads as a distinct, deliberate strip right below the nav rather than
   floating in blank page background; its own .st-full-bleed-inner is centered so the pill sits
   in the middle of the bar instead of flush left. */
.st-coverage-selector-bar {
    background: var(--st-color-surface);
    border-bottom: 1px solid var(--st-color-border);
    padding: var(--st-space-3) 0;
}
/* Cancels .safetrace-main's own padding-top (var(--st-space-6) here, var(--st-space-8) at the
   960px breakpoint below) so the coverage bar -- now the first element on the page -- sits right
   under the header instead of leaving a visible gap equal to that padding-top. .st-hero used to
   carry this same cancellation back when it was the first element itself, before the coverage bar
   was added in front of it. */
.st-coverage-wrapper { margin-top: calc(-1 * var(--st-space-6)); }
.st-coverage-selector-bar .st-full-bleed-inner { display: flex; justify-content: center; }

/* The pill control itself -- a two-option segmented control (radio inputs styled as buttons).
   Pure CSS: the checked radio's sibling label gets the filled/active look via the adjacent-
   sibling selector, and :has() shows/hides the matching panel -- no JavaScript, no new Blazor
   circuit. Sized deliberately larger than a typical filter control since this is a primary,
   page-level choice, not an incidental one. */
.st-coverage-selector {
    display: inline-flex; align-items: center; gap: var(--st-space-2); flex-wrap: wrap;
    border: 2px solid var(--st-color-border); border-radius: var(--st-radius-pill);
    padding: var(--st-space-2); background: var(--st-color-bg); box-shadow: var(--st-shadow-card, 0 2px 6px rgba(0,0,0,0.08));
}
.st-coverage-selector-label { font-weight: 700; font-size: var(--st-font-size-lg); color: var(--st-color-text-muted); padding: 0 var(--st-space-3); }
.st-coverage-option {
    display: inline-flex; align-items: center; gap: var(--st-space-2); cursor: pointer;
    padding: var(--st-space-3) var(--st-space-8); border-radius: var(--st-radius-pill);
    font-weight: 700; font-size: var(--st-font-size-lg); color: var(--st-color-text);
    transition: background-color var(--st-transition-fast), color var(--st-transition-fast);
}
.st-flag-icon { width: 1.4em; height: auto; flex-shrink: 0; border-radius: 2px; }
.st-coverage-radio:checked + .st-coverage-option { background: var(--st-color-accent); color: var(--st-color-text-inverse); box-shadow: var(--st-shadow-glow-accent-strong); }
.st-coverage-radio:focus-visible + .st-coverage-option { outline: 3px solid var(--st-color-accent); outline-offset: 2px; }

@media (max-width: 480px) {
    .st-coverage-selector-bar .st-full-bleed-inner { display: block; }
    .st-coverage-selector { display: flex; flex-direction: column; align-items: stretch; width: 100%; }
    .st-coverage-selector-label { text-align: center; padding: 0 0 var(--st-space-2); }
    .st-coverage-option { justify-content: center; }

    /* GlobalSearchBox's input+button flex row never wraps on its own -- at this width the Large
       variant's button (extra padding plus the longer "Search Public Health Trace" label) no
       longer fits beside the input, and the whole page scrolled sideways instead of the button
       shrinking or dropping to its own line. Stacking fixes every GlobalSearchBox instance (hero,
       /search, /not-found), not just the homepage. */
    .st-global-search-box { flex-direction: column; }
    .st-global-search-box-lg .st-btn { padding: var(--st-space-3) var(--st-space-4); font-size: var(--st-font-size-sm); min-height: 3rem; }
}

.st-coverage-panel-global { display: none; }
.st-coverage-wrapper:has(#coverage-global:checked) .st-coverage-panel-us { display: none; }
.st-coverage-wrapper:has(#coverage-global:checked) .st-coverage-panel-global { display: block; }

.st-segmented-control { display: inline-flex; border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); overflow: hidden; }
.st-segmented-control-option {
    font: inherit; font-size: var(--st-font-size-sm); font-weight: 600; padding: var(--st-space-2) var(--st-space-4);
    border: none; background: var(--st-color-surface); color: var(--st-color-text-muted); cursor: pointer;
    border-left: 1px solid var(--st-color-border);
}
.st-segmented-control-option:first-child { border-left: none; }
.st-segmented-control-option:hover { background: var(--st-color-bg); }
.st-segmented-control-option[aria-pressed="true"] { background: var(--st-color-primary); color: var(--st-color-text-inverse); }

/* Coverage tabs (e.g. /outbreaks' United States / Global) reuse the same segmented-control look
   as plain links (a real navigation, not a client-side view switch), so aria-current="page"
   marks the active one instead of aria-pressed. */
.st-segmented-control-option[aria-current="page"] { background: var(--st-color-primary); color: var(--st-color-text-inverse); }
.st-coverage-tabs { margin-top: var(--st-space-5); margin-bottom: var(--st-space-5); }

/* Health Alerts' secondary nav row (e.g. Recalls' All/Food/Drug) -- same segmented-control look,
   just indented and slightly smaller so it visibly reads as one level below the primary
   All Alerts/Recalls/Diseases row rather than a second, equal-weight nav. */
.st-segmented-control-secondary { margin-top: var(--st-space-2); font-size: 0.9em; margin-left: var(--st-space-3); }

/* Outbreaks page's own Active/Historical/Both map-view tabs -- same real-navigation segmented
   control as .st-coverage-tabs above (aria-current="page", not a client-side toggle), just at the
   shared control's normal compact size rather than the coverage tabs' larger pill styling. */
.st-map-view-tabs { margin: var(--st-space-3) 0 var(--st-space-4); }

/* Outbreak page's own United States/Global tabs -- scoped to .st-coverage-tabs specifically (not
   the shared .st-segmented-control-option base, which the Timeline/Disease/State/Table chart-view
   tabs also use and must stay compact) so this is a real, deliberate page-level choice: larger
   type, a visible flag/globe icon, and a stronger border than a typical filter control. */
.st-coverage-tabs { border-width: 2px; border-radius: var(--st-radius-pill); overflow: hidden; }
.st-coverage-tabs .st-segmented-control-option {
    display: inline-flex; align-items: center; gap: var(--st-space-2);
    font-size: var(--st-font-size-lg); font-weight: 700; padding: var(--st-space-3) var(--st-space-6);
}
.st-coverage-tabs .st-segmented-control-option span[aria-hidden="true"] { font-size: 1.4em; line-height: 1; }

/* A segmented-control tab that opens a flyout menu instead of navigating directly (Data
   Explorer's "United States" tab, whose two children -- Outbreaks and Recalls -- are real
   navigation links inside the flyout, rather than "United States" itself being a destination;
   "Global" sits alongside it as its own direct destination, with no dropdown of its own, since it
   only ever has one content type -- Outbreaks -- to show). Native <details>/<summary>, same
   zero-JS-required philosophy as the site's other nav dropdowns; nav-dropdown.js's existing
   "click outside/Escape closes it" behavior is reused via the same
   name="safetrace-nav-dropdown-group" grouping rather than duplicated. Every .st-segmented-control
   sets overflow:hidden by default (used to clip children into rounded corners), which would also
   clip this flyout invisible -- .st-tabs-with-dropdown is the general opt-out for ANY segmented
   control that has a dropdown attached (not just .st-coverage-tabs' pill-styled variant -- Health
   Alerts' plain segmented-control nav needs this exact same override), declared after the base
   rule above so it wins regardless of which other segmented-control class is also present. */
.st-tabs-with-dropdown { overflow: visible; }
.st-tabs-with-dropdown > .st-segmented-control-dropdown:first-child > summary {
    border-top-left-radius: var(--st-radius-pill); border-bottom-left-radius: var(--st-radius-pill);
}
.st-segmented-control-dropdown { position: relative; }
.st-segmented-control-dropdown > summary {
    list-style: none; cursor: pointer;
}
.st-segmented-control-dropdown > summary::-webkit-details-marker { display: none; }
.st-segmented-control-dropdown > summary::after { content: "\25BE"; font-size: 0.7em; margin-left: var(--st-space-1); }
.st-tabs-with-dropdown > .st-segmented-control-option:last-child {
    border-top-right-radius: var(--st-radius-pill); border-bottom-right-radius: var(--st-radius-pill);
}
.st-segmented-control-dropdown[open] > summary,
.st-segmented-control-dropdown > summary[aria-current="page"] { background: var(--st-color-primary); color: var(--st-color-text-inverse); }
.st-segmented-control-dropdown-menu {
    position: absolute; top: 100%; left: 0; z-index: 5; min-width: 10rem; margin: var(--st-space-1) 0 0;
    display: flex; flex-direction: column; gap: var(--st-space-1);
    background: var(--st-color-surface-raised); border: 1px solid var(--st-color-border);
    border-radius: var(--st-radius-md); box-shadow: var(--st-shadow-card-hover); padding: var(--st-space-2);
}
.st-segmented-control-dropdown-menu a {
    display: flex; align-items: center; gap: var(--st-space-2); padding: var(--st-space-2) var(--st-space-3);
    border-radius: var(--st-radius-sm); color: var(--st-color-text); text-decoration: none;
    font-size: var(--st-font-size-sm); font-weight: 500;
}
.st-segmented-control-dropdown-menu a:hover { background: var(--st-color-bg); color: var(--st-color-accent); }
.st-segmented-control-dropdown-menu a[aria-current="page"] { background: var(--st-color-primary); color: var(--st-color-text-inverse); }

/* Trend Explorer body: chart column noticeably wider than the summary column, matching the
   reference layout -- stacks to a single column below the breakpoint where a ~2:1 split would
   otherwise squeeze the chart unreadably narrow. */
.st-trend-explorer-body { display: grid; grid-template-columns: 1fr; gap: var(--st-space-6); align-items: start; }
@media (min-width: 900px) {
    .st-trend-explorer-body { grid-template-columns: 2fr 1fr; }
}

.st-trend-summary { background: var(--st-color-surface); border: 1px solid var(--st-color-border); border-radius: var(--st-radius-lg); padding: var(--st-space-5); }

/* Recall page's Timeline/Classification/Table chart-view tabs: same pure-CSS radio-driven
   :has() pattern as the US/Global coverage selector above, generalized from two states to
   three. The radios themselves are visually hidden (st-visually-hidden); their labels double
   as the visible segmented-control buttons via the ordinary for/id relationship, so no
   JavaScript or Blazor interactivity is needed just to switch tabs. */
.st-chart-view-tabs { display: inline-flex; margin-bottom: var(--st-space-5); }
/* The tabs interleave <input>/<label> pairs (input first, so the label is never :first-child),
   unlike the plain-<button> segmented controls elsewhere -- :first-of-type still correctly finds
   the first *label* among its label siblings, so only that one loses its left border. */
.st-chart-view-tabs .st-segmented-control-option:first-of-type { border-left: none; }
.st-chart-view-panel-classification, .st-chart-view-panel-table { display: none; }
.st-chart-view-wrapper:has(#data-explorer-recall-chart-view-classification:checked) .st-chart-view-panel-timeline { display: none; }
.st-chart-view-wrapper:has(#data-explorer-recall-chart-view-classification:checked) .st-chart-view-panel-classification { display: block; }
.st-chart-view-wrapper:has(#data-explorer-recall-chart-view-table:checked) .st-chart-view-panel-timeline { display: none; }
.st-chart-view-wrapper:has(#data-explorer-recall-chart-view-table:checked) .st-chart-view-panel-table { display: block; }

/* Outbreak page's own Timeline/Disease/State(-or-Country)/Table chart-view tabs -- same pattern
   as Recall's above, generalized from three states to four. Panel class names are shared
   (.st-chart-view-panel-table) since both pages' Table tab plays the identical "hide unless its
   own radio is checked" role; -disease and -state are Outbreak-only panel names. */
.st-chart-view-panel-disease, .st-chart-view-panel-state { display: none; }
.st-chart-view-wrapper:has(#data-explorer-outbreak-chart-view-disease:checked) .st-chart-view-panel-timeline { display: none; }
.st-chart-view-wrapper:has(#data-explorer-outbreak-chart-view-disease:checked) .st-chart-view-panel-disease { display: block; }
.st-chart-view-wrapper:has(#data-explorer-outbreak-chart-view-state:checked) .st-chart-view-panel-timeline { display: none; }
.st-chart-view-wrapper:has(#data-explorer-outbreak-chart-view-state:checked) .st-chart-view-panel-state { display: block; }
.st-chart-view-wrapper:has(#data-explorer-outbreak-chart-view-table:checked) .st-chart-view-panel-timeline { display: none; }
.st-chart-view-wrapper:has(#data-explorer-outbreak-chart-view-table:checked) .st-chart-view-panel-table { display: block; }

.st-chart-view-radio:checked + .st-segmented-control-option { background: var(--st-color-primary); color: var(--st-color-text-inverse); }
.st-chart-view-radio:focus-visible + .st-segmented-control-option { outline: 3px solid var(--st-color-accent); outline-offset: 2px; }

/* Homepage "Latest Recalls" Food/Drug tabs -- same pure-CSS radio-driven :has() pattern as the
   chart-view tabs above (reuses .st-chart-view-tabs/.st-chart-view-radio for the tab buttons
   themselves). Food is checked by default, so the existing homepage experience is unchanged for a
   visitor who never touches this control. */
.st-latest-recalls-panel-drug { display: none; }
.st-latest-recalls-wrapper:has(#latest-recalls-drug:checked) .st-latest-recalls-panel-food { display: none; }
.st-latest-recalls-wrapper:has(#latest-recalls-drug:checked) .st-latest-recalls-panel-drug { display: block; }

/* "View all X recalls" link + that panel's own search box, together above the card grid (matching
   the "View all"-above-the-cards convention every other homepage section already uses via
   .st-section-heading-row) -- wraps to its own line below the link on narrow viewports rather than
   squeezing the search input unreadably narrow. */
.st-latest-recalls-panel-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--st-space-4); flex-wrap: wrap; margin-bottom: var(--st-space-4); }
.st-latest-recalls-panel-toolbar .st-global-search-box { flex: 0 1 360px; margin: 0; }
.st-trend-summary-heading { margin: 0 0 var(--st-space-3); font-size: var(--st-font-size-lg); }
.st-trend-summary-change { display: flex; align-items: baseline; gap: var(--st-space-2); }
.st-trend-summary-change-up { color: var(--st-color-alert-active-text); }
.st-trend-summary-change-down { color: var(--st-color-status-resolved-text); }
.st-trend-summary-incomplete { color: var(--st-color-text-muted); font-style: italic; }
.st-trend-summary-legend { list-style: none; display: flex; gap: var(--st-space-4); padding: 0; margin: var(--st-space-4) 0 0; font-size: var(--st-font-size-xs); color: var(--st-color-text-muted); }
.st-trend-legend-swatch { display: inline-block; width: 0.625rem; height: 0.625rem; border-radius: 50%; margin-right: var(--st-space-1); vertical-align: middle; }
.st-trend-legend-complete { background: #0072B2; }
.st-trend-legend-incomplete { background: #E69F00; }

/* Hero -- vertical padding is ~15% taller than the base section-rhythm unit so the hero reads
   as a deliberately larger "stage," not just another section. On mobile/tablet the background
   is the plain CSS navy/teal gradient (kept simple and reliably legible at narrow widths). On
   desktop, the background switches to a photo that already has the same navy-to-teal gradient
   graded into it (a studio product shot, not a natural-light photo), so it blends into the
   section on its own -- no separate <img>, no absolute-positioned panel, no manual gradient-fade
   overlay needed, unlike the first version of this hero. image-set() picks the smaller WebP
   where supported, falling back to the plain background-image declaration (PNG) otherwise. */
/* Full-bleed breakout: .st-hero lives inside .safetrace-main (max-width: 1200px, centered), so a
   plain negative margin only cancels that parent's own padding -- it never reaches the actual
   viewport edge on a wide screen. calc(50% - 50vw) instead re-centers the element against the
   viewport itself, regardless of how constrained its ancestor is, without moving the hero out of
   @Body in markup (which every page shares via SafeTraceMainLayout). */
.st-hero {
    padding: var(--st-space-12) var(--st-space-4);
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    margin-bottom: var(--st-space-12);
    background: var(--st-gradient-hero); color: var(--st-color-hero-text);
}
/* width: 100% (not max-width: 1200px + auto margins) -- a centered inner box left a wide empty
   gutter of plain gradient before the icon on anything wider than ~1200px, since the full-bleed
   .st-hero background photo already spans the true 100vw viewport regardless. Only .st-hero's
   own horizontal padding now separates the icon from the actual left edge. */
.st-hero-inner { width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: var(--st-space-6); }
.st-hero-content { text-align: left; }
/* Decorative brand icon, stacked above the text on narrow screens (no room for a side-by-side
   column) and moved to its own column to the left of the text at the same 960px breakpoint the
   hero's background photo switches on. */
/* filter: drop-shadow (not box-shadow) -- the icon is a non-rectangular shield shape with real
   transparency around it, so a box-shadow would glow the image's rectangular bounding box
   instead of tracing the actual visible silhouette. */
.st-hero-icon { width: 288px; height: auto; flex-shrink: 0; filter: var(--st-glow-icon-accent); }
.st-hero h1 {
    color: var(--st-color-hero-text); margin: 0 0 var(--st-space-4); max-width: 20ch;
    font-size: var(--st-font-size-hero); line-height: var(--st-line-height-tight); font-weight: 700;
}
/* :not() exclusions matter here -- .st-hero-actions and .st-hero-meta are also <p> tags, and a
   bare ".st-hero p" selector (specificity 0,0,1,1) is MORE specific than their own single-class
   selectors (0,0,1,0), so it was silently winning and resetting their margin back to 0/24px no
   matter what was declared on those two rules -- the real reason repeated margin increases on
   .st-hero-actions never visibly changed anything. */
.st-hero p:not(.st-hero-actions):not(.st-hero-meta) { color: var(--st-color-hero-text-muted); font-size: var(--st-font-size-lg); max-width: 60ch; margin: 0 0 var(--st-space-6); line-height: var(--st-line-height-relaxed); }
.st-hero .st-global-search-box { margin: 0; max-width: 720px; }
.st-hero .st-global-search-box input { border-color: transparent; }

.st-hero-actions { display: flex; flex-wrap: wrap; justify-content: flex-start; gap: var(--st-space-3); margin: 1.875rem 0 0; }
/* Overrides the base .st-btn-secondary glow (navy) with the teal accent glow -- a navy glow is
   nearly invisible against this button's own dark navy/teal hero background. */
.st-hero-actions .st-btn-secondary { background: rgba(255, 255, 255, 0.08); color: var(--st-color-hero-text); border-color: rgba(255, 255, 255, 0.4); box-shadow: var(--st-shadow-glow-accent); }
.st-hero-actions .st-btn-secondary:hover, .st-hero-actions .st-btn-secondary:focus-visible { background: rgba(255, 255, 255, 0.16); box-shadow: var(--st-shadow-glow-accent-strong); }
.st-hero-meta { font-size: var(--st-font-size-sm); color: var(--st-color-hero-text-muted); margin: var(--st-space-5) 0 0; }

@media (min-width: 960px) {
    .st-hero-inner { flex-direction: row; align-items: center; gap: var(--st-space-10); }
    .st-hero-icon { width: 480px; }
    .st-hero-content { max-width: 30rem; }
    .st-hero {
        /* Absolute paths (leading slash) are required here: inside a stylesheet, a relative
           url() resolves against the CSS file's own location (/_content/SafeTrace.RazorLib/css/),
           not the page's -- a bare "_content/..." path silently 404s by resolving to a doubled-up
           .../css/_content/... URL. This is why the image never rendered in any real browser
           despite the file itself being correct and reachable at its intended absolute path. */
        /* background-position-y: top, not center -- the hero is now full-bleed (spans the full
           viewport width) and much wider relative to the image's own aspect ratio than before, so
           cover scales the image up until it fills that width, then crops whatever height
           overflows. Anchoring vertical center split that overflow evenly top and bottom, cutting
           into the produce sitting at the top of the source photo; anchoring top instead crops
           only from the bottom (plain grocery bag/background), leaving the produce intact. */
        background: url("/_content/SafeTrace.RazorLib/images/hero-grocery-bg.png") right top / cover no-repeat;
        background-image: image-set(
            url("/_content/SafeTrace.RazorLib/images/hero-grocery-bg.webp") type("image/webp") 1x,
            url("/_content/SafeTrace.RazorLib/images/hero-grocery-bg.png") type("image/png") 1x
        );
        background-position: right top; background-size: cover; background-repeat: no-repeat;
    }

    /* Global coverage swaps the hero photo for a world/pathogen graphic -- same right-top/cover
       treatment as the U.S. grocery photo, just a different source image, so the layout rules
       above never need touching. */
    .st-coverage-wrapper:has(#coverage-global:checked) .st-hero {
        background: url("/_content/SafeTrace.RazorLib/images/hero-global-bg.png") right top / cover no-repeat;
        background-image: image-set(
            url("/_content/SafeTrace.RazorLib/images/hero-global-bg.webp") type("image/webp") 1x,
            url("/_content/SafeTrace.RazorLib/images/hero-global-bg.png") type("image/png") 1x
        );
        background-position: right top; background-size: cover; background-repeat: no-repeat;
    }
}

/* Slow color-resolve reveal for the hero background photo (only exists at the >=960px breakpoint
   above, where there's an actual photo instead of a flat gradient): the photo starts very light
   and washed out, then slowly settles into its real color over a full second. (An earlier
   stepped-blur "pixelate" attempt wasn't visibly registering as intended, so this replaces it
   with a plainer, slower fade that's easier to actually see.)
   The opacity fade-in is deliberately much SHORTER (150ms) than the color resolve (900ms), both
   starting together -- running them the same length left the section still mostly transparent
   (opacity climbing from 0) for most of the resolve's own run, which visibly washed the effect
   out. Finishing the opacity fade quickly means the section is already fully opaque while the
   color resolve keeps visibly playing out afterward, so it actually reads as its own effect.
   Combined with .st-hero's own fade-in (declared earlier, in the width-unscoped reduced-motion
   block) via the animation shorthand here -- shorthand replaces the whole animation list, so both
   are named together in this one, later, more specific rule. */
@media (min-width: 960px) and (prefers-reduced-motion: no-preference) {
    .st-hero { animation: st-hero-bg-fade-in 150ms ease both, st-hero-bg-resolve 900ms ease both; }

    @keyframes st-hero-bg-resolve {
        from { filter: brightness(1.9) saturate(0.15); }
        to { filter: brightness(1) saturate(1); }
    }
}

/* Section rhythm: consistent 64-96px vertical spacing between homepage sections, and a shared
   section-heading style (2rem) distinct from ordinary in-page <h2>s used on detail/listing pages. */
.st-home-section { margin: var(--st-space-8) 0; }
.st-home-section:first-child { margin-top: 0; }

/* Same full-bleed-section rhythm as .st-home-section, just a smaller gap -- this is a dense
   listing page (filters, cards, chart), not a marketing homepage, so 64-96px between every band
   would push results further down than a filter/browse page should. */
.st-recall-section { margin: var(--st-space-6) 0; }
.st-recall-section:first-child { margin-top: 0; }

/* Pulls the section immediately after the hero up so its card visibly overlaps the hero's own
   bottom edge (the "floating card" look in the reference design), rather than sitting in the
   plain gap .st-hero's own margin-bottom otherwise leaves below it. Selector is the two classes
   together (0,0,2,0), not just ".st-hero-overlap" alone (0,0,1,0) -- the single-class version has
   the SAME specificity as the plain ".st-home-section" margin rule above and the 960px+ version of
   it below, so at desktop widths the later, equal-specificity ".st-home-section" media-query rule
   was silently winning and resetting margin-top back to positive var(--st-space-16), the same
   category of bug already hit once on .st-hero-actions (see that rule's own comment). */
.st-home-section.st-hero-overlap { margin-top: calc(-1 * var(--st-space-16)); position: relative; z-index: 1; }

/* Same overlap, scoped to .st-recall-section instead -- Today.razor (the promoted homepage) uses
   .st-recall-section's denser spacing rhythm throughout rather than .st-home-section's wider one,
   so the section directly under the hero needs its own copy of the rule to match. */
.st-recall-section.st-hero-overlap { margin-top: calc(-1 * var(--st-space-16)); position: relative; z-index: 1; }
.st-section-heading { font-size: var(--st-font-size-section); margin: 0 0 var(--st-space-2); }
.st-section-intro { color: var(--st-color-text-muted); font-size: var(--st-font-size-lg); max-width: 70ch; margin: 0 0 var(--st-space-6); }
.st-section-footer { margin-top: var(--st-space-5); }
.st-section-link-above { text-align: right; font-weight: 600; margin: var(--st-space-6) 0 var(--st-space-3); }

/* Heading + "View all" link sharing one row (link at the far right), for sections that put that
   link at the top instead of below the card grid -- .st-section-heading's own margin-bottom
   still applies since it's a child of this row, not replaced by it. */
.st-section-heading-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--st-space-4); flex-wrap: wrap; }
.st-section-heading-link { font-weight: 600; white-space: nowrap; }

@media (min-width: 960px) {
    .st-home-section { margin: var(--st-space-10) 0; }
}

/* The coverage bar sits directly above the hero with nothing in between, so it opts out of
   .st-home-section's shared margin-bottom (48px at narrow widths, var(--st-space-10) at the
   960px breakpoint above) -- that gap made sense between ordinary homepage sections but just left
   a wide dead strip of surface color before the hero. Declared after both .st-home-section rules
   (equal specificity, so source order decides) to win at every width, not just below 960px. */
.st-coverage-selector-bar { margin-bottom: 0; }

/* Full-bleed container, breaking every homepage section out of the standard 1200px
   .safetrace-main: no max-width cap at all -- just padding -- so content genuinely fills the
   viewport on any screen size, rather than leaving visible margin once the screen is wider than
   some fixed cap. */
.st-full-bleed { width: 100vw; margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); }
.st-full-bleed-inner { padding: 0 var(--st-space-4); }

@media (min-width: 960px) {
    .st-full-bleed-inner { padding: 0 var(--st-space-6); }
}

/* Homepage split layout: a main column paired with a narrower sidebar (an ad slot, a second
   content block, or both) -- stacked on narrow viewports, side by side from 1200px up (needs more
   room than a bare 960px check: the main column's own content -- TrustIndicators' 4-column band,
   the 4-card metrics grid -- wasn't designed to also give up width to a sidebar). Sidebar width is
   a CSS variable per instance so a 300x250 and a 300x600 slot don't have to share one fixed track
   size. */
.st-home-split { display: grid; grid-template-columns: 1fr; gap: var(--st-space-8); align-items: start; }
.st-home-split-main { min-width: 0; align-self: start; }
.st-home-split-aside { display: flex; flex-direction: column; gap: var(--st-space-6); align-items: center; align-self: start; margin: 0; }

@media (min-width: 1200px) {
    .st-home-split { grid-template-columns: 1fr var(--st-home-split-aside-width, 300px); }
}

.st-ad-slot {
    /* Deliberately NOT sized off aspect-ratio: var(--st-ad-width)/var(--st-ad-height) -- the
       IAB-standard label numbers (e.g. 728x90) don't reflect how much space these slots actually
       get in this layout, and forcing that literal ratio inside a ~300px-wide sidebar column
       collapsed a "728x90" slot down to a barely-visible ~37px-tall sliver. Height is set per call
       site instead (--st-ad-height, in real pixels) to something that actually reads as a
       placeholder box at the width it's given. */
    width: 100%; max-width: var(--st-ad-width, 300px); height: var(--st-ad-height, 250px);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--st-space-1);
    border: 1px dashed var(--st-color-border); border-radius: var(--st-radius-md); background: var(--st-color-surface);
    color: var(--st-color-text-muted); font-size: var(--st-font-size-xs); text-transform: uppercase; letter-spacing: 0.05em;
}
.st-ad-slot-dimensions { text-transform: none; letter-spacing: normal; font-size: var(--st-font-size-xs); }
/* margin-top/bottom only, not the "margin: X 0" shorthand -- that would also set margin-left/
   right to 0, clobbering .st-full-bleed's own explicit left/right values when both classes land
   on the same element (equal specificity, and this rule comes later in the file, so it would
   silently win and cancel the breakout -- the same category of bug as .st-hero-overlap above). */
.st-ad-banner { display: flex; justify-content: center; margin-top: var(--st-space-10); margin-bottom: var(--st-space-10); }

/* Two content columns of comparable weight (e.g. Active Outbreaks paired with Search by Food or
   Disease) -- distinct from .st-home-split, whose narrow aside track is sized for an ad slot,
   not a second content block. */
.st-home-split-content { display: grid; grid-template-columns: 1fr; gap: var(--st-space-8); align-items: start; }

@media (min-width: 960px) {
    .st-home-split-content { grid-template-columns: 3fr 2fr; }
}

/* Explore by State: map, ranked list, and an ad slot as three tracks on wide screens. */
.st-home-states-row { display: grid; grid-template-columns: 1fr; gap: var(--st-space-8); align-items: start; }

@media (min-width: 1200px) {
    .st-home-states-row { grid-template-columns: 2fr 1.3fr auto; }
}

/* At a glance: a dedicated 4-column grid (not the shared 3-column .st-card-grid) so the metric
   cards keep matching the reference layout even when paired with an ad slot next to them. */
.st-metrics-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--st-space-5); }

@media (min-width: 700px) {
    .st-metrics-grid { grid-template-columns: repeat(4, 1fr); }
}

/* The homepage's US "At a Glance" grid only (Active investigations/Food Recalls/Drug Recalls/
   Elevated activity/States) -- 5 cards, so this additive modifier goes to 5 columns instead of the
   shared 4, at a wider breakpoint than the base rule since 5 needs more room per card to stay
   legible. Global "At a Glance" (4 metrics) and every other .st-metrics-grid on the site (recall/
   outbreak page stats, etc.) never get this class and are completely unaffected. */
@media (min-width: 700px) and (max-width: 1099px) {
    .st-metrics-grid-5 { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1100px) {
    .st-metrics-grid-5 { grid-template-columns: repeat(5, 1fr); }
}

.st-insight-card { display: flex; flex-direction: column; gap: var(--st-space-1); text-align: left; }
.st-insight-card .st-metric-label { color: var(--st-color-text-muted); font-size: var(--st-font-size-base); font-weight: 600; }
.st-insight-value { font-size: var(--st-font-size-xl); font-weight: 700; color: var(--st-color-primary); text-decoration: none; }
a.st-insight-value:hover { text-decoration: underline; }
.st-insight-value-empty { color: var(--st-color-text-muted); font-weight: 600; font-size: var(--st-font-size-base); }
.st-insight-card .st-metric-context { color: var(--st-color-text-muted); font-size: var(--st-font-size-xs); }

/* Stat cards + Quick insights paired with an ad slot (an .st-home-split instance, e.g. Recall's
   .st-recall-stats-split or Outbreak's own summary section) -- narrower than these cards' usual
   full-bleed width, so this scoped variant shrinks the section headings and card text and drops
   the metrics grid to 2 columns (never the shared 4-column default) so cards read as compact/
   near-square instead of being squeezed into an awkwardly narrow 4-across row. Scoped to this
   class only -- every other .st-metrics-grid/.st-section-heading on the site is untouched. */
.st-stats-compact .st-section-heading { font-size: var(--st-font-size-xl); }
/* .st-section-heading's own base margin has no margin-top (see its rule above), which reads fine
   when a heading opens a section but left "Quick insights" sitting flush against the Recall
   totals cards right above it once both were stacked in the same compact column. Only targets the
   heading that follows a metrics grid, not the first ("Recall totals") heading. */
.st-stats-compact .st-metrics-grid + .st-section-heading { margin-top: var(--st-space-8); }
.st-stats-compact .st-metrics-grid { grid-template-columns: repeat(2, 1fr); gap: var(--st-space-4); }
.st-stats-compact .st-card { padding: var(--st-space-4); }
.st-stats-compact .st-metric-card .st-metric-value { font-size: var(--st-font-size-2xl); }
.st-stats-compact .st-insight-value { font-size: var(--st-font-size-lg); }

@media (min-width: 500px) {
    .st-stats-compact .st-metrics-grid { grid-template-columns: repeat(4, 1fr); }
}

@media (min-width: 1200px) {
    .st-stats-compact .st-metrics-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Entrance sequencing: the hero background fades in alone first (HERO_BG_REVEAL_MS below, kept in
   sync with scroll-reveal.js's own INITIAL_REVEAL_DELAY_MS so the rest of the page doesn't start
   revealing mid-fade). Everything else -- the icon, then each hero-content row in turn -- follows
   one at a time afterward, using explicit st-hero-reveal-N classes (see Home.razor) rather than
   :nth-child position counting, which silently mis-sequenced once the US/Global coverage panels
   added more than one element per visual row. Never applied to chart/map content, which must
   never animate decoratively. prefers-reduced-motion disables all of this entirely rather than
   just shortening it. */
@media (prefers-reduced-motion: no-preference) {
    .st-hero { animation: st-hero-bg-fade-in 600ms ease both; }

    @keyframes st-hero-bg-fade-in {
        from { opacity: 0; }
        to { opacity: 1; }
    }

    /* Brand icon drops in from above and settles with a slight overshoot -- a bounce, not just a
       fade -- once the background has resolved (900ms delay, matching .st-hero-bg-resolve's
       duration at the >=960px breakpoint), before the hero text's own row-by-row reveal starts. */
    /* Two independent animations on different properties (transform/opacity vs. filter) run
       together without conflict: the bounce plays once on load, the glow pulse loops forever. */
    .st-hero-icon {
        animation: st-hero-icon-bounce-down 700ms cubic-bezier(0.34, 1.56, 0.64, 1) 900ms both,
                   st-hero-icon-glow-pulse 2.8s ease-in-out 900ms infinite;
    }

    @keyframes st-hero-icon-glow-pulse {
        0%, 100% { filter: var(--st-glow-icon-accent); }
        50% { filter: var(--st-glow-icon-accent-strong); }
    }

    @keyframes st-hero-icon-bounce-down {
        0% { transform: translateY(-140%); opacity: 0; }
        55% { transform: translateY(6%); opacity: 1; }
        75% { transform: translateY(-3%); }
        100% { transform: translateY(0); }
    }

    /* Each row starts only once the previous one has -- a real one-after-another sequence, not
       everything appearing at once with two rows arbitrarily singled out for a stagger. Rows 2-5
       are 150ms apart; a US/Global pair sharing a row (see Home.razor's comment) gets the same
       delay, which is correct since only one half of any pair is ever visible at a time. */
    .st-hero-reveal-1, .st-hero-reveal-2, .st-hero-reveal-3, .st-hero-reveal-4, .st-hero-reveal-5 {
        animation: st-fade-rise 420ms ease both;
    }
    .st-hero-reveal-1 { animation-delay: 900ms; }
    .st-hero-reveal-2 { animation-delay: 1050ms; }
    .st-hero-reveal-3 { animation-delay: 1200ms; }
    .st-hero-reveal-4 { animation-delay: 1350ms; }
    .st-hero-reveal-5 { animation-delay: 1500ms; }

    /* Selected coverage tab breathes between the resting and strong accent glow, rather than
       sitting at a fixed brightness -- reads as "live/active", not just "brighter than the rest". */
    .st-coverage-radio:checked + .st-coverage-option { animation: st-coverage-glow-pulse 2.4s ease-in-out infinite; }

    @keyframes st-coverage-glow-pulse {
        0%, 100% { box-shadow: var(--st-shadow-glow-accent); }
        50% { box-shadow: var(--st-shadow-glow-accent-strong); }
    }

    .st-card { animation: st-fade-rise 360ms ease both; }

    @keyframes st-fade-rise {
        from { opacity: 0; transform: translateY(8px); }
        to { opacity: 1; transform: translateY(0); }
    }

    /* Scroll reveal (scroll-reveal.js): each homepage section fades/rises in once it scrolls into
       view. Scoped under body.st-scroll-reveal-ready, a class the script only adds once it's
       actually running -- so a section is never invisible-by-default in a no-JS browser, only
       when the script exists and will actually go on to reveal it. */
    body.st-scroll-reveal-ready .st-home-section,
    body.st-scroll-reveal-ready .st-reveal-section,
    body.st-scroll-reveal-ready .st-ad-banner {
        opacity: 0; transform: translateY(20px);
        transition: opacity 560ms ease, transform 560ms ease;
    }
    body.st-scroll-reveal-ready .st-home-section.st-in-view,
    body.st-scroll-reveal-ready .st-reveal-section.st-in-view,
    body.st-scroll-reveal-ready .st-ad-banner.st-in-view {
        opacity: 1; transform: translateY(0);
    }
}

.st-card { transition: transform var(--st-transition-base), box-shadow var(--st-transition-base), border-color var(--st-transition-base); }
.st-card-grid .st-card:hover, .st-metrics-grid .st-card:hover, .st-related-links li:hover { transform: translateY(-2px); box-shadow: var(--st-shadow-card-hover); border-color: var(--st-color-border); }
.st-btn { transition: background-color var(--st-transition-fast), transform var(--st-transition-fast), box-shadow var(--st-transition-fast); }
.st-btn:active { transform: scale(0.97); }

/* Listing layout: filter drawer stacks above results on mobile, sits beside them on desktop */
.st-listing-layout { display: flex; flex-direction: column; gap: var(--st-space-5); }
.st-filter-drawer { border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); padding: var(--st-space-3) var(--st-space-4); background: var(--st-color-surface); }
.st-filter-drawer summary { cursor: pointer; font-weight: 600; }
.st-listing-results { min-width: 0; }

/* Health Alerts' nested "Advanced filters" -- collapsed by default (navigation-first: only Search/
   Date range/Sort are visible without opening this), set apart from the fields above it with a
   rule and its own top spacing so it doesn't read as just another sibling fieldset. */
.st-filter-drawer-advanced { margin-top: var(--st-space-3); padding-top: var(--st-space-3); border-top: 1px solid var(--st-color-border); }
.st-filter-drawer-advanced summary { cursor: pointer; font-weight: 600; }

/* Standalone filter panel (Outbreaks page redesign) -- same bordered-box look as .st-filter-drawer
   above, but deliberately its own class rather than reused: .st-filter-drawer also carries a
   desktop-width sticky/flex-basis rule scoped for the two-column .st-listing-layout it normally
   lives inside, which would apply here too (bare selector, not scoped to the parent) if this
   panel reused that class while standing alone, full-width, between the map and the results below. */
.st-filter-panel-standalone { border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); padding: var(--st-space-3) var(--st-space-4); background: var(--st-color-surface); margin-bottom: var(--st-space-5); }
.st-filter-panel-standalone summary { cursor: pointer; font-weight: 600; }

@media (min-width: 960px) {
    .st-listing-layout { flex-direction: row; align-items: flex-start; }
    .st-filter-drawer { flex: 0 0 260px; position: sticky; top: var(--st-space-4); }
    .st-listing-results { flex: 1; }
}

/* Coverage badge next to a page <h1> -- reuses .st-badge/.st-badge-neutral (see RecallCard's own
   type badge), just inline-flex so it sits on the same line as the heading text on wide screens
   and wraps cleanly below it on narrow ones. */
.st-badge-coverage { display: inline-flex; align-items: center; gap: var(--st-space-1); vertical-align: middle; margin-left: var(--st-space-3); }

/* Per-source "data last refreshed" strip on the recall page -- deliberately near the top (not
   buried by the pagination footer) since a public-health audience checking whether a recall
   notice is current is a primary use case, not an afterthought. */
.st-data-freshness-strip { display: flex; flex-wrap: wrap; gap: var(--st-space-3); margin-top: var(--st-space-4); }
.st-data-freshness-badge { font-weight: 600; }

/* Coverage callout: the "why this page is United States only, here's where Global data lives"
   box -- same bordered-surface treatment as .st-filter-drawer, so it reads as one family of
   "supplementary info" boxes rather than inventing a third visual language. */
.st-coverage-callout {
    border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md);
    padding: var(--st-space-4); background: var(--st-color-surface); margin: var(--st-space-4) 0;
}
.st-coverage-callout p { margin: 0 0 var(--st-space-2); }
.st-coverage-callout-links { display: flex; flex-wrap: wrap; gap: var(--st-space-2) var(--st-space-5); list-style: none; margin: 0; padding: 0; }

.st-classification-explainer {
    border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md);
    padding: var(--st-space-3) var(--st-space-4); background: var(--st-color-surface); margin: var(--st-space-5) 0;
}
.st-classification-explainer summary { cursor: pointer; font-weight: 600; }
.st-classification-explainer .st-definition-grid { grid-template-columns: max-content minmax(0, 1fr); }

.st-recall-faq-item {
    border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md);
    padding: var(--st-space-3) var(--st-space-4); background: var(--st-color-surface); margin: var(--st-space-3) 0;
}
.st-recall-faq-item summary { cursor: pointer; font-weight: 600; }
.st-recall-faq-item p { margin: var(--st-space-2) 0 0; }

.st-results-toolbar { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); margin: 0 0 var(--st-space-3); }

/* Related links: rendered as .st-card tiles (border/shadow/hover-lift, matching every other card
   grid on the site) instead of a plain text link list, so this closing section reads as more
   scannable destinations rather than a footer-style link dump. */
.st-related-links { margin: var(--st-space-8) 0; }
.st-related-links ul { display: grid; grid-template-columns: 1fr; gap: var(--st-space-4); list-style: none; margin: var(--st-space-4) 0 0; padding: 0; }
.st-related-links a { font-weight: 700; font-size: var(--st-font-size-lg); color: var(--st-color-primary); }
.st-related-links a::after { content: ""; position: absolute; inset: 0; }
.st-related-links p { margin: var(--st-space-1) 0 0; color: var(--st-color-text-muted); font-size: var(--st-font-size-sm); }

@media (min-width: 600px) {
    .st-related-links ul { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
    .st-related-links ul { grid-template-columns: repeat(3, 1fr); }
}

/* Charts */
.st-chart-container { margin: var(--st-space-5) 0; }
.st-chart-container figcaption { font-weight: 600; margin-bottom: var(--st-space-2); }
.st-chart-canvas-wrapper { position: relative; height: 320px; margin-bottom: var(--st-space-3); }
.st-chart-legend { list-style: none; display: flex; flex-wrap: wrap; gap: var(--st-space-4); padding: 0; margin: 0 0 var(--st-space-3); font-size: var(--st-font-size-sm); }
.st-chart-legend-swatch { display: inline-block; width: 0.75rem; height: 0.75rem; border-radius: 2px; margin-right: var(--st-space-1); vertical-align: middle; }
.st-chart-meta { display: flex; flex-wrap: wrap; gap: var(--st-space-4); font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); margin-top: var(--st-space-3); }

/* Maps */
.st-map-container { margin: var(--st-space-5) 0; }
.st-map-container figcaption { font-weight: 600; margin-bottom: var(--st-space-2); }
.st-map { height: 400px; margin-bottom: var(--st-space-2); border-radius: var(--st-radius-md); border: 1px solid var(--st-color-border); background: var(--st-color-surface); }
.st-map-legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--st-space-1); margin: 0 0 var(--st-space-2); font-size: var(--st-font-size-xs); color: var(--st-color-text-muted); }
.st-map-legend-swatch { display: inline-block; width: 1rem; height: 0.75rem; border-radius: var(--st-radius-sm); margin-right: var(--st-space-2); }
.st-map-legend-swatch:not(:first-child) { margin-left: -0.25rem; }
.st-map-legend-swatch-neutral { background: #e4e9ed; margin-left: var(--st-space-4); }
.st-map-legend-scale-label { font-variant-numeric: tabular-nums; margin-right: var(--st-space-1); }
.st-map-precision-note { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); margin: 0 0 var(--st-space-3); }

/* De-emphasized full data table -- still the same real, accessible table (see
   MapFallbackTable's own doc comment), just visually collapsed behind a native disclosure so
   it doesn't compete with the map for attention. No JS required; <details> is natively
   keyboard- and screen-reader-accessible. */
.st-map-table-disclosure { margin-top: var(--st-space-3); }
.st-map-table-disclosure summary {
    cursor: pointer; font-weight: 600; font-size: var(--st-font-size-base); color: var(--st-color-text-muted);
    padding: var(--st-space-2) 0;
}
.st-map-table-disclosure summary:hover { color: var(--st-color-accent); }
.st-map-table-disclosure[open] summary { margin-bottom: var(--st-space-2); }

/* Top affected states -- a compact ranked summary shown above the map/table on the homepage. */
.st-top-states-list { list-style: none; padding: 0; margin: 0 0 var(--st-space-4); display: flex; flex-direction: column; gap: var(--st-space-2); }
.st-top-states-list li { border-radius: var(--st-radius-md); background: var(--st-color-surface); }
.st-top-states-list a {
    display: flex; align-items: center; justify-content: space-between; gap: var(--st-space-3);
    padding: var(--st-space-3) var(--st-space-4); font-size: var(--st-font-size-base);
    color: var(--st-color-text); text-decoration: none; border-radius: var(--st-radius-md);
}
.st-top-states-list a:hover, .st-top-states-list a:focus-visible { background: var(--st-color-bg); }
.st-top-states-list .st-top-states-rank { color: var(--st-color-text-muted); font-weight: 600; margin-right: var(--st-space-2); }
.st-top-states-list .st-top-states-count { color: var(--st-color-primary); font-weight: 700; }

/* Forms (alert subscription/manage) */
.st-form-fieldset { border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); padding: var(--st-space-4); margin: 0 0 var(--st-space-4); }
.st-form-fieldset legend { font-weight: 600; padding: 0 var(--st-space-2); }

/* Nested inside a .st-form-fieldset (e.g. "Recall alerts" inside "Recalls") -- a lighter background
   and border distinguishes the sub-grouping without introducing a whole new visual language. */
.st-form-subgroup { background: var(--st-color-bg); border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); padding: var(--st-space-3); margin: 0 0 var(--st-space-4); }
.st-form-subgroup legend { font-weight: 600; padding: 0 var(--st-space-2); }
.st-form-hint { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); margin-top: 0; }
.st-form-field { margin-bottom: var(--st-space-3); display: flex; flex-direction: column; gap: var(--st-space-1); }
.st-form-field label { font-weight: 600; }
.st-form-field select, .st-form-field input[type="email"] { font: inherit; padding: var(--st-space-2); border: 1px solid var(--st-color-border); border-radius: var(--st-radius-sm); }
.st-form-checkbox { display: flex; align-items: center; gap: var(--st-space-2); margin-bottom: var(--st-space-2); font-weight: 400; }
.st-form-error { color: var(--st-color-alert-active-text, #a5231a); font-size: var(--st-font-size-sm); margin: var(--st-space-1) 0; }
.st-form-actions { display: flex; gap: var(--st-space-3); margin-top: var(--st-space-4); flex-wrap: wrap; }

/* /alerts/subscribe's 3-step mobile wizard */
.st-wizard-steps { display: flex; list-style: none; margin: 0 0 var(--st-space-4); padding: 0; gap: var(--st-space-2); font-size: var(--st-font-size-sm); }
.st-wizard-step { flex: 1; text-align: center; padding: var(--st-space-2); border-bottom: 3px solid var(--st-color-border); color: var(--st-color-text-muted); }
.st-wizard-step-current { border-bottom-color: var(--st-color-primary); color: var(--st-color-text); font-weight: 600; }

/* Minimum comfortable touch target (WCAG 2.5.5) for every tappable control in the wizard. */
.st-tap-target { min-height: 44px; }
button.st-tap-target { min-width: 44px; }

/* Delivery-frequency radio cards (Subscribe.razor Step 3, AlertPreferenceForm.razor) -- replaces a
   <select>, which is easy to change accidentally (a scroll/swipe on mobile) and hides Immediate's
   consequences behind a one-line label. Each option is its own full-width, large tap target so the
   three choices are visually distinct instead of a menu to scroll past. */
.st-frequency-legend { font-weight: 600; }
.st-frequency-choices { display: flex; flex-direction: column; gap: var(--st-space-2); margin-top: var(--st-space-1); }
.st-frequency-choice {
    display: flex; align-items: flex-start; gap: var(--st-space-3);
    border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md);
    padding: var(--st-space-3); font-weight: 400; cursor: pointer;
}
.st-frequency-choice input[type="radio"] { margin-top: 0.2em; width: 1.1em; height: 1.1em; flex-shrink: 0; }
.st-frequency-choice:has(input:checked) { border-color: var(--st-color-primary); background: var(--st-color-surface-raised); }
.st-frequency-choice-body { display: flex; flex-direction: column; gap: var(--st-space-1); }
.st-frequency-choice-title { font-weight: 600; }
.st-frequency-choice-hint { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); }
.st-frequency-recommended {
    display: inline-block; margin-left: var(--st-space-2); font-size: var(--st-font-size-sm); font-weight: 600;
    color: var(--st-color-primary); border: 1px solid var(--st-color-primary); border-radius: 999px;
    padding: 0 var(--st-space-2);
}
/* Immediate is the option we want subscribers to pick deliberately, not by default/accident --
   the hint above already explains why; this just keeps the card itself visually the same weight as
   the others rather than styling it as an error, since it's a legitimate choice, not a mistake. */
.st-frequency-choice-immediate .st-frequency-choice-hint { color: var(--st-color-alert-active-text, #a5231a); }

.st-chip-select { margin-bottom: var(--st-space-4); }
.st-chip-select-header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--st-space-2); }
.st-chip-select-label { font-weight: 600; }
.st-chip-select-count { font-size: var(--st-font-size-sm); color: var(--st-color-text-muted); }
.st-chip-select-all, .st-chip-select-specific { display: flex; align-items: center; gap: var(--st-space-2); min-height: 44px; }
.st-chip-list { list-style: none; margin: var(--st-space-2) 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--st-space-2); }
.st-chip {
    display: flex; align-items: center; gap: var(--st-space-1);
    background: var(--st-color-surface-raised); border: 1px solid var(--st-color-border);
    border-radius: 999px; padding: var(--st-space-1) var(--st-space-1) var(--st-space-1) var(--st-space-3);
    font-size: var(--st-font-size-sm);
}
.st-chip-remove {
    background: none; border: none; cursor: pointer; font-size: var(--st-font-size-lg); line-height: 1;
    padding: var(--st-space-1) var(--st-space-2); min-width: 32px; min-height: 32px; color: var(--st-color-text-muted);
}
.st-chip-remove:hover, .st-chip-remove:focus-visible { color: var(--st-color-text); }
.st-chip-search { width: 100%; font: inherit; padding: var(--st-space-2); border: 1px solid var(--st-color-border); border-radius: var(--st-radius-sm); margin-bottom: var(--st-space-2); box-sizing: border-box; }
.st-chip-options { list-style: none; margin: 0; padding: 0; max-height: 15rem; overflow-y: auto; border: 1px solid var(--st-color-border); border-radius: var(--st-radius-sm); }
.st-chip-option { display: flex; align-items: center; gap: var(--st-space-2); padding: var(--st-space-2) var(--st-space-3); min-height: 44px; }
.st-chip-option:hover { background: var(--st-color-surface-raised); }
.st-chip-no-matches { padding: var(--st-space-3); color: var(--st-color-text-muted); font-size: var(--st-font-size-sm); }

.st-collapsible { border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); margin-bottom: var(--st-space-3); padding: var(--st-space-2) var(--st-space-3); }
.st-collapsible summary { cursor: pointer; font-weight: 600; padding: var(--st-space-2) 0; min-height: 44px; display: flex; align-items: center; }
.st-collapsible[open] summary { margin-bottom: var(--st-space-2); }

.st-subscription-summary { border: 1px solid var(--st-color-border); border-radius: var(--st-radius-md); padding: var(--st-space-4); margin-bottom: var(--st-space-4); background: var(--st-color-surface-raised); }
.st-subscription-summary-heading { margin-top: 0; font-size: var(--st-font-size-lg); }
.st-subscription-summary dl { display: grid; grid-template-columns: auto 1fr; gap: var(--st-space-1) var(--st-space-3); margin: 0; }
.st-subscription-summary dt { font-weight: 600; color: var(--st-color-text-muted); }
.st-subscription-summary dd { margin: 0; }

/* Breakpoints: mobile-first. Tablet 600px+ (2 columns), Desktop 960px+ (3 columns) */
@media (min-width: 600px) {
    .st-card-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
    .safetrace-primary-nav { display: block; }
    .safetrace-mobile-nav-toggle { display: none; }
    .safetrace-brand-logo { height: 2.75rem; }
    .safetrace-header-cta.st-btn { display: inline-flex; }
    .safetrace-main { padding: var(--st-space-8) var(--st-space-6); }
    .st-coverage-wrapper { margin-top: calc(-1 * var(--st-space-8)); }
    .st-card-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Latest Recalls and Latest Global Outbreak Notices both show exactly 5 cards
   (DashboardDto.RecentRecalls / RecentGlobalOutbreakNotices), and each section is full-bleed with
   real room to spare -- this modifier lets those two grids go to 5 columns on a wide screen
   without changing the shared 3-column cap every other .st-card-grid (state/disease/food-scoped
   outbreak lists, foods, diseases, companies, brands) still uses. */
@media (min-width: 1400px) {
    .st-recalls-grid { grid-template-columns: repeat(5, 1fr); }
}

/* Trust indicators band -- sits just under the hero, establishes legitimacy before any data.
   Four equally-weighted groups (Sources, Coverage, Last Updated, Official Links) rather than
   three items plus a visually-different footnote, so nothing here reads as an afterthought. */
.st-trust-band {
    display: grid; grid-template-columns: 1fr; gap: var(--st-space-6);
    padding: var(--st-space-6); background: var(--st-color-surface); border: 1px solid var(--st-color-border);
    border-radius: var(--st-radius-lg);
}
.st-trust-item h3 {
    font-size: var(--st-font-size-xs); text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--st-color-text-muted); margin: 0 0 var(--st-space-2); font-weight: 700;
    display: flex; align-items: center; gap: var(--st-space-1);
}
.st-trust-item p { margin: 0; font-size: var(--st-font-size-base); }
.st-trust-item ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--st-space-1); }
.st-trust-sources { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--st-space-2); }
.st-trust-sources li { display: flex; align-items: center; gap: var(--st-space-2); font-size: var(--st-font-size-base); }
.st-trust-sources li .st-icon { flex-shrink: 0; width: 1rem; height: 1rem; color: var(--st-color-text-muted); }

@media (min-width: 600px) {
    .st-trust-band { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
    .st-trust-band { grid-template-columns: repeat(4, 1fr); }
}

/* Each step is a number chip beside its own title+description, in a row -- not stacked with the
   number/icon above the text -- three steps side by side once there's room. */
.st-how-it-works { display: grid; grid-template-columns: 1fr; gap: var(--st-space-6); }
.st-how-it-works-step { display: flex; align-items: flex-start; gap: var(--st-space-3); }
.st-how-it-works-number {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 2rem; height: 2rem;
    border-radius: 50%; background: var(--st-color-accent); color: var(--st-color-text-inverse);
    font-weight: 700; font-size: var(--st-font-size-base);
}
.st-how-it-works-number .st-icon { width: 1.1rem; height: 1.1rem; }
.st-how-it-works-step-text h3 { font-size: var(--st-font-size-lg); margin: 0 0 var(--st-space-1); }
.st-how-it-works-step-text p { margin: 0; color: var(--st-color-text-muted); font-size: var(--st-font-size-sm); line-height: var(--st-line-height-relaxed); }

/* This is the last section on the homepage, right above the footer -- .st-home-section's own
   margin (top and bottom) reads fine between two sections stacked on top of each other, but felt
   flush against the footer's own top border with nothing else below it. Scoped to just this
   section, not a blanket change to .st-home-section's margin everywhere else on the page. */
.st-home-how-it-works-section { margin-bottom: var(--st-space-12); }

@media (min-width: 960px) {
    .st-how-it-works { grid-template-columns: repeat(3, 1fr); gap: var(--st-space-5); }
    .st-flow-diagram { grid-template-columns: repeat(4, 1fr); }
}

/* Alerts callout -- must read as visually distinct from an ordinary st-card, not another one.
   This is the page's second conversion point, so its heading/button sit a step above the
   ordinary section-heading/button scale. Pale mint background/border (st-color-success-bg) --
   a full standalone card, not nested inside anything else. */
.st-alerts-callout {
    background: var(--st-color-success-bg); border: 1px solid var(--st-color-accent);
    border-radius: var(--st-radius-lg); padding: var(--st-space-10) var(--st-space-6); text-align: center;
}
.st-alerts-callout h2 { font-size: calc(var(--st-font-size-section) * 1.15); margin: 0 0 var(--st-space-3); }
.st-alerts-callout p { max-width: 60ch; margin: 0 auto var(--st-space-5); color: var(--st-color-text); font-size: var(--st-font-size-lg); }
.st-alerts-callout-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--st-space-3); }
.st-alerts-callout-benefits {
    list-style: none; padding: 0; margin: 0 0 var(--st-space-5); display: flex; flex-wrap: wrap; justify-content: center;
    gap: var(--st-space-2) var(--st-space-5); font-size: var(--st-font-size-sm); color: var(--st-color-text-muted);
}
.st-alerts-callout-benefits li { display: inline-flex; align-items: center; gap: var(--st-space-1); }
.st-alerts-callout-benefits .st-icon { color: var(--st-color-accent); }

/* Inline icon set -- small, self-contained SVGs (no external font/CDN); decorative by default */
.st-icon { display: inline-block; width: 1.25em; height: 1.25em; vertical-align: -0.2em; flex-shrink: 0; }
.st-icon-lg { width: 2rem; height: 2rem; }
