/* ==========================================================================
   DYBO Finance & Tax dashboard — built on the DYBO design system.

   `vendor/dybo-theme.css` is a verbatim copy of dybo-design's `theme.css` and
   owns every brand value (`--dybo-*`). This file never hard-codes a brand hex:
   it maps those tokens onto the app's own semantic names (`--surface`, `--text`,
   `--accent`, ...) and layers on the two things the design system does not
   define — a dark theme and a categorical chart palette.

   The indirection earns its keep twice over: light/dark becomes a variable swap,
   and `js/core.js` reads the series palette straight out of the stylesheet
   instead of duplicating hex codes in two places.
   ========================================================================== */

@import url("/static/vendor/dybo-theme.css");

:root {
  color-scheme: light;

  /* ---- Surfaces: DYBO glassmorphism ---- */
  --bg:            var(--dybo-surface-page);          /* #e6eaf0 */
  --surface:       var(--dybo-surface-card);          /* rgba(240,241,244,.70) */
  --surface-solid: var(--dybo-surface-card-solid);
  --surface-alt:   rgba(255, 255, 255, .55);
  --surface-input: var(--dybo-surface-input);
  /* Opaque equivalent of the glass card, for the few places translucency is
     wrong: sticky table headers (rows would show through) and chart tooltips. */
  --surface-opaque: #edeff3;

  --stroke:        var(--dybo-line-stroke);           /* #ffffff */
  --border:        rgba(255, 255, 255, .85);
  --border-strong: var(--dybo-color-grayish-blue-300);/* #c2c7cf */

  --text:          var(--dybo-color-text-primary);    /* #0a1323 */
  /* `muted-foreground: 216 31% 50%`. The newer design system re-points muted
     text at blue-yonder, the same hue as `--accent` — so secondary text and
     interactive accent now share a hue and separate by lightness alone. Stated
     rather than silently inherited: it is a role remap, not a shade tweak.

     **Darkened to L 44% here, and only in light mode.** The system's own 50%
     reads 3.9:1 on this page — under WCAG AA's 4.5:1 for normal text, and this
     token carries a lot of it: card labels, table headers, the range caption,
     inactive tabs. Hue and saturation are the system's, untouched; only
     lightness moves, and only as far as the first whole percent that clears AA
     against `--bg` (#e6eaf0, the darkest surface ink sits on, so the worst
     case). Measures 4.63:1 there and 4.85:1 on a card. */
  --text-muted:    hsl(216 31% 44%);                  /* #4d6993 */
  --text-faint:    var(--dybo-color-text-gray);       /* #868686 */
  --text-invert:   var(--dybo-color-white);

  --accent:        var(--dybo-color-brand-blue-yonder); /* #5878a7 */
  --accent-strong: var(--dybo-color-brand-logo);        /* #1d335c */
  --accent-soft:   rgba(88, 120, 167, .14);

  /* Ink for text sitting ON a filled accent/danger surface. A token rather than
     a literal because the fill flips lightness between themes: light mode fills
     with deep navy (needs white on top), dark mode fills with a pale blue (needs
     dark ink). Hard-coding white gave 1.7:1 on the dark theme's active menu item. */
  --on-accent:     var(--dybo-color-white);
  --on-danger:     var(--dybo-color-white);

  /* `destructive: 355 95% 60%` and `warning: 30 100% 40%` come from the newer
     design system and are a real change of shade, not a rename: its red is far
     brighter than the vendored `#a21a17` (+24 lightness, +20 saturation) and
     its warning is a darker amber than `#f16c24`. Written as literal HSL rather
     than pointed at a `--dybo-*` token because the vendored theme (v0.1.1)
     still carries the older values — this is the app choosing the newer
     definition, and it reverts to a token the moment the vendor catches up.

     `--bad` is darkened to L 42% on the same terms as `--text-muted` above: the
     system's 60% reads 3.2:1 here, and this token is *text* on every KPI card's
     delta, not just a fill. Hue and saturation are the system's. 4.64:1 on the
     page, and white on the filled version improves to 5.6:1 as a side effect.
     Dark mode keeps the system's value untouched — it measures 4.0:1 there,
     because the surface behind it is navy rather than near-white.

     `--good` is darkened on the same terms and for the same reason, even though
     no design system changed it: the vendored `#20a156` reads **2.76:1**, worse
     than either value above and failing even the 3:1 floor. It sits directly
     beside `--bad` on every KPI card's delta at the same 14px, so leaving it
     would have made the pair inconsistent — one delta legible, its neighbour
     not. Neither design system offers a green *ink* token to defer to: the
     newer one's `success` is a pale badge **background** (L 74%) meant to carry
     dark text, not to be text. Hue and saturation are the vendored token's. */
  --good:          hsl(145 67% 28%);      /* #18773f — from #20a156 */
  --bad:           hsl(355 95% 42%);      /* #d10516 */
  --neutral:       var(--text-muted);
  --good-bg:       var(--dybo-success-bg);
  --warn:          hsl(30 100% 40%);      /* #cc6600 */
  --warn-bg:       var(--dybo-warning-bg);
  --bad-bg:        var(--dybo-danger-bg);

  /* The design system assigns its two radii by *element*, not by size, and
     `tokens.css` says so outright — its own annotation on `--radius-sm: 12px`
     reads "padrão para inputs, selects, cards", and on `--radius-md: 20px`
     "padrão para dropdowns, botões pill". Every card in `components.md` is
     `rounded-[12px]`, and the nav tab is `rounded-[20px]`. This app had the two
     the other way round, so the names below deliberately do not track the token
     they read: `--radius` is the *card/surface* radius (12) and `--radius-pill`
     the *tab* radius (20). Re-pointed here rather than at ~26 call sites. */
  --radius:     var(--dybo-radius-sm);    /* 12px — cards, panels, surfaces */
  --radius-sm:  var(--dybo-radius-sm);    /* 12px — buttons, inputs */
  --radius-alt: var(--dybo-radius-alt);   /* 16px — popovers */
  --radius-pill:var(--dybo-radius-md);    /* 20px — nav tab pills */
  --radius-full:var(--dybo-radius-full);  /* 999px — badges, avatars */

  --shadow:    var(--dybo-elevation-card);
  --shadow-sm: var(--dybo-drop-shadow);
  --shadow-lg: 0 16px 40px rgba(10, 19, 35, .16), 0 4px 12px rgba(170, 180, 192, .40);

  --font: var(--dybo-font-family);
  /* Urbanist has no monospace sibling. Figures use the UI font with
     `tabular-nums` so columns still align, rather than dropping to a mono face
     that would read as foreign against the rest of the type. */
  --font-num: var(--dybo-font-family);

  /* ---- Categorical chart palette (light) ----------------------------------
     Derived, not invented: slots keep DYBO's own OKLCH hue angles — blueYonder
     (#5878a7), high-priority plum (#682a5d), medium-priority orange (#f16c24)
     and resolved teal (#0399ae) — lifted into the lightness/chroma band a chart
     mark needs, since the raw tokens are too desaturated (blue C .082) or too
     dark (plum L .392) to work as line colours.

     Slot ORDER alternates two lightness tiers (L .54 / .70) rather than walking
     the colour wheel. Red-green colour blindness collapses hue but preserves
     lightness, so crossing tiers on every adjacent pair is what carries the
     separation: hue-sorted, teal landed next to plum at deutan dE 3.5, which is
     indistinguishable. Alternating gives dE 15.2.

     DYBO's success green and danger red are deliberately absent. They are
     reserved for the delta colours below, and on a finance chart a red or green
     line reads as a verdict rather than an identity.

     Validated with the dataviz skill's validator against the glass card surface
     (#edeff3): lightness band, chroma floor, protan/deutan separation,
     normal-vision floor and contrast all pass. Four light-tier slots sit under
     3:1 contrast, which the always-present legend and data table discharge — if
     you ever hide the table by default, re-run the validator first.

     The design system defines no categorical palette of its own — checked
     across its tokens, components and reference page; the only multi-hue scales
     it ships are semantic status badges. So this stays local, and stays the
     app's own responsibility to re-validate. Re-run on any change to a series
     colour *or to a chart surface*: the surface is half of the contrast check.

     Assign in fixed order and never cycle: a ninth series is not a generated
     hue. `MAX_KPIS` (12) is above 8, so `js/dashboard.js` warns past that. */
  --series-1: #336dc1;  /* blue   — DYBO blueYonder hue */
  --series-2: #d28f0e;  /* amber  */
  --series-3: #a04990;  /* plum   — DYBO high-priority hue */
  --series-4: #00b4bb;  /* cyan   */
  --series-5: #b04c15;  /* orange — DYBO medium-priority hue */
  --series-6: #aca212;  /* lime   */
  --series-7: #7d57b5;  /* violet */
  --series-8: #00b2ca;  /* teal   — DYBO resolved hue */

  --sidebar-width: 232px;

  /* ---- Layering ------------------------------------------------------------
     `backdrop-filter` (the glass effect) establishes a stacking context on
     every element that uses it — cards, panels, the controls bar, the picker
     itself. With that many glass siblings, DOM order alone decides who paints
     on top of whom, so an overlay that needs to escape its own box (the KPI
     picker's dropdown, positioned absolute and taller than its trigger) can
     end up painted UNDER a later sibling's glass panel even though it is
     visually on top of it. A small explicit scale, applied consistently,
     removes the ambiguity instead of chasing it with one-off magic numbers. */
  --z-raised:   10;   /* a bar that must sit above later glass siblings (.controls) */
  --z-dropdown: 100;  /* floating overlays anchored to a trigger (.picker__panel) */
  --z-modal:    1000; /* a dialog over the whole page (.viewer) */
}

/* Dark theme, taken from the design system's own `.dark` block rather than
   derived here. This used to be a local invention because the Figma library
   shipped a Theme Switch and a `Midnight Sky` scale but never extracted a dark
   variable mode; the newer definition closes that gap, so the guesswork goes.

   **The mechanism stays `data-theme`, deliberately.** The design system toggles
   a `.dark` class on the root and persists it to localStorage. This app cannot:
   its embed contract carries `theme` in the URL and over `postMessage`, and
   `js/core.js` resolves `?theme=auto` to an explicit value on `data-theme`
   (following the OS if it changes). Adopting the values without the plumbing is
   the whole of the deviation — one selector still covers forced and automatic. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:            hsl(218 56% 9%);    /* background  #0a1323 */
  --surface:       hsl(218 35% 13%   / .70);  /* card at bg-card/70 */
  --surface-solid: hsl(218 35% 13%   / .92);
  --surface-alt:   hsl(210 15% 30%   / .30);
  --surface-input: hsl(218 35% 13%   / .70);  /* input */
  --surface-opaque: hsl(218 35% 13%); /* #151f2d — tooltips, sticky headers */

  --stroke:        hsl(210 15% 30%);   /* border */
  --border:        hsl(210 15% 30%);
  --border-strong: hsl(210 15% 40%);

  --text:          hsl(244 20% 96%);   /* foreground */
  --text-muted:    hsl(244 20% 80%);   /* muted-foreground */
  --text-faint:    hsl(244 20% 66%);
  --text-invert:   hsl(218 56% 9%);    /* primary-foreground */

  --accent:        hsl(216 31% 50%);   /* accent — blue-yonder */
  --accent-strong: hsl(244 20% 96%);   /* primary flips to near-white in dark */
  --accent-soft:   hsl(216 31% 50% / .18);

  /* `--accent-strong` is near-white here (primary flips), so ink on it must be
     dark; `--bad` stays a saturated red, which takes white. The two therefore
     do NOT agree in dark mode and must stay separate tokens. */
  --on-accent:     hsl(218 56% 9%);    /* primary-foreground */
  --on-danger:     hsl(244 20% 96%);   /* destructive-foreground */

  --good:          var(--dybo-success-bright);  /* #28ca6b */
  --bad:           hsl(0 78% 55%);     /* destructive (dark) #e63535 */
  --neutral:       var(--text-muted);
  --good-bg:       hsl(150 68% 26%);   /* success-background (dark) */
  --warn:          hsl(30 100% 40%);   /* warning */
  --warn-bg:       hsl(30 100% 22%);   /* warning-background (dark) */
  --bad-bg:        hsl(0 78% 55% / .16);

  --shadow:    0 8px 24px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .30);
  --shadow-sm: 0 4px 4px rgba(0, 0, 0, .35);
  --shadow-lg: 0 16px 40px rgba(0, 0, 0, .55);

  /* Dark steps, re-derived from the same hue angles and validated against the
     dark card surface — not an automatic flip of the light values. Tier
     lightnesses move to .55 / .665 so every mark clears 3:1 on the darker
     surface while adjacent CVD separation stays above the target (dE 10.9).

     Re-validated against #151f2d after the dark theme moved to the design
     system's own values (it was #1e2030). All eight slots clear 3:1 there, so
     the darker surface strictly improved the margin — no re-derivation needed,
     but the check is what established that rather than the assumption. */
  --series-1: #3d71bc;
  --series-2: #c28621;
  --series-3: #9e5190;
  --series-4: #00a8af;
  --series-5: #ae5429;
  --series-6: #a09723;
  --series-7: #7e5db1;
  --series-8: #06a6bc;
}

* { box-sizing: border-box; }

/* Class selectors below set `display`, which would otherwise beat the UA
   stylesheet's rule for [hidden] and leave "hidden" elements on screen. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  /* DYBO body copy is weight 500 — heavier than a typical default. */
  font-weight: var(--dybo-font-weight-medium);
  font-size: var(--dybo-font-size-sm);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* --------------------------------------------------------------------------
   Shell: left menu + content
   -------------------------------------------------------------------------- */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  gap: var(--dybo-space-250);
  max-width: 1560px;
  margin: 0 auto;
  padding: var(--dybo-space-250);
  align-items: start;
}

/* `/embed` and `?chrome=compact` drop the left menu: the DYBO host supplies its
   own navigation, and importing from inside a customer-facing iframe is not a
   thing we want to offer.

   `SERVE_MODE=read_only` drops it for a different reason: that deployment does
   not mount the import router at all, so every menu row but "Dashboard" leads
   to a 404 — and a menu with one row that is already the page on screen is not
   navigation, it is furniture. The brand goes with it rather than being left
   floating over an empty rail. */
.app.is-embedded,
.app.is-readonly { grid-template-columns: minmax(0, 1fr); }
.app.is-embedded .sidebar,
.app.is-readonly .sidebar { display: none; }

.sidebar {
  position: sticky;
  top: var(--dybo-space-250);
  display: flex;
  flex-direction: column;
  gap: var(--dybo-space-250);
  padding: var(--dybo-space-250) var(--dybo-space-200);
  background: var(--surface);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  backdrop-filter: blur(var(--dybo-glass-blur));
  -webkit-backdrop-filter: blur(var(--dybo-glass-blur));
}

.sidebar__brand {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--dybo-space-050) var(--dybo-space-200);
  border-bottom: var(--dybo-border-sm) solid var(--border);
}
.sidebar__logo {
  height: 57.6px;
  width: auto;
  display: block;
}
/* Two chroma-keyed PNGs (navy ink / light ink) rather than one image with a
   CSS filter — the yellow accent must stay identical in both themes, and a
   filter (invert/brightness) that fixes the ink would also shift the accent. */
.sidebar__logo--dark { display: none; }
:root[data-theme="dark"] .sidebar__logo--light { display: none; }
:root[data-theme="dark"] .sidebar__logo--dark { display: block; }

.nav { display: flex; flex-direction: column; gap: var(--dybo-space-050); }

.nav__group {
  font-size: var(--dybo-font-size-sm);
  font-weight: var(--dybo-font-weight-semibold);
  line-height: 1.35;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: var(--dybo-space-150) var(--dybo-space-150) var(--dybo-space-050);
}

/* Sidebar menu: unselected items are DYBO's muted ink (#545f74). */
.nav__item {
  display: flex;
  align-items: center;
  gap: var(--dybo-space-150);
  width: 100%;
  font: inherit;
  font-size: var(--dybo-font-size-sm);
  text-align: left;
  cursor: pointer;
  border: none;
  background: transparent;
  color: var(--text-muted);
  padding: var(--dybo-space-150);
  border-radius: var(--radius-sm);
  transition: background .14s, color .14s;
}
/* `w-5 h-5` — the design system's nav icon size. */
.nav__item svg { width: 20px; height: 20px; flex: none; }
.nav__item:hover { background: var(--surface-alt); color: var(--text); }
.nav__item.is-active {
  background: var(--accent-strong);
  color: var(--on-accent);
  font-weight: var(--dybo-font-weight-semibold);
}
.nav__item[disabled] { opacity: var(--dybo-opacity-intense); cursor: not-allowed; }

/* "Finance" submenu toggle under Data Import — a row shaped like `.nav__item`
   but it expands/collapses `.nav__children` instead of navigating. */
.nav__item--parent { justify-content: space-between; }
.nav__item--parent span { flex: 1; text-align: left; }
.nav__caret {
  width: 12px; height: 12px; flex: none;
  color: var(--text-faint);
  transition: transform .14s;
}
.nav__item--parent[aria-expanded="true"] .nav__caret { transform: rotate(180deg); }

.nav__children {
  display: flex;
  flex-direction: column;
  gap: var(--dybo-space-050);
  /* Indent under the parent row's icon rather than its own left edge, so the
     hierarchy reads at a glance without a connecting line. */
  padding-left: calc(18px + var(--dybo-space-150));
}
.nav__item--child { font-size: var(--dybo-font-size-sm); }

.main {
  display: flex;
  flex-direction: column;
  /* A visible beat between top-level sections (top bar, error banner, page)
     rather than them reading as one continuous block. */
  gap: var(--dybo-space-150);
  min-width: 0;
}

/* Every page (dashboard and each import page) is one `.page` section holding
   several sibling blocks — the domain tabs, the controls bar, the cards grid,
   the chart panel, the table panel, and so on for the dashboard; a form panel,
   a result panel and a log panel for the import pages. `.page` itself had no
   layout rules, so those blocks had no gap between them at all and touched
   directly — this is what gives them their visible separation. */
.page {
  display: flex;
  flex-direction: column;
  gap: var(--dybo-space-100);
}

/* --------------------------------------------------------------------------
   Top bar
   -------------------------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--dybo-space-200);
  flex-wrap: wrap;
  padding: 0 var(--dybo-space-050);
}

/* `font-normal text-lg md:text-2xl leading-[1.35] tracking-[1px]` — the design
   system's page heading (layout.md). The hierarchy comes from size, never from
   weight; a bold H1 is the one thing its typography rules single out. */
.topbar h1 {
  margin: 0;
  font-size: var(--dybo-font-size-xl);   /* 24px */
  font-weight: var(--dybo-font-weight-regular);
  letter-spacing: 1px;
  line-height: 1.35;
}

.topbar__range {
  margin: 2px 0 0;
  color: var(--text-muted);
  font-size: var(--dybo-font-size-sm);
}

.topbar__actions {
  display: flex;
  gap: var(--dybo-space-100);
  align-items: center;
  flex-wrap: wrap;
}

/* Compact chrome for tight DYBO panels: ?chrome=compact */
.app.is-compact .topbar__title h1 { display: none; }
.app.is-compact .panel--table    { display: none; }
/* Only the Chart panel survives compact chrome (`.panel--table` above), so a
   tab bar offering Tabular/Form 1120 would just be two dead buttons. */
.app.is-compact .tabs--view      { display: none; }
.app.is-compact                  { padding: var(--dybo-space-150); gap: var(--dybo-space-150); }

/* --------------------------------------------------------------------------
   Buttons — DYBO button specs
   -------------------------------------------------------------------------- */

.btn {
  font: inherit;
  font-size: var(--dybo-font-size-sm);
  font-weight: var(--dybo-font-weight-medium);
  letter-spacing: 1px;
  cursor: pointer;
  border-radius: var(--radius-sm);
  border: var(--dybo-border-sm) solid transparent;
  background: var(--dybo-color-grayish-blue-50);
  color: var(--text);
  padding: var(--dybo-space-100) var(--dybo-space-250);
  display: inline-flex;
  align-items: center;
  gap: var(--dybo-space-100);
  transition: background .14s, border-color .14s, color .14s, opacity .14s;
}
.btn:hover { background: var(--surface-alt); }
.btn:disabled { opacity: var(--dybo-opacity-intense); cursor: not-allowed; }

.btn--primary {
  background: var(--text);
  color: var(--text-invert);
}
.btn--primary:hover { background: var(--accent-strong); color: var(--on-accent); }
:root[data-theme="dark"] .btn--primary { background: var(--accent-strong); color: var(--on-accent); }

.btn--ghost {
  background: var(--surface);
  border-color: var(--stroke);
  box-shadow: var(--shadow-sm);
}
.btn--ghost:hover { background: var(--surface-solid); }

.btn--danger { background: var(--bad); color: var(--on-danger); }

.btn--link {
  border: none;
  background: none;
  color: var(--accent-strong);
  padding: 2px var(--dybo-space-050);
  font-weight: var(--dybo-font-weight-bold);
  letter-spacing: 0;
}
:root[data-theme="dark"] .btn--link { color: var(--accent); }

.btn--icon {
  padding: var(--dybo-space-100);
  width: 36px; height: 36px;
  justify-content: center;
}
/* `h-6 w-6` — the design system's card-header / icon-button size. */
.btn--icon svg { width: 24px; height: 24px; }

.btn:focus-visible,
.nav__item:focus-visible,
.picker__trigger:focus-visible,
.segmented button:focus-visible,
.tabs button:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}
:root[data-theme="dark"] .btn:focus-visible,
:root[data-theme="dark"] .nav__item:focus-visible,
:root[data-theme="dark"] .tabs button:focus-visible,
:root[data-theme="dark"] input:focus-visible { outline-color: var(--accent); }

/* --------------------------------------------------------------------------
   Domain tabs (Finance / Tax)
   -------------------------------------------------------------------------- */

/* Domain tabs left, view tabs right. `align-items: flex-start` keeps each bar
   its own height rather than stretching the shorter one, and wrapping puts the
   view tabs on their own line on a narrow embed instead of squeezing both. */
.tabs-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dybo-space-100);
  align-items: flex-start;
  justify-content: space-between;
}
/* Right-aligned even after a wrap, where `space-between` has nothing to push
   against because each line holds a single bar. */
.tabs-row .tabs--view { margin-left: auto; }
/* A switcher that belongs to the panel below it rather than to the page
   header, so it stays hard left against that panel's own edge instead of
   drifting to the far side of the content area. */
.tabs-row--panel .tabs--view { margin-left: 0; }
/* Wrap rather than shrink: `.tabs` carries `overflow-x: auto` for a bar too wide
   for the page, and letting flex compress these two would trade a clean wrap for
   a scrollbar inside each pill. */
.tabs-row > .tabs { flex: 0 0 auto; }

.tabs {
  display: flex;
  gap: var(--dybo-space-050);
  padding: var(--dybo-space-050);
  background: var(--surface);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  backdrop-filter: blur(var(--dybo-glass-blur));
  -webkit-backdrop-filter: blur(var(--dybo-glass-blur));
  align-self: flex-start;
  max-width: 100%;
  overflow-x: auto;
}
/* `.navtab` from the design system's own tokens.css, geometry included:
   `height: 26px; padding: 0 12px; border-radius: 20px`, 12px/500, 1px tracking,
   UPPERCASE. The casing is not decorative — it is one of the patterns the
   system lists as never to be broken.

   The height is load-bearing for the *shape*, not just the size: 20px of radius
   on a 26px-tall element is very nearly a full pill (13px would be exactly
   one), which is how the system's tabs read. Left at the 34px this app used
   before, the same radius renders as a rounded rectangle instead — the right
   token producing the wrong silhouette. `inline-flex` + centring because the
   button no longer has vertical padding to position its own label. */
.tabs button {
  font: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  font-size: var(--dybo-font-size-xs);
  font-weight: var(--dybo-font-weight-medium);
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  border: none;
  background: transparent;
  color: var(--text-muted);
  padding: 0 var(--dybo-space-150);
  border-radius: var(--radius-pill);
  white-space: nowrap;
  transition: background .14s, color .14s;
}
.tabs button:hover { color: var(--text); }
.tabs button.is-active {
  background: var(--accent-strong);
  color: var(--on-accent);
  font-weight: var(--dybo-font-weight-semibold);
}
.tabs button[disabled] { opacity: var(--dybo-opacity-intense); cursor: not-allowed; }
.tabs button[disabled]:hover { color: var(--text-muted); }

/* --------------------------------------------------------------------------
   Controls
   -------------------------------------------------------------------------- */

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dybo-space-200) var(--dybo-space-250);
  align-items: flex-end;
  padding: var(--dybo-space-200);
  background: var(--surface);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  backdrop-filter: blur(var(--dybo-glass-blur));
  -webkit-backdrop-filter: blur(var(--dybo-glass-blur));
  /* backdrop-filter makes this bar its own stacking context, which traps the
     picker panel's z-index inside it. The cards and chart below are later
     siblings with their own backdrop-filter stacking contexts, so they'd paint
     over the open dropdown. Lift the whole bar above them — see the `--z-*`
     scale in :root for why this needs a shared token, not a local guess. */
  position: relative;
  z-index: var(--z-raised);
}

.control { display: flex; flex-direction: column; gap: var(--dybo-space-100); min-width: 0; }
.control--kpis { flex: 1 1 260px; max-width: 420px; }

/* DYBO "Uppercase" type preset. */
.control__label {
  font-size: var(--dybo-font-size-xs);
  font-weight: var(--dybo-font-weight-medium);
  line-height: 1.35;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Which company's numbers you're looking at is the most consequential choice
   on the page, so its label gets more weight than the other control labels. */
.control__label--entity {
  font-size: var(--dybo-font-size-sm);
  font-weight: var(--dybo-font-weight-semibold);
  color: var(--text-muted);
}

.segmented {
  display: inline-flex;
  gap: var(--dybo-space-050);
  padding: var(--dybo-space-050);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-sm);
  background: var(--surface-input);
  box-shadow: var(--shadow-sm);
  flex-wrap: wrap;
}
.segmented button {
  font: inherit;
  font-size: var(--dybo-font-size-sm);
  cursor: pointer;
  border: none;
  background: transparent;
  color: var(--text-muted);
  padding: var(--dybo-space-100) var(--dybo-space-150);
  border-radius: var(--dybo-radius-sm);
  white-space: nowrap;
  transition: background .14s, color .14s;
}
.segmented button:hover { background: var(--surface-alt); color: var(--text); }
.segmented button.is-active {
  background: var(--accent-strong);
  color: var(--on-accent);
  font-weight: var(--dybo-font-weight-semibold);
}

/* DYBO input field spec: translucent fill, 1px white stroke, 12px radius,
   4px drop shadow. */
input[type="month"], input[type="search"], input[type="text"],
input[type="date"], input[type="number"], select {
  font: inherit;
  font-size: var(--dybo-font-size-sm);
  color: var(--text);
  background: var(--surface-input);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  padding: var(--dybo-space-100) var(--dybo-space-150);
  max-width: 100%;
}
input::placeholder { color: var(--text-muted); letter-spacing: 1px; }
select { cursor: pointer; }

.entity-select { display: flex; align-items: center; gap: var(--dybo-space-100); }
.entity-select select { min-width: 190px; }
.picker--entity .picker__trigger { min-width: 190px; max-width: 280px; }

/* A single-select row: no checkbox, so the current company is marked instead.
   These rows are <button>s (one click picks and closes) where the KPI picker's
   are <label>s, so the shared .picker__option rule — written for a label — has
   to be joined by a button reset. Without it the browser's own chrome shows
   through: outset border, grey fill, Arial, centred text, shrink-to-fit. */
.picker__option--single {
  width: 100%;
  justify-content: space-between;
  appearance: none;
  background: none;
  border: 0;
  margin: 0;
  font: inherit;
  font-size: var(--dybo-font-size-sm);
  color: inherit;
  text-align: left;
}
.picker__option--single[aria-selected="true"] {
  color: var(--accent-strong);
  font-weight: var(--dybo-font-weight-semibold);
}
.picker__option--single[aria-selected="true"]::after {
  content: "";
  width: 6px;
  height: 10px;
  flex: none;
  border: solid currentColor;
  border-width: 0 1.8px 1.8px 0;
  transform: rotate(45deg) translate(-1px, -1px);
}

/* --------------------------------------------------------------------------
   KPI picker
   -------------------------------------------------------------------------- */

.picker { position: relative; }

.picker__trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dybo-space-100);
  font: inherit;
  font-size: var(--dybo-font-size-sm);
  cursor: pointer;
  text-align: left;
  color: var(--text);
  background: var(--surface-input);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  padding: var(--dybo-space-100) var(--dybo-space-150);
}
.picker__trigger:hover { background: var(--surface-solid); }
.picker__trigger > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.picker__chevron { width: 12px; height: 12px; flex: none; color: var(--text-faint); }
.picker__trigger[aria-expanded="true"] .picker__chevron { transform: rotate(180deg); }

.picker__panel {
  position: absolute;
  /* Above every other glass surface on the page, not just `.controls`'s own
     z-index — a floating overlay anchored to a trigger has to clear whatever
     it visually escapes over, wherever that ends up in the DOM. */
  z-index: var(--z-dropdown);
  top: calc(100% + 6px);
  left: 0;
  width: min(380px, 92vw);
  /* Fully opaque, not glass: a scrolling list of checkboxes read over whatever
     is behind it (cards, chart) is a readability problem the translucent
     surfaces elsewhere on the page don't have to solve. */
  background: var(--surface-opaque);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-alt);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Anchored to the trigger's right edge. The entity picker lives in the topbar's
   right-hand actions group, where a left-anchored panel would hang off-screen. */
.picker__panel--end { left: auto; right: 0; }
/* A list of tax years needs nothing like 380px. */
.picker__panel--narrow { width: max-content; min-width: 200px; max-width: 92vw; }

/* The returns picker sits beside "Export CSV" in a panel head, so its trigger
   is dressed as a ghost button rather than as an input: two controls that do the
   same kind of thing should not look like different kinds of control. It stays a
   `.picker__trigger` for the chevron and the open/close behaviour. */
.picker--returns .picker__trigger {
  width: auto;
  background: var(--surface);
  border-color: var(--stroke);
  font-weight: var(--dybo-font-weight-medium);
  letter-spacing: 1px;
  padding: var(--dybo-space-100) var(--dybo-space-250);
}
.picker--returns .picker__trigger:hover { background: var(--surface-solid); }

/* One return per row, labelled by tax year — one PDF per year, so the year is
   the only thing a row needs to say. */
/* An <a> so the browser handles the download; the reset is the counterpart of
   the one `.picker__option--single` needs for its <button>s. `.picker__option`
   was written for the KPI picker's <label>s and resets no element chrome of its
   own, so anything else placed in a picker list arrives wearing the UA default —
   here, underlined #0000ee. */
.picker__option--file {
  color: var(--text);
  text-decoration: none;
}
.picker__option--file .picker__option-year {
  font-weight: var(--dybo-font-weight-semibold);
}

.picker__search { padding: var(--dybo-space-150); border-bottom: var(--dybo-border-sm) solid var(--border); }
.picker__search input { width: 100%; }

.picker__list { max-height: 340px; overflow-y: auto; padding: var(--dybo-space-100) 0; }

.picker__group {
  font-size: var(--dybo-font-size-xs);
  font-weight: var(--dybo-font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-faint);
  padding: var(--dybo-space-150) var(--dybo-space-200) var(--dybo-space-050);
}

.picker__option {
  display: flex;
  align-items: center;
  gap: var(--dybo-space-150);
  padding: var(--dybo-space-050) var(--dybo-space-200);
  cursor: pointer;
  font-size: var(--dybo-font-size-sm);
}
.picker__option:hover { background: var(--surface-alt); }
.picker__option input { accent-color: var(--accent-strong); flex: none; }
.picker__option.is-disabled { opacity: var(--dybo-opacity-intense); cursor: not-allowed; }

/* One line per indicator. The unit/aggregation sub-line was dropped when the
   catalogue grew to 26: it pushed the list past a screen to restate what the
   axis and the card already show. */
.picker__option-label {
  display: flex;
  align-items: baseline;
  gap: var(--dybo-space-100);
  min-width: 0;
}
.picker__option-unit { font-size: var(--dybo-font-size-xs); color: var(--text-faint); }

.picker__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--dybo-space-100) var(--dybo-space-150);
  border-top: var(--dybo-border-sm) solid var(--border);
  background: var(--surface-alt);
}
.picker__count { font-size: var(--dybo-font-size-xs); color: var(--text-muted); }

/* --------------------------------------------------------------------------
   KPI cards
   -------------------------------------------------------------------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
  gap: var(--dybo-space-100);
}

/* The signature DYBO glass card. The accent is a left border rather than a
   filled header so the translucent fill stays readable over the page. */
.card {
  background: var(--surface);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-left: 3px solid var(--card-accent, var(--border-strong));
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  backdrop-filter: blur(var(--dybo-glass-blur));
  -webkit-backdrop-filter: blur(var(--dybo-glass-blur));
  padding: var(--dybo-space-200);
  display: flex;
  flex-direction: column;
  gap: var(--dybo-space-100);
  min-width: 0;
}

/* The KPI cell's label: `text-base font-medium leading-tight` (components.md).
   Medium, not semibold — the value below it carries the weight. */
.card__label {
  font-size: var(--dybo-font-size-md);
  font-weight: var(--dybo-font-weight-medium);
  letter-spacing: 1px;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: var(--dybo-space-100);
  min-width: 0;
}
.card__label span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card__swatch {
  width: 9px; height: 9px;
  border-radius: 3px;
  flex: none;
  background: var(--card-accent);
}

.card__value {
  font-family: var(--font-num);
  font-size: var(--dybo-font-size-xl);
  font-weight: var(--dybo-font-weight-semibold);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.card__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--dybo-space-100);
  min-width: 0;
}
/* The delta and period text are `nowrap`, so without this the block sets a floor
   on the card's width and a long formatted figure pushes the sparkline out past
   the card edge — which scrolls the whole page sideways on a phone. */
.card__foot > div { min-width: 0; overflow: hidden; }

/* The delta and the periods it compares read as one line: the % move first,
   the "Jun 2026 – Jul 2026" it was computed from right after it. Wraps as a
   unit on narrow cards rather than being clipped or forced onto its own row. */
.card__delta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--dybo-space-100);
  row-gap: 2px;
}

/* Delta colour follows `higher_is_better`, which is nullable: a neutral KPI
   (AR/AP balance) must stay uncoloured rather than defaulting to good or bad. */
.card__delta {
  font-size: var(--dybo-font-size-sm);
  font-weight: var(--dybo-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: var(--dybo-space-050);
  white-space: nowrap;
}
.card__delta.is-good    { color: var(--good); }
.card__delta.is-bad     { color: var(--bad); }
.card__delta.is-neutral { color: var(--neutral); }

.card__compare { font-size: var(--dybo-font-size-sm); color: var(--text-muted); white-space: nowrap; }
.card__spark  { flex: none; }

/* --------------------------------------------------------------------------
   Panels, chart, table
   -------------------------------------------------------------------------- */

.panel {
  background: var(--surface);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  backdrop-filter: blur(var(--dybo-glass-blur));
  -webkit-backdrop-filter: blur(var(--dybo-glass-blur));
  padding: var(--dybo-space-200);
}

.panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--dybo-space-200);
  flex-wrap: wrap;
  margin-bottom: var(--dybo-space-200);
}
/* More than one control in a panel head. `align-items: center` rather than the
   head's own `baseline`: a picker trigger and a button have no shared text
   baseline to line up on, so baseline alignment staggers them by a pixel or two.
   `position: relative` gives the returns picker's panel something to anchor to
   other than the whole page. */
.panel__actions {
  display: flex;
  align-items: center;
  gap: var(--dybo-space-100);
  position: relative;
}

.panel__head h2 {
  margin: 0;
  font-size: var(--dybo-font-size-lg);
  font-weight: var(--dybo-font-weight-regular);
  letter-spacing: 1px;
}
/* Which units the selected indicators are actually in, stated once next to
   "Indicators" rather than repeated as a $ or % sign on every card/row —
   normal case and spacing, since it sits inside `.control__label`'s uppercase,
   letter-spaced "DYBO Uppercase" preset and reads as a label, not a value. */
.panel__unit {
  text-transform: none;
  font-size: var(--dybo-font-size-sm);
  font-weight: var(--dybo-font-weight-medium);
  letter-spacing: normal;
  color: var(--text-muted);
}
.panel__hint { margin: 0; font-size: var(--dybo-font-size-sm); color: var(--text-muted); }

.chart { width: 100%; height: 400px; }

.chart__footnote {
  margin: var(--dybo-space-100) 2px 0;
  font-size: var(--dybo-font-size-xs);
  color: var(--text-faint);
  line-height: 1.5;
}

.table-wrap { overflow-x: auto; }

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--dybo-font-size-sm);
  font-variant-numeric: tabular-nums;
}
.table th, .table td {
  padding: var(--dybo-space-100) var(--dybo-space-150);
  text-align: right;
  white-space: nowrap;
  border-bottom: var(--dybo-border-sm) solid var(--border);
}
/* Sticky cells need an opaque fill: over the glass card the scrolled rows
   underneath would otherwise show straight through the header. */
.table thead th {
  position: sticky;
  top: 0;
  background: var(--surface-opaque);
  color: var(--text-muted);
  /* `sm` + semibold + 1px tracking is the design system's own `.dybo-h6`, one
     step up from the `xs` uppercase token. Both the Tabular view and the Form
     1120 matrix read as dense grids of figures, and at 12px the column headers
     were the smallest text on the page while being the key to reading it.
     The `overflow-x: auto` wrapper absorbs the extra width. */
  font-size: var(--dybo-font-size-sm);
  font-weight: var(--dybo-font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 1px;
  border-bottom: var(--dybo-border-sm) solid var(--border-strong);
}
.table th:first-child, .table td:first-child {
  text-align: left;
  font-weight: var(--dybo-font-weight-semibold);
  position: sticky;
  left: 0;
  background: var(--surface-opaque);
}
.table thead th:first-child { z-index: 1; }
.table tbody tr:hover td { background: var(--surface-alt); }
.table td.is-empty { color: var(--text-faint); }
.table .is-partial-row td:first-child::after {
  content: " *";
  color: var(--text-faint);
  font-weight: var(--dybo-font-weight-regular);
}
.table td.num { font-family: var(--font-num); }

/* Tax matrix: 1120 line items down, years across. */
.table--matrix td:nth-child(2),
.table--matrix th:nth-child(2) { text-align: left; white-space: normal; min-width: 220px; }
.table--matrix .is-group td {
  background: var(--surface-alt);
  font-size: var(--dybo-font-size-xs);
  font-weight: var(--dybo-font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-muted);
  text-align: left;
}
.table--matrix td.is-line {
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
  text-align: left;
  width: 1%;
}

/* Forecast table: three states, and they must never look like each other —
   read from the database (plain), made up by the app (tinted), and typed by
   the reader (tinted plus the accent border). The boundary between measured
   and assumed is then visible at a glance rather than something you have to
   remember which column it was. */
.table--forecast th.is-projected {
  background: var(--surface-alt);
  color: var(--text-muted);
}
.table--forecast td.is-projected { background: var(--surface-alt); }
.table--forecast td.is-override { background: var(--accent-soft); }

/* A month an annual line cannot report in — blank, and visibly *not* one of
   the projected cells you can type into. Left plain rather than tinted: it is
   neither read nor made up, it is simply not a period this row has. */
.table--forecast td.is-na { background: transparent; }

/* The editable cell is a borderless input that reads as a table cell until it
   is focused, so a table of 60 of them does not look like a form. */
.table--forecast .cell-input {
  width: 100%;
  min-width: 84px;
  box-sizing: border-box;
  padding: 2px 4px;
  border: var(--dybo-border-sm) solid transparent;
  border-radius: var(--dybo-radius-sm, 6px);
  background: transparent;
  color: inherit;
  font: inherit;
  font-family: var(--font-num);
  text-align: right;
}
.table--forecast .cell-input:hover { border-color: var(--border); }
.table--forecast .cell-input:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface-opaque);
}
.table--forecast td.is-override .cell-input {
  border-color: var(--accent);
  font-weight: var(--dybo-font-weight-semibold);
}

/* --------------------------------------------------------------------------
   Import pages
   -------------------------------------------------------------------------- */

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--dybo-space-200);
}

.field { display: flex; flex-direction: column; gap: var(--dybo-space-100); min-width: 0; }
.field > label {
  font-size: var(--dybo-font-size-xs);
  font-weight: var(--dybo-font-weight-medium);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-faint);
}
.field__hint { font-size: var(--dybo-font-size-xs); color: var(--text-muted); line-height: 1.5; }

/* Drop zone for Form 1120 PDFs. */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--dybo-space-100);
  padding: var(--dybo-space-400);
  text-align: center;
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-alt);
  background: var(--surface-input);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color .14s, background .14s, color .14s;
}
.dropzone:hover, .dropzone.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
}
.dropzone svg { width: 28px; height: 28px; opacity: .7; }
.dropzone strong { font-weight: var(--dybo-font-weight-semibold); letter-spacing: .5px; }
.dropzone small { font-size: var(--dybo-font-size-xs); color: var(--text-faint); }

/* A text input with a button welded to its right (folder path + Scan). */
.input-row { display: flex; gap: var(--dybo-space-100); align-items: stretch; }
.input-row input { flex: 1 1 auto; min-width: 0; }
.input-row .btn { flex: none; }

.filelist { display: flex; flex-direction: column; gap: var(--dybo-space-050); }
/* A filelist row that is also a checkbox: used by the 1120 folder scan, where
   each row is a company the operator can include or leave out. */
.filelist__item--pick { cursor: pointer; }
.filelist__item--pick:hover { background: var(--surface-alt); }
.filelist__pick { display: flex; align-items: center; gap: var(--dybo-space-150); min-width: 0; }
.filelist__pick input { accent-color: var(--accent-strong); flex: none; }
.filelist__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dybo-space-150);
  padding: var(--dybo-space-100) var(--dybo-space-150);
  background: var(--surface-input);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-sm);
  font-size: var(--dybo-font-size-sm);
}
.filelist__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filelist__size { color: var(--text-faint); font-size: var(--dybo-font-size-xs); flex: none; }

/* Company checklist for the QuickBooks import. */
.checklist {
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--dybo-space-100);
  background: var(--surface-input);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-sm);
}
.checklist__item {
  display: flex;
  align-items: center;
  gap: var(--dybo-space-150);
  padding: var(--dybo-space-100);
  border-radius: var(--dybo-radius-sm);
  cursor: pointer;
  font-size: var(--dybo-font-size-sm);
}
.checklist__item:hover { background: var(--surface-alt); }
.checklist__item input { accent-color: var(--accent-strong); flex: none; }
.checklist__text { display: flex; flex-direction: column; min-width: 0; }
.checklist__text b { font-weight: var(--dybo-font-weight-semibold); }
.checklist__text span {
  font-size: var(--dybo-font-size-xs);
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dybo-space-150);
  flex-wrap: wrap;
}
.toolbar__links { display: flex; gap: var(--dybo-space-100); }

/* Job log. Monospace here is deliberate — it is process output, not UI copy,
   and column alignment carries meaning. */
.log {
  margin: 0;
  padding: var(--dybo-space-200);
  max-height: 340px;
  overflow: auto;
  background: var(--surface-input);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: var(--dybo-font-size-xs);
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-muted);
}
.log:empty::before { content: "No import run in this session yet."; color: var(--text-faint); }

/* DYBO status pill: Tiny Semibold on a tinted background. */
.badge {
  display: inline-block;
  padding: 2px var(--dybo-space-150);
  /* `rounded-full`, not the tab radius — the design system's badge families
     (`.pri`/`.st`/`.lbl`) are all `border-radius: 999px`. */
  border-radius: var(--radius-full);
  font-size: var(--dybo-font-size-xs);
  font-weight: var(--dybo-font-weight-semibold);
  letter-spacing: 1px;
  white-space: nowrap;
}
.badge--idle    { color: var(--dybo-status-brand-fg);    background: var(--dybo-status-brand-bg); }
.badge--running { color: var(--dybo-status-resolved-fg); background: var(--dybo-status-resolved-bg); }
.badge--ok      { color: var(--good); background: var(--good-bg); }
.badge--warn    { color: var(--warn); background: var(--warn-bg); }
.badge--error   { color: var(--bad);  background: var(--bad-bg); }

.spinner {
  width: 13px; height: 13px;
  flex: none;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --------------------------------------------------------------------------
   Notices, empty, loading
   -------------------------------------------------------------------------- */

.notice {
  padding: var(--dybo-space-150) var(--dybo-space-200);
  border-radius: var(--radius-sm);
  font-size: var(--dybo-font-size-sm);
  border: var(--dybo-border-sm) solid transparent;
  line-height: 1.5;
}
.notice--error { background: var(--bad-bg);  border-color: var(--bad);  color: var(--bad); }
.notice--warn  { background: var(--warn-bg); border-color: var(--warn); color: var(--text); }
.notice--info  { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
.notice--ok    { background: var(--good-bg); border-color: var(--good); color: var(--text); }

.empty, .loading {
  padding: var(--dybo-space-400) var(--dybo-space-250);
  text-align: center;
  color: var(--text-muted);
  background: var(--surface);
  border: var(--dybo-border-sm) dashed var(--border-strong);
  border-radius: var(--radius);
}
.empty p { margin: 0 0 var(--dybo-space-050); }
.empty__hint { font-size: var(--dybo-font-size-sm); color: var(--text-faint); }

.loading { border-style: solid; }

.is-busy { opacity: .55; transition: opacity .15s; pointer-events: none; }

/* --------------------------------------------------------------------------
   Document viewer (the Form 1120 preview dialog)
   -------------------------------------------------------------------------- */

.viewer {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--dybo-space-250);
}

/* Its own element rather than a `::before` on `.viewer`, because it is also the
   click target that dismisses the dialog — a pseudo-element cannot carry a
   listener, and putting the listener on `.viewer` itself would make every click
   inside the frame a dismissal too. */
.viewer__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 19, 35, .48);
  /* Blur here rather than on the frame: the frame is the one surface on this
     page that must NOT be glass — a scanned return read through the chart
     behind it is unreadable. Same call as `.picker__panel`. */
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.viewer__frame {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(1100px, 100%);
  /* A return is portrait and a reader wants a whole page of it at once, so this
     takes the height it can get rather than sizing to content. */
  height: min(92vh, 1100px);
  background: var(--surface-opaque);
  border: var(--dybo-border-sm) solid var(--stroke);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.viewer__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--dybo-space-200);
  flex-wrap: wrap;
  padding: var(--dybo-space-200) var(--dybo-space-250);
  border-bottom: var(--dybo-border-sm) solid var(--border);
}
.viewer__titles { min-width: 0; }
/* Same treatment as `.panel__head h2`: the dialog is titled like a panel,
   because that is what it is standing in for. */
.viewer__head h2 {
  margin: 0;
  font-size: var(--dybo-font-size-lg);
  font-weight: var(--dybo-font-weight-regular);
  letter-spacing: 1px;
}
.viewer__hint {
  margin: var(--dybo-space-050) 0 0;
  font-size: var(--dybo-font-size-sm);
  color: var(--text-faint);
  /* The operator's own filename, which can be long and is not worth wrapping
     the header for. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.viewer__actions { display: flex; gap: var(--dybo-space-100); flex: none; }
/* `.btn` is written for <button>; the two actions here are <a>s so that the
   browser handles the download and the new tab. */
.viewer__actions a { text-decoration: none; }

.viewer__doc {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: 0;
  /* The browser's own PDF viewer paints its toolbar on a mid-grey; matching it
     stops a light strip appearing around the page while the file loads. */
  background: var(--surface-alt);
}

/* Set while the dialog is open. Scrolling the page behind a modal moves the
   thing the reader is coming back to, not the document they are reading. */
body.has-modal { overflow: hidden; }

/* --------------------------------------------------------------------------
   Narrow viewports and embeds
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {
  /* The left menu becomes a horizontal strip rather than eating half a phone
     screen. It stays a real menu — nothing is hidden behind a hamburger. */
  .app { grid-template-columns: minmax(0, 1fr); padding: var(--dybo-space-150); }
  .sidebar { position: static; }
  .nav { flex-direction: row; flex-wrap: wrap; }
  .nav__group { display: none; }
  .nav__item { width: auto; }

  /* A collapse/expand caret does not fit a horizontal icon strip, and the
     Finance children are exactly the content this menu exists to reach — so
     at this width they are always shown, inline with the other top-level
     items, rather than tucked behind a toggle. */
  .nav__caret { display: none; }
  .nav__children { padding-left: 0; }
  /* Beats the global `[hidden] { display: none !important; }` rule: same
     importance, and this compound selector is more specific. */
  .nav__children[hidden] { display: contents !important; }
  .nav__item--child { width: auto; }
}

@media (max-width: 640px) {
  .app { gap: var(--dybo-space-150); }
  .controls { gap: var(--dybo-space-150); padding: var(--dybo-space-150); }
  .control--kpis { flex-basis: 100%; max-width: none; }
  .cards { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--dybo-space-100); }
  .card { padding: var(--dybo-space-150); }
  .card__value { font-size: var(--dybo-font-size-lg); }
  /* A fixed 62px sparkline does not fit beside the delta in a 150px card. It is
     decorative (aria-hidden) and the chart below says the same thing, so it goes
     rather than being squeezed into illegibility. */
  .card__spark { display: none; }
  .chart { height: 320px; }
  .topbar h1 { font-size: var(--dybo-font-size-lg); }
  /* Icon-only strip — except the Finance children, which have no icon of
     their own and would render as empty buttons without their label. */
  .nav__item:not(.nav__item--child) span { display: none; }
  .nav__item { padding: var(--dybo-space-150); }

  /* A phone has no room to spend on a margin around a page of a tax return, so
     the dialog takes the whole screen. */
  .viewer { padding: 0; }
  .viewer__frame {
    width: 100%;
    height: 100%;
    border: 0;
    border-radius: 0;
  }
  .viewer__head { padding: var(--dybo-space-150); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
