/* Reset default body margin so the sticky header sits flush to the top edge and
   spans the full browser width (no inset gap), and does not jump on first scroll. */
html, body { margin: 0; padding: 0; }

/* Inter + JetBrains Mono — SIL Open Font License 1.1, bundled (privacy + robust on
   networks that block CDNs; the dashboard needs internet to fetch, but the UI chrome
   should never depend on a third-party font CDN). */
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/inter-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url('fonts/inter-latin-500-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/inter-latin-600-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/inter-latin-700-normal.woff2') format('woff2');}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/jetbrains-mono-latin-600-normal.woff2') format('woff2');}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/jetbrains-mono-latin-700-normal.woff2') format('woff2');}

/* ----------------------------------------------------------------------------
   Theme tokens (Increment 21A — dark mode). The Python palette `C` emits these as
   var(--token), so every inline style themes automatically; the actual light/dark
   values live here, in ONE place. Default = light; `html[data-theme="dark"]` flips
   it. The applied theme is set on <html> by a clientside callback (stored preference,
   falling back to the OS prefers-color-scheme). Colour stays reserved for meaning:
   teal = accent, amber/red = feed health only — no severity/trend colour in either theme.
   ---------------------------------------------------------------------------- */
:root{
  --ink:#0f1b2a; --bg:#eef2f6; --panel:#ffffff; --panel2:#f6f9fc;
  --primary:#0b7488; --primaryD:#075664; --accent:#d6453a; --amber:#c97a12;
  --muted:#5a6b7b; --faint:#92a2b2; --line:#e4e9ef; --good:#0c8a5b;
  --navy:#10243d; --navyText:#8fb3c9;
  --tint-teal:rgba(11,116,136,0.08); --on-primary:#ffffff;
  --banner-bg:#fffaf2; --banner-border:#f0e2cc; --banner-text:#a5640a;
  --good-bg:#f3faf6; --good-border:#cfe9da;
  --chip-bg:rgba(201,122,18,.12); --chip-border:#f0e2cc; --chip-text:#a5640a;
  /* gloss tokens (Increment 26 — visual polish; "noticeable but tasteful").
     The inset top-highlight is the glossy edge; the visible drop shadow lifts cards off
     a slightly deeper body so white panels read as elevated. */
  --panel-grad:linear-gradient(180deg,#ffffff,#f1f5fa);
  --primary-grad:linear-gradient(180deg,var(--primary),var(--primaryD));
  --appbar-grad:linear-gradient(135deg,#0c2036 0%,#12273f 55%,#1a3a5c 100%);
  --body-grad:radial-gradient(1200px 560px at 50% -10%,#ffffff,rgba(255,255,255,0) 60%),linear-gradient(180deg,#e9eef5 0%,#e3e9f1 60%,#dfe6ee 100%);
  --sh:inset 0 1px 0 rgba(255,255,255,.7),0 1px 2px rgba(15,27,42,.06),0 8px 20px -8px rgba(15,27,42,.22);
  --sh-lg:inset 0 1px 0 rgba(255,255,255,.7),0 4px 10px rgba(15,27,42,.10),0 24px 50px -16px rgba(15,27,42,.34);
  --ring:0 0 0 3px rgba(11,116,136,.25);
}
html[data-theme="dark"]{
  --ink:#e7ecf3; --bg:#10151d; --panel:#1a212e; --panel2:#232d3c;
  --primary:#3bbaa2; --primaryD:#62cbb6; --accent:#f4796d; --amber:#e3a44a;
  --muted:#9aa8ba; --faint:#6f7e90; --line:#2c3645; --good:#41c391;
  --navy:#0b1420; --navyText:#8aa0b8;
  --tint-teal:rgba(59,186,162,0.14); --on-primary:#07221c;
  --banner-bg:#2a2113; --banner-border:#4a3a1c; --banner-text:#e9b15c;
  --good-bg:#122620; --good-border:#1f4133;
  --chip-bg:rgba(227,164,74,.16); --chip-border:#4a3a1c; --chip-text:#e9b15c;
  /* gloss tokens — dark variants (--primary-grad inherits from :root, resolves per theme) */
  --panel-grad:linear-gradient(180deg,#232f43,#1a212e);
  --appbar-grad:linear-gradient(135deg,#091220 0%,#0c1828 55%,#13294a 100%);
  --body-grad:radial-gradient(1200px 560px at 50% -10%,#1a2330,rgba(16,21,29,0) 60%),linear-gradient(180deg,#0f141c 0%,#0d121a 60%,#0b1017 100%);
  --sh:inset 0 1px 0 rgba(255,255,255,.05),0 1px 2px rgba(0,0,0,.5),0 8px 20px -8px rgba(0,0,0,.66);
  --sh-lg:inset 0 1px 0 rgba(255,255,255,.05),0 4px 10px rgba(0,0,0,.55),0 24px 50px -16px rgba(0,0,0,.78);
  --ring:0 0 0 3px rgba(59,186,162,.32);
}
/* Keep the page background themed even before the app tree paints over it. */
body{ background:var(--bg); color:var(--ink); }

/* ----------------------------------------------------------------------------
   Click feedback — every interactive element gives a quick, tactile response so a
   click always feels registered (buttons press in, the collapsible header tints,
   links and control rows dim). transform/filter/opacity/background are never set
   inline, so no !important is needed (the disabled cursor is the one exception).
   ---------------------------------------------------------------------------- */
button, summary { transition: transform .07s ease, filter .12s ease, background .12s ease; }
a, label        { transition: opacity .12s ease, filter .12s ease; }

/* Buttons (block / inline-block) — a real press-in: refresh, worldwide chip, clear-filter. */
button:not(:disabled)        { cursor: pointer; }
button:hover:not(:disabled)  { filter: brightness(1.06); }
button:active:not(:disabled) { transform: scale(.95); filter: brightness(.96); }

/* Collapsible card header ("About this data & methodology") — a gentle press + tint.
   Scaling a full-width header would shift its text, so keep the scale tiny. */
summary        { cursor: pointer; border-radius: 8px; }
summary:hover  { background: rgba(11,116,136,.04); }
summary:active { transform: scale(.99); background: rgba(11,116,136,.09); }

/* Links are inline (CSS transforms don't apply to them), so press = a quick dim:
   the header "About" anchor, source links, the table "open ↗" links. */
a:hover  { filter: brightness(1.08); }
a:active { opacity: .5; }

/* Sidebar radio / checkbox rows — a light press on the row (the input keeps its own state). */
label:active { opacity: .6; }

/* The primary "Refresh now" button keeps its richer hover lift + busy/disabled state. */
#refresh-btn { transition: transform .07s ease, filter .15s ease, box-shadow .15s ease; }
#refresh-btn:hover:not(:disabled)  { box-shadow: 0 2px 9px rgba(11,116,136,.38); }
#refresh-btn:active:not(:disabled) { box-shadow: none; }
#refresh-btn:disabled { opacity: .6; cursor: progress !important; transform: none; filter: none; }

/* ----------------------------------------------------------------------------
   Visual polish (Increment 26) — depth on the app bar, a gentle hover-lift on
   interactive chrome, and a keyboard focus ring. Subtle/trust-first: cards stay
   static; lift is reserved for things you click. Dark-mode safe (uses tokens).
   ---------------------------------------------------------------------------- */
#appbar { box-shadow: 0 6px 22px -12px rgba(15,27,42,.55); }
#refresh-btn:hover:not(:disabled) { transform: translateY(-1px); }
.osd-feeds-pill { transition: transform .14s ease, filter .12s ease; }
.osd-feeds-pill:hover { transform: translateY(-1px); filter: brightness(1.07); }
.osd-theme-toggle { transition: transform .14s ease, filter .12s ease; }
.osd-theme-toggle:hover { filter: brightness(1.14); }
button:focus-visible, a:focus-visible, summary:focus-visible,
input:focus-visible { outline: none; box-shadow: var(--ring); }
/* Sidebar filter rows — a gentle rounded hover tint (more polished, still subtle). */
#date-filter label, #source-filter label { border-radius: 7px; transition: background .12s ease; }
#date-filter label:hover, #source-filter label:hover { background: var(--tint-teal); }

/* ----------------------------------------------------------------------------
   Mobile / responsive. The desktop layout is a fixed ~248px filter sidebar beside
   the main column, which forces a ~690px-wide row and makes phones zoom out. On
   narrow screens, stack the sidebar above the main column (full width, not sticky)
   and let the app bar wrap. Inline flex styles are overridden with !important.
   ---------------------------------------------------------------------------- */
@media (max-width: 760px) {
  /* stack to a column AND stretch children full-width: the desktop row uses
     align-items:flex-start, which in a column would shrink each child to its
     content width and collapse the main column to the map's intrinsic size. */
  .osd-bodyrow { flex-direction: column !important; align-items: stretch !important; }
  #filter-sidebar { width: 100% !important; box-sizing: border-box !important; position: static !important; }
  #appbar { flex-wrap: wrap; row-gap: 8px; }
  /* let the app bar's inner clusters (brand, and the Sources/About/Refresh/theme/version
     group) wrap too, so they never force horizontal overflow on very narrow phones. */
  #appbar div { flex-wrap: wrap !important; }
  /* informational tables (sources directory, About) — let long words/links break so
     the table shrinks to the viewport instead of forcing horizontal overflow. The
     signals DataTable (.cell-table) keeps its own internal horizontal scroll. */
  table:not(.cell-table) th, table:not(.cell-table) td,
  table:not(.cell-table) a { overflow-wrap: anywhere; word-break: break-word; }
}

/* ----------------------------------------------------------------------------
   Feeds health window (Increment 20) — a click-to-open overlay listing live per-source
   freshness. Position + visibility live here so the open/close clientside callback only
   toggles the .open class (no inline-style juggling). Hidden by default.
   ---------------------------------------------------------------------------- */
.osd-feeds-window { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 100;
  display: none; align-items: flex-start; justify-content: center; padding: 68px 16px 16px; }
.osd-feeds-window.open { display: flex; }

/* Feeds pill: on very narrow screens drop the trailing "· {state}" word but keep the
   dot + "Feeds" anchor (the tooltip still carries the detail). */
@media (max-width: 600px) { .osd-feeds-pill .osd-feeds-word { display: none; } }

/* ----------------------------------------------------------------------------
   DataTable + global search (Increment 22). The native per-column filter row was
   removed (filter_action="none") in favour of one "Search signals…" box, so the
   former .dash-filter case-toggle styling is gone. Data cells still need to inherit
   the themed panel (Dash defaults them to white — matters in dark mode).
   ---------------------------------------------------------------------------- */
.dash-spreadsheet-inner td.dash-cell { background-color: transparent !important; }
/* Dash also paints the SELECTED row's <tr> ~white by default (rgb(253,253,253)) — invisible on
   the light panel but a glaring white bar in dark mode (with light text on it). The read-only
   table needs no row-selection highlight, so neutralise it in both themes (the active cell keeps
   its subtle teal border). This matches the no-highlight light-mode behaviour. */
.dash-spreadsheet-inner tr { background-color: transparent !important; }
/* dcc.Input(type="search") wraps the <input> in a div — our box styling is on that wrapper
   (.osd-search); make the inner input blend into it (transparent, borderless, themed text)
   so it reads as one box and themes correctly in dark mode. */
.osd-search input { width: 100%; background: transparent; border: none; outline: none;
  color: var(--ink); font: inherit; }
.osd-search input::placeholder { color: var(--faint); }
/* the box border is set via inline style on the wrapper, so the focus highlight needs
   !important to override it (input has outline:none, so this is the only focus indicator). */
.osd-search:focus-within { border-color: var(--primary) !important; }

/* Country / destination picker (Increment 28) — theme dcc.Dropdown (Dash's own dropdown, not
   react-select) for light + dark; Dash ships a light default (dark placeholder/options) that is
   unreadable on the dark panel. The menu is portaled to <body>, so the menu/option/search rules
   are global — OSD has a single dropdown, so this is safe. */
.osd-country-picker .dash-dropdown-trigger { background: var(--panel); border: 1px solid var(--line);
  border-radius: 9px; color: var(--ink); }
.osd-country-picker .dash-dropdown-placeholder { color: var(--faint); }
.osd-country-picker .dash-dropdown-value { color: var(--ink); }
.dash-dropdown-content, .dash-dropdown-options { background: var(--panel) !important;
  border: 1px solid var(--line) !important; }
.dash-dropdown-search, .dash-dropdown-search-container input { background: var(--panel) !important;
  color: var(--ink) !important; border: 1px solid var(--line) !important; }
.dash-options-list-option { background: transparent !important; color: var(--ink) !important; }
.dash-options-list-option:hover,
.dash-options-list-option.selected { background: var(--tint-teal) !important; }

/* ----------------------------------------------------------------------------
   Print / Save-PDF — a clean, stamped snapshot. Hide interactive chrome; reveal the
   print-only stamp + map note. The WebGL map can print blank, so it is omitted from
   print and replaced by a note (the table is the data). !important beats the inline
   display:none on the print-only elements.
   ---------------------------------------------------------------------------- */
@media print {
  #appbar, #filter-sidebar, #refresh-btn, #refresh-note, #ww-chip, #clear-filter,
  a[href="#about"], .osd-export, #map, .osd-feeds-window { display: none !important; }
  #print-stamp, #map-print-note { display: block !important; }
  #print-stamp { font-family: ui-monospace, "JetBrains Mono", monospace; font-size: 11px;
    color: #5a6b7b; border-top: 1px solid #e4e9ef; border-bottom: 1px solid #e4e9ef;
    padding: 8px 0; margin: 10px 0 4px; }
  details#about { break-inside: avoid; }
  body { background: #fff !important; background-image: none !important; }
  a { color: #0b7488 !important; text-decoration: none; }
}
