/* ── Typeface ────────────────────────────────────────────────────────────────
   Inter, self-hosted as a single variable woff2 per subset (SIL Open Font
   License 1.1, Rasmus Andersson). Replaces a render-blocking @import of DM Sans
   and Plus Jakarta Sans from Google Fonts: that import sat in front of first
   paint and leaked every visitor's IP to a third party, which is worth avoiding
   for a public-health page.

   One variable file covers weight 100–900, so the whole range costs a single
   request. latin-ext is split out and gated on its unicode-range, so it is only
   fetched if a glyph outside latin actually appears — the French UI needs it
   for œ and for the accents that fall outside latin-1.

   The licensed brand faces (Agrandir display, Apercu text) were referenced here
   by @font-face but the files were never added to assets/, so every page load
   fired four 404s and silently fell back. Those rules are removed rather than
   left dangling; the family names are kept at the head of the stacks below, so
   dropping the real files in and restoring an @font-face block is all it takes
   to activate them. */
@font-face {
    font-family: 'Inter';
    src: url('/assets/Inter-Variable-latin.woff2') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                   U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Inter';
    src: url('/assets/Inter-Variable-latin-ext.woff2') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
                   U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
                   U+2C60-2C7F, U+A720-A7FF;
}

/* ── Condensed sticky KPI bar ─────────────────────────────────────────────────
   Appears once the full KPI strip scrolls out of view. Visibility is toggled by
   assets/sticky-kpi.js; everything below is presentation only.

   z-index 1003 is deliberate and must stay above the map's floating Reset
   view button (1000), its hover panel (1001) and the sticky control bar (1002) —
   all live inside the scrolling content and would otherwise paint over a
   pinned bar while you scroll past the map. */
.sticky-kpi {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1003;
    display: flex;
    align-items: center;
    gap: 24px;
    height: 48px;
    padding: 0 32px;
    background-color: rgba(255, 255, 255, 0.82);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid rgba(15, 20, 25, 0.08);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";

    /* Hidden state. visibility is delayed by the transition duration so the bar
       stays keyboard- and screen-reader-inert once it has finished sliding out,
       rather than lingering as a focusable strip off the top of the page. */
    transform: translateY(-100%);
    opacity: 0;
    visibility: hidden;
    transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
                visibility 0s linear 0.2s;
}

.sticky-kpi.is-visible {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
    transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
                visibility 0s linear 0s;
}

/* 48px out of a short viewport is not a good trade — the page already carries a
   fixed footer bar. */
@media (max-height: 700px) {
    .sticky-kpi { display: none; }
    .viz-toolbar { top: 0 !important; }
}

/* ── Control bar (date + colour) — sticky above the visual ───────────────────
   Pins directly under the 48px condensed KPI bar, so while you scroll the map
   or treemap the headline figures and these controls stay in view. It only
   reaches the top after the KPI strip has scrolled off, which is exactly when
   the condensed bar appears, so the two never overlap. z-index sits above the
   map's floating controls (1000/1001) and below the KPI bar (1003). */
.viz-toolbar {
    position: sticky;
    top: 48px;
    z-index: 1002;
    background-color: #f5f5f7;
    border-bottom: 1px solid rgba(15, 20, 25, 0.06);
}

@media (prefers-reduced-motion: reduce) {
    .sticky-kpi,
    .sticky-kpi.is-visible { transition: none; }
}

/* ── Data-flags panel ─────────────────────────────────────────────────────────
   Collapsed by default; the chip-bar button toggles .is-open. Animating
   max-height rather than height because the panel's natural height depends on
   how many flags fired and how far the unit lists wrap — there is no fixed value
   to transition to. The open value only needs to exceed any real height; it is
   a ceiling, not a size. */
.flags-panel {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}

.flags-panel.is-open {
    max-height: 600px;
}

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

/* The toggle sits in a row of plain chips, so the button's own chrome has to go
   — otherwise it reads as a form control dropped into a data strip. */
.flag-chip-btn {
    background: none;
    border: none;
    font: inherit;
    cursor: pointer;
    text-align: left;
    -webkit-appearance: none;
    appearance: none;
    transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}

.flag-chip-btn:hover { opacity: 0.7; }

.flag-chip-btn:focus-visible {
    outline: 2px solid #1e3a5c;
    outline-offset: 2px;
}

.flag-chip-caret {
    font-size: 9px;
    color: #6b7280;
    margin-left: 2px;
    display: inline-block;
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Caret follows the panel, so the control always states the panel's condition */
.flag-chip-btn[aria-expanded="true"] .flag-chip-caret {
    transform: rotate(180deg);
}

/* ── Base ──────────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

:root {
    --Dash-Fill-Interactive-Strong: #1e3a5c;
}

body {
    background-color: #f5f5f7;
    margin: 0;
    font-family: 'Apercu', 'Inter', system-ui, sans-serif;
}

/* ── Tabular numerals ─────────────────────────────────────────────────────────
   Proportional digits change width as they change value, so every figure that
   updates in place — the KPI strip, the zone table, the slider's date readout —
   shifts sideways while the slider scrubs or the map animation plays. Fixed-
   width digits hold the layout still. */
#stats-bar,
#ir-table,
#data-flags,
#slider-date-display,
#slider-day-display,
#header-eyebrow,
#header-wow {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

/* ── Header font — Agrandir Bold (falls back to Plus Jakarta Sans) ─────────────── */
.app-header-title {
    font-family: 'Agrandir', 'Inter', system-ui, sans-serif !important;
    font-weight: 800 !important;
    letter-spacing: -0.03em !important;
}

/* Hide radio inputs inside toggles so they don't inflate label widths */
.view-toggle-radio input[type="radio"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    margin: 0;
    padding: 0;
    pointer-events: none;
}

/* Dash 4 adds margin-right: 16px to each inline radio option — remove it
   so the last option sits flush with the container and the thumb aligns */
.view-toggle-radio .dash-options-list-option {
    margin-right: 0 !important;
    margin-left: 0 !important;
}

/* ── Segmented control — shared base ────────────────────────────────────────── */

/* Neutral track, navy thumb: the accent earns its meaning by marking the one
   selected segment rather than tinting the whole control. */
.view-toggle-radio {
    display: inline-flex;
    align-items: center;
    position: relative;
    background-color: #f1f2f4;
    border: 1px solid rgba(15,20,25,0.08);
    border-radius: 10px;
    padding: 3px;
    gap: 0;
}

/* Sliding thumb — navy pill that moves between options */
.toggle-thumb {
    position: absolute;
    border-radius: 7px;
    background-color: #1e3a5c;
    pointer-events: none;
    z-index: 0;
    transition: left 0.22s cubic-bezier(0.4, 0, 0.2, 1),
                top  0.22s cubic-bezier(0.4, 0, 0.2, 1),
                width 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Labels sit above the thumb; off state: transparent track + black text */
.view-toggle-radio label {
    position: relative;
    z-index: 1;
    display: inline-block;
    padding: 5px 14px;
    border-radius: 7px;
    cursor: pointer;
    background: transparent;
    color: #3d4551;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
    user-select: none;
    white-space: nowrap;
}

/* Unselected segments respond to hover — interaction density without motion */
.view-toggle-radio label:hover:not(:has(input:checked)):not(:has(input:disabled)) {
    color: #0f1419;
}

/* Active label text flips to white once the navy thumb is behind it */
.view-toggle-radio label:has(input[type="radio"]:checked) {
    color: #ffffff;
}

/* ── Map/Treemap view toggle — larger than the lang toggle ──────────────────────── */

.view-toggle-lg label {
    padding: 8px 22px !important;
    font-size: 14px !important;
}

/* ── Interactive surfaces ─────────────────────────────────────────────────────
   Hover changes the surface, not the geometry. Lifting a card on hover
   (translateY + a large drop shadow, on a spring curve that overshoots) reads
   as dated and, on the zone list, made a 40-row table wobble under the cursor.
   A tint plus a border change carries the same affordance while the layout
   stays still. Shadows are reserved for genuinely floating things. */

.reset-btn {
    transition: background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1),
                border-color     0.15s cubic-bezier(0.4, 0, 0.2, 1),
                color            0.15s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ── Reset view button — matches the off-state of the toggle segments ─────────── */

.reset-btn {
    display: inline-block;
    padding: 5px 14px;
    border-radius: 8px;
    border: 1px solid rgba(15,20,25,0.10);
    background-color: #f1f2f4;
    color: #3d4551;
    cursor: pointer;
}

.reset-btn:hover:not(:disabled) {
    background-color: #1e3a5c;
    border-color: #1e3a5c;
    color: #ffffff;
}

.reset-btn:active:not(:disabled) {
    background-color: #162d47;
    border-color: #162d47;
}

/* Keyboard focus must be visible even though hover is a colour change */
.reset-btn:focus-visible {
    outline: 2px solid #1e3a5c;
    outline-offset: 2px;
}

.reset-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* ── Global catch-all: any radio/checkbox label defaults to dark ─────────────── */

label {
    color: #111111;
}

/* ── Date slider ─────────────────────────────────────────────────────────────── */

/* Track */
.rc-slider-rail {
    background-color: #d0d0d0 !important;
    height: 3px !important;
}

.rc-slider-track {
    background-color: #1e3a5c !important;
    height: 3px !important;
}

/* Handle */
.rc-slider-handle {
    border-color: #1e3a5c !important;
    background-color: #1e3a5c !important;
    width: 14px !important;
    height: 14px !important;
    margin-top: -5px !important;
    opacity: 1 !important;
    box-shadow: 0 0 0 3px rgba(30,58,92,0.2) !important;
}

.rc-slider-handle:hover,
.rc-slider-handle:active {
    box-shadow: 0 0 0 5px rgba(30,58,92,0.25) !important;
}

/* Dash 4 native range input fallback */
#date-slider input[type="range"] {
    accent-color: #1e3a5c;
}
[class*="rc-slider-track"],
[class*="SliderTrack"] {
    background-color: #1e3a5c !important;
}
[class*="rc-slider-handle"],
[class*="SliderHandle"] {
    border-color: #1e3a5c !important;
    background-color: #1e3a5c !important;
}

.dash-slider-root {
    --slider-color: #1e3a5c !important;
    --accent: #1e3a5c !important;
    --primary: #1e3a5c !important;
    --color-primary: #1e3a5c !important;
    --color-accent: #1e3a5c !important;
    --track-color: #1e3a5c !important;
    --fill-color: #1e3a5c !important;
    --interactive-color: #1e3a5c !important;
    --strong-color: #1e3a5c !important;
}

#date-slider .dash.fill.interactive.strong,
#date-slider .fill.interactive.strong,
#date-slider .fill.strong {
    background-color: #1e3a5c !important;
    border-color: #1e3a5c !important;
}

.dash-slider-thumb {
    background-color: #1e3a5c !important;
    border-color: #1e3a5c !important;
    box-shadow: none !important;
}

.dash-slider-thumb:hover,
.dash-slider-thumb:focus,
.dash-slider-thumb:active {
    background-color: #1e3a5c !important;
    box-shadow: 0 0 0 4px rgba(30,58,92,0.2) !important;
}

/* Tick marks */
.rc-slider-mark-text {
    color: #6b7280 !important;
    font-size: 12px !important;
    font-family: 'Inter', system-ui, sans-serif !important;
    margin-top: 4px !important;
}

/* Hide the raw index-number tooltip and Dash 4's editable input beside the handle */
.rc-slider-tooltip,
.rc-slider-tooltip-inner,
[class*="slider-tooltip"],
[class*="SliderTooltip"],
#date-slider input,
#date-slider [role="tooltip"],
#date-slider [class*="tooltip"],
#date-slider [class*="handle-label"],
#date-slider [class*="Tooltip"],
#date-slider [class*="Input"] {
    display: none !important;
    opacity: 0 !important;
    visibility: hidden !important;
    pointer-events: none !important;
}

/* ── Treemap tile lift ───────────────────────────────────────────────────────── */

/* Let the shadow bleed outside the SVG canvas */
#treemap .main-svg,
#treemap svg {
    overflow: visible !important;
}

/* Base transition on every tile. The lift is kept here — unlike the buttons,
   a treemap tile has no border or background of its own to tint, and the lift
   is the only cue that a tile is clickable. Retuned off the spring curve onto
   the same ease-out as everything else, with a softer shadow. */
#treemap g.slice {
    transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
                filter   0.15s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Lifted state — set by JS mouseenter so only the topmost tile activates */
#treemap g.slice.slice-lift {
    transform: translateY(-2px);
    filter: drop-shadow(0 4px 12px rgba(15, 20, 25, 0.16));
}

/* Hide Plotly's hover label anchor arrow on the choropleth map */
#choropleth-map .hovertext {
    display: none !important;
}

/* Crosshair cursor over the choropleth map — covers Plotly drag layers,
   MapLibre/Mapbox canvas, and any element Plotly sets dynamically */
#choropleth-map,
#choropleth-map *,
#choropleth-map canvas,
#choropleth-map .mapboxgl-canvas,
#choropleth-map .mapboxgl-canvas-container,
#choropleth-map .mapboxgl-interactive,
#choropleth-map .maplibregl-canvas,
#choropleth-map .maplibregl-canvas-container,
#choropleth-map .maplibregl-interactive,
#choropleth-map .nsewdrag,
#choropleth-map .nsdrag,
#choropleth-map .ewdrag,
#choropleth-map .drag {
    cursor: crosshair !important;
}

/* ── Zone rankings DataTable ─────────────────────────────────────────────────── */

/* Remove all cell borders that Dash injects by default */
#ir-table .dash-spreadsheet-container .dash-spreadsheet,
#ir-table .dash-spreadsheet-container .dash-spreadsheet td,
#ir-table .dash-spreadsheet-container .dash-spreadsheet th {
    border: none !important;
    box-shadow: none !important;
}

/* Sort arrows (Carbon data table): shown on hover and on the sorted column
   only, never on the unlabeled status and "+X" columns. */
#ir-table th .column-header--sort {
    opacity: 0;
    transition: opacity 0.12s ease;
}
#ir-table th:hover .column-header--sort,
#ir-table th:focus-within .column-header--sort,
#ir-table th .column-header--sort:has(svg[data-icon="sort-up"]),
#ir-table th .column-header--sort:has(svg[data-icon="sort-down"]) {
    opacity: 1;
}
#ir-table th[data-dash-column="status"] .column-header--sort,
#ir-table th[data-dash-column="cases_new"] .column-header--sort,
#ir-table th[data-dash-column="deaths_new"] .column-header--sort {
    display: none;
}

/* Row hover — highlight entire row */
#ir-table .dash-spreadsheet-container tbody tr:hover td.dash-cell {
    background-color: rgba(200, 220, 238, 0.35) !important;
    cursor: pointer;
}

/* Active / selected cell — no blue outline ring */
#ir-table .dash-spreadsheet-container td.dash-cell.focused {
    outline: none !important;
    box-shadow: none !important;
}

/* ── Footer ──────────────────────────────────────────────────────────────────── */

/* No extra margin above footer */
body > div:last-child {
    margin-top: 0;
}

/* ── Previous / next report buttons beside the slider date ───────────────── */
.step-btn {
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid rgba(15, 20, 25, 0.10);
    border-radius: 8px;
    background: #ffffff;
    color: #1e3a5c;
    font-size: 16px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
}
.step-btn:hover:not(:disabled) {
    background: #eef3f9;
}
.step-btn:focus-visible {
    outline: 2px solid #2d6aa8;
    outline-offset: 2px;
}
.step-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* ── Scroll cue at the bottom of the weekly-incidence scroll port ────────── */
.scroll-fade {
    position: sticky;
    bottom: 0;
    height: 24px;
    margin-top: -24px;
    pointer-events: none;
    background: linear-gradient(to bottom, rgba(245, 245, 247, 0), rgba(245, 245, 247, 1));
}

/* ── Help section: expandable sections (Carbon accordion pattern) ─────────── */
.footer-section {
    border-top: 1px solid #e4e6ea;
}
.footer-section:last-child {
    border-bottom: 1px solid #e4e6ea;
}
.footer-summary {
    list-style: none;
    cursor: pointer;
    padding: 12px 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    color: #3d4551;
    display: flex;
    align-items: center;
    gap: 8px;
}
.footer-summary::-webkit-details-marker {
    display: none;
}
.footer-summary::before {
    content: "›";
    display: inline-block;
    width: 12px;
    font-size: 16px;
    transition: transform 0.12s ease;
}
.footer-section[open] > .footer-summary::before {
    transform: rotate(90deg);
}
.footer-summary:hover {
    color: #0f1419;
}
.footer-summary:focus-visible {
    outline: 2px solid #2d6aa8;
    outline-offset: 2px;
}
.footer-section-body {
    padding: 0 0 16px 20px;
}

/* ── Phone layout (Carbon "sm"/"md" breakpoint: below 672px) ───────────────
   The layout is built with inline styles, so these overrides need !important.
   Each target carries a className added for exactly this purpose. */
@media (max-width: 672px) {
    /* Header: title on its own lines, the toggles beneath it */
    .app-header {
        flex-direction: column !important;
        align-items: flex-start !important;
        gap: 16px;
        padding: 24px 16px 16px !important;
    }
    .app-header-title {
        font-size: 28px !important;
    }
    .app-header > div:last-child {
        gap: 16px !important;
        flex-wrap: wrap;
    }
    .view-toggle-lg label {
        padding: 8px 16px !important;
    }

    /* KPI strip: two tiles per row instead of one long row off the screen */
    .kpi-row {
        flex-wrap: wrap !important;
    }
    .kpi-row > div {
        flex: 1 1 50% !important;
        min-width: 50%;
        padding: 16px !important;
        border-right: none !important;
        border-bottom: 1px solid rgba(15, 20, 25, 0.07);
    }

    /* Weekly chips wrap as whole chips */
    .chip-row {
        flex-wrap: wrap !important;
        row-gap: 8px;
        padding: 8px 16px !important;
    }
    .chip-row > * {
        white-space: nowrap;
    }

    .flag-row {
        padding: 8px 16px !important;
    }

    /* Two-plus wrapped lines would eat a phone screen if pinned */
    .viz-toolbar {
        position: static;
        padding: 16px !important;
    }

    /* The slider's date labels can't fit at 12px; the readout and the
       previous/next buttons below the slider carry the date instead. */
    .dash-slider-mark {
        display: none !important;
    }

    /* Side-by-side chart panels stack. Each panel is flex: 1 1 0 for the
       desktop row; in a column that basis collapses its height to zero and the
       contents spill over whatever follows, so it sizes to content instead. */
    .chart-row {
        flex-direction: column !important;
        padding: 24px 16px 0 !important;
    }
    .chart-row > div {
        width: 100%;
        flex: 0 0 auto !important;
    }

    /* Weekly-incidence week numbers: every other one, or 20 labels collide */
    #zone-growth-axis .xtick:nth-of-type(even) text {
        display: none;
    }

    /* Metric switcher: label above, tighter segments so all three fit */
    .viz-toolbar > div:first-child {
        flex-wrap: wrap;
    }
    #color-metric label {
        padding: 4px 8px !important;
    }

    /* The condensed sticky bar's four figures don't fit a phone; the full KPI
       strip is two short scrolls away instead. */
    .sticky-kpi {
        display: none !important;
    }

    #methodology-footer > div {
        padding: 32px 16px !important;
    }
    .howto-cols {
        flex-direction: column !important;
        gap: 0 !important;
    }
    .howto-cols > div {
        flex: 1 1 auto !important;
    }
}

/* ── Laptop widths: the nine KPI tiles are sized for 1440px and ran ~95px off
   the right edge at 1280.
   1200–1439px: one row; each tile shrinks to its widest word or number
   (min-content) with its label wrapping, so numbers never overlap.
   673–1199px: two even rows (5 + 4) — one row can no longer hold the numbers
   (their minimum widths total ~1,185px). */
@media (min-width: 1200px) and (max-width: 1439px) {
    .kpi-row > div {
        flex: 1 1 auto;
        min-width: min-content;
        padding-left: 16px !important;
        padding-right: 16px !important;
    }
}
@media (min-width: 673px) and (max-width: 1199px) {
    .kpi-row {
        flex-wrap: wrap;
    }
    .kpi-row > div {
        flex: 1 1 18%;
        min-width: min-content;
        padding-left: 16px !important;
        padding-right: 16px !important;
        border-bottom: 1px solid rgba(15, 20, 25, 0.07);
    }
    /* Summary chips wrap as whole chips, not mid-label */
    .chip-row {
        flex-wrap: wrap !important;
        row-gap: 8px;
    }
    .chip-row > * {
        white-space: nowrap;
    }
}
