/* ============================================================================
   Income Tracker — Global stylesheet ("Fiscal Horizon" design system, Phase 2)
   Forked from PATs' assets/styles.css (token architecture + app chrome are the
   design decision already made and approved — ported verbatim, not redesigned).
   Dual-theme: DARK is the default (:root, no attribute); LIGHT is opt-in via
   :root[data-theme="light"], set pre-paint by assets/theme.js.
   GRASP/FRAT/Catalogue/scenario-compare rules are NOT ported — this is a fork,
   not a copy. New shared components (money, KPI tile, payment dot grid, status
   pills, property switcher) live at the bottom for WP4/WP5 to consume.
   ========================================================================== */

/* ── 1. Theme tokens (ported verbatim — do not redesign the palette) ────────── */

:root {
  /* Dark surfaces (v1 ramp) */
  --surface: #0b1326;
  --surface-dim: #060e20;
  --surface-container-lowest: #060e20;
  --surface-container-low: #131b2e;
  --surface-container: #171f33;
  --surface-container-high: #222a3d;
  --surface-container-highest: #2d3449;
  --on-surface: #dae2fd;
  --on-surface-variant: #c2c6d6;
  --outline: #8c909f;
  --outline-variant: #424754;

  /* Actions (accent on dark — Deep Navy is a surface here, not an action) */
  --primary: #adc6ff;
  --on-primary: #002e6a;
  --primary-container: #4d8eff;
  --on-primary-container: #00285d;
  --secondary: #b9c8de;
  --primary-tint: rgba(173, 198, 255, .08);  /* low-opacity primary: focus rings, selected fills */

  /* Inputs */
  --input-bg: #171f33;
  --input-border: #424754;

  /* Semantics (brightened for ≥4.5:1 on dark surfaces) */
  --positive: #34d399;   /* emerald */
  --negative: #fb7185;   /* ruby */
  --warn: #fbbf24;       /* amber */

  /* Band tint sets (bg ≈ 10% tint + high-contrast text) — reused by KPI
     deltas and the status-pill component below */
  --band-great-text: #34d399;
  --band-great-bg: rgba(52, 211, 153, .16);
  --band-great-border: rgba(52, 211, 153, .35);
  --band-good-text: #34d399;
  --band-good-bg: rgba(52, 211, 153, .10);
  --band-good-border: rgba(52, 211, 153, .28);
  --band-okay-text: #fbbf24;
  --band-okay-bg: rgba(251, 191, 36, .12);
  --band-okay-border: rgba(251, 191, 36, .30);
  --band-bad-text: #fb7185;
  --band-bad-bg: rgba(251, 113, 133, .12);
  --band-bad-border: rgba(251, 113, 133, .30);
  --band-na-text: #c2c6d6;
  --band-na-bg: rgba(194, 198, 214, .08);
  --band-na-border: rgba(194, 198, 214, .20);

  /* Depth: tonal layers + 1px borders; "Soft Ambient" only on elevation */
  --shadow-ambient: 0 4px 16px rgba(0, 0, 0, .35);
  --zebra: rgba(255, 255, 255, .025);

  /* Layout/typography (dimension tokens — not theme-dependent, declared once) */
  --content-max: 1440px;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

:root[data-theme="light"] {
  /* Light surfaces — warm off-white/beige ramp. Chroma leans warm consistently
     across the whole ramp. */
  --surface: #faf7f1;
  --surface-dim: #e6e0d4;
  --surface-container-lowest: #fffdf9;
  --surface-container-low: #f4efe6;
  --surface-container: #efe9dd;
  --surface-container-high: #e8e1d2;
  --surface-container-highest: #e1d9c8;
  --on-surface: #1f1b16;
  --on-surface-variant: #4d473e;
  --outline: #7a7368;
  --outline-variant: #ddd5c5;

  /* Actions: dark blue (navy) grounds primary actions on the warm field —
     navy is reserved for actions/selection/focus only, never decoration */
  --primary: #1e3a8a;
  --on-primary: #ffffff;
  --primary-container: #1e3a8a;
  --on-primary-container: #ffffff;
  --secondary: #45527a;
  --primary-tint: rgba(30, 58, 138, .06);

  /* Inputs: white field, warm border */
  --input-bg: #ffffff;
  --input-border: #cfc6b4;

  /* Semantics (Emerald / Ruby / amber middle band — reads correctly on the
     warm surfaces) */
  --positive: #10b981;
  --negative: #e11d48;
  --warn: #b45309;

  --band-great-text: #047857;
  --band-great-bg: rgba(16, 185, 129, .14);
  --band-great-border: rgba(16, 185, 129, .35);
  --band-good-text: #047857;
  --band-good-bg: rgba(16, 185, 129, .09);
  --band-good-border: rgba(16, 185, 129, .28);
  --band-okay-text: #b45309;
  --band-okay-bg: rgba(245, 158, 11, .12);
  --band-okay-border: rgba(245, 158, 11, .35);
  --band-bad-text: #be123c;
  --band-bad-bg: rgba(225, 29, 72, .10);
  --band-bad-border: rgba(225, 29, 72, .28);
  --band-na-text: #45464d;
  --band-na-bg: rgba(69, 70, 77, .07);
  --band-na-border: rgba(69, 70, 77, .18);

  /* Soft Ambient: navy-tinted shadow to match the new primary */
  --shadow-ambient: 0 4px 16px rgba(30, 58, 138, .10);
  --zebra: rgba(122, 106, 79, .06);
}

/* ── 2. Base ─────────────────────────────────────────────────────────────── */

* {
  box-sizing: border-box
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--surface);
  color: var(--on-surface);
  font-family: Inter, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.45;
}

a {
  color: var(--primary);
  text-decoration: none
}

a:hover {
  text-decoration: underline
}

h2 {
  margin: 0 0 12px;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.small {
  font-size: 12px;
  color: var(--on-surface-variant)
}

/* Material Symbols defaults (font served by fonts.googleapis.com) */
.material-symbols-outlined {
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  font-size: 20px;
  line-height: 1;
  vertical-align: middle;
  flex-shrink: 0;
  user-select: none;
}

/* Baseline focus-visible ring for every interactive element that doesn't
   already define its own (inputs/buttons below layer their own :focus
   states on top of this). Keyboard-only outline — mouse/touch activation
   never triggers :focus-visible. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* ── 3. App chrome: fixed 256px sidebar + top header ──────────────────────── */

.app-shell {
  display: flex;
  min-height: 100vh;
  background: var(--surface);
}

.sidebar {
  width: 256px;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: var(--surface-container-lowest);
  border-right: 1px solid var(--outline-variant);
  z-index: 30;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 20px 16px;
  border-bottom: 1px solid var(--outline-variant);
}

.sidebar-brand h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--on-surface);
}

.sidebar-brand .brand-sub {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

.logo {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  background: var(--primary);
  flex-shrink: 0;
}

/* Sidebar nav — the <nav class="nav"> contract element. */
.nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 8px;
  flex: 1;
}

.nav a,
.nav button {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--on-surface-variant);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.nav a:hover,
.nav button:hover {
  background: var(--surface-container-low);
  color: var(--on-surface);
  text-decoration: none;
}

:root[data-theme="light"] .nav a:hover,
:root[data-theme="light"] .nav button:hover {
  background: var(--surface-container);
}

.nav a.active {
  background: var(--primary-tint);
  color: var(--primary);
  font-weight: 700;
}

.nav .material-symbols-outlined {
  color: inherit;
}

/* Main column */
.app-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 64px;
  padding: 12px 24px;
  background: var(--surface-container-lowest);
  border-bottom: 1px solid var(--outline-variant);
  z-index: 20;
}

.topbar .page-title {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--on-surface);
}

.topbar .page-title .small {
  font-weight: 500;
}

/* Narrow phones: the subtitle wraps word-per-line beside the user cluster */
@media (max-width: 560px) {
  .topbar .page-title .small {
    display: none;
  }
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: 12px;
  position: relative;
}

.page-content {
  flex: 1;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 24px;
}

/* Mobile (<1100px): CSS-only collapse — sidebar becomes a top bar */
@media (max-width: 1100px) {
  .app-shell {
    flex-direction: column;
  }

  .sidebar {
    position: static;
    width: 100%;
    height: auto;
    overflow: visible;
    border-right: none;
    border-bottom: 1px solid var(--outline-variant);
  }

  .sidebar-brand {
    border-bottom: none;
    padding: 12px 16px;
  }

  .nav {
    flex-direction: row;
    flex-wrap: wrap;
    padding: 0 8px 10px;
  }

  .nav a,
  .nav button {
    width: auto;
    padding: 8px 10px;
  }

  .page-content {
    padding: 16px;
  }
}

/* ── 4. Property switcher (NEW — the piece every page depends on) ─────────── */
/* Markup contract (assets/app.js wires this exactly): a .property-switcher
   wrapper containing a .property-switcher-trigger button and a
   .property-switcher-list popup of .property-switcher-option rows.
   window.itProperty is the JS-side source of truth — see app.js. */

.property-switcher {
  position: relative;
  margin: 12px 12px 0;
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  background: var(--surface-container-low);
}

:root[data-theme="light"] .property-switcher {
  background: var(--surface-container-lowest);
}

.property-switcher-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
  color: var(--on-surface);
  border-radius: 8px;
}

.property-switcher-trigger:hover {
  background: var(--surface-container-high);
}

.property-switcher-trigger:disabled {
  cursor: default;
  opacity: .6;
}

.property-switcher-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
}

.property-switcher-code {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .01em;
}

.property-switcher-address {
  font-size: 11px;
  color: var(--on-surface-variant);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 168px;
}

.property-switcher-trigger .material-symbols-outlined {
  font-size: 18px;
  color: var(--on-surface-variant);
  flex-shrink: 0;
}

.property-switcher-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--surface-container-high);
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  box-shadow: var(--shadow-ambient);
  max-height: 260px;
  overflow-y: auto;
  z-index: 40;
  display: none;
  padding: 4px;
}

:root[data-theme="light"] .property-switcher-list {
  background: var(--surface-container-lowest);
}

.property-switcher.open .property-switcher-list {
  display: block;
}

.property-switcher-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: 6px;
  background: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--on-surface);
}

.property-switcher-option:hover {
  background: var(--surface-container-highest);
}

.property-switcher-option.selected {
  background: var(--primary-tint);
}

.property-switcher-option .option-code {
  font-size: 13px;
  font-weight: 700;
}

.property-switcher-option .option-address {
  font-size: 11px;
  color: var(--on-surface-variant);
}

.property-switcher-empty {
  padding: 12px 10px;
  font-size: 12px;
  color: var(--on-surface-variant);
}

/* ── 5. Buttons ──────────────────────────────────────────────────────────── */

.btn {
  background: var(--surface-container-high);
  border: 1px solid var(--outline-variant);
  padding: 9px 14px;
  border-radius: 4px;
  color: var(--on-surface);
  font-weight: 600;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.btn:hover {
  background: var(--surface-container-highest);
  text-decoration: none;
}

.btn.primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}

.btn.primary:hover {
  opacity: .92;
  background: var(--primary);
}

.btn.ghost {
  background: transparent;
  border: 1px dashed var(--outline-variant)
}

.btn.btn-danger {
  background: var(--band-bad-bg);
  border-color: var(--band-bad-border);
  color: var(--band-bad-text);
}

.btn.btn-danger:hover {
  background: var(--band-bad-border);
}

.btn:disabled,
.btn.btn-danger:disabled {
  opacity: .5;
  cursor: not-allowed;
}

/* Theme toggle control (any element carrying data-theme-toggle) */
[data-theme-toggle] {
  cursor: pointer;
}

.theme-toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 1px solid var(--outline-variant);
  border-radius: 9999px;
  padding: 6px 14px;
  color: var(--on-surface-variant);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
}

.theme-toggle-btn:hover {
  color: var(--on-surface);
  border-color: var(--outline);
}

.theme-when-dark,
.theme-when-light {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}

.theme-when-light {
  display: none;
}

:root[data-theme="light"] .theme-when-dark {
  display: none;
}

:root[data-theme="light"] .theme-when-light {
  display: inline-flex;
}

.theme-toggle-btn .material-symbols-outlined {
  font-size: 16px;
}

/* ── 6. Cards & grid ─────────────────────────────────────────────────────── */

.card {
  background: var(--surface-container-low);
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  padding: 18px
}

:root[data-theme="light"] .card {
  background: var(--surface-container-lowest);
}

.grid {
  display: grid;
  gap: 16px
}

.grid.one {
  grid-template-columns: 1fr
}

.grid.two {
  grid-template-columns: repeat(2, minmax(0, 1fr))
}

.grid.three {
  grid-template-columns: repeat(3, minmax(0, 1fr))
}

.grid.four {
  grid-template-columns: repeat(4, minmax(0, 1fr))
}

@media (max-width:1100px) {

  .grid.one,
  .grid.two,
  .grid.three,
  .grid.four {
    grid-template-columns: 1fr
  }
}

.section-title {
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
  margin: 6px 0 10px
}

/* Better wide utilization for content pages */
.page-content>.grid.two {
  grid-template-columns: 1.2fr .8fr
}

@media (max-width:1100px) {

  .page-content>.grid.two {
    grid-template-columns: 1fr
  }
}

/* ── 7. Inputs ───────────────────────────────────────────────────────────── */

.input {
  display: flex;
  flex-direction: column;
  gap: 6px
}

.input label {
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--on-surface-variant)
}

.input input,
.input select,
.input textarea {
  width: 100%;
  padding: 10px 12px;
  border-radius: 4px;
  border: 1px solid var(--input-border);
  background: var(--input-bg);
  color: var(--on-surface);
  outline: none;
  font-size: 14px;
  font-family: inherit;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.input input:focus,
.input select:focus,
.input textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary-tint);
}

.input select {
  cursor: pointer;
}

/* Bare (unwrapped) search/filter bars — table toolbars on Ledger/Expenses */
input[type="search"] {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 4px;
  font-size: 14px;
  font-family: inherit;
  color: var(--on-surface);
  padding: 10px 12px;
  transition: border-color 0.15s, box-shadow 0.15s;
}

input[type="search"]:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary-tint);
}

.row {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap
}

.divider {
  height: 1px;
  background: var(--outline-variant);
  margin: 12px 0
}

/* ── 8. Toggle switch ────────────────────────────────────────────────────── */

.toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px
}

.toggle input {
  display: none
}

.toggle .pill {
  width: 44px;
  height: 24px;
  border-radius: 9999px;
  background: var(--surface-container-high);
  border: 1px solid var(--outline-variant);
  position: relative;
  transition: .2s;
}

.toggle .pill::after {
  content: "";
  width: 18px;
  height: 18px;
  border-radius: 9999px;
  background: var(--on-surface-variant);
  position: absolute;
  top: 2px;
  left: 2px;
  transition: .2s;
}

.toggle input:checked+.pill {
  background: var(--primary);
  border-color: var(--primary);
}

.toggle input:checked+.pill::after {
  background: var(--on-primary);
  transform: translateX(20px)
}

/* ── 9. Tables ───────────────────────────────────────────────────────────── */

.table {
  width: 100%;
  border-collapse: collapse
}

.table th,
.table td {
  padding: 10px;
  border-bottom: 1px solid var(--outline-variant);
  text-align: left;
  font-size: 14px
}

.table th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-container-low);
  font-size: 11px;
  line-height: 16px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

/* Zebra striping — very faint tint on alternate rows */
.table tbody tr:nth-child(even) {
  background: var(--zebra);
}

/* ── 10. Role badge — shared across all pages (topbar-user contract) ──────── */

.role-badge {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
  padding: 3px 10px;
  border-radius: 9999px;
  background: var(--surface-container-highest);
  color: var(--primary);
}

.role-badge.investor {
  background: var(--band-okay-bg);
  color: var(--band-okay-text);
}

.role-badge:empty {
  display: none;
}

/* ── 11. Spinner (page-level loading text) ─────────────────────────────────── */

.spinner {
  color: var(--on-surface-variant);
}

/* ── 12. Modal confirm (base chrome only — CRUD flows are WP4/WP5's to add) ── */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 22, .6);
  backdrop-filter: blur(2px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9998
}

:root[data-theme="light"] .modal-overlay {
  background: rgba(30, 58, 138, .4);
}

.modal {
  width: min(520px, 90vw);
  background: var(--surface-container-low);
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  padding: 18px;
  box-shadow: var(--shadow-ambient);
}

:root[data-theme="light"] .modal {
  background: var(--surface-container-lowest);
}

.modal h3 {
  margin: 0 0 8px 0;
  font-size: 18px;
  letter-spacing: -0.01em;
}

.modal p {
  margin: 0 0 14px 0;
  color: var(--on-surface-variant)
}

.modal .actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end
}

.modal .btn.primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}

/* ── 13. Auth pages (login.html / mfa-challenge.html / reset-password.html) ── */

.login-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
}

.login-card {
  background: var(--surface-container-low);
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  padding: 40px 36px;
  width: 100%;
  max-width: 400px;
  box-shadow: var(--shadow-ambient);
}

:root[data-theme="light"] .login-card {
  background: var(--surface-container-lowest);
}

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

.login-brand h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--on-surface);
}

.login-brand .brand-sub {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

.login-title {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--on-surface);
  margin: 0 0 24px;
}

.login-subtitle {
  font-size: 13px;
  color: var(--on-surface-variant);
  line-height: 1.5;
  margin: -16px 0 24px;
}

.login-card .input {
  margin-bottom: 16px;
}

.login-submit {
  width: 100%;
  justify-content: center;
  padding: 11px 14px;
  font-size: 14px;
  margin-top: 8px;
}

.auth-code-input {
  font-size: 22px;
  letter-spacing: .3em;
  text-align: center;
}

.login-error {
  background: var(--band-bad-bg);
  border: 1px solid var(--band-bad-border);
  border-radius: 8px;
  color: var(--band-bad-text);
  font-size: 13px;
  padding: 10px 12px;
  margin-top: 12px;
  display: none;
}

.login-error.visible {
  display: block;
}

.login-error.success {
  background: var(--band-great-bg);
  border-color: var(--band-great-border);
  color: var(--band-great-text);
}

.login-footer {
  margin-top: 20px;
  font-size: 13px;
  color: var(--on-surface-variant);
  text-align: center;
}

.login-footer a {
  color: var(--primary);
  cursor: pointer;
}

/* ── 14. Money (NEW) ─────────────────────────────────────────────────────────
   Every currency figure in the app carries this class so columns of numbers
   line up. Applied directly to a <td>/<th>, it also right-aligns; used on an
   inline element elsewhere, pair it with `.money.block` (or a right-aligned
   parent) to get the alignment too — the monospace/tabular-nums treatment
   always applies regardless of context. */

.money {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

td.money,
th.money {
  text-align: right;
}

.money.block {
  display: block;
  text-align: right;
}

/* ── 15. KPI stat tile (NEW) ───────────────────────────────────────────────
   Markup: <div class="kpi"><span class="kpi-label">…</span>
   <span class="kpi-value">…</span><span class="kpi-delta up|down|flat">…</span></div>
   Lay several out with .kpi-row (responsive, no breakpoints needed). */

.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
}

.kpi {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 18px;
  background: var(--surface-container-low);
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
}

:root[data-theme="light"] .kpi {
  background: var(--surface-container-lowest);
}

.kpi-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

.kpi-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--on-surface);
}

.kpi-delta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 600;
}

.kpi-delta.up {
  color: var(--positive);
}

.kpi-delta.down {
  color: var(--negative);
}

.kpi-delta.flat {
  color: var(--on-surface-variant);
}

/* WP B3: the Total Behind tile's disclosure note ("+ $X on ended leases —
   not included") — deliberately quiet, not alarming, since it is explaining
   an exclusion rather than reporting a new problem. Collapses to zero space
   via :empty when there is nothing to disclose (ledger.js sets textContent
   to '' rather than hiding the element, matching this file's existing
   .lease-generator-status:empty convention) so BIG TWO's tile shows no gap. */
.kpi-note {
  font-size: 11px;
  color: var(--on-surface-variant);
}

.kpi-note:empty {
  display: none;
}

/* ── 16. Status pills (NEW) ───────────────────────────────────────────────
   Rent-payment status chips, reusing the --band-* token sets that already
   exist for this exact purpose. Named .status-pill (not .pill) to avoid
   colliding with the toggle-switch's `.toggle .pill` knob above. */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: 9999px;
  border: 1px solid transparent;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
}

.status-pill-paid {
  background: var(--band-great-bg);
  border-color: var(--band-great-border);
  color: var(--band-great-text);
}

.status-pill-partial {
  background: var(--band-okay-bg);
  border-color: var(--band-okay-border);
  color: var(--band-okay-text);
}

.status-pill-pending {
  background: var(--band-na-bg);
  border-color: var(--band-na-border);
  color: var(--band-na-text);
}

.status-pill-overdue {
  background: var(--band-bad-bg);
  border-color: var(--band-bad-border);
  color: var(--band-bad-text);
}

/* ── 17. Payment dot grid (NEW) ───────────────────────────────────────────
   A 12-across Jan–Dec grid of per-month status dots (Tenant Ledger, WP4).
   Five states, each carrying a SHAPE/FILL difference on top of color so the
   grid reads without relying on color alone (accessibility requirement):
     .dot-paid            solid fill + check glyph
     .dot-partial         diagonal half-fill (no glyph needed — shape differs)
     .dot-pending         hollow ring, dashed border
     .dot-overdue         solid fill + "!" glyph
     .dot-not-operational flat dash, no circle at all — reads as "nothing was
                           expected here" (quiet), never as a failure state.
   Every .dot needs an aria-label naming the month + status (e.g. "March:
   paid") — the glyphs/shapes here are a visual aid, not a substitute for the
   accessible name. The ROLE depends on whether the dot does anything:
     - non-interactive dot -> <span role="img">
     - clickable dot       -> native <button>, NO role override. Putting
       role="img" on a button announces an activatable control as a picture.
   Markup: .dot-grid > (.dot-grid-cell > .dot-grid-label + .dot) × 12 */

.dot-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 6px;
}

.dot-grid-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.dot-grid-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

.dot {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--outline-variant);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

.dot-paid {
  background: var(--band-great-text);
  border-color: var(--band-great-text);
  color: var(--surface);
}

.dot-paid::after {
  content: "\2713";
}

.dot-partial {
  background: linear-gradient(135deg, var(--band-okay-text) 0 50%, transparent 50% 100%);
  border-color: var(--band-okay-border);
}

.dot-pending {
  background: transparent;
  border-style: dashed;
  border-color: var(--band-na-text);
}

.dot-overdue {
  background: var(--band-bad-text);
  border-color: var(--band-bad-text);
  color: var(--surface);
}

.dot-overdue::after {
  content: "!";
}

.dot-not-operational {
  background: transparent;
  border: none;
}

.dot-not-operational::after {
  content: "";
  width: 8px;
  height: 2px;
  border-radius: 1px;
  background: var(--outline-variant);
}

/* Compact legend pairing a sample dot with its label — one row, wraps. */
.dot-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
}

.dot-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--on-surface-variant);
}

.dot-legend-item .dot {
  width: 14px;
  height: 14px;
  font-size: 8px;
}

/* ── 18. Lease document print view (NEW) ───────────────────────────────────
   Markup contract (lease-document.html): .lease-document-page wraps the
   whole standalone view (no .app-shell/sidebar on this page — see that
   file); .lease-document-toolbar holds the print button and any other
   on-screen-only control; .lease-document-paper is the container
   renderLeaseMarkdownToHtml()'s output HTML is inserted into. On screen
   this reads as a legible article in the current theme; @media print
   below strips everything but the document itself and forces plain
   black-on-white regardless of which theme was active, so the PDF a
   browser's print dialog produces is never theme-dependent. */

.lease-document-page {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}

.lease-document-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 20px;
}

.lease-document-paper {
  background: var(--surface-container-lowest);
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  padding: 48px 56px;
  color: var(--on-surface);
  line-height: 1.6;
}

.lease-document-paper h1 {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-align: center;
  margin: 0 0 24px;
}

.lease-document-paper h2 {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .01em;
  margin: 28px 0 12px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--outline-variant);
}

.lease-document-paper h3 {
  font-size: 13px;
  font-weight: 700;
  margin: 20px 0 8px;
}

.lease-document-paper p {
  margin: 0 0 14px;
}

.lease-document-paper ul,
.lease-document-paper ol {
  margin: 0 0 14px;
  padding-left: 28px;
}

.lease-document-paper li {
  margin-bottom: 4px;
}

.lease-document-paper hr {
  border: none;
  border-top: 1px solid var(--outline-variant);
  margin: 28px 0;
}

.lease-document-paper strong {
  color: var(--on-surface);
}

.lease-document-loading,
.lease-document-error {
  text-align: center;
  padding: 60px 24px;
  color: var(--on-surface-variant);
}

@media print {

  /* Chrome hidden entirely — this page's whole point is that print-to-PDF
     yields the document alone, with no app chrome around it. */
  .lease-document-toolbar,
  .lease-document-loading,
  .lease-document-error {
    display: none !important;
  }

  body,
  .lease-document-page {
    background: #fff;
    margin: 0;
    padding: 0;
    max-width: none;
  }

  .lease-document-paper {
    background: #fff;
    color: #000;
    border: none;
    border-radius: 0;
    padding: 0;
  }

  .lease-document-paper h2 {
    border-bottom-color: #000;
  }

  .lease-document-paper hr {
    border-top-color: #000;
  }

  /* Never split a heading, paragraph, or list item across a page boundary
     — paragraphs include the signature blocks (see lease-document.js's
     signatureLines(): role label + signature rule + printed name, all one
     paragraph joined by hard breaks), which is exactly what must not land
     split across two pages. */
  .lease-document-paper h1,
  .lease-document-paper h2,
  .lease-document-paper h3,
  .lease-document-paper p,
  .lease-document-paper li {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  @page {
    size: letter;
    margin: 0.75in;
  }
}

/* ── 19. Lease renewal / extension + post-lease-end prompt (WP4) ──────────
   Reuses §12's base modal chrome and tenant.html's own inline .field-grid /
   .input layout (already shared with the record-payment form on that same
   page) rather than duplicating either — only what's genuinely NEW to these
   two modals lives here: a wider modal variant (the renewal form carries
   more fields than §12's delete-receipt confirm was ever sized for), the
   live rent/end-date "before commit" preview lines, and letting the
   post-lease-end prompt's three actions wrap on a narrow viewport instead of
   overflowing the modal. */

.modal-wide {
  width: min(640px, 92vw);
}

/* A modal is never actually "wide" in absolute terms — fixed at 2 columns
   regardless of viewport width, unlike the page's own .field-grid, whose
   4-column default only collapses to 2 under a 900px VIEWPORT breakpoint
   (irrelevant here — a 640px-wide modal is always "narrow" for that rule's
   purposes, on any screen). */
.modal-wide .field-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.modal-context-line {
  margin: -6px 0 14px;
  font-size: 12px;
  font-weight: 600;
  color: var(--on-surface-variant);
}

.modal-preview-line {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--on-surface-variant);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  min-height: 16px;
}

.post-lease-end-actions {
  flex-wrap: wrap;
}

/* ── 20. Tenant catalogue — assignment + lease-signed states (WP6) ────────
   Extends §16's .status-pill component (same shape, same --band-* tokens)
   with four new named states rather than repurposing paid/partial/pending/
   overdue under mismatched labels — a future reader grepping for
   "status-pill-paid" should never land on an assignment indicator that has
   nothing to do with rent. assigned/signed reuse the SAME great-band colour
   deliberately: assignment IS gated on signed-ness (migration 22), so the
   same "settled/confirmed" green means the same thing in both places.
   Markup: <span class="status-pill status-pill-assigned">Assigned</span> /
   status-pill-catalogue-only, and, inside a lease chip,
   status-pill-signed / status-pill-draft. */

.status-pill-assigned,
.status-pill-signed {
  background: var(--band-great-bg);
  border-color: var(--band-great-border);
  color: var(--band-great-text);
}

.status-pill-catalogue-only,
.status-pill-draft {
  background: var(--band-na-bg);
  border-color: var(--band-na-border);
  color: var(--band-na-text);
}

/* Masked government-ID fragment ("•••• 4821") — monospaced like every other
   numeric/code-like value in this app (.money, receipt dates), so the
   redacted look is deliberate rather than an unstyled accident. */
.gov-id-fragment {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--on-surface-variant);
}

/* SURPRISE FOUND VIA E2E, FIXED HERE: §19's .modal-wide had no max-height —
   fine for the renewal form it was sized for, but WP6's assign-to-property
   modal, in "create a new tenant" mode, stacks tenant-resolution fields
   ABOVE the property/unit/term fields and can exceed a real viewport's
   height. Neither .modal nor .modal-overlay have ever needed a max-height
   before now (.modal-overlay is a fixed, inset:0 flex container with no
   overflow of its own, so an oversized child has never had anywhere to
   scroll) — a Playwright run confirmed it concretely: the submit button
   was "visible, enabled and stable" yet permanently outside the viewport,
   with 60s of retries never able to scroll it into reach. Capping height
   and letting the MODAL ITSELF scroll (not the page behind it) fixes this
   for any .modal-wide content, present or future, on any viewport short
   enough to need it — including tenant.js's own renewal modal (WP4), which
   already uses this class and gets the same safety net for free, at zero
   visible change today since its content has never come close to this
   limit. Scoped to .modal-wide only, not the base .modal — every base-.modal
   usage in this app (expense/delete-confirm/add-tenant modals) already
   fits comfortably within a normal viewport; this fix is scoped to exactly
   the class where the problem was proven, not applied speculatively wider. */
.modal-wide {
  max-height: min(86vh, 760px);
  overflow-y: auto;
}

/* ── 21. Mortgage derivation (Expenses page, WORK PACKAGE A) ──────────────
   Two new named states, deliberately separate from every existing
   .status-pill variant (same principle §20's own header comment states:
   a future reader grepping for one status-pill name should never land on
   an indicator about something unrelated):
     - .status-pill-scheduled: a derived (schedule-projection) mortgage
       ledger row — NOT a database row, not editable, not deletable. Reuses
       the neutral/na band (same visual family as §20's
       .status-pill-catalogue-only) because "scheduled, not yet actual" and
       "catalogue-only, not yet assigned" are the same kind of fact:
       provisional, not wrong.
     - .status-pill-undated: a mortgage_rates row with a NULL effective_date
       in the schedule editor (e.g. BIG ONE's still-undiscovered post-PMI
       figure) — same neutral treatment, different context, so it keeps its
       own name rather than reusing status-pill-catalogue-only out of its
       tenant-catalogue context. */
.status-pill-scheduled,
.status-pill-undated {
  background: var(--band-na-bg);
  border-color: var(--band-na-border);
  color: var(--band-na-text);
}

/* A derived ledger row (assets/expenses.js buildExpenseRow) — subtle italic
   treatment distinguishes it from a real, database-backed expense row at a
   glance, without needing color alone to carry the distinction. */
.expense-row-derived td {
  font-style: italic;
  color: var(--on-surface-variant);
}
.expense-row-derived .category-pill,
.expense-row-derived .status-pill {
  font-style: normal; /* pills keep their own upright text regardless of the row */
}

/* The "overrode $X scheduled" annotation under a hand-entered mortgage
   row's amount that suppressed a derived line — explains the number on
   screen instead of leaving a bare total (see mergeMortgageLedger,
   income-formulas.js). */
.scheduled-override-note {
  font-weight: 400;
  color: var(--on-surface-variant);
  margin-top: 2px;
}

/* ── 22. Lease Generator (NEW) ──────────────────────────────────────────
   lease-generator.html: a founders-only, chrome-less tool page (same
   reasoning as §18's lease-document view — not part of the day-to-day
   .app-shell nav). Markup contract: .lease-generator-page wraps the whole
   page; .lease-generator-toolbar holds the back link + title;
   .lease-generator-body wraps the form, revealed only after the auth gate
   in assets/lease-generator.js passes. .input/.card/.btn/.grid/.divider/
   .section-title/.modal-error are all reused as-is from §5-§7 — nothing
   below redefines them.

   .lease-generator-print-frame is the actual print mechanism (see that
   file's loadAndFillTemplate): a same-origin iframe holding one of
   lease-templates/*.html, filled via [data-field]/textContent, then
   printed via its own contentWindow.print() so each template's own
   @page/print CSS applies untouched. Deliberately NOT display:none, which
   breaks printing in some browsers — hidden from the visible layout by
   position instead, per the work-package brief. */

.lease-generator-page {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}

.lease-generator-toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 8px;
}

.lease-generator-back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--on-surface-variant);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}

.lease-generator-back-link:hover {
  color: var(--on-surface);
}

.lease-generator-back-link .material-symbols-outlined {
  font-size: 18px;
}

.lease-generator-toolbar .page-title {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--on-surface);
}

.lease-generator-intro {
  margin: 0 0 20px;
  color: var(--on-surface-variant);
  font-size: 13px;
  line-height: 1.6;
  max-width: 68ch;
}

.lease-generator-loading,
.lease-generator-error {
  text-align: center;
  padding: 60px 24px;
  color: var(--on-surface-variant);
}

.lease-generator-error {
  color: var(--band-bad-text);
}

.lease-generator-body .full-span {
  grid-column: 1 / -1;
}

.lease-generator-body textarea {
  width: 100%;
  padding: 10px 12px;
  border-radius: 4px;
  border: 1px solid var(--input-border);
  background: var(--input-bg);
  color: var(--on-surface);
  outline: none;
  font-size: 14px;
  font-family: inherit;
  resize: vertical;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.lease-generator-body textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary-tint);
}

/* Suggested-value affordance ("suggest, never auto-apply" — same principle
   as the tenant page's late-fee suggestion): hidden until there's a real
   suggestion to show, and applying it is always an explicit click, never
   automatic. */
.lease-generator-suggestion {
  display: none;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 8px;
  padding: 8px 10px;
  border: 1px dashed var(--outline-variant);
  border-radius: 6px;
  background: var(--primary-tint);
  font-size: 12px;
  color: var(--on-surface-variant);
}

.lease-generator-suggestion.visible {
  display: flex;
}

.lease-generator-suggestion .btn {
  padding: 5px 10px;
  font-size: 11px;
  flex-shrink: 0;
}

.lease-generator-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--outline-variant);
}

/* Progress line for the two-document print run (agreement, then addenda).
   Empty by default and given no box/padding when empty, so it takes no vertical
   space until there is something to say. */
.lease-generator-status {
  margin-top: 10px;
  font-size: 0.9rem;
  color: var(--on-surface-variant);
}

.lease-generator-status:empty {
  margin-top: 0;
}

.lease-generator-print-frame {
  position: absolute;
  top: 0;
  left: -10000px;
  width: 850px;
  height: 1100px;
  border: 0;
}

/* ── 23. Calendar-year ledger grid + vacancies (NEW, WP-C) ─────────────────
   ledger.html's DEFAULT view: one row per lease (active AND ended) x
   Jan-Dec of a selected year (.ledger-grid-table), plus a compact per-unit
   vacancy grid below it (.vacancy-table) — both plain .table instances
   (§9), reusing its zebra striping and sticky header row. A .view-toggle
   segmented control switches back to the original "Active Leases" status
   table, which this redesign does NOT remove.

   Every grid cell that isn't 'outside the lease window' carries an ICON +
   the collected amount as TEXT, never color alone — same accessibility
   principle as the retired dot grid (§17, CLAUDE.md bug B001: an ARIA role
   mistake on that grid was a real defect) — plus an aria-label/title
   stating month, status, and collected-of-due. .grid-cell-outside is
   deliberately the quietest state (a plain dash, no icon): nothing was
   owed, so it must not visually compete with a month that was. */

.ledger-controls {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 4px;
}

.ledger-year-input {
  min-width: 100px;
}

/* WP B1: "Hide ended leases" checkbox, default ON. Visibility is toggled
   inline (flex/none) by renderHideEndedControl() in ledger.js, not CSS —
   the toggle is a no-op in Status Table view (already active-only), so it
   is removed from layout there entirely rather than merely dimmed. */
.ledger-hide-ended {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--on-surface-variant);
  cursor: pointer;
  padding-bottom: 9px;
}

.ledger-hide-ended input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--primary);
  cursor: pointer;
}

.view-toggle {
  display: inline-flex;
  border: 1px solid var(--outline-variant);
  border-radius: 6px;
  overflow: hidden;
}

.view-toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  background: var(--surface-container-low);
  border: none;
  border-right: 1px solid var(--outline-variant);
  color: var(--on-surface-variant);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.view-toggle-btn:last-child {
  border-right: none;
}

.view-toggle-btn .material-symbols-outlined {
  font-size: 17px;
}

.view-toggle-btn:hover {
  color: var(--on-surface);
  background: var(--surface-container-high);
}

.view-toggle-btn.active {
  background: var(--primary);
  color: var(--on-primary);
}

/* ── Tenant grid ── */

.ledger-grid-table {
  min-width: 1520px;
}

.ledger-grid-table th,
.ledger-grid-table td {
  font-size: 12px;
  padding: 7px 5px;
  text-align: center;
  white-space: nowrap;
}

/* Scroll affordance (B2): a soft edge shadow on the inner, scroll-facing
   side of each pinned column signals that the grid continues underneath
   it. A blurred box-shadow fades to nothing at its edges even with a
   fully opaque color, so reusing the --outline token (rather than a new
   rgba literal) still reads as "soft." Static, not scroll-position-aware
   — CSS only, no JS scroll listener — because the Total column never
   fits at any screen width (--content-max caps usable width below the
   grid's min-width), so the cue is always relevant, not conditional. */
.ledger-grid-table th:first-child,
.ledger-grid-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  text-align: left;
  white-space: normal;
  min-width: 150px;
  background-color: var(--surface-container-low);
  box-shadow: 4px 0 6px -4px var(--outline);
}

.ledger-grid-table thead th:first-child {
  z-index: 3;
}

/* ⚠️ A pinned column exists to occlude whatever scrolls underneath it, so
   its background MUST stay fully opaque (alpha 1) on EVERY row, in
   EVERY theme — not just the odd ones. --zebra is a deliberately
   translucent tint (rgba, ~2.5% alpha) meant to sit over an opaque base
   on ordinary rows; setting it via `background: var(--zebra)` on a
   sticky cell REPLACES that opaque base instead of tinting it, dropping
   the whole cell to ~2.5% opacity. This was a real, reproduced defect,
   not a hypothetical one: at that opacity, whatever normal-flow column's
   static position happened to coincide with this cell's fixed screen
   position rendered visibly superimposed through it on every even row —
   almost certainly the founder's long-standing, previously unreproduced
   "column content overlapping when scrolling" report, present at every
   screen size, not just mobile. Fix: layer the tint as a
   `background-image` gradient ON TOP of an untouched, always-opaque
   `background-color` — two separate properties that both simply apply,
   instead of one `background` shorthand fought over by two rules on
   specificity. This also removes the need for a separate light-theme
   zebra override rule (the trap the old comment here warned about):
   --zebra is already redefined per theme, so this same rule picks up
   the right tint for free in both. */
.ledger-grid-table tbody tr:nth-child(even) td:first-child {
  background-image: linear-gradient(var(--zebra), var(--zebra));
}

:root[data-theme="light"] .ledger-grid-table th:first-child,
:root[data-theme="light"] .ledger-grid-table td:first-child {
  background-color: var(--surface-container-lowest);
}

/* The <tfoot> label cell (also a td:first-child, now colSpan 1 after the
   B2 tfoot-overlap fix) must keep the footer's own opaque background
   (surface-container-high, matching every other tfoot cell) rather than
   the sticky-column background-color set above — the IDENTICAL
   specificity trap as the tfoot Total cell further down this file, one
   column over, fixed the same way and for the same reason. TWO rules:
   - .ledger-grid-table tfoot td:first-child beats the theme-agnostic
     base rule above (1 class+1 pseudo-class+2 elements outranks 1
     class+1 pseudo-class+1 element).
   - :root[data-theme="light"] .ledger-grid-table tfoot td:first-child is
     needed SEPARATELY: the light-theme override just above (4
     classes+1 element) would otherwise beat the theme-agnostic tfoot
     rule below on class-count alone, the same way it did for the Total
     cell. Without this, the light-theme footer label cell would render
     as surface-container-lowest (near-white) — at the label cell's
     current 150px width (post-colspan-fix) that reads as a seam against
     the footer's 2px top border, not as styling. */
.ledger-grid-table tfoot td:first-child {
  background-color: var(--surface-container-high);
}

:root[data-theme="light"] .ledger-grid-table tfoot td:first-child {
  background-color: var(--surface-container-high);
}

/* ── Pinned Total column (B2) — mirrors the sticky-left column above ──────
   The Total column is the one number a reader scans the row for, but it
   never fits: --content-max caps usable width at 1392px while
   .ledger-grid-table needs 1520px, identically at every screen size (the
   content column is capped, so widening the viewport doesn't help).
   Pinning Total right — not widening the page — is the founder-chosen
   fix. Reuses the existing `grid-total-cell` class (already applied to
   the body Total <td> for font-weight only, below) rather than inventing
   a second selector; renderLedgerGrid/buildGridSummaryTfoot in ledger.js
   apply the same class to the header Total <th> and to BOTH <tfoot>
   final cells — skip either <tfoot> cell and the grid's own totals
   scroll out of view underneath this column. */
.ledger-grid-table th.grid-total-cell,
.ledger-grid-table td.grid-total-cell {
  position: sticky;
  right: 0;
  z-index: 2;
  background-color: var(--surface-container-low);
  box-shadow: -4px 0 6px -4px var(--outline);
}

/* Sticky in both axes (pinned right + thead's own sticky top) and must
   sit above every scrolling cell, so it gets the same elevated z-index
   as the sticky-left header corner (they never overlap, so a tie is
   fine — both simply need to beat the plain `.table th { z-index: 1 }`
   sticky-top rule and the unpinned body/header cells scrolling beneath). */
.ledger-grid-table thead th.grid-total-cell {
  z-index: 3;
}

/* Same opacity requirement, and same background-color/background-image
   split, as the sticky-left column above — see that comment for why a
   single `background` shorthand is the wrong tool on a pinned cell. */
.ledger-grid-table tbody tr:nth-child(even) td.grid-total-cell {
  background-image: linear-gradient(var(--zebra), var(--zebra));
}

:root[data-theme="light"] .ledger-grid-table th.grid-total-cell,
:root[data-theme="light"] .ledger-grid-table td.grid-total-cell {
  background-color: var(--surface-container-lowest);
}

/* The <tfoot> Total cell must keep the footer's own opaque background
   (surface-container-high, matching every other tfoot cell) rather than
   the sticky-column background-color set above. TWO rules, matching the
   two contexts that would otherwise win instead:
   - .ledger-grid-table tfoot td.grid-total-cell beats the theme-agnostic
     base rule above (2 classes+2 elements outranks 2 classes+1 element).
   - :root[data-theme="light"] .ledger-grid-table tfoot td.grid-total-cell
     is needed SEPARATELY: the light-theme override just above (4
     classes+1 element) would otherwise beat the theme-agnostic tfoot
     rule below on class-count alone. Found while restructuring for the
     opacity fix — without this second rule, the light-theme footer
     Total cell was rendering as surface-container-lowest (near-white),
     mismatching the rest of that row. */
.ledger-grid-table tfoot td.grid-total-cell {
  background-color: var(--surface-container-high);
}

:root[data-theme="light"] .ledger-grid-table tfoot td.grid-total-cell {
  background-color: var(--surface-container-high);
}

.grid-tenant-cell {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.grid-row-ended {
  opacity: .68;
}

.status-pill-ended {
  background: var(--band-na-bg);
  border-color: var(--band-na-border);
  color: var(--band-na-text);
}

.grid-cell {
  border-radius: 4px;
}

.grid-cell-icon {
  font-size: 15px;
  vertical-align: -3px;
  margin-right: 3px;
}

.grid-cell-amount {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.grid-cell-paid {
  background: var(--band-great-bg);
  color: var(--band-great-text);
  font-weight: 600;
}

.grid-cell-partial {
  background: var(--band-okay-bg);
  color: var(--band-okay-text);
  font-weight: 600;
}

.grid-cell-pending {
  background: transparent;
  color: var(--on-surface-variant);
  border: 1px dashed var(--band-na-border);
}

.grid-cell-overdue {
  background: var(--band-bad-bg);
  color: var(--band-bad-text);
  font-weight: 700;
}

.grid-cell-outside {
  color: var(--on-surface-variant);
  opacity: .45;
}

.grid-legend-swatch.grid-cell-outside {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px dashed var(--outline-variant);
  opacity: 1;
}

.grid-total-cell {
  font-weight: 700;
}

.ledger-grid-table tfoot td {
  border-top: 2px solid var(--outline-variant);
  border-bottom: none;
  font-weight: 700;
  background: var(--surface-container-high);
}

.ledger-grid-table tfoot tr:first-child td {
  border-bottom: 1px solid var(--outline-variant);
}

/* Compact legend pairing a sample icon/swatch with its label — wraps on narrow screens. */
.grid-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  padding: 10px 4px 0;
  font-size: 12px;
  color: var(--on-surface-variant);
}

.grid-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.grid-legend-item .material-symbols-outlined {
  font-size: 16px;
}

/* ── Vacancy grid ── */

.vacancy-table {
  min-width: 900px;
}

.vacancy-table th,
.vacancy-table td {
  font-size: 12px;
  padding: 7px 5px;
  text-align: center;
  white-space: nowrap;
}

.vacancy-table th:first-child,
.vacancy-table td:first-child {
  text-align: left;
  font-weight: 700;
}

.vacancy-cell-vacant {
  background: var(--band-bad-bg);
  color: var(--band-bad-text);
  font-weight: 700;
  border-radius: 4px;
}

.vacancy-cell-occupied {
  color: var(--on-surface-variant);
  opacity: .55;
}

/* WP B4: a month before the property's income_start_date — nothing could
   possibly have been owed, so it must not compete visually with a genuine
   "Vacant" cell. Deliberately mirrors .grid-cell-outside's own quietness
   (§23 above) rather than inventing a second "nothing to see here" style. */
.vacancy-cell-untracked {
  color: var(--on-surface-variant);
  opacity: .45;
}

/* ── 24. Security deposit receipt + room-level (tenant-chargeable) expenses
   (NEW, WP-B) ──────────────────────────────────────────────────────────────
   tenant.html: .deposit-status is the founder's "green check" confirmation
   next to the Security deposit figure — three states (received / pending /
   none), each carrying an ICON + TEXT LABEL together, never color alone
   (same accessibility principle §17/§23 already document, re: bug B001). A
   $0/null security_deposit is its OWN 'none' state, not a nonsensical
   "received $0". .deposit-summary is the resulting deposit-position
   breakdown (deposit minus linked expenses) below it.

   expenses.html: .tenant-charge-pill marks a lease-linked expense in the
   NORMAL ledger table (the founder's explicit "catalogued along with
   generic expenses" ask) and links back to tenant.html?leaseId=<uuid>.
   .ledger-filters wraps the category chips + the new "Tenant-chargeable
   only" toggle so .ledger-header-row's existing two-child space-between
   layout is unaffected. */

.deposit-status {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 8px;
  border: 1px solid transparent;
  font-size: 13px;
  margin-bottom: 16px;
}

.deposit-status .material-symbols-outlined {
  font-size: 20px;
  flex-shrink: 0;
  margin-top: 1px;
}

.deposit-status-text {
  font-weight: 700;
}

.deposit-status-received {
  background: var(--band-great-bg);
  border-color: var(--band-great-border);
  color: var(--band-great-text);
}

.deposit-status-pending {
  background: var(--band-okay-bg);
  border-color: var(--band-okay-border);
  color: var(--band-okay-text);
}

.deposit-status-none {
  background: var(--band-na-bg);
  border-color: var(--band-na-border);
  color: var(--band-na-text);
}

.deposit-status-form {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}

/* Bare (unwrapped) date input — mirrors §7's own `input[type="search"]`
   bare-input convention, since this control lives inside a dynamically
   rendered status widget, not a form field wrapped in `.input`. */
.deposit-status-form input[type="date"] {
  padding: 8px 10px;
  border-radius: 4px;
  border: 1px solid var(--input-border);
  background: var(--input-bg);
  color: var(--on-surface);
  font-family: inherit;
  font-size: 13px;
}

.deposit-status .form-error {
  margin: 6px 0 0;
  width: 100%;
}

/* ── Deposit ledger summary (tenant.html) ── */

.deposit-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px;
  margin-bottom: 16px;
}

.deposit-summary-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.deposit-summary-item .label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

.deposit-summary-item .value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 16px;
  font-weight: 700;
  color: var(--on-surface);
}

.deposit-summary-item .value.negative {
  color: var(--negative);
}

/* B5 (founder's visual pass, 2026-08-17): a 'pending' deposit's "Agreed"
   row is deliberately demoted secondary text under the primary "Held
   $0.00" figure — smaller and muted so HELD reads as the number that
   matters, the agreed amount as context. Additive only. */
.deposit-summary-item-secondary .label {
  font-size: 10px;
}

.deposit-summary-item-secondary .value {
  font-size: 12px;
  font-weight: 500;
  color: var(--on-surface-variant);
}

/* ── Tenant-chargeable expenses (expenses.html) ── */

.tenant-charge-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--on-surface-variant);
}

.tenant-charge-pill .material-symbols-outlined {
  font-size: 15px;
}

.tenant-charge-pill a {
  color: var(--primary);
  font-weight: 600;
  text-decoration: none;
}

.tenant-charge-pill a:hover {
  text-decoration: underline;
}

.ledger-filters {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
