/* ===== Design tokens =====
   Two hairline weights, from docs/design/DESIGN.md. `--hairline` is passive
   structure (card borders, row separators) — it organizes without asserting.
   `--hairline-strong` is for edges competing with something active (inputs,
   buttons, totals rows). If a new border needs a third value, that's a signal
   to revisit the system in DESIGN.md, not to reach for another gray — the
   running CSS already drifted to 8+ near-identical grays once before this
   existed. New borders should reference these variables, not hardcode a hex. */
:root {
    --hairline: #E6E9EF;
    --hairline-strong: #CFD5E0;

    /* Surface + text, from DESIGN.md — card-sunken is for wells inside a
       card (grid headers, read-only cells); card-hover is row hover, not a
       button state. mute/action-soft are read here for grid affordances. */
    /* Surfaces. The page is the gray field and cards are white on top of it —
       this was inverted (white page, #F9FAFB cards), which made every card
       read as inset and left the segmented-control tracks invisible against
       the card they sat on. Sunken elements *inside* a card stay gray and
       became correct for free once the two outer layers swapped. */
    --canvas: #F7F8FA;
    --card: #FFFFFF;
    --card-sunken: #F1F3F7;
    --card-hover: #FBFCFE;

    /* Stronger than DESIGN.md's `raised` (0 1px 2px rgba(12,35,64,.04)),
       which was invisible against the canvas. DESIGN.md needs revising to
       match rather than this being special-cased. */
    --raised: 0 1px 3px rgba(12, 35, 64, 0.06);
    /* The one elevation for floating surfaces (dropdown menus, calendar
       popups, tooltips). Dash ships its own two-layer near-black shadow on
       these; this replaces it so a popup reads as the same system as a card,
       one step further off the page. */
    --popover: 0 6px 16px rgba(12, 35, 64, 0.10),
        0 2px 5px rgba(12, 35, 64, 0.06);
    --ink: #0C2340;
    --mute: #8A94A6;
    /* body and faint were specified in DESIGN.md from the start but never
       wired. Their absence is the direct cause of the ad-hoc text grays
       across .rt-*, .tk-*, .gate-* and friends. */
    --body: #475569;
    --faint: #AEB6C4;

    /* Action is indigo — see DESIGN.md. Chosen over a navy-adjacent blue that
       didn't separate from brand-navy, and over teal, which collides with
       success green. Teal survives only as the timeline's range fill, where
       it is never adjacent to a semantic chip. */
    --action: #4F46E5;
    --action-hover: #4338CA;
    --action-soft: #EEF2FF;

    /* Semantic — flat vs. -soft/-deep is a judgment call (see DESIGN.md
       Semantic section), not a fixed rule: flat text for a dense column of
       values read as a set (Biggest Movers), -soft/-deep chip for a single
       standalone status word. warning-soft runs hot — reserve for rare,
       genuinely serious cases; routine callouts stay neutral gray with
       warning-colored text. */
    --success: #16A34A;
    --success-soft: #F0FDF4;
    --success-deep: #15803D;
    --danger: #DC2626;
    --danger-soft: #FEF2F2;
    --danger-deep: #B91C1C;
    --warning: #D97706;
    --warning-soft: #FFF7ED;
    --warning-deep: #B45309;
}

/* ===== Brand fonts =====
   Self-hosted, SIL OFL — free for commercial use, no attribution required in
   the running app. D-DIN is a signage/instrument face: reserved for headline
/* ===== Design-system scope =====
   `.ds-page` is the shared scope for every themed page. It replaced
   `.expenses-page` on 2026-08-02 (170 selectors) when the Expenses pilot
   rolled out to a second page: extending each selector per page would have
   grown all 170 rules again on page three.

   Add the class to a page's root container to opt it in. Keep page-specific
   classes (`.expenses-page`, etc.) for rules that must NOT generalize.
   ============================================================ */
   figures and page titles only (see .ds-page rules below), never for
   anything that stacks in a column — it has no tabular-numeral variant, so a
   money column set in it saws left-right. Geist covers everything else and
   carries real tabular numerals via `font-variant-numeric: tabular-nums`. */
@font-face {
    font-family: "D-DIN";
    src: url("fonts/D-DIN.woff2") format("woff2");
    font-weight: 400;
    font-display: swap;
}
@font-face {
    font-family: "D-DIN";
    src: url("fonts/D-DIN-Bold.woff2") format("woff2");
    font-weight: 700;
    font-display: swap;
}
@font-face {
    font-family: "Geist";
    src: url("fonts/Geist[wght].ttf") format("truetype-variations");
    font-weight: 100 900;
    font-display: swap;
}

/* ===== Global Reset ===== */
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    padding: 0;
    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        sans-serif;
    background-color: var(--canvas);
}

/* ===== Expenses page — typography pilot =====
   Scoped to .ds-page only, per the design-system rollout plan: approve
   a component here before it spreads to the rest of the app. */
.ds-page {
    font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        sans-serif;
}

.ds-page h2,
.ds-page .thermo-metric,
.ds-page .kpi-card-value {
    font-family: "D-DIN", "Geist", sans-serif;
}

/* Stat-card type scale — matched to the thermometer card.
   These need the .ds-page prefix to win: `.page-container h4` (0,1,1) and
   `.page-container p` (0,1,1) out-specify the bare `.kpi-card-value` (0,1,0) and
   `.kpi-card-subtitle` (0,1,0) further down this file, so the card's own sizes
   were never actually applying — the value rendered at 1.1rem instead of 1.25rem
   and the subtitle at 0.95rem instead of 0.68rem, which is why a subtitle could
   out-shout the label above it. */
.ds-page .kpi-card-value {
    font-size: 1.9rem;
    font-weight: 700;
    line-height: 2.05rem;
    letter-spacing: -0.4px;
    color: #101828;
    margin-bottom: 0;
}

/* _kpi_label_row() hard-codes minHeight: 3.4rem inline while .thermo-head is
   1.9rem — a 24px difference that pushed every stat-card figure below its
   neighbouring thermometer figure. Overridden here rather than fixed in
   components/kpi_card.py because that component is shared with the other pages,
   which are not part of this pilot. !important is required to beat the inline
   style. Fold this into the component when the rollout goes site-wide. */
.ds-page .kpi-card-label {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.6px;
    color: #8a94a6;
    min-height: 1.9rem !important;
}

.ds-page .kpi-card-subtitle {
    font-size: 0.78rem;
    font-weight: 500;
    color: #8a94a6;
    margin-top: 3px;
}

/* AG Grid v33 sets its font on .ag-root-wrapper from --ag-font-family, so a
   font-family on .ag-theme-alpine is inherited-over by the grid's own rule.
   Setting the variable is the supported override. */
.ds-page .ag-theme-alpine {
    --ag-font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, sans-serif;
    font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        sans-serif;
}
.ds-page .ag-theme-alpine .ag-cell {
    font-variant-numeric: tabular-nums;
}

/* ===== Timeline range slicer =====
   Replaces the Lookback + Forecast pill rows. One axis, Today fixed in the
   middle, a teal band spanning the selected range so the *span* is legible
   before any label is read. Stops whose window exceeds the holding period are
   omitted server-side, so the axis ends where the history does.

   The bar's lift is deliberately stronger than the `raised` elevation token
   (0 1px 2px rgba(12,35,64,.04)) — against the #F7F8FA canvas that token was
   invisible. See docs/design/rollout-plan.md; the token needs revising rather
   than this rule being special-cased. */
.ds-page .tl-wrap {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
}
.ds-page .tl-bar {
    display: inline-flex;
    align-items: stretch;
    background: #ffffff;
    border: 1px solid var(--hairline);
    border-radius: 6px;
    padding: 4px;
    box-shadow: var(--raised);
}
.ds-page .tl-stop {
    padding: 9px 15px;
    font-size: 0.78rem;
    color: var(--mute);
    white-space: nowrap;
    display: flex;
    align-items: center;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.12s ease, color 0.12s ease;
}
.ds-page .tl-stop:hover {
    color: var(--ink);
}
.ds-page .tl-mid {
    background: #ccfbf1;
    color: #0f766e;
}
.ds-page .tl-end {
    background: #0d9488;
    color: #ffffff;
    font-weight: 600;
    border-radius: 6px;
}
.ds-page .tl-now {
    font-weight: 600;
}
/* The two anchors are matched in width rather than sized to their text, so the
   axis reads symmetrical even though "Purchase" and "Payoff" differ by two
   characters. */
.ds-page .tl-stop:first-child,
.ds-page .tl-stop:last-child {
    min-width: 104px;
    justify-content: center;
}
.ds-page .tl-stop.tl-now:not(.tl-mid):not(.tl-end) {
    color: var(--ink);
}
.ds-page .tl-range {
    font-size: 0.78rem;
    color: var(--ink);
    white-space: nowrap;
}
.ds-page .tl-sep {
    width: 1px;
    height: 16px;
    background: var(--hairline);
}
.ds-page .tl-span {
    font-size: 0.78rem;
    color: var(--mute);
    white-space: nowrap;
}

/* ===== Category tabs =====
   Underline tabs, not pills. Unselected tabs carry no border and no fill —
   that is the whole point: seven bordered boxes above the content read as a
   grid of controls, plain text does not. Hue identity lives in the selected
   tab's underline and text. */
.ds-page .cat-tabs {
    display: flex;
    gap: 2px;
    border-bottom: 1px solid var(--hairline);
    /* Defaults; the category callback overrides these inline per selection. */
    --cat-ink: #0C2340;
    --cat-hue: #0C2340;
}
.ds-page .cat-tabs label {
    padding: 9px 14px;
    font-size: 0.82rem;
    color: var(--mute);
    cursor: pointer;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin: 0 !important;
}
.ds-page .cat-tabs label:not(.selected):hover {
    color: var(--ink) !important;
}

/* Hue identity — the selected tab's underline and text carry the category's
   own color, so the tab row teaches the chart legend without a legend. The
   hue arrives as a CSS variable set on the container by a callback, rather
   than seven :has(input[value=...]) rules: Dash's own .dash-options-list-option
   rule wins the `color` property against those, and .selected is the class
   Dash already puts on the chosen label. "All" keeps navy — it isn't a
   category and isn't in the legend. */
.ds-page .cat-tabs label.selected {
    color: var(--cat-ink) !important;
    font-weight: 600;
    border-bottom-color: var(--cat-hue);
}

/* ===== Panel header =====
   Chart title with the controls that scope it sitting to the right. */
.ds-page .panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.ds-page .panel-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--ink);
}
.ds-page .panel-controls {
    display: flex;
    align-items: center;
    gap: 8px;
}
.ds-page .seg-label {
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mute);
    white-space: nowrap;
    margin-right: 2px;
}
.ds-page .seg-label + .seg-toggle {
    margin-right: 6px;
}
/* The one segmented control. Every mutually-exclusive view switch on this
   page uses it — $/%, CapEx, View, the reserve period, the calendar's Order
   and Color, and Catch-All's mode — replacing five separate implementations
   that each invented their own selected state (teal, near-black, dark slate,
   indigo). Selected is a raised white pill with ink text, never a color:
   a chosen segment is a *state*, and color here would collide with `action`
   (which means "do something") and with the semantic greens and reds.

   Matches both markups: RadioItems render <label> and mark the chosen one
   `.selected`; the calendar's custom control renders <button> and marks it
   `.active`. Inputs are hidden here rather than per call site. */
.ds-page .seg-toggle {
    display: inline-flex;
    align-items: stretch;
    background: var(--card-sunken);
    border-radius: 6px;
    padding: 3px;
    gap: 2px;
}
.ds-page .seg-toggle input {
    display: none;
}
.ds-page .seg-toggle label,
.ds-page .seg-toggle button {
    padding: 4px 11px;
    font: inherit;
    font-size: 0.72rem;
    /* Weight is fixed across states on purpose: bolding the selected option
       widens its text and shoves the rest of the row sideways, which was very
       visible on the calendar's long "Chronological". The raised white pill
       already carries selection without it. */
    font-weight: 500;
    line-height: 1.5;
    color: var(--mute);
    background: transparent;
    border: 0;
    cursor: pointer;
    white-space: nowrap;
    border-radius: 4px;
    margin: 0 !important;
    transition: background-color 0.12s ease, color 0.12s ease;
}
.ds-page .seg-toggle label:hover,
.ds-page .seg-toggle button:hover {
    color: var(--ink);
}
.ds-page .seg-toggle label.selected,
.ds-page .seg-toggle button.active,
.ds-page .seg-toggle .form-check-input:checked + .form-check-label {
    background: var(--card);
    color: var(--ink);
    box-shadow: 0 1px 2px rgba(12, 35, 64, 0.12);
}

/* dbc.RadioItems wraps each option in a .form-check with its own padding and
   inline margin, which would break the track's flex row. dcc.RadioItems and
   the calendar's buttons need none of this. */
.ds-page .seg-toggle .form-check {
    display: flex;
    padding: 0;
    margin: 0 !important;
    min-height: 0;
}

/* Sized down to sit inside a card header alongside a card title. */
.ds-page .seg-toggle-sm label,
.ds-page .seg-toggle-sm button {
    padding: 2px 9px;
    font-size: 0.68rem;
}

/* Catch-All's Simple/Bank switches the whole panel's method rather than one
   card's presentation, so it gets equal-width halves and room to breathe. */
.ds-page .seg-toggle-wide {
    width: 340px;
    padding: 4px;
    gap: 3px;
}
/* The flex child is the .form-check wrapper under dbc, and the label itself
   under dcc — so both have to stretch or the halves come out uneven. */
.ds-page .seg-toggle-wide .form-check,
.ds-page .seg-toggle-wide > label,
.ds-page .seg-toggle-wide > button {
    flex: 1;
}
.ds-page .seg-toggle-wide label,
.ds-page .seg-toggle-wide button {
    flex: 1;
    justify-content: center;
    text-align: center;
    padding: 9px 0;
    font-size: 0.82rem;
}

/* HOA / MGMT ask a yes-no question about the property rather than switching a
   view, so they stay switches — restyled to the system, not converted. */
.ds-page .form-switch .form-check-input {
    width: 34px;
    height: 19px;
    background-color: var(--card-sunken);
    border-color: var(--hairline-strong);
    cursor: pointer;
    box-shadow: none;
}
.ds-page .form-switch .form-check-input:checked {
    background-color: var(--action);
    border-color: var(--action);
}
.ds-page .form-switch .form-check-input:focus {
    box-shadow: 0 0 0 3px var(--action-soft);
    border-color: var(--action);
}
.ds-page .form-switch .form-check-label {
    font-size: 0.85rem;
    color: var(--ink);
    cursor: pointer;
    padding-left: 4px;
}

/* ===== Buttons =====
   Until now every button on this page was raw Bootswatch LUX (app.py loads
   dbc.themes.LUX and nothing here touched `.btn`): uppercase, square-cornered,
   near-black primary. That was never drift — it was an untouched theme.

   This block is CSS-only on purpose. Every `dbc.Button` call in
   pages/expenses.py already encodes its role through `color=` / `outline=`,
   so Bootstrap's own class is the role token and no Python has to change.

   Bootswatch 5.3 splits the two halves of a button, and they have to be
   overridden differently:

   * **Color** is exposed as `--bs-btn-*` custom properties, so setting those
     lets Bootstrap keep resolving hover/active/disabled itself.
   * **Geometry is not.** LUX re-declares `border-radius`, `border-width`,
     `font-size` and `line-height` as *literal* values on `.btn` / `.btn-sm`,
     which beat the variables entirely — a `--bs-btn-border-radius: 6px` that
     computes to `border-radius: 0px` was the first symptom. Geometry
     therefore has to be set as real properties, not vars.

   `.ds-page .btn` is (0,2,0) and beats every bare `.btn-*` rule in the
   theme, which also means `.btn-sm` has to be re-scoped below or this rule's
   sizing would swallow the modifier. */
.ds-page .btn {
    --bs-btn-font-weight: 500;
    /* Disabled is a state we draw ourselves (see the destructive rules), not
       a dimmed version of the enabled one — so no global opacity fade. */
    --bs-btn-disabled-opacity: 1;
    /* Literal, not --bs-btn-*: LUX hard-codes these. 36px tall. */
    border-radius: 6px;
    border-width: 1px;
    padding: 8px 14px;
    font-size: 0.85rem;
    line-height: 1.35;
    font-weight: 500;
    font-family: inherit;
    text-transform: none;
    letter-spacing: 0;
    box-shadow: none;
    transition: background-color 0.12s ease, border-color 0.12s ease,
        color 0.12s ease;
}
.ds-page .btn:disabled,
.ds-page .btn.disabled {
    cursor: not-allowed;
}
/* The in-card variant, 32px — same relationship to the base button that
   .seg-toggle-sm has to .seg-toggle. It is the page's *de facto* standard:
   21 of the 23 dbc.Button calls in pages/expenses.py pass size="sm", because
   almost every button here sits inside a card next to a form row. Same
   specificity as the base rule above, so this has to come after it.

   LUX ships `.btn-sm { font-size: 10px }` as a literal, which is why an
   unstyled sm button rendered at 10px — restate the size here. */
.ds-page .btn-sm {
    border-radius: 6px;
    padding: 6px 12px;
    font-size: 0.78rem;
    line-height: 1.35;
}
/* Focus ring per DESIGN.md, replacing Bootstrap's .25rem 50%-alpha halo. */
.ds-page .btn:focus-visible {
    box-shadow: 0 0 0 3px var(--action-soft);
    outline: none;
}

/* Primary — one per logical form/section, not per page. CapEx's entry form,
   its edit modal and Catch-All's two lists each own a local primary. */
.ds-page .btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--action);
    --bs-btn-border-color: var(--action);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--action-hover);
    --bs-btn-hover-border-color: var(--action-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--action-hover);
    --bs-btn-active-border-color: var(--action-hover);
    --bs-btn-disabled-color: var(--mute);
    --bs-btn-disabled-bg: var(--card-sunken);
    --bs-btn-disabled-border-color: var(--hairline-strong);
}

/* Secondary — white field, hairline-strong edge, ink text. Both the filled
   and outline classes land here; the page only uses `outline=True`, but a
   plain color="secondary" shouldn't silently render as LUX gray. */
.ds-page .btn-secondary,
.ds-page .btn-outline-secondary {
    --bs-btn-color: var(--ink);
    --bs-btn-bg: var(--card);
    --bs-btn-border-color: var(--hairline-strong);
    --bs-btn-hover-color: var(--ink);
    --bs-btn-hover-bg: var(--card-sunken);
    --bs-btn-hover-border-color: var(--hairline-strong);
    --bs-btn-active-color: var(--ink);
    --bs-btn-active-bg: var(--card-sunken);
    --bs-btn-active-border-color: var(--hairline-strong);
    --bs-btn-disabled-color: var(--mute);
    --bs-btn-disabled-bg: var(--card);
    --bs-btn-disabled-border-color: var(--hairline);
}

/* Destructive — three distinct states, not one color at three opacities.
   At rest it's an outline: an audit of the running page found at most one
   danger button visible at a time, so a solid red field bought nothing and
   cost a loud red sitting next to Add in three places. The fill is saved for
   hover — the moment of intent, right before the click lands. */
.ds-page .btn-danger,
.ds-page .btn-outline-danger {
    --bs-btn-color: var(--danger);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--danger);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--danger);
    --bs-btn-hover-border-color: var(--danger);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--danger-deep);
    --bs-btn-active-border-color: var(--danger-deep);
    /* Third state. Delete Entry before a row is selected must read as inert
       at a glance, not as a quieter red — a disabled button has no hover to
       explain itself with. */
    --bs-btn-disabled-color: var(--mute);
    --bs-btn-disabled-bg: var(--card);
    --bs-btn-disabled-border-color: var(--hairline-strong);
}
.ds-page .btn-outline-danger:focus-visible,
.ds-page .btn-danger:focus-visible {
    box-shadow: 0 0 0 3px var(--danger-soft);
}

/* Tertiary / ghost — no field, action text, action-soft on hover. */
.ds-page .btn-link {
    --bs-btn-color: var(--action);
    --bs-btn-hover-color: var(--action-hover);
    --bs-btn-active-color: var(--action-hover);
    --bs-btn-disabled-color: var(--mute);
    text-decoration: none;
}
.ds-page .btn-link:hover {
    background: var(--action-soft);
}
/* The disclosure ghost ("Other adjustments") used p-0 to sit flush with the
   text above it, which leaves a hover tint no room to breathe. Padding it and
   pulling it back by the same amount keeps the optical alignment. */
.ds-page .btn-ghost-inline {
    padding: 3px 8px;
    margin-left: -8px;
    border-radius: 6px;
}

/* ===== Fields: inputs, affixes, dropdowns, date pickers =====
   One field geometry for everything you type into or pick from: 32px tall,
   6px radius, white, a hairline-strong edge, and an action border plus
   focus-ring on focus. That matches the 32px `sm` button, so a form row of
   [date picker][dropdown][amount][Add] lines up on one baseline.

   Two surprises worth recording, both found by reading the live DOM rather
   than trusting the docs:

   1. **This app is on Dash 4.x, not 2.x.** `dcc.Dropdown` is no longer
      react-select — it renders a Radix listbox (`.dash-dropdown` trigger,
      `.dash-dropdown-content` popup), and `DatePickerSingle` is no longer
      react-dates (`.dash-datepicker-*`, not `.DateInput_*`). Every selector
      below was read off the running page. The old `.dr-dropdown
      .Select-control` rule further down this file is dead code aimed at
      classes that no longer exist.
   2. **Dash injects its component CSS *after* this file.** Ties on
      specificity therefore go to Dash, not to us — so each rule here carries
      `.ds-page` *and* keeps Dash's own selector depth, staying one
      class ahead rather than relying on source order.

   LUX's own inputs are underline-only (border-width 0 with a bottom rule) on
   a gray field, which is what made these read as unstyled gray slabs. */

/* --- Text and number inputs --- */
.ds-page .form-control,
.ds-page .form-control-sm {
    height: 32px;
    min-height: 32px;
    padding: 5px 10px;
    border: 1px solid var(--hairline-strong);
    border-radius: 6px;
    background-color: var(--card);
    font-size: 0.82rem;
    line-height: 1.35;
    color: var(--ink);
    box-shadow: none;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.ds-page .form-control::placeholder {
    color: var(--mute);
}
.ds-page .form-control:focus {
    border-color: var(--action);
    box-shadow: 0 0 0 3px var(--action-soft);
    background-color: var(--card);
    color: var(--ink);
}
.ds-page .form-control:disabled {
    background-color: var(--card-sunken);
    color: var(--mute);
    cursor: not-allowed;
}
/* Money and rates are figures, not prose — right-aligned and tabular, the
   same rule the data grid follows. */
.ds-page input[type="number"].form-control,
.ds-page .affix-field .form-control {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.ds-page .form-label {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--mute);
    margin-bottom: 4px;
}

/* --- Affixes ($ prefix, % suffix) ---
   Replaces two parallel systems: `%` fields borrowed `.value-box`, a 92px
   slider-readout component from Property Setup, while `$` fields were
   unclassed default-size groups — so the same idea rendered at two heights
   with two different addon treatments. `.affix-field` is one class for both
   sides; whether the addon leads or trails is decided by call-site order,
   not by a separate class.

   The affix is *part of* the field, so the border is drawn once around the
   whole group and the seam between addon and input carries none. Focus is
   `:focus-within` on the group for the same reason — focusing the input
   should light the entire control, not the half you happen to be in.

   The unit is a *ghost* — mute text on the field's own ground, no fill and
   no divider. The earlier filled chip made a single control read as two
   glued parts, which is what made the `$` field look like a gray slab.

   Money and percentages then diverge deliberately, because they're used
   differently: a dollar amount is typed once and read (right-aligned, no
   stepper), while a rate is nudged (centred, flanked by `−`/`+`). */
.ds-page .affix-field {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--hairline-strong);
    border-radius: 6px;
    background: var(--card);
    overflow: hidden;
    flex-wrap: nowrap;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.ds-page .affix-field:focus-within {
    border-color: var(--action);
    box-shadow: 0 0 0 3px var(--action-soft);
}
.ds-page .affix-field .form-control,
.ds-page .affix-field .input-group-text {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    height: 30px;
    min-height: 30px;
}
.ds-page .affix-field .form-control:focus {
    box-shadow: none;
}
.ds-page .affix-field .input-group-text {
    background: transparent;
    color: var(--mute);
    font-size: 0.78rem;
    font-weight: 400;
    padding: 0 8px;
    display: flex;
    align-items: center;
}
/* Native spinners are suppressed on every affix field: dollars get no
   stepper at all, and percentages get the flanking pair below. Chrome's own
   arrows are ~10px, hover-only, and land between the value and the unit —
   a position nobody chose, it's just where the browser puts them. */
.ds-page .affix-field input[type="number"]::-webkit-outer-spin-button,
.ds-page .affix-field input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.ds-page .affix-field input[type="number"] {
    -moz-appearance: textfield;
}

/* Money: the `$` leads, the figure is right-aligned and tabular. */
.ds-page .affix-money .form-control {
    text-align: right;
}
.ds-page .affix-money .input-group-text {
    padding-right: 2px;
}

/* Percent: `−` value `%` `+`. The unit stays welded to the number so the
   stepper never interrupts the pair, which was the complaint about the
   native spinners. Buttons are injected by assets/field_stepper.js — see
   that file for why they aren't in the Python.

   Centring here means centring the *ink* — the glyphs `6%` — not the input's
   box. Two earlier attempts got this wrong in the same way: `text-align:
   center` centres the text inside the input, after which the trailing `%`
   pushes the pair right; and a fixed-width right-aligned box centres the box
   while parking the digits against its right edge, which leaves a wide gap
   after `−` that grows and shrinks with the digit count.

   So the input has to shrink to its content.
   The input's width is set inline by field_stepper.js from the measured
   width of its own text, so the box *is* the ink. Two CSS-only attempts
   failed here and both failed the same way — the box centred while the
   glyphs sat at one end of it — so the width is now computed rather than
   guessed. `field-sizing: content` looked like the clean answer but did not
   collapse the box in this flex context.

   Centring is done with `margin-right: auto` on two elements, never
   `margin-left`. Bootstrap's input-group border-collapse rule
   (`.input-group > :not(:first-child):not(.dropdown-menu)…`) chains six
   `:not()`s, which scores (0,7,0) and owns `margin-left` on every child but
   the first — no practical selector here outranks it. `margin-right` it
   never touches, and the leading `−` is `:first-child` so it escapes the
   rule entirely. One auto after `−` and one after the unit splits the free
   space evenly around the pair.

   Note the `input[type="number"]` qualifier. The shared numeric rule above
   is `.ds-page input[type="number"].form-control` — (0,3,1), because
   the attribute selector counts — which quietly outranks a plain
   three-class selector here. Matching its shape is what makes these apply. */
/* `text-align: center`, not `right`: the box is text-sized, so the only
   slack is the caret allowance and the floor on very short values. Centred,
   that slack splits either side instead of pooling on the left and pushing
   the pair off-centre — worth ~6px on a single digit. */
.ds-page .affix-pct input.form-control {
    flex: 0 0 auto;
    width: 46px;
    min-width: 0;
    text-align: center;
    padding: 0;
}
.ds-page .affix-pct .input-group-text {
    flex: 0 0 auto;
    padding: 0 0 0 2px;
    margin-right: auto;
}
.ds-page .affix-pct .affix-step:first-child {
    margin-right: auto;
}
/* Unit-less variant — a plain counter (deposit in months) whose unit is
   spelled out in the text beside it. With no unit element to carry the
   trailing auto margin, the input carries it. */
.ds-page .affix-count input.form-control {
    margin-right: auto;
}
/* Deliberately no resting fill. A `card-sunken` ground was tried first and
   read as two gray slabs bracketing the value — heavier than the control
   deserves, and heavier than DESIGN.md asks for anywhere (it never specified
   this ground at all). The glyphs carry the affordance; the tint arrives on
   hover, at the moment of intent. */
.ds-page .affix-step {
    width: 26px;
    flex: 0 0 26px;
    border: 0;
    padding: 0;
    background: transparent;
    color: var(--mute);
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 0.12s ease, color 0.12s ease;
}
.ds-page .affix-step:hover {
    background: var(--action-soft);
    color: var(--action);
}
.ds-page .affix-step:disabled {
    color: var(--hairline-strong);
    cursor: not-allowed;
    background: transparent;
}

/* Width scale. Replaces four unrelated mechanisms that had grown up
   independently — `.value-box`'s 92px cap, an inline `width: 120px`, a 200px
   cap, and nothing at all — which is why one field bled to the full card
   width while another truncated a two-decimal figure. Width is chosen per
   call site from the longest value the field can actually hold, not from
   the column it happens to sit in.

   `sm` is the *floor* for anything with a stepper: two 26px buttons plus
   the 46px value box need 98px, so `xs` overflows and is for money or plain
   numeric fields only. */
.ds-page .fw-xs { width: 76px; }
.ds-page .fw-sm { width: 116px; }
.ds-page .fw-md { width: 144px; }
.ds-page .fw-lg { width: 190px; }
/* The empty-state amount is the hero of the card it sits in, so it keeps a
   larger figure and a taller frame while using the same affix component —
   the one sanctioned size departure. Width now comes from `fw-lg` like any
   other field; the old 200px cap here was a fourth private width mechanism.
   `display: flex` (not inline-flex) is what lets the auto margins centre it. */
.ds-page .expense-empty-value-box {
    display: flex;
    margin-left: auto;
    margin-right: auto;
}
.ds-page .expense-empty-value-box .form-control,
.ds-page .expense-empty-value-box .input-group-text {
    height: 36px;
    min-height: 36px;
}
.ds-page .expense-empty-value-box .form-control {
    font-size: 1rem;
    font-weight: 600;
}

/* --- Dropdown (Dash 4 / Radix) --- */
.ds-page .dash-dropdown {
    min-height: 32px;
    height: 32px;
    padding: 0 10px;
    border: 1px solid var(--hairline-strong);
    border-radius: 6px;
    background: var(--card);
    font-size: 0.82rem;
    color: var(--ink);
    box-shadow: none;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.ds-page .dash-dropdown:hover {
    border-color: var(--mute);
}
.ds-page .dash-dropdown:focus-visible,
.ds-page .dash-dropdown[data-state="open"] {
    border-color: var(--action);
    box-shadow: 0 0 0 3px var(--action-soft);
    outline: none;
}
.ds-page .dash-dropdown .dash-dropdown-value {
    font-size: 0.82rem;
    color: var(--ink);
}
.ds-page .dash-dropdown .dash-dropdown-placeholder {
    color: var(--mute);
}
.ds-page .dash-dropdown .dash-dropdown-trigger-icon {
    color: var(--mute);
    transition: transform 0.12s ease;
}
.ds-page .dash-dropdown[data-state="open"] .dash-dropdown-trigger-icon {
    transform: rotate(180deg);
}

/* Menu. Dash ships a heavy two-layer drop shadow here; `popover` is the
   system's one elevation for floating surfaces. */
.ds-page .dash-dropdown-content,
.ds-page .dash-datepicker-content {
    border: 1px solid var(--hairline);
    border-radius: 6px;
    background: var(--card);
    box-shadow: var(--popover);
    padding: 4px;
}
.ds-page .dash-dropdown-content .dash-dropdown-option {
    padding: 6px 10px;
    border-radius: 4px;
    font-size: 0.82rem;
    color: var(--ink);
    cursor: pointer;
}
.ds-page .dash-dropdown-content .dash-dropdown-option:hover {
    background: var(--card-hover);
}
/* Selected is the one place `action` appears in the menu — a tint, not a
   fill, so a long list doesn't turn into a block of indigo. */
.ds-page .dash-dropdown-content .dash-dropdown-option.selected {
    background: var(--action-soft);
    color: var(--action-hover);
    font-weight: 500;
}

/* The menu's search box was the one place a *second* accent reached the
   screen: Dash styles it with its own purple, which sat next to our indigo
   focus ring on the trigger directly above it. Same field treatment as
   everything else, same `action` focus. */
.ds-page .dash-dropdown-content .dash-dropdown-search-container {
    height: 30px;
    border: 1px solid var(--hairline-strong);
    border-radius: 6px;
    background: var(--card);
    margin-bottom: 4px;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.ds-page .dash-dropdown-content .dash-dropdown-search-container:focus-within {
    border-color: var(--action);
    box-shadow: 0 0 0 3px var(--action-soft);
}
.ds-page .dash-dropdown-content .dash-dropdown-search {
    font-size: 0.82rem;
    color: var(--ink);
    background: transparent;
    border: 0;
    outline: none;
}
.ds-page .dash-dropdown-content .dash-dropdown-search::placeholder {
    color: var(--mute);
}

/* --- Date picker ---
   The border lives on the wrapper, not the input, so the field styling and
   the focus state both have to go there.

   `justify-content: space-between` is load-bearing: Dash auto-sizes the
   input to its own text, so with the default packing the chevron came to
   rest wherever the text happened to end — mid-field, with dead space to
   its right. The dropdown already pins its caret to the edge; this makes
   the two controls agree. */
.ds-page .dash-datepicker-input-wrapper {
    height: 32px;
    padding: 0 10px;
    border: 1px solid var(--hairline-strong);
    border-radius: 6px;
    background: var(--card);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.ds-page .dash-datepicker-input-wrapper > svg {
    flex: 0 0 auto;
    color: var(--mute);
}
.ds-page .dash-datepicker-input-wrapper:focus-within {
    border-color: var(--action);
    box-shadow: 0 0 0 3px var(--action-soft);
}
.ds-page .dash-datepicker-input {
    font-size: 0.82rem;
    color: var(--ink);
    background: transparent;
    border: 0;
    outline: none;
}
.ds-page .dash-datepicker-input::placeholder {
    color: var(--mute);
}

/* Calendar popup. Stock Dash renders 16px day figures on a 34px cell with no
   selected treatment of its own worth keeping. */
.ds-page .dash-datepicker-calendar-wrapper {
    --day-size: 32px;
    padding: 4px;
}
.ds-page .dash-datepicker-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 2px 2px 8px;
}
.ds-page .dash-datepicker-month-nav {
    width: 26px;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--mute);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ds-page .dash-datepicker-month-nav:hover {
    background: var(--card-sunken);
    color: var(--ink);
}
/* The month select and year stepper sit inside the popup and would otherwise
   inherit the full 32px field treatment, which overwhelms a 254px calendar.
   The year stepper is the second place Dash's own purple accent surfaced —
   it ships unstyled, so it gets the same field treatment as everything
   else rather than a third look. */
.ds-page .dash-datepicker-controls .dash-dropdown,
.ds-page .dash-datepicker-controls .dash-input {
    height: 28px;
    min-height: 28px;
    font-size: 0.78rem;
}
.ds-page .dash-datepicker-controls .dash-input-container {
    height: 28px;
    border: 1px solid var(--hairline-strong);
    border-radius: 6px;
    background: var(--card);
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.ds-page .dash-datepicker-controls .dash-input-container:focus-within {
    border-color: var(--action);
    box-shadow: 0 0 0 3px var(--action-soft);
}
.ds-page .dash-datepicker-controls .dash-input-element {
    font-size: 0.78rem;
    color: var(--ink);
    background: transparent;
    border: 0;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.ds-page .dash-datepicker-controls .dash-input-stepper {
    color: var(--mute);
    background: transparent;
    border: 0;
    cursor: pointer;
}
.ds-page .dash-datepicker-controls .dash-input-stepper:hover {
    color: var(--action);
}
.ds-page .dash-datepicker-calendar {
    border-collapse: separate;
    border-spacing: 1px;
}
/* Weekday header: the same eyebrow the grid header uses. */
.ds-page .dash-datepicker-calendar thead th {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mute);
    padding-bottom: 4px;
}
.ds-page .dash-datepicker-calendar td {
    height: 30px;
    border-radius: 6px;
    font-size: 0.8rem;
    color: var(--ink);
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    font-variant-numeric: tabular-nums;
}
.ds-page .dash-datepicker-calendar td:hover {
    background: var(--card-sunken);
}
/* Days from the neighbouring month stay legible but clearly secondary —
   dropping them out entirely makes the grid hard to scan. */
.ds-page .dash-datepicker-calendar td.dash-datepicker-calendar-date-outside {
    color: var(--mute);
    opacity: 0.6;
}
.ds-page .dash-datepicker-calendar td.dash-datepicker-calendar-date-selected {
    background: var(--action);
    color: #fff;
    font-weight: 600;
}
.ds-page .dash-datepicker-calendar td.dash-datepicker-calendar-date-selected:hover {
    background: var(--action-hover);
}
.ds-page .dash-datepicker-calendar td.dash-datepicker-calendar-date-highlighted {
    background: var(--action-soft);
    color: var(--action-hover);
}
.ds-page .dash-datepicker-calendar td.dash-datepicker-calendar-date-disabled,
.ds-page .dash-datepicker-calendar td[aria-disabled="true"] {
    color: var(--mute);
    opacity: 0.4;
    cursor: not-allowed;
    background: transparent;
}

/* ===== Note =====
   One component for every inline message on the page, replacing three
   bespoke ones (`dbc.Alert color="info"`, `.capex-tip`, `.ca-mode-warn`).
   Tone is the only thing that varies, and it maps to *why the message
   exists*, not to how important it feels:

   - `neutral`  — states a constraint or an assumption. The default.
   - `action`   — suggests something the reader can do.
   - `warning`  — flags a real risk of a wrong number.
   - `danger`   — something is broken. Nothing on Expenses uses it yet;
                  it exists so the next person doesn't invent a fourth thing.

   The rule this replaces: the read-only rollup notice was a full-bleed
   saturated blue block. Blue is `action` here, and `action` means "do
   something" — spending it on an explanation is the same category error as
   letting Dash's purple through. Explanations get no colour at all.

   Only `action` carries a field, because it's the only tone offering the
   reader a next step and the only one that has earned the weight. Warning
   follows DESIGN.md's existing rule — routine callouts stay neutral with
   warning-coloured text rather than an orange field, which "runs hot". */
.ds-page .note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--mute);
    margin: 0 0 10px;
}
.ds-page .note-icon {
    flex: 0 0 auto;
    line-height: 1.35;
    font-size: 0.8rem;
    color: var(--mute);
}
.ds-page .note-text {
    flex: 1 1 auto;
    min-width: 0;
}
/* Dismiss sits at the far end and stays quiet until reached for — it's an
   escape hatch, not a call to action. */
.ds-page .note-dismiss {
    flex: 0 0 auto;
    margin-left: auto;
    background: transparent;
    border: 0;
    padding: 0 2px;
    font-size: 0.8rem;
    line-height: 1;
    color: var(--mute);
    cursor: pointer;
    border-radius: 4px;
}
.ds-page .note-dismiss:hover {
    color: var(--ink);
    background: var(--card-sunken);
}

/* Action — the one tone with a field, since it offers a next step. */
.ds-page .note-action {
    align-items: center;
    background: var(--action-soft);
    border-radius: 6px;
    padding: 8px 10px;
    color: var(--action-hover);
}
.ds-page .note-action .note-icon {
    color: var(--action);
}
.ds-page .note-badge {
    flex: 0 0 auto;
    background: var(--action);
    color: #fff;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    border-radius: 999px;
    padding: 2px 8px;
}

/* Warning — neutral ground, warning ink. Per DESIGN.md, `warning-soft`
   runs hot and is reserved for rare, genuinely serious cases. */
.ds-page .note-warning {
    color: var(--warning-deep);
}
.ds-page .note-warning .note-icon {
    color: var(--warning);
}

.ds-page .note-danger {
    color: var(--danger-deep);
}
.ds-page .note-danger .note-icon {
    color: var(--danger);
}

/* Data grid: 36px rows, hairline row separators (never vertical gridlines —
   columns separate by alignment), card-hover on hover, action-soft on
   selection. Alpine is configured through its own custom properties rather
   than fighting its selectors.

   The header carries no fill. DESIGN.md line 511 specs `card-sunken` here,
   but a flat tint doesn't read as sunken — it just reads as a gray band, and
   with a hairline bottom border the header already separates cleanly. Revisit
   only if the grid needs a stronger header on a denser page. */
.ds-page .ag-theme-alpine {
    --ag-header-background-color: transparent;
    --ag-header-column-separator-display: none;
    --ag-row-height: 36px;
    --ag-header-height: 36px;
    --ag-font-size: 13px;
    --ag-borders: solid;
    --ag-border-color: var(--hairline);
    --ag-row-border-color: var(--hairline);
    --ag-cell-horizontal-border: none;
    --ag-row-hover-color: var(--card-hover);
    --ag-selected-row-background-color: var(--action-soft);
}
.ds-page .ag-theme-alpine .ag-header-cell-label {
    color: var(--mute);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.6px;
    text-transform: uppercase;
}

/* Read-only cells carry no resting fill. Editable is the *minority* in these
   grids (Amount alone in most), so tinting everything else turned the table
   gray. Hover on the genuinely-editable cells carries the affordance instead
   — .ro-cell exists only to exclude the rest from that hover, and is applied
   via cellClassRules (cellClass loses to `type: "numericColumn"`). */
.ds-page .ag-theme-alpine .ag-cell:not(.ro-cell):not(.ag-cell-inline-editing):hover {
    background-color: var(--action-soft);
    cursor: text;
}

/* Columns auto-size to their content, so on a wide screen the last column
   stops short of the grid's right edge — leaving row separators hanging in
   white space. AG Grid sets these container widths inline from the summed
   column widths; min-width overrides that without touching the columns
   themselves, and stays inert when the columns genuinely overflow. */
.ds-page .ag-theme-alpine .ag-center-cols-container,
.ds-page .ag-theme-alpine .ag-header-container {
    min-width: 100%;
}

/* Plotly writes font-family as an inline style on every SVG <text>, defaulting
   to Open Sans, so only !important reaches it. Doing it here rather than adding
   a font to six separate update_layout() calls keeps the pilot in one place and
   trivially reversible. */
.ds-page .js-plotly-plot text {
    font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        sans-serif !important;
    /* DESIGN.md makes tabular numerals mandatory on chart axes. Plotly has no
       layout property for this, and it writes an inline `font-variant: normal`
       shorthand on every <text> — which resets the longhand — so this needs
       !important to land, same as the family above. Verified in the live DOM:
       without it, computed font-variant-numeric stayed `normal`. */
    font-variant-numeric: tabular-nums !important;
}

/* ===== App Container ===== */
.app-container {
    display: flex;
    min-height: 100vh;
}

/* ===== Sidebar ===== */
.sidebar {
    width: 240px;
    min-width: 240px;
    background-color: #1a1d23;
    color: #c9d1d9;
    padding: 0;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    overflow-y: hidden;
    z-index: 100;
    display: flex;
    flex-direction: column;
}

/* Scrolls independently between the frozen brand header and footer */
.sidebar-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 20px 20px 12px 20px;
    flex-shrink: 0;
}

.sidebar-logo {
    width: 32px;
    height: 32px;
    object-fit: contain;
}

.sidebar-title {
    font-size: 1.25rem;
    font-weight: 700;
    color: #ffffff;
    letter-spacing: 0.02em;
}

.sidebar-divider {
    border-color: rgba(255, 255, 255, 0.08);
    margin: 0 16px 8px 16px;
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
    padding: 0 8px;
}

/* ===== Live financial summary ===== */
.sidebar-live-link {
    display: block;
    text-decoration: none !important;
    padding: 0 12px;
    margin-top: 6px;
    margin-bottom: 18px;
}
/* Spec: Claude Design project ce8a7e98 (PeakCard.dc.html). Colors, weights and
   tracking are copied verbatim; sizes are the design's ratios rescaled — the
   component is authored on a 380px card and this sidebar is 216px, where the
   literal px values overflow. The 40px hero is the anchor and every other size
   is its design ratio against that. */
.sidebar-live-inner {
    background: #1b1e25;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 13px;
    padding: 17px 16px;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.sidebar-live-link:hover .sidebar-live-inner {
    background: #222630;
    border-color: rgba(255, 255, 255, 0.16);
}
.live-idle { color: #8b93a3; font-size: 0.75rem; line-height: 1.4; }

/* Sub-captions — AT PEAK IN / IF SOLD TODAY (16px of 40 in the spec) */
.live-cap {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: #8b93a3;
}
/* Section headers — AHEAD OF S&P / BEHIND S&P (22px of 40 in the spec) */
.live-cap-lg {
    font-size: 0.9rem;
    letter-spacing: 0.06em;
    line-height: 1.15;
    color: #8b93a3;
}
.live-cap-row { line-height: 1; }
.live-cap-year { font-size: 0.6rem; font-weight: 600; color: #8b93a3; }
.live-cap-spaced { display: block; margin-top: 0; }
.live-muted { font-size: 0.8rem; color: #8b93a3; margin-top: 2px; }

/* "AT PEAK IN  [17 yrs · 2043]" */
.live-peak-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
}
/* At 216px sidebar width the caption + pill only fit on one line if the caption
   can't wrap and the slack between them is reclaimed (tighter tracking, gap and
   pill padding). */
.live-peak-row .live-cap {
    white-space: nowrap;
    letter-spacing: 0.04em;
    flex: 0 0 auto;
}
.live-peak-row .live-pill {
    padding: 4px 9px;
    letter-spacing: 0;
}
.live-pill {
    background: #2a2e37;
    border-radius: 999px;
    padding: 5px 12px;
    font-size: 0.66rem;
    font-weight: 600;
    color: #f5f6f7;
    white-space: nowrap;
}

/* Peak IRR comparison, fenced by a 1px rule above and below */
.live-irr-block {
    margin: 13px 0;
    padding: 13px 0;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.live-irr-row {
    display: flex;
    align-items: center;
    gap: 8px;
}
/* "PEAK / IRR" — same flex-column + gap as .live-irr-col, so this line gap is
   identical to the value/sub-label gap below, not just visually close. */
.live-irr-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex-shrink: 0;
}
.live-irr-label-line {
    font-size: 0.825rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    line-height: 1;
    color: #8b93a3;
    white-space: nowrap;
}
/* S&P and Prop. sit close together as one comparison unit — no "vs" divider,
   so the gap between them is tight while the label/flash keep their own
   breathing room via the row's larger gap. */
.live-irr-pair {
    display: flex;
    align-items: center;
    gap: 5px;
}
/* Each figure stacks its percentage over its label, left-aligned. */
.live-irr-col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    flex-shrink: 0;
}
.live-irr-val { font-size: 0.825rem; line-height: 1; white-space: nowrap; }
.live-irr-sub {
    font-size: 0.825rem;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
}
.live-irr-sp { color: #8b93a3; font-weight: 600; }
.live-irr-sub-sp { color: #8b93a3; }
.live-irr-prop { color: #f5f6f7; font-weight: 700; }
.live-irr-sub-prop { color: #f5f6f7; }
/* The flash gets its own lane on the right so nothing shifts when it fires. */
.live-irr-row .ghost-a, .live-irr-row .ghost-b { margin-left: auto; }

/* IRR change tint — deliberately quieter than the hero background pulse so the
   dollar figures stay the loudest thing in the widget. Distinct keyframe names
   per parity for the same animation-restart reason as the hero pulses. */
@keyframes irrUpA { 0% { color: #22c55e; } 100% { color: #f5f6f7; } }
@keyframes irrUpB { 0% { color: #22c55e; } 100% { color: #f5f6f7; } }
@keyframes irrDownA { 0% { color: #f87171; } 100% { color: #f5f6f7; } }
@keyframes irrDownB { 0% { color: #f87171; } 100% { color: #f5f6f7; } }
.irr-up-a { animation: irrUpA 2.25s ease; }
.irr-up-b { animation: irrUpB 2.25s ease; }
.irr-down-a { animation: irrDownA 2.25s ease; }
.irr-down-b { animation: irrDownB 2.25s ease; }

/* Hero numbers — the two figures that move on every assumption tweak */
.live-hero-row {
    display: flex;
    align-items: baseline;
    /* Only a floor — margin-left:auto on the flash sets the real spacing. Small
       enough that the widest hero + widest flash still fit the 182px line. */
    gap: 4px;
    flex-wrap: nowrap;
    margin: 6px 0;
}
/* Flash sits to the right of the hero number in its own lane — pushed there
   rather than sharing flow, so the number never shifts when it appears. */
.live-hero-row .ghost-a, .live-hero-row .ghost-b { margin-left: auto; }
.live-hero {
    font-size: 1.65rem;
    font-weight: 700;
    color: #f5f6f7;
    line-height: 1;
    letter-spacing: -0.01em;
    border-radius: 4px;
    padding: 0 3px;
    margin-left: -3px;
}

/* SELL alert — shown only when the verdict turns (HOLD stays silent) */
.live-alert {
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: rgba(220, 38, 38, 0.14);
    border: 1px solid rgba(220, 38, 38, 0.4);
    border-radius: 6px;
    padding: 5px 8px;
    margin-bottom: 8px;
}
.live-alert-word { color: #f87171; font-weight: 800; font-size: 0.8rem; letter-spacing: 0.04em; }
.live-alert-reason { display: block; color: #fca5a5; font-size: 0.7rem; }

/* Directional pulse on change. Two parities (a/b) with DISTINCT keyframe names
   per direction — a changed animation-name is what forces the browser to restart
   the animation, so every change re-fires (a shared name only plays once). */
@keyframes pulseUpA { 0% { background: rgba(34, 197, 94, 0.5); } 100% { background: transparent; } }
@keyframes pulseUpB { 0% { background: rgba(34, 197, 94, 0.5); } 100% { background: transparent; } }
@keyframes pulseDownA { 0% { background: rgba(248, 113, 113, 0.5); } 100% { background: transparent; } }
@keyframes pulseDownB { 0% { background: rgba(248, 113, 113, 0.5); } 100% { background: transparent; } }
.pulse-up-a { animation: pulseUpA 2.25s ease; }
.pulse-up-b { animation: pulseUpB 2.25s ease; }
.pulse-down-a { animation: pulseDownA 2.25s ease; }
.pulse-down-b { animation: pulseDownB 2.25s ease; }

/* Inline +/- delta that fades out (distinct keyframe names per parity, same reason) */
@keyframes ghostFadeA { 0% { opacity: 1; } 60% { opacity: 1; } 100% { opacity: 0; } }
@keyframes ghostFadeB { 0% { opacity: 1; } 60% { opacity: 1; } 100% { opacity: 0; } }
.ghost-a { animation: ghostFadeA 4s ease forwards; }
.ghost-b { animation: ghostFadeB 4s ease forwards; }
.ghost-a, .ghost-b {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    /* "−$82.1k" is one token — without these the right-pushed lane gets squeezed
       and the sign breaks onto a second line. */
    white-space: nowrap;
    flex: 0 0 auto;
}
.ghost-up { color: #22c55e; }
.ghost-down { color: #f87171; }

/* ===== Nav Links (Top-Level) ===== */
.nav-link-item {
    color: #9ca3af !important;
    padding: 10px 16px !important;
    border-radius: 6px !important;
    margin-bottom: 2px;
    font-size: 0.9rem;
    font-weight: 500;
    transition: background-color 0.15s ease, color 0.15s ease;
    text-decoration: none !important;
}

.nav-link-item:hover {
    color: #e5e7eb !important;
    background-color: rgba(255, 255, 255, 0.06) !important;
}

.nav-link-item.active {
    color: #ffffff !important;
    background-color: rgba(99, 102, 241, 0.15) !important;
    font-weight: 600;
}

/* Parent items with sub-nav: label left, chevron right */
.nav-link-item.has-subnav {
    display: flex !important;
    align-items: center;
    justify-content: space-between;
}

.nav-chevron {
    display: flex;
    align-items: center;
    margin-left: 8px;
}

/* Tailless arrow built from two borders; points down when collapsed */
.nav-chevron-icon {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
    transition: transform 0.15s ease;
    opacity: 0.8;
}

/* Rotates to point up when the sub-nav is expanded */
.nav-chevron.open .nav-chevron-icon {
    transform: rotate(225deg);
}

/* ===== Nav Sub-Items (Indented, Subordinate) ===== */
.nav-sub-item {
    color: #6b7280 !important;
    padding: 7px 16px 7px 40px !important;
    border-radius: 6px !important;
    margin-bottom: 1px;
    font-size: 0.82rem;
    font-weight: 400;
    transition: background-color 0.15s ease, color 0.15s ease;
    text-decoration: none !important;
}

.nav-sub-item:hover {
    color: #d1d5db !important;
    background-color: rgba(255, 255, 255, 0.04) !important;
}

.nav-sub-item.active {
    color: #e5e7eb !important;
    background-color: rgba(99, 102, 241, 0.1) !important;
    font-weight: 500;
}

/* ===== Content Area ===== */
.content-area {
    margin-left: 240px;
    padding: 32px 40px;
    flex: 1;
    min-height: 100vh;
    background-color: var(--canvas);
}

/* ===== Onboarding gate (blur + unlock overlay) ===== */

/* Blur the analysis charts underneath the gate. pointer-events off so the
   dimmed content can't be interacted with while locked. */
.content-area.content-gated > * {
    filter: blur(6px);
    pointer-events: none;
    user-select: none;
}

.onboarding-gate {
    position: fixed;
    top: 0;
    left: 240px;
    right: 0;
    bottom: 0;
    z-index: 500;
    background: rgba(255, 255, 255, 0.55);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 80px 24px;
    overflow-y: auto;
}

.gate-overlay-inner {
    width: 100%;
    max-width: 440px;
}

.gate-card {
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 16px;
    box-shadow: 0 12px 40px rgba(15, 23, 42, 0.14);
    padding: 28px 28px 24px;
}

.gate-title {
    font-size: 1.35rem;
    font-weight: 700;
    color: #111827;
    margin-bottom: 6px;
}

.gate-subtitle {
    font-size: 0.9rem;
    color: #6b7280;
    line-height: 1.5;
    margin-bottom: 18px;
}

.gate-progress-track {
    height: 6px;
    background: #f1f5f9;
    border-radius: 99px;
    overflow: hidden;
    margin-bottom: 6px;
}

.gate-progress-fill {
    height: 100%;
    background: #38bdf8;
    border-radius: 99px;
    transition: width 0.3s ease;
}

.gate-progress-label {
    font-size: 0.78rem;
    color: #9ca3af;
    margin-bottom: 16px;
}

.gate-item-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Fine-tuning (collapsed) inside the gate card — mirrors the sidebar checklist */
.gate-tuning-group {
    margin-top: 14px;
    border-top: 1px solid #eef2f7;
    padding-top: 10px;
}
.gate-group-summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #64748b;
}
.gate-group-summary::-webkit-details-marker { display: none; }
.gate-group-summary::before {
    content: "▼";
    font-size: 0.72rem;
    color: #94a3b8;
}
.gate-tuning-group[open] > .gate-group-summary::before,
.gate-essentials-group[open] > .gate-group-summary::before {
    content: "▲";
}

.gate-essentials-group {
    margin-top: 6px;
}
.gate-essentials-group .gate-item-list { margin-top: 4px; }
.gate-tuning-group .gate-item-list { margin-top: 4px; }

.gate-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 4px;
    border-top: 1px solid #f1f5f9;
}
.gate-item:first-child {
    border-top: none;
}

.gate-item-icon {
    font-size: 0.95rem;
    width: 16px;
    text-align: center;
    flex-shrink: 0;
}
.gate-icon-done { color: #22c55e; }
.gate-icon-empty { color: #38bdf8; }
.gate-icon-default { color: #9ca3af; }

.gate-item-label {
    font-size: 0.92rem;
    color: #111827;
    flex: 1;
}
.gate-item-done .gate-item-label { color: #6b7280; }

.gate-item-add {
    font-size: 0.85rem;
    font-weight: 600;
    color: #ffffff !important;
    text-decoration: none !important;
    background: #2563eb;
    padding: 4px 14px;
    border-radius: 6px;
}
.gate-item-add:hover { background: #1d4ed8; }

.gate-item-done {
    font-size: 0.8rem;
    color: #22c55e;
    font-weight: 600;
}

/* ===== Page Container ===== */
.page-container {
    max-width: 1200px;
}

.page-container h2 {
    color: #111827;
    font-weight: 700;
    font-size: 1.75rem;
    margin-bottom: 8px;
}

.page-container h4 {
    color: #374151;
    font-weight: 600;
    font-size: 1.1rem;
    margin-bottom: 16px;
}

.page-container p {
    color: #6b7280;
    font-size: 0.95rem;
}

/* ===== Hide Dash default dev tools ribbon in production ===== */
._dash-loading {
    display: none;
}

/* ===== Property Setup Page ===== */

.page-subtitle {
    color: #6b7280;
    font-size: 0.95rem;
    margin-bottom: 28px;
}

.setup-section {
    background-color: var(--card);
    border: 1px solid var(--hairline);
    border-radius: 10px;
    box-shadow: var(--raised);
    padding: 24px 28px;
    margin-bottom: 24px;
}

.section-heading {
    color: #111827;
    font-weight: 600;
    font-size: 1rem;
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid #e5e7eb;
}

.section-subtext {
    color: #9ca3af;
    font-size: 0.82rem;
    margin-top: -12px;
    margin-bottom: 18px;
}

.form-label {
    color: #374151;
    font-size: 0.88rem;
    font-weight: 500;
    margin-bottom: 0;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* ===== Performance assumptions row =====
   Flex, not a 12-column grid: each control takes only the width it needs, so
   five fit comfortably and the labels never wrap. The boxes themselves reuse
   .value-box / .value-box-input (see Property Setup's tax matrix) rather than a
   parallel style — a bespoke max-width let the "%" addon wrap onto its own line. */

.assumption-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    column-gap: 20px;
    row-gap: 14px;
}
/* Natural width only — without this the cells stretch to fill and push the
   last two onto a second line. */
.assumption-cell {
    flex: 0 0 auto;
}
.assumption-cell label,
.assumption-cell .form-label {
    white-space: nowrap;
    font-size: 0.8rem;
}
/* The addon must sit beside the input, never below it. */
.assumption-cell .value-box {
    flex-wrap: nowrap;
}

/* ===== Segmented Toggle (e.g. Tax View: Pre-tax / After-tax) ===== */

/* Bootstrap's form-check-inline puts a 1rem margin between the options, which
   reads as two separate buttons rather than one segmented control. */
.segmented-toggle .form-check-inline {
    margin-right: 0;
}
.segmented-toggle .btn {
    white-space: nowrap;
    border: 1px solid #d1d5db !important;
    background-color: #ffffff;
    color: #374151;
    font-weight: 600;
    font-size: 0.78rem;
    letter-spacing: 0.02em;
    padding: 6px 16px;
}

.segmented-toggle .btn:hover {
    background-color: #f3f4f6;
}

.segmented-toggle .btn-check:checked + .btn {
    background-color: #111827;
    border-color: #111827 !important;
    color: #ffffff;
}

.segmented-toggle .btn-check:checked + .btn:hover {
    background-color: #111827;
}

/* ===== Info Icon & Benchmark Tooltips (CSS-only, hoverable) ===== */

.info-icon {
    color: #9ca3af;
    font-size: 0.8rem;
    cursor: default;
    vertical-align: middle;
    transition: color 0.15s ease;
    user-select: none;
}

.custom-tooltip-wrapper {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* Invisible bridge fills the gap between icon and tooltip panel so the
   mouse doesn't leave the hover zone while traveling between them. */
.custom-tooltip-wrapper::after {
    content: "";
    position: absolute;
    left: 100%;
    top: 0;
    width: 12px;        /* matches the gap + tooltip arrow area */
    height: 100%;
    display: block;
}

.custom-tooltip-content {
    display: none;
    position: absolute;
    left: calc(100% + 10px);
    top: 50%;
    transform: translateY(-50%);
    background-color: #1f2937;
    color: #e5e7eb;
    font-size: 0.8rem;
    text-align: left;
    max-width: 280px;
    padding: 8px 12px;
    border-radius: 6px;
    line-height: 1.5;
    white-space: normal;
    pointer-events: auto;   /* allow mouse events so links are clickable */
    z-index: 9999;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
    /* small left-pointing arrow */
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
}

/* Show the panel when hovering the wrapper OR the panel itself */
.custom-tooltip-wrapper:hover .custom-tooltip-content,
.custom-tooltip-content:hover {
    display: block;
}

.custom-tooltip-wrapper:hover .info-icon {
    color: #6366f1;
}

/* ===== Dropdowns (Dash dcc.Dropdown override) =====
   Removed 2026-08-02. Two rules here targeted `.Select-control` and
   `.Select-menu-outer` — react-select classes from Dash 2's dropdown. This
   app is on Dash 4, where dcc.Dropdown renders a Radix listbox
   (`.dash-dropdown`), so neither selector had matched anything for some
   time; deleting them is a visual no-op on every page. The `.dr-dropdown`
   class itself is still applied in property_setup.py and expenses.py and is
   left in place — it's a usable hook once the rollout reaches those pages.
   The live dropdown styling is in the Fields block near the top of this
   file, scoped to .ds-page. */

/* ===== Nominal Badge ===== */

.badge-nominal {
    font-size: 0.7rem;
    font-weight: 500;
    vertical-align: middle;
    background-color: #6366f1 !important;
}

/* ===== Auto-calc hint beneath rent field ===== */

.form-hint {
    color: #9ca3af;
    font-size: 0.78rem;
    font-style: italic;
}

/* ===== Slider tooltip bubbles — uniform width ===== */

.rc-slider-tooltip-inner {
    min-width: 52px !important;
    text-align: center !important;
}

/* ===== Date Slicer Pills ===== */

.slicer-group {
    display: flex;
    align-items: center;
    gap: 6px;
}

.slicer-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: #6b7280;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.slicer-pills {
    display: inline-flex !important;
    border: 1px solid #d1d5db;
    border-radius: 999px;
    overflow: hidden;
    background: #ffffff;
}

.slicer-pills label {
    /* Padding lives on .dash-options-list-option-text (below), not here — a
       tooltip target must be an element whose own box covers the full pill,
       and content-box sizing on a child can never reach a parent's padding. */
    padding: 0 !important;
    font-size: 0.82rem !important;
    font-weight: 500;
    color: #6b7280;
    cursor: pointer;
    border-right: 1px solid #e5e7eb;
    user-select: none;
    white-space: nowrap;
    transition: background-color 0.15s ease, color 0.15s ease;
    /* Dash 4 adds margin-right via .dash-radioitems-inline — zero it out so
       teal fill reaches both edges of every pill slot with no white gaps. */
    margin: 0 !important;
}

.slicer-pills label:last-child {
    border-right: none;
}

.slicer-pills label:has(input:checked) {
    background-color: #0d9488;
    color: #ffffff;
}

.slicer-pills label:hover:not(:has(input:checked)):not(:has(input:disabled)) {
    background-color: #f3f4f6;
}

/* Lookback windows longer than the holding period are dead (same range as
   Since Purchase), so they're disabled + grayed with a hover note. */
.slicer-pills label:has(input:disabled) {
    color: #cbd5e1;
    cursor: not-allowed;
    background-color: #f9fafb;
}

.slicer-pills label:has(input:disabled) span {
    cursor: not-allowed;
}

/* The generated .dash-options-list-option-text span only wraps the text
   glyphs; a tooltip targeting it (or a native title attribute) only fires
   over the letters themselves. The pill's visual padding is moved onto this
   span (see .slicer-pills label above) so the span's own box IS the full
   pill, and hover works anywhere on it. */
.slicer-pills .dash-options-list-option-text {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 5px 12px;
    height: 100%;
    box-sizing: border-box;
}

.date-range-text {
    font-size: 0.85rem;
    font-weight: 500;
    color: #374151;
    white-space: nowrap;
}

/* ===== KPI Cards ===== */

.kpi-card {
    border: 1px solid var(--hairline);
    border-radius: 8px;
    background: var(--card);
    box-shadow: var(--raised);
}

.kpi-card-wrapper {
    padding: 4px;
    border-radius: 12px;
    cursor: pointer;
    transition: background-color 0.2s ease;
    height: 100%;
}

.kpi-card-wrapper .kpi-card {
    height: 100%;
}

.kpi-card-wrapper.active {
    background-color: #eff6ff;
}

.kpi-card-wrapper:hover:not(.active) {
    background-color: #f9fafb;
}

.kpi-card-label {
    font-size: 0.72rem;
    color: #6b7280;
    margin-bottom: 2px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.kpi-card-value {
    font-size: 1.25rem;
    font-weight: 700;
    color: #111827;
    margin-bottom: 4px;
}

.kpi-sparkline {
    margin: 0 -8px;
}

/* Status border — REMOVED 2026-08-01. The pastel left rail restated the verdict
   the thermometer pill already gives, so it was a second semantic cue competing
   with category color on the same card. The classes are kept as no-ops because
   ~10 call sites build `kpi-border-{status}` class strings; retiring the rule
   here avoids touching every one of them. */

.kpi-border-great,
.kpi-border-good,
.kpi-border-poor,
.kpi-border-neutral {
    border-left: 1px solid #e5e7eb;
}

/* Stat card — delta line + inline share bar */

.kpi-stat-delta {
    font-size: 0.72rem;
    font-weight: 600;
    margin-top: -2px;
    margin-bottom: 4px;
}

.kpi-delta-ok    { color: #16a34a; }
.kpi-delta-alert { color: #dc2626; }
.kpi-delta-neutral { color: #6b7280; }

.kpi-stat-bar {
    height: 12px;
    background: #f1f5f9;
    border-radius: 6px;
    overflow: hidden;
    /* Lands on the same baseline as .thermo-track (143px from card top) so the
       bar reads as one row across the card grid.
       CAUTION: this offset is the remainder after the label, value and subtitle
       above it — change .kpi-card-label min-height, .kpi-card-value font-size,
       or .kpi-card-subtitle metrics and this number must be re-measured, or the
       bars silently drift apart again. */
    margin-top: 35px;
}

.kpi-stat-bar-fill {
    height: 100%;
    background: #64748b;
    border-radius: 6px;
    transition: width 0.3s ease;
}

/* Benchmark bar — fill on a scale sized to fit both the value and the
   benchmark tick, so overshoot stays visible instead of clamping at full. */

.kpi-bench-track {
    position: relative;
    height: 12px;
    background: #f1f5f9;
    border-radius: 6px;
    margin-top: 8px;
}

.kpi-bench-fill {
    height: 100%;
    border-radius: 6px;
    transition: width 0.3s ease;
}

/* Slate rather than near-black, with a white ring: the share bar's fill is now
   navy, and a dark marker sitting on top of a dark fill vanished once the fill
   ran past the benchmark. The ring keeps the tick readable over either ground,
   and slate keeps the benchmark subordinate to the value it is a reference for. */
.kpi-bench-marker {
    position: absolute;
    top: -3px;
    bottom: -3px;
    width: 2px;
    background: #64748b;
    border-radius: 1px;
    box-shadow: 0 0 0 2px #ffffff;
}

.kpi-bench-caption {
    position: relative;
    height: 0.95rem;
    margin-top: 4px;
}

.kpi-bench-caption span {
    position: absolute;
    transform: translateX(-50%);
    white-space: nowrap;
    color: #6b7280;
    font-size: 0.68rem;
    font-weight: 600;
}

/* Ranked mini-bar list (Where the money goes) */

.kpi-rank-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 0;
    font-size: 0.72rem;
}

.kpi-rank-name {
    flex: 0 0 68px;
    color: #4b5563;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.kpi-rank-track {
    flex: 1 1 auto;
    height: 10px;
    background: #f1f5f9;
    border-radius: 5px;
    overflow: hidden;
}

.kpi-rank-fill {
    height: 100%;
    border-radius: 5px;
}

.kpi-rank-val {
    flex: 0 0 auto;
    min-width: 46px;
    text-align: right;
    color: #1f2937;
    font-variant-numeric: tabular-nums;
}

/* Empty state for the ranked list — shown until a category has two years of
   actuals, in place of a full column of blank tracks. */
.kpi-rank-empty {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    min-height: 130px;
    padding: 0 6px;
}

.kpi-rank-empty-title {
    font-size: 0.78rem;
    font-weight: 600;
    color: #6b7280;
    margin-bottom: 4px;
}

.kpi-rank-empty-sub {
    font-size: 0.7rem;
    color: #9ca3af;
    line-height: 1.45;
    max-width: 210px;
}

/* Horizontal 100% composition bar + itemized legend (Expense Composition) */

/* Composition card — the per-row bar carries share *and* category colour, so
   there is no separate legend dot. Deliberately mirrors .kpi-rank-* above: the
   two cards sit side by side and should read as one family. */
.kpi-comp-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}

.kpi-comp-legend {
    display: flex;
    flex-direction: column;
}

.kpi-comp-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 0;
    font-size: 0.72rem;
}

.kpi-comp-name {
    flex: 0 0 68px;
    color: #4b5563;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.kpi-comp-track {
    flex: 1 1 auto;
    height: 10px;
    background: #f1f5f9;
    border-radius: 5px;
    overflow: hidden;
}

.kpi-comp-fill {
    height: 100%;
    border-radius: 5px;
}

.kpi-comp-val {
    flex: 0 0 auto;
    min-width: 46px;
    text-align: right;
    color: #1f2937;
    font-variant-numeric: tabular-nums;
}

/* $/% switcher — the reserve card's segmented control, shrunk to sit in a card
   header rather than a footer. */
/* Rounded on/off switches (HOA / MGMT tab toggles) */
#hoa-enabled-switch.form-check-input,
#mgmt-enabled-switch.form-check-input {
    border-radius: 2em !important;
}

/* Suggested Reserve headline: % on the left, $ conversion on the right */

.kpi-suggested-headline {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.kpi-suggested-dollar {
    font-size: 0.9rem;
    font-weight: 600;
    color: #6b7280;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Compare bars (suggested vs current reserve) */

.kpi-compare-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
    font-size: 0.72rem;
}

.kpi-compare-label {
    flex: 0 0 76px;
    color: #6b7280;
    white-space: nowrap;   /* keep "Reserve /mo" on one line in both views */
}

.kpi-compare-track {
    flex: 1 1 auto;
    height: 10px;
    background: #f1f5f9;
    border-radius: 5px;
    overflow: hidden;
}

.kpi-compare-fill {
    height: 100%;
    border-radius: 5px;
    background: #f59e0b;
}

/* Suggested Reserve — label line + bar row ($ · % / yr) */

.kpi-reserve-rowlabel {
    color: #6b7280;
    font-size: 0.72rem;
    font-weight: 500;
    margin-top: 8px;
    margin-bottom: 3px;
}

.kpi-reserve-barrow {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.72rem;
}

.kpi-reserve-barrow .kpi-compare-track {
    flex: 1 1 auto;
}

.kpi-reserve-amt {
    color: #374151;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.kpi-reserve-dot {
    color: #cbd5e1;
    font-weight: 700;
}

.kpi-reserve-pct {
    color: #6b7280;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* In-card CapEx toggle (segmented) */

/* Pin the toggle footer to the bottom of the card body so the CapEx and
   Reserve-period toggles line up across cards of equal height. */
.kpi-card > .card-body {
    display: flex;
    flex-direction: column;
}

/* Keep every expense KPI card the same height across sub-tabs so switching
   tabs doesn't shift the chart/table below. Scoped to the expenses KPI row so
   the Dashboard cards are unaffected. */
#expense-kpi-row .kpi-card {
    min-height: 268px;
}

.kpi-card-footer {
    margin-top: auto;
    padding-top: 8px;
    border-top: 1px solid #f1f5f9;
}

/* Signal card — icon + message */

.kpi-signal-message {
    font-size: 0.95rem;
    font-weight: 600;
    color: #1f2937;
    line-height: 1.3;
}

.kpi-signal-icon { font-size: 1.05rem; }

.kpi-signal-great { color: #16a34a; }
.kpi-signal-good  { color: #ca8a04; }
.kpi-signal-poor  { color: #dc2626; }
.kpi-signal-neutral { color: #6b7280; }

/* CapEx treatment toggle above the KPI row */

/* Status badge — small pill below the sparkline */

.kpi-status-badge {
    font-size: 0.68rem;
    font-weight: 600;
    padding: 2px 10px;
    border-radius: 12px;
    display: inline-block;
    letter-spacing: 0.02em;
    /* .kpi-card > .card-body is a column flexbox; without this the badge
       (a flex item) stretches to the card's full width instead of hugging
       its own text, despite display:inline-block. */
    align-self: flex-start;
}

.kpi-status-great {
    background-color: #f0fdf4;
    color: #16a34a;
}

.kpi-status-good {
    background-color: #fefce8;
    color: #ca8a04;
}

.kpi-status-poor {
    background-color: #fef2f2;
    color: #dc2626;
}

/* ===== KPI Card Subtitle ===== */

.kpi-card-subtitle {
    font-size: 0.68rem;
    color: #9ca3af;
    margin-top: -2px;
    margin-bottom: 4px;
    font-weight: 500;
}


/* TTM tooltip — pure CSS hover */
.ttm-tooltip {
    position: relative;
    display: inline;
    cursor: help;
    border-bottom: 1px dotted #9ca3af;
}

.ttm-tooltip::after {
    content: "Trailing 12 Months";
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    background-color: #1f2937;
    color: #e5e7eb;
    font-size: 0.7rem;
    padding: 4px 8px;
    border-radius: 4px;
    white-space: nowrap;
    display: none;
    z-index: 999;
    margin-bottom: 4px;
    pointer-events: none;
}

.ttm-tooltip:hover::after {
    display: block;
}

/* ===== Benchmark Threshold Inputs (gradient bar) ===== */

.benchmark-row {
    padding: 12px 20px 16px;
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    margin-top: 12px;
}

.benchmark-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}

.benchmark-label-group {
    display: flex;
    align-items: center;
    gap: 6px;
}

.benchmark-label {
    display: inline;
    font-size: 0.82rem;
    font-weight: 400;
    color: #6b7280;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.benchmark-label-prefix {
    font-weight: 800;
    font-size: 0.92rem;
    color: #374151;
}

.benchmark-info-icon {
    font-size: 0.85rem;
}

.benchmark-reset-btn {
    background: none;
    border: none;
    padding: 0;
    color: #6b7280;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    cursor: pointer;
    text-decoration: underline;
}

.benchmark-reset-btn:hover {
    color: #374151;
}

/* Relative frame: the bar sits at the top, the two chips are pinned
   absolutely under their zone boundaries (left set by the callback). */
.benchmark-bar-inner {
    position: relative;
    height: 66px;
    margin: 0 12px;
}

.benchmark-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 26px;
}

.benchmark-bar-track {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 26px;
    border-radius: 6px;
}

.benchmark-zone-label {
    position: absolute;
    top: 0;
    height: 26px;
    line-height: 26px;
    transform: translateX(-50%);
    font-size: 11px;
    font-weight: 500;
    white-space: nowrap;
    pointer-events: none;
}

/* Draggable handles: a RangeSlider overlaid on the gradient bar. Dash 4's
   RangeSlider renders as Radix primitives (dash-slider-*), not the old
   rc-slider markup — track/range are forced transparent so only the
   gradient bar shows, and the thumbs are restyled as thin vertical ticks
   instead of Dash's default purple circles. */
.benchmark-slider-layer {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 26px;
    /* The Radix slider's own container/root stay a fixed 34px tall and
       top-align by default, which pushed the thumb's overhang entirely to
       the bottom. Centering the whole stack here makes it symmetric. */
    display: flex;
    align-items: center;
}

.benchmark-slider-layer .dash-slider-root {
    align-items: center;
}

.benchmark-slider-layer .dash-slider-track,
.benchmark-slider-layer .dash-slider-range {
    background: transparent !important;
}

.benchmark-slider-layer .dash-slider-thumb {
    width: 3px;
    /* Taller than the 26px bar so it overhangs evenly top and bottom —
       the root is centered via align-items: center above. */
    height: 36px;
    border-radius: 1px;
    background: #374151;
    border: none;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
    cursor: grab;
}

.benchmark-slider-layer .dash-slider-thumb:hover,
.benchmark-slider-layer .dash-slider-thumb:focus-visible {
    background: #111827;
    box-shadow: 0 0 0 4px rgba(55, 65, 81, 0.15);
}

.benchmark-slider-layer .dash-slider-thumb:active {
    cursor: grabbing;
}

/* Read-only value boxes pinned under each handle (full colored border). */
.benchmark-chip {
    position: absolute;
    top: 38px;
    transform: translateX(-50%);
}

.benchmark-value-box {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    font-size: 0.85rem;
    font-weight: 500;
    border-radius: 6px;
    background: var(--card);
    border: 1px solid var(--hairline-strong);
    color: var(--ink);
    white-space: nowrap;
}

/* Status rides the dot, not the border. A full colored border read as a
   field's focus/validation state on something that isn't editable. */
.benchmark-value-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
}

.benchmark-value-box-good .benchmark-value-dot { background: var(--warning); }
.benchmark-value-box-great .benchmark-value-dot { background: var(--success); }

/* ===== Expense Controls Row ===== */

.expense-controls-row {
    padding: 8px 0;
}

/* ===== Expense Empty State (first-time entry) ===== */

/* Wraps the hero chart + content area so the empty-state form can float on
   top of the chart (blurred behind it) instead of requiring a scroll past
   the chart to reach a form stacked below. */
.expense-chart-content-wrap {
    position: relative;
}

.expense-chart-blurred {
    filter: blur(6px);
    pointer-events: none;
    user-select: none;
    /* Guarantees a backdrop tall enough to centre the guided card in, so the
       card sits in the same spot whether the tab's chart is full, sparse, or
       (Catch-All, empty) rendering nothing at all. */
    min-height: 380px;
}

.expense-content-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
}
.expense-content-overlay .expense-empty-wrap {
    padding: 0;
}
.expense-content-overlay .expense-empty-card {
    background-color: #ffffff;
    box-shadow: 0 8px 28px rgba(15, 23, 42, 0.18);
}

/* CapEx onboarding — two numbered steps, built from the same form controls as
   the real logging form so the real one is familiar the second time. */
.capex-ob-lede {
    font-size: 0.85rem;
    color: #6b7280;
    margin-bottom: 18px;
}

.capex-ob-step {
    border: 1px dashed var(--hairline-strong);
    border-radius: 12px;
    background-color: #f8fafc;
    padding: 20px 22px;
    margin-bottom: 16px;
}

/* Step 2 before the reserve is accepted — visibly next, not yet actionable. */
.capex-ob-step-waiting {
    opacity: 0.55;
}

.capex-ob-head {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-bottom: 6px;
}

.capex-ob-num {
    flex: 0 0 22px;
    height: 22px;
    border-radius: 999px;
    background: #111827;
    color: #fff;
    font-size: 0.72rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}

.capex-ob-title {
    font-size: 0.98rem;
    font-weight: 600;
    color: #111827;
}

.capex-ob-done {
    font-size: 0.72rem;
    font-weight: 700;
    color: #16a34a;
}

.capex-ob-desc {
    font-size: 0.82rem;
    color: #6b7280;
    line-height: 1.5;
    margin-bottom: 14px;
    max-width: 620px;
}

/* "$2,500  =  [0.5] %" — the figure leads, the rate explains it. */
.capex-ob-figure-row {
    display: flex;
    align-items: flex-end;
    gap: 18px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.capex-ob-figure-label {
    font-size: 0.72rem;
    font-weight: 600;
    color: #6b7280;
}

.capex-ob-figure {
    font-size: 1.7rem;
    font-weight: 700;
    color: #111827;
    line-height: 1.15;
}

.capex-ob-eq {
    font-size: 1.1rem;
    color: #9ca3af;
    padding-bottom: 4px;
}

.expense-empty-wrap {
    display: flex;
    justify-content: center;
    padding: 40px 0;
}

.expense-empty-card {
    width: 100%;
    max-width: 360px;
    text-align: center;
    padding: 28px 24px;
    border: 1px dashed var(--hairline-strong);
    border-radius: 12px;
    background-color: #f8fafc;
}

.expense-empty-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: #111827;
    margin-bottom: 4px;
}

.expense-empty-desc {
    font-size: 0.85rem;
    color: #6b7280;
    margin-bottom: 16px;
}

.expense-empty-value-box {
    max-width: 200px;
    margin: 0 auto;
}

.expense-empty-input {
    text-align: center;
    font-weight: 600;
    font-size: 1rem;
}

.expense-empty-hint {
    font-size: 0.76rem;
    color: #9ca3af;
    margin: 10px 0 4px;
}

/* ===== Login Page ===== */

.login-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background-color: #0f1117;
}

.login-card {
    background-color: #1a1d23;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 12px;
    padding: 40px 36px;
    width: 100%;
    max-width: 380px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}

.login-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 6px;
}

.login-logo {
    width: 36px;
    height: 36px;
    object-fit: contain;
}

.login-brand-name {
    font-size: 1.4rem;
    font-weight: 700;
    color: #ffffff;
    letter-spacing: 0.02em;
}

.login-tagline {
    color: #6b7280;
    font-size: 0.82rem;
    margin-bottom: 0;
}

.login-divider {
    border-color: rgba(255, 255, 255, 0.08);
    margin: 20px 0 24px 0;
}

.login-btn {
    font-weight: 600;
    letter-spacing: 0.02em;
}

.login-error {
    margin-top: 10px;
    font-size: 0.84rem;
    color: #f87171;
    min-height: 20px;
}

/* ===== Sidebar Setup Checklist ===== */

.sidebar-onboarding-wrap {
    position: relative;
    padding: 0 8px 8px;
}
/* When the checklist is hidden (gated pages) the wrap holds only empty
   placeholders — collapse it so it doesn't leave a gap under the brand. */
.sidebar-onboarding-wrap:not(:has(.onboarding-panel)):not(:has(.onboarding-toast)) {
    padding: 0;
}

.onboarding-panel {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
    padding: 10px 10px 22px;
    margin-bottom: 4px;
}

.onboarding-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.onboarding-title {
    font-size: 0.82rem;
    font-weight: 700;
    color: #e5e7eb;
}

.onboarding-count {
    font-size: 0.72rem;
    color: #9ca3af;
}

.onboarding-progress-track {
    height: 4px;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 99px;
    overflow: hidden;
    margin-bottom: 10px;
}

.onboarding-progress-fill {
    height: 100%;
    background: #38bdf8;
    border-radius: 99px;
    transition: width 0.3s ease;
}

.onboarding-group-label {
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #6b7280;
    margin: 6px 0 3px;
}

/* Fine-tuning collapses by default via native <details> — no JS needed */
.onboarding-tuning-group {
    margin: 6px 0 0;
}

.onboarding-group-summary {
    margin: 0;
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 4px;
}
.onboarding-group-summary::-webkit-details-marker { display: none; }
.onboarding-group-summary::before {
    content: "▼";
    font-size: 0.66rem;
}
.onboarding-tuning-group[open] > .onboarding-group-summary::before,
.onboarding-essentials-group[open] > .onboarding-group-summary::before {
    content: "▲";
}

.onboarding-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 0;
}

.onboarding-item-icon {
    font-size: 0.78rem;
    width: 14px;
    text-align: center;
    flex-shrink: 0;
}
.onboarding-icon-done { color: #22c55e; }
.onboarding-icon-empty { color: #38bdf8; }
.onboarding-icon-default { color: #6b7280; }

.onboarding-item-label {
    font-size: 0.76rem;
    color: #c9d1d9;
    flex: 1;
}
.onboarding-item-done .onboarding-item-label { color: #9ca3af; }

.onboarding-item-add {
    font-size: 0.7rem;
    color: #38bdf8 !important;
    text-decoration: none !important;
    font-weight: 600;
}
.onboarding-item-add:hover { color: #7dd3fc !important; }

/* Anchored to the checklist card's bottom-right corner, inset just inside
   its border so it reads as part of the card rather than a floating link.
   The offset accounts for .sidebar-onboarding-wrap's own bottom padding
   (8px) plus .onboarding-panel's margin-bottom (4px) — those sit *outside*
   the panel's border, so a naive small offset lands the button below the
   card instead of inside it. */
.onboarding-dismiss-btn {
    position: absolute;
    right: 16px;
    bottom: 18px;
    background: rgba(255, 255, 255, 0.05);
    border: none;
    border-radius: 5px;
    color: #6b7280;
    font-size: 0.65rem;
    padding: 2px 7px;
    cursor: pointer;
    width: auto;
    text-align: right;
}
.onboarding-dismiss-btn:hover { color: #9ca3af; background: rgba(255, 255, 255, 0.1); }

.onboarding-toast {
    font-size: 0.74rem;
    color: #9ca3af;
    padding: 10px 12px;
    margin-bottom: 12px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
}

.onboarding-restore-btn {
    background: none;
    border: 1px solid #d1d5db;
    color: #374151;
    font-size: 0.82rem;
    padding: 6px 14px;
    border-radius: 8px;
    cursor: pointer;
    margin-bottom: 12px;
}
.onboarding-restore-btn:hover { background: #f3f4f6; }

/* ===== Sidebar Footer (Sign Out) ===== */

.sidebar-footer {
    flex-shrink: 0;
    margin-top: 0;
    padding: 8px 8px 16px 8px;
}

/* Autosave status — pinned above the account divider, always visible. */
.sidebar-save-status {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 4px 16px 10px;
    font-size: 0.8rem;
    color: #9ca3af;
    opacity: 0.7;
}

.save-status-icon {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25'/%3E%3Cpolyline points='8 16 11 19 16 12'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25'/%3E%3Cpolyline points='8 16 11 19 16 12'/%3E%3C/svg%3E") center / contain no-repeat;
}

.save-status-label {
    line-height: 1;
}

.sidebar-save-status.saved-pulse {
    animation: savePulse 1.1s ease;
}

@keyframes savePulse {
    0%   { color: #34d399; opacity: 1; }
    35%  { color: #34d399; opacity: 1; }
    100% { color: #9ca3af; opacity: 0.7; }
}

.sidebar-account {
    color: #9ca3af;
    font-size: 0.9rem;
    padding: 10px 16px 4px;
    text-align: left;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.sidebar-signout {
    color: #6b7280 !important;
    font-size: 0.84rem !important;
}

/* Capital Performance — time metric strip below the hero chart */
/* Footer band inside the chart card: full-width divider bleeds to the card
   edges (cancelling the card's 24px/28px padding) so the strip reads as one
   object with the graph above it. */
.time-strip-footer {
    border-top: 1px solid #e5e7eb;
    margin: 18px -28px -24px;
    padding: 14px 28px 4px;
}

.time-strip {
    display: flex;
    align-items: stretch;
    gap: 22px;
}

.time-strip-col {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 0 0 auto;
}

/* Fixed to each column's worst-case content (e.g. "24 years ago",
   "−$50.4k/yr") so the strip never reflows while scrubbing the chart. */
.time-strip-col-viewing  { width: 70px; }
.time-strip-col-holding  { width: 90px; }
.time-strip-col-horizon  { width: 110px; }
.time-strip-col-value    { width: 100px; }

.time-strip-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #9ca3af;
    white-space: nowrap;
}

.time-strip-value {
    font-size: 1.05rem;
    font-weight: 700;
    color: #1f2937;
    line-height: 1.2;
    white-space: nowrap;
}

.time-strip-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background-color: #4338ca;
    margin-right: 8px;
    vertical-align: middle;
}

.time-strip-divider {
    width: 1px;
    background: #e5e7eb;
    margin: 2px 0;
}

/* Unify hover info boxes: dbc.Tooltip is dark by default, dbc.Popover (used for
   richer content — links, multi-line text) is light by default. Both are used
   for the same "ⓘ" hover pattern across KPI cards and inputs, so the popover
   is re-themed to match the tooltip's dark style rather than leaving the two
   inconsistent. */
.popover {
    --bs-popover-bg: #1a1d23;
    --bs-popover-border-color: #1a1d23;
    --bs-popover-header-bg: #1a1d23;
    --bs-popover-header-color: #f3f4f6;
    --bs-popover-body-color: #e5e7eb;
    --bs-popover-arrow-border: #1a1d23;
}

.popover .popover-body a {
    color: #a5b4fc;
}

/* Hide dcc.Slider's built-in numeric input box — we supply our own value boxes */
.dash-slider-container .dash-input-container,
.dash-slider-container input.dash-range-slider-input {
    display: none !important;
}

/* ===== Property Setup — financing row layout ===== */

.fin-row {
    display: flex;
    align-items: center;
    gap: 20px;
    margin-bottom: 6px;
    padding: 16px 18px;
    border-radius: 8px;
}
/* Subtle alternating bands so each major section reads as its own block */
.fin-row:nth-of-type(odd) {
    background-color: rgba(15, 23, 42, 0.04);
}
.fin-row:nth-of-type(even) {
    background-color: rgba(15, 23, 42, 0.015);
}
.fin-row-label {
    flex: 0 0 150px;
    font-size: 0.88rem;
    font-weight: 500;
    color: #374151;
}
.fin-row-mid {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0 6px;
}
.fin-row-right {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Fixed-width slot for %/yr value-box; always present (empty for rows without one)
   so the equation columns align at the same X across all rows. */
.fin-val-slot {
    flex: 0 0 96px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
}

/* Fixed-width editable boxes so they line up down the right edge */
.fin-box-price {
    width: 150px;
}
.fin-box-dollar {
    width: 150px;
}
.fin-box-price .form-control,
.fin-box-dollar .form-control {
    text-align: right;
    font-weight: 600;
    color: #111827;
}
/* Editable dollar prefix — visible box, darker gray */
.fin-box-dollar.fin-input .input-group-text {
    color: #4b5563;
    background-color: #e9ecef;
    font-weight: 600;
}
/* Calculated/disabled dollar prefix — no box, just a plain $ */
.fin-box-dollar:not(.fin-input) .input-group-text {
    background-color: transparent;
    border-color: transparent;
    color: #9ca3af;
    font-weight: 400;
    padding-left: 6px;
    padding-right: 2px;
}

/* ===== Property Setup — financing controls ===== */

/* Compact editable readout box on the right end of each slider */
.value-box {
    max-width: 92px;
}
.value-box .value-box-input {
    text-align: center;
    font-weight: 600;
    color: #111827;
    padding-left: 6px;
    padding-right: 6px;
}
.value-box .input-group-text {
    color: #4b5563;
    background-color: #e9ecef;
    font-weight: 600;
    padding-left: 8px;
    padding-right: 8px;
}

/* Vertical loan + down = purchase equation (down payment row) */
.fin-equation-stack {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.fin-eq-row {
    display: flex;
    align-items: center;
    gap: 0;
}
.fin-eq-op {
    flex: 0 0 20px;
    text-align: center;
    color: #9ca3af;
    font-weight: 700;
}
.fin-eq-cap {
    flex: 0 0 100px;
    margin-left: 8px;
    font-size: 0.72rem;
    color: #9ca3af;
    white-space: nowrap;
    overflow: hidden;
}
.fin-eq-sum-row {
    padding-top: 6px;
    border-top: 1.5px solid #d1d5db;
    margin-top: 1px;
}
.fin-box-dollar .form-control:disabled {
    background-color: #f9fafb;
    color: #374151;
    opacity: 1;
}

/* Purchase Date sits on the left of the Purchase Price row */
.fin-date-left {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}
.fin-date-label {
    font-size: 0.88rem;
    font-weight: 500;
    color: #374151;
    margin: 0;
    white-space: nowrap;
}
.fin-date-input {
    max-width: 180px;
}
.fin-price-label {
    font-size: 0.88rem;
    font-weight: 500;
    color: #374151;
    white-space: nowrap;
}

/* Property Configuration — units evenly span the full box width */
.unit-line {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    align-items: end;
    gap: 24px;
    margin-top: 8px;
}
.unit-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.unit-line-label,
.unit-cell .unit-grid-label,
.unit-cell label {
    font-size: 0.82rem;
    font-weight: 500;
    color: #374151;
    margin: 0;
    white-space: nowrap;
}
.unit-cell label.disabled {
    color: #c0c5cd;
}
/* The redesign distributes width via the even grid columns, not by bloating
   any single control — each stays a modest size, centered in its column. */
.unit-count-dd {
    width: 180px;
}
.unit-name-input {
    width: 110px;
    text-align: center;
}

/* Tax Assumptions — Federal/State matrix; value columns flex to fill the box */
.tax-grid {
    display: grid;
    grid-template-columns: max-content 1fr 1fr;
    column-gap: 16px;
    row-gap: 10px;
    align-items: center;
}
/* Federal/State headers and value cells center within their flexible column */
.tax-grid-head:not(:first-child),
.tax-grid > .tax-cell {
    justify-self: center;
}
.tax-grid-head {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6b7280;
    text-align: center;
}
.tax-grid-label {
    font-size: 0.88rem;
    font-weight: 500;
    color: #374151;
    margin: 0;
    white-space: nowrap;
}
.tax-cell {
    display: flex;
    align-items: center;
    gap: 6px;
}
.tax-info-icon {
    color: #9ca3af;
    cursor: help;
    font-size: 0.8rem;
}

/* Disabled-state explainer (100% down → no mortgage) */
.slider-note {
    font-size: 0.78rem;
    color: #b45309;
    font-style: italic;
    margin-top: 6px;
    min-height: 1rem;
}

/* ── 10-K statement grid ──────────────────────────────────── */
.tk-statement-grid .tk-label-cell {
    font-weight: 600;
    color: #374151;
}
.tk-statement-grid .ag-row.tk-row-subtotal {
    font-weight: 700;
    background: #f8fafc;
    border-top: 1px solid #e5e7eb;
}
.tk-statement-grid .ag-row.tk-row-total {
    font-weight: 800;
    background: #fef3c7;
    border-top: 2px solid #f59e0b;
    border-bottom: 2px solid #f59e0b;
}
.tk-statement-grid .ag-row.tk-row-return {
    background: #eef2ff;
    color: #4338ca;
}
.tk-statement-grid .ag-row.tk-row-driver {
    color: #94a3b8;
    font-style: italic;
    font-size: 0.82rem;
}
.tk-statement-grid .ag-row.tk-row-signal {
    font-size: 0.82rem;
    font-weight: 500;
}
/* Blank separator between major sections — no border, no hover, not clickable. */
.tk-statement-grid .ag-row.tk-row-spacer,
.tk-statement-grid .ag-row.tk-row-spacer .ag-cell {
    background: transparent !important;
    border: none !important;
    cursor: default !important;
}
.tk-statement-grid .ag-row.tk-row-child .tk-label-cell {
    padding-left: 26px;
    font-weight: 400;
    color: #6b7280;
}
.tk-statement-grid .ag-cell {
    cursor: pointer;
}
/* ── Investment disclaimer box ─────────────────────────────────── */
.tk-disclaimer {
    margin-top: 24px;
    padding: 14px 18px;
    border: 1px solid #e5e7eb;
    border-left: 3px solid #d1d5db;
    border-radius: 6px;
    background: #f9fafb;
}
.tk-disc-heading {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #9ca3af;
    margin-bottom: 8px;
}
.tk-disc-list {
    margin: 0;
    padding-left: 18px;
    list-style: disc;
}
.tk-disc-list li {
    font-size: 0.75rem;
    color: #6b7280;
    margin-bottom: 4px;
    line-height: 1.5;
}
.tk-disc-title {
    font-weight: 600;
    color: #4b5563;
}
/* Current-year column tint ("Highlight Today" toggle). Semi-transparent so row
   backgrounds (subtotal/total) show through; click-to-explain inline styles win. */
.tk-statement-grid .ag-cell.tk-col-today {
    background-color: rgba(13, 148, 136, 0.07);
}
.tk-statement-grid .ag-header-cell.tk-col-today-header {
    background-color: rgba(13, 148, 136, 0.10);
}

/* Column-marker legend (shown below slicers when markers are present). */
.tk-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 2px;
}
.tk-legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 0.75rem;
    color: #6b7280;
}
.tk-legend-star {
    color: #b45309;
    font-weight: 700;
    font-size: 0.9rem;
    flex-shrink: 0;
}

/* First-sell optimal year column (amber accent, distinct from teal "today"). */
.tk-statement-grid .ag-cell.tk-col-sell {
    background-color: rgba(245, 158, 11, 0.08);
}
.tk-statement-grid .ag-header-cell.tk-col-sell-header {
    background-color: rgba(245, 158, 11, 0.15);
    font-weight: 700;
}

/* ── 10-K sticky header + formula explainer (Phase 3) ─────────── */
.tk-sticky-header {
    position: sticky;
    top: 0;
    z-index: 10;
    background: #fff;
    padding: 8px 0 10px;
    border-bottom: 1px solid #eef2f7;
    margin-bottom: 8px;
}
.tk-header-flex {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 20px;
    flex-wrap: wrap;
}
.tk-explainer {
    flex: 1 1 420px;
    min-width: 360px;
    max-width: 680px;
    height: 172px;             /* fixed — never resizes with the equation */
    position: relative;
    text-align: left;          /* equations & detail are left-justified */
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    padding: 12px 16px 16px;
    background: #fafafa;
    overflow-y: auto;          /* scroll long explanations instead of clipping */
}
.tk-explainer-head {
    position: absolute;        /* pinned to the top-right corner (equation shown) */
    top: 10px;
    right: 16px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9ca3af;
}
/* Idle state — title + prompt centered both ways */
.tk-explainer-idle {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px;
    text-align: center;
}
.tk-explainer-head-center {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9ca3af;
}
.tk-idle-hint {
    color: #9ca3af;
    font-size: 0.9rem;
}
.tk-eq-title { font-weight: 700; color: #111827; }
.tk-eq-year { color: #6b7280; font-weight: 600; }
.tk-eq-formula { color: #6b7280; font-size: 0.8rem; margin-bottom: 6px; }
.tk-eq-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    font-size: 0.95rem;
    font-variant-numeric: tabular-nums;
}
.tk-eq-chip { padding: 1px 7px; border-radius: 5px; font-weight: 700; }
.tk-eq-op { color: #6b7280; font-weight: 600; }
.tk-eq-result { font-weight: 800; color: #111827; }
.tk-eq-muted { color: #9ca3af; font-style: italic; }
.tk-eq-source {
    margin-top: 6px;
    font-size: 0.75rem;
    color: #9ca3af;
}
.tk-eq-link {
    display: inline-block;
    margin-top: 6px;
    font-size: 0.75rem;
    font-weight: 600;
    color: #2563eb;
}
.tk-eq-link:hover { text-decoration: underline; }
.tk-eq-warn {
    margin-top: 8px;
    font-size: 0.8rem;
    font-weight: 600;
    color: #b45309;
}

/* CSS fraction — numerator stacked over a bar over denominator */
.tk-frac {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    vertical-align: middle;
    margin: 0 4px;
}
.tk-frac-num {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 0 6px 3px;
}
.tk-frac-den {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 3px 6px 0;
    border-top: 2px solid #374151;
}

/* ===== Rent Health & Target (Property Performance bottom section) ===== */
.rt-section {
    margin-top: 2.5rem;
    padding-top: 1.75rem;
    border-top: 1px solid var(--hairline);
}

.rt-section-title {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--ink);
    margin-bottom: 0.15rem;
}

/* Scoped one class ahead deliberately: `.page-container p` is (0,1,1) and
   out-specifies a bare `.rt-section-subtitle` (0,1,0), so the token silently
   lost and this kept rendering the old #6B7280. Same failure mode as Dash's
   late-injected CSS — win on specificity, never on source order. */
.ds-page .rt-section-subtitle {
    font-size: 0.9rem;
    color: var(--body);
    margin-bottom: 1.25rem;
}

.rt-panel {
    background: #ffffff;
    border: 1px solid var(--hairline);
    border-radius: 12px;
    padding: 1rem 1.15rem;
    height: 100%;
}

.rt-panel-label {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--mute);
    margin-bottom: 0.1rem;
}

.rt-panel-title {
    font-size: 1rem;
    font-weight: 500;
    color: var(--ink);
    margin-bottom: 0.75rem;
}

.rt-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-bottom: 0.35rem;
}

.rt-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.78rem;
    color: var(--body);
}

.rt-swatch {
    display: inline-block;
    width: 12px;
    height: 2.5px;
    border-radius: 2px;
}

.rt-swatch-rent { background: #2a78d6; }
.rt-swatch-expense { background: #D85A30; }
.rt-swatch-required { background: #2a78d6; }
.rt-swatch-breakeven { background: #888780; }
.rt-swatch-target { background: #16A34A; }

.rt-chart-note {
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--mute);
    margin-top: 0.25rem;
}

.rt-cards-row {
    display: grid;
    gap: 10px;
    margin-top: 0.5rem;
}

.rt-cards-2 { grid-template-columns: 1fr 1fr; }
.rt-cards-3 { grid-template-columns: 1fr 1fr 1fr; }

.rt-target-badge {
    display: inline-flex;
    align-items: center;
    padding: 6px 16px;
    font-size: 0.82rem;
    font-weight: 500;
    /* Was blue-700 on blue-50, which read as `action`. Nothing here is
       clickable — it's the *result* of dragging the CoC slider — and Target is
       the favorable value, so it takes `success`. */
    color: var(--success-deep, #15803D);
    background: var(--success-soft, #F0FDF4);
    border-radius: 999px;
    white-space: nowrap;
}

.rt-card {
    background: var(--card-sunken);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    text-align: center;
}

.rt-card-accent {
    background: var(--success-soft, #F0FDF4);
}

.rt-card-label {
    font-size: 0.72rem;
    color: var(--body);
    margin-bottom: 0.1rem;
}

.rt-card-accent .rt-card-label {
    color: var(--success-deep, #15803D);
}

.rt-card-value {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--ink);
}

.rt-card-accent .rt-card-value {
    color: var(--success-deep, #15803D);
}

.rt-sensitivity-wrap {
    /* `card` white, not `card-sunken`. This block sits on the page canvas, and
       DESIGN.md reserves card-sunken for wells *inside* a card — used on canvas
       it just reads as a darker rectangle. White + hairline makes it its own
       object, which is what the border was for. */
    background: var(--card);
    border: 1px solid var(--hairline);
    border-radius: 12px;
    padding: 0.9rem 1.1rem;
    margin-top: 1rem;
}

.rt-sensitivity-intro {
    font-size: 0.85rem;
    color: var(--body);
    margin-bottom: 0.85rem;
}

.rt-sensitivity-table {
    width: 100%;
    table-layout: fixed;
    border-collapse: collapse;
    font-size: 0.8rem;
    text-align: center;
}

.rt-sensitivity-table th,
.rt-sensitivity-table td {
    padding: 5px 10px;
}

/* The tinted Break-even and Target columns are full-bleed fills, so at 4px
   padding the tint ran right up against the row label's descenders. The label
   column gets its own gutter that the fills can't cross. */
/* The gutter has to come from column width, not padding. The column was
   width:100px with nowrap while the longest label ("Cash-on-cash") measures
   107px, so it overflowed to exactly the tinted cell's left edge. Padding made
   the content box narrower and pushed the overflow further in. */
.rt-sensitivity-table .rt-row-label {
    width: 128px;
    padding-right: 14px;
}

.rt-row-label {
    text-align: left !important;
    color: var(--body);
    width: 100px;
    font-weight: 500;
    white-space: nowrap;
}

.rt-col-head {
    font-weight: 400;
    color: var(--mute);
}

.rt-col-current {
    font-weight: 600;
    color: var(--ink);
}

.rt-col-breakeven {
    background: var(--warning-soft);
    font-weight: 500;
}

.rt-col-target {
    background: var(--success-soft, #F0FDF4);
    color: var(--success-deep, #15803D);
    font-weight: 600;
}

.rt-col-head.rt-col-breakeven { color: var(--warning-deep); }

.rt-coc-up { color: var(--success-deep); }
.rt-coc-down { color: var(--danger-deep); }


/* ===== CapEx replacement calendar (Phase A) ============================== */
.capex-cal { border: 1px solid var(--hairline); border-radius: 12px; overflow: hidden;
  background: #fff; }
.capex-cal-scroll { max-height: 640px; overflow: auto; }
.capex-cal-head { display: flex; position: sticky; top: 0; z-index: 6;
  background: #fff; min-width: max-content; border-bottom: 1px solid var(--hairline-strong); }
.capex-cal-corner { position: sticky; left: 0; z-index: 8; background: #fff; }
.capex-cal-gutter { width: 96px; flex: 0 0 96px; position: sticky; left: 0;
  z-index: 4; background: #fff; border-right: 1px solid var(--hairline-strong); }
.capex-cal-gutterhead { padding: 8px; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .05em; color: #94a3b8; font-weight: 600; display: flex;
  justify-content: space-between; }
.capex-cal-cols { display: flex; }
.capex-cal-colhead { width: 104px; flex: 0 0 104px; padding: 8px;
  border-right: 1px solid var(--hairline); text-align: left; min-height: 66px;
  display: flex; flex-direction: column; justify-content: space-between; }
.capex-cal-colhead > :first-child { align-self: flex-start; }
.capex-cal-colhead > :last-child { align-self: flex-start; margin-top: auto; }
.capex-cal-cat { font-size: 12.5px; font-weight: 600; line-height: 1.15; color: #1e293b; }
.capex-cal-unitplain { font-size: 11px; color: #94a3b8; font-weight: 600; margin-top: 5px; }
.capex-cal-pill { display: inline-block; font-size: 11px; font-weight: 600;
  padding: 2px 9px; border-radius: 20px; color: #fff; }
.capex-cal-catpill { font-size: 12px; line-height: 1.15; }
.capex-cal-gridrow { display: flex; min-width: max-content; }
.capex-cal-col { width: 104px; flex: 0 0 104px; border-right: 1px solid var(--hairline); }
.capex-cal-lane { position: relative; }
.capex-cal-yearcell { position: relative; overflow: hidden; border-bottom: 1px solid var(--hairline);
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 2px 8px 0 8px; font-size: 11px; font-variant-numeric: tabular-nums; }
.capex-cal-yr { color: #475569; }
.capex-cal-amt { font-size: 10.5px; color: #94a3b8; position: relative; z-index: 2; }
/* Reserve inputs integrated at the bottom of the CapEx chart card */
.capex-reserve-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 14px;
    padding-top: 16px;
    border-top: 1px solid var(--hairline);
}

.capex-cal-yearcell.dense .capex-cal-amt { color: #f59e0b; font-weight: 700; }
.capex-cal-yearcell.current { background: #fff5e6; }
.capex-cal-yearcell.current .capex-cal-yr { color: #f59e0b; font-weight: 700; }
.capex-cal-spendbar { position: absolute; right: 0; bottom: 3px; height: 4px;
  background: #f59e0b; opacity: .45; border-radius: 2px; z-index: 1; }
.capex-cal-curband { position: absolute; left: 0; right: 0; background: #fff5e6; z-index: 0; }
/* Click-to-latch year highlight (one at a time). The rule is pointer-events:none
   and nothing changes block geometry, so drag/extend targets are unaffected. */
.capex-cal-yearcell[data-year] { cursor: pointer; }
.capex-cal-yearcell.capex-cal-yearsel { background: #eef2ff; }
.capex-cal-yearcell.capex-cal-yearsel .capex-cal-yr { color: #4f46e5; font-weight: 700; }
.capex-cal-selline { position: absolute; left: 0; right: 0; height: 2px;
  background: #4f46e5; pointer-events: none; z-index: 5; }
/* Hover preview of the rule a click would commit — dashed and faint so it reads
   as "not yet", and pointer-events:none so it never eats a click on a block. */
.capex-cal-hovline { position: absolute; left: 0; right: 0; height: 0;
  border-top: 2px dashed rgba(79, 70, 229, 0.45); pointer-events: none;
  z-index: 4; display: none; }
.capex-cal-yearcell.capex-cal-yearhover { background: #f5f6ff; }
.capex-cal-yearcell.capex-cal-yearhover .capex-cal-yr { color: #4f46e5; }

/* Basis line under the Add-a-Lease rent field. */
.rent-suggest-caption {
    font-size: 0.75rem;
    color: #6b7280;
    margin-top: 6px;
}
.rent-suggest-info { color: #9ca3af; cursor: help; }

/* The year-rail hint now uses the shared .note component (tone=action,
   badge, dismissible). Its five bespoke rules were retired 2026-08-02. */
/* Blocks outside the latched year invert: white fill, own color as ink + border.
   --blk-fill is set from the block's data-fill (the true hue, not a faded rgba). */
.capex-cal-blk.capex-cal-dim {
    background: #ffffff !important;
    color: var(--blk-fill) !important;
    border-color: var(--blk-fill) !important;
    text-shadow: none;
}
.capex-cal-blk { position: absolute; left: 5px; right: 5px; border-radius: 6px;
  overflow: hidden; padding: 5px 7px 4px; font-size: 10.5px; line-height: 1.25;
  display: flex; flex-direction: column; justify-content: space-between; }
.capex-cal-blk.future { color: #fff; }
.capex-cal-blk.active { color: #1e293b; }
.capex-cal-blk.b-over { border: 3.5px solid #0f172a; }
.capex-cal-blk.b-dash { border: 2.5px dashed #334155; }
.capex-cal-blk.b-solid { border: 1px solid rgba(15,23,42,.14); }
.capex-cal-btop { display: flex; flex-direction: column; }
.capex-cal-bt { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.capex-cal-bprice { font-weight: 600; opacity: .9; }
.capex-cal-bbot { display: flex; flex-direction: column; }
.capex-cal-byr { font-variant-numeric: tabular-nums; font-weight: 600; }
.capex-cal-blife, .capex-cal-bact { opacity: .82; font-size: 10px; }
.capex-cal-bact { font-weight: 600; opacity: .95; }

/* ----- CapEx calendar toolbar + legend (prototype styling) --------------- */
.capex-cal-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  padding: 10px 12px; background: #f8fafc; border: 1px solid var(--hairline);
  border-radius: 10px; margin-bottom: 12px; }
.capex-cal-tgroup { display: flex; align-items: center; gap: 8px; }
.capex-cal-tlabel { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: #94a3b8; font-weight: 600; }
/* Icon role. Stays bespoke (a 30px square is not a text button) but tracks the
   6px control radius and the surface tokens. */
.capex-zoom-btn.btn { width: 30px; height: 30px; padding: 0; border-radius: 6px;
  border: 1px solid var(--hairline-strong); background: var(--card); color: var(--ink); font-size: 16px;
  line-height: 1; margin-left: 6px; box-shadow: none; }
.capex-zoom-btn.btn:hover { background: var(--card-sunken); color: var(--ink); }
.capex-cal-legend { display: flex; flex-wrap: wrap; gap: 14px; margin: 0 2px 12px;
  font-size: 12px; color: #475569; }
.capex-cal-legitem { display: inline-flex; align-items: center; gap: 6px; }
.capex-cal-sw { width: 26px; height: 13px; border-radius: 3px; background: #eef2f7;
  display: inline-block; box-sizing: border-box; }
.capex-cal-sw.sw-dash { border: 1.5px dashed #334155; }
.capex-cal-sw.sw-solid { border: 1px solid #cbd5e1; }
.capex-cal-sw.sw-over { border: 2.5px solid #0f172a; }
.capex-cal-legnote { color: #94a3b8; }

.capex-cal-blk { cursor: pointer; transition: box-shadow .12s ease, transform .12s ease; }
.capex-cal-blk:hover { box-shadow: 0 2px 8px rgba(15,23,42,.22); z-index: 3; }

.capex-cal-blk { user-select: none; -webkit-user-select: none; }
.capex-cal-blk:hover { cursor: grab; }

/* Repositioning the whole bar — a bigger, more obvious lift than hover. */
.capex-cal-blk.capex-cal-moving {
    box-shadow: 0 12px 28px rgba(15,23,42,.4);
    transform: scale(1.05);
    z-index: 6;
    cursor: grabbing;
}

/* ----- CapEx "Not planned" strip (under the calendar) ------------------- */
.np-strip {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 14px;
    padding-top: 16px;
    border-top: 1px solid var(--hairline);
}
.np-strip-label {
    flex: 0 0 auto;
    line-height: 26px;   /* == chip height, so it centers on the first chip row */
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #b45309;
    white-space: nowrap;
}
.np-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    flex: 1 1 auto;
}
.np-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 9px;
    background: #fef3c7;
    border: 1px dashed #d97706;
    border-radius: 8px;
}
.np-chip-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: #b45309;
    white-space: nowrap;
}
.np-chip-badges { display: inline-flex; gap: 5px; }
.np-chip-badge {
    font-size: 0.72rem;
    font-weight: 600;
    color: #92400e;
    background: #ffffff;
    border: 1px solid #fcd34d;
    border-radius: 5px;
    padding: 1px 7px;
    cursor: pointer;
    transition: background .1s ease, border-color .1s ease;
}
.np-chip-badge:hover { background: #d97706; border-color: #d97706; color: #ffffff; }
.np-chip-more {
    font-size: 0.72rem;
    font-weight: 600;
    color: #92400e;
    padding: 1px 4px;
    align-self: center;
}

/* ----- CapEx calendar: resize handle + legend row 2 --------------------- */
.capex-cal-resize { position: absolute; left: 0; right: 0; bottom: 0; height: 9px;
  cursor: ns-resize; z-index: 4; }
.capex-cal-legend.capex-cal-legend-color { margin-top: 6px; }

/* ----- CapEx calendar: re-sort (refresh) button ------------------------- */
.capex-cal-refresh.btn { font-size: 12px; padding: 5px 12px; border-radius: 6px;
  border: 1px solid #fca5a5; background: #fef2f2; color: #b91c1c;
  box-shadow: none; white-space: nowrap; font-weight: 600; }
.capex-cal-refresh.btn:hover { background: #fee2e2; color: #991b1b;
  border-color: #f87171; }

/* ---- Catch-All method toggle (Simple vs Advanced) ---- */
.ca-mode-bar { display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; }
.ca-mode-copy { max-width: 70%; }
.ca-mode-title { font-weight: 700; font-size: 0.95rem; color: #1f2937;
  margin-bottom: 3px; }
.ca-mode-desc { font-size: 0.8rem; color: #6b7280; line-height: 1.4; }
/* Stacked rather than inline: at 340px the wide control is too big to sit
   beside its label without the label reading as a stray word. */
.ca-mode-switch { display: flex; flex-direction: column; align-items: flex-start;
  gap: 6px; flex-shrink: 0; }
.ca-mode-switch-label { font-size: 0.78rem; font-weight: 600; color: #6b7280;
  text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; }
/* The caveat itself is now .note (tone=warning); this keeps only the spacing
   that positions it under the copy it qualifies. */
.ca-mode-warn { margin-top: 6px; }

/* ===== Tax Profile — heading row with inline action ===== */

/* The hairline normally belongs to .section-heading. When a button shares the
   header line, the border moves up to this wrapper so both sit above it. */
.section-heading-row {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid #e5e7eb;
}

.section-heading-row .section-heading {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: none;
}

/* margin-left rather than justify-content, so the button stays pinned right
   even if the header later gains a third element (badge, subtext). */
.section-heading-row .tax-reset-btn {
    margin-left: auto;
}

/* Filled variant of .onboarding-restore-btn — same radius and geometry, but
   solid so it reads as an action rather than a link. #111827 is the same
   accent the segmented toggles use for their active state. */
.tax-reset-btn {
    background: #111827;
    border: 1px solid #111827;
    color: #ffffff;
    font-size: 0.9rem;
    font-weight: 500;
    padding: 5px 18px;
    border-radius: 8px;
    cursor: pointer;
    line-height: 1.4;
}

.tax-reset-btn:hover,
.tax-reset-btn:focus {
    background: #1f2937;
    border-color: #1f2937;
    color: #ffffff;
}

/* Reserve three lines so switching states never resizes the panel. Notes are
   capped at ~140 chars (see config/state_tax.py) to fit this box. */
#setup-tax-notes {
    min-height: 3.4rem;
}

/* ===== KPI band card (thermometer replacement) ===== */
.thermo-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 10px;
    min-height: 1.9rem;
}

.thermo-eyebrow,
.thermo-scope {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.6px;
    color: #8a94a6;
}

.thermo-eyebrow {
    text-transform: uppercase;
}

.thermo-scope {
    white-space: nowrap;
    flex: none;
}

.thermo-numrow {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-top: 2px;
}

.thermo-metric {
    font-size: 1.9rem;
    font-weight: 700;
    line-height: 2.05rem;
    letter-spacing: -0.4px;
    font-variant-numeric: tabular-nums;
    color: #101828;
}

.thermo-vs {
    font-size: 0.78rem;
    color: #8a94a6;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Tightened against the headline; the breathing room is spent below instead,
   so the pill's tail clears the actual marker rather than hiding behind it. */
.thermo {
    position: relative;
    margin-top: 1.05rem;
}

.thermo-pillrow {
    position: relative;
    height: 34px;
}

.thermo-pill {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    border-radius: 999px;
    padding: 5px 15px;
    font-size: 0.85rem;
    font-weight: 700;
    line-height: 1.15rem;
    white-space: nowrap;
}

.thermo-tail {
    position: absolute;
    bottom: 2px;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
}

.thermo-pill--good { background: #e6f4ea; color: #137333; }
.thermo-pill--okay { background: #fef7e0; color: #b06000; }
.thermo-pill--bad  { background: #fce8e6; color: #c5221f; }

.thermo-tail--good { border-top: 6px solid #e6f4ea; }
.thermo-tail--okay { border-top: 6px solid #fef7e0; }
.thermo-tail--bad  { border-top: 6px solid #fce8e6; }

/* 10px of clearance below the tail so the actual marker's white ring can
   never overlap the tail tip. */
.thermo-track {
    position: relative;
    height: 6px;
    margin-top: 10px;
}

.thermo-seg {
    position: absolute;
    top: 0;
    height: 6px;
    border-radius: 999px;
}

.thermo-seg--good { background: #34a853; }
.thermo-seg--okay { background: #fbbc04; }
.thermo-seg--bad  { background: #ea4335; }

.thermo-mark-actual {
    position: absolute;
    top: 50%;
    width: 15px;
    height: 15px;
    border-radius: 999px;
    background: #ffffff;
    border: 3px solid #101828;
    transform: translate(-50%, -50%);
    box-shadow: 0 0 0 2px #ffffff;
    z-index: 3;
}

.thermo-mark-actual--projected {
    border-style: dashed;
    border-color: #5b6273;
}

.thermo-mark-assumption {
    position: absolute;
    top: 50%;
    width: 3px;
    height: 15px;
    border-radius: 1.5px;
    background: #1a3e7a;
    transform: translate(-50%, -50%);
    box-shadow: 0 0 0 2px #ffffff;
    z-index: 2;
}

.thermo-axis {
    position: relative;
    height: 15px;
    margin-top: 8px;
}

.thermo-axlab {
    position: absolute;
    transform: translateX(-50%);
    font-size: 0.6875rem;
    line-height: 15px;
    color: #8a94a6;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.thermo-legend {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 20px;
    margin-top: 14px;
}

.thermo-key {
    display: flex;
    align-items: center;
    gap: 7px;
}

.thermo-key-actual {
    width: 12px;
    height: 12px;
    border-radius: 999px;
    background: #ffffff;
    border: 2.5px solid #101828;
    flex: none;
}

.thermo-key-actual--projected {
    border-style: dashed;
    border-color: #5b6273;
}

.thermo-key-assumption {
    width: 3px;
    height: 13px;
    border-radius: 1.5px;
    background: #1a3e7a;
    flex: none;
}

.thermo-key-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: #1a3e7a;
    line-height: 13px;
}

/* ===== Page lede =====
   Replaces the page H2 on themed pages. The sidebar already names and
   highlights the current page, so a heading that repeats it is decoration —
   the lede carries what the nav label can't say, which is what the page is
   for. Sized between `body` and `section-title` so it reads as an opening
   line, not as a heading. */
.ds-page .page-lede {
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--body);
    margin: 0 0 24px;
    max-width: 68ch;
}

/* Chart card header — the chart's name as an HTML `section-title`, not a
   Plotly in-plot title. Plotly titles collide with the frame's top-left
   legend, and DESIGN.md puts the name in the card header anyway. */
.ds-page .chart-card-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--ink, #0C2340);
    padding: 16px 20px 0;
    margin: 0;
}

/* KPI verdict pill — pinned to the card's top-right corner rather than sitting
   under the sparkline. It's a verdict on the whole card, so it belongs beside
   the label, not at the end of the reading order. The card body gets relative
   positioning to anchor it. */
.ds-page .kpi-card .card-body {
    position: relative;
}

.ds-page .kpi-card .kpi-status-badge {
    position: absolute;
    top: 14px;
    right: 16px;
    margin: 0;
    align-self: auto;
}
