/* style.css — Return Grid: rental investment calculator
   Design concept: precision fintech instrument for real estate underwriting.
   Cool slate surfaces, brand green + navy accent, red/amber/green semantic scale for risk. */

:root {
  /* ---- Type scale (fluid clamp) ---- */
  --text-xs: clamp(0.75rem, 0.71rem + 0.2vw, 0.8125rem);
  --text-sm: clamp(0.875rem, 0.83rem + 0.25vw, 0.9375rem);
  --text-base: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  --text-lg: clamp(1.125rem, 1.03rem + 0.5vw, 1.375rem);
  --text-xl: clamp(1.5rem, 1.28rem + 1vw, 2.125rem);

  /* ---- 4px spacing ---- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;

  /* ---- Fonts ---- */
  --font-display: 'Cabinet Grotesk', 'Segoe UI', sans-serif;
  --font-body: 'Satoshi', 'Segoe UI', sans-serif;
  /* tenant.css carries its own copy of this token (the Tenant Portal does not
     load this file) -- keep the two in step; #4603's byte-identical test in
     tests/locksAndDoorCodes4564.unit.js fails if they drift. */
  --font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;

  --radius-sm: 0.375rem;
  --radius-md: 0.625rem;
  --radius-lg: 1rem;
  --radius-xl: 1.375rem;
  --radius-full: 9999px;

  --transition-interactive: 160ms cubic-bezier(0.16, 1, 0.3, 1);
  --content-wide: 1240px;

  /* Sticky-offset metrics. app.js measures the real header and action bar on
     load and resize and overwrites these on the root element, so the values
     here are only what the first paint uses -- they match the fallbacks every
     call site used to carry inline. Declared rather than left implicit so
     tests/contrast.unit.js can require every custom property style.css reads
     to be defined somewhere in it. */
  --header-h: 64px;
  --action-bar-h: 0px;

  /* Per-element input: each .branding-color-swatch button sets its own
     `--swatch` inline (style="--swatch:#17324d"). The default only applies to
     a swatch that ships without one, which would otherwise be an invisible
     44px hit target -- `background: var(--swatch)` with no definition is
     invalid at computed-value time and drops to transparent. Points at a theme
     token so the placeholder follows light/dark like everything else. */
  --swatch: var(--color-surface-2);

  /* #2955/#2953: the rent-roll row's "⋯" overflow menu is positioned by JS
     from the trigger button's real getBoundingClientRect() on open (see
     app.js buildPropertyUnitsTableRow) rather than CSS-relative to an
     ancestor, so it can escape .grid-scroll's clipping. These are only the
     off-screen pre-open default -- same reasoning as --header-h above. */
  --rr-more-menu-top: -9999px;
  --rr-more-menu-left: -9999px;
}

/* ---- Light mode (default) ---- */
:root,
[data-theme='light'] {
  color-scheme: light;
  --color-bg: #f2f3f5;
  --color-surface: #ffffff;
  --color-surface-2: #fbfbfc;
  --color-surface-offset: #e9ebee;
  --color-divider: #e1e4e8;
  --color-border: #d5d9df;
  /* #860/#861: alternating row / muted background variant */
  --color-bg-alt: #f1f5f9;
  /* #860/#861: danger/destructive accent (red) */
  --color-danger: #dc2626;

  /* Text scale. --color-text-faint is an ACCENT token: borders, decorative
     rules, and inactive (WCAG-exempt) controls only. It must never colour
     normal-sized text -- it measures 2.62:1 on --color-surface, well under
     WCAG 2.1 AA 1.4.3's 4.5:1, and under 1.4.11's 3:1 for meaningful icons
     too. Task List ID 265 audited all 48 rules that were colouring text with
     it (.kpi-sub, .hint, .suffix, the landing pipeline chips, table captions,
     *-meta lines, status pills, icon buttons) and moved every one to
     --color-text-muted; tests/contrast.unit.js now fails if any come back.
     Why muted and not a new lighter "accessible faint" tier: solved for it and
     there is no room. The lightest slate on the faint->muted line that clears
     4.5:1 on every surface a faint role can land on (including the tinted
     highlights, where --color-error-highlight is the binding constraint) is
     #5b6471 -- 92% of the way to muted and 1.08:1 away from it, i.e. visually
     identical. Restricting the constraint to the four flat surfaces only gets
     to #626b78, still 1.20:1 from muted. Hierarchy below muted therefore comes
     from size and weight, not colour. See scripts/dev/solve-faint-text-token.py
     and scripts/dev/audit-faint-text.py to re-derive both numbers. */
  --color-text: #171b21;
  --color-text-muted: #565f6c;
  --color-text-faint: #98a1ac;
  --color-text-inverse: #f5f6f8;

  --color-primary: #006837;
  --color-primary-hover: #005a2f;
  --color-primary-active: #004425;
  --color-primary-highlight: #d4e8dc;

  --color-brand-navy: #243d86;
  /* Completes the navy with the tint partner every other tone already had.
     Rent Roll's tenant chips and its "recently refreshed" pill are the only
     blue surfaces in the app and were hardcoded light literals; navy on this
     measures 7.70:1. */
  --color-brand-navy-highlight: #dbe1f2;

  --color-success: #2f7d43;
  /* ID 265: --color-success is border/icon-safe but measures 4.07:1 as text on
     --color-success-highlight (the "Closed" pipeline chip). This variant clears
     4.5:1 on every surface INCLUDING that tint (4.58:1). Same pattern as
     --color-warning-text below. */
  --color-success-text: #2c743e;
  --color-success-highlight: #dbeadf;
  --color-warning: #a8720f;
  /* Text-only amber. --color-warning is 4.13:1 on white, which is fine for the
     borders/icons/fills it mostly drives (WCAG 1.4.11, >= 3:1) but short of
     1.4.3's 4.5:1 for normal-sized copy such as .field-note-warn. */
  --color-warning-text: #805609;
  --color-warning-highlight: #f0e3c8;
  --color-error: #b23a3a;
  /* ID 265: 4.48:1 as text on --color-error-highlight, a hair under AA. */
  --color-error-text: #b03939;
  --color-error-highlight: #f2dbdb;

  --shadow-sm: 0 1px 2px oklch(0.2 0.02 240 / 0.06);
  --shadow-md: 0 6px 16px oklch(0.2 0.02 240 / 0.08);
  --shadow-lg: 0 16px 40px oklch(0.2 0.02 240 / 0.14);
}

[data-theme='dark'] {
  color-scheme: dark;
  --color-bg: #12151a;
  --color-surface: #181c22;
  --color-surface-2: #1d2128;
  --color-surface-offset: #21262e;
  --color-divider: #2a3038;
  --color-border: #333a44;
  --color-bg-alt: #1a1e25;
  --color-danger: #ef4444;

  --color-text: #e7e9ec;
  --color-text-muted: #98a1ac;
  --color-text-faint: #626b78;
  --color-text-inverse: #12151a;

  --color-primary: #3fae66;
  --color-primary-hover: #5cc47e;
  --color-primary-active: #2f8a52;
  --color-primary-highlight: #1a2f22;

  --color-brand-navy: #6d84d6;
  /* Deep enough that the lighter dark-mode navy still clears AA on it
     (4.62:1); the obvious #1d2438 only reaches 4.35:1. */
  --color-brand-navy-highlight: #191f31;

  --color-success: #6fbf7c;
  /* Dark mode already clears AA on the tint (6.05:1), so the text variant is
     the base colour -- same as --color-warning-text below. */
  --color-success-text: #6fbf7c;
  --color-success-highlight: #1f3324;
  --color-warning: #d9a53f;
  /* Already clears 4.5:1 on every dark surface, so text and accents share it. */
  --color-warning-text: #d9a53f;
  --color-warning-highlight: #3a2f18;
  --color-error: #e07a7a;
  --color-error-text: #e07a7a;
  --color-error-highlight: #3a2222;

  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.3);
  --shadow-md: 0 6px 16px oklch(0 0 0 / 0.4);
  --shadow-lg: 0 16px 40px oklch(0 0 0 / 0.5);
}

/* ================= Layout shell ================= */

html {
  /* overflow-x: clip (not hidden) -- clipping horizontal overflow without
     establishing a scroll container. "hidden" on only one axis forces the
     other axis's used overflow value to "auto" per spec, which turns html
     and body into scroll containers and breaks position: sticky everywhere
     on the page (site-header, results-col, results-tabs, export-row) since
     sticky then anchors to those non-scrolling boxes instead of the real
     viewport scroll. clip has no such side effect. */
  overflow-x: clip;
  max-width: 100%;
}
body {
  font-family: var(--font-body);
  overflow-x: clip;
  max-width: 100%;
}
/* The Unit page temporarily reuses the visits modal as an overlay. Lock both
   axes while it is open: combining overflow-x: clip with a visible y-axis
   serializes as `clip visible`, which does not stop the page behind it. */
body.modal-open {
  overflow: hidden;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  padding: var(--space-2) var(--space-4);
  z-index: 100;
  border-radius: var(--radius-sm);
}
.skip-link:focus {
  left: var(--space-4);
  top: var(--space-4);
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) clamp(var(--space-4), 4vw, var(--space-10));
  background: color-mix(in oklab, var(--color-surface) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-divider);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-text);
  text-decoration: none;
  /* #1165: .header-actions grew past its #487 budget (search box, Units,
     Leases, Inspections buttons) and .site-header's space-between flex was
     squeezing this flex item instead of the nav -- the brand rendered
     clipped mid-word ("Return Gr") rather than the nav reflowing. The brand
     is the one thing that must never be the shrink target. */
  flex-shrink: 0;
}
.brand-mark {
  height: 32px;
  width: auto;
  display: block;
  flex-shrink: 0;
}
.brand-name {
  font-family: var(--font-display);
  font-weight: 650;
  font-size: var(--text-base);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .brand-name {
    font-size: var(--text-sm);
    max-width: 9.5rem;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
@media (max-width: 420px) {
  .brand-name {
    max-width: 6.5rem;
  }
}
.brand-tag {
  display: none;
}

.header-actions {
  display: flex;
  align-items: center;
  /* Start-align so scrollWidth includes any temporary overflow. The dynamic
     More controller relies on that native measurement; end alignment would
     place excess controls at negative x-coordinates, invisible to scrollWidth. */
  justify-content: flex-start;
  gap: var(--space-3);
  min-width: 0;
  flex: 1 1 auto;
}
.header-actions > * {
  flex: 0 0 auto;
}
/* #1165: the main nav is allowed to take the room left by the fixed brand.
   app.js observes this element and moves only the eligible trailing
   destinations into More when its contents no longer fit. */
@media (max-width: 1180px) {
  .brand-name {
    display: none;
  }
}
@media (max-width: 1060px) {
  .header-actions {
    gap: var(--space-2);
  }
  .header-actions .nav-caret {
    display: none;
  }
}
@media (max-width: 480px) {
  .header-actions {
    gap: var(--space-1);
  }
  .theme-toggle {
    width: 36px;
    height: 36px;
  }
}
@media (max-width: 500px) {
  .nav-more > .nav-dropdown-toggle {
    justify-content: center;
    width: 34px;
    padding: 6px;
  }
  .nav-more > .nav-dropdown-toggle .btn-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
  }
}
@media (max-width: 420px) {
  .site-header {
    padding-inline: var(--space-3);
    gap: var(--space-2);
  }
  .brand {
    gap: var(--space-2);
  }
  .brand-name {
    max-width: 8rem;
  }
}

.nav-dropdown {
  position: relative;
}
.nav-dropdown-toggle {
  cursor: pointer;
}
.header-actions > .nav-dropdown > .nav-dropdown-toggle,
.header-actions > .nav-top-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 34px;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.header-actions > .nav-dropdown > .nav-dropdown-toggle:hover,
.header-actions > .nav-top-btn:hover {
  background: var(--color-bg);
}
#settingsNavToggle {
  width: 34px;
  justify-content: center;
  padding: 6px;
}
#settingsNavToggle svg {
  width: 18px;
  height: 18px;
}
.nav-more[hidden] {
  display: none;
}
.nav-more > .nav-dropdown-menu {
  right: 0;
  left: auto;
}
.nav-more .nav-dropdown {
  width: 100%;
}
.nav-more .nav-dropdown-toggle,
.nav-more .nav-top-btn {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 34px;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
}
.nav-more .nav-dropdown-toggle:hover,
.nav-more .nav-top-btn:hover {
  background: var(--color-bg);
  color: var(--color-primary);
}
.nav-more .nav-dropdown > .nav-dropdown-menu {
  top: 0;
  left: calc(100% + var(--space-1));
}
.nav-caret {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  transition: transform var(--transition-interactive);
}
.nav-dropdown.open .nav-caret {
  transform: rotate(180deg);
}
.nav-dropdown.open .nav-dropdown-toggle {
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.nav-dropdown-menu {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 40;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg, 0 12px 32px rgba(0, 0, 0, 0.16));
}
.nav-dropdown-menu[hidden] {
  display: none;
}
.nav-dropdown-item[hidden] {
  display: none;
}
.nav-dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.nav-dropdown-item svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.nav-dropdown-item:hover {
  background: var(--color-bg);
  color: var(--color-primary);
}
.nav-dropdown-section-label {
  padding: var(--space-2) var(--space-3) var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* #1165: Settings is a compact trigger, but its menu retains every existing
   destination in four scannable configuration groups. */
#settingsNavMenu {
  right: 0;
  left: auto;
  width: min(52rem, calc(100vw - var(--space-4)));
  padding: var(--space-3);
}
.settings-nav-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(10rem, 1fr));
  gap: var(--space-3);
}
.settings-nav-section {
  min-width: 0;
}
.settings-nav-section .nav-section-label {
  padding-inline: var(--space-2);
  margin-top: 0;
}
.settings-nav-docs {
  margin-top: var(--space-2);
}
.settings-nav-docs .profile-menu-divider {
  margin-block: var(--space-2);
}
@media (max-width: 900px) {
  .settings-nav-grid {
    grid-template-columns: repeat(2, minmax(11rem, 1fr));
  }
}
@media (max-width: 480px) {
  .nav-dropdown-menu:not([style*="position: fixed"]) {
    left: auto;
    right: 0;
  }
}

/* ---- My Profile nav dropdown: avatar + initials button, opening a menu
   with an email header, a My Profile item, and Log out. Shared pattern
   with the admin portal's own My Profile dropdown (admin.css). ---- */
.profile-avatar-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color var(--transition-interactive), background var(--transition-interactive);
}
.header-actions #profileNavGroup .profile-avatar-btn {
  gap: 0;
  padding: 2px;
}
.profile-avatar-btn:hover {
  border-color: var(--color-text-faint, var(--color-border));
}
.nav-dropdown.open .profile-avatar-btn {
  border-color: var(--color-primary);
  background: var(--color-primary-highlight, var(--color-bg));
}
.profile-avatar-btn .nav-caret {
  margin-right: var(--space-1);
}
.avatar-circle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-text-inverse, #fff);
  font-size: 11px;
  font-weight: 650;
  flex-shrink: 0;
}
.profile-dropdown-menu {
  left: auto;
  right: 0;
  min-width: 15rem;
}
.profile-menu-header {
  padding: var(--space-1) var(--space-3) var(--space-2);
}
.profile-menu-email {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-menu-divider {
  height: 1px;
  border: none;
  background: var(--color-divider, var(--color-border));
  margin: var(--space-1) 0;
}
.nav-dropdown-item.logout-item {
  color: var(--color-error);
}
.nav-dropdown-item.logout-item:hover {
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}
.theme-toggle:hover {
  color: var(--color-text);
  border-color: var(--color-text-faint);
}
.theme-toggle svg {
  width: 18px;
  height: 18px;
}

main {
  max-width: var(--content-wide);
  margin-inline: auto;
  padding: clamp(var(--space-6), 5vw, var(--space-12)) clamp(var(--space-4), 4vw, var(--space-10));
}

/* The single-property analysis view (income/equity/amortization tables)
   and the portfolio dashboard (wide deals table) benefit from more
   horizontal room on large screens instead of a fixed 1240px column with
   growing dead space on either side. Floor matches the shared --content-wide
   so nothing changes on smaller/medium screens; only wide monitors expand.
   #5045: Tenants joined this group once #4976 (Portal status) and #4948
   (Property/Unit/Lease) columns pushed the tenants table to 11 columns --
   comfortably wider than 1240px on any monitor, so the fixed page cap was
   forcing the internal .grid-scroll's overflow-x scrollbar (and clipping
   the trailing action buttons) well before it needed to. */
#calculator-main,
#dashboard-main,
#tenants-main {
  max-width: clamp(var(--content-wide), 92vw, 1760px);
}

.page-intro {
  max-width: 68ch;
  margin-bottom: var(--space-8);
}
.page-intro h1 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 650;
  margin-bottom: var(--space-3);
  letter-spacing: -0.01em;
}
.page-intro p {
  color: var(--color-text-muted);
  font-size: var(--text-base);
  max-width: 62ch;
}

/* #1334: unit-record actions sit with the record identity, rather than
   disappearing inside a lease card. */
.unit-page-header-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
/* #2184: Inventory's "Manage locations" entry point, moved up next to the
   page title so it's discoverable instead of a small text link buried
   below the filter row. */
.inventory-page-header-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}
/* #6580: 16rem (not the original 11rem) so this settles on exactly 2
   columns at the modal's new 46rem (.modal-card-wide) desktop width --
   2 x 16rem + 1 gap fits comfortably under the ~40.5rem of usable grid
   width there, while 3 x 16rem does not, so auto-fit never renders 3+
   cramped columns. At the smallest supported mobile width (375px card,
   ~279px of usable grid width after overlay+card padding) a single
   16rem (256px) track still fits without overflow, so this still
   degrades to 1 column on mobile exactly as before -- unlike the
   original 11rem, 18rem would have overflowed that same 279px floor.

   #6587: align-items: start -- without it, pairing a 3-line label (Market
   rent, which adds a .field-note span under its input) with a plain
   2-line label (Garage spaces) in the same 2-column row stretched the
   shorter label -- and, since that label is itself display:grid, its
   input as a default-stretch grid item -- to fill the taller row, so
   Garage spaces rendered as an oversized box next to normal-height
   siblings. This never showed at the old 1-column width, where every
   field had its own row with nothing taller beside it. */
.unit-page-edit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-4);
  align-items: start;
}
.unit-page-edit-grid label {
  display: grid;
  gap: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.unit-page-edit-grid input {
  width: 100%;
}

.export-row {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
  position: sticky;
  top: var(--header-h);
  z-index: 15;
  margin-inline: calc(-1 * clamp(var(--space-4), 4vw, var(--space-10)));
  padding: var(--space-3) clamp(var(--space-4), 4vw, var(--space-10));
  margin-bottom: var(--space-8);
  background: color-mix(in oklab, var(--color-surface) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-divider);
}

/* #939/#6340: Leases list action row -- the scope-dependent "Serve a notice
   to…" button and Clear filters. The five Property/Unit/Status/Lease
   timing/Signing filters used to live here as hand-rolled <select>s; they are
   now injected by ListKit as a .listkit-toolbar immediately below this row
   (see ensureLeasesListPager's `filters:` config in app.js). */
.leases-list-filters {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  align-items: flex-end;
  margin-bottom: var(--space-6);
}
.leases-list-filters > .action-btn {
  align-self: center;
}
.export-row[hidden] {
  display: none;
}
/* Modal footer button rows. These used to reuse .export-row, which is really
   the sticky page toolbar -- it pins itself under the site header and pulls
   itself outward with a negative inline margin so it can bleed to the edges of
   the main column. Inside a modal card that bleed made the row ~30px wider
   than the card (measured 734 clientWidth vs 750 scrollWidth on the add-owned
   modal), so every modal with a button row got a stray horizontal scrollbar,
   and the sticky positioning was meaningless inside a modal anyway. Task List
   ID 424 migrated the nine in-modal call sites onto this class; .export-row is
   now only the three real page toolbars (compare, Defaults, Credentials).
   Matches the .modal-actions class the Admin Portal already uses in admin.css,
   minus the right-alignment, so Owner Portal footers stay left-aligned as they
   were. */
.modal-actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
  margin-top: var(--space-5);
}
.modal-actions[hidden] {
  display: none;
}

/* #6895: the #1942 inline maintenance-tags-in-grid-row rules were removed
   here -- that UI moved into the Edit User modal, which reuses the
   existing .multiselect-dropdown / .field / .group-label rules instead of
   a dedicated block. The grid cell is now a plain .link-btn summary. */
.sticky-save-bar .modal-note {
  flex: 1 1 100%;
  margin: 0;
}
/* Shared low-emphasis action. Unlike text links, these controls retain a
   bordered surface, a 44px touch target, and a dedicated focus ring. */
.action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  text-decoration: none;
  transition: border-color var(--transition-interactive), background var(--transition-interactive), color var(--transition-interactive);
}
.action-btn:hover:not(:disabled) {
  border-color: var(--color-primary);
  background: var(--color-surface);
  color: var(--color-primary);
}
.action-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.action-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.action-btn[hidden] {
  display: none;
}
/* #6669: Collections Aging's Actions cell now holds two buttons
   (Record payment/View ledger + Collection log) -- a small gap so the
   inline-flex buttons don't sit flush against each other, wrapping on
   narrow widths instead of overflowing the cell. */
.actions-cell {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.action-btn-group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.action-btn-danger {
  border-color: var(--color-error);
  color: var(--color-error);
}
.action-btn-danger:hover:not(:disabled) {
  border-color: var(--color-error);
  background: var(--color-surface);
  color: var(--color-error);
}
.action-btn-danger:focus-visible {
  outline-color: var(--color-error);
}
/* #2855: the bill status filter chips (#allVendorBillStatusFilters,
   #vendorBillStatusFilters) toggle .is-active on click but had no visual
   state for it -- every chip except the buggy "Overdue" one (fixed in #2854)
   looked identical whether selected or not. Mirrors .tabs button[aria-selected='true']. */
#allVendorBillStatusFilters .action-btn.is-active,
#vendorBillStatusFilters .action-btn.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
#allVendorBillStatusFilters .action-btn.is-active:hover:not(:disabled),
#vendorBillStatusFilters .action-btn.is-active:hover:not(:disabled) {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
#allVendorBillStatusFilters .action-btn.is-active.action-btn-danger,
#vendorBillStatusFilters .action-btn.is-active.action-btn-danger {
  background: var(--color-error);
  border-color: var(--color-error);
  color: var(--color-text-inverse);
}
#allVendorBillStatusFilters .action-btn.is-active.action-btn-danger:hover:not(:disabled),
#vendorBillStatusFilters .action-btn.is-active.action-btn-danger:hover:not(:disabled) {
  background: var(--color-error);
  border-color: var(--color-error);
  color: var(--color-text-inverse);
}
/* #1712/#1719: These navigation actions are anchors, so their button treatment
   must explicitly cancel the browser's default link underline. Keep this scoped
   to the independent offer, comparison, and leases-list contexts. */
.dash-offer-actions .btn-export,
#compareTableBody .btn-export,
#leases-list-main .btn-export {
  text-decoration: none;
}
.btn-save-success {
  border-color: var(--color-success) !important;
  color: var(--color-success) !important;
  background: var(--color-success-highlight) !important;
}
/* The toolbar's Pull Remote Data spinner: same refresh glyph as its idle
   state, spun while the RentCast calls are in flight. The Property Info
   "Sync data feeds" button drives the same run, so it spins with it. */
#pullRemoteDataBtn[aria-busy='true'] #pullRemoteDataBtnIcon,
#dataFeedSyncBtn[aria-busy='true'] #dataFeedSyncBtnIcon {
  animation: pull-remote-spin 0.9s linear infinite;
}
@keyframes pull-remote-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  #pullRemoteDataBtn[aria-busy='true'] #pullRemoteDataBtnIcon,
  #dataFeedSyncBtn[aria-busy='true'] #dataFeedSyncBtnIcon { animation: none; }
}

/* ============ Property Info "Data feeds" card ==============================
   Force-refresh entry point for the four RentCast lookups, sitting in the
   Property Info section next to the lifecycle status card. Laid out like
   .export-load-error (compact bordered notice, not a full panel) rather than
   like a lifecycle card, because it carries one action and a result list.
   The [hidden] rule is explicit for the same reason it is there: the element
   has a class setting `display`, and author styles beat the browser's
   `[hidden] { display: none }` at any specificity. */
.data-feed-sync-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.data-feed-sync-card[hidden] {
  display: none;
}
.data-feed-sync-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.data-feed-sync-titles {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.data-feed-sync-title {
  font-weight: 650;
}
.data-feed-sync-synced {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* .btn-export carries margin-left:auto for toolbar rows; neutralised here the
   same way .panel-load-error-actions does it. */
.data-feed-sync-head .btn-export {
  margin-left: 0;
}
.data-feed-sync-note {
  margin: 0;
  max-width: 44rem;
}
.data-feed-sync-results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.data-feed-sync-results[hidden] {
  display: none;
}
.data-feed-sync-result {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-xs);
  padding: 0.2rem 0;
  border-top: 1px solid var(--color-border);
}
.data-feed-sync-result-feed {
  color: var(--color-text-muted);
}
.data-feed-sync-result-state {
  font-weight: 650;
}
/* Colour is a reinforcement, not the signal -- the state word itself
   ("Updated" / "No data found" / "Unavailable") carries the meaning, so this
   stays readable for colour-blind users and in forced-colours mode. */
.data-feed-sync-result-ok .data-feed-sync-result-state {
  color: var(--color-success);
}
/* --color-warning is 4.13:1 on white -- fine for the border/icon it was minted
   for, short of 1.4.3's 4.5:1 for this label. Use the text-only amber. */
.data-feed-sync-result-nodata .data-feed-sync-result-state {
  color: var(--color-warning-text, #805609);
}
/* A budget block is not a transient failure -- nothing was asked upstream and
   retrying today will not change that -- so it reads amber like "no data"
   rather than red like "try again". */
.data-feed-sync-result-blocked .data-feed-sync-result-state {
  color: var(--color-warning-text, #805609);
}
.data-feed-sync-result-failed .data-feed-sync-result-state {
  color: var(--color-error);
}

/* #5858: the deposit-holding checkbox's disagreement note in the bank
   account edit modal. Text-only amber, same reasoning as the two rules
   just above -- informational, never blocking, so it reads as a nudge
   rather than an error. */
.field-note-warning {
  color: var(--color-warning-text, #805609);
}

/* ---- "Fresh values that differ from yours" override dialog ---- */

/* A wide table in a narrow modal: scroll it rather than let the action column
   fall off the card. */
.data-feed-override-scroll {
  overflow-x: auto;
  margin-bottom: var(--space-4);
}
/* The shared .mini-table header colour is --color-text-faint, which is ~2.6:1
   on this surface. These headers are the only label the value columns get, so
   they use the 4.5:1-clearing muted token instead. */
.data-feed-override-table thead th {
  color: var(--color-text-muted);
}
/* The field name is the row's own heading, not a muted axis label like the
   first column of a metrics table, and it reads as prose rather than a figure. */
.data-feed-override-table tbody th {
  text-align: left;
  font-weight: 600;
  color: var(--color-text);
}
/* Long assessor values and long field names both wrap instead of forcing the
   table wider than the card. */
.data-feed-override-table th,
.data-feed-override-table td {
  overflow-wrap: anywhere;
  vertical-align: baseline;
}
.data-feed-override-table .data-feed-override-new {
  font-weight: 650;
  color: var(--color-text);
}
.data-feed-override-table .data-feed-override-action {
  font-family: var(--font-body);
  white-space: nowrap;
}
.data-feed-override-table .data-feed-override-apply {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-3);
}
/* Applied rows keep their old value visible so the replacement stays auditable;
   the button carries the state. Same green-disabled treatment as the tax
   panel's "Use $X as property taxes" action. */
.data-feed-override-table .data-feed-override-apply:disabled {
  border-color: var(--color-success);
  color: var(--color-success-text);
  background: var(--color-success-highlight);
  cursor: default;
}
.data-feed-override-table .data-feed-override-row-applied .data-feed-override-current {
  text-decoration: line-through;
  color: var(--color-text-muted);
}
/* "Replace all" is the secondary action: it sits left, away from Done, and is
   text-styled rather than a button, so the per-row buttons stay the primary
   interaction. Done holds the right edge whether or not it is showing. */
.data-feed-override-actions {
  align-items: center;
  justify-content: flex-end;
}
.data-feed-override-actions .action-btn {
  margin-right: auto;
}

/* Transient completion notice for Pull Remote Data (success / partial /
   failure). Fixed to the viewport bottom so it never shifts page layout. */
.app-toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  z-index: 60;
  max-width: min(90vw, 34rem);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  text-align: center;
}
.app-toast.app-toast-success {
  border-color: var(--color-success);
  background: var(--color-success-highlight);
}
.app-toast.app-toast-warn {
  border-color: var(--color-warning);
  background: var(--color-warning-highlight);
}
.app-toast.app-toast-error {
  border-color: var(--color-error);
}
/* #6374: the optional Undo action stays visually inline with the toast
   message without relying on an inline declaration in app.js. */
.app-toast-action {
  display: inline;
  margin-left: 8px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.empty-state {
  text-align: center;
  padding: var(--space-8) var(--space-4);
}
.empty-state .panel-heading {
  justify-content: center;
}
.empty-state .field-note {
  margin: 0 auto;
  max-width: 40ch;
}
.record-status-group {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}
.record-status-group[hidden] {
  display: none;
}
.status-pill {
  border-radius: var(--radius-full);
  border-width: 1px;
  cursor: pointer;
}
.status-legend-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text-muted);
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.status-legend-btn svg {
  width: 16px;
  height: 16px;
}
.status-legend-btn:hover {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}
.btn-export[hidden] {
  display: none;
}

/* ================= Property page header ================= */
.property-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-5);
}
.property-header-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  align-self: flex-start;
  padding: var(--space-1) 0;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
  transition: color var(--transition-interactive);
}
.property-header-back svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.property-header-back:hover {
  color: var(--color-primary);
}
.property-header-title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 650;
  letter-spacing: -0.01em;
  margin: 0;
  overflow-wrap: anywhere;
}
.property-header-address {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-base);
}
.property-header-address[hidden] {
  display: none;
}
/* Record provenance line (Task List ID 347) -- "Saved by ... (last edited
   ...)". Carries the metadata that used to sit inside the full-width
   #viewingBanner callout. Deliberately quieter than the address above it:
   useful context, not a notice. */
.property-header-provenance {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}
.property-header-provenance[hidden] {
  display: none;
}

/* #4320: header gains an inline click-to-edit affordance for the same
   address/name fields the identity fieldset edits. .property-header-titles
   wraps the existing title/address/help-anchor trio plus the new Edit
   button so they can be hidden as one unit while .property-header-edit-form
   is shown in their place -- same toggle shape as
   propertyOwnerRow/operatingEntityRow's edit <-> save/cancel swap. */
.property-header-titles {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.property-header-edit-btn {
  flex-shrink: 0;
}
.property-header-edit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2);
  max-width: 40rem;
}
.property-header-edit-form .field {
  flex: 1 1 14rem;
  min-width: 0;
}
.property-header-save-btn,
.property-header-cancel-btn {
  flex-shrink: 0;
}

/* #4320: primary tab strip only shows the 7 everyday sections; Loan,
   Purchase Info, Metrics and the Own+/Assumptions tab (.main-tab-in-more)
   move into the new More dropdown instead. Buttons stay in their existing
   DOM position/order (tests/propertySystems.unit.js depends on it) -- this
   is a purely visual demotion layered on top of, and independent from, the
   existing phase-based `hidden` attribute toggling in app.js. A tab that is
   BOTH .main-tab-in-more and genuinely [hidden] by phase stays correctly
   invisible either way, since [hidden] already wins via the browser's
   built-in display:none. */
.main-tab-in-more {
  display: none;
}
/* The More toggle button lives alongside the same .main-tabs strip as the
   plain .main-tab buttons, so it borrows that strip's tab-like look
   (padding/size/weight/underline-on-open) rather than the pill-shaped
   .header-actions toggle used by the top nav's own dropdowns.
   #4723: scoped to .main-tabs-wrap, not .main-tabs -- the group moved to be
   .main-tabs's sibling (see the aria-required-children fix above) rather
   than its last flex child, so a .main-tabs descendant selector no longer
   matches it. */
.main-tabs-wrap .nav-dropdown {
  flex-shrink: 0;
  display: flex;
  align-self: stretch;
  /* #4723: same border/margin .main-tabs draws for the tab strip -- this
     group is now .main-tabs's flex sibling instead of its last child, so it
     needs its own copy to keep reading as one continuous bottom-bordered
     row instead of the border stopping short where the tab buttons end. */
  border-bottom: 1px solid var(--color-divider);
  margin-bottom: var(--space-6);
}
.main-tabs-wrap .nav-dropdown-toggle.main-tab-more-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
  padding: var(--space-3) var(--space-4);
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  transition: color var(--transition-interactive), border-color var(--transition-interactive);
}
.main-tabs-wrap .nav-dropdown-toggle.main-tab-more-toggle:hover {
  color: var(--color-text);
}
.main-tabs-wrap .nav-dropdown.open .nav-dropdown-toggle.main-tab-more-toggle {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}
/* Menu opens from the right edge of the strip rather than the left, since
   the toggle sits at the far right of #mainTabNav and a left-anchored menu
   would overflow the viewport on narrow screens -- mirrors .nav-more's own
   right-anchored menu above. */
.main-tabs-more > .nav-dropdown-menu {
  right: 0;
  left: auto;
}

/* No-baseline underwriting note (Task List ID 349). Stands in for the hidden
   "Underwriting vs. Actual" panel on a property that was already owned when it
   was added, so it has no original_underwriting snapshot to compare against.
   Deliberately a quiet sentence, NOT a panel or a callout: ID 347 stripped
   three stacked banners off this page and replacing a hidden card with a new
   filled block would undo that. Same muted treatment as the header provenance
   line above so it reads as context rather than as a warning. */
.uw-no-baseline-note {
  margin: 16px 0 0;
  /* --color-text-muted, not --color-text-faint: the first pass used faint and
     the explanation ended up LIGHTER than the .panel-note footer directly
     below it on the Sold card, which inverted the importance of the two. */
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.5;
  max-width: 68ch;
}
.uw-no-baseline-note[hidden] {
  display: none;
}

/* Lifecycle phase badge -- sits between the back button and the title.
   Tone comes from data-phase so it always tracks the four canonical
   phases (analyze/own/sell/sold) without extra JS-side class juggling. */
.phase-badge {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  padding: 3px var(--space-2);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
}
.phase-badge[hidden] {
  display: none;
}
/* ID 266: same base-tone-as-text problem as .kpi-flag above -- the Own badge
   measured 4.07:1 and the Sell badge 3.25:1. Borders keep the base tone. */
.phase-badge[data-phase='own'] {
  border-color: var(--color-success);
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}
.phase-badge[data-phase='sell'] {
  border-color: var(--color-warning);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.phase-badge[data-phase='sold'] {
  border-color: var(--color-text-faint);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}

/* Properties Nav Restructure (113): compact, non-interactive phase pill
   inside the saved-list table's Phase column -- reuses the same
   .phase-badge tone rules as the property header badge above, just
   without the pointer cursor/click affordance. */
.phase-col-td .phase-badge {
  cursor: default;
  align-self: center;
}

/* The Phase column only carries useful information on Owned Properties
   (113) -- every row on Analyzing Properties already shares the same
   Analyze phase, so the column is hidden there via this class on the
   table root rather than duplicating markup per mode. */
.phase-col-hidden .phase-col-th,
.phase-col-hidden .phase-col-td {
  display: none;
}

/* Owned Properties list (ID 168): the Status column (analyze_status dropdown)
   is meaningless in Own/Sell phase -- analyze_status is frozen from before
   the property left Analyze -- so the header + per-row cell are hidden on
   the Owned destination via this class on the table root. Same pattern as
   .phase-col-hidden above, just inverted (hidden in Owned, shown in
   Analyzing). */
.status-col-hidden .status-col-th,
.status-col-hidden .status-col-td {
  display: none;
}

/* Closing date column (ID 632): the target closing date comes off the
   Analyze-phase purchase contract and is only populated at analyze_status
   'offer_accepted', so it is hidden on the Owned destination the same way
   Status is -- an owned property has already closed. */
.closing-col-hidden .closing-col-th,
.closing-col-hidden .closing-col-td {
  display: none;
}
.closing-col-td {
  white-space: nowrap;
}

/* #4314: the Option A grouped-view rules that used to live here
   (.saved-list-group-row/-cell/-count for OWNED/SELLING section headers)
   were removed -- Owned Properties now uses a phase filter plus the
   existing Phase column instead of grouped section headers. */

/* Phase lock hint (Task List ID 347). Replaces the retired
   .underwriting-lock-banner, which was a full-width warning callout pinned
   above the property action toolbar on every Own/Sell/Sold load. Same warning
   tone tokens so contrast holds in both themes, but rendered inline next to
   the locked fields (Property Info tab + Financing sub-tab) instead of as a
   page-level notice. */
.phase-lock-hint {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-4);
  max-width: 46rem;
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  font-size: var(--text-sm);
  line-height: 1.5;
}
.phase-lock-hint[hidden] {
  display: none;
}
/* Inline SVG lock rather than a 🔒 emoji: the rest of the app draws icons as
   currentColor SVGs, and an emoji renders at a different size and weight per
   platform. */
.phase-lock-hint-icon {
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  margin-top: 0.15em;
}
.phase-lock-hint .link-btn {
  flex-shrink: 0;
  color: var(--color-warning);
  font-weight: 600;
  text-decoration: underline;
}

/* Property Managers / Maintenance rows (Task List ID 347; moved onto the
   Overview tab as a pair by #3160) -- reuse .status-card-row's shell. The
   name list is allowed to wrap and the Edit link is pinned right, so a long
   list of PMs/contacts can't push Edit off the row on narrow viewports. */
.overview-department-rows {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.overview-department-rows .status-card-row {
  flex: 1 1 20rem;
  margin-bottom: 0;
}
.property-managers-row,
.maintenance-row,
.property-owner-row {
  flex-wrap: wrap;
}
.property-managers-row-text,
.maintenance-row-text,
.property-owner-row-text {
  font-size: var(--text-sm);
  color: var(--color-text);
  overflow-wrap: anywhere;
  min-width: 0;
  flex: 1 1 12rem;
}
.property-managers-row-edit,
.maintenance-row-edit,
.property-owner-row-edit {
  flex-shrink: 0;
  margin-left: auto;
}
/* #3490: Owner row's inline select + Save/Cancel pair, shown in place of
   the summary text + Edit button while editing. Save stays primary-styled
   via the existing .action-btn base; Cancel intentionally reuses the same
   class so it doesn't need its own rule, matching the plain-text-button
   feel of a "never mind" action elsewhere in the app. */
.property-owner-row-select {
  flex: 1 1 12rem;
  min-width: 0;
  font-size: var(--text-sm);
}
.property-owner-row-save,
.property-owner-row-cancel {
  flex-shrink: 0;
}
/* Past Analyze with nobody assigned: auto-generated capital-forecast tasks have
   nobody to assign to. Deliberately warning-toned TEXT only, not a filled
   warning block -- these rows sit directly above the KPI grid and, on an Own
   property, near the amber .phase-lock-hint, and two stacked amber blocks is
   the same visual shouting this task set out to remove. (It also avoids a
   pointless specificity fight: .status-card-row's background is declared
   later in this file, so a plain background override here would silently
   lose anyway.) */
.property-managers-row-warning .property-managers-row-text,
.maintenance-row-warning .maintenance-row-text {
  /* #3527: was var(--color-warning). The base amber (#a8720f) lands at 4.0:1 on
     the light-theme card surface (#fbfbfc) -- a serious WCAG 2.1 AA
     color-contrast failure that axe flagged on the Overview tab. Dark theme
     passed at 7.23:1, which is why only the light scan went red. The
     text-specific token is 6.24:1 and is already the convention for amber TEXT
     everywhere else in this file (see the note by --color-warning-text). */
  color: var(--color-warning-text);
  font-weight: 500;
}

/* Task List ID 124: balloon-approaching cue on the property Overview tab
   and a shared style for the Portfolio Dashboard rollup rows. Two tones:
     data-tone="warn"    -> yellow, balloon 3-12mo out
     data-tone="danger"  -> red,    balloon <=3mo out */
.overview-balloon-banner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  margin: var(--space-4) 0;
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  font-size: var(--text-sm);
}
.overview-balloon-banner[data-tone="danger"] {
  border-color: var(--color-error);
  background: color-mix(in srgb, var(--color-error) 12%, transparent);
  color: var(--color-error-text);
}
.overview-balloon-banner[hidden] { display: none; }
.overview-balloon-banner-icon { font-size: 1.1em; }

/* #4149: single Actuals-first KPI strip heading + basis tag, and the
   no-Actuals-logged fallback explainer. Same warning palette as the
   balloon banner above -- both are "heads up, read the fine print" cues. */
.overview-kpi-heading-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}
.overview-kpi-heading {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 650;
  margin: 0;
}
.overview-kpi-basis-tag {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-success-text, var(--color-primary));
}
.overview-fallback-banner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-4);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  font-size: var(--text-sm);
}
.overview-fallback-banner[hidden] { display: none; }

/* #4149: "Looking ahead" run-rate panel -- a lightweight projection, not
   the full scenario tool, so it gets a quieter highlighted-callout treatment
   rather than a full data table. */
.overview-runrate-callout {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-2, var(--color-bg));
}
.overview-runrate-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.overview-runrate-label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-primary);
}
.overview-runrate-value {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--color-primary);
}
.overview-runrate-caption {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.overview-runrate-link {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-primary);
}

.dashboard-balloon-panel .balloon-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider, var(--color-border));
}
.dashboard-balloon-panel .balloon-row:last-of-type { border-bottom: none; }
.dashboard-balloon-panel .balloon-row .balloon-days[data-tone="warn"] {
  color: var(--color-warning-text);
  font-weight: 600;
}
.dashboard-balloon-panel .balloon-row .balloon-days[data-tone="danger"] {
  color: var(--color-error-text);
  font-weight: 700;
}
.dashboard-balloon-panel .balloon-footer {
  padding-top: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.nav-dropdown-divider {
  width: 100%;
  border: none;
  border-top: 1px solid var(--color-divider, var(--color-border));
  margin: var(--space-1) 0;
}
.nav-dropdown-divider[hidden] {
  display: none;
}

/* Properties Nav Consolidation (Task List ID 154, Option A): uppercase
   phase labels grouping items inside the consolidated Properties
   dropdown menu (Analyzing / Owned / Sold). First label sits flush
   against the menu's own top padding, so it gets no extra top margin. */
.nav-section-label {
  padding: var(--space-1) var(--space-3) 2px;
  margin-top: var(--space-1);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.nav-section-label:first-child {
  margin-top: 0;
}

#lifecycleModalError {
  color: var(--color-error-text);
  font-weight: 600;
}

/* -----------------------------------------------------------------
   "I bought it" modal - acquisition capture (Task List ID 120).
   Collapsible <details> sections keep the modal compact while
   still allowing full capture of all financing/ops/audit fields.
   Modal itself scrolls if content overflows the viewport.
   ----------------------------------------------------------------- */
#lifecycleModal .modal-card {
  max-height: 90vh;
}
#lifecycleModal .modal-body {
  max-height: calc(90vh - 88px);
  overflow-y: auto;
}
.lifecycle-buy-extras {
  display: flex;
  flex-direction: column;
  gap: var(--space-3, 12px);
  margin-top: var(--space-3, 12px);
}
.lifecycle-buy-units-confirmation {
  margin-top: var(--space-3, 12px);
  padding: var(--space-3, 12px);
  border: 1px solid var(--color-border, #d5d8dd);
  border-radius: 8px;
  background: var(--color-surface-2, #fafbfc);
}
.lifecycle-buy-units-confirmation h3 {
  margin: 0;
  font-size: 1rem;
}
.lifecycle-buy-units-confirmation-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(190px, auto);
  align-items: center;
  gap: var(--space-3, 12px);
  padding: var(--space-2, 8px) 0;
  border-top: 1px solid var(--color-border, #d5d8dd);
}
.lifecycle-buy-units-confirmation-row:first-child {
  margin-top: var(--space-2, 8px);
}
.lifecycle-buy-units-confirmation-row select {
  width: 100%;
}
@media (max-width: 640px) {
  .lifecycle-buy-units-confirmation-row {
    grid-template-columns: 1fr;
  }
}
.lifecycle-buy-section {
  border: 1px solid var(--color-border, #d5d8dd);
  border-radius: 8px;
  padding: var(--space-2, 8px) var(--space-3, 12px);
  background: var(--color-surface-2, #fafbfc);
}
.lifecycle-buy-section > summary {
  font-weight: 600;
  cursor: pointer;
  padding: var(--space-2, 8px) 0;
  list-style: none;
  color: var(--color-text, #1f2328);
}
.lifecycle-buy-section > summary::marker,
.lifecycle-buy-section > summary::-webkit-details-marker {
  display: none;
}
.lifecycle-buy-section > summary::before {
  content: '\25B8';
  display: inline-block;
  width: 1em;
  margin-right: 4px;
  transition: transform 0.15s ease;
}
.lifecycle-buy-section[open] > summary::before {
  transform: rotate(90deg);
}
.lifecycle-buy-section .field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(3, auto);
  align-items: start;
  gap: var(--space-3, 12px);
  margin-top: var(--space-2, 8px);
}
/* #4131 (mirrors #549's #mainPanelPropertyInfo fix): a field-note with more
   wrapped lines than its row partner (e.g. Loan Term's long autofill note
   next to Interest Rate's short one) pushed the paired field down inside
   its own cell even though the row's overall height was already equal.
   Subgrid keeps label/input/note aligned across both fields in the row
   regardless of note length. Scoped to .lifecycle-buy-section, which is
   verified (see #4131) to contain only plain label-input-note .field
   triples -- no .field-checkbox or .group-label that would want the old
   stretch-to-row-height behavior instead. */
.lifecycle-buy-section .field-row > .field {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: 1 / 4;
  margin-bottom: 0;
}
.lifecycle-buy-section .field-row > .field > label {
  grid-row: 1;
}
.lifecycle-buy-section .field-row:has(> .field:only-child) {
  grid-template-columns: 1fr;
}
@media (max-width: 640px) {
  .lifecycle-buy-section .field-row {
    grid-template-columns: 1fr;
  }
}
.lifecycle-buy-inline-toggle .toggle-label,
.lifecycle-buy-tax-reassessment .toggle-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
  cursor: pointer;
}
.lifecycle-buy-inline-toggle input,
.lifecycle-buy-tax-reassessment input[type="checkbox"] {
  margin: 0;
}
.lifecycle-buy-computed {
  font-size: 1.05em;
  font-weight: 600;
  color: var(--color-text, #1f2328);
  margin: 4px 0;
  padding: 6px 10px;
  /* --color-surface-3 was never defined, so this always fell back to the
     hard-coded light #eef1f4 -- in dark mode that put near-white text on a
     near-white chip (contrast ~1.05:1, effectively invisible). ID 384 QA. */
  background: var(--color-surface-offset);
  border-radius: 4px;
  display: inline-block;
  min-width: 100px;
}
.lifecycle-buy-tax-reassessment {
  margin-top: var(--space-3, 12px);
  padding-top: var(--space-3, 12px);
  border-top: 1px dashed var(--color-border, #d5d8dd);
}
/* #289: provenance marker under a Current-column figure in the Own-phase
   Underwriting vs. Current table. Marks the rows that are measured off the
   rent roll rather than typed, so a reader can tell them apart from the
   assumptions sitting in the same column. Block so it sits under the number
   rather than pushing the cell wide; no margin-top, unlike .field-note,
   because a table row has no room for one. */
.uw-src-note {
  display: block;
  font-size: var(--text-xs);
  line-height: 1.4;
  font-weight: 400;
  color: var(--color-text-muted);
}
.uw-src-note[hidden] {
  display: none;
}

.field-note.field-note-warn {
  color: var(--color-warning-text, #805609);
  font-weight: 500;
}

/* Balloon date field on Analyze form */
.field-row-1 {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3, 12px);
  margin-top: var(--space-2, 8px);
}

/* ================= Property actions row ================= */
.property-actions-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  position: sticky;
  top: var(--header-h);
  z-index: 15;
  margin-inline: calc(-1 * clamp(var(--space-4), 4vw, var(--space-10)));
  padding: var(--space-3) clamp(var(--space-4), 4vw, var(--space-10));
  margin-bottom: var(--space-6);
  background: color-mix(in oklab, var(--color-surface) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-divider);
}
.property-actions-row[hidden] {
  display: none;
}
.property-actions-scroll {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.property-actions-scroll::-webkit-scrollbar {
  display: none;
}
.property-actions-row .btn-export {
  flex-shrink: 0;
}
.property-more-dropdown {
  flex-shrink: 0;
}
.property-more-menu {
  left: auto;
  right: 0;
}

/* Hidden modal-trigger proxies: the Showings/Repairs buttons are no longer
   shown in the actions row (their main-tab-nav items call .click() on them
   instead), but they stay in the DOM with their original ids/handlers
   untouched. This wrapper keeps them invisible regardless of their own
   `hidden` attribute, which is still toggled by updateSaveButtonState(). */
.hidden-modal-triggers {
  display: none;
}

/* ================= Sticky results strip (#211, spec 5.4.2) =================
   Pins directly under the property actions row, which is itself sticky under
   the site header. Three sticky bands is already the practical ceiling on a
   short laptop viewport -- #210 finding 3 measured the stack at roughly 200px
   of 900px -- so this strip is deliberately one compact line, and the
   max-height query below squeezes it further rather than letting the chrome
   eat the form.

   --property-actions-h is measured by app.js (ResizeObserver on the actions
   row) instead of hard-coded, because that row wraps to two lines on narrow
   viewports and a magic number would leave the strip either overlapping it or
   floating below a gap. The 60px fallback is only what is used before the
   first measurement lands. */
:root {
  --property-actions-h: 60px;
}
.results-strip {
  grid-column: 1 / -1;
  position: sticky;
  top: calc(var(--header-h) + var(--property-actions-h));
  z-index: 14;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  margin-inline: calc(-1 * clamp(var(--space-4), 4vw, var(--space-10)));
  padding: var(--space-3) clamp(var(--space-4), 4vw, var(--space-10));
  margin-bottom: var(--space-5);
  background: color-mix(in oklab, var(--color-surface) 94%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-divider);
}
.results-strip[hidden] {
  display: none;
}
.results-strip-metrics {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-5);
  flex: 1 1 auto;
  min-width: 0;
}
.results-strip-metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.results-strip-label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.results-strip-value {
  font-family: var(--font-mono, inherit);
  font-size: var(--text-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
  white-space: nowrap;
}
/* Tone comes from the same flag vocabulary renderKPIs() already uses, so a
   figure does not read one colour in the strip and another on the Metrics
   tab. Set by app.js via data-tone. */
/* The -text variants, not the plain ones: legacy ID 265 established that
   --color-success/--color-warning are border- and icon-safe but fall under
   4.5:1 as text, and these are text. The contrast suite checks this. */
.results-strip-value[data-tone='good'] {
  color: var(--color-success-text);
}
.results-strip-value[data-tone='warn'] {
  color: var(--color-warning-text);
}
.results-strip-value[data-tone='bad'] {
  color: var(--color-error-text);
}
.results-strip-basis {
  flex: 0 0 auto;
}
.results-strip-caption,
.results-strip-note {
  flex: 1 1 100%;
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.results-strip-note[hidden] {
  display: none;
}
@media (max-height: 820px) {
  /* Short viewport: drop the per-tab caption and tighten the band. The four
     figures and the basis toggle are the load-bearing parts. */
  .results-strip {
    padding-block: var(--space-2);
    margin-bottom: var(--space-4);
  }
  .results-strip-caption {
    display: none;
  }
  .results-strip-value {
    font-size: var(--text-base);
  }
}
@media (max-width: 700px) {
  /* Narrow screens: the band is the third sticky layer under .site-header and
     .property-actions-row (finding 3 in #210), so it cannot be allowed to wrap
     into three rows -- at 390x780 it measured 172px, which with the header put
     roughly a third of the viewport behind fixed chrome. One scrollable row
     instead: the four figures keep their real values and the basis toggle stays
     reachable, the row just scrolls sideways rather than growing downward. */
  .results-strip {
    flex-wrap: nowrap;
    gap: var(--space-3);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .results-strip::-webkit-scrollbar {
    display: none;
  }
  .results-strip-metrics {
    flex-wrap: nowrap;
    gap: var(--space-4);
    /* Must NOT be flex: 1 1 auto here. With nowrap and min-width: 0 the four
       figures compress instead of overflowing, and at 390px they render on top
       of one another -- caught in QA, the labels literally overprinted. Pinning
       both the track and each metric to their content size is what makes the
       row actually overflow so the scroll above has something to scroll. */
    flex: 0 0 auto;
  }
  .results-strip-metric,
  .results-strip-basis {
    flex: 0 0 auto;
  }
  .results-strip-caption,
  .results-strip-note {
    display: none;
  }
}
@media print {
  /* Sticky chrome has no meaning in a PDF, and the same four figures are in
     the exported KPI block already. */
  .results-strip {
    display: none;
  }
}

/* ================= Derived read-only lines (#211, spec 5.4.1a) =================
   Gross potential rent and Effective gross income inside the Income block.
   Styled as statements, not inputs -- no border, no input-wrap -- because they
   are computed and typing into them is not a thing you can do. */
.derived-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--text-sm);
}
/* #4367: at phone width a longer label (e.g. "Effective gross income")
   plus its nowrap value (e.g. "$15,600/yr") can together exceed the row's
   available width. Flex children don't wrap by default, so instead of
   reflowing the row grows past its container with no scrollbar -- the
   value clips mid-word, silently, exactly like the ticket's screenshot.
   Letting the row itself wrap keeps each side internally nowrap (numbers
   still read as one token) while allowing label and value to drop onto
   separate lines when they don't both fit -- desktop width is unaffected
   since normal labels/values fit on one line well above 640px. */
@media (max-width: 640px) {
  .derived-line {
    flex-wrap: wrap;
  }
}
.derived-line-total {
  border-top: 1px solid var(--color-divider);
  margin-top: var(--space-2);
  font-weight: 600;
}
.derived-line-label {
  color: var(--color-text-muted);
}
.derived-line-total .derived-line-label {
  color: var(--color-text);
}
.derived-line-basis {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.derived-line-value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}

/* ================= Main tab navigation ================= */
/* .calculator is a 2-column grid (input col + results col) left over from
   the old side-by-side layout. The tab nav and each tab panel are direct
   children of .calculator now, so they must span both grid columns --
   otherwise grid auto-placement would put the nav and the one visible
   panel side by side instead of stacked. */
.main-tabs-wrap,
.main-tabs,
.main-tabpanel {
  grid-column: 1 / -1;
}
/* #4321: positioning context for the trailing scroll-fade affordance below --
   .main-tabs itself scrolls, so the fade lives on this non-scrolling wrapper,
   overlaid on top via ::after rather than inside the scrolling nav. */
/* #4723: now the flex row itself -- #mainTabsMoreGroup moved out of
   #mainTabNav (it isn't a role="tab", so axe's aria-required-children
   flagged it as an unallowed direct child of the tablist) to become this
   wrapper's second flex child instead of .main-tabs's last one. Visually
   identical: .main-tabs still owns the scrolling tab strip and its own
   trailing gap, this wrapper just also lays out the More group flush
   against its trailing edge the same as before. */
.main-tabs-wrap {
  position: relative;
  display: flex;
  align-items: stretch;
}
.main-tabs {
  display: flex;
  flex: 1;
  min-width: 0;
  gap: var(--space-1);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  border-bottom: 1px solid var(--color-divider);
  margin-bottom: var(--space-6);
}
.main-tabs::-webkit-scrollbar {
  display: none;
}
/* #4321: mobile-only hint that the tab bar has more content to scroll to --
   audit found 12 tabs (~929px) in a 375px viewport with zero affordance.
   Desktop rarely overflows at typical widths, so this is gated to the same
   900px breakpoint the rest of this screen's mobile fixes use. Pointer-events
   none since it's a visual hint layered over (not blocking) the real tabs.
   #4321 QA-FAILED follow-up: this was originally a `.main-tabs-wrap::after`
   anchored to the WRAPPER's right:0 -- but on the property tab bar the
   wrapper's last flex child is the always-visible, never-scrolling
   #mainTabsMoreGroup ("More" button), so that overlay sat on top of a
   transparent static button instead of the actual scrolling edge of
   .main-tabs, and was never visible over real tab content no matter how far
   you scrolled. Moving the fade to be `.main-tabs`'s OWN flex item, with
   `position: sticky; right: 0`, keeps it pinned to the scrolling strip's own
   trailing edge as you scroll (sticky flex items track their scroll
   container's edge) regardless of whether a trailing More group exists --
   this also makes it correct on any future `.main-tabs` instance that has no
   wrapper/More group at all (e.g. the Deal Inbox tab bar). */
@media (max-width: 900px) {
  .main-tabs::after {
    content: '';
    position: sticky;
    top: 0;
    right: 0;
    flex-shrink: 0;
    align-self: stretch;
    width: 28px;
    margin-left: -28px;
    background: linear-gradient(to right, transparent, var(--color-surface));
    pointer-events: none;
  }
}
.main-tab {
  flex-shrink: 0;
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  transition: color var(--transition-interactive), border-color var(--transition-interactive);
}
.main-tab:hover {
  color: var(--color-text);
}
.main-tab[aria-selected='true'] {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}
.main-tab[hidden] {
  display: none;
}
.main-tabpanel[hidden] {
  display: none;
}
/* #3156: Assumptions (#mainPanelPropertyInfo) and Purchase Info
   (#mainPanelPurchaseInfo) used to cap at 46rem here, leaving a large empty
   gutter on wider screens while the Overview tab's .overview-dashboard (no
   cap) filled the full tab width. Removed entirely rather than raised --
   .field-row's 2-column grid (see #549 alignment fix below) has no width
   dependency and no mobile breakpoint, so there was nothing here worth
   keeping. #unitInfoInputPanel below is untouched: it's an ID selector with
   its own explicit max-width, never inherited from this rule. */
/* #3062: Unit Info's "Rent by unit" strip is a 4-column grid (lease amount /
   lease type / lease ends / market rent), not the plain label-input pairs
   the old 46rem cap above was sized for. At 46rem the row's per-field column
   drops to ~169px, too narrow for the "Market rent" cell's label + assumption
   badge to stay on one line -- the wrapped badge squeezes the input itself
   down to ~107px and visually truncates a normal 4-digit rent value. */
#unitInfoInputPanel {
  max-width: 64rem;
}

/* Overview tab -- Phase 2 dashboard content. */
.overview-dashboard {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
/* Sold Exclusion (48d): renderSoldPropertySummaryCard() sets the [hidden]
   attribute on this section when a Sold Property Summary card should show
   instead, but the explicit `display: flex` above is an author-stylesheet
   rule that always wins over the browser's default `[hidden]{display:none}`
   UA style -- so without this override the dashboard stayed visibly
   rendered underneath the summary card even while [hidden] was set. */
.overview-dashboard[hidden] {
  display: none;
}
.dashboard-intro {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin: 0;
}
.overview-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}
@media (max-width: 900px) {
  .overview-two-col {
    grid-template-columns: 1fr;
  }
}
.overview-stat-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px dashed var(--color-border);
  font-size: var(--text-sm);
}
.overview-stat-row:last-child {
  border-bottom: none;
}
.overview-stat-row span:first-child {
  color: var(--color-text-muted);
}
.overview-stat-row span:last-child {
  font-weight: 600;
  text-align: right;
}
.overview-quick-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.overview-quick-link {
  flex: 1 1 200px;
  display: block;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-weight: 600;
  font-size: var(--text-sm);
  text-decoration: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.overview-quick-link:hover,
.overview-quick-link:focus-visible {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}
.overview-quick-link small {
  display: block;
  margin-top: var(--space-1);
  color: var(--color-text-muted);
  font-weight: 400;
  font-size: var(--text-xs);
}
/* #4381 QA fix: .overview-kpi-grid is now a .stat-strip (flex, horizontal
   scroll -- see #4377's stat-strip component), not the old .kpi-grid
   (CSS grid that wrapped to 2 columns here below 640px). The old
   grid-template-columns rule no longer applies to a flex container and is
   removed rather than left dead; .stat-strip's own overflow-x:auto is the
   single mobile behavior now, matching the ticket's "one compact
   single-row strip" requirement at every width. */
/* #5682 fix: on phone-width viewports the horizontal-scroll behavior above
   silently clips the 4th stat (DSCR) off the right edge with only a faint
   scrollbar as a cue -- easy to miss entirely. Scoped to .overview-kpi-grid
   only (NOT the shared .stat-strip/.rg-stat rule at ~21486, which other
   consumers like the Rent Roll summary strip deliberately keep as a
   non-wrapping horizontal scroll per its own #4377 comment at ~21566).
   Below 420px this specific strip wraps into a 2x2 grid instead of
   scrolling, so all 4 stats are always visible without a swipe.

   #5682 QA-FAIL follow-up: the element carries BOTH classes
   (class="stat-strip overview-kpi-grid", index.html:2332), and the shared
   rule's own @media (max-width: 420px) block at ~21569 sets nowrap on the
   base .stat-strip selector -- same specificity (one class), later in the
   file, so cascade order silently won and forced nowrap back
   on, while this block's half-width flex-basis kept applying on top of an
   un-overridden nowrap layout and produced overlapping stat labels. Every
   selector below is now the compound `.stat-strip.overview-kpi-grid` (two
   classes, higher specificity than either single-class rule) so this
   block reliably wins regardless of file order, instead of relying on
   position in a 22,000+ line stylesheet to not move again. */
@media (max-width: 420px) {
  .stat-strip.overview-kpi-grid {
    flex-wrap: wrap;
    overflow-x: visible;
  }
  .stat-strip.overview-kpi-grid .rg-stat {
    flex: 1 1 calc(50% - var(--space-4));
    min-width: 0;
    box-sizing: border-box;
    border-right: none;
    margin-right: 0;
    padding-right: 0;
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-divider);
  }
  .stat-strip.overview-kpi-grid .rg-stat:nth-last-child(-n+2) {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: none;
  }
}

/* Status card at the top of the Property Info tab. */
.status-card-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2, var(--color-bg));
  max-width: 46rem;
}
.status-card-row[hidden] {
  display: none;
}
.status-card-label {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  flex-shrink: 0;
}

@media (max-width: 480px) {
  .property-actions-row .btn-export .btn-label {
    display: none;
  }
  .property-actions-row .btn-export {
    flex-shrink: 0;
    width: 38px;
    height: 38px;
    padding: 0;
    justify-content: center;
    gap: 0;
  }
  .btn-export-primary {
    width: auto;
    padding: var(--space-2) var(--space-4);
  }
  .btn-export-primary .btn-label {
    display: inline;
  }
  .main-tab {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
  }
}


/* ================= Landing page ================= */
#landing-main[hidden] {
  display: none;
}
#landing-main {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.landing-hero {
  max-width: 38rem;
  margin-bottom: var(--space-8);
  text-align: center;
}
.landing-mark {
  height: 48px;
  width: auto;
  display: block;
  margin-bottom: var(--space-4);
}
.landing-hero h1 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 650;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-3);
}
.landing-hero p {
  color: var(--color-text-muted);
  font-size: var(--text-base);
}
.landing-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: var(--space-1);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color var(--transition-interactive), box-shadow var(--transition-interactive), transform var(--transition-interactive);
}
.landing-card:hover,
.landing-card:focus-visible {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
.landing-card-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.landing-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-md);
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  flex-shrink: 0;
}
.landing-card-icon svg {
  width: 16px;
  height: 16px;
}
.landing-card-title {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.landing-card-desc {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  line-height: 1.4;
  display: none;
}
@media (min-width: 641px) {
  .landing-card-desc {
    display: block;
  }
}

/* ================= Landing dashboard ================= */
.landing-pipeline-card {
  width: 100%;
  max-width: clamp(var(--content-wide), 92vw, 1400px);
  margin: var(--space-6) auto 0;
  text-align: left;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-4) var(--space-5);
}
.landing-pipeline-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.landing-pipeline-header h2 {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.landing-pipeline-total {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.landing-dashboard {
  width: 100%;
  max-width: clamp(var(--content-wide), 92vw, 1400px);
  margin: var(--space-8) auto 0;
  text-align: left;
}
.landing-pipeline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.landing-pipeline-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.35rem 0.75rem;
  border-radius: var(--radius-full, 999px);
  cursor: pointer;
  border: none;
  font-family: inherit;
  transition: transform var(--transition-interactive), box-shadow var(--transition-interactive);
}
.landing-pipeline-chip:hover,
.landing-pipeline-chip:focus-visible {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}
.landing-pipeline-chip-count {
  font-weight: 700;
}
/* ID 265: no opacity here. At 0.75 this span dropped every chip below AA (e.g.
   3.97:1 on the "To review" chip, 2.35:1 on "Offer accepted") because opacity
   composites the text toward its own background. The weight step against the
   700 count span carries the hierarchy instead. */
.landing-pipeline-chip-units {
  font-weight: 500;
}
.landing-dashboard-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}
@media (max-width: 900px) {
  .landing-dashboard-grid {
    grid-template-columns: 1fr;
  }
}
.landing-widget-toolbar {
  width: 100%;
  max-width: clamp(var(--content-wide), 92vw, 1400px);
  margin: var(--space-6) auto 0;
  display: flex;
  justify-content: flex-end;
}
.landing-widget-grid {
  width: 100%;
  max-width: clamp(var(--content-wide), 92vw, 1400px);
  margin: var(--space-3) auto 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
  text-align: left;
}
@media (max-width: 900px) {
  .landing-widget-grid {
    grid-template-columns: 1fr;
  }
}
.landing-widget {
  grid-column: 1 / -1;
  min-width: 0;
}
.landing-widget--half {
  grid-column: auto;
}
@media (max-width: 900px) {
  .landing-widget--half {
    grid-column: 1 / -1;
  }
}
.landing-widget[hidden] {
  display: none;
}
.landing-customize-list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.landing-customize-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 10px);
  background: var(--color-surface);
}
.landing-customize-row-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.landing-customize-row-reorder {
  display: flex;
  gap: var(--space-1, 4px);
}
.landing-customize-row-reorder button {
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  border-radius: var(--radius-sm, 6px);
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--color-text-muted);
}
.landing-customize-row-reorder button:hover:not(:disabled) {
  color: var(--color-text);
  border-color: var(--color-primary, var(--color-border));
}
.landing-customize-row-reorder button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
/* Settings > Customize landing page (Task List 196e): the org-default
   editor reuses .landing-customize-list rows above, laid out beside a
   read-only preview of the resulting order. */
.landing-layout-editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, .8fr);
  gap: var(--space-6);
  align-items: start;
}
@media (max-width: 900px) {
  .landing-layout-editor {
    grid-template-columns: 1fr;
  }
}
.landing-layout-preview {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.landing-layout-preview li {
  color: var(--color-text);
}
.landing-layout-preview li.is-hidden-widget {
  color: var(--color-text-muted);
  text-decoration: line-through;
}
.landing-layout-preview-empty {
  margin: var(--space-3) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.landing-panel {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-5);
}
.landing-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}
.landing-panel-header h2 {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 650;
  margin: 0;
}
.landing-recent-list,
.landing-searchwatch-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.landing-recent-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border: none;
  background: transparent;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
  text-decoration: none;
}
.landing-recent-item + .landing-recent-item {
  border-top: 1px solid var(--color-divider);
}
.landing-recent-item:hover .landing-recent-address {
  color: var(--color-primary);
}
.landing-recent-main {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}
.landing-recent-address {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 22rem;
}
.landing-recent-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.landing-recent-side {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}
.landing-recent-caprate {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}
.landing-searchwatch-item {
  padding: var(--space-3) 0;
}
.landing-searchwatch-item + .landing-searchwatch-item {
  border-top: 1px solid var(--color-divider);
}
.landing-searchwatch-name-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: 0.2rem;
}
.landing-searchwatch-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.landing-searchwatch-badge {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  flex-shrink: 0;
}
.landing-searchwatch-badge[data-enabled='true'] {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}
.landing-searchwatch-summary {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
}

/* ================= Calculator grid ================= */

.calculator {
  display: grid;
  grid-template-columns: minmax(0, 400px) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

@media (max-width: 900px) {
  .calculator {
    grid-template-columns: minmax(0, 1fr);
  }
}

.panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  /* Task List ID 134: bare .panel had no default padding, causing content
     (ledger rows, stat rows, quick-link boxes) to bleed outside the card's
     visible border on Overview dashboard cards -- Income & Expense Snapshot,
     At a Glance, Jump to. The specialized panel variants (.input-panel,
     .breakdown-panel, .sensitivity-panel, .projection-panel) each redundantly
     re-declared the same padding: var(--space-6), which is now the default
     here -- their explicit rules keep the same value so no visual change. */
  padding: var(--space-6);
}

.input-panel {
  padding: var(--space-6);
}

.field-group {
  border: none;
  padding: 0;
  margin: 0 0 var(--space-6) 0;
}
.field-group:last-child {
  margin-bottom: 0;
}

/* #5812: open-ended operating-expense list (Analyze fieldset + Own+
   Renewals & Expenses card share these classes -- see
   renderOperatingExpensesList() in app.js). */
.opex-list-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.opex-list-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.opex-list-row {
  display: grid;
  grid-template-columns: 1fr 160px 32px;
  gap: var(--space-3);
  align-items: center;
}
.opex-row-label {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  font-size: var(--text-sm);
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-surface);
}
.opex-row-label:disabled {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}
.opex-row-cadence {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .03em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: var(--radius-full);
  font-weight: 700;
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  margin-left: 6px;
  white-space: nowrap;
}
.opex-row-remove {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-error-text);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.opex-row-remove:disabled {
  opacity: .35;
  cursor: default;
}
.btn-add-row {
  margin-top: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px dashed var(--color-primary);
  color: var(--color-primary);
  background: transparent;
  border-radius: var(--radius-sm);
  padding: 7px 14px;
  font-size: var(--text-xs);
  font-weight: 700;
  cursor: pointer;
}
.btn-add-row:disabled {
  border-color: var(--color-border);
  color: var(--color-text-muted);
  cursor: not-allowed;
}
/* #5871: houses the Utilities/Garbage/Custom trio next to each other --
   .btn-add-row keeps its own margin-top so this wraps the same as a lone
   button did before. */
.opex-add-row-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.opex-add-row-group .btn-add-row {
  margin-top: var(--space-3);
}
.opex-list-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-divider);
}
.opex-list-total .label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 600;
}
.opex-list-total .val {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  font-weight: 700;
}
/* Own+ Renewals card compact (read-only) row variant. */
.opex-list-rows.is-compact .opex-list-row {
  grid-template-columns: 1fr auto;
  padding: 5px 0;
  border-bottom: 1px solid var(--color-divider);
}
.opex-list-rows.is-compact .opex-list-row:last-of-type {
  border-bottom: none;
}
.opex-row-label-static {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  display: flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.opex-row-amt-static {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  white-space: nowrap;
}
.field-group legend {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-divider);
  width: 100%;
}

/* #5188 -- Assumptions tab card redesign. RESTYLE ONLY: extends .field-group
   rather than fighting it, on the same `<fieldset data-fieldset="...">` nodes
   applyFieldsetLayout() (app.js) already moves between containers by phase.
   No wrapping element around the fieldset itself, because a wrapper would be
   left behind (or dragged along wrongly) by that DOM-move -- see the
   `purchase`/`loan` fieldset comments in index.html. Giving the bordered-card
   look to the fieldset directly means the card travels correctly wherever
   the fieldset is reparented (Purchase Info, Loan tab, or the Assumptions
   scroll), in every phase, with zero JS changes to the move logic itself. */
.assumptions-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-4) var(--space-5);
  margin: 0 0 var(--space-4) 0;
}
.assumptions-card:last-child {
  margin-bottom: 0;
}
.assumptions-card legend {
  margin-bottom: var(--space-2);
}
/* Purchase & financing is one visual card split across two <fieldset>s
   (`purchase` then `loan`) that stay DOM-adjacent only in Analyze -- see the
   `purchase` fieldset's comment in index.html for why they cannot share one
   wrapping element. This adjacent-sibling rule is what actually draws them
   as a single seamless box when they are next to each other; on the Own+
   Loan tab, `loan` has no preceding `.assumptions-card` sibling, the
   selector does not match, and it renders as its own independent card. */
.assumptions-card + .assumptions-card {
  margin-top: calc(-1 * var(--space-4));
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  padding-top: var(--space-3);
}
.assumptions-card + .assumptions-card > legend {
  border-bottom-style: dashed;
}

/* #5454: Underwriting tab card-grid redesign (Analyze only). The single-
   column "Property and financing inputs" scroll becomes a responsive card
   grid -- Purchase, Financing, Income, Operating expenses, Growth &
   projection each their own bordered card. Grid lives on the SAME container
   element applyFieldsetLayout() already appends fieldsets into
   (`[data-fieldset-container~="underwriting"]`), so no DOM restructuring or
   JS change was needed to place the cards -- only this CSS.
   `.underwriting-grid-static` marks the header/badge/status-row children that
   must span the full grid width instead of becoming grid cells; every
   `.assumptions-card` (the fieldsets themselves) gets an implicit single
   grid cell with no span rule, so the grid auto-flows them two per row. */
[data-fieldset-container~="underwriting"].underwriting-card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  align-items: start;
}
[data-fieldset-container~="underwriting"].underwriting-card-grid > .underwriting-grid-static {
  grid-column: 1 / -1;
}
[data-fieldset-container~="underwriting"].underwriting-card-grid > .assumptions-card {
  margin: 0;
}
/* Purchase and Financing must render as two INDEPENDENT cards in the grid,
   never the merged single box `.assumptions-card + .assumptions-card`
   produces when they are adjacent siblings on the old single-column scroll
   (still true and unchanged for Own+'s Loan tab, a different container
   entirely -- see that rule's own comment above). Grid children are still
   DOM-adjacent siblings, so the merge selector would otherwise fire here
   too; this override wins on specificity and restores the normal card look
   for both, without touching the shared selector itself. */
[data-fieldset-container~="underwriting"].underwriting-card-grid > .assumptions-card + .assumptions-card {
  margin-top: 0;
  border-top: 1px solid var(--color-border);
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  padding-top: var(--space-4);
}
[data-fieldset-container~="underwriting"].underwriting-card-grid > .assumptions-card + .assumptions-card > legend {
  border-bottom-style: solid;
}
@media (max-width: 900px) {
  [data-fieldset-container~="underwriting"].underwriting-card-grid {
    grid-template-columns: 1fr;
  }
}

/* #5454: collapsed "More financing details" section inside the Financing
   card -- Loan ID, points paid at close, first payment date, payment due
   day, prepayment penalty + notes. Closed by default; native <details> so
   no JS is needed for the toggle itself. Deliberately lighter-weight than
   `.lifecycle-buy-section` (no border/background of its own) since it
   already lives inside a bordered `.assumptions-card` -- a second border
   here would nest boxes inside boxes. */
.assumptions-card-more {
  margin-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
}
.assumptions-card-more > summary {
  font-weight: 600;
  font-size: var(--text-sm, 0.875rem);
  cursor: pointer;
  list-style: none;
  color: var(--color-text-muted);
}
.assumptions-card-more > summary::marker,
.assumptions-card-more > summary::-webkit-details-marker {
  display: none;
}
.assumptions-card-more > summary::before {
  content: '\25B8';
  display: inline-block;
  width: 1em;
  margin-right: 4px;
  transition: transform 0.15s ease;
}
.assumptions-card-more[open] > summary::before {
  transform: rotate(90deg);
}
.assumptions-card-more > summary:hover {
  color: var(--color-text);
}
.assumptions-card-more > *:not(summary) {
  margin-top: var(--space-3);
}


/* Card header row: sits directly under the legend, holds the phase-dependent
   lock label (Purchase & financing, Income) or field-count badge (Operating
   expenses). Empty/hidden children collapse it to zero height, so it never
   adds visible space in Analyze where none of these labels render. */
.assumptions-card-head {
  display: flex;
  justify-content: flex-end;
  margin: calc(-1 * var(--space-2)) 0 var(--space-2);
  min-height: 0;
}
.assumptions-card-lock-label,
.assumptions-card-count-badge {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.assumptions-card-lock-label[hidden],
.assumptions-card-count-badge[hidden] {
  display: none;
}

/* Locked-state treatment (Own+): the Income card gets a visually muted/tinted
   card, matching the wireframe's dimmed-card look for "read-only from here
   on". CSS-only -- this class never disables an input; the real lock is
   setUnderwritingFieldsDisabled()/OWN_PHASE_UNLOCKED_FIELD_IDS in app.js,
   applied and removed independently of this class.
   QA-FAILED finding (2026-09-03, #5188): the original opacity:0.92 tweak was
   too subtle to read as "locked" at a glance -- it only dimmed the legend and
   left every disabled input looking identical to an editable one apart from
   cursor. This card now gets a distinct tinted background, a left accent bar,
   and every disabled field inside it gets a visibly muted fill so "you can't
   edit this here" reads without having to click into a field to find out. */
.assumptions-card-locked {
  background: var(--color-surface-offset);
  border-color: var(--color-border);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-md);
  padding-left: calc(var(--space-4) - 3px);
}
.assumptions-card-locked > legend {
  color: var(--color-text-muted);
}
.assumptions-card-locked input:disabled,
.assumptions-card-locked select:disabled,
.assumptions-card-locked textarea:disabled {
  background: var(--color-bg-alt);
  color: var(--color-text-muted);
  border-color: var(--color-border);
}

/* Phase-state badge in the Assumptions tab header ("Analyze -- editable" /
   "Own -- N fields moved"). Pill shape reuses the same visual language as
   .status-pill / .field-assumption-badge elsewhere on this page rather than
   inventing a new badge language. */
.assumptions-phase-badge-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-3);
}
.assumptions-phase-badge {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  background: var(--color-surface);
  white-space: nowrap;
}
.assumptions-phase-badge:empty {
  display: none;
}

/* Operating expenses' moved-fields note (#5188, extends #5145's redirect
   note to also cover management fee per #5052). A quieter tint than
   .phase-lock-hint's amber warning -- this is informational ("here is where
   it went"), not a warning about a locked field. */
.assumptions-moved-fields-note {
  padding: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-text-muted);
}
.assumptions-moved-fields-note a {
  color: var(--color-brand-navy);
  font-weight: 600;
}

/* Green confirmation note under Growth & projection, Own+ only (#5188):
   "same underlying value, nothing duplicated". Reuses the same success
   tokens as the rest of the app (.rr-occ-committed / the "Closed" pipeline
   chip) rather than inventing a new green. */
.assumptions-confirmation-note {
  padding: var(--space-2) var(--space-3);
  margin: var(--space-3) 0 0;
  border: 1px solid var(--color-success);
  border-radius: var(--radius-md);
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  font-size: var(--text-sm);
}
.assumptions-confirmation-note[hidden] {
  display: none;
}

.field {
  margin-bottom: var(--space-4);
}
.field:last-child {
  margin-bottom: 0;
}
/* #1310, quieted by #5361: a fact (purchase price, taxes, unit count) and a
   modeling assumption (vacancy %, rent growth %, interest rate) live in the
   same fieldsets with no visual difference, so editing one can look exactly
   like editing the other. #1310 originally solved this with a left accent
   bar plus a solid tinted background box on the field itself; #5188's
   wireframe (agreed with Tony) never showed that box treatment at all --
   only a small, quiet, outline-only badge next to the label, no field
   background change. #5361 brings the shipped styling in line with that
   wireframe: the badge alone carries the distinction now, sitewide,
   everywhere ASSUMPTION_FIELD_IDS applies (Assumptions tab, Purchase Info,
   Loan tab, and per-unit table cells via .unit-cell-assumption below). */
.field-assumption {
  /* Intentionally no padding/margin/border/background here anymore --
     the wrapper still exists as a hook for .field-assumption-badge[hidden]
     handling (see #2486/#3891 below) and for any future JS querying it,
     but it no longer changes the field's own box appearance. */
}
.field-assumption-badge {
  display: inline-block;
  font-size: 10px;
  line-height: 1;
  padding: 1px 5px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  /* --color-text-muted, not --color-text-secondary: this stylesheet has no
     token by that name, so reading it silently fell back to the literal
     `color` default and could never follow the theme (caught by
     contrast.unit.js's undefined-custom-property guard). */
  color: var(--color-text-muted);
  white-space: nowrap;
  vertical-align: middle;
  font-weight: 500;
  letter-spacing: 0.02em;
}
/* #2486: explicit [hidden] override -- same reason as .data-feed-sync-card
   above. renderDealTermsAssumptionTreatment() (renamed from
   renderDealTermsAssumptionBadges by #3891) hides the 7 deal-terms badges via
   the `hidden` attribute once Own/Sell/Sold locks them; without this rule the
   class's own `display: inline-block` would beat the browser default and the
   badge would stay visible. #3891: that same function now also REMOVES the
   .field-assumption class above from the wrapping .field when locked, so the
   navy left-bar and tint stop implying "modeling input" on numbers that are
   by then a signed note's actual terms. Both are restored by Re-underwrite. */
.field-assumption-badge[hidden] {
  display: none;
}
/* A field-row can hold one fact and one assumption side by side (e.g.
   Vacancy rate next to Other monthly income) -- the tint must stay
   scoped to the individual .field, never bleed across the row, or the
   fact field beside it would look tagged too. */
.field-row .field-assumption {
  align-self: start;
}
/* #5359: a field whose value is always computed (never typed in) --
   currently just the two Rent Averages Per Unit fields. Distinct from
   .field-assumption (a fact vs. deliberate-modeling-input distinction,
   still editable) and from the bare browser :read-only pseudo-class (used
   for the occasional rent-roll-locked field, too subtle on its own for a
   field that is ALWAYS locked and must read as "don't bother clicking
   here" at a glance). Dashed border + muted surface + a small lock glyph
   via ::after on the label, no focus ring since the input can never take
   a meaningful edit. */
.field-readonly-computed .field-label-name::after {
  content: '\1F512';
  margin-left: var(--space-1, 4px);
  font-size: var(--text-xs);
  opacity: 0.6;
  vertical-align: middle;
}
.field-readonly-computed input[readonly] {
  background: var(--color-surface-offset);
  border-style: dashed;
  color: var(--color-text-muted);
  cursor: default;
}
.field-readonly-computed input[readonly]:focus {
  outline: none;
  box-shadow: none;
}
.building-subcard-subtitle {
  margin: calc(-1 * var(--space-2, 8px)) 0 var(--space-4, 16px);
}
/* #1452, quieted by #5361: per-unit table cells that are assumptions
   (marketRent) get the same badge as the building-level assumption fields.
   Previously also carried the left-bar/tint box treatment removed above --
   dropped for the same reason and at the same time, so the two never drift
   apart again. */
.unit-cell-assumption {
}
.field-row[hidden] {
  display: none;
}
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
/* #5765: the GL report screens (Trial Balance / P&L / Balance Sheet) each put
   a trailing status-pill + Export button directly inside .field-row, sized
   like every other grid cell. At typical widths the row (several filter
   fields plus this trailing group) doesn't fit the fixed 2-column grid
   cleanly, so the group could land split across cells or crowd a filter
   field. .field-row-toolbar opts a trailing group out of that grid entirely:
   grid-column: 1 / -1 forces it to span the full row width on its own line,
   and its own flex layout keeps its contents together at the right edge
   regardless of how many input fields precede it. Scoped to a new class (not
   a change to .field-row itself, which is reused by ~90 other unrelated
   forms sitewide) so nothing outside the three GL report screens is
   affected.

   QA-FAIL (2026-09-08): v1 of this fix moved only the status pill + Export
   into .field-row-toolbar and left Refresh behind as an ordinary .field
   sibling -- so Refresh still carried .field-row .field's default grid-cell
   sizing and landed wherever it fell among the input fields, sometimes
   stranded away from the pill/Export it visually belongs with. Refresh now
   lives inside .field-row-toolbar too (as the first child, sized via
   .toolbar-btn-inline's flex:0 0 auto instead of stretching), so all three
   controls travel as one unit at every width.

   NOTE: this rule was originally written into admin.css (Admin Portal) by
   mistake -- these GL screens are index.html/Owner Portal markup, which
   never links admin.css, so that version was a dead rule the whole time
   (caught by tests/ownerPortalButtonClasses766.unit.js's *btn* token guard
   flagging the also-misplaced .btn-inline class). Owner Portal's .field-row
   is a grid, not flex like Admin's, hence grid-column: 1 / -1 here rather
   than admin.css's flex-basis: 100%. */
.field-row-toolbar {
  display: flex;
  grid-column: 1 / -1;
  gap: var(--space-3);
  align-items: center;
  justify-content: flex-end;
  margin-top: var(--space-2);
}
/* Sized to its label instead of stretching like a .field-row grid cell would;
   named distinctly from Admin Portal's unrelated .btn-inline (a full-width ->
   auto override for auth-card buttons) since this is a different override
   for a different reason on a different portal. */
.toolbar-btn-inline {
  flex: 0 0 auto;
}
/* #549: on the Assumptions/Property Info tab, a label that wraps to two
   lines (e.g. "Other monthly income") pushes that field's input DOWN inside
   its own cell, while the shorter one-line label next to it (e.g. "Vacancy
   rate") does not -- so the two inputs land on different visual lines even
   though .field-row's grid cells are the same overall height. Stretch only
   equalizes the CELL height; it does nothing to align what sits at which
   vertical position INSIDE each cell.
   Fix: give every .field inside one of these rows a six-row subgrid. The
   first two rows align the labels and inputs; the remaining four give a
   field's note, validation error, CapEx forecast flag, and forecast detail
   their own inherited tracks. Keeping those rows on the shared parent makes
   the Maintenance and CapEx columns advance together instead of letting the
   CapEx extras overlap the input or create a lopsided paired row. Scoped to
   #mainPanelPropertyInfo because that container's
   .field-rows are uniformly plain label-then-input .field pairs (verified,
   zero .field-checkbox/.group-label inside it); .field-row is reused by ~90
   other unrelated forms sitewide with different internal shapes (a
   .field-checkbox that bottom-aligns against the row's stretched height,
   for one), so this does not touch the base rule other pages depend on. */
#mainPanelPropertyInfo .field-row {
  grid-template-rows: repeat(6, auto);
  align-items: start;
}
#mainPanelPropertyInfo .field-row > .field {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: 1 / 7;
  margin-bottom: 0;
}
#mainPanelPropertyInfo .field-row > .field > label {
  grid-row: 1;
}
/* #4367: the base .field-row (line ~2791) is a hard 2-column grid with no
   stacking breakpoint of its own -- every OTHER 2-up .field-row on the site
   either inherits one from a scoped override like this (see
   .lifecycle-buy-section above) or happens to fit at phone width anyway.
   #mainPanelPropertyInfo's pairs (Vacancy rate/Other monthly income,
   Property taxes/Insurance, Maintenance/CapEx Reserve, Management/Other,
   Property appreciation/Rent growth) do not: at 390px the right-hand field
   in every pair rendered fully off-screen with no scrollbar, blocking entry
   of three of those inputs entirely. Same breakpoint and same
   single-column collapse as .lifecycle-buy-section's mobile rule; the
   subgrid rule above needs no change since a 1-column row still gives each
   .field its own full-width row 1/7 span. */
@media (max-width: 640px) {
  #mainPanelPropertyInfo .field-row {
    grid-template-columns: 1fr;
  }
  /* The #549 subgrid fix above places every .field at an explicit
     `grid-row: 1 / 7` so labels/inputs/notes align across the pair. With
     only ONE explicit column track, two items both claiming the full
     row 1-7 span can't both fit in column 1 -- grid auto-placement then
     creates an IMPLICIT second column for the second .field instead of
     stacking it below the first, silently reproducing the exact
     2-column overflow this breakpoint exists to remove. Overriding the
     row span to a plain single auto row per field (stacked, not
     subgridded) sidesteps that: at this width the two fields are already
     full-width and stacked, so cross-field label/input alignment via
     subgrid buys nothing anyway. */
  #mainPanelPropertyInfo .field-row > .field {
    grid-template-rows: none;
    grid-row: auto;
  }
  #mainPanelPropertyInfo .field-row > .field > label {
    grid-row: auto;
  }
}

/* ================= Repair form grid ==================
   The Repairs & Improvements 'New / Edit repair item' form used to be a
   stack of .field-row 2-col grids. That produced two layout bugs:
   1. Uneven row heights (Category's BAR-test helper text made the right
      column taller than the left) pushed the NEXT row's cells onto
      different visual lines -- Priority Window sat under Location while
      Status sat further down under Category, so the form appeared to
      'jump' between rows.
   2. On mobile there was no stacking rule, so number inputs got
      squeezed into two skinny columns and dates into three.
   Fix: one CSS grid that owns the entire form, with items aligned to
   the row start (so a tall right-column note never pushes the left
   column) and explicit mobile stacking. Fields opt out via .rf-col-full
   or .rf-cost-pair / .rf-date-triple when they need a nested layout. */
/* Defensive: the Repairs & Improvements 'form' sub-panel MUST honor the
   [hidden] attribute since setRepairsPanel() toggles it via JS. Without
   an explicit rule the browser UA sheet handles it, but if any other
   rule ever wins on specificity we'd bleed the form into the list view.
   Same for the accruals sub-panel. Both use !important to shut this
   whole class of bugs down permanently. */
#repairsFormPanel[hidden],
#repairsAccrualsPanel[hidden],
#repairsListPanel[hidden] {
  display: none !important;
}

.repair-form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--space-3);
  row-gap: var(--space-4);
  align-items: start;
}
.repair-form-grid > .field {
  /* Fields inside the grid own their own top-margin via grid row-gap, so
     reset the legacy bottom-margin the .field selector applies. */
  margin-bottom: 0;
}
.repair-form-grid .rf-col-full {
  grid-column: 1 / -1;
}
/* #218: a grid item is display:grid-item, which overrides the UA's
   [hidden]{display:none}, so the component-type field would stay visible (as
   an empty row) when JS hides it. Explicit here rather than relying on the
   global rule. */
.repair-form-grid > .field[hidden] {
  display: none;
}
.rf-cost-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
.rf-date-triple {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}
.rf-date-quad {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
}
.rf-cost-pair > .field,
.rf-date-triple > .field,
.rf-date-quad > .field {
  margin-bottom: 0;
}
@media (max-width: 640px) {
  .repair-form-grid {
    grid-template-columns: 1fr;
  }
  .rf-date-triple {
    grid-template-columns: 1fr;
  }
  .rf-date-quad {
    grid-template-columns: 1fr;
  }
  /* Keep the estimated-cost low/high pair side-by-side even on mobile --
     they're conceptually one range, and two short number inputs fit
     comfortably at typical phone widths. */
}

.field label,
.field .group-label {
  display: flex;
  align-items: baseline;
  /* ID 868: pack the label's parts together on the left rather than spreading
     them across the cell. A label can hold up to three things -- its text, a
     `.hint-required` asterisk and a `.hint` -- and `space-between` pushed them
     to opposite edges of whatever width the field happened to be. Measured on
     prod before this change: median 228px between a label and its own hint,
     7 of 18 visible fields over 400px, worst 888px. At that distance the hint
     stops reading as belonging to the label and the required asterisk lands
     stranded in open space mid-row.

     This is deliberately the base rule and not a per-field override. Nothing
     in the Owner Portal wants right-aligned label content -- no label contains
     a button or a link, and BOTH existing label patterns (`.field-checkbox`
     from ID 742 and `.user-admin-toggle`) already hand-undo `space-between`
     because it was wrong for them too. It caused two defects in two days
     (ID 742, ID 858), so the default is the thing to fix.

     Blast radius, measured by pixel-diffing before/after renders of each
     screen: ~50 hinted labels exist in the Owner Portal and 39 of them are on
     the analysis form, which is where this visibly changes. The e-sign card
     changes in one 25px band (the Jurisdiction row). Communications and Manage
     users are pixel-identical -- a label with a single child lays out the same
     either way. style.css is loaded only by index.html and accessibility.html,
     so the Tenant and Admin portals are untouched.

     Tradeoff accepted by Tony on 2026-07-31: the analysis form's two-column
     rows lose their flush right edge, where hints like "% of rent" used to
     line up under each other. */
  justify-content: flex-start;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-2);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.field .group-label .hint,
.field label .hint {
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 400;
}
/* ID 742: a .field whose label WRAPS a checkbox instead of captioning an input
   below it. The class was in the markup with no rule behind it, so the wrapping
   label fell through to `.field label` above -- which is built for a standalone
   caption and set `justify-content: space-between`. That drove the box and its
   own text to opposite edges of the grid cell (measured: 381px apart at 1280px),
   rendered the text as an uppercase muted caption, and left it top-aligned
   against the NEIGHBOUR's caption rather than the neighbour's input.
   `.user-admin-toggle` already hand-undoes the same four declarations; this is
   that fix as a reusable class.
   MUST stay after `.field label`: both selectors score (0,1,1), so source order
   is the only thing making this win. tests/leaseTemplateCheckbox.unit.js asserts
   the order and scripts/dev/qa-742-field-checkbox-dom.js asserts the painted
   result, because equal-specificity-later-wins is exactly how ID 589 broke. */
.field-checkbox {
  display: flex;
  align-items: flex-end;
  /* The cell stretches to the tallest item in the .field-row, and that height
     INCLUDES the sibling .field's own `margin-bottom: var(--space-4)` -- which
     this cell does not have, being :last-child. Without matching padding here,
     bottom-aligning lands the checkbox exactly one --space-4 BELOW the input it
     is supposed to line up with. Measured, not assumed. */
  padding-bottom: var(--space-4);
}
.field-checkbox label {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  /* Same vertical box as the sibling text input (padding + a transparent
     border standing in for its 1px one), so bottom-aligning the two cells
     lands the caption text on the input's text rather than near it. */
  padding: var(--space-3) 0;
  border: 1px solid transparent;
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--text-sm);
  color: var(--color-text);
  font-weight: 400;
  margin-bottom: 0;
  cursor: pointer;
}
.field-checkbox input[type='checkbox'] {
  width: 16px;
  height: 16px;
  accent-color: var(--color-primary);
  flex-shrink: 0;
  cursor: pointer;
}
/* .field-note is the app's shared helper/body copy under form fields and in
   settings cards, so it is normal-sized text and must hold WCAG 2.1 AA 1.4.3
   contrast (>= 4.5:1). It previously used --color-text-faint, which measures
   2.61:1 on --color-surface in light mode and 3.17:1 in dark mode.
   --color-text-faint is kept for borders, icons and other non-text accents
   (1.4.11, >= 3:1); text roles use --color-text-muted, which clears 4.5:1 on
   every surface and highlight background in both themes.
   Guarded by tests/contrast.unit.js. */
.field-note {
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--color-text-muted);
  margin-top: var(--space-3);
}
.field-note.is-saving {
  color: var(--color-text-muted);
}
.field-note.is-error {
  color: var(--color-error);
}

/* #6637 -- Collections Aging receivable-type chips (All receivables / Rent &
   charges / Deposits) and the deposit-split rows in the #6630 batch payments
   drawer. Follows the existing .asset-register-chip[aria-pressed] pattern
   (single-select pill group, selected state read from aria-pressed rather
   than a second class) instead of inventing a third chip treatment. */
.type-chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.type-chip {
  min-height: 30px;
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
}
.type-chip[aria-pressed="true"] {
  border-color: var(--color-text);
  color: var(--color-text);
  font-weight: 600;
  background: var(--color-surface-offset);
}
.type-chip:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

/* A Collections Aging row's "incl. $X deposit"/"all deposit" secondary line
   under Total owed, and the KPI strip's matching "incl. $X deposits" line --
   both reuse .field-note's already-AA-audited color rather than a new one. */
.aging-deposit-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: 2px;
}

/* The #6630 drawer's deposit-split sub-rows (Security deposit / Rent &
   charges) under a row whose lease owes a deposit. Indented and dimmed like
   the wireframe's .split rows, using existing tokens rather than the
   wireframe's own literal colors. */
.payment-batch-split-row td {
  background: var(--color-surface-offset);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}
.payment-batch-split-label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding-left: var(--space-4);
}
.payment-batch-split-total.field-note-warn {
  font-weight: 600;
}
.payment-batch-split-moved-note {
  font-size: var(--text-xs);
  color: var(--color-warning-text);
  margin-top: 2px;
}

/* #4315: Maintenance Hours settings UI. */
.radio-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-right: var(--space-4);
  font-size: var(--text-sm);
}
/* #6467 -- explicit for the same reason .data-feed-sync-card's is: this class
   sets `display`, and author styles beat the browser's own
   `[hidden] { display: none }` at any specificity. Needed once
   adSendInvoiceWrap started getting a real `hidden` toggle. */
.radio-inline[hidden] {
  display: none;
}
.checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
}
.checkbox-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.business-hours-window {
  width: 100%;
  max-width: 30rem;
  border-collapse: collapse;
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}
.business-hours-window th,
.business-hours-window td {
  text-align: left;
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.business-hours-window th {
  font-weight: 600;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.business-hours-window input[type="time"] {
  width: 6.5rem;
}
.custom-holiday-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.custom-holiday-add input[type="date"] {
  width: 9rem;
}
.custom-holiday-add input[type="text"] {
  flex: 1 1 16rem;
  min-width: 12rem;
}
.custom-holiday-list {
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
}
.custom-holiday-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.link-button {
  background: none;
  border: none;
  padding: 0 var(--space-1);
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: var(--text-lg);
  line-height: 1;
}
.link-button:hover {
  color: var(--color-error);
}
.maintenance-preview {
  margin-top: var(--space-4);
  padding: var(--space-4);
  background: var(--color-surface-offset);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.maintenance-preview p {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.maintenance-preview p:last-child {
  margin-bottom: 0;
}
.user-admin-toggle {
  display: flex;
  align-items: center;
  margin-bottom: var(--space-4);
  padding-top: 26px;
}
.user-admin-toggle label {
  display: flex;
  align-items: center;
  /* ID 868, drive-by: this rule never declared justify-content. It got away
     with it under the old `space-between` base only because its two children
     happen to fill the row. Stating it explicitly, like `.field-checkbox label`
     already does, means neither checkbox pattern depends on what `.field label`
     happens to be set to -- which is exactly the coupling that caused ID 742. */
  justify-content: flex-start;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  cursor: pointer;
  margin-bottom: 0;
}
.user-admin-toggle input[type='checkbox'] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
.hint-required {
  color: var(--color-error) !important;
  font-weight: 700 !important;
  font-size: var(--text-sm) !important;
  line-height: 1 !important;
}
.field-error {
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--color-error);
  margin-top: var(--space-2);
}
.field input:invalid.field-input-error,
.field input.field-input-error,
.field select.field-input-error {
  border-color: var(--color-error);
}

.address-validated-badge {
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--text-xs);
  font-weight: 600 !important;
  color: var(--color-success);
}

.autofill-note {
  color: var(--color-primary);
  font-weight: 600;
  margin-top: 0;
  margin-bottom: var(--space-3);
}

.field-with-suggestions {
  position: relative;
}
.address-suggestions {
  position: absolute;
  z-index: 20;
  top: 100%;
  left: 0;
  right: 0;
  margin: var(--space-1) 0 0;
  padding: var(--space-1);
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  max-height: 15rem;
  overflow-y: auto;
}
.address-suggestions li {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
  line-height: 1.4;
}
.address-suggestions li .suggestion-secondary {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.address-suggestions li:hover,
.address-suggestions li.is-active {
  background: var(--color-primary-highlight);
}
.address-suggestions li.is-loading,
.address-suggestions li.is-empty {
  cursor: default;
  color: var(--color-text-muted);
  font-style: italic;
}
.address-suggestions li.is-loading:hover,
.address-suggestions li.is-empty:hover {
  background: transparent;
}

.input-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.input-wrap .prefix,
.input-wrap .suffix {
  position: absolute;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  pointer-events: none;
}
.input-wrap .prefix {
  left: var(--space-3);
}
.input-wrap .suffix {
  right: var(--space-3);
}
.field input[type='number'],
.field input[type='text'],
.field input[type='url'],
.field input[type='email'],
.field input[type='password'],
.field input[type='date'],
.field select,
.unit-cell input,
.unit-cell select {
  width: 100%;
  padding: var(--space-3) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums lining-nums;
}
.field select {
  font-family: inherit;
  cursor: pointer;
  /* #113: a <select> was 5px SHORTER than the text input it sits beside, even
     though the shared rule above already gives both the same padding, border,
     radius and font-size -- the block plainly intends two identical-looking
     controls, and line-height was the one property it missed. The input inherits
     body's `line-height: 1.6` (24px at --text-sm), while Chromium forces
     `line-height: normal` on a select and IGNORES an author line-height outright
     (measured: setting 1.6 here changes nothing). min-height is therefore the
     only lever that works.
     Derived rather than hardcoded at 50px so it tracks the font-size and
     --space-3 it is built from: 1.6em line box + the rule's vertical padding on
     both sides + its 1px border on both sides.
     Surfaced by #113 replacing the free-text Jurisdiction input on the
     lease-template form with a select, which left the neighbouring checkbox
     2.5px off-centre and failed scripts/dev/qa-742-field-checkbox-dom.js.
     Guarded by tests/fieldSelectHeight.unit.js. */
  min-height: calc(1.6em + var(--space-3) * 2 + 2px);
}
.input-wrap.has-prefix input {
  padding-left: var(--space-8);
}
.input-wrap.has-suffix input {
  padding-right: var(--space-8);
}
.input-wrap.has-link-action input {
  padding-right: var(--space-8);
}
.input-link-action {
  position: absolute;
  right: var(--space-2);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  transition: color 0.15s ease, background-color 0.15s ease;
}
.input-link-action svg {
  width: 1rem;
  height: 1rem;
}
.input-link-action:hover,
.input-link-action:focus-visible {
  color: var(--color-primary);
  background: var(--color-surface-offset);
}
.input-link-action:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.field input:focus-visible,
.field select:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  border-color: var(--color-primary);
}

/* ---------------------------------------------------------------------------
   #765 -- multi-source Listing Links (Option A: one primary + an "also listed
   on" list). The primary input above (#listingUrl) is completely unstyled by
   this block -- it keeps whatever .input-wrap.has-link-action already gave
   it. Everything here is the ADDITIONAL-links list and its controls.
   --------------------------------------------------------------------------- */
.listing-links-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.listing-link-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.listing-link-row .src-badge {
  flex: 0 0 auto;
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  cursor: pointer;
  font-family: inherit;
}
.listing-link-row .src-badge:hover,
.listing-link-row .src-badge:focus-visible {
  background: var(--color-surface-offset);
  outline: none;
}
.listing-link-row .src-badge:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
/* Frozen (Sold phase) rows: badge is no longer a promote-to-primary control. */
.listing-link-row.is-frozen .src-badge {
  cursor: default;
}
.listing-link-row.is-frozen .src-badge:hover,
.listing-link-row.is-frozen .src-badge:focus-visible {
  background: var(--color-surface-2);
}
.listing-link-row .url-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.listing-link-row .url-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.listing-link-row .icon-btn,
.asset-register-add.asset-register-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.listing-link-row .icon-btn svg,
.asset-register-add.asset-register-icon-btn svg {
  width: 1rem;
  height: 1rem;
}
.listing-link-row .icon-btn:hover,
.listing-link-row .icon-btn:focus-visible,
.asset-register-add.asset-register-icon-btn:hover,
.asset-register-add.asset-register-icon-btn:focus-visible {
  color: var(--color-primary);
  background: var(--color-surface-offset);
}
.listing-link-row .icon-btn.is-danger:hover,
.listing-link-row .icon-btn.is-danger:focus-visible,
.asset-register-add.asset-register-icon-btn.is-danger:hover,
.asset-register-add.asset-register-icon-btn.is-danger:focus-visible {
  color: var(--color-error-text);
  background: var(--color-error-highlight);
}
.listing-link-row .icon-btn:focus-visible,
.asset-register-add.asset-register-icon-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
/* Beats .asset-register-add's own color:var(--color-primary) below via a
   more specific (double-class) selector -- source order alone would let
   that older, lower-in-the-cascade rule win over this one otherwise. */
/* #4665: the asset register's Actions column packs up to 5 of these
   buttons on one row (e.g. a lock: Edit/Remove/Codes/Replace) -- tighter
   gap than .listing-link-row's default flex gap so they read as one
   compact action group instead of drifting apart. */
.asset-register-actions-cell {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
}
/* #6374: ghost rows have two independent actions in a plain table cell.
   Keep the same compact flex treatment as recorded-row actions, while using a
   distinct class so the markup documents why this cell is also a flex layout. */
.asset-register-ghost-actions-cell {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
}

.chip-btn {
  margin-top: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: 999px;
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.chip-btn:hover,
.chip-btn:focus-visible {
  color: var(--color-primary);
  border-color: var(--color-primary);
  background: var(--color-surface-offset);
}
.chip-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
/* Sold phase: no add control at all, matching every other frozen field on
   this fieldset (see fieldsetLayout.js's "everything is a fact or a frozen
   assumption" comment). */
.chip-btn[hidden] {
  display: none;
}

/* #944: one-click suggestion chips in the empty common-area repair picker.
   Reuses .chip-btn's pill styling but lays multiple out in a wrapping row
   instead of the single stacked chip .chip-btn was built for. */
.repair-area-suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.repair-area-suggestions[hidden] {
  display: none;
}
.repair-area-suggestions .chip-btn {
  margin-top: 0;
}
.repair-area-suggestions .chip-btn:disabled {
  cursor: default;
  opacity: 0.6;
}

.listing-link-picker {
  position: relative;
  margin-top: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  max-width: 22rem;
}
.listing-link-picker-head {
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.listing-link-picker-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2);
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--color-text);
}
.listing-link-picker-item:hover,
.listing-link-picker-item:focus-visible {
  background: var(--color-surface-offset);
  outline: none;
}
.listing-link-picker-item .fwd {
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.listing-link-picker-custom-label {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-divider);
}
.listing-link-picker-custom-label input {
  width: 100%;
}
.listing-link-picker-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.field input::-webkit-outer-spin-button,
.field input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.field input[type='number'] {
  -moz-appearance: textfield;
}

.lock-remember {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  text-align: left;
  margin-bottom: var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  cursor: pointer;
}
.lock-remember input[type='checkbox'] {
  margin-top: 0.15rem;
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
  flex-shrink: 0;
}
.footer-meta {
  margin-top: var(--space-3);
}
.link-btn {
  background: none;
  border: none;
  padding: 0;
  font-size: var(--text-xs);
  /* WCAG 2.1 AA (Decision 14 accessibility baseline): --color-text-faint only
     hits ~2.6:1 on white, below the 4.5:1 minimum for actionable link text. */
  color: var(--color-text-muted);
  text-decoration: underline;
  cursor: pointer;
}
.link-btn:hover,
.link-btn:focus-visible {
  color: var(--color-text);
}
.link-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* Runtime templates outside index.html still emit the legacy inline class.
   Keep its established visual treatment until their own markup sweep. */
.link-btn-inline {
  background: none;
  border: none;
  padding: 0;
  margin-left: var(--space-1);
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}

.segmented {
  display: flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface-2);
}
.segmented button {
  flex: 1;
  padding: var(--space-3) var(--space-3);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
  border-right: 1px solid var(--color-border);
}
.segmented button:last-child {
  border-right: none;
}
.segmented button[aria-pressed='true'] {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
.segmented button:not([aria-pressed='true']):hover {
  color: var(--color-text);
  background: var(--color-surface-offset);
}

/* #2491: reuse the standard segmented treatment for the compact down-payment
   unit chooser instead of introducing a separate control style. */
.dp-mode-toggle {
  width: fit-content;
  margin-bottom: var(--space-2);
}
.dp-mode-toggle button {
  flex: 0 0 auto;
  min-width: 2.5rem;
  padding: var(--space-1) var(--space-2);
}

/* #927: the Visits All / Condition / Prospect family toggle -- a .segmented
   control (same base look as the existing purchase-method segmented
   control) plus a small muted count badge per option so the split is
   informative even before anything is filtered. */
.showings-family-toggle {
  margin-bottom: var(--space-3);
}
.showings-family-count {
  font-weight: 400;
  opacity: 0.7;
  font-size: var(--text-xs);
}

/* #5065: the All / Site Inspections / Prospect Showings tab strip replacing
   #927's .showings-family-toggle above -- reuses the shared .segmented base
   layout (border/flex/divider rules already cover it) but its buttons use
   role="tab"/aria-selected, not aria-pressed, so they need their own active-
   state rule rather than picking up .segmented button[aria-pressed='true']
   above, which must stay untouched for the other aria-pressed segmented
   controls elsewhere in the app (scenario toggle, down-payment unit chooser,
   etc). */
.visits-tab-strip {
  margin-bottom: var(--space-3);
}
.visits-tab-strip button[aria-selected='true'] {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
.visits-tab-strip button:not([aria-selected='true']):hover {
  color: var(--color-text);
  background: var(--color-surface-offset);
}

/* #929: the six-number prospect funnel strip, shown only when the family
   toggle above it is on Prospect (app.js toggles [hidden]; see
   renderProspectFunnelStrip). A row of equal-width cells rather than a
   table -- six numbers, no headers needed beyond the label under each. */
.showings-prospect-strip {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
}
/* #1168: an author `display` beats the browser's built-in
   `[hidden] { display: none }` at equal specificity -- same bug class
   already fixed once for .showings-type-filter[hidden] (ID 404). Without
   this override the strip's grey grid background rendered as a blank,
   unstyled-looking spacer bar between the family toggle and the
   Inspection schedules heading even while [hidden] was set and the
   family filter was on All or Condition. */
.showings-prospect-strip[hidden] {
  display: none;
}
.showings-prospect-strip-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
}
.showings-prospect-strip-value {
  font-size: var(--text-lg);
  font-weight: 600;
}
.showings-prospect-strip-label {
  font-size: var(--text-xs);
  opacity: 0.7;
}
@media (max-width: 640px) {
  .showings-prospect-strip {
    grid-template-columns: repeat(3, 1fr);
    row-gap: var(--space-3);
  }
}

.financing-fields {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--color-divider);
}

/* ===== Task List ID 135: condense the Metrics > Financing sub-tab =====
   The Financing fieldset is reparented from Property Info (narrow 2-col
   calculator layout) into the full-width Metrics sub-tab, which made the
   segmented purchase-method control + Down Payment + Balloon Date fields
   each stretch to ~1500px and turned the panel into a giant scroll-heavy
   surface. These scoped rules only apply inside #financingTermsSection so
   the original Property Info layout stays untouched.

   Layout:
     - Whole reparented content capped at 720px so fields stay legible.
     - Loan inputs (down payment / interest / term) collapse into a single
       3-column row on wider screens, stacking on narrow ones.
     - Balloon date sits on a shorter half-width row.
     - The <legend> "Financing" text is hidden (redundant with the panel's
       "Current loan terms" heading), but the <fieldset> itself remains
       for a11y grouping.

   Own-phase gating:
     - When #financingTermsSection has [data-purchase-mode-locked="true"],
       the segmented purchase-method toggle is hidden (baked in at close;
       switching Financed<->Cash post-purchase does not model reality --
       refis flow through Record refinance instead). A small pill above
       the loan inputs indicates the locked mode; JS sets its text. */
/* #5373 follow-up -- this cap dates from when financingTermsSection lived
   in a narrow ~840px main column (the pre-#5373 two-column model); #5373
   widened the card to the full loan-tab-grid width (1160px+ on desktop),
   leaving a large empty gutter to the right of the loan fields -- the same
   "empty gutter on wider screens" shape #3156 already fixed for Property
   Info / Purchase Info by removing their equivalent 46rem cap outright
   rather than raising it, since .field-row's 2-column grid has no width
   dependency and no mobile breakpoint of its own. Same fix here: removed,
   not raised. */
#financingTermsSection #financingReparentTarget .field-group > legend {
  /* Hide 'Financing' section label -- it duplicates the panel heading. */
  display: none;
}
#financingTermsSection .financing-fields {
  /* Tighter internal spacing than the calculator's default. */
  gap: var(--space-3);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
}
/* #3798: the old position-based 3-col-squeeze + half-width-balloon overrides
   that used to live here (targeting .financing-fields > .field / .field-row /
   .field-row-1 by ROW POSITION) were written when this fieldset had only 3
   rows. It has grown to 5 field-rows + 3 field-row-1s since (#3830/#3832/
   #3866/#3886/#3983), and the position selectors kept applying the same
   3-col squeeze and 320px half-width cap to every one of them, not just the
   original 3 -- cramming Lender/Product, Points, First-payment/Due-day and
   the Prepay rows into a layout none of them were designed for. Deleted;
   the app's default un-scoped .field-row (2-col grid) and .field-row-1
   (1-col) already give a clean, consistent layout, matching what the
   Refinance/Buy modals use for the identical fields. */
#financingTermsSection .loan-summary {
  /* Loan summary card kept, just aligned with the constrained field width. */
  max-width: 100%;
  margin-top: var(--space-3);
}
/* #3798 introduced a 3-column row for Lender / Loan Product / Loan ID here,
   mirroring #addOwnedModal .field-row-3 below. #5813 split that trio back
   apart -- Loan product moved to the top of the `loan` fieldset (reparented
   here in Own+ same as everywhere else) and Lender/Loan ID moved into
   #financingMoreDetails as a plain 2-column .field-row -- so no .field-row-3
   element ever reaches this container anymore. Rule removed rather than left
   as dead CSS. */
/* Purchase-method locked pill shown in Own/Sell/Sold phases in lieu of the
   editable Financed/All-cash toggle. Populated + toggled by
   updateFinancingPurchaseMethodLock() in app.js.
   #5514 QA-FAIL: this selector used to be bare ".segmented", which also
   matched the Down Payment $/% display toggle (".segmented dp-mode-toggle",
   index.html ~2808) since it lives inside the same #financingTermsSection
   container -- a selector collision, not a JS bug, that unconditionally
   hid the $/% toggle any time the property phase-locked. The down-payment
   toggle is meant to stay visible/clickable while locked (it only switches
   display unit; the underlying value stays readonly via
   setLoanInputsReadOnly()), so it is excluded here rather than folding the
   purchase-method collapse-to-pill rule into a broader change.
*/
#financingTermsSection[data-purchase-mode-locked="true"] .segmented:not(.dp-mode-toggle) {
  display: none;
}
#financingTermsSection .purchase-method-locked-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text);
  letter-spacing: 0.02em;
}
#financingTermsSection:not([data-purchase-mode-locked="true"]) .purchase-method-locked-pill {
  display: none;
}
/* Pill color variants (set via data-variant by app.js). Financed -> tinted
   accent (loan still active), Cash -> neutral, Paid-off -> success green. */
#financingTermsSection .purchase-method-locked-pill[data-variant="paid-off"] {
  background: color-mix(in oklab, var(--color-success) 15%, var(--color-surface-2));
  border-color: color-mix(in oklab, var(--color-success) 40%, var(--color-border));
  color: color-mix(in oklab, var(--color-success) 80%, var(--color-text));
}
#financingTermsSection .purchase-method-locked-pill[data-variant="cash"] {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}

/* Paid-off summary strip -- horizontal metrics grid at the top of the
   Loan history panel (Task List ID 135). Uses the same visual language as
   the Overview "At a Glance" strip. */
#loanPaidOffSummarySection .metric-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  margin: 0;
}
#loanPaidOffSummarySection .metric-grid .metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
}
#loanPaidOffSummarySection .metric-grid dt {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 500;
  margin: 0;
}
#loanPaidOffSummarySection .metric-grid dd {
  font-size: var(--text-base);
  color: var(--color-text);
  font-weight: 600;
  margin: 0;
}
/* Task List ID 136: "Total interest paid" gets a tag chip in the label
   (Simulated / Actual) and an optional sub-line showing the sim value
   when the primary is an entered actual. */
#loanPaidOffSummarySection .metric-grid .metric-tag {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
  background: color-mix(in oklab, var(--color-primary) 12%, var(--color-surface-2));
  color: color-mix(in oklab, var(--color-primary) 70%, var(--color-text));
  border: 1px solid color-mix(in oklab, var(--color-primary) 30%, var(--color-border));
}
#loanPaidOffSummarySection .metric-grid .metric-tag[data-variant="actual"] {
  background: color-mix(in oklab, var(--color-success) 15%, var(--color-surface-2));
  color: color-mix(in oklab, var(--color-success) 80%, var(--color-text));
  border-color: color-mix(in oklab, var(--color-success) 40%, var(--color-border));
}
#loanPaidOffSummarySection .metric-grid .metric-sub {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 400;
}
/* Task List ID 136: modal field-hint helpers used by the Actual
   total interest paid input. Inline .field-hint sits next to the label
   as a muted parenthetical; .field-hint-block is a full-width sub-line
   below the input explaining what to enter. */
.field-hint {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 400;
  margin-left: 4px;
}
.field-hint-block {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 4px 0 0;
  line-height: 1.4;
}
/* Task List ID 204: variant of .field-hint-block for a soft, conditional
   nudge (currently the "Date started" prompt shown when a repair item is
   marked Done with no start date). Warning-toned so it reads as "worth
   filling in" without looking like a validation error -- the field stays
   optional on purpose. `[hidden]` guard keeps display:block from
   overriding the attribute. */
.field-hint-attention {
  color: var(--color-warning);
}
.field-hint-block[hidden] {
  display: none;
}

/* Task List ID 138: prepay penalty surface. Styles the optional
   penalty inputs on the initial-loan financing block + refi modal, the
   Financing sub-tab warning banner, and the payoff-modal preview. */
.prepay-penalty-fieldset legend {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.prepay-penalty-fieldset .field-note {
  margin-bottom: var(--space-2);
}
.prepay-penalty-when-set > .field {
  flex: 1;
  min-width: 0;
}
.prepay-penalty-banner {
  border: 1px solid color-mix(in oklab, var(--color-warning, #b45309) 45%, var(--color-border));
  background: color-mix(in oklab, var(--color-warning, #b45309) 10%, var(--color-surface));
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}
.prepay-penalty-banner-body {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}
.prepay-penalty-banner-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: color-mix(in oklab, var(--color-warning, #b45309) 75%, transparent);
  color: #fff;
  font-weight: 700;
  font-size: var(--text-sm);
  line-height: 1;
}
.prepay-penalty-banner-title {
  font-weight: 600;
  color: color-mix(in oklab, var(--color-warning, #b45309) 80%, var(--color-text));
  margin-bottom: 2px;
}
.prepay-penalty-banner-detail {
  color: var(--color-text);
  font-size: var(--text-sm);
  line-height: 1.5;
}
.prepay-penalty-preview-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.prepay-penalty-preview-value {
  font-weight: 600;
  font-size: var(--text-lg);
  color: var(--color-text);
}
.prepay-penalty-preview-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.prepay-penalty-override-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
  margin: 0;
}
.prepay-penalty-override-toggle input[type="checkbox"] {
  margin: 0;
}
.prepay-penalty-override-row {
  margin-top: var(--space-2);
}

/* Loan-history table -- "Original purchase loan" + "Paid off" rows get
   subtle emphasis to distinguish them from ordinary refi rows. */
#refinanceHistoryTable .loan-history-original-row {
  background: color-mix(in oklab, var(--color-primary) 6%, transparent);
}
#refinanceHistoryTable .loan-history-payoff-row {
  background: color-mix(in oklab, var(--color-success) 8%, transparent);
  font-weight: 500;
}

/* Panel-heading actions container -- lets Record refinance + Mark paid off
   + Undo paid off buttons live together in the same panel header without
   forcing a wrap-around. */
.panel-heading-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-left: auto;
}
/* #4443: .panel-heading itself is `flex-wrap: nowrap` (see the rule below),
   so on a narrow viewport this actions block was squeezed into whatever
   space was left after the heading text/note -- not given its own row --
   and "Record refinance" (a fixed-width button, its label never wraps)
   overflowed past the right edge of the viewport with no visual sign
   anything was cut off. Drop .panel-heading to `flex-wrap: wrap` at the
   same 640px breakpoint used everywhere else in this file so the actions
   container gets a full-width row of its own instead of being crushed. */
@media (max-width: 640px) {
  .panel-heading-actions {
    margin-left: 0;
    width: 100%;
  }
}

.loan-summary {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
}
.loan-summary-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.loan-summary-row span:last-child {
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}
.loan-summary-total {
  padding-top: var(--space-2);
  border-top: 1px dashed var(--color-divider);
  font-weight: 600;
  color: var(--color-text);
}
.loan-summary-total span:last-child {
  color: var(--color-text);
}
.loan-summary-payment span:last-child {
  color: var(--color-primary);
}

/* #3798: ID 139's Original-lender + loan-product info strip retired --
   #lenderName/#loanProduct/#loanIdNumber are now populated with the same
   data directly (see updateFinancingSubtabState in app.js), so a separate
   muted echo strip is redundant. */

/* Task List ID 138 (markup landed under ID 406): prepayment-penalty status.
   Rendered as an inline callout inside the loan card and inside the payoff
   modal rather than a page-level banner -- ID 347 deliberately took banners
   off this page, and this is loan context rather than an alert. Amber border
   on a tinted surface, using --color-warning-text for the copy (the plain
   --color-warning token is 4.13:1 on white, fine for a border, not for text). */
.loan-prepay-callout {
  margin: var(--space-3) 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-warning);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--color-warning-highlight);
}
.loan-prepay-callout[hidden] {
  display: none;
}
.loan-prepay-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-warning-text);
}
.loan-prepay-detail {
  margin: 4px 0 0;
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--color-text);
  max-width: 68ch;
}
.loan-prepay-detail:empty {
  display: none;
}
.loan-prepay-callout .checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}
.loan-prepay-callout .checkbox-row input {
  width: auto;
  margin: 0;
  flex-shrink: 0;
}
.loan-prepay-callout .field {
  margin-top: var(--space-2);
  margin-bottom: 0;
}
/* Task List ID 406: placement inside the reparented Metrics > Financing
   panel. That panel (ID 135) turns #financingFields into a 3-column grid and
   pins every `> .field-row` to columns 2-4, so without these two rules the
   penalty detail row started in column 2 with an empty column 1, and the
   callout -- a bare div matching neither scoped selector -- got auto-placed
   into a single ~200px column and wrapped its copy to five short lines. */
#financingTermsSection .financing-fields > #prepayPenaltyDetailRow {
  grid-column: 1 / 4;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
}
#financingTermsSection .financing-fields > .loan-prepay-callout {
  grid-column: 1 / -1;
  max-width: 720px;
}

/* ================= Comparable rent lookup ================= */

.rent-comps {
  margin-top: var(--space-4);
  margin-bottom: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--color-divider);
}
legend + .rent-comps {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.rent-comps .btn-export {
  width: 100%;
  justify-content: center;
}
.rent-comps-panel {
  margin-top: var(--space-3);
}
.rent-comps-status {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0;
}
.rent-comps-status.rent-comps-error {
  color: var(--color-error);
}
.rent-comps-result {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
}
.rent-comps-estimate {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
}
.rent-comps-estimate-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  width: 100%;
}
.rent-comps-estimate-value {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-primary);
}
.rent-comps-estimate-range {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.rent-comps-result > .btn-ghost {
  justify-self: start;
  font-weight: 500;
}
.rent-comps-result > .btn-ghost:disabled {
  color: var(--color-success);
  text-decoration: none;
  cursor: default;
}
/* #549: the history/assessment/owner detail block sits behind this toggle so
   the tax lookup's headline estimate no longer pushes the rest of the Opex
   fieldset down the page by default. Styled as a left-aligned text link,
   not a button, since it discloses more of the SAME card rather than
   triggering an action -- the action is the "Use $X" button above it. */
.rent-comps-details-toggle {
  justify-self: start;
  background: none;
  border: none;
  padding: 0;
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--color-text-muted);
  text-decoration: underline;
  cursor: pointer;
}
.rent-comps-details-toggle:hover,
.rent-comps-details-toggle:focus-visible {
  color: var(--color-primary);
}
.rent-comps-details {
  display: grid;
  gap: var(--space-3);
}
.rent-comps-details[hidden] {
  display: none;
}
.rent-comps-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding-top: var(--space-2);
  border-top: 1px dashed var(--color-divider);
}
.rent-comps-item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 var(--space-3);
  font-size: var(--text-xs);
}
.rent-comps-item-link {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-column: 1 / -1;
  gap: 0 var(--space-3);
  padding: var(--space-1) var(--space-2);
  margin: calc(var(--space-1) * -1) calc(var(--space-2) * -1);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: inherit;
  transition: background-color 0.15s ease;
}
.rent-comps-item-link:hover,
.rent-comps-item-link:focus-visible {
  background-color: var(--color-surface-offset);
}
.rent-comps-item-link:hover .rent-comps-item-address,
.rent-comps-item-link:focus-visible .rent-comps-item-address {
  color: var(--color-primary);
  text-decoration: underline;
}
.rent-comps-item-address {
  color: var(--color-text);
  font-weight: 500;
}
.rent-comps-item-rent {
  color: var(--color-text);
  font-weight: 600;
  white-space: nowrap;
}
.rent-comps-item-meta {
  grid-column: 1 / -1;
  color: var(--color-text-muted);
}
.rent-comps-source {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.rent-comps-source a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--color-border);
}
.rent-comps-details-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.rent-comps-chip {
  font-size: var(--text-xs);
  color: var(--color-text);
  background: var(--color-surface-offset);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-full, 999px);
  padding: var(--space-1) var(--space-3);
  white-space: nowrap;
}
.rent-comps-subsection {
  display: grid;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-divider);
}
/* Advanced-mode per-unit-type rent estimates, one block per bed/bath combo. */
.rent-comps-fanout {
  display: grid;
  gap: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-divider);
}
.rent-comps-fanout-item {
  border-top: none;
  padding-top: 0;
}
.rent-comps-fanout-item .rent-comps-fanout-apply {
  justify-self: start;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
}
.unit-listing-apply-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.unit-listing-apply-row .unit-listing-apply-btn {
  flex: 0 0 auto;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
}
.unit-listing-specs {
  font-size: var(--text-sm);
}
.rent-comps-subsection-title {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.rent-comps-owner-name {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.rent-comps-owner-type {
  font-weight: 400;
  color: var(--color-text-muted);
}
.rent-comps-owner-address {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.rent-comps-delta {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--text-xs);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
}
.rent-comps-delta-good {
  color: var(--color-success-text);
  background: var(--color-success-highlight);
}
.rent-comps-delta-warn {
  color: var(--color-warning-text);
  background: var(--color-warning-highlight);
}
.rent-comps-delta-bad {
  color: var(--color-error-text);
  background: var(--color-error-highlight);
}
.rent-comps-delta-neutral {
  color: var(--color-text-muted);
  background: var(--color-surface-offset);
}

/* ================= Results column ================= */

.results-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  position: sticky;
  top: calc(var(--header-h) + var(--action-bar-h) + var(--space-6));
  max-height: calc(100vh - var(--header-h) - var(--action-bar-h) - var(--space-6) - var(--space-6));
  overflow-y: auto;
  padding-right: var(--space-2);
  padding-bottom: var(--space-2);
}
@media (max-width: 900px) {
  .results-col {
    position: static;
    max-height: none;
    overflow-y: visible;
    padding-right: 0;
  }
}

.results-tabs {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-surface);
  padding-bottom: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.results-tabs .tabs {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}
.results-tabs .tabs button {
  flex: 1 1 auto;
  white-space: nowrap;
}
.results-tabpanel[hidden] {
  display: none;
}
.results-tabpanel {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* Help toggle ("?") buttons that reveal field-note text on click */
.help-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  min-width: 18px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  margin-left: var(--space-2);
  cursor: pointer;
  vertical-align: middle;
}
/* Wraps a field label's text together with its help-toggle button so
   the two stay adjacent as a single flex item -- keeps the "?" right
   next to the field name instead of drifting to the middle when a
   trailing hint/badge span is also present in the label. */
.field-label-name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.field-label-name .help-toggle-btn {
  margin-left: 0;
}
/* Pairs a full-width lookup/action button with its help-toggle button
   on one row, so the "?" sits next to the button's own label text
   instead of floating below it. */
.rent-comps-action-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.rent-comps-action-row .btn-export {
  flex: 1 1 auto;
  width: auto;
}
.rent-comps-action-row .help-toggle-btn {
  margin-left: 0;
  flex-shrink: 0;
}
/* Panel heading, since these panels no longer carry their own lookup button
   to name them -- the pull is driven from the toolbar's Pull Remote Data. */
.rent-comps-heading {
  flex: 1 1 auto;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.rent-comps-pulled-at {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0;
}
.rent-comps-empty {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0;
  font-style: italic;
}
.help-toggle-btn:hover,
.help-toggle-btn[aria-expanded='true'] {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}
.field-note {
  margin-top: var(--space-2);
}
/* Small checkmark badge appended to a field's <label> to flag that its
   value is actually sent to RentCast's comp lookups (rent + sale-price),
   as opposed to the majority of Building details fields which are stored
   for reference only. See the Building details help note for the legend. */
.comp-used-icon {
  display: inline-block;
  width: 13px;
  height: 13px;
  margin-left: 3px;
  vertical-align: -2px;
  color: var(--color-primary);
  flex-shrink: 0;
}
.field-note .comp-used-icon {
  vertical-align: -2.5px;
  margin-left: 0;
  margin-right: 2px;
}

.kpi-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}
@media (max-width: 900px) {
  .kpi-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 640px) {
  .kpi-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 901px) {
  #dashboardContent .kpi-grid,
  #landingKpiGrid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ID 531: CapEx reserve funding badge. The funding percentage used to render
   as bare text, so a shortfall showed up as an unremarkable "-53%" with no
   colour and no warning. The three bands below are set from
   capexRulFundingStatus() in app.js via [data-funding].

   Sized down from .kpi-value's display type because the badge carries a
   sentence ("Underfunded · -53% · $3,250 short"), not a single figure, and at
   KPI size that wraps out of the card. ID 589 took that one step further: all
   the chip states share --text-sm, because --text-base still wrapped the
   longest 'short' string in the four-column Analyze tile.

   All four states use the contrast-checked -text/-highlight token pairs from
   ID 265 rather than white-on-solid: --color-error is #b23a3a in light mode
   but #e07a7a in dark, so a white-on-error chip would silently fail AA for
   every dark-mode user. */
.kpi-value[data-funding] {
  display: inline-block;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.3;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid transparent;
}
.kpi-value[data-funding='none'] {
  /* Genuinely nothing to report -- stays visually quiet, and stays 'N/A'. */
  padding: 0;
  border: 0;
  font-size: inherit;
}
.kpi-value[data-funding='funded'] {
  color: var(--color-success-text);
  background: var(--color-success-highlight);
  border-color: var(--color-success);
}
.kpi-value[data-funding='partial'] {
  color: var(--color-warning-text);
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
}
.kpi-value[data-funding='short'] {
  color: var(--color-error-text);
  background: var(--color-error-highlight);
  border-color: var(--color-error);
}
/* The overdrawn chip is deliberately NOT a red variant of the funding badge
   above -- it is a different state answering a different question (the reserve
   is spent past zero with no open work left to measure it against, so there is
   no percentage at all). Distinguished by silhouette rather than by hue: square
   corners, a heavier border and uppercase tracking, so it does not read as
   "just a worse version of short" at a glance. */
.kpi-value[data-funding='overdrawn'] {
  color: var(--color-error-text);
  background: var(--color-error-highlight);
  border: 2px solid var(--color-error);
  border-radius: var(--radius-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  /* Redundant with the shared [data-funding] rule since ID 589 moved every tone
     to --text-sm, but restated so this block stays a complete description of
     the ID 531 chip -- the ID 575 card is checked against it verbatim. */
  font-size: var(--text-sm);
}

.kpi-value.deal-grade {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
}
.deal-grade-A { color: var(--color-success); }
.deal-grade-B { color: var(--color-primary); }
.deal-grade-C { color: var(--color-warning); }
.deal-grade-D,
.deal-grade-F { color: var(--color-error); }

.kpi-card {
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  min-width: 0;
}
.kpi-card[data-flag-level='good'] {
  border-left-color: var(--color-success);
}
.kpi-card[data-flag-level='mid'] {
  border-left-color: var(--color-warning);
}
.kpi-card[data-flag-level='low'] {
  border-left-color: var(--color-error);
}
.kpi-card-clickable {
  cursor: pointer;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.kpi-card-clickable:hover {
  box-shadow: var(--shadow-md, var(--shadow-sm));
  border-color: var(--color-primary);
}
.kpi-card-clickable:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.kpi-card-clickable[aria-pressed='true'] {
  border-color: var(--color-primary);
}
.kpi-sort-hint {
  margin-left: auto;
  color: var(--color-text-muted);
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
}
.dashboard-legend-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.dashboard-legend-row-top {
  margin-bottom: var(--space-3);
}
.kpi-legend-btn-top {
  display: inline-block;
}
.dashboard-sort-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.legend-flag-row-note {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.kpi-card .kpi-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  margin-bottom: var(--space-3);
}
/* ID 589: the [data-funding] chips (ID 531) are excluded here by selector
   rather than by source order. Both `.kpi-value[data-funding='overdrawn']` and
   a bare `.kpi-card .kpi-value` score (0,2,0), so this rule -- being later in
   the file -- silently won the tie and put every chip back to mono at
   --text-xl. That is display type for a single figure, and the chips carry a
   sentence, so "FULLY DRAWN - -$8,540" wrapped to three lines and pushed the
   Analyze row to nearly twice its height. 'none' is exempt from the exclusion:
   it renders a plain figure and should keep the numeral treatment. */
.kpi-card .kpi-value:not([data-funding]),
.kpi-card .kpi-value[data-funding='none'] {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--color-text);
  line-height: 1;
}
.kpi-card .kpi-sub {
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.kpi-card[data-tone='primary'] .kpi-value {
  color: var(--color-primary);
}
.kpi-card .kpi-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-3);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 600;
}
/* ID 266: these three carried the base tone tokens as text on their own
   highlight backgrounds, which measured 4.07 / 3.25 / 4.48:1 -- all under WCAG
   2.1 AA 1.4.3. ID 265 already created the text-safe variants for exactly this
   pairing (4.58 / 5.08 / 4.56:1); the base tokens stay correct for borders and
   icons. Found by the authenticated axe scan of the property analysis view. */
.kpi-flag[data-level='good'] {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}
.kpi-flag[data-level='mid'] {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.kpi-flag[data-level='low'] {
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}

.legend-flag-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.legend-flag-row {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  line-height: 1.6;
}
.legend-flag-row strong {
  color: var(--color-text);
}
.flag-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 2px;
  vertical-align: middle;
}
.flag-dot[data-level='good'] {
  background: var(--color-success);
}
.flag-dot[data-level='mid'] {
  background: var(--color-warning);
}
.flag-dot[data-level='low'] {
  background: var(--color-error);
}

.breakdown-panel {
  padding: var(--space-6);
}
.panel-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
/* #4443: at the standard 640px mobile breakpoint, let the heading wrap so a
   .panel-heading-actions block (e.g. Record refinance / Mark loan paid off
   on the Loan tab) drops to its own full-width row instead of being
   squeezed against the h2+panel-note and overflowing off-screen. */
@media (max-width: 640px) {
  .panel-heading {
    flex-wrap: wrap;
  }
}
.panel-heading h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 650;
}
.panel-heading .panel-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* #3155: the Closing Task panel's fully-derived collapsed state -- shown
   instead of #ctBody once every task is done, never both at once. */
.ct-collapsed-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  font-size: var(--text-sm);
  font-weight: 550;
}
.ct-collapsed-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25em;
  height: 1.25em;
  border-radius: 50%;
  background: var(--color-success);
  color: var(--color-surface);
  font-size: var(--text-xs);
  flex: none;
}

.ledger {
  width: 100%;
  font-size: var(--text-sm);
}
.ledger tr {
  border-bottom: 1px solid var(--color-divider);
}
.ledger tr:last-child {
  border-bottom: none;
}
.ledger td {
  padding: var(--space-2) 0;
}
.ledger td:first-child {
  color: var(--color-text-muted);
}
.ledger td:last-child {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}
.ledger tr.subtotal td {
  font-weight: 600;
  color: var(--color-text);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
.ledger tr.subtotal td:first-child {
  color: var(--color-text);
}
.ledger tr.deduct td:last-child::before {
  content: '\2212\00A0';
  color: var(--color-error);
}
.ledger-subnote {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 400;
  margin-top: 2px;
}
.ledger tr.result td {
  font-weight: 700;
  padding-top: var(--space-3);
  border-top: 2px solid var(--color-primary);
}
.ledger tr.result td:last-child {
  color: var(--color-primary);
  font-size: var(--text-base);
}

/* ================= Sensitivity section ================= */

.sensitivity-panel {
  padding: var(--space-6);
}

.projection-panel {
  padding: var(--space-6);
}
#yearlyTable {
  min-width: 760px;
}

.sensitivity-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.grid-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  width: 100%;
  max-width: 100%;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
}

.heatmap {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--text-sm);
  min-width: 560px;
}
.heatmap caption {
  text-align: left;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  padding: var(--space-3);
}
.heatmap th,
.heatmap td {
  padding: var(--space-3);
  text-align: center;
  border-bottom: 1px solid var(--color-divider);
  border-right: 1px solid var(--color-divider);
}
.heatmap th:last-child,
.heatmap td:last-child {
  border-right: none;
}
.heatmap tbody tr:last-child td {
  border-bottom: none;
}
.heatmap thead th {
  background: var(--color-surface-2);
  font-weight: 600;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  position: sticky;
  top: 0;
}
/* #4384 QA-FAIL follow-up: the horizontal-scroll affordance (applyScrollHint,
   above) tells a mobile user there is more grid off-screen, but scrolling it
   used to carry the price/row-label column (.heatmap tbody th) away with the
   data cells, leaving no pinned context once scrolled. Freeze that column the
   same way .grid-scroll .mini-table already does (style.css ~7421): sticky
   left, its own stacking context above the scrolling data cells, resting on
   the solid background each rule below already declares so a scrolled-under
   cell doesn't show through. thead th:first-child (the corner cell) needs a
   higher z-index so it stays above the tbody th cells at the intersection of
   both sticky axes. */
.heatmap tbody th {
  background: var(--color-surface-2);
  font-weight: 600;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
  text-align: left;
  position: sticky;
  left: 0;
  z-index: 1;
}
.heatmap thead th:first-child {
  background: var(--color-surface-offset);
  position: sticky;
  left: 0;
  z-index: 2;
}
.heatmap td[data-current='true'] {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
  position: relative;
}
.heatmap .cell-value {
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  display: block;
}
.heatmap .cell-sub {
  font-size: var(--text-xs);
  opacity: 0.75;
  display: block;
  margin-top: 2px;
}
/* #5366: Own-phase sensitivity grid -- flags whichever rent row's resulting
   per-unit rent is closest to the record's market rent (only rendered when
   a usable market rent exists; see nearestOwnRentRowToMarket() in app.js). */
.heatmap .market-badge {
  display: block;
  margin-top: 2px;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-primary);
}

.legend-scale {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.legend-swatch {
  width: 68px;
  height: 10px;
  border-radius: var(--radius-full);
  background: linear-gradient(
    to right,
    var(--color-error),
    var(--color-warning),
    var(--color-success)
  );
}

/* #786: several role="tab" buttons in index.html (dashboardRentViewCurrentBtn,
   compareNormalizeTotalBtn, compareRentViewCurrentBtn and siblings) carry NO
   class attribute at all, which looked like it might be the same base.css:89
   reset bug as #766. It is not -- confirmed by inspecting the live computed
   styles in the Docs Demo fixture. `.tabs button` below is a plain element
   selector, so it fully styles any <button> inside a `.tabs` container by
   position alone; a class was never needed here, same conclusion #766 already
   reached for `.scenario-toggle-btn`. No fix required. Do not add a class to
   these buttons expecting it to change anything -- `.tabs button` already wins. */
.tabs {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.tabs button {
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
  border-right: 1px solid var(--color-border);
}
.tabs button:last-child {
  border-right: none;
}
.tabs button[aria-selected='true'] {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
.tabs button:not([aria-selected='true']):hover {
  background: var(--color-surface-offset);
  color: var(--color-text);
}

.tab-panel[hidden] {
  display: none;
}

.tenant-detail-tabs {
  display: flex;
  max-width: 100%;
  margin-bottom: var(--space-6);
  overflow-x: auto;
}
.tenant-detail-tabs button {
  flex: 1 0 auto;
}
.tenant-detail-panel > :first-child {
  margin-top: 0;
}
.tenant-detail-section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.tenant-detail-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.tenant-detail-overflow {
  display: inline-block;
}
.tenant-detail-overflow .btn-export {
  min-width: 2.25rem;
  justify-content: center;
  font-size: var(--text-lg);
  line-height: 1;
  padding-inline: var(--space-2);
}
.tenant-detail-more-menu {
  left: auto;
  right: 0;
}
.tenant-detail-menu-danger,
.tenant-detail-menu-danger:hover {
  color: var(--color-error);
}

/* #1164: page-header layout (name/contact/pills + Edit/overflow at the top
   of the page, not on the "Tenant info" card) and the two Overview cards,
   per docs/wireframes/tenant-detail.html screens 1-3. */
.tenant-detail-header-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.tenant-detail-header-main h1 {
  display: inline;
  margin: 0;
}
.tenant-detail-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.tenant-detail-header-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
.tenant-overview-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
  align-items: start;
}
@media (max-width: 900px) {
  .tenant-overview-cols {
    grid-template-columns: 1fr;
  }
}
.tenant-overview-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  padding: var(--space-4) var(--space-5);
}
.tenant-overview-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.tenant-overview-card-head h2 {
  margin: 0;
  font-size: var(--text-base);
}
.tenant-overview-dl {
  margin: 0;
}
.tenant-overview-dl-row {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.tenant-overview-dl-row:last-child {
  border-bottom: none;
}
.tenant-overview-dl-row dt {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
}
.tenant-overview-dl-row dd {
  margin: 0;
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}
.tenant-detail-admin-caption {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.tenant-archive-note {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.tenant-archive-note[hidden] {
  display: none;
}
#tenantDetailInteractiveContent.tenant-detail-archived {
  opacity: 0.48;
  filter: grayscale(0.4);
  pointer-events: none;
  user-select: text;
}
.tenant-delete-modal .modal-card {
  border-top: 4px solid var(--color-error);
}
.tenant-delete-modal-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  margin-right: var(--space-2);
  border-radius: 50%;
  background: var(--color-error);
  color: var(--color-text-inverse);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}
.tenant-delete-modal-copy {
  display: grid;
  gap: var(--space-2);
  padding-left: var(--space-5);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.5;
}
.tenant-delete-modal-error {
  color: var(--color-error);
}
.tenant-delete-modal-actions {
  justify-content: flex-end;
}
.tenant-delete-confirm-btn,
.tenant-delete-confirm-btn:hover:not(:disabled) {
  border-color: var(--color-error);
  background: var(--color-error);
  color: var(--color-text-inverse);
}

.rent-view-toggle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  margin-bottom: var(--space-5);
}
.rent-view-toggle-row[hidden] {
  display: none;
}
.unit-mode-toggle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.unit-mode-toggle-row[hidden] {
  display: none;
}
/* #1255 -- Normal-mode rent detail. This deliberately uses compact rows rather
   than a second copy of the Advanced card editor: only the three rent/lease
   fields vary in Normal mode, and a 12-unit building must remain scannable. */
.basic-unit-rent-strip {
  margin: 0 0 var(--space-4);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.basic-unit-rent-strip[hidden] {
  display: none;
}
.basic-unit-rent-strip-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.basic-unit-rent-strip-head h3 {
  margin: 0 0 var(--space-1);
  font-family: var(--font-display);
  font-size: var(--text-sm);
}
.basic-unit-rent-strip-head .field-note {
  margin: 0;
}
.basic-unit-rent-totals {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 650;
  white-space: nowrap;
}
.basic-unit-rent-list {
  max-height: 28rem;
  overflow: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.basic-unit-rent-row {
  display: grid;
  grid-template-columns: minmax(4.5rem, 0.65fr) repeat(3, minmax(9rem, 1fr));
  gap: var(--space-2);
  align-items: end;
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.basic-unit-rent-row:last-child {
  border-bottom: 0;
}
.basic-unit-rent-label {
  padding-bottom: calc(var(--space-2) + 1.8rem);
  font-size: var(--text-sm);
  font-weight: 650;
}
.basic-unit-rent-cell {
  min-width: 0;
}
.basic-unit-rent-cell label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.basic-unit-rent-cell input {
  width: 100%;
}
@media (max-width: 680px) {
  .basic-unit-rent-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .basic-unit-rent-label {
    grid-column: 1 / -1;
    padding-bottom: 0;
  }
}
/* Per-unit editor. The input column is capped at 400px on desktop and only
   goes full-width below the 900px breakpoint, so the usual wide-table-that-
   stacks-on-mobile pattern is backwards here -- a nine-column row never fits
   where this lives. Each unit is a card at every width instead, and only the
   field grid inside it reflows. */
/* Advanced mode hides one of the two fields in several .field-row pairs;
   without this the survivor keeps its half-width column and reads as a
   layout bug. Degrades to that same half-width look where :has() is
   unsupported. */
.field-row:has(> .field[hidden]) {
  grid-template-columns: minmax(0, 1fr);
}
.unit-editor-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.unit-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--color-surface);
}
.unit-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.unit-card-title {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
}
.unit-card-actions {
  display: flex;
  gap: var(--space-2);
}
.unit-action-btn {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  line-height: 1;
  padding: var(--space-2);
  cursor: pointer;
}
.unit-action-btn:hover:not(:disabled) {
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.unit-action-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.unit-card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}
.unit-cell {
  min-width: 0;
}
.unit-cell-label {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}
.unit-cell input,
.unit-cell select {
  width: 100%;
}
.unit-row-error[hidden] {
  display: none;
}
.unit-cell input.field-input-error,
.unit-cell select.field-input-error {
  border-color: var(--color-error);
}

/* #5587 -- Unit Details modal copy-to-other-units bulk action. */
.unit-details-modal-card {
  max-height: 85vh;
  overflow-y: auto;
}
.unit-details-copy-box {
  background: var(--color-bg-alt);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-5);
}
.unit-details-copy-title {
  font-weight: 600;
  margin-bottom: var(--space-3);
}
.unit-details-copy-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.unit-details-copy-row input[type="number"] {
  width: 4.5em;
}
.unit-details-copy-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.unit-details-copy-chip {
  display: inline-block;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full, 999px);
  background: var(--color-surface);
  border: 0.5px solid var(--color-border);
}
.unit-details-copy-picker {
  margin-top: var(--space-3);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-1) var(--space-3);
  max-height: 180px;
  overflow-y: auto;
}
.unit-details-copy-picker-item {
  font-size: var(--text-sm);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.unit-details-copy-status {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: var(--space-3);
  margin-bottom: 0;
}
/* #245 -- the per-unit grade-split hint. Same amber treatment as any other
   .field-note-warn; the only additions are the hidden rule (an unhidden
   [hidden] paragraph still occupies grid space here) and the top margin, since
   it sits under the card grid rather than inside a field. */
.unit-row-grade-flag {
  margin: var(--space-2) 0 0;
}
.unit-row-grade-flag[hidden] {
  display: none;
}
.unit-editor-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.unit-editor-totals {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 650;
}
@media (max-width: 420px) {
  .unit-card-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* #234 -- common areas (spec 8.2). Reuses .unit-card / .unit-cell wholesale so
   a shared space reads as a sibling of a unit rather than a different kind of
   object; the only additions are the archived treatment, the tenant-visible
   badge and the suggested-set box. */
.area-card.is-archived {
  opacity: 0.62;
  background: var(--color-surface-offset);
}
.area-card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.area-badge {
  font-size: var(--text-xs);
  line-height: 1;
  padding: 3px 7px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.area-badge.is-private {
  border-color: var(--color-warning);
  /* --color-warning is 4.13:1 on white -- fine for a border, not for text, so
     the label uses the text-only amber. CI runs axe-core at WCAG 2.1 AA. */
  color: var(--color-warning-text);
}
.area-suggest-box {
  margin-top: var(--space-4);
  padding: var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
/* `display: flex` above beats the UA sheet's `[hidden] { display: none }`, so
   without this the suggested-set box stays visible while the add/edit form is
   open -- setting .hidden looks like it works and does nothing. Caught in the
   #234 Playwright pass: the JSON said hidden:true and the screenshot disagreed. */
.area-suggest-box[hidden] {
  display: none;
}
.area-suggest-text {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  flex: 1 1 220px;
  min-width: 0;
}
.area-archived-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  cursor: pointer;
}
.area-archived-toggle input {
  width: auto;
  margin: 0;
}

/* ---------------------------------------------------------------------------
   Building Systems -- #217 / spec 5.4.5 + 8.4.
   Deliberately borrows .unit-editor / .unit-editor-card and
   .area-archived-toggle rather than inventing a second card idiom: the tab is a
   list of physical things attached to a property, which is exactly what the
   unit and common-area lists already are.
   --------------------------------------------------------------------------- */
.systems-completeness {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.assumed-items-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.assumed-items-list li {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.assumed-items-list li:last-child { border-bottom: 0; }
.assumed-items-list input { width: auto; margin: 0; }
.assumed-items-label { font-weight: 600; }
.assumed-items-detail { color: var(--color-text-muted); }
.systems-completeness-label {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.systems-completeness-missing {
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* Mixed scope is a WARNING and has to READ like one without reading like an
   error -- spec 8.4.2 is explicit that a building boiler plus one converted
   unit furnace is legitimate, so this must not look like something to fix. */
.systems-warning {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  /* --color-warning is 4.13:1 on white: fine for a border, not for text.
     CI runs axe-core at WCAG 2.1 AA. */
  color: var(--color-warning-text);
}
.systems-warning[hidden] {
  display: none;
}

/* #247 -- open source-precedence suggestions.
   Deliberately quieter than .systems-warning: a suggestion is not a problem
   with the data, it is one source disagreeing with another, and the owner's
   number is still the one in effect. Styling it as an alarm would push people
   to click "use theirs" just to make the red go away. */
.systems-suggestions {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.systems-suggestions[hidden] {
  display: none;
}
.systems-suggestions-head {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-family: var(--font-display);
  color: var(--color-text);
}
.systems-suggestions-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
}
.systems-suggestion {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.systems-suggestion-text {
  flex: 1 1 22rem;
  min-width: 0;
}
.systems-suggestion-where {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.systems-suggestion-actions {
  display: flex;
  gap: var(--space-2);
  flex: 0 0 auto;
}
.systems-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
}
.systems-card-title {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.systems-card-where {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.systems-badge {
  font-size: var(--text-xs);
  line-height: 1;
  padding: 3px 7px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.systems-badge-good,
.systems-badge-excellent {
  color: var(--color-success-text);
}
.systems-badge-poor,
.systems-badge-failed {
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}
.systems-badge-retired {
  border-style: dashed;
}
/* .unit-editor-card carries its frame from a `.unit-editor-list >` selector, so
   a systems card reusing the class alone renders as an unseparated run of text.
   Give it its own frame rather than widening that selector, which would drag
   the unit editor's own spacing into this list. */
.systems-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--color-surface-offset);
}
.systems-card + .systems-card {
  margin-top: var(--space-2);
}
/* A retired row is history, not clutter: still legible, visibly not current.
   Dimming it away entirely would defeat the point of keeping it (spec 8.6). */
.systems-card-retired {
  border-style: dashed;
}
.systems-card-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin-top: var(--space-2);
}
.systems-card-fact {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
}
.systems-card-notes {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
/* #1304: read-only visit-document list on a Building Systems card. Follows
   the same token pattern as .systems-card-facts/-notes above -- no
   hardcoded hex/px where a design token exists. Hidden (not just empty)
   when a card has no visit-linked documents, per the issue's own
   "render nothing" empty-state rule -- see systemsRenderCardDocs in app.js. */
.systems-card-docs {
  margin-top: var(--space-2);
}
.systems-card-docs[hidden] {
  display: none;
}
.systems-card-docs-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}
.systems-card-doc {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-xs);
}
.systems-card-doc-badge {
  flex: 0 0 auto;
  padding: 1px 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}
.systems-card-doc-main {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}
.systems-card-doc-title {
  color: var(--color-text);
  text-decoration: underline;
  cursor: pointer;
}
.systems-card-doc-via {
  color: var(--color-text-muted);
}
.systems-card-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.systems-form {
  margin-top: var(--space-4);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.systems-form[hidden] {
  display: none;
}
.systems-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
}
.systems-form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  min-width: 0;
}
.systems-form-field-wide {
  margin-top: var(--space-3);
}
.systems-bulk-toggle {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  cursor: pointer;
}
.systems-bulk-toggle[hidden] {
  display: none;
}
.systems-bulk-toggle input {
  width: auto;
  margin: 0;
}
.systems-details {
  margin-top: var(--space-3);
}
.systems-details > summary {
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}
/* The Owner Portal has no .btn-primary (that class lives in admin.css); the
   save action still needs to read as the primary one, so tint .btn-export
   rather than introduce a second button system. */
.systems-save-btn {
  border-color: var(--color-text);
  font-weight: 600;
}
.systems-form-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* #1631 -- Asset Register: compact category bands make a property with
   hundreds of rows readable as seven expandable groups rather than a flat
   scroll. The table keeps ordinary semantic table markup under each band. */
.asset-register {
  display: grid;
  gap: var(--space-4);
}
.asset-register-breadcrumb {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.asset-register-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--color-border);
}
.asset-register-summary > div {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-3);
  border-right: 1px solid var(--color-border);
}
.asset-register-summary > div:last-child { border-right: 0; }
.asset-register-summary span {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.asset-register-summary strong {
  font-family: var(--font-display);
  font-size: var(--text-sm);
}
.asset-register-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}
.asset-register-tabs button {
  margin-bottom: -1px;
  padding: 0 0 var(--space-2);
  border: 0;
  border-bottom: 3px solid transparent;
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  cursor: pointer;
}
.asset-register-tabs button[aria-selected="true"] {
  border-color: var(--color-primary);
  color: var(--color-text);
  font-weight: 600;
}
.asset-register-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.asset-register-controls input[type="search"] {
  flex: 1 1 240px;
  min-width: 0;
}
.asset-register-chip,
.asset-register-category-label {
  min-height: 34px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.asset-register-chip {
  padding: 0 var(--space-3);
  cursor: pointer;
}
.asset-register-chip[aria-pressed="true"] {
  border-color: var(--color-text);
  color: var(--color-text);
}
.asset-register-category-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding-left: var(--space-2);
}
.asset-register-category-label select {
  width: auto;
  min-height: 32px;
  border: 0;
  background: transparent;
}
.asset-register-groups {
  display: grid;
  /* #4465 follow-up: this grid has exactly one column and always did, but
     with no grid-template-columns the implicit track defaults to `auto`,
     which sizes to the widest child's max-content rather than to the
     available width -- the same #4377 trap this file documents elsewhere.
     .asset-mobile-list's rows depend on their ellipsis rules firing, which
     requires the box to already be width-constrained; without this, the
     track (and every row inside it) sized to the longest unellipsed name,
     measured pushing document.body.scrollWidth to 959px against a 375px
     viewport with overflow-x: clip silently hiding it (no scrollbar, text
     just cut off at the edge). One column, so this line only fixes sizing
     and changes no layout. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
}
.asset-register-group {
  border: 1px solid var(--color-border);
  min-width: 0;
}
.asset-register-group-head {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: var(--color-surface-offset);
  color: var(--color-text);
  text-align: left;
  font: 600 var(--text-sm) var(--font-display);
  cursor: pointer;
}
.asset-register-table-scroll { overflow-x: auto; }
.asset-register-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.asset-register-table th,
.asset-register-table td {
  padding: var(--space-2);
  border-top: 1px solid var(--color-border);
  text-align: left;
  vertical-align: middle;
}
.asset-register-table th {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
}
.asset-register-ghost {
  color: var(--color-text-muted);
  font-style: italic;
  background: var(--color-surface-offset);
}
.asset-register-add {
  border: 0;
  background: transparent;
  color: var(--color-primary);
  font: inherit;
  cursor: pointer;
}
.asset-register-not-applicable-strike {
  text-decoration: line-through;
}

/* #2331: register row selection + the bulk-visit action bar. */
.asset-register-select-cell {
  width: 32px;
}
.asset-register-select-cell input[type="checkbox"],
.asset-register-select-checkbox {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
.asset-register-selection-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-offset);
  font-size: var(--text-sm);
}
/* #4465: the class rule above sets display:flex unconditionally, which beats
   the UA's native [hidden]{display:none} per normal cascade rules -- so
   renderAssetRegisterSelectionBar() was setting bar.hidden = true correctly at
   zero selected and the bar stayed on screen anyway. Verified live at 375px
   with nothing selected: the bar rendered, including two buttons with no
   accessible name at all (#assetRegisterSelectAllMatching and
   #assetRegisterRecordVisitOnSelectionBtn), whose text is only ever filled in
   by that same function AFTER its early return at count 0. The pre-existing
   .action-btn[hidden] rule never applied here -- it matches on the class
   `action-btn`, and this element's class is `asset-register-selection-bar`.
   Fixed at the CSS layer with a per-class companion rule, the same way this
   file's other display-vs-[hidden] collisions are fixed, rather than from JS. */
.asset-register-selection-bar[hidden] {
  display: none;
}
.asset-register-selection-hidden-note {
  color: var(--color-text-muted);
}
.asset-register-selection-bar-spacer {
  flex: 1 1 auto;
}

/* #4465: #2522's stacked-card mechanism (forcing .asset-register-table/
   thead/tbody/tr/td to display:block with data-label::before pseudo-
   labels, below) is REMOVED here and replaced by .asset-mobile-list, a
   separate JS-built sibling of .asset-register-table-scroll that Agent B
   renders (buildAssetMobileRow/renderAssetMobileList in app.js). Leaving
   the old card CSS in place would render BOTH the stacked-card table and
   the new mobile list at once -- every asset appearing twice. Removed
   rules, all previously inside this same @media (max-width: 640px) block:
     .asset-register-table-scroll { overflow-x: visible; }
     .asset-register-table { display: block; width: 100%; max-width: 100%; }
     .asset-register-table thead { display: none; }
     .asset-register-table tbody { display: block; width: 100%; }
     .asset-register-table tr { display: block; width: 100%; box-sizing:
       border-box; border: 1px solid var(--color-divider); border-radius:
       var(--radius-sm); padding: var(--space-2); margin-bottom: var(--space-2); }
     .asset-register-table td { display: block; width: 100%; box-sizing:
       border-box; min-width: 0; padding: 2px 0; border-top: 0;
       overflow-wrap: anywhere; }
     .asset-register-table td[data-label]::before { content: attr(data-label);
       display: block; font-size: var(--text-xs); color: var(--color-text-muted);
       margin-bottom: 1px; }
     .asset-register-table td:empty::before { height: 0; }
     .asset-register-select-cell { width: auto; display: inline-block;
       margin-bottom: var(--space-1); } (the mobile-only override)
     .asset-register-table td.asset-register-select-cell:empty::before { content: none; }
     .asset-register-table td:nth-child(2) { width: 100%; font-weight: 600; }
     .asset-register-table tr.asset-register-ghost { border-style: dashed; }
   The <table>, its <thead>, and every data-label attribute STAY in the
   markup unchanged -- a test pins the thead byte-for-byte and the
   data-label attributes still document column identity for the table's
   own (unchanged) desktop and the (b)-path horizontal-scroll convention
   below. Only the CSS that restyled the table into cards is gone; the
   table itself now simply hides at this width while .asset-mobile-list
   shows, same on/off relationship as the #4377 .mobile-cards-table-scroll/
   .mobile-cards-list pair further down this file. Do not reintroduce a
   second responsive scheme for this table. */
@media (max-width: 640px) {
  .asset-register-table-scroll {
    display: none;
  }
}

/* #4465: mobile row list for the asset register, replacing #2522's stacked
   table-as-cards mechanism above. Follows the same on/off relationship as
   the #4377 .mobile-cards-table-scroll/.mobile-cards-list pair (style.css
   ~19730 below) and the .mobile-card visual precedent right after it --
   hidden by default, shown only inside the media query, with the table's
   own wrapper doing the reverse (see the rule immediately above). Agent B
   builds the DOM (buildAssetMobileRow/renderAssetMobileList in app.js);
   this file only owns how it looks. */
.asset-mobile-list {
  display: none;
}
/* #6374: the ghost-row Not applicable action is a compact secondary control
   beside the mobile + Add button. */
.asset-mobile-not-applicable-btn {
  border: 0;
  background: transparent;
  color: var(--color-primary);
  font: inherit;
  cursor: pointer;
  align-self: flex-start;
  padding: 0;
  margin-top: 2px;
}
@media (max-width: 640px) {
  .asset-mobile-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }
}

/* #4465 rule 6: "the six stacked filter controls collapse behind a single
   Filters button showing a count of what is active". Above 640px the
   toggle button and panel wrapper are both invisible no-ops -- every chip
   and the category select render exactly where they always have, inline
   in .asset-register-controls, so desktop is byte-for-byte unaffected.
   Search (.asset-register-controls > input) is deliberately OUTSIDE
   .asset-register-filter-panel and stays always-visible per the wireframe
   ("Search stays permanently visible -- it is the primary finder"). */
.asset-register-filters-toggle {
  display: none;
}
.asset-register-filters-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.1rem;
  height: 1.1rem;
  margin-left: var(--space-1);
  padding: 0 0.3rem;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-text-inverse, #fff);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
}
@media (max-width: 640px) {
  .asset-register-filters-toggle {
    display: inline-flex;
    align-items: center;
  }
  /* Collapsed by default on mobile; .asset-register-filter-panel-open
     (toggled by app.js on the Filters button click) reveals it as a
     simple stacked block -- same layout the controls already used
     before this ticket, just gated behind one tap instead of always on. */
  .asset-register-filter-panel {
    display: none;
    flex-direction: column;
    gap: var(--space-2);
    width: 100%;
    order: 10;
  }
  .asset-register-filter-panel.asset-register-filter-panel-open {
    display: flex;
  }
  .asset-register-filter-panel .asset-register-chip {
    width: 100%;
    text-align: left;
  }
  .asset-register-active-filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
  }
}
.asset-register-active-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-full);
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
}
.asset-register-active-filter-chip-x {
  font-weight: 700;
}
/* #4465: the row button and its selection checkbox are SIBLINGS (never
   nested -- see the .asset-mobile-row-check comment below), so something has
   to lay them out side by side. .asset-mobile-list is flex-direction: column,
   so without this rule the wrapper defaults to display: block and the
   checkbox lands on its own line ABOVE each row -- turning every one-line row
   back into two and undoing the whole point of the ticket.
   `width: auto` overrides the `width: 100%` below: as a flex item with a
   checkbox sibling, a 100% basis would push the row wider than its wrapper. */
.asset-mobile-row-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.asset-mobile-row-wrap > .asset-mobile-row {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
}
.asset-mobile-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition-interactive);
}
.asset-mobile-row:hover {
  border-color: var(--color-primary);
}
.asset-mobile-row:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* Ghost rows (an expected item not yet recorded) are still rows in this
   list; keep them visually distinct the same way the old card mechanism
   did with border-style: dashed on .asset-register-ghost. */
.asset-mobile-row.asset-register-ghost {
  border-style: dashed;
  background: var(--color-surface-offset);
}
/* The checkbox is a <label> OUTSIDE the row <button> on purpose (see the
   contract table) so tapping it toggles selection without also firing the
   button's click handler and opening the record -- nesting a checkbox
   inside a button either double-fires or is invalid HTML depending on the
   browser. flex-shrink: 0 keeps it from being squeezed by the ellipsing
   text column next to it. */
.asset-mobile-row-check {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  min-width: 44px;
  min-height: 44px;
  justify-content: center;
  cursor: pointer;
}
.asset-mobile-row-check input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
/* #4465: min-width: 0 is mandatory here, not decorative -- a flex item
   defaults to min-width: auto, which refuses to shrink below its content's
   intrinsic width and silently disables text-overflow: ellipsis on the
   children below. Same trap as the #4377 table-scroll convention note
   documents for nested tables in a grid/flex ancestor. */
.asset-mobile-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.asset-mobile-row-identity,
.asset-mobile-row-digest {
  /* #4465 follow-up: both are flex ITEMS of .asset-mobile-row-main
     (display:flex; flex-direction:column above), and a flex item defaults
     to min-width:auto -- refusing to shrink below its content's intrinsic
     width, exactly the trap .asset-mobile-row-check's sibling comment above
     already warns about. Without this, a long asset name/location never
     ellipses; it forces this row (and the whole page) to overflow
     horizontally instead, cutting text off the right edge with no scroll
     affordance. Measured live: a 41-row Plumbing group pushed
     document.body.scrollWidth to 959px against a 375px viewport before this
     fix. */
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.asset-mobile-row-identity {
  font-size: var(--text-sm);
}
.asset-mobile-row-name {
  font-weight: 600;
}
.asset-mobile-row-where {
  /* The name and the location are two adjacent inline spans inside a
     white-space: nowrap line, so there is no whitespace text node between
     them -- app.js emits '\u00b7 <location>' with the separator FIRST, which
     rendered as "Trane XR80· Whole building" with the middot jammed against
     the name. Measured at 375px. Spacing it here rather than by prepending a
     space in JS keeps it a styling concern and cannot be collapsed away. */
  margin-left: 4px;
  font-weight: 400;
  color: var(--color-text-muted);
}
/* One step smaller than line 1, but never below the 12px WCAG-adjacent
   floor this app already treats as its minimum readable size elsewhere
   (see --text-xs, whose clamp() floor is 0.75rem = 12px). */
.asset-mobile-row-digest {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* Pill: exactly one per row, precedence Overdue -> Missing -> Unverified ->
   Condition, set by app.js via one of the variant classes below. The word
   is always in the pill text node -- colour never carries the meaning
   alone (WCAG 1.4.1). Each variant reuses the *-highlight/*-text token pair
   this file already uses for AA-checked text-on-tint elsewhere (see
   .status-pill-success/-warn/-error and .kpi-flag), rather than the plain
   --color-success/-warning/-error tones, which are border/icon-safe but
   fall short of 4.5:1 as text on their own tint. */
.asset-mobile-pill {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}
/* "Overdue = danger" per spec, but --color-danger has no audited *-text/
   *-highlight tint pair (unlike success/warning/error) and measures only
   3.66:1 light / 3.90:1 dark against --color-error-highlight -- both under
   WCAG AA 4.5:1. --color-error-text on --color-error-highlight is this
   file's existing audited danger-toned text-on-tint pair (4.56:1 light /
   5.05:1 dark, same family as .status-pill-error/.kpi-flag) and is what a
   danger-toned tint pill needs to read as normal-sized text rather than an
   icon/border. Checked, not assumed, per the spec's own instruction. */
.asset-mobile-pill-overdue {
  background: var(--color-error-highlight);
  border-color: var(--color-error);
  color: var(--color-error-text);
}
/* Missing = neutral outline, no fill -- deliberately the quietest of the
   four so it doesn't visually compete with the danger-toned Overdue pill
   for attention. Same neutral pairing as .status-select-offer_submitted. */
.asset-mobile-pill-missing {
  background: var(--color-surface-offset);
  border-color: var(--color-border);
  color: var(--color-text-muted);
}
.asset-mobile-pill-unverified {
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}
/* Condition ("Good"/"Fair"/"Poor"/etc): the contract table fixes exactly
   ONE class for this variant, .asset-mobile-pill-condition -- no per-value
   submodifier exists to hook into from this file, and inventing one would
   mean Agent B's DOM has to apply a second class this table never named.
   Spec A6 calls for "success/neutral by value", but with a single fixed
   class and no CSS attribute selector to key off (the condition word
   itself is opaque catalog text, not a fixed enum this file can enumerate
   with [data-value=] selectors), a single neutral tone is what one class
   can actually deliver -- it already satisfies the harder requirement
   (colour never carries meaning alone; the word is always in the pill).
   If a future ticket wants true success/neutral-by-value styling, it needs
   a data-* attribute added to the contract table first so both agents are
   building against the same hook -- flagged back to the parent rather than
   guessed at here. */
.asset-mobile-pill-condition {
  background: var(--color-surface-offset);
  border-color: var(--color-border);
  color: var(--color-text-muted);
}
@media (prefers-reduced-motion: reduce) {
  .asset-mobile-row {
    transition: none;
  }
}
.asset-register-unverified {
  display: inline-block;
  padding: 2px 6px;
  border: 1px solid var(--color-warning);
  border-radius: 999px;
  color: var(--color-warning-text);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.asset-register-overdue {
  color: var(--color-danger);
  font-weight: 600;
}
.inventory-screen {
  display: grid;
  gap: var(--space-3);
}
.inventory-screen[hidden] {
  display: none;
}
.inventory-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.inventory-controls input[type="search"] {
  flex: 1 1 240px;
  min-width: 0;
}
.inventory-location-strip {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.inventory-location-count {
  white-space: nowrap;
}
.inventory-groups {
  display: grid;
  gap: var(--space-2);
}
.inventory-group-head {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-offset);
  font: 600 var(--text-sm) var(--font-display);
}
.inventory-action-link,
.inventory-adjust {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--color-primary);
  font: inherit;
  cursor: pointer;
}
.inventory-adjust {
  min-width: 1.5rem;
  margin-right: var(--space-2);
  font-weight: 700;
}
.inventory-stock-tag {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  background: var(--color-surface-offset);
  font-size: var(--text-xs);
  text-transform: lowercase;
}
.inventory-stock-tag-out {
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}

/* #2765: Punch list tab -- mirrors .inventory-screen's layout rhythm
   exactly (same gap tokens, same group-head treatment) rather than
   inventing a new visual language for a fourth tab on the same panel. */
.punch-list-screen {
  display: grid;
  gap: var(--space-3);
}
.punch-list-screen[hidden] {
  display: none;
}
.punch-list-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.punch-list-controls input[type="search"] {
  flex: 1 1 240px;
  min-width: 0;
}
.punch-list-groups {
  display: grid;
  gap: var(--space-2);
}
.punch-list-group-head {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-offset);
  font: 600 var(--text-sm) var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.punch-list-rows {
  display: grid;
  gap: 1px;
  background: var(--color-border);
}
.punch-list-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface);
}
.punch-list-row-main {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
  flex: 1 1 320px;
}
.punch-list-row-title {
  font-weight: 600;
}
.punch-list-row-note {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-style: italic;
}
.punch-list-row-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.punch-list-condition {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.punch-list-condition-poor {
  background: var(--color-warning-highlight);
  border: 1px solid var(--color-warning);
  color: var(--color-warning-text);
}
.punch-list-condition-failed {
  background: var(--color-error-highlight);
  border: 1px solid var(--color-error);
  color: var(--color-error-text);
}
.punch-list-row-action {
  flex: 0 0 auto;
}
/* #2776: the tracked-detail line a promoted row shows instead of a plain
   "Already tracked" badge -- see punchListBuildRow's repairDetail branch.
   Kept in the same muted/meta styling family as .punch-list-row-found so a
   tracked row reads as more information on the row, not a different kind
   of row. */
.punch-list-row-tracked {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.punch-list-row-tracked-label {
  font-weight: 600;
  color: var(--color-text);
}
.punch-list-repair-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--radius-sm);
  background: var(--color-primary-highlight);
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: 600;
}
/* #4465 rule 6: at <=680px this used to become a static 2x2 grid (below).
   Superseded at <=640px (the same breakpoint the rest of #4465's mobile
   chrome uses) by a single horizontally-scrolling strip per the agreed
   wireframe (docs/wireframes/mobile-asset-rows/compact-mobile-asset-rows.html
   ".kpistrip") -- "the KPI block becomes one horizontally-scrolling strip".
   The 680px 2x2 rule still applies in the narrow 641-680px band the newer
   640px query does not reach. */
@media (max-width: 680px) {
  .asset-register-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .asset-register-summary > div:nth-child(2) { border-right: 0; }
  .asset-register-summary > div:nth-child(n+3) { border-top: 1px solid var(--color-border); }
}
@media (max-width: 640px) {
  .asset-register-summary {
    display: flex;
    grid-template-columns: none;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }
  .asset-register-summary > div {
    flex: 0 0 auto;
    min-width: 8.5rem;
    border-right: 1px solid var(--color-border);
    border-top: 0 !important;
  }
  .asset-register-summary > div:last-child {
    border-right: 0;
  }
}
/* #2776: the punch list tab as cards on a phone -- same data, same order,
   same actions as the desktop rows (per the #2331 wireframe's "what changes
   on a phone" note 1: cards on a phone, a table on a desktop, never a
   different feature). .punch-list-rows loses its 1px hairline-background
   trick (that's a desktop table-border simulation) and each row becomes an
   actual bordered, rounded card with its own shadow-free outline; the action
   button goes full width so it's a comfortable tap target instead of the
   desktop's compact trailing button. */
@media (max-width: 680px) {
  .punch-list-rows {
    gap: var(--space-2);
    background: none;
  }
  .punch-list-row {
    flex-direction: column;
    align-items: stretch;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
  }
  .punch-list-row-action {
    flex: 1 1 auto;
    width: 100%;
  }
  .punch-list-row-action .btn-export,
  .punch-list-row-action .action-btn {
    width: 100%;
  }
}

/* #1632: "Add an item" modal. A deliberately narrow single-column card (the
   wireframe specs ~420pt) so the required block reads as three fields, not a
   dense form -- width is capped, not stretched to the app's usual modal size. */
.asset-add-item-card {
  max-width: 420px;
}
.asset-add-item-card.asset-add-item-card-history {
  max-width: 760px;
}
.asset-add-item-required-heading {
  margin: 0 0 var(--space-2);
  font: 700 var(--text-xs) var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
}
.asset-add-item-divider {
  border: 0;
  border-top: 1px solid var(--color-border);
  margin: var(--space-4) 0 var(--space-3);
}
.asset-add-item-dynamic-heading {
  margin: 0 0 var(--space-2);
  font: 700 var(--text-xs) var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-primary);
}
.asset-add-item-year-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.asset-add-item-year-row input[type="number"] {
  flex: 1;
}
.asset-add-item-unknown-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.asset-add-item-optional-bar {
  display: block;
  width: 100%;
  margin: var(--space-3) 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  text-align: left;
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
.asset-add-item-optional-fields {
  display: grid;
  /* #4465: this grid had no grid-template-columns, so its single implicit
     `auto` track sized to its widest child's max-content instead of the
     container -- the same trap docs/ documents for #4377 (see the
     table-scroll convention block below). Measured live: 25 descendants
     resolved to 375px inside a 277px box, giving the card a real
     scrollWidth of 399 vs a clientWidth of 325 -- a horizontal scrollbar
     inside a form. minmax(0, 1fr) lets the track shrink to the container
     instead of the content; it is also correct on desktop, where the same
     grid silently overflowed ~3px before this fix. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.asset-add-item-optional-fields[hidden] {
  /* The class rule above sets display:grid unconditionally, which beats the
     UA's native [hidden]{display:none} default per normal cascade rules --
     the attribute was being toggled correctly in JS but had no visual effect
     (#1632 QA-fail, then a wrongly-closed re-verification). This re-asserts
     the hidden behavior at higher specificity than the bare class selector. */
  display: none;
}
.asset-add-item-optional-bar {
  transition: color 0.15s ease;
}
.asset-add-item-optional-bar .chevron {
  display: inline-block;
  transition: transform 0.15s ease;
}
.asset-add-item-optional-bar[aria-expanded="true"] .chevron {
  transform: rotate(90deg);
}
.asset-add-item-history {
  margin-top: var(--space-3);
}
.asset-add-item-history .asset-history-toggle {
  margin-bottom: 0;
}
.asset-history-content {
  display: grid;
  gap: var(--space-3);
  padding-top: var(--space-3);
}
.asset-history-content[hidden] {
  display: none;
}
.asset-history-panels {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}
.asset-history-panel {
  min-width: 0;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.asset-history-panel-heading {
  margin: 0;
  padding: var(--space-2) var(--space-3) 0;
  font: 700 var(--text-sm) var(--font-display);
}
.asset-history-identity {
  margin: var(--space-1) var(--space-3) var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}
.asset-history-list {
  max-height: 14rem;
  overflow-y: auto;
  border-top: 1px solid var(--color-border);
}
.asset-history-row {
  display: grid;
  grid-template-columns: minmax(4.5rem, auto) minmax(0, 1fr);
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--color-divider);
  font-size: var(--text-sm);
}
.asset-history-row:first-child {
  border-top: 0;
}
.asset-history-date,
.asset-history-range {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.asset-history-description,
.asset-history-occupant {
  min-width: 0;
  overflow-wrap: anywhere;
}
.asset-history-slot-row {
  grid-template-columns: minmax(5.25rem, auto) minmax(0, 1fr) auto;
}
.asset-history-duration {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  text-align: right;
  white-space: nowrap;
}
.asset-history-empty {
  margin: 0;
  padding: var(--space-3);
}
.asset-history-insight {
  margin: 0;
  color: var(--color-primary);
  font-size: var(--text-sm);
  font-style: italic;
}
@media (max-width: 42rem) {
  .asset-history-panels {
    grid-template-columns: 1fr;
  }
}
.asset-add-item-reminders-preview {
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-sm, 4px);
  padding: var(--space-3);
  margin-top: var(--space-3);
}
.asset-add-item-reminders-preview h3 {
  margin: 0 0 var(--space-2);
  font: 700 var(--text-xs) var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-primary);
}
.asset-add-item-reminders-preview ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-1);
  font-size: var(--text-sm);
}
.asset-add-item-reminders-preview li.is-empty {
  color: var(--color-text-muted);
  font-style: italic;
}
.asset-add-item-reminders-note {
  margin: var(--space-2) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-style: italic;
}
/* Inside .results-col (the per-property Overview/Sensitivity/etc. panel),
   the parent is itself a flex column with its own var(--space-6) gap
   between children. Without this override, that gap STACKS with this row's
   own top/bottom margins, roughly doubling the visual space above and below
   the Current/Market rents toggle vs. every other gap in that column. Zero
   the row's own margins here so the parent's gap is the only spacing. */
.results-col > .rent-view-toggle-row {
  margin-top: 0;
  margin-bottom: 0;
}
.rent-view-note {
  margin-top: 0;
  color: var(--color-warning-text);
}

.scenario-toggle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  margin-bottom: var(--space-5);
}
.scenario-summary-note {
  margin: 0;
  color: var(--color-text-muted);
}
/* #766: the three Base/Best/Worst case buttons carried a `.scenario-toggle-btn`
   class no stylesheet defined -- with the base.css:89 button reset that
   rendered as plain unstyled text. They already sit inside a
   `<div class="tabs" role="tablist">` (see #scenarioBaseBtn and siblings in
   index.html), and `.tabs button` / `.tabs button[aria-selected='true']`
   above already fully style any button in that container by element and
   attribute selector alone -- no class needed. `.scenario-toggle-btn` was
   therefore never load-bearing for CSS; this is a no-op marker rule so the
   class has a real definition (nothing to override -- `.tabs button` already
   wins) rather than reading as broken to a future audit. Do not add visual
   properties here; change the `.tabs button` rules above instead. */
.scenario-toggle-btn {
}

.line-tables {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-5);
  margin-top: var(--space-6);
}
/* #5366 QA-FAIL fix: this class sets `display`, and an author `display` rule
   beats the browser's own `[hidden] { display: none }` at ANY specificity --
   the same trap already documented on .data-feed-sync-card. Without this rule
   the Own-phase Sensitivity panel kept showing the analyze-only "Cap rate by
   price alone (+/-20%)" block (#analyzePriceOnlyBlock) even though
   updateSensitivityPhaseUI() had correctly set its `hidden` attribute, and
   symmetrically #ownLineTablesBlock leaked onto the analyze phase. Both are
   .line-tables, so one rule covers both directions. */
.line-tables[hidden] {
  display: none;
}
@media (max-width: 720px) {
  .line-tables {
    grid-template-columns: 1fr;
  }
}
.line-tables h3 {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
  margin-bottom: var(--space-3);
}
.rent-scenario-block {
  margin-top: var(--space-6);
}
.rent-scenario-block h3 {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
  margin-bottom: var(--space-3);
}
#rentScenarioTable {
  min-width: 640px;
}
#rentScenarioTable th,
#rentScenarioTable td {
  white-space: nowrap;
}
@media (max-width: 640px) {
  #rentScenarioTable th,
  #rentScenarioTable td {
    padding: var(--space-2) var(--space-1);
    font-size: 12px;
  }
}
.mini-table {
  width: 100%;
  font-size: var(--text-sm);
}
.mini-table th,
.mini-table td {
  padding: var(--space-2) var(--space-2);
  text-align: right;
  border-bottom: 1px solid var(--color-divider);
}
.mini-table th:first-child,
.mini-table td:first-child {
  text-align: left;
  color: var(--color-text-muted);
}
/* #4593: .mini-table's base rules above (right-align everything, mute the
   first column) are correct for a REPORT table -- yearlyTable, amortTable,
   a financial statement -- where the first column is a row label and every
   other column is a figure aligned on its right edge. They actively work
   against a RECORD list (vendorsTable, tenantsTable, usersTable, and every
   other list a paying customer scans to find one row): the primary
   identifier lands in the most muted colour in the row while its neighbours
   are full-contrast, and prose columns (address, date, status) get a
   ragged right-aligned edge that is harder to scan than flush-left.
   This modifier is added automatically by ListController's constructor in
   listkit.js (as `lk-record-table`) to every ListKit-driven table unless
   the host's config opts out with `reportStyle: true`.
   The :not(.lk-align-right) below is deliberate, not decorative: without it
   this rule and the existing .lk-align-right opt-in (ID 452, listkit.css)
   both sit at specificity (0,2,1), and which one wins would depend on
   stylesheet load order (style.css happens to load after listkit.css today,
   which would make THIS rule win -- backwards). The :not() bumps this
   selector's specificity past .lk-align-right's so a money column inside an
   otherwise left-aligned record list (e.g. lateFeeOverridesTable) stays
   right-aligned regardless of load order. */
.mini-table.lk-record-table td:not(.lk-align-right) {
  text-align: left;
}
.mini-table.lk-record-table th:first-child,
.mini-table.lk-record-table td:first-child {
  color: var(--color-text);
  font-weight: 500;
}
/* #3779: the cash-basis report's sub-line for a partially-attributed
   merged row (see financialStatementRenderCashBasis) -- smaller and
   muted so it reads as detail under the account row above it, never as a
   second peer row. */
.cash-basis-sub-row {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  border-bottom: none !important;
}
.grid-scroll .mini-table th:first-child,
.grid-scroll .mini-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--color-bg);
  box-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.18);
}
.grid-scroll .mini-table thead th:first-child {
  z-index: 2;
}
/* #1110: per-tenant proof-of-service list in the Issued documents table's
   Service column. Left-aligned regardless of .mini-table td's default
   right-align, since this cell holds names and timestamps, not a number. */
.issued-service-list {
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
}
.issued-service-list li + li {
  margin-top: var(--space-2);
}
.grid-scroll .mini-table tr[data-current='true'] td:first-child {
  background: var(--color-bg);
}
.mini-table td {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.year-zero-row td {
  background: var(--color-surface-2);
}
.grid-scroll .mini-table tr.year-zero-row td:first-child {
  background: var(--color-surface-2);
}
.panel-note-inline {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 400;
}
.dashboard-totals-row td {
  background: var(--color-surface-2);
  border-top: 2px solid var(--color-border);
  font-weight: 600;
}
.grid-scroll .mini-table tr.dashboard-totals-row td:first-child {
  background: var(--color-surface-2);
}
.mini-table thead th {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}
/* #5667: unitInfoTable's data cells are auto-left-aligned by ListKit's own
   .lk-record-table td:not(.lk-align-right) rule above (every column here is
   a "2bd / 1.5ba"-shape shorthand string, not a single right-aligned
   number), but that rule only ever targeted td -- these headers are plain
   static <th> markup (index.html), never rebuilt into ListKit sort buttons
   since every column but # is `sortable: false`, so they fell through to
   the base .mini-table th right-align rule instead and visibly sat to the
   right of the left-aligned data underneath. Left-align them to match.
   Scoped to this table rather than widening .lk-record-table th generally,
   since 66 other .mini-table instances exist and most pair sortable/numeric
   headers with right-aligned data, where this mismatch doesn't occur. */
#unitInfoTable.mini-table thead th {
  text-align: left;
}
.mini-table tr[data-current='true'] td {
  color: var(--color-primary);
  font-weight: 700;
}
/* #5876: chart-of-accounts drilldown rows drill in to the underlying
   journal entry on click/Enter -- signal that with a pointer cursor and a
   subtle hover, matching this table's existing data-current highlight
   convention rather than inventing a new one. */
.chart-of-accounts-drilldown-row {
  cursor: pointer;
}
.chart-of-accounts-drilldown-row:hover td,
.chart-of-accounts-drilldown-row:focus-visible td {
  /* --color-surface-2 is this stylesheet's row-hover token (147 uses,
     including table.data-table tbody tr:hover, which this row should match).
     --color-surface-hover is not defined anywhere here, so the old value
     fell through to its literal rgba fallback and ignored the theme.
     tests/contrast.unit.js is the guard that catches that. */
  background: var(--color-surface-2);
}
.chart-of-accounts-drilldown-row:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

.sort-btn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
}
.sort-btn:hover {
  color: var(--color-text);
}
.sort-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.sort-indicator {
  font-size: 0.65em;
  opacity: 0.35;
  min-width: 0.7em;
  display: inline-block;
  line-height: 1;
}
th[aria-sort='ascending'] .sort-indicator,
th[aria-sort='descending'] .sort-indicator {
  opacity: 1;
  color: var(--color-primary);
}

/* ================= Search watch jobs page ================= */

.sw-job-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  white-space: nowrap;
}
.sw-status-on {
  color: var(--color-success, #2f855a);
  font-weight: 600;
}
.sw-status-off {
  color: var(--color-text-muted);
}
.sw-job-name {
  font-weight: 600;
  color: var(--color-text);
}
.sw-job-meta {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: 0.15rem;
}

.methodology {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-divider);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  max-width: 78ch;
}
.methodology strong {
  color: var(--color-text-muted);
}

.site-footer {
  margin-top: var(--space-16);
  padding: var(--space-8) clamp(var(--space-4), 4vw, var(--space-10));
  border-top: 1px solid var(--color-divider);
  text-align: center;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.site-footer a {
  color: var(--color-text-muted);
}

@media (max-width: 900px) {
  .brand-tag {
    display: none;
  }
}

/* ===== Zoho sign-in lock screen ===== */
#appRoot {
  display: none;
}
.lock-screen {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  padding: var(--space-6);
}
.lock-screen[hidden] {
  display: none;
}
.lock-card {
  width: 100%;
  max-width: 22rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-8) var(--space-6);
  text-align: center;
}
.lock-mark {
  height: 2.75rem;
  width: auto;
  display: block;
  margin: 0 auto var(--space-4);
}
.lock-card h1 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 650;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-2);
}
.lock-sub {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--space-6);
  line-height: 1.5;
}
#lockPassword,
.lock-input {
  width: 100%;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: var(--text-base);
  margin-bottom: var(--space-3);
}
#lockPassword:focus-visible,
.lock-input:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.lock-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0 var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.lock-divider::before,
.lock-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--color-border);
}
#localLoginToggle {
  display: block;
  width: 100%;
  text-align: center;
  margin-bottom: var(--space-4);
}
.local-login-form {
  text-align: left;
}
.local-login-form .lock-submit {
  margin-top: var(--space-1);
}
.lock-submit {
  width: 100%;
  padding: 0.7rem 0.9rem;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: var(--text-base);
}
.lock-submit:hover {
  background: var(--color-primary-hover);
}
.lock-submit:active {
  background: var(--color-primary-active);
}
/* #4586: the SSO sign-in button's layout, moved off an inline `style` attribute.
   Inline styles beat every stylesheet rule, so an inline `display` made the
   element's `hidden` attribute permanently inert. See .lock-sso-btn[hidden]. */
.lock-sso-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  text-decoration: none;
}
.lock-error {
  margin-top: var(--space-4);
  font-size: var(--text-xs);
  color: var(--color-error);
}
.lock-notice {
  margin-top: var(--space-4);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.lock-forgot-link {
  display: block;
  width: 100%;
  text-align: center;
  margin-top: var(--space-3);
  font-size: var(--text-xs);
}
.lock-forgot-hint {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-3);
  line-height: 1.4;
}
#forgotPasswordCancel {
  display: block;
  width: 100%;
  text-align: center;
  margin-top: var(--space-3);
}
.modal-divider {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: var(--space-5) 0;
}

/* ================= Plan picker + checkout (subscription signup) =================
   Both screens reuse the .lock-screen/.lock-card shell above (they are part of
   the same unauthenticated signup flow) and only widen it -- three pricing
   cards and an embedded checkout page don't fit the 22rem sign-in card. */

.plan-screen,
.checkout-screen {
  /* Taller than the viewport on small screens, so scroll instead of clipping. */
  align-items: flex-start;
  overflow-y: auto;
}
/* #381: onboarding wizard. Same "wider, scrollable, left-aligned" override
   as .checkout-screen/.checkout-card just above -- a form with this many
   fields does not fit the narrow centered .lock-card used for sign-in. */
.onboarding-wizard {
  align-items: flex-start;
  overflow-y: auto;
}
.onboarding-wizard-card {
  max-width: 34rem;
  margin: auto;
  text-align: left;
}
.onboarding-wizard-card h1,
.onboarding-wizard-card .lock-sub {
  text-align: center;
}
.onboarding-wizard-steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-6);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.onboarding-wizard-steps li {
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-full, 999px);
  border: 1px solid var(--color-border);
}
.onboarding-wizard-steps li.onboarding-step-current {
  border-color: var(--color-primary);
  color: var(--color-primary);
  font-weight: 600;
}
.onboarding-wizard-steps li.onboarding-step-done {
  color: var(--color-text);
}
.onboarding-wizard-step[hidden] {
  display: none;
}
.onboarding-wizard-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-6);
}
.onboarding-wizard-nav .lock-submit {
  width: auto;
  padding-left: var(--space-6);
  padding-right: var(--space-6);
}
.plan-select-card {
  max-width: 56rem;
  margin: auto;
}
.checkout-card {
  max-width: 48rem;
  margin: auto;
}

.plan-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
  text-align: left;
}
.plan-grid[hidden] {
  display: none;
}
.plan-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-2);
}
/* The recommended plan gets the primary border rather than a different
   background, matching how .landing-card signals emphasis on hover. */
.plan-card-featured {
  border-color: var(--color-primary);
}
.plan-badge {
  align-self: flex-start;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  font-weight: 600;
}
.plan-badge-trial {
  color: var(--color-success-text);
  background: var(--color-success-highlight);
  border-color: var(--color-success);
}
.plan-badge-popular {
  color: var(--color-primary);
  background: var(--color-primary-highlight);
  border-color: var(--color-primary);
}
.plan-name {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 650;
}
.plan-price {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.plan-price strong {
  font-size: var(--text-xl);
  color: var(--color-text);
}
/* Enterprise shows words rather than a figure -- at --text-xl "Custom pricing"
   wraps onto two lines and dwarfs the two real prices beside it. */
.plan-price-text strong {
  font-size: var(--text-lg);
}
.plan-features {
  list-style: none;
  margin: var(--space-2) 0 var(--space-4);
  padding: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  line-height: 1.6;
}
/* Pushes every card's button to the bottom edge so the three line up even
   when the feature lists differ in length. */
.plan-cta {
  margin-top: auto;
  text-align: center;
  text-decoration: none;
}
.plan-cta[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* An empty card with no badge (Enterprise) would otherwise sit lower than the
   two badged ones. */
.plan-card:not(:has(.plan-badge)) .plan-name {
  margin-top: 1.6rem;
}

.checkout-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  justify-content: space-between;
  text-align: left;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.checkout-summary-item {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.checkout-summary-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.checkout-frame {
  width: 100%;
  min-height: 700px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

@media (max-width: 720px) {
  .plan-grid {
    grid-template-columns: 1fr;
  }
  .plan-card:not(:has(.plan-badge)) .plan-name {
    margin-top: 0;
  }
}

/* ================= Viewing banner (loaded saved analysis) ================= */

.viewing-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  background: var(--color-primary-highlight);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text);
  margin-bottom: var(--space-6);
}
.viewing-banner .action-btn {
  flex-shrink: 0;
}
.viewing-banner-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}
.viewing-banner[hidden] {
  display: none;
}

/* ====== Listing-import warnings banner (Task List ID 193) ======
   Info-toned sibling of .viewing-banner, shown on the Calculator page when a
   property was created via Listing URL Import and the adapter reported
   fields it could not supply that are STILL blank. Uses the same theme
   tokens as .viewing-banner so contrast holds in both light and dark mode
   (no hardcoded colors -- an earlier hardcoded blue washed out in dark). */
.import-warnings-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--color-primary-highlight);
  border: 1px solid var(--color-primary);
  border-left-width: 4px;
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text);
  margin-bottom: var(--space-4);
}
.import-warnings-banner[hidden] {
  display: none;
}
.import-warnings-banner-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  font-size: 12px;
  font-weight: 700;
  font-style: italic;
  line-height: 1;
}
.import-warnings-banner-body {
  flex: 1 1 auto;
  min-width: 0;
}
.import-warnings-banner-title {
  margin: 0;
  font-weight: 600;
}
.import-warnings-banner-list {
  margin: var(--space-2) 0 0 0;
  padding-left: 20px;
}
.import-warnings-banner-list li {
  margin: 2px 0;
}
.import-warnings-banner-note {
  margin: var(--space-2) 0 0 0;
  opacity: 0.85;
}
.import-warnings-banner-note[hidden] {
  display: none;
}
/* #145: the explained annual-taxes prompt. Sits between the field bullets and
   the informational note, styled as body copy (not the muted note) because it
   is instructional, not an aside. */
.import-warnings-banner-tax-note {
  margin: var(--space-2) 0 0 0;
  line-height: 1.45;
}
.import-warnings-banner-tax-note[hidden] {
  display: none;
}
.import-warnings-banner .action-btn {
  flex-shrink: 0;
}

/* #297: read-only Zillow source_facts reference panel. Deliberately styled as
   neutral reference material (muted border, no primary-color accent bar) so it
   never reads like an actionable banner -- unlike the import-warnings banner
   above it, there is nothing here for the user to fix or fill in. */
.source-facts-panel {
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text);
  margin-bottom: var(--space-4);
}
.source-facts-panel[hidden] {
  display: none;
}
.source-facts-panel-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.source-facts-panel-title {
  margin: 0 0 var(--space-2) 0;
  font-weight: 600;
}
.source-facts-panel-toggle {
  flex-shrink: 0;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--color-text);
  cursor: pointer;
  opacity: 0.8;
}
.source-facts-panel-toggle:hover {
  opacity: 1;
}
.source-facts-panel-body[hidden] {
  display: none;
}
.source-facts-panel-badge {
  margin-left: var(--space-2);
  font-weight: 400;
  font-size: var(--text-xs);
  opacity: 0.7;
  text-transform: none;
}
.source-facts-panel-section {
  margin-top: var(--space-2);
}
.source-facts-panel-section[hidden] {
  display: none;
}
.source-facts-panel-heading {
  margin: 0 0 2px 0;
  font-weight: 600;
  opacity: 0.85;
}
.source-facts-panel-list {
  margin: 0;
  padding-left: 20px;
}
.source-facts-panel-list li {
  margin: 2px 0;
}
.source-facts-panel-rent-zestimate {
  margin: var(--space-2) 0 0 0;
  opacity: 0.85;
}
.source-facts-panel-rent-zestimate[hidden] {
  display: none;
}
.source-facts-panel-more {
  margin-top: var(--space-2);
}
.source-facts-panel-more[hidden] {
  display: none;
}
.source-facts-panel-more summary {
  cursor: pointer;
  font-weight: 600;
  opacity: 0.85;
}

/* ================= Save & Saved-analyses modals ================= */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in oklab, var(--color-bg) 55%, transparent);
  padding: var(--space-6);
}
.modal-overlay[hidden] {
  display: none;
}
/* #1869: a shared confirmation can open while the Unit page temporarily
   renders the New Visit form in #showingsModal. Both overlays are direct
   body children, so the shared .modal-overlay z-index (1000) previously
   left the later #showingsModal DOM node painting over this one. Raise the
   confirmation overlay root itself, rather than a card inside it, so every
   generic confirmation remains above an active app modal. */
/* #2726: same DOM-order bug #1869 fixed, one modal earlier.
   #assetRegisterAddItemModal (index.html ~3248) is also a plain
   .modal-overlay at the shared z-index (1000), and it sits BEFORE
   #showingsModal (~12110) in the DOM -- both are direct body children, so
   #showingsModal's later paint order put it on top and "+ Add an item
   found on site" opened a dialog that rendered behind the visit form and
   looked dead. Raise its overlay root the same way #1869 raised
   #genericConfirmModal's, without restructuring either overlay or moving
   any DOM nodes. */
#assetRegisterAddItemModal {
  z-index: 1001;
}
/* #2726: a confirm dialog can still open ON TOP of the add-item modal (a
   confirm inside the add-item flow), so #genericConfirmModal must be able
   to paint above #assetRegisterAddItemModal's new 1001, not just above the
   shared 1000 baseline #1869 originally raised it past. */
#genericConfirmModal {
  z-index: 1002;
}
/* #5653: same DOM-order bug as #1869/#2726, this time on the shared
   in-page prompt modal (#2442's window.prompt() replacement). Several call
   sites open #genericPromptModal from a click handler INSIDE an
   already-open app modal -- e.g. "Edit" on a saved Deal Scenario (from
   #scenariosModal), "+ Add" a checklist step (from #sellTemplateModal), or
   editing a late-fee override (from #lateFeeOverrideModal) -- and all three
   of those host modals sit LATER than #genericPromptModal in index.html.
   Tied at the shared z-index: 1000 baseline, later DOM order wins the
   paint, so the prompt opens and populates correctly (confirmed live: Tab
   lands focus in its first field with the right pre-filled value) but
   renders completely hidden behind the still-open host modal -- indistin-
   guishable from the click doing nothing. Raise it the same way
   #assetRegisterAddItemModal was raised, to 1001, so it clears every plain
   baseline-1000 host modal; keep it below #genericConfirmModal's 1002 so a
   confirm dialog opened from within an open prompt (if any call site ever
   does that) still paints on top of the prompt, same relative order as
   #2726 established between those two. */
#genericPromptModal {
  z-index: 1001;
}
/* #6647: the custom Building System/Appliance type dialogs can be opened
   from the Maintenance Interval modal. Both overlays are otherwise at the
   shared 1000 layer, so DOM order leaves the parent interval modal painted
   above the newly-opened child and swallows every click. Raise both catalog
   dialogs as a pair because the same nested create flow exists for either
   category. */
#customSystemTypeModal,
#customApplianceTypeModal {
  z-index: 1001;
}
.modal-card {
  width: 100%;
  max-width: 26rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
  max-height: 85vh;
  overflow-y: auto;
}
.modal-card-wide {
  max-width: 46rem;
}
/* #6630: the batch payments drawer's rows table (tenant, owed, amount,
   method, reference, applies-to, remove) needs more width than the
   standard wide modal gives a form -- reuses .modal-overlay/.modal-card
   rather than a new drawer implementation, just wider. */
.modal-card-drawer {
  max-width: 64rem;
  width: 100%;
}

/* #4465: mobile record sheet for the asset register's record modals
   (currently just #assetRegisterAddItemModal). Scoped with :has() to the
   overlay/card pair that opts in via .asset-modal-sheet, so no other
   .modal-overlay/.modal-card in the app is affected. Tony's decision
   2026-08-29: keep a SMALL INSET WITH ROUNDED CORNERS, not a full-bleed
   squared-off sheet -- it must still read as a dialog sitting over the list,
   not a second screen. Do not "simplify" --radius-lg to 0 later; that is a
   deliberate re-affirmation below, not a leftover.
   Measured at 375px (iPhone SE-class, the narrowest real device this app
   supports): the old .modal-overlay padding (var(--space-6), 24px each
   side) nested inside the old .modal-card padding (also var(--space-6),
   24px each side) left only 277px of usable form width -- 26% of the
   screen's 375px spent on padding alone. Overlay var(--space-2) (8px) +
   card var(--space-4) (16px) gives 327px instead. Before this fix, Save sat
   1663px down a 688px scroll window because max-height: 85vh left the
   header/body/footer all fighting the same scroller; the head/body/foot
   split below (A2) plus max-height: calc(100dvh - var(--space-4)) here
   fixes that by making .asset-modal-sheet-body the ONLY scroller. */
/* #4468: box-sizing: content-box + width: 100% adds border/padding to the
   container width instead of absorbing it, so any 100%-wide control in this
   sheet is always wider than its box. #4465 found this at 375px (offsetWidth
   351 in a 325px body) and scoped the fix to max-width: 640px only, since
   above that width the record card (420px) is wide enough that the same
   arithmetic reads as a slightly clipped input rather than a scrolling form.
   Tony's decision 2026-08-29 (#4468): fix it unconditionally instead -- one
   rule, not two, and correct sizing at every width, accepting that every
   control in this record form is ~26px narrower on desktop than before. */
.asset-modal-sheet input,
.asset-modal-sheet select,
.asset-modal-sheet textarea {
  box-sizing: border-box;
  max-width: 100%;
}
@media (max-width: 640px) {
  .modal-overlay:has(> .asset-modal-sheet) {
    padding: var(--space-2);
  }
  /* .asset-add-item-card.asset-modal-sheet, not a bare .asset-modal-sheet:
     .asset-add-item-card-history (~line 6423) is .asset-add-item-card.
     asset-add-item-card-history at specificity (0,2,0) and applies at every
     width, so a bare .asset-modal-sheet at (0,1,0) would lose to its
     max-width: 760px on mobile whenever a history-mode item is open.
     Matching its specificity and relying on this rule coming later in the
     file to win the tie (checked, not assumed) keeps this max-width: none
     in force at every width instead of only when history mode is off. */
  .asset-add-item-card.asset-modal-sheet {
    padding: var(--space-4);
    max-width: none;
    width: 100%;
    /* Explicitly kept per Tony's 2026-08-29 decision -- see comment above. */
    border-radius: var(--radius-lg);
    max-height: calc(100dvh - var(--space-4));
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
}

/* #4465: head/body/foot split for .asset-modal-sheet cards. Above 640px
   these three are just plain flow children of .modal-card/.modal-header/
   .modal-body -- zero extra margin/padding here so desktop stays pixel-
   identical to before this split (verified: .modal-header and .modal-body
   already carried their own spacing; .asset-modal-sheet-foot reuses the
   pre-existing .form-row that held Cancel/Save, so it is unchanged above
   640px too). Only inside the media query do head/foot become sticky and
   body become the sole scroller. */
.asset-modal-sheet-body {
  min-width: 0;
}
.asset-modal-sheet-foot {
  /* flex-shrink: 0 has no visual effect outside a flex context, and
     .asset-modal-sheet only becomes display:flex inside the media query
     below (see A1) -- declaring it here unconditionally, per spec wording,
     is inert on desktop rather than a second desktop-only behavior change. */
  flex-shrink: 0;
}
@media (max-width: 640px) {
  .asset-modal-sheet-head {
    flex-shrink: 0;
    position: sticky;
    top: 0;
    background: var(--color-surface);
    z-index: 1;
  }
  .asset-modal-sheet-body {
    flex: 1;
    overflow-y: auto;
  }
  .asset-modal-sheet-foot {
    display: flex;
    position: sticky;
    bottom: 0;
    background: var(--color-surface);
    /* Only drawn on mobile: it separates the sticky foot from body content
       scrolled up underneath it. Desktop has no scrolling body to separate
       from, so adding it unconditionally would be a visible desktop change
       .form-row never had -- kept inside the media query per the "visually
       identical above 640px" requirement. */
    border-top: 1px solid var(--color-border);
    /* Buttons wrap to a second row instead of shrinking below the 44px tap
       target -- five buttons (Cancel/Remove/Replace/Inspection/Save) no
       longer fit one row at 327px of usable width. */
    flex-wrap: wrap;
  }
  .asset-modal-sheet-foot .action-btn,
  .asset-modal-sheet-foot .btn-export {
    min-height: 44px;
  }
}
/* #5067: the mobile units-picker bottom sheet. Reuses .modal-overlay/
   .asset-modal-sheet chrome (the closest existing full-screen-sheet
   convention -- see #4465 above) but, unlike the asset register's
   deliberate SMALL INSET WITH ROUNDED CORNERS, this one is a genuine
   full-screen sheet per the wireframe: no inset, no rounded corners,
   header/body split with the header pinned and the body (search + floor
   chips + results list) filling the rest of the viewport. Desktop
   (>640px) never shows this -- openShowingScopeUnitSheet() is only called
   from the matchMedia(max-width:640px) branch, so no desktop-visible rule
   is needed outside this media query. */
@media (max-width: 640px) {
  .modal-overlay.scope-unit-sheet-overlay {
    padding: 0;
  }
  .scope-unit-sheet.asset-modal-sheet {
    max-width: none;
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .scope-unit-sheet-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--color-border);
  }
  .scope-unit-sheet-title {
    flex: 1;
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 650;
    text-align: center;
  }
  .scope-unit-sheet-close {
    /* 44px-minimum tap target per the wireframe, same as the results rows
       below -- this is a small glyph button that would otherwise sit well
       under that floor. */
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    color: var(--color-text);
  }
  .scope-unit-sheet-done {
    min-height: 44px;
    white-space: nowrap;
  }
  .scope-unit-sheet-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-3);
  }
  /* Inside the sheet, .scope-unit-picker is a normal flow block (not the
     positioned popup it is on desktop) -- the search results list is
     always inline under the search input/chips, filling the sheet body,
     never portalled/fixed (renderShowingScopeUnitSearchResults skips
     positionShowingScopeUnitSearchResults at this breakpoint). */
  .scope-unit-sheet-body .scope-unit-picker {
    position: static;
  }
  .scope-unit-sheet-body .scope-unit-search-results {
    position: static;
    box-shadow: none;
    max-height: none;
  }
  /* 44px-minimum tap targets on result rows, per the wireframe. */
  .scope-unit-sheet-body .scope-unit-search-result {
    min-height: 44px;
    justify-content: center;
  }
  .scope-unit-sheet-body .scope-unit-floor-chip {
    min-height: 32px;
    display: inline-flex;
    align-items: center;
  }
}
.fanout-notice-section {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.fanout-notice-section:first-child {
  margin-top: 0;
}
.fanout-notice-section-heading {
  margin: 0 0 var(--space-3);
  color: var(--color-text);
  font-size: var(--text-base);
}
.fanout-notice-section > .field-note {
  margin: calc(-1 * var(--space-2)) 0 var(--space-3);
}
.fanout-notice-fields-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.fanout-notice-rules {
  display: grid;
  gap: 0;
  border-top: 1px solid var(--color-border);
}
.fanout-notice-rule-row {
  display: grid;
  grid-template-columns: minmax(10rem, 0.8fr) minmax(0, 1.2fr);
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.fanout-notice-rule-label {
  color: var(--color-text-muted);
  font-weight: 600;
}
.fanout-notice-rule-value {
  min-width: 0;
  overflow-wrap: anywhere;
}
.fanout-notice-rule-value .badge {
  margin-right: var(--space-1);
}
.fanout-notice-rule-citation {
  display: block;
  margin-top: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
@media (max-width: 640px) {
  .fanout-notice-fields-grid,
  .fanout-notice-rule-row {
    grid-template-columns: 1fr;
  }
  .fanout-notice-rule-row {
    gap: var(--space-1);
  }
}
#genericConfirmModalOptions {
  margin-top: var(--space-4);
}
#genericConfirmModalOptions[hidden] {
  display: none;
}
.generic-confirm-options {
  display: grid;
  gap: var(--space-2);
}
.generic-confirm-options .field-note {
  margin: 0 0 var(--space-1);
}
.generic-confirm-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--color-text);
  cursor: pointer;
}
.generic-confirm-option input[type="checkbox"] {
  margin: 0.2em 0 0;
  flex: 0 0 auto;
}
.generic-confirm-option:has(input:disabled) {
  color: var(--color-text-muted);
  cursor: not-allowed;
}
.fanout-notice-source-mode-option:has(input:disabled) {
  color: var(--color-text-muted);
  cursor: not-allowed;
}
/* #6540: belt-and-suspenders alongside the .ld-field input:not([type=radio]) fix --
   pins the radio to its intrinsic size even if another width rule ever reaches it
   again, so the label text stays immediately beside it instead of shoving across
   the row. */
.fanout-notice-source-mode-option input[type="radio"] {
  flex: 0 0 auto;
  width: auto;
}
.generic-confirm-option-note {
  display: block;
  grid-column: 2;
  margin-top: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
/* #6029: the list of records a cascade delete will also remove, rendered
   into #genericConfirmModalOptions by txCascadePreviewHtml(). Scrolls rather
   than growing the modal past the viewport -- a cascade is usually 1-3 rows,
   but an invoice with many payments must not push the Delete button off
   screen, which would leave the operator unable to either confirm or read
   what they were confirming. */
.txn-delete-cascade-list {
  margin: var(--space-1) 0 0;
  padding-left: var(--space-4);
  max-height: 11rem;
  overflow-y: auto;
  color: var(--color-text);
  font-size: var(--text-sm);
}
.txn-delete-cascade-list li + li {
  margin-top: var(--space-1);
}
/* #659: the bundle builder uses its own modal wrapper, so the utility class cannot reach it. */
#bundleBuilderModal .modal-card {
  max-width: 46rem;
}

/* #717: editable merge-field review between choosing a document and sending it. */
.ld-send-picker-merge-fields {
  display: grid;
  gap: var(--space-3);
  max-width: 46rem;
  margin: var(--space-3) 0;
}
.ld-send-picker-merge-field {
  display: grid;
  gap: var(--space-1);
}
.ld-send-picker-merge-field input {
  width: 100%;
}

/* #1703: review step -- "Goes to" / "Not included" signer lists, and the
   "Bundle components" optional-inclusion checkboxes. Same 46rem measure as
   the merge fields above so the whole review step reads as one column. */
.ld-send-picker-review-signers {
  display: grid;
  gap: var(--space-3);
  max-width: 46rem;
  margin: var(--space-3) 0;
}
.ld-review-signers-group h4 {
  margin: 0 0 var(--space-1);
  font-size: 0.9rem;
  color: var(--color-text-muted);
}
.ld-review-signers-group ul {
  margin: 0;
  padding-left: 1.25rem;
}
.ld-review-signers-excluded {
  color: var(--color-text-muted);
}
.ld-send-picker-review-components {
  max-width: 46rem;
  margin: var(--space-3) 0;
}
.ld-send-picker-review-components h4 {
  margin: 0 0 var(--space-1);
  font-size: 0.9rem;
}
.ld-send-picker-review-components ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-1);
}
.ld-send-picker-review-components li label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* #6299: "Also include (not signed)" on the send review step. Deliberately
   quiet when empty -- heading plus one button, no box, no empty-state row --
   because the overwhelming majority of sends attach nothing and this must
   never look like an unfinished step. The border and rows only appear once
   something is actually attached. */
.ld-send-picker-attachments {
  max-width: 46rem;
  margin: var(--space-3) 0;
}
.ld-send-picker-attachments-heading {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-1);
  font-size: 0.9rem;
  font-weight: 600;
}
.ld-send-picker-attachments-list:not(:empty) {
  display: grid;
  gap: var(--space-1);
  margin: var(--space-2) 0;
}
.ld-send-picker-attachment-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.ld-send-picker-attachment-info {
  min-width: 0;
}
.ld-send-picker-attachment-name {
  font-weight: 500;
  /* Filenames are user-supplied and routinely long; wrap rather than
     letting one push the status pill and Remove off the row. */
  overflow-wrap: anywhere;
}
.ld-send-picker-attachment-status {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

/* #1702: Step 2 of the bundle send flow -- one section per document,
   grouped fields, and the shared utility table (wireframe pages 5-6). */
.ld-send-picker-fields-docs {
  display: grid;
  gap: var(--space-4);
  max-width: 46rem;
  margin: var(--space-3) 0;
}
.ld-bundle-fields-doc {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--color-surface-offset);
}
.ld-bundle-fields-doc-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.ld-bundle-fields-doc-header h4 {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 650;
}
.ld-bundle-fields-count-badge {
  font-size: var(--text-xs);
  line-height: 1;
  padding: 3px 7px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
}
.ld-bundle-fields-group-table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-2) 0;
  font-size: var(--text-sm);
}
.ld-bundle-fields-group-table th,
.ld-bundle-fields-group-table td {
  border: 1px solid var(--color-border);
  padding: var(--space-1) var(--space-2);
  text-align: center;
}
.ld-bundle-fields-group-table thead th {
  background: var(--color-surface-2);
  font-weight: 600;
  color: var(--color-text-muted);
}
/* #6544: groups checkbox_group merge fields sharing the same options array
   into one table (mirrors Step 2's own utility-fields grid), left-aligned
   row label + full-width select -- unlike the base rule above (centered,
   built for a per-option radio cell), this table has only one dropdown
   column so a centered, shrink-to-fit select would look broken. Compound
   selector for specificity over the base rule regardless of source order. */
.ld-bundle-fields-group-table.ld-send-picker-merge-field-grid td {
  text-align: left;
}
.ld-bundle-fields-group-table.ld-send-picker-merge-field-grid select.ld-send-picker-merge-field-input {
  width: 100%;
}
.ld-bundle-fields-group-table th[scope="row"] {
  text-align: left;
  font-weight: normal;
  background: var(--color-surface-2);
}
.ld-bundle-field-row {
  display: grid;
  gap: var(--space-1);
  margin: var(--space-2) 0;
}
.ld-bundle-field-row span {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.ld-bundle-field-checkbox-row {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: var(--space-2);
}
.ld-bundle-field-checkbox-row span {
  color: var(--color-text);
}
.ld-bundle-field-input {
  width: 100%;
}
.ld-bundle-field-textarea {
  min-height: 4.5em;
  resize: vertical;
}
.ld-bundle-field-short {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.ld-bundle-field-prefix {
  color: var(--color-text-muted);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.modal-header h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 650;
  letter-spacing: -0.01em;
}
.modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  color: var(--color-text-muted);
}
/* #4423: .modal-close's plain class selector ties the UA [hidden]{display:none}
   rule on specificity, and author styles always win that tie regardless of
   source order -- so toggling the `hidden` attribute on a .modal-close button
   (the unit/tenant record popups' lease-view back arrows) left it visibly
   showing and clickable. Buttons that are only ever visible (X close buttons)
   never surfaced this because they're never given the `hidden` attribute. */
.modal-close[hidden] {
  display: none;
}
.modal-close:hover {
  color: var(--color-text);
  background: var(--color-surface-2);
}
.modal-close svg {
  width: 16px;
  height: 16px;
}
.modal-note {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin-bottom: var(--space-3);
}
/* #5194: prospect detail save/delete failures (including the delete-blocked-by-
   linked-application 409) were rendering as plain .modal-note gray text,
   indistinguishable from ordinary field hints -- easy to miss entirely, which is
   what made "clicking delete never deletes it" read as a silent failure rather
   than a clearly refused action. Same treatment as .tenant-delete-modal-error. */
.prospect-detail-save-error {
  color: var(--color-error);
  font-weight: 600;
}
/* #2558: prior payments already recorded against this bill, shown in the
   mark-paid modal above the submit note so a second partial payment is made
   with the running history visible rather than blind. */
.vendor-bill-payment-history {
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border-radius: var(--radius-sm);
}
.vendor-bill-payment-history-list {
  margin: 0;
  padding-left: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
/* ID 276: "pre-filled from the accepted contract" note in the I-bought-it
   modal. Reads as a callout rather than fine print so the user notices the
   close price/date were filled in for them. */
.modal-note.uc-buy-prefill-note {
  color: var(--color-text);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
.share-link-row {
  display: flex;
  gap: var(--space-2);
}
.share-link-row input {
  flex: 1;
  min-width: 0;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
}
.share-link-row .btn-export {
  flex-shrink: 0;
}
.quick-links-card {
  padding: var(--space-4) var(--space-5);
}
.quick-links-card h2,
.quick-links-card h3 {
  margin: var(--space-1) 0 var(--space-3);
}
.quick-links-list {
  display: grid;
  gap: var(--space-2);
}
.quick-links-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px dashed var(--color-border);
}
.quick-links-row > div {
  min-width: 0;
}
.quick-links-label {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.quick-links-url {
  display: block;
  color: var(--color-primary);
  font-size: var(--text-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.quick-links-copy {
  flex: 0 0 auto;
}
.quick-links-unpublished {
  margin: 0;
  padding: var(--space-2) 0;
}
.quick-links-status {
  margin: var(--space-2) 0 0;
}
@media (max-width: 640px) {
  .quick-links-row {
    align-items: flex-start;
  }
}
.public-share-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
  flex-wrap: wrap;
}
.public-share-actions .btn-export {
  text-decoration: none;
}
.public-share-sms-row {
  align-items: center;
}
/* .lock-input is sized for the full-width sign-in card; inside this flex row
   its intrinsic input width would push the send button onto its own line. */
.public-share-sms-row .lock-input {
  flex: 1 1 10rem;
  width: auto;
  min-width: 0;
  margin-bottom: 0;
}

.share-with-users-panel {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
/* #3160: Users/Groups picker sections inside the Property Managers and
   Maintenance modals -- reuses .share-with-users-list for each section's
   scroll box, just with a small heading above each one. */
.modal-picker-section + .modal-picker-section {
  margin-top: var(--space-3);
}
.modal-picker-section-heading {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-1);
}
.share-with-users-list {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  max-height: 10rem;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  background: var(--color-surface-2);
}
.share-with-users-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-sm);
  padding: 0.3rem 0;
}
.share-with-users-row label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.share-with-users-row .share-with-users-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.share-with-users-row .share-with-users-pref-badge {
  flex-shrink: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.share-with-users-empty {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  padding: 0.3rem 0;
}
.share-with-users-result-sent {
  color: var(--color-success, #1a7f37);
}
.share-with-users-result-failed {
  color: var(--color-error-text);
  font-weight: 600;
}
.share-with-users-result-failed-heading {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-top: 0.35rem;
}
.share-with-users-result-failed-list {
  list-style: none;
  margin: 0.2rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.share-with-users-result-failed-item {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: baseline;
  font-size: var(--text-sm);
}
.share-with-users-result-reason {
  color: var(--color-text-muted);
}

/* Read-only public share view (?public=<token>) — hide anything that
   requires sign-in or edits access, leave the calculator readable. */
body.public-view #saveAnalysisBtn,
body.public-view #pullRemoteDataBtn,
body.public-view #duplicateAnalysisBtn,
body.public-view #shareAnalysisBtn,
body.public-view #statusCard,
body.public-view #dataFeedSyncCard,
body.public-view #recordStatusGroup,
body.public-view #propertiesNavGroup,
body.public-view #reportsNavGroup,
body.public-view #settingsNavGroup,
body.public-view #profileNavGroup,
body.public-view #viewingBannerDismiss,
body.public-view #landing-main,
body.public-view #saved-main,
body.public-view #search-watch-main,
body.public-view #comparePickerRow,
body.public-view #compareSaveBtn,
body.public-view #compareShareBtn,
body.public-view #compareBackBtn,
body.public-view #dashboard-main,
body.public-view #map-main,
body.public-view #saved-compares-main,
body.public-view #unitModeToggleRow,
body.public-view #unitsFieldset {
  display: none !important;
}
body.public-view .calculator button:disabled,
body.public-view .calculator input:disabled,
body.public-view .calculator select:disabled,
body.public-view .calculator textarea:disabled {
  opacity: 0.75;
  cursor: not-allowed;
}
.saved-open-link {
  font-weight: 600;
  color: var(--color-primary);
}
.dashboard-property-link {
  font-weight: 600;
  color: var(--color-primary);
  text-decoration: none;
}
.dashboard-property-link:hover {
  text-decoration: underline;
}
.saved-delete-btn {
  color: var(--color-error);
}

/* ================= Saved analyses page (back link, bulk actions, pagination) ================= */

.saved-back-link {
  display: inline-block;
  margin-bottom: var(--space-3);
}
/* Runtime templates outside index.html still emit this legacy modifier. */
.link-btn-danger {
  color: var(--color-error);
}
.link-btn-danger:hover {
  color: var(--color-error);
  opacity: 0.75;
}
.bulk-action-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  background: var(--color-primary-highlight);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
}
.bulk-action-count {
  font-weight: 600;
  color: var(--color-text);
  flex-shrink: 0;
}
.bulk-action-controls {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.bulk-action-controls select {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-family: inherit;
}
.saved-pagination,
.list-pagination-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  flex-wrap: wrap;
}
/* Pre-existing gap noticed while building the Properties Nav Restructure
   (113) empty-state views: display:flex above otherwise beats the bare
   [hidden] attribute, so the pagination bar stayed visible whenever a
   list rendered its empty state. */
.saved-pagination[hidden],
.list-pagination-bar[hidden] {
  display: none;
}
/* ID 265: the one place --color-text-faint legitimately colours text. WCAG 2.1
   1.4.3 exempts text that is part of an inactive user interface component, and
   a faded disabled pager link is exactly that -- the low contrast IS the
   affordance. Do not "fix" this to muted; it would read as enabled. */
.saved-pagination .action-btn:disabled,
.list-pagination-bar .action-btn:disabled {
  color: var(--color-text-muted);
  opacity: 0.5;
  cursor: default;
  text-decoration: none;
}
/* #4314: ListKit's render() now hides the Previous/Next buttons
   individually via the native hidden attribute on a single page (rather
   than hiding the whole .pagination-nav wrapper, which used to take the
   Showing X-Y of N count down with it -- see listkit.js render()). Same
   .action-btn display:inline-flex problem #1617 already found and fixed
   for .page-size-control below: a class selector setting a display value
   otherwise beats the browser's default hidden-attribute style. */
.pagination-nav .action-btn[hidden] {
  display: none;
}
/* Shared "rows per page" control -- reused on every unbounded table
   (Properties, Dashboard, Actuals, Search Watch jobs, Users). Positioned
   as its own group on one side of the pagination bar via margin-left:auto
   on the nav group next to it, so it doesn't fight the centered prev/next. */
/* #1617: ListKit marks controls unavailable for the current list with the
   native `hidden` attribute. A class selector setting display:flex otherwise
   wins over the browser's default hidden style, leaving a dead control visible. */
.page-size-control:not([hidden]) {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-muted);
}
.page-size-select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-family: inherit;
}
.pagination-nav:not([hidden]) {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
#savedListTable th:first-child,
#savedListTable td:first-child {
  width: 2rem;
  text-align: center;
}
.saved-row-checkbox,
#savedListSelectAll {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
  cursor: pointer;
}

/* ================= Status filter + status select ================= */

.saved-list-toolbar {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
}
.saved-list-toolbar label {
  color: var(--color-text-muted);
  font-weight: 600;
}
/* #3383: the Owned-only "Export to CSV" button moved into this toolbar out
   of the Properties > Owned nav dropdown. .action-btn is inline-flex but
   sets no gap and does not size child SVGs (the nav-dropdown-item rules did
   both), so the inline icon needs an explicit box here or it inherits the
   viewBox and blows the button out. */
#ownedExportCsvBtn {
  gap: var(--space-2);
}
#ownedExportCsvBtn svg {
  width: 1rem;
  height: 1rem;
  flex: none;
}
.saved-list-toolbar select,
.status-select {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-family: inherit;
}
.status-select {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.3rem 0.5rem;
}
.status-select-to_review {
  background: var(--color-surface);
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
}
.status-select-reviewing {
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  border-color: transparent;
}
.status-select-offer_submitted {
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  border-color: transparent;
}
/* ID 265: these three tones colour normal-sized text on a tinted background --
   the landing pipeline chips and the saved-list status selects -- so they use
   the *-text variants, which clear 4.5:1 on the tint. The plain --color-* tones
   measured 3.25:1 (accepted), 4.48:1 (lost) and 4.07:1 (closed) in light mode. */
.status-select-offer_accepted {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  border-color: transparent;
}
.status-select-offer_lost {
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  border-color: transparent;
}
.status-select-closed {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  border-color: transparent;
}
.status-select-not_interested {
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  border-color: transparent;
}

.saved-search-group {
  display: flex;
  align-items: center;
  flex: 1 1 260px;
  min-width: 220px;
  max-width: 360px;
}
.saved-search-input-wrap {
  position: relative;
  width: 100%;
}
.saved-search-icon {
  position: absolute;
  left: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  width: 1rem;
  height: 1rem;
  color: var(--color-text-muted);
  pointer-events: none;
}
.saved-search-input {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.45rem 3.5rem 0.45rem var(--space-8);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
}
.saved-search-input:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.saved-search-input-wrap > .action-btn {
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  width: 2.75rem;
  min-width: 2.75rem;
  padding: var(--space-2);
  font-size: 1.25rem;
  line-height: 1;
}

.status-filter-group {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/* Properties Nav Restructure (113): the analyze-status checkbox filter is
   hidden on Owned Properties -- without this, [hidden] alone can't win
   against the display:flex rule above. */
.status-filter-group[hidden] {
  display: none;
}
.status-filter-label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  flex-shrink: 0;
}
.status-filter-checkboxes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
}
.status-filter-checkboxes label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  white-space: nowrap;
}
.status-filter-checkboxes input[type='checkbox'] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
}
.basement-split-inputs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: var(--space-3);
}
.basement-split-inputs .field {
  margin-bottom: 0;
}

/* ================= System Defaults: Property / Tenant sub-tabs =================
   Property Defaults uses a deliberate three / one / three card rhythm. Each
   three-up row stretches its cards to the same height while keeping every
   existing field id/name and submit handler intact. */
#defaults-main {
  max-width: clamp(var(--content-wide), 92vw, 1680px);
}
.defaults-section {
  margin-top: var(--space-6);
}
.defaults-section-heading {
  margin: 0 0 var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.defaults-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-5);
  align-items: stretch;
}
.defaults-grid .settings-card {
  margin-bottom: 0;
  height: 100%;
}
.tenant-defaults-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5);
  align-items: stretch;
}
.tenant-defaults-grid .settings-card {
  height: 100%;
  margin-bottom: 0;
}
.tenant-defaults-ledger-link {
  margin-top: var(--space-5);
}
@media (max-width: 1180px) {
  .defaults-grid,
  .tenant-defaults-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 720px) {
  .defaults-grid,
  .tenant-defaults-grid {
    grid-template-columns: 1fr;
  }
}
.settings-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
  margin-bottom: var(--space-5);
}

/* #2743: intake submission detail/convert two-column layout -- a left
   read-only detail-list of submitted answers, a right stack of action
   cards (match, tenant, lease, ACH, buttons). Stacks to one column below
   the same breakpoint listkit.css already uses for narrow layouts. */
.intake-detail-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
  gap: var(--space-5);
  align-items: start;
}
@media (max-width: 900px) {
  .intake-detail-grid {
    grid-template-columns: 1fr;
  }
}
.intake-detail-actions .settings-card:last-child {
  margin-bottom: 0;
}
.detail-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0;
}
.detail-list dt {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}
.detail-list dd {
  margin: 0;
  font-size: var(--text-sm);
  word-break: break-word;
}
.detail-list dd .file-chip {
  display: inline-block;
  margin: 0 var(--space-2) var(--space-1) 0;
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 4px);
  font-size: var(--text-xs, 0.75rem);
  font-style: normal;
}
.intake-convert-field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
@media (max-width: 480px) {
  .intake-convert-field-row {
    grid-template-columns: 1fr;
  }
}
.settings-card .field-group {
  margin-bottom: 0;
}
/* #516: a checkbox paired with a long wrapping sentence (e.g. the Unit Info
   source card's two org-level toggles) has no home in the plain field-group
   label styles above -- those assume either a short nowrap label or a
   for=/input pair on separate lines. Stacks each checkbox+sentence as its
   own row with the checkbox pinned to the first line of text (not the
   wrapped block's vertical center) and a visible gap between rows, instead
   of the two labels running together with default inline rendering. */
.settings-checkbox-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.settings-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text);
  font-weight: 400;
}
.settings-checkbox-row input[type='checkbox'] {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  margin-top: 0.2rem;
  accent-color: var(--color-primary);
}
.settings-checkbox-row .help-toggle-btn {
  vertical-align: middle;
}

#tenant-providers-main,
#communications-main {
  max-width: clamp(var(--content-wide), 92vw, 1500px);
}
#maintenance-main {
  max-width: min(96vw, 1800px);
}
.provider-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-3);
}
.provider-card-header h2,
.provider-connect-form h3 {
  margin: 0;
}
.provider-eyebrow {
  margin: 0 0 var(--space-1);
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.provider-actions,
.provider-row-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.capability-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0;
}
.capability-chip {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 5px 10px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  font-size: 12px;
  font-weight: 700;
}
.capability-chip.is-supported {
  color: #17603a;
  border-color: #acd8bd;
  background: #edf8f1;
}
.provider-connect-form {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-divider);
}
/* #6815 -- the maintenance reply box's "Insert saved reply..." picker
   (top-right of the Reply label, per the wireframe) and the "Save new
   canned reply.../Send" row (bottom, split left/right). Both reuse
   existing controls (.maintenance-sort-select, .link-btn, .btn-export) --
   these rules are only the layout wrapper. The picker is positioned with
   plain absolute positioning against .maintenance-reply-form rather than
   any new markup wrapping .field/.notes-input-wrap -- #6706 fixed a real
   production bug (y: -4804) caused by breaking their immediate sibling
   adjacency, so this form's structure there stays untouched. */
.maintenance-reply-form {
  position: relative;
}
.maintenance-canned-reply-label {
  position: absolute;
  top: 0;
  right: 0;
}
.maintenance-canned-reply-label .maintenance-sort-select {
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
}
.maintenance-reply-actions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
/* ID 858: `.provider-templates-section` had no rule behind it at all, so the
   "Lease templates" heading sat flush against the `.provider-actions` button
   row above it -- measured 0px of separation on prod -- and read as a caption
   belonging to those buttons rather than as the start of a new block. Every
   other top-level block inside a provider card is a `.provider-connect-form`
   and picks up the separator above; the templates section is the one block
   that is a plain <div>, so it missed out. Give it the same separator, and
   note the h3 needs its own `margin`: the `.provider-connect-form h3` reset
   does not reach this heading either.
   The nested upload form then has to DROP its separator, otherwise the card
   draws two horizontal rules one paragraph apart. */
.provider-templates-section {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-divider);
}
.provider-templates-section > h3 {
  margin: 0 0 var(--space-2);
}
.provider-templates-section .provider-connect-form {
  margin-top: var(--space-4);
  padding-top: 0;
  border-top: 0;
}
/* #730: manual-invoice multi-line-item rows. Base .field-row is a fixed
   1fr/1fr 2-column grid built for label+input pairs; a line-item row is
   charge-type + description + amount + a remove button, so it needs its own
   column layout rather than wrapping 4 children into 2 columns. Amount gets
   a narrower fixed track since it never needs the space description does. */
.manual-invoice-line-item {
  grid-template-columns: minmax(140px, 1fr) minmax(160px, 2fr) 110px auto;
  align-items: end;
  margin-bottom: var(--space-3);
}
.manual-invoice-line-amount-field {
  min-width: 0;
}
.manual-invoice-line-remove {
  white-space: nowrap;
}
@media (max-width: 720px) {
  .manual-invoice-line-item {
    grid-template-columns: 1fr;
  }
}

/* #731: one row per active charge type in the Zoho setup-time mapping step.
   Checkbox says "I use this charge type"; the Item <select> only matters (and
   only enables) once checked -- mirrors the enable-on-check pattern already
   used by .communications-emergency-toggle above, applied to a 3-column row
   instead of a single toggle. */
.zoho-mapping-row {
  display: grid;
  grid-template-columns: minmax(160px, 1fr) minmax(200px, 2fr);
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  margin-bottom: var(--space-2);
}
.zoho-mapping-row-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
}
.zoho-mapping-row select:disabled {
  opacity: 0.5;
}
@media (max-width: 720px) {
  .zoho-mapping-row {
    grid-template-columns: 1fr;
  }
}
.zoho-mapping-error {
  color: var(--color-error-text);
}

.communications-fieldset {
  margin: var(--space-5) 0;
  padding: 0;
  border: 0;
}
.communications-fieldset legend,
.communications-check-grid + .field-note {
  font-weight: 700;
}
.communications-channel-grid,
.communications-check-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.communications-channel-grid label,
.communications-check-grid label,
.communications-emergency-toggle {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.communications-channel-grid input,
.communications-check-grid input,
.communications-emergency-toggle input {
  flex: 0 0 auto;
  margin-top: 2px;
}
.communications-emergency-toggle {
  margin: var(--space-5) 0;
  border-color: var(--color-warning);
  background: var(--color-warning-highlight);
}
.communications-emergency-toggle span {
  display: grid;
  gap: var(--space-1);
}
.communications-emergency-toggle small {
  color: var(--color-text-muted);
}
.communications-history-table td {
  vertical-align: top;
}
.communications-history-table td:last-child {
  min-width: 14rem;
}
@media (max-width: 900px) {
  .communications-channel-grid,
  .communications-check-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 560px) {
  .communications-channel-grid,
  .communications-check-grid {
    grid-template-columns: 1fr;
  }
}
.provider-invoices-table td:last-child,
.provider-invoices-table th:last-child {
  text-align: left;
  min-width: 9rem;
}
.invoice-provider-badge {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-1);
  padding: 1px 6px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.45;
  vertical-align: middle;
  white-space: nowrap;
}
.invoice-provider-badge.is-zoho-books {
  color: #0a5d80;
  border-color: #a6d6e8;
  background: #eef9fd;
}
.invoice-provider-badge.is-return-grid {
  color: #17603a;
  border-color: #acd8bd;
  background: #edf8f1;
}
.late-fee-policy-warning {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  color: var(--color-warning-text);
  background: var(--color-warning-highlight);
}
.late-fee-adjustments {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
  color: var(--color-warning);
}
.late-fee-ledger {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-divider);
}
.late-fee-waived {
  color: var(--color-text-muted);
  text-decoration: line-through;
}
#lateFeePolicyForm .check-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
}
#lateFeePolicyForm .check-label input {
  width: 18px;
  height: 18px;
}
.maintenance-title-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
}
.maintenance-title-row h1,
.maintenance-title-row p {
  margin-top: 0;
}
.maintenance-provider-note {
  margin-bottom: var(--space-4);
}
.maintenance-board {
  display: grid;
  grid-template-columns: repeat(6, minmax(220px, 1fr));
  gap: var(--space-3);
  overflow-x: auto;
  padding-bottom: var(--space-3);
}
.maintenance-column {
  min-height: 280px;
  padding: var(--space-3);
  background: var(--color-surface-offset);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
/* #358: the New column is the unread inbox -- everything a tenant sent that no
   owner has answered yet, including email replies that arrived overnight. It
   gets an accent border so it reads as the place to start rather than as one
   more equal-weight lane. Border only, deliberately: the surface and text
   colors stay on the shared tokens the WCAG contrast suite already covers. */
.maintenance-column-new {
  border-color: var(--color-primary);
}
.maintenance-column > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.maintenance-column h2 {
  margin: 0;
  font-size: var(--text-sm);
}
.maintenance-column header span {
  min-width: 1.75rem;
  padding: 2px 7px;
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font: 700 var(--text-xs) / 1.5 var(--font-mono);
  text-align: center;
}
.maintenance-column-list {
  display: grid;
  gap: var(--space-2);
}
.owner-maintenance-card {
  display: grid;
  gap: 5px;
  width: 100%;
  padding: var(--space-3);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-primary);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition-interactive), transform var(--transition-interactive), box-shadow var(--transition-interactive);
}
.owner-maintenance-card:hover,
.owner-maintenance-card:focus-visible {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
/* #6707: drag-and-drop between Kanban columns. The card being dragged
   dims in place (the browser already renders its own drag image); the
   column currently under the pointer gets a dashed highlight so a drop
   target is unambiguous. cursor:grab hints the affordance is there even
   before the first drag starts. */
.owner-maintenance-card[draggable="true"] { cursor: grab; }
.owner-maintenance-card.is-dragging { opacity: 0.4; cursor: grabbing; }
.maintenance-column-list.is-drop-target {
  outline: 2px dashed var(--color-primary);
  outline-offset: -2px;
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.owner-maintenance-card.priority-urgent { border-left-color: var(--color-error); }
.owner-maintenance-card.priority-high { border-left-color: var(--color-warning); }
/* #4295: a New ticket sitting past the org's maintenance response SLA
   (default 24h, overridable under Settings > Defaults > Maintenance Hours).
   Red left border + an SLA badge make aging tickets impossible to miss. */
.owner-maintenance-card.sla-breaching {
  border-left-color: var(--color-error);
  background: var(--color-error-highlight);
}
.owner-maintenance-sla-badge {
  display: inline-block;
  padding: 1px 6px;
  margin-bottom: 2px;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* #6315: auto-escalated overdue-maintenance ticket. Deliberately quieter
   than the SLA badge -- it is provenance, not a warning. */
.owner-maintenance-auto-badge {
  display: inline-block;
  padding: 1px 6px;
  margin-bottom: 2px;
  border-radius: var(--radius-full, 9999px);
  /* Real tokens, matching .phase-badge. The three names originally used here
     (--color-surface-secondary, --color-text-secondary, --color-border-primary)
     are not defined anywhere in this file, so each silently fell back to its
     hardcoded literal and the badge kept light-mode colours in dark mode --
     a near-invisible grey-on-dark, which is what tests/contrast.unit.js caught. */
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* #6816: possible-duplicate flag, same badge slot/convention as SLA/Auto
   above -- amber/warning-ish tone since this is a soft heads-up ("go take a
   look"), not an error like the SLA badge nor pure provenance like Auto. */
.owner-maintenance-dup-badge {
  display: inline-block;
  padding: 1px 6px;
  margin-bottom: 2px;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* #6816: "\u2192 merged into #4471" -- small dim suffix after a merged
   (losing) ticket's subject on the List view. */
.owner-maintenance-merged-into {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  margin-left: 4px;
}
.owner-maintenance-age { font-variant-numeric: tabular-nums; }
/* #4796: bare 6-digit ticket number, styled as a quiet label above the
   subject -- distinct from the SLA badge (which is a warning), not a
   status/priority indicator. */
.owner-maintenance-number {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}
.owner-maintenance-detail-number {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}
/* #6741 -- Watchers. Outline star by default; .is-watching switches to the
   filled glyph + accent color, matching the wireframe's filled/outline
   convention. */
.owner-maintenance-watch-star {
  background: none;
  border: none;
  padding: 0;
  margin-right: 2px;
  font-size: 1em;
  line-height: 1;
  cursor: pointer;
  color: var(--color-text-muted);
  vertical-align: -0.05em;
}
.owner-maintenance-watch-star.is-watching {
  color: var(--color-primary);
}
.owner-maintenance-watch-star:disabled {
  cursor: default;
  opacity: 0.6;
}
.owner-maintenance-watchers-line {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin: 4px 0 0;
}
.owner-maintenance-watchers-line strong {
  color: var(--color-text);
  font-weight: 500;
}
.owner-maintenance-watcher-pill {
  display: inline-block;
  background: var(--color-surface-2);
  border-radius: 999px;
  padding: 1px 8px;
  margin-right: 4px;
  font-size: var(--text-xs);
}
.owner-maintenance-card > span,
.owner-maintenance-card > time,
.maintenance-column-empty {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.maintenance-column-empty {
  margin: var(--space-4) 0;
  text-align: center;
}
/* #1558: search + priority filter + per-column paging controls above the board */
.maintenance-board-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
/* #6707: Kanban column visibility picker -- reuses .nav-dropdown-menu's
   popover shape (position/border/shadow), just anchored to this button
   instead of a nav-bar toggle. */
.maintenance-columns-picker { position: relative; display: inline-flex; }
.maintenance-columns-menu { min-width: 11rem; gap: var(--space-1); }
.maintenance-columns-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  cursor: pointer;
}
.maintenance-columns-menu-item:hover { background: var(--color-surface-2); }
/* #6798: the keyboard shortcuts menu is a sibling of the Columns picker,
   so it uses the same anchored popover shape without coupling its content to
   the column-checkbox styles. */
.maintenance-shortcuts-help { position: relative; display: inline-flex; }
.maintenance-shortcuts-menu { min-width: 20rem; }
.maintenance-shortcuts-title { margin: 0 0 var(--space-1); font-weight: 700; }
.maintenance-shortcuts-menu ul { margin: 0; padding-left: var(--space-5); }
.maintenance-shortcuts-menu li { margin: var(--space-1) 0; }
.maintenance-shortcuts-menu kbd {
  padding: 0.1rem 0.3rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  font-size: var(--text-xs);
}
/* #4295: sort dropdown inside the board controls (newest/oldest within each column). */
.maintenance-sort-label { display: inline-flex; align-items: center; }
.maintenance-sort-select {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  cursor: pointer;
}
.maintenance-search-input {
  flex: 1 1 240px;
  min-width: 180px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font: inherit;
}
.maintenance-search-input:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.maintenance-priority-filters {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
}
/* #6754: sits in the same controls bar as the priority filter chips and
   search input, so it takes the same compact field sizing rather than the
   larger default <select> used in a standalone form. */
.maintenance-tag-filter-field select {
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
}
.maintenance-priority-filter {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.maintenance-priority-filter:hover {
  background: var(--color-surface-offset);
}
.maintenance-priority-filter.is-active {
  background: var(--color-primary);
  color: var(--color-surface);
  border-color: var(--color-primary);
}
/* #5856: the 7th, visually distinct chip -- dashed border per #5843's
   original wireframe, marking it as orthogonal to the 6 solid-border status
   chips (a sub-view switch, not a narrowing toggle within the current view). */
.maintenance-archived-filter {
  border-style: dashed;
}
.maintenance-archived-filter.is-active {
  background: var(--color-surface-offset);
  color: var(--color-text);
  border-color: var(--color-border);
  border-style: dashed;
}
.maintenance-archived-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.maintenance-archived-banner p {
  margin: 0;
}
/* #5856: same visual treatment as .link-btn (background: none, underlined
   text button) but its own class -- this is a standalone banner action, not
   inline prose text, so tests/linkButtonAffordance.unit.js's inline-context
   allowlist deliberately does not cover it. */
.maintenance-archived-back-link {
  background: none;
  border: none;
  padding: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-decoration: underline;
  cursor: pointer;
  flex-shrink: 0;
}
/* #6816 -- possible-duplicate callout on the detail panel, right after the
   header/tenant-unit lines and before Tags. Same box shape as
   .maintenance-archived-banner (padded, bordered, rounded) but solid amber
   -- a heads-up worth a beat of attention, unlike the archived banner's
   purely informational dashed-border treatment. */
.owner-maintenance-dup-callout {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  font-size: var(--text-sm);
  margin: var(--space-3) 0;
}
.owner-maintenance-dup-callout p {
  margin: 0;
}
.owner-maintenance-dup-callout .owner-maintenance-dup-callout-title {
  font-weight: 600;
}
/* Same standalone-banner-action treatment as .maintenance-archived-back-link
   just above -- a link-styled button, not inline prose text. */
.owner-maintenance-dup-dismiss-link {
  background: none;
  border: none;
  padding: 0;
  font-size: var(--text-xs);
  color: var(--color-warning-text);
  text-decoration: underline;
  cursor: pointer;
}
.owner-maintenance-dup-dismiss-link:hover,
.owner-maintenance-dup-dismiss-link:focus-visible {
  color: var(--color-text);
}
/* #6816 -- one row per candidate in the merge confirmation modal's radio
   list (never a dropdown, per the wireframe -- all N candidates need to be
   visible with subject + age at once). Reuses the modal's own default text
   color/spacing tokens rather than the warning-callout ones above; this list
   lives inside the plain #maintenanceBulkModal shared with the other 3 bulk
   modes, not inside the amber callout. */
.maintenance-merge-candidate-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
}
.maintenance-merge-candidate-row:last-child {
  border-bottom: none;
}
.maintenance-merge-candidate-row input[type="radio"] {
  margin-top: 3px;
  flex-shrink: 0;
}
.maintenance-archived-back-link:hover,
.maintenance-archived-back-link:focus-visible {
  color: var(--color-text);
}
.maintenance-archived-back-link:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* #5856: the two ownership facets are disabled (not just visually dimmed --
   see the click handlers' own early-return guards) while the Archived view
   is active, since neither "assigned to me" nor "owned by my team" is a
   live concern for a ticket quiet enough to have archived. */
.maintenance-priority-filter.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
/* #6742: bulk action bar -- List view only. Same tinted-surface convention
   as the archived banner above, but using --color-primary-highlight (the
   token that already carries a real dark-mode value) rather than a fresh
   opacity tint, since this bar is meant to read as "selection is active",
   not merely informational. */
.maintenance-bulk-actions-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-primary-highlight);
}
.maintenance-bulk-actions-left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.maintenance-bulk-selected-count {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-primary);
}
.maintenance-bulk-actions-right {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* #6742: the "more rows match this filter but aren't loaded yet" note --
   same muted, no-border treatment as other inline field notes, sitting
   between the (hidden-by-default) action bar and the table itself so it
   reads as a caveat on the whole List view, not on any one row. */
#maintenanceBulkMoreUnloadedNote {
  margin: 0 0 var(--space-2);
}
/* #6742: idempotent per-row highlight for a checked bulk-selection row --
   shared class name (bank reconciliation's own bulk pane already uses
   .selected-row the same way for the same reason: cheap visual confirmation
   of what's currently ticked without a second lookup against the JS state). */
.selected-row {
  background: var(--color-primary-highlight);
}
.maintenance-board-summary {
  width: 100%;
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.maintenance-show-more {
  width: 100%;
  margin-top: var(--space-2);
  padding: var(--space-2);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  text-align: center;
  cursor: pointer;
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.maintenance-show-more:hover {
  background: var(--color-surface-offset);
  color: var(--color-text);
}
/* #4293: board-level "Load more tickets" -- fetches the next server page,
   distinct from .maintenance-show-more above (which only reveals rows
   already loaded within one column). Sits below the whole board, not
   inside a column. */
.maintenance-load-more {
  display: block;
  width: 100%;
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  text-align: center;
  cursor: pointer;
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.maintenance-load-more:hover {
  background: var(--color-surface-offset);
  color: var(--color-text);
}
/* #6754 -- Tags. Freeform pill row on the detail panel, List view and Kanban
   card. A small fixed color rotation (not per-tag-configurable in v1, per the
   wireframe) keyed off a hash of the tag text so the same tag string always
   lands on the same color within one render, without needing a stored color
   column. */
.maintenance-tag-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--color-warning-highlight, var(--color-surface-2));
  color: var(--color-warning-text, var(--color-text));
  border-radius: 999px;
  padding: 2px 8px;
  margin: 0 4px 4px 0;
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: 1.4;
}
/* Only two hues -- warning (default) and success -- since those are the only
   tinted highlight/text token pairs already defined in this file. Inventing
   a third global color token for one feature's pill rotation is a bigger
   design-system decision than this ticket, not something to do in passing. */
.maintenance-tag-pill.tag-hue-1 { background: var(--color-success-highlight); color: var(--color-success-text); }
.maintenance-tag-pill-remove {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  color: inherit;
  opacity: 0.7;
  font-size: 1em;
  line-height: 1;
}
.maintenance-tag-pill-remove:hover { opacity: 1; }
.owner-maintenance-tags-line {
  margin: var(--space-2) 0;
}
.owner-maintenance-tags-label {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}
.owner-maintenance-add-tag-btn {
  background: none;
  border: 1px dashed var(--color-border);
  border-radius: 999px;
  padding: 2px 10px;
  margin-bottom: 4px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
}
.owner-maintenance-add-tag-btn:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.owner-maintenance-tag-input-wrap {
  position: relative;
  display: inline-block;
}
.owner-maintenance-tag-input {
  font-size: var(--text-xs);
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  width: 140px;
}
.owner-maintenance-tag-suggestions {
  position: absolute;
  z-index: 20;
  top: 100%;
  left: 0;
  min-width: 140px;
  margin-top: 2px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.12));
  max-height: 180px;
  overflow-y: auto;
}
.owner-maintenance-tag-suggestion {
  padding: 4px 10px;
  font-size: var(--text-xs);
  cursor: pointer;
}
.owner-maintenance-tag-suggestion:hover,
.owner-maintenance-tag-suggestion.is-active {
  background: var(--color-surface-2);
}
/* #6754 -- Resolution note. Only revealed when status is resolved/closed;
   see renderOwnerMaintenanceDetail's status-change listener. Plain textarea,
   no rich text -- matches the wireframe exactly. */
.owner-maintenance-resolution-note-section {
  margin: var(--space-3) 0;
}
.owner-maintenance-resolution-note-section label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 500;
  margin-bottom: 4px;
}
.owner-maintenance-resolution-note-hint {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  margin: 4px 0 0;
}
.owner-maintenance-resolution-note-section textarea {
  width: 100%;
  min-height: 72px;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font: inherit;
  resize: vertical;
}
/* #6754 -- Kanban card tag row. Own line, collapses to nothing when the
   ticket has no tags (no empty row/whitespace left behind), same convention
   as the SLA/Auto badges above. */
.owner-maintenance-card-tags {
  margin-top: 2px;
}
/* #236: the "Where" line on an owner maintenance detail. Body-sized rather than
   --text-xs because it is the first thing a PM reads to decide who to send. */
.owner-maintenance-where-line {
  color: var(--color-text);
  font-size: var(--text-sm);
  margin: var(--space-2) 0 var(--space-3);
}
/* #3434: the "Tenant / Unit" line, same treatment as the Where line above --
   it sits directly above it and answers the other question a PM needs before
   the Where line even matters: whose request is this. */
.owner-maintenance-who-line {
  color: var(--color-text);
  font-size: var(--text-sm);
  margin: var(--space-2) 0 0;
}
.owner-maintenance-detail {
  margin-top: var(--space-5);
  scroll-margin-top: var(--space-5);
}
.maintenance-detail-controls {
  display: grid;
  /* #4305: two more slots (Owned by, Assigned to) ahead of the existing
     Status/Priority/Linked task/Save row. position:relative so the shared
     picker popup (appended into whichever trigger's own .field wrapper on
     open) anchors under that field rather than the panel's top-left. */
  grid-template-columns: minmax(140px, 0.8fr) minmax(140px, 0.8fr) minmax(120px, 0.6fr) minmax(120px, 0.6fr) minmax(200px, 1.2fr) auto;
  gap: var(--space-3);
  align-items: end;
  padding: var(--space-4) 0;
  border-top: 1px solid var(--color-divider);
  border-bottom: 1px solid var(--color-divider);
  position: relative;
}
.maintenance-detail-controls .field {
  margin: 0;
  position: relative;
}
/* #4305: the Owned by / Assigned to trigger reads like the <select> beside
   it (same height/border/radius) rather than a plain link-styled button --
   this is a field, not an inline action. */
.maintenance-assignee-trigger {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}
.maintenance-assignee-trigger:hover {
  border-color: var(--color-primary);
}
.maintenance-detail-controls .hint {
  display: block;
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.maintenance-assignee-picker {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
  width: min(380px, 90vw);
  margin-top: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
  padding: var(--space-3);
}
.maintenance-assignee-picker .field {
  margin: 0 0 var(--space-2);
}
.maintenance-assignee-picker input[type="text"] {
  width: 100%;
  box-sizing: border-box;
}
#maintenanceAssigneePickerResults {
  max-height: 320px;
  overflow-y: auto;
  display: grid;
  gap: 1px;
}
.maintenance-assignee-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-2);
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text);
}
.maintenance-assignee-option:hover {
  background: var(--color-surface-offset);
}
.maintenance-assignee-option.is-current {
  background: var(--color-surface-offset);
}
.maintenance-assignee-option-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.maintenance-assignee-option-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.maintenance-assignee-option-current {
  font-size: var(--text-xs);
  color: var(--color-primary);
  font-weight: 600;
  white-space: nowrap;
}
.maintenance-assignee-option-clear {
  border-top: 1px solid var(--color-divider);
  margin-top: var(--space-1);
  padding-top: var(--space-2);
}
.owner-maintenance-thread {
  display: grid;
  gap: var(--space-3);
  max-height: 460px;
  overflow-y: auto;
  padding: var(--space-5) 0;
}
/* #989: "Linked repair items" on the maintenance ticket detail panel --
   same list rhythm as .owner-maintenance-thread just below it, but a plain
   list rather than chat bubbles since a repair item is a fact, not a
   message. */
.owner-maintenance-repair-items {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-divider);
}
.owner-maintenance-repair-items-heading {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
/* #2163: heading and the create-repair action share a row on a wide screen and
   stack on a narrow one. flex-wrap rather than a media query, because the only
   thing that decides it is whether the button still fits beside the heading --
   and the button's own label changes length once a repair exists. */
.owner-maintenance-repair-items-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}
.owner-maintenance-repair-items-header .owner-maintenance-repair-items-heading {
  margin-bottom: 0;
}
/* Stays above the 44px touch target on a phone, where most of this work
   actually happens. */
.owner-maintenance-create-repair {
  min-height: 44px;
  white-space: nowrap;
}
.owner-maintenance-repair-items-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.owner-maintenance-repair-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
.owner-maintenance-repair-item-status,
.owner-maintenance-repair-item-cost {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
/* #6717 -- "Scheduled visit" card on the maintenance ticket detail panel,
   replacing the raw ownerMaintenanceLinkedTask text box. Same section
   rhythm as .owner-maintenance-repair-items immediately above it, but kept
   as its own top-level block per the agreed scope that the two are separate
   features on one screen. */
.owner-maintenance-visit-task {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-divider);
}
.owner-maintenance-visit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}
.owner-maintenance-visit-form .field {
  min-width: 160px;
}
.owner-maintenance-visit-form .field.grow {
  flex: 1 1 220px;
}
.owner-maintenance-visit-hint {
  margin-top: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.owner-maintenance-visit-card {
  display: flex;
  padding: var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.owner-maintenance-visit-card.is-overdue {
  border-color: var(--color-error);
  background: var(--color-error-highlight);
}
.owner-maintenance-visit-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
}
.owner-maintenance-visit-info {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1;
  min-width: 0;
}
.owner-maintenance-visit-date-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
  font-size: var(--text-sm);
}
.owner-maintenance-visit-date.is-done {
  color: var(--color-text-muted);
  text-decoration: line-through;
}
.owner-maintenance-visit-note {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.owner-maintenance-visit-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
.owner-maintenance-clear-visit {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border-radius: 50%;
  line-height: 1;
}
.owner-maintenance-visit-overdue-pill {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border: 1px solid var(--color-error);
  border-radius: var(--radius-full, 9999px);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  font-size: var(--text-xs);
  font-weight: 600;
}
.owner-maintenance-visit-badge {
  display: inline-block;
  padding: 1px 6px;
  margin-bottom: 2px;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-primary-highlight, var(--color-surface-2));
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
}
.owner-maintenance-visit-overdue-badge {
  display: inline-block;
  padding: 1px 6px;
  margin-bottom: 2px;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.02em;
}
/* #6711: vendor read-through on the ticket-side linked-repair-items row,
   and the "also on #N, #M" note for a repair item shared by more than one
   ticket (the shared-equipment case, e.g. one water heater repair covering
   three tenants' tickets). Both are additive to the existing row -- see
   the row styles above, unchanged. */
.owner-maintenance-repair-item-vendor {
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: 600;
}
.owner-maintenance-repair-item-also-linked {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-style: italic;
}
/* #6711: the repair item form's "Maintenance tickets" chip list, replacing
   the old single <select>. Same chip visual language as .rr-tenant-chip
   (Rent Roll), scoped to its own class since this chip is removable via an
   inline × rather than clickable-to-open. */
.repair-ticket-links-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}
.repair-ticket-links-list:empty {
  display: none;
}
.repair-ticket-link-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: 6px;
  font-size: var(--text-sm);
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
  border: 1px solid var(--color-brand-navy);
}
.repair-ticket-link-chip-remove {
  font-family: inherit;
  line-height: inherit;
  cursor: pointer;
  border: 0;
  background: none;
  color: inherit;
  font-weight: 700;
  padding: 0 var(--space-1);
}
.repair-ticket-link-chip-remove:hover,
.repair-ticket-link-chip-remove:focus-visible {
  color: var(--color-danger);
}
/* #6711: the ticket panel's "Link an existing repair item" search picker.
   Reuses .owner-maintenance-repair-item for each result row (same row
   shape as the linked-items list above it) so a row looks identical
   whether it's already linked or just found via search. */
.owner-maintenance-link-repair-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding: var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
#ownerMaintenanceLinkRepairSearch {
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 6px);
  font-size: var(--text-sm);
}
#ownerMaintenanceLinkRepairResults {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-height: 260px;
  overflow-y: auto;
}
#ownerMaintenanceLinkRepairResults .owner-maintenance-repair-item {
  flex-wrap: wrap;
}
.owner-maintenance-link-repair-row-btn {
  margin-left: auto;
}
/* #989: the read-only "which documents are linked" summary on the repair
   form itself (attaching/detaching happens on the Documents tab). */
.repair-linked-documents-list {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding-left: var(--space-4);
  font-size: var(--text-sm);
}
.owner-thread-message {
  max-width: min(760px, 90%);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.owner-thread-message.is-internal {
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
}
/* #4310 (epic #4303): an automation row in the ticket's Activity timeline.
   These are interleaved with human replies in the SAME list on purpose, so
   without a tint the only thing separating "Automation" from "Property
   manager" is one word in the byline -- and the whole reason this screen
   exists is somebody asking "did a rule do this, or did a person?". Navy
   rather than amber (already the internal-note tint) or green/red (already
   the outcome pills), so no two meanings share a colour. Both themes define
   the pair. */
.owner-thread-message.is-automation {
  background: var(--color-brand-navy-highlight);
  border-color: var(--color-brand-navy);
}
.owner-thread-message.is-automation > div strong {
  color: var(--color-brand-navy);
}
.owner-thread-message > div {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.owner-thread-message p {
  margin: var(--space-2) 0 0;
  white-space: pre-wrap;
}
@media (max-width: 620px) {
  .provider-card-header {
    align-items: stretch;
    flex-direction: column;
  }
  .maintenance-title-row,
  .maintenance-detail-controls {
    grid-template-columns: 1fr;
    align-items: stretch;
  }
  .maintenance-title-row {
    display: grid;
  }
  .maintenance-board {
    grid-template-columns: repeat(6, minmax(82vw, 1fr));
    scroll-snap-type: x proximity;
  }
  .maintenance-column {
    scroll-snap-align: start;
  }
  .owner-thread-message {
    max-width: 100%;
  }
}
/* Fields pack as many per row as fit (min 9.5rem each) instead of the
   fixed 2-column .field-row grid, so short numeric fields don't waste
   the wider card width. */
.field-row-auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: var(--space-3);
  align-items: stretch;
}
.field-row-auto[hidden] {
  display: none;
}
/* Bottom-anchor each field's label+input so inputs stay level across a row
   even when one label wraps to 2 lines (e.g. "Other expenses ($/yr)") while
   its neighbor (e.g. "Insurance/unit ($/yr)") fits on one line. */
.field-row-auto .field {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  margin-bottom: 0;
}

/* ---- Deal Score formula: table layout (replaces 16 stacked .field divs) ---- */
.score-table-wrap {
  overflow-x: auto;
}
.score-table {
  width: 100%;
  border-collapse: collapse;
}
.score-table caption {
  caption-side: top;
  text-align: left;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin-bottom: var(--space-4);
}
.score-table th {
  font-size: var(--text-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  text-align: left;
  padding: 0 var(--space-3) var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider);
}
.score-table td {
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  border-bottom: 1px solid var(--color-divider);
  vertical-align: middle;
}
.score-table tbody tr:last-child td {
  border-bottom: none;
}
.score-table .metric-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}
.score-table-cell {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 8rem;
}
.score-table-cell input {
  width: 100%;
  min-width: 0;
}
.score-table-cell .unit-suffix {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  flex-shrink: 0;
}
.score-table-total {
  padding-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.score-table-total strong {
  color: var(--color-text);
}
.score-table-total .is-balanced {
  color: var(--color-success);
}
.score-grade-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.score-grade-box {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, var(--radius-lg));
  padding: var(--space-2) var(--space-3);
}
.score-grade-letter {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-lg, 1.125rem);
  width: 1.5rem;
  text-align: center;
  flex-shrink: 0;
}
.score-grade-letter[data-grade='A'] { color: var(--color-success); }
.score-grade-letter[data-grade='B'] { color: var(--color-primary); }
.score-grade-letter[data-grade='C'] { color: var(--color-warning); }
.score-grade-letter[data-grade='D'] { color: var(--color-error); }
.score-grade-box input {
  width: 100%;
  min-width: 0;
}
.score-grade-box .unit-suffix {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  flex-shrink: 0;
}

/* ---- System Defaults: individual list/status cards in the final three-up row ---- */
.defaults-status-card .field-group {
  margin-bottom: 0;
}
.defaults-status-card .field-group legend {
  border-bottom: none;
  padding-bottom: 0;
  margin-bottom: var(--space-1);
  font-size: var(--text-sm);
}
.defaults-status-card .field-note {
  margin-top: 0;
  margin-bottom: var(--space-2);
}

/* Multi-select dropdown (e.g. Basement type): a closed toggle button that
   opens a checkbox panel, reusing the same open/hidden/caret-rotate
   conventions as the header nav dropdowns above. */
.multiselect-dropdown {
  position: relative;
}
.multiselect-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.multiselect-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.multiselect-dropdown.open .multiselect-toggle {
  border-color: var(--color-primary);
}
.multiselect-toggle-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.multiselect-toggle-text.is-placeholder {
  color: var(--color-text-muted);
}
.multiselect-caret {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  transition: transform var(--transition-interactive);
}
.multiselect-dropdown.open .multiselect-caret {
  transform: rotate(180deg);
}
.multiselect-panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg, 0 12px 32px rgba(0, 0, 0, 0.16));
  /* #662: request-routing panels hold a checkbox per org user and can grow
     past a screenful (e.g. a 30-person org), unlike the short, fixed
     Basement-type list this component was first built for. Cap and scroll
     instead of letting the panel push page content down or run off-screen. */
  max-height: 16rem;
  overflow-y: auto;
}
.multiselect-panel[hidden] {
  display: none;
}
.multiselect-panel label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  font-size: var(--text-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--color-text);
  cursor: pointer;
}
/* #4845: the asset-add-item multi-destination panel's "Whole building"
   option is a radio, mutually exclusive with every checkbox below it --
   the border/margin visually separates it as an alternative to the list
   rather than one more row in it. */
.multiselect-where-whole {
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-1);
  padding-bottom: var(--space-2) !important;
}
.multiselect-panel input[type='radio'] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
}
.multiselect-panel input[type='checkbox'] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
}

/* #3227: the collections action composer's invoice picker needs the same
   checkbox-list look as .multiselect-panel, but always visible inline
   (never a position:absolute dropdown panel), so it gets its own static
   class instead of overriding .multiselect-panel's positioning per-caller. */
.collections-action-invoice-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-height: 12rem;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.collections-action-invoice-picker label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text);
  cursor: pointer;
}
.collections-action-invoice-picker input[type='checkbox'] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
}

/* ================= Status guide (explanations) ================= */

.status-guide {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
.status-guide-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) 0;
}
.status-guide-row + .status-guide-row {
  border-top: 1px solid var(--color-divider);
}
.status-guide-badge {
  flex-shrink: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-md);
  white-space: nowrap;
}
.status-guide-desc {
  color: var(--color-text-muted);
}

/* ================= Property list cash flow ================= */

.saved-cashflow-positive {
  color: var(--color-success);
  font-weight: 600;
}
.saved-cashflow-negative {
  color: var(--color-error);
  font-weight: 600;
}

/* ================= Notes / comments thread ================= */

.notes-thread {
  max-height: 18rem;
  overflow-y: auto;
  margin-bottom: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.note-item {
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.note-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.note-author {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-text);
}
.note-date {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  flex-shrink: 0;
}
.note-body {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: pre-wrap;
  word-break: break-word;
}

/* ================= Saved underwriting scenarios ================= */

.scenarios-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.scenario-default-badge {
  display: inline-block;
  margin-left: var(--space-1);
  padding: 0.1rem var(--space-2);
  border-radius: var(--radius-full);
  background: color-mix(in oklab, var(--color-primary) 14%, transparent);
  color: var(--color-primary);
  font-size: 0.7rem;
  font-weight: 700;
  vertical-align: middle;
}
.scenario-row-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.scenarios-compare-wrap {
  overflow-x: auto;
}
.scenarios-compare-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.scenarios-compare-table th,
.scenarios-compare-table td {
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  text-align: right;
  white-space: nowrap;
}
.scenarios-compare-table th:first-child {
  text-align: left;
}
.scenarios-compare-table thead th {
  color: var(--color-text);
  font-weight: 700;
}
.scenarios-compare-table .scenario-section-row th {
  text-align: left;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.scenarios-compare-table .scenario-diff {
  background: color-mix(in oklab, var(--color-primary) 10%, transparent);
  color: var(--color-text);
  font-weight: 600;
}
@media (max-width: 38rem) {
  .scenarios-form-grid {
    grid-template-columns: 1fr;
  }
}
.note-delete-btn {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-error);
}
.note-edit-btn {
  margin-top: var(--space-1);
  margin-right: var(--space-3);
  font-size: var(--text-xs);
}
.note-actions {
  display: flex;
  align-items: center;
}

/* #675 -- Internal Notes panel on the owner application detail page. Uses the
   same warning-tint tokens as .phase-lock-hint (a different card, same
   "pay attention, this is not routine content" register) so a staff-only,
   never-shown-to-applicants panel reads as visually distinct from every
   applicant-facing card on the page at a glance, not just via its label. */
.rg-internal-notes-card {
  border: 1px solid var(--color-warning);
  background: var(--color-warning-highlight);
}
.rg-internal-notes-card .provider-eyebrow {
  color: var(--color-warning-text, var(--color-warning));
}
.rg-internal-notes-icon {
  color: var(--color-warning);
  margin-right: var(--space-1);
  vertical-align: -0.15em;
}
.rg-internal-notes-card .note-item {
  background: var(--color-surface);
}
/* #676 -- application messages are applicant-facing, unlike the warning-tinted
   internal-notes card below. The direction is visible at a glance: staff
   messages sit on the right with the org accent, applicant replies stay left. */
.application-message-item {
  max-width: min(760px, 90%);
}
.application-message-item.is-inbound {
  align-self: flex-start;
}
.application-message-item.is-outbound {
  align-self: flex-end;
  background: var(--color-primary-highlight);
  border-color: var(--color-primary);
}
.notes-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.notes-form textarea {
  width: 100%;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-family: inherit;
  resize: vertical;
}
.notes-submit-btn {
  align-self: flex-end;
  padding: 0.55rem 1.1rem;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: var(--text-sm);
  border: none;
}
.notes-submit-btn:hover {
  background: var(--color-primary-hover);
}
.notes-submit-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Search Watch settings modal */
.field textarea {
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  resize: vertical;
}
.field textarea:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  border-color: var(--color-primary);
}
.sw-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-2) var(--space-3);
}
/* #3430: explicit [hidden] override -- this class sets `display`, so author
   styles beat the browser's `[hidden] { display: none }` at any specificity
   (see the .data-feed-sync-card comment above for the general rule). Without
   this, #showingScopeUnitOptions -- hidden by default and only ever meant to
   be the checkbox backing store under the chip/search picker -- rendered as a
   visible checkbox wall on top of the chip UI. */
.sw-checkbox-grid[hidden] {
  display: none;
}
.sw-checkbox-grid label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}
.sw-checkbox-grid input[type='checkbox'] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
  flex-shrink: 0;
}
.sw-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.sw-area-type-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.sw-area-type-group label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}
.sw-area-type-group input[type='radio'] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-primary);
  flex-shrink: 0;
}

/* ================= Tabs (Notes / Activity) ================= */

.tab-group {
  display: flex;
  gap: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-4);
}
.tab-btn {
  padding: var(--space-2) var(--space-1);
  border: none;
  background: none;
  border-bottom: 2px solid transparent;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  margin-bottom: -1px;
}
.tab-btn:hover {
  color: var(--color-text);
}
.tab-btn.is-active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

/* ================= @mention autocomplete ================= */

.notes-input-wrap {
  position: relative;
}
.mention-suggest {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + var(--space-1));
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  z-index: 5;
}
.mention-suggest-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border: none;
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  cursor: pointer;
}
.mention-suggest-item:hover,
.mention-suggest-item:focus {
  background: var(--color-surface-offset);
}

/* ================= Dashboard status breakdown ================= */

.status-breakdown {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-4) 0;
}
.status-breakdown .status-guide-badge {
  padding: 0.25rem 0.65rem;
}

/* ================= Map view ================= */

.map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.map-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.map-legend-dot {
  display: inline-block;
  width: 0.65rem;
  height: 0.65rem;
  border-radius: var(--radius-full);
}
.map-container {
  width: 100%;
  height: 480px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  overflow: hidden;
}
.map-pin-dot {
  display: block;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  border: 2px solid var(--color-surface);
  box-shadow: var(--shadow-sm);
}
.map-legend-dot.map-pin-to_review,
.map-pin-to_review .map-pin-dot {
  background: var(--color-primary);
}
.map-legend-dot.map-pin-reviewing,
.map-pin-reviewing .map-pin-dot {
  background: var(--color-primary-highlight);
  border-color: var(--color-primary);
}
.map-legend-dot.map-pin-offer_submitted,
.map-pin-offer_submitted .map-pin-dot {
  background: var(--color-text-muted);
}
.map-legend-dot.map-pin-offer_accepted,
.map-pin-offer_accepted .map-pin-dot {
  background: var(--color-warning);
}
.map-legend-dot.map-pin-offer_lost,
.map-pin-offer_lost .map-pin-dot {
  background: var(--color-error);
}
.map-legend-dot.map-pin-closed,
.map-pin-closed .map-pin-dot {
  background: var(--color-success);
}
.map-legend-dot.map-pin-not_interested,
.map-pin-not_interested .map-pin-dot {
  background: var(--color-text-faint);
}

/* ================= Dashboard/Map page export actions ================= */

.dashboard-export-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-left: auto;
}

/* ================= Print-friendly Map view export ================= */

@media print {
  body.printing-map .site-header,
  body.printing-map .site-footer,
  body.printing-map .page-intro,
  body.printing-map .saved-list-toolbar {
    display: none !important;
  }
  body.printing-map main:not(#map-main) {
    display: none !important;
  }
  body.printing-map #map-main {
    padding: 0;
    margin: 0;
  }
  body.printing-map #mapContent {
    display: block !important;
  }
  body.printing-map .map-container {
    width: 100%;
    height: 100vh;
  }
}

/* ================= Compare deals page (Feature 2) ================= */

.compare-picker-row {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
.compare-picker-label {
  display: block;
  color: var(--color-text-muted);
  font-weight: 600;
  font-size: var(--text-sm);
  margin-bottom: var(--space-1);
}
.compare-picker-field {
  min-width: 260px;
}
.compare-picker-field input[type='text'] {
  width: 100%;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-family: inherit;
}

.compare-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.compare-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text);
  background: var(--color-surface-offset);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
  white-space: nowrap;
}
.compare-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  min-width: 18px;
  border-radius: var(--radius-full);
  border: none;
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
}
.compare-chip-remove:hover {
  background: var(--color-border);
  color: var(--color-text);
}

.compare-col-address {
  font-weight: 400;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: 2px;
}
.compare-table th[scope='col'] {
  text-align: left;
  vertical-align: top;
}
.compare-best-cell {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  font-weight: 600;
  border-radius: var(--radius-sm);
}

/* ================= Actual vs. projected tracking (Feature 4) ================= */

.actuals-summary-grid {
  margin-bottom: var(--space-5);
}
.actuals-entry-form {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1.6fr auto;
  gap: var(--space-3);
  align-items: end;
  margin-bottom: var(--space-2);
}
.actuals-entry-field {
  display: flex;
  flex-direction: column;
}
.actuals-entry-field-note {
  grid-column: span 1;
}
/* ID 273: the carrying-cost card borrows this form's control styling but not
   its five-column template -- it has two date fields, two buttons and two
   full-width notes, so the actuals columns left a dead 1.6fr gap between the
   buttons and the status line. Its own columns also keep the notes out of the
   baseline row (same reasoning as .actuals-opex-help above). */
#carryingCostDatesForm {
  grid-template-columns: repeat(2, minmax(0, 190px)) auto auto;
  justify-content: start;
}
#carryingCostDatesForm > p {
  grid-column: 1 / -1;
  margin: var(--space-2) 0 0;
}
/* ID 358: the CapEx rule and the coverage flag both span the whole form.
   Inside a field cell they would break the align-items:end baseline the
   inputs share. */
.actuals-opex-help {
  grid-column: 1 / -1;
  margin: var(--space-2) 0 0;
  line-height: 1.5;
}
.actuals-coverage-note {
  margin: var(--space-2) 0 var(--space-3);
  padding: var(--space-3);
  border-left: 3px solid var(--color-warning-text);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  border-radius: var(--radius-sm);
  line-height: 1.5;
}
.actuals-entry-field label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-2);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.actuals-entry-field input {
  width: 100%;
  padding: var(--space-3) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums lining-nums;
}
.actuals-entry-form .btn-export {
  height: fit-content;
  white-space: nowrap;
}
@media (max-width: 900px) {
  .actuals-entry-form {
    grid-template-columns: 1fr 1fr;
  }
  .actuals-entry-field-note {
    grid-column: span 2;
  }
  .actuals-entry-form .btn-export {
    grid-column: span 2;
  }
}
#actualsTable {
  min-width: 980px;
  table-layout: fixed;
}
@media (max-width: 900px) {
  #actualsTable {
    min-width: 760px;
  }
  #actualsTable th:nth-child(7),
  #actualsTable td:nth-child(7) {
    width: 160px;
  }
  #actualsTable th:nth-child(8),
  #actualsTable td:nth-child(8) {
    width: 130px;
  }
}
#actualsTable th:nth-child(1),
#actualsTable td:nth-child(1) {
  width: 90px;
}
#actualsTable th:nth-child(2),
#actualsTable td:nth-child(2),
#actualsTable th:nth-child(3),
#actualsTable td:nth-child(3),
#actualsTable th:nth-child(4),
#actualsTable td:nth-child(4),
#actualsTable th:nth-child(5),
#actualsTable td:nth-child(5),
#actualsTable th:nth-child(6),
#actualsTable td:nth-child(6) {
  width: 110px;
}
#actualsTable th:nth-child(7),
#actualsTable td:nth-child(7) {
  width: 240px;
  white-space: normal;
  text-align: left;
}
#actualsTable th:nth-child(8),
#actualsTable td:nth-child(8) {
  width: 190px;
  white-space: nowrap;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
}
#actualsTable th:nth-child(9),
#actualsTable td:nth-child(9) {
  width: 70px;
}
.actuals-variance-positive {
  color: var(--color-success);
  font-weight: 600;
}
.actuals-variance-negative {
  color: var(--color-error);
  font-weight: 600;
}
/* #4887 Discounts card: amount is always a reduction against rent. */
.discount-amount-negative {
  color: var(--color-error);
  font-weight: 600;
}
.actuals-delete-btn {
  color: var(--color-text-muted);
}

/* ================= Property showings / walkthrough checklist ================= */

.showings-list-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}
/* #955: distinct from .showings-list-header on purpose -- the #927 markup
   test anchors on that class string being unique to the Visits modal's own
   list header, and the Schedules panel/card sit in the same document. Same
   layout, different name so the two never collide. */
.schedules-list-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}
.showings-type-filter {
  margin: 0;
}
/* ID 404: .field sets a display, and an author `display` beats the browser's
   built-in `[hidden] { display: none }` at any specificity -- same reason the
   .data-feed-sync-card rule above is explicit. Without this the filter stays
   visible on a single-type property. */
.showings-type-filter[hidden] {
  display: none;
}
.showings-type-filter label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.showings-type-filter select {
  font-size: var(--text-sm);
}
.showing-item-pills {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
.showing-item {
  position: relative;
  padding: var(--space-3);
  padding-right: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  cursor: pointer;
  transition: border-color var(--transition-interactive);
}
.showing-item:hover {
  border-color: var(--color-primary);
}
/* #2495: chevron affordance signalling the whole row is clickable, echoing
   .report-clickable-row's other uses elsewhere in the app. Absolutely
   positioned and vertically centered so it reads as a trailing-edge
   indicator rather than another line of row content. */
.showing-item-chevron {
  position: absolute;
  top: 50%;
  right: var(--space-2);
  transform: translateY(-50%);
  color: var(--color-text-muted);
  font-size: var(--text-lg);
  line-height: 1;
}
.showing-item-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}
.showing-item-date {
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
}
.showing-status-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  text-transform: capitalize;
}
.showing-status-pill.is-completed {
  color: var(--color-success);
  border-color: var(--color-success);
}
/* #5586: Unit Info table status dot (green/amber/red = complete/missing
   physical data/vacant) -- see unitInfoStatusDot() in app.js for the rule. */
.unit-info-table-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}
.unit-info-table-legend span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
/* #5649: restores the grouped unit-mix line lost when the old Advanced-mode
   per-unit card list (and its renderUnitEditorTotals() totals line) was
   removed during #5590 -- same muted small-text treatment as the legend
   directly above it, since it is the same kind of secondary summary text. */
.unit-info-table-mix-summary {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}
.unit-status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.unit-status-dot-ok { background: var(--color-success); }
.unit-status-dot-warn { background: var(--color-warning); }
.unit-status-dot-vacant { background: var(--color-danger); }
.unit-info-table-cell {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.unit-info-table-cell.is-muted,
td.unit-info-table-muted {
  color: var(--color-text-muted);
}
/* #5667: replaced the bare chevron cell with two real row-action buttons
   (View / Copy) per docs/frontend-ui-standards.md's button-not-bare-icon
   convention. Left-aligned to match #unitInfoTable's other left-aligned
   data columns (see #unitInfoTable.mini-table thead th above); wrap: nowrap
   so "View" and "Copy" always sit on one line rather than stacking. */
td.unit-info-table-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  white-space: nowrap;
}
.showing-type-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  white-space: nowrap;
}
.showing-type-diligence {
  color: #6b4fbb;
  background: #f1ecfb;
}
.showing-type-rental {
  color: #1b6e4a;
  background: #e8f5ee;
}
.showing-type-rental_sale {
  color: #9c5808;
  background: #fdf1e0;
}
.showing-type-inspection {
  color: #1c5fa8;
  background: #e8f1fb;
}
/* #6262: maintenance interval defaults -- STATUS badge, four mutually
   exclusive states per the agreed wireframe. Active mirrors the plain-text
   look of an unmodified row; the other three get color so a scan of the
   column alone tells the story without reading every cell.
   Fixed hex pairs only (mirroring .showing-type-* above), never a
   CSS-variable foreground over a literal background -- ID 729's audit
   evaluates each theme separately, and a --color-success/--color-text-muted
   token swaps value in dark mode while a literal background does not, which
   is exactly what failed here the first time (contrastPairs.unit.js). */
.maintenance-interval-status-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  white-space: nowrap;
}
.maintenance-interval-status-active {
  color: #2c743e;
  background: #e8f5ee;
}
.maintenance-interval-status-override {
  color: #1c5fa8;
  background: #e8f1fb;
}
.maintenance-interval-status-deactivated {
  color: #586170;
  background: #eceef1;
}
.maintenance-interval-status-custom {
  color: #9c5808;
  background: #fdf1e0;
}
/* Deactivated rows dim rather than disappear -- the row is still findable by
   search, it just reads as "off" at a glance. Same literal-pair rule as
   above: this only ever sits under the pill's own hex-on-hex background,
   never inherited onto an arbitrary page background, so a fixed color is
   safe and auditable the same way. */
tr.is-dimmed {
  color: #586170;
}
/* #6630: Collections Aging's paid-in-full row state -- a light success tint
   so a row with nothing left to collect reads as "done" at a glance,
   mirroring the wireframe's .paidtoday. */
tr.is-paid-today {
  background: var(--color-success-highlight);
}
tr.is-dimmed .maintenance-interval-status-pill {
  opacity: 0.9;
}
/* #6310: Who-handles-it pill, same fixed-hex-pair rule as the status pills
   above (contrastPairs.unit.js audits both themes separately). */
.maintenance-interval-assigned-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  white-space: nowrap;
}
.maintenance-interval-assigned-owner {
  color: #586170;
  background: #eceef1;
}
.maintenance-interval-assigned-tenant {
  color: #1c5fa8;
  background: #e8f1fb;
}
.maintenance-interval-assigned-na {
  color: #98a1ad;
}
.maintenance-interval-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  white-space: nowrap;
}
/* Fixed two-slot layout (Tony-approved deviation from the single-action
   convention, precedent #4665): the left slot is always Override/Edit, the
   right slot is always Deactivate/Reactivate, so a row's buttons never
   reflow as its status changes. Delete (custom rows only) sits past both. */
.maintenance-interval-actions .link-btn {
  min-width: 54px;
  text-align: left;
}
/* Distinct from .showings-list-header on purpose, same reason #955 gave
   .schedules-list-header its own name: tests/visitsFamilyToggle927.unit.js
   anchors on "showings-list-header" being unique to the Visits modal's own
   list header via a bare indexOf, and this settings screen shares the
   document with it. Same layout, different name so they never collide. */
.maintenance-intervals-list-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}
.showing-item-meta {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* #928: what a visit covers -- "Whole property" / "Unit 2" / "Units 1, 2 +2".
   Sits under the date/type/status head, above the outcome summary, so scope
   reads before outcome. */
.showing-item-scope {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* #955: inspection schedules -- property Schedules panel + Unit page Visits
   card share this row styling. Deliberately close to .showing-item rather
   than a new visual language, per Tony's #955 UI resolution ("match the
   existing Visits modal style... reuse it; don't invent a new pattern"). */
.schedule-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.schedule-item.is-paused {
  /* #955 fix (found during a11y coverage): this used to be a blanket
     `opacity: 0.65` on the whole row. That dimmed every text color in it --
     including .schedule-item-purpose's --color-text-muted and
     .link-btn-danger's --color-error, both already at the edge of their
     tokens' tuned contrast -- below 4.5:1 in both themes. Narrowing the
     opacity to just .schedule-item-main (tried first) only moved the failure
     onto .schedule-item-purpose. Opacity math on top of an already-muted
     color is the wrong tool here: the paused pill (border + muted color of
     its own, at full opacity) is a sufficient, already-passing visual cue on
     its own, so the row's other text stays at its normal, unmodified color.
  */
}
.schedule-item-main {
  min-width: 0;
}
.schedule-item-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.schedule-item-cadence {
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
}
.schedule-item-scope-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}
.schedule-item-paused-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}
.schedule-item-purpose {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.schedule-item-next-due {
  margin-top: var(--space-1);
  font-size: var(--text-sm);
}
.schedule-item-next-due.is-unavailable {
  color: var(--color-text-muted);
  font-style: italic;
}
.schedule-item-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
/* #949 decision 9 / Tony's #955 UI resolution: the internal reason field's
   "never shown to the tenant" note must be a persistent label, never a
   placeholder that disappears on focus. #1168 QA: neither --color-error/
   --color-danger (reserved for actual validation failures) NOR the
   warning-amber treatment -- this field is optional with nothing wrong,
   so it should read as routine field guidance. Use the same neutral,
   contrast-checked muted color as the base .field-note rule
   (--color-text-muted, see above) instead. Bold <strong> text below still
   keeps the "never shown to the tenant" phrase structurally unmistakable
   without needing a distinct color. */
.schedule-internal-only-note {
  color: var(--color-text-muted);
  font-weight: 500;
}
.schedule-internal-only-note strong {
  font-weight: 700;
}
.showings-empty {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding: var(--space-4) 0;
  text-align: center;
}
.showings-form-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.showings-form-status {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.showings-form-status.is-saving {
  color: var(--color-text-muted);
}
.showings-form-status.is-error {
  color: var(--color-error);
}
.showings-form-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.showings-form-actions .action-btn-danger {
  margin-right: auto;
}
/* #2210: keep the full inspection panel's Save draft confirmation directly
   beneath the bottom action row, rather than in the form toolbar far above
   the long condition list. The nested condition-capture Save uses the same
   .inspect-save-success treatment in its own footer. */
.showings-form-actions {
  flex-wrap: wrap;
}
.showings-form-actions #showingSaveDraftConfirmation {
  flex-basis: 100%;
  margin: 0;
  order: 2;
}
#showingSharedFieldStatus:empty,
#showingsFormStatus:empty {
  display: none;
}

/* #931: the old stacked-field two-column layout class (removed here) was
   dead CSS -- no HTML has rendered it since the Showings overhaul
   (cff6c02) replaced every SHOWING_SIMPLE_SECTIONS/UNIT_*_SECTION renderer
   with the .srows component-table layout below. Removed rather than kept
   "just in case"; git history has the old rule if it's ever needed again.

   Shrink empty note/textarea fields -- these render as a single-row input
   by default (see showingCompactControlHtml rows="1") instead of a fixed
   3-line empty box, cutting a lot of the "dead space" on mobile. Users can
   still drag the native resize handle open for longer free-text notes. */
.showing-subcard textarea {
  min-height: 2.1rem;
  resize: vertical;
}

/* Accordion sections -- the 9 dynamic checklist sections (exterior,
   cold-climate flags, mechanical, interior/per-unit, garage & parking,
   common areas, tenant observations, neighborhood impression, repairs,
   overall assessment) render as native <details>/<summary> instead of a
   plain fieldset/legend, so the whole section collapses on mobile instead
   of forcing one long linear scroll. "Shared property details" stays an
   always-visible plain fieldset since it's short and used every time.
   Sections auto-open on load if they already have data (see
   setShowingAccordionOpenState in app.js); everything else starts
   collapsed. Native <details> gives free keyboard + screen-reader support. */
/* #4321: bordered-card treatment, matching .unit-card's existing
   border/radius so this screen's cards read as one consistent visual
   language instead of introducing a second card style. Each accordion is
   its own card with a solid header bar (the <summary>) rather than the
   flat divider-only rule this replaces -- the goal is that a user can see
   at a glance where one section ends and the next begins, and that the
   header bar (not the body text below it) is the clickable part. */
.showing-accordion {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  overflow: hidden; /* keeps the summary's own background inside the card's rounded corners */
  margin-bottom: var(--space-3);
}
.showing-accordion:last-of-type {
  margin-bottom: 0;
}
.showing-accordion > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
  padding: var(--space-3);
  background: var(--color-surface-2);
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}
/* Closed state has no body below the summary to draw a border against --
   keeps a collapsed card from showing a stray internal line with nothing
   under it. */
.showing-accordion:not([open]) > summary {
  border-bottom-color: transparent;
}
.showing-accordion > summary::-webkit-details-marker {
  display: none;
}
.showing-accordion > summary::after {
  content: '';
  width: 7px;
  height: 7px;
  flex-shrink: 0;
  border-right: 2px solid var(--color-text-muted);
  border-bottom: 2px solid var(--color-text-muted);
  transform: rotate(45deg);
  transition: transform var(--transition-interactive);
}
.showing-accordion[open] > summary::after {
  transform: rotate(-135deg);
}
.showing-accordion > summary:hover {
  color: var(--color-primary);
}
.showing-accordion > summary:hover::after {
  border-color: var(--color-primary);
}
.showing-accordion > summary ~ * {
  margin-top: var(--space-3);
  margin-left: var(--space-3);
  margin-right: var(--space-3);
}
.showing-accordion > summary ~ *:last-child {
  margin-bottom: var(--space-3);
}

/* Per-unit card nesting -- bathrooms and bedrooms render as sub-cards
   inside the unit card, one level down from .unit-card. */
.showing-unit-card {
  margin-bottom: var(--space-4);
}
.showing-unit-card:last-child {
  margin-bottom: 0;
}
.showing-subsection-title {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  margin: var(--space-4) 0 var(--space-2);
}
.showing-subsection-title:first-child {
  margin-top: 0;
}
.showing-subcard-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.showing-subcard {
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  background: var(--color-surface-2);
}

.showing-repairs-total {
  font-weight: 650;
  color: var(--color-text);
}
.unit-card-badge {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 1px 6px;
  flex-shrink: 0;
}

/* Showing photo widget. Auto-fill rather than a fixed column count so the
   same grid works inside a full-width checklist section and inside the
   narrower repair/unit subcards without a per-context override. */
.showing-photos {
  margin-top: var(--space-3);
}
.showing-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: var(--space-2);
}
.showing-photo-grid:empty {
  display: none;
}
.showing-photo {
  position: relative;
  aspect-ratio: 1;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface-2);
}
.showing-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.showing-photo.is-uploading {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-align: center;
}
.showing-photo-delete {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 22px;
  height: 22px;
  line-height: 1;
  font-size: var(--text-sm);
  border: none;
  border-radius: 50%;
  color: #fff;
  background: rgba(0, 0, 0, 0.6);
  cursor: pointer;
}
.showing-photo-delete:hover {
  background: rgba(0, 0, 0, 0.8);
}
.showing-photo-add {
  display: inline-block;
  margin-top: var(--space-2);
  cursor: pointer;
}
.showing-photo-error {
  margin: var(--space-2) 0 0;
  padding: var(--space-2);
  border: 1px solid var(--color-error-text);
  border-radius: var(--radius-sm);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  font-size: var(--text-sm);
}

/* #1940: the repair photo widget's two labelled strips (Found / Completed).
   Reuses .showing-photo/.showing-photo-grid/.showing-photo-add as-is inside
   each strip -- only the strip wrapper, its label, and its empty-state note
   are new, so a strip visually matches the single-list widget it replaced
   rather than introducing a second photo-grid look. */
.repair-photo-strip {
  margin-top: var(--space-3);
}
.repair-photo-strip + .repair-photo-strip {
  margin-top: var(--space-4);
}
.repair-photo-strip-label {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}
/* Same look as .field-hint-block -- a strip with nothing in it yet still
   needs a full-width sub-line explaining that, not silence, per the brief's
   "an empty strip shows a short note rather than vanishing". */
.repair-photo-strip-empty {
  margin: 0 0 var(--space-2);
}

/* ================= Repairs & Capital Improvements tracker ================= */

.repairs-summary-bar {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.repairs-summary-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
/* Analyze-phase Repairs UI cleanup (Task List ID 121): updateRepairsPhaseUI()
   sets [hidden] on repairsSummaryStat3 to drop the "Next due" stat during
   Analyze, but the explicit `display: flex` above is an author-stylesheet
   rule that always wins over the browser's default `[hidden]{display:none}`
   UA style (same class of bug as .overview-dashboard[hidden] above) -- so
   without this override the stat stayed visibly rendered even while
   [hidden] was set. */
.repairs-summary-stat[hidden] {
  display: none;
}
/* #4441: CapEx Accrual History (reserveSummaryBar) and Duration & Aging
   (repairsTimingSummaryBar) each render 4 stat boxes into the base
   3-column grid above, wrapping 3-then-1-orphaned on row 2. Same fix
   shape as .under-contract-card .uc-summary-bar below -- a scoped
   override to a 4-column grid, falling back to 2 columns on narrow
   viewports so a stat box never squeezes below a readable width. */
.repairs-summary-bar-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 900px) {
  .repairs-summary-bar-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.repairs-summary-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.repairs-summary-value {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 650;
  color: var(--color-text);
}
/* ID 575: the overdrawn reserve chip on the property-detail "Reserve balance"
   card. Deliberately a PARALLEL block rather than widening the ID 531
   selectors off .kpi-value: [data-funding] is otherwise unique to KPI tiles,
   and loosening it would style every future element that grows the attribute.

   Only the overdrawn tone gets a rule. capexReserveBalanceStatus() returns
   'none' for any balance at or above zero, and 'none' matching nothing here is
   what guarantees a healthy card renders byte-identically to before -- there
   is no reset rule that could drift from the plain .repairs-summary-value.

   Same silhouette as the ID 531 chip (square corners, 2px border, uppercase,
   stepped-down type) so the two surfaces read as one state, and the same
   ID 265 -text/-highlight pairing: --color-error retints to #e07a7a in dark
   mode, so white-on-solid would fail AA there. Colour is never the only
   signal -- the words "Fully drawn" are in the text node, so this survives
   greyscale printing and reads correctly to a screen reader.

   align-self keeps the chip hugging its text: .repairs-summary-stat is a
   flex column, which would otherwise stretch it to the full card width. */
.repairs-summary-value[data-funding='overdrawn'] {
  align-self: flex-start;
  color: var(--color-error-text);
  background: var(--color-error-highlight);
  border: 2px solid var(--color-error);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--text-sm);
}
/* ID 667: a negative reserve balance measured against REAL open capital work
   is not the overdrawn state. capexRulFundingStatus() calls it "Underfunded"
   and gives it a percentage, and the ID 531 silhouette is reserved for the
   case where no percentage exists at all -- so this takes the ordinary 'short'
   pill and leaves the square/uppercase treatment above alone. The silhouette
   is therefore still the signal for "no ratio to state", not merely "worse".

   Unlike the overdrawn chip this does NOT step down to --text-sm: that step
   exists because "FULLY DRAWN · -$8,921" is a sentence, whereas here the value
   slot still holds only the figure and the sentence has moved to the sub-line.
   'funded' and 'partial' get no rule because they are unreachable here -- a
   negative balance over a positive exposure is always a negative percentage,
   and any balance at or above zero returns 'none'. */
.repairs-summary-value[data-funding='short'] {
  align-self: flex-start;
  color: var(--color-error-text);
  background: var(--color-error-highlight);
  border: 1px solid var(--color-error);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-2);
}
.repairs-summary-sub {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* ID 667: the sufficiency sub-line. The value slot answers "how much is in the
   reserve?", this answers "is that enough?" -- and it is the only slot on the
   card that ever shows a percentage, which is why the balance figure above
   survives intact. Same three ID 531 bands, same ID 265 -text/-highlight
   pairing (--color-error retints to #e07a7a in dark mode, so white-on-solid
   would fail AA there), and the words are in the text node so colour is never
   the only signal. Kept at --text-xs so the sentence stays on one line in a
   card a third of the bar wide -- ID 589's constraint, measured in
   scripts/dev/qa-589-kpi-chip-dom.js rather than assumed. */
.repairs-summary-sub[data-funding] {
  align-self: flex-start;
  margin-top: 2px;
  display: inline-block;
  font-weight: 600;
  line-height: 1.3;
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  border: 1px solid transparent;
}
.repairs-summary-sub[data-funding='funded'] {
  color: var(--color-success-text);
  background: var(--color-success-highlight);
  border-color: var(--color-success);
}
.repairs-summary-sub[data-funding='partial'] {
  color: var(--color-warning-text);
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
}
.repairs-summary-sub[data-funding='short'] {
  color: var(--color-error-text);
  background: var(--color-error-highlight);
  border-color: var(--color-error);
}
/* Last, so an element that somehow carries [data-funding] with no text still
   collapses rather than painting an empty pill. */
.repairs-summary-sub:empty {
  display: none;
}

.repairs-promote-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: var(--text-sm);
}
.repairs-promote-banner[hidden] {
  display: none;
}

.repairs-list-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.repairs-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
/* Analyze-phase Repairs UI cleanup (Task List ID 121): same [hidden]
   vs. author `display: flex` conflict as .repairs-summary-stat[hidden]
   above -- without this override updateRepairsPhaseUI() hiding the filter
   chip row during Analyze had no visual effect. */
.repairs-filter-chips[hidden] {
  display: none;
}
.repairs-chip {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: border-color var(--transition-interactive), color var(--transition-interactive), background var(--transition-interactive);
}
.repairs-chip:hover {
  border-color: var(--color-primary);
  color: var(--color-text);
}
/* ID 266: `--color-on-primary` is defined nowhere, so this always resolved to
   the #fff fallback -- fine on the light theme's #006837, but 2.81:1 on the
   dark theme's lighter #3fae66. --color-text-inverse is the token that already
   flips per theme and is what every other on-primary surface in this file uses
   (6.9:1 light / 6.5:1 dark). Caught by the authenticated axe scan of the
   owned-property Repairs tab. */
.repairs-chip.is-active {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
/* #6000: inline per-chip count badge -- Deposit Interest's status filter
   chips are the first .repairs-chip use to need one (repairs' own chips
   above show no counts). Deliberately not a new pill component: just muted,
   slightly smaller text riding inside the existing chip, so it reads as
   part of the label rather than a second status pill next to it. */
.repairs-chip-count {
  opacity: 0.75;
  font-weight: 500;
}
/* #6000: a checkbox-only <th>/<td> should not stretch to a data column's
   width -- same "settings-card mini-table has no built-in narrow-column
   helper" gap the row-select tables in this file each hit; declared once
   here since Deposit Interest is the first ListKit-server-paged table to
   need a select-column. */
.checkbox-col {
  width: 2.25rem;
  text-align: center;
}

.repair-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  cursor: pointer;
  transition: border-color var(--transition-interactive);
}
.repair-item:hover {
  border-color: var(--color-primary);
}
/* #866: keyboard focus visibility for repair item rows, which are now
   role="button" tabindex="0". Matches the :hover style. */
.repair-item:focus-visible {
  border-color: var(--color-primary);
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}
.repair-item-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.repair-item-desc-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
}
.repair-item-desc {
  flex: 1 1 auto;
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.repair-forecast-badge {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-brand-navy);
  border: 1px solid var(--color-brand-navy);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}
.repair-saleprep-badge {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-warning);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}
.repair-qa-test-badge {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}
/* #4407: deliberately muted/neutral, not the navy (Forecasted) or warning
   (Sale prep) treatment above -- this flags a row as not-real data for QA
   purposes, not an operational signal that needs the same visual weight. */
.repair-testdata-badge {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.45rem;
  white-space: nowrap;
}
.repair-item-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* #937: a plain, non-focusable span standing in for a link to the unit page
   (see the click-delegation comment in renderRepairsList/wireCapexRulRowClicks'
   sibling handler for why this is not a real <a> nested in a role="button" row). */
.repair-item-unit-link {
  cursor: pointer;
  text-decoration: underline;
  text-decoration-style: dotted;
}
.repair-item-side {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
.repair-status-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  text-transform: capitalize;
  white-space: nowrap;
}
.repair-status-pill.status-done {
  color: var(--color-success);
  border-color: var(--color-success);
}
.repair-status-pill.status-blocked {
  color: var(--color-error);
  border-color: var(--color-error);
}
.repair-status-pill.status-in_progress {
  color: var(--color-warning);
  border-color: var(--color-warning);
}
.repair-status-pill.status-forecasted {
  color: var(--color-brand-navy);
  border-color: var(--color-brand-navy);
}
/* #6514: Evictions worklist -- overdue "Due" text, table cell and the
   summary panel's own due line alike. Same --color-error token as
   .dash-dates-overdue/.important-dates-item.is-overdue elsewhere in this
   file, just scoped to these two ids rather than adding a new bare
   .is-error rule (every existing .is-error selector in this file is a
   compound class off a specific component -- .field-note.is-error,
   .badge.is-error -- never a bare rule other markup could pick up
   unintentionally). */
#evictionsListTable td.is-error,
#evictionsDetailDueLine.is-error {
  color: var(--color-error);
  font-weight: 600;
}
/* Tasks list complete/reopen toggle -- a button rendering a check-circle icon,
   not a checkbox (screenshot fix after #5833 put a real selection checkbox
   immediately to its left and the two read as an unlabeled double-checkbox).
   Reuses the .icon-btn sizing/hover pattern; .is-done tints it success-green
   so a completed row reads as done at a glance, matching .repair-status-pill
   .status-done's color instead of introducing a new one. */
.task-complete-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.task-complete-toggle svg {
  width: 1.15rem;
  height: 1.15rem;
}
.task-complete-toggle:hover,
.task-complete-toggle:focus-visible {
  color: var(--color-primary);
  background: var(--color-surface-offset);
}
.task-complete-toggle.is-done {
  color: var(--color-success);
}
.task-complete-toggle.is-done:hover,
.task-complete-toggle.is-done:focus-visible {
  color: var(--color-success-text);
  background: var(--color-success-highlight);
}
.task-complete-toggle:disabled {
  color: var(--color-text-muted);
  cursor: not-allowed;
  opacity: 0.6;
}
/* #4274: below ~700px the org-wide Repairs table (13 columns -- the widest
   .mini-table in the app) switches from horizontal-scroll to one card per
   repair, instead of leaving 8+ columns to scroll past the sticky Property
   column on a phone. Scoped to #repairsListTable only -- .grid-scroll and
   .mini-table are shared by many other tables that stay untouched. Each
   <td>'s data-label (set in repairsListController's renderRow) becomes the
   visible field label in card mode; the empty-label actions cell (the
   'View' button) and the Description cell (shown as the card's line under
   the address, no label needed) opt out via their own selectors below. */
@media (max-width: 700px) {
  #repairsListWrap {
    overflow-x: visible;
    border: none;
    border-radius: 0;
  }
  #repairsListTable, #repairsListTable thead, #repairsListTable tbody {
    display: block;
    width: 100%;
  }
  #repairsListTable thead {
    position: absolute;
    left: -9999px;
    top: -9999px;
  }
  #repairsListTable tr {
    display: block;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
    background: var(--color-bg);
  }
  #repairsListTable td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    text-align: right;
    border-bottom: 1px solid var(--color-divider);
    padding: var(--space-2) 0;
  }
  #repairsListTable td:last-child {
    border-bottom: none;
  }
  #repairsListTable td::before {
    content: attr(data-label);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    text-align: left;
    flex-shrink: 0;
  }
  /* Property is the card's title, not a labelled row -- full width, left
     aligned, no ::before pseudo-label, matches the address-first hierarchy
     the mobile mock in the approved wireframe shows. */
  #repairsListTable td[data-label="Property"] {
    display: block;
    text-align: left;
    font-weight: 700;
    font-size: var(--text-base);
    border-bottom: none;
    padding-bottom: var(--space-1);
  }
  #repairsListTable td[data-label="Property"]::before {
    content: none;
  }
  /* Description reads as a plain line under the address, same reason. */
  #repairsListTable td.repairs-cell-description {
    display: block;
    text-align: left;
    color: var(--color-text-muted);
    border-bottom: 1px solid var(--color-divider);
    padding-bottom: var(--space-2);
    margin-bottom: var(--space-1);
  }
  #repairsListTable td.repairs-cell-description::before {
    content: none;
  }
  /* Actions ("View" button) stretches full width at the bottom of the card
     instead of a labelled row. */
  #repairsListTable td.repairs-cell-actions {
    display: block;
    border-bottom: none;
    padding-top: var(--space-3);
  }
  #repairsListTable td.repairs-cell-actions::before {
    content: none;
  }
  #repairsListTable td.repairs-cell-actions .repairs-list-view-btn {
    width: 100%;
    text-align: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-2);
  }
}
/* #5065: the same one-card-per-row treatment as #repairsListTable above,
   applied to all three Visits tab tables (All / Site Inspections / Prospect
   Showings) with one shared selector list -- they're the same 7-column
   shape (Date/Type/Unit(s)/Shown-by-Inspector/Status/Items/Actions) so a
   single block covers all three rather than tripling the rules. Actions
   here is a two-part cell (Open link-btn + a .row-actions-menu <details> for
   Delete), not a single centered button like Repairs' lone "View", so its
   full-width/no-label treatment stays but skips the single-button centering
   the repairs block applies to its one .repairs-list-view-btn. */
@media (max-width: 700px) {
  #visitsAllListWrap, #visitsInspectionsListWrap, #visitsShowingsListWrap {
    overflow-x: visible;
    border: none;
    border-radius: 0;
  }
  #visitsAllListTable, #visitsAllListTable thead, #visitsAllListTable tbody,
  #visitsInspectionsListTable, #visitsInspectionsListTable thead, #visitsInspectionsListTable tbody,
  #visitsShowingsListTable, #visitsShowingsListTable thead, #visitsShowingsListTable tbody {
    display: block;
    width: 100%;
  }
  #visitsAllListTable thead, #visitsInspectionsListTable thead, #visitsShowingsListTable thead {
    position: absolute;
    left: -9999px;
    top: -9999px;
  }
  #visitsAllListTable tr, #visitsInspectionsListTable tr, #visitsShowingsListTable tr {
    display: block;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
    background: var(--color-bg);
  }
  #visitsAllListTable td, #visitsInspectionsListTable td, #visitsShowingsListTable td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    text-align: right;
    border-bottom: 1px solid var(--color-divider);
    padding: var(--space-2) 0;
  }
  #visitsAllListTable td:last-child, #visitsInspectionsListTable td:last-child, #visitsShowingsListTable td:last-child {
    border-bottom: none;
  }
  #visitsAllListTable td::before, #visitsInspectionsListTable td::before, #visitsShowingsListTable td::before {
    content: attr(data-label);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    text-align: left;
    flex-shrink: 0;
  }
  /* Date is the card's title, not a labelled row -- full width, left
     aligned, no ::before pseudo-label, same reasoning as Repairs' Property
     column above. */
  #visitsAllListTable td[data-label="Date"], #visitsInspectionsListTable td[data-label="Date"], #visitsShowingsListTable td[data-label="Date"] {
    display: block;
    text-align: left;
    font-weight: 700;
    font-size: var(--text-base);
    border-bottom: none;
    padding-bottom: var(--space-1);
  }
  #visitsAllListTable td[data-label="Date"]::before, #visitsInspectionsListTable td[data-label="Date"]::before, #visitsShowingsListTable td[data-label="Date"]::before {
    content: none;
  }
  /* #5141: Actions (Open + Delete, two plain sibling buttons) stretches
     full width at the bottom of the card instead of a labelled row. */
  #visitsAllListTable td.visits-cell-actions, #visitsInspectionsListTable td.visits-cell-actions, #visitsShowingsListTable td.visits-cell-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: none;
    padding-top: var(--space-3);
  }
  #visitsAllListTable td.visits-cell-actions::before, #visitsInspectionsListTable td.visits-cell-actions::before, #visitsShowingsListTable td.visits-cell-actions::before {
    content: none;
  }
}
/* Sell Tracker (48b) */
.sell-tracker-toolbar {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  margin: var(--space-2) 0 var(--space-3);
}
.sell-tracker-steps {
  display: flex;
  flex-direction: column;
}
.sell-step-row {
  border-bottom: 1px solid var(--color-divider);
  padding: var(--space-2) 0;
}
.sell-step-row:last-of-type {
  border-bottom: none;
}
.sell-step-row > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  padding: var(--space-1) 0;
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}
.sell-step-row > summary::-webkit-details-marker {
  display: none;
}
.sell-step-order {
  flex-shrink: 0;
  width: 1.6em;
  height: 1.6em;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.sell-step-title {
  flex: 1;
}
.sell-step-body {
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sell-step-fields-row {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.sell-step-fields-row .field {
  flex: 1;
  min-width: 160px;
}
.sell-step-status-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.sell-step-status-pill.status-complete {
  color: var(--color-success);
  border-color: var(--color-success);
}
.sell-step-status-pill.status-na {
  color: var(--color-text-muted);
  border-color: var(--color-text-faint);
}
.sell-step-status-pill.status-in_progress {
  color: var(--color-warning);
  border-color: var(--color-warning);
}
.sell-step-custom-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.sell-step-custom-field-row {
  display: flex;
  gap: var(--space-1);
  align-items: center;
}
.sell-step-custom-field-row input {
  flex: 1;
}
.sell-step-attachments {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.sell-step-attachment-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-sm);
  padding: 0.2rem 0;
}
/* Sell Tracker (48c) -- list price editor/history, 1031 deadline panel,
   offers sub-log. */
.sell-tracker-listprice-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--color-surface-2);
}
.sell-tracker-listprice-editor-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.sell-tracker-listprice-history {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
  padding-left: var(--space-2);
  border-left: 2px solid var(--color-border);
}
.sell-tracker-listprice-history-row {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.sell-tracker-subheading {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
  margin: 0;
}
.sell-tracker-1031-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--color-surface-2);
}
.sell-tracker-1031-toggle-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
}
.sell-tracker-1031-deadlines {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.sell-tracker-1031-deadline {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.sell-tracker-1031-deadline.is-upcoming {
  color: var(--color-warning);
  border-color: var(--color-warning);
}
.sell-tracker-1031-deadline.is-overdue {
  color: var(--color-error);
  border-color: var(--color-error);
}
.sell-tracker-1031-deadline.is-ok {
  color: var(--color-success);
  border-color: var(--color-success);
}
.sell-tracker-offers-section {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-divider);
}
.sell-tracker-offers-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.sell-tracker-offer-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--color-surface-2);
}
/* Task List ID 276 QA: this class sets display:flex, which outranks the UA
   [hidden] rule, so every consumer that toggles the form with el.hidden was
   leaving it permanently on screen. Caught on the new Under Contract card's
   add-deadline form (#ucDeadlineForm); the Sell Tracker's own
   #sellTrackerOfferForm (open/closeSellTrackerOfferForm, app.js) had the same
   latent bug and is fixed by the same rule. */
.sell-tracker-offer-form[hidden] {
  display: none;
}
/* Task List ID 362: same display-outranks-[hidden] bug as ID 276 above, found
   on the rest of the Sell Tracker while moving it from a modal to an inline
   tab panel. Every one of these is toggled with el.hidden in app.js
   (openSellTrackerModal / closeSellTrackerListPriceEditor /
   toggleSellTrackerListPriceHistory / renderSellTracker1031Panel), so without
   these rules the list-price editor, its price history, the 1031 toggle row
   and the 1031 panel all rendered permanently -- visible in the popup as an
   always-open "New list price" box and an always-open 1031 card. Setting a
   display value on a class that a consumer hides via the hidden attribute
   needs a matching [hidden] guard; see the ID 276 note above. */
.sell-tracker-listprice-editor[hidden],
.sell-tracker-listprice-history[hidden],
.sell-tracker-1031-toggle-row[hidden],
.sell-tracker-1031-panel[hidden] {
  display: none;
}
/* Shared by the Repairs modal's summary bar too, which is hidden until its
   items load -- same latent bug, same fix. */
.repairs-summary-bar[hidden] {
  display: none;
}
.sell-tracker-offer-form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.sell-tracker-offers-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sell-tracker-offer-row {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
}
.sell-tracker-offer-row-main {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.sell-tracker-offer-amount {
  font-family: var(--font-display);
  font-weight: 650;
  color: var(--color-text);
}
.sell-tracker-offer-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.sell-tracker-offer-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.sell-tracker-offer-status-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
  text-transform: capitalize;
}
.sell-tracker-offer-status-pill.status-accepted {
  color: var(--color-success);
  border-color: var(--color-success);
}
.sell-tracker-offer-status-pill.status-rejected,
.sell-tracker-offer-status-pill.status-withdrawn,
.sell-tracker-offer-status-pill.status-expired {
  color: var(--color-text-muted);
  border-color: var(--color-text-faint);
}
.sell-tracker-offer-status-pill.status-pending {
  color: var(--color-warning);
  border-color: var(--color-warning);
}

.sell-template-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.sell-template-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2);
}
.sell-template-row input[type="text"] {
  flex: 1;
}
.sell-template-row-actions {
  display: flex;
  gap: var(--space-1);
  flex-shrink: 0;
}
.repair-item-cost {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}
.repair-category-tag {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.repairs-empty {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding: var(--space-4) 0;
  text-align: center;
}

/* #5807 -- expanded detail under a capitalization project row: the repair
   items tagged into it, plus its capitalize/reverse history line. Inset and
   bullet-free so it reads as a continuation of the row above it rather than a
   default browser list. */
.capitalization-project-detail {
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-2);
  border-bottom: 1px solid var(--color-border);
}
.capitalization-project-item-list {
  list-style: none;
  margin: 0 0 var(--space-2) 0;
  padding: 0;
}
.capitalization-project-item-list:last-child {
  margin-bottom: 0;
}
.capitalization-project-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  padding: var(--space-1) 0;
}
.capitalization-project-item + .capitalization-project-item {
  border-top: 1px solid var(--color-border);
}
/* The cost sits hard right so a column of them stays comparable. */
.capitalization-project-item .repair-item-cost {
  margin-left: auto;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.is-auto-generated-note {
  font-style: italic;
}

/* AI Cost Estimator (Phase 2) -- "Estimate with AI" widget on a repair item */
.ai-estimate-widget {
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--color-surface-2);
}
.ai-estimate-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-primary);
  background: none;
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-full);
  padding: 0.35rem 0.9rem;
  cursor: pointer;
}
.ai-estimate-btn:hover {
  background: var(--color-primary-highlight);
}
.ai-estimate-btn:disabled {
  opacity: 0.6;
  cursor: default;
}
.ai-estimate-status {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.ai-estimate-status.is-error {
  color: var(--color-error);
}
.ai-estimate-result {
  margin-top: var(--space-3);
}
.ai-estimate-range {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  margin: 0 0 var(--space-1);
}
.ai-estimate-confidence {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}
.ai-estimate-rationale {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}
.ai-estimate-citations {
  list-style: none;
  margin: 0 0 var(--space-2);
  padding: 0;
  font-size: var(--text-xs);
}
.ai-estimate-citations li {
  margin-bottom: 0.2rem;
}
.ai-estimate-citations a {
  color: var(--color-primary);
  text-decoration: none;
  word-break: break-all;
}
.ai-estimate-citations a:hover {
  text-decoration: underline;
}
.ai-estimate-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* -----------------------------------------------------------------
 * Rent Roll (52b): summary bar + occupancy pills + tenant chips +
 * market-rent freshness pill. Kept self-contained so removing 52b
 * (feature flag off) doesn't leave orphaned styling elsewhere.
 * ----------------------------------------------------------------- */
.section-block-heading {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.tenant-portal-access-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.tenant-portal-access-controls .field-note {
  flex-basis: 100%;
  margin: 0;
}
.rent-roll-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0;
  padding: var(--space-3);
  background: var(--color-surface-2);
  border-radius: 8px;
}
/* #1575: display:grid overrides the hidden attribute, so a loading state
   that sets summary.hidden = true still shows the stat row with its default
   zero values. This restores the hidden attribute's display:none behaviour. */
.rent-roll-summary[hidden] {
  display: none;
}
.rr-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.rr-stat-num {
  font-size: var(--text-lg, 1.125rem);
  font-weight: 600;
  color: var(--color-text, #111);
}
.rr-stat-lbl {
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
/* Base tone as text on a tint -- the exact defect ID 266 fixed on .kpi-flag and
   .phase-badge, in the summary card it was auditing at the time. 4.00:1 on
   --color-surface-2 in light mode; --color-warning-text takes it to 6.24:1 and
   is unchanged in dark, where the base tone already cleared AA. */
.rr-stat-secondary .rr-stat-num { color: var(--color-warning-text); }

/* #4382 -- Restyle the rent-roll summary bar to match the #4377 compact
   stat-strip pattern (.stat-strip/.rg-stat, see the #4377 block later in
   this file) instead of the bulky auto-fit grid above. Additive override
   layer ONLY: the base .rent-roll-summary/.rr-stat rules above stay exactly
   as they are (nothing else in the app reuses those selectors outside this
   one summary bar, but overriding in place rather than deleting keeps this
   diff small and reversible). No id changes -- renderRentRollSummary() in
   app.js keeps writing into the same #rrSum* ids untouched.

   Follow-up fix (post-review): #rentRollSummary itself is NOT the scroll
   container. It is a plain flex row with exactly two children: the new
   .rr-stat-strip-scroll wrapper (which scrolls) and .rr-summary-actions
   (which does not). Putting overflow-x:auto directly on #rentRollSummary
   put the Refresh button inside the same scrolling sequence as the stat
   tiles, so once the strip overflowed, the button scrolled off with no
   affordance telling anyone it was still there (#4382 review). Splitting
   the scroll container from the action button means the button is always
   fully visible regardless of tile count or scroll position -- it no
   longer needs margin-left:auto or any pinning trick, it's simply a
   second, non-scrolling flex item. */
#rentRollSummary {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-4);
  max-width: 100%;
}
/* #1575's [hidden] fix above was written for display:grid; repeat it here
   since this rule now sets display:flex on the same element and the later
   rule in the file wins otherwise. */
#rentRollSummary[hidden] {
  display: none;
}
#rentRollSummary .rr-stat-strip-scroll {
  display: flex;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  min-width: 0;
  flex: 1 1 auto;
}
#rentRollSummary .rr-stat {
  flex-direction: column;
  gap: 2px;
  padding-right: var(--space-4);
  margin-right: var(--space-4);
  border-right: 1px solid var(--color-divider);
  min-width: fit-content;
  flex-shrink: 0;
}
/* The Refresh market rent (all units) action is a sibling of
   .rr-stat-strip-scroll, not one of its children (see the HTML comment on
   #rentRollSummary) -- it is a fixed, always-visible flex item at the end
   of the row, independent of whether the stat strip is scrolled. */
#rentRollSummary .rr-summary-actions {
  flex-shrink: 0;
}
#rentRollSummary .rr-stat:last-of-type {
  border-right: none;
  margin-right: 0;
  padding-right: 0;
}
#rentRollSummary .rr-stat-num {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
#rentRollSummary .rr-stat-lbl {
  font-size: var(--text-xs);
  white-space: nowrap;
}
/* Under ~420px (this app's existing narrow-mobile breakpoint, matching the
   #4377 .stat-strip rule): stay a single non-wrapping row that scrolls
   instead of wrapping to multiple rows. */
@media (max-width: 420px) {
  #rentRollSummary .rr-stat-strip-scroll {
    flex-wrap: nowrap;
  }
  #rentRollSummary .rr-stat {
    min-width: 108px;
  }
}
.rr-occ-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 12px;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 500;
  text-transform: capitalize;
}
/* ID 266 converted .rr-tenant-chip-empty and .rr-fresh-missing to theme tokens
   and left the other nine members of this chip family on hardcoded light
   literals, so the Rent Roll it reported dark-mode-clean still renders dark
   green/amber/slate text on its own translucent tint over a near-black surface
   -- 2.06:1 for the occupied pill. Same tint/-text pairing as .phase-badge. */
.rr-occ-occupied {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}
.rr-occ-notice {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.rr-occ-vacant {
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
}
/* #243: a unit nobody lives in yet whose next lease is already signed. Reads as
   navy rather than green or amber on purpose -- it is neither occupied nor a
   vacancy that needs action, and colouring it like either would undo the whole
   point of splitting the state out. Same token pairing as .rr-fresh-recent
   below, which is already verified dark-mode-clean per the ID 266 note above. */
.rr-occ-committed {
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
}
.rr-tenant-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.rr-tenant-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: var(--text-xs, 0.75rem);
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
  border: 1px solid var(--color-brand-navy);
}
.rr-tenant-chip-empty {
  color: var(--color-text-muted);
  font-style: italic;
}
/* ID 898: the chip became a button (it opens the tenant's message history), so
   it has to opt out of the user-agent button font to keep looking like a chip. */
.rr-tenant-chip-btn {
  font-family: inherit;
  line-height: inherit;
  cursor: pointer;
}
.rr-tenant-chip-btn:hover,
.rr-tenant-chip-btn:focus-visible {
  background: var(--color-brand-navy);
  color: var(--color-text-inverse);
}

/* Tenant communication history (ID 898, Phase a). */
.tenant-comms-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* #796: the .tenant-comms-* flat-row rules that used to live here (caveat,
   filters, list, row, summary, when/channel/event, subject, status, detail,
   reason, body, meta, direction, test, sender) were deleted -- every caller
   now mounts .comms-view-* (see #795's block above) instead. .tenant-comms-
   panel (layout wrapper) and .tenant-comms-card (modal sizing) are still
   live and kept, above and below this comment respectively. */
.rg-inline-link {
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--text-sm, 0.875rem);
  color: var(--color-primary);
  text-decoration: underline;
  cursor: pointer;
}
.rg-inline-link:hover {
  color: var(--color-primary-hover);
}
/* #426 provenance banner on the owner ticket / message detail. */
.ticket-origin {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-primary);
  border-radius: 6px;
  background: var(--color-surface-offset);
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-start;
}
.ticket-origin p {
  margin: 0;
  font-size: var(--text-sm, 0.875rem);
  color: var(--color-text);
}
.ticket-origin .ticket-origin-sub {
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted);
}
.tenant-comms-card {
  max-width: 900px;
  width: 92vw;
}
/* #795 -- shared communications view component (thread list + messages).
   Kept as its own block, separate from .tenant-comms-*, because the two are
   not visually interchangeable: .tenant-comms-* is the flat <details> row
   list this component replaces (T4/#796's job to actually swap callers
   over to), not a set of primitives this file extends. Every class below
   was checked against this file before use -- #766 found 84 button
   element-uses elsewhere pointing at undefined classes; this component only
   spends btn-export and link-btn, both already real. */
.comms-view {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.comms-view-caveat {
  margin: 0;
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--color-warning);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  font-size: var(--text-sm, 0.875rem);
}
.comms-view-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
}
.comms-view-filter {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted);
}
.comms-view-filter-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.comms-view-status {
  margin: 0;
}
/* Thread list + selected-thread messages, side by side per the #757
   wireframe -- not one flat scroll, which is the complaint that wireframe
   exists to fix. Stacks on narrow viewports the same way the wireframe's
   own message bubbles collapse their max-width at 820px. */
.comms-view-body {
  display: flex;
  gap: 16px;
  min-height: 320px;
  align-items: stretch;
}
.comms-view-list {
  flex: 0 0 260px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  max-height: 520px;
  border-right: 1px solid var(--color-divider);
  padding-right: 12px;
}
@media (max-width: 820px) {
  .comms-view-body {
    flex-direction: column;
  }
  .comms-view-list {
    flex: 0 0 auto;
    max-height: 220px;
    border-right: 0;
    border-bottom: 1px solid var(--color-divider);
    padding-right: 0;
    padding-bottom: 12px;
  }
}
.comms-view-thread-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.comms-view-thread-item:hover {
  background: var(--color-surface-offset);
}
/* Selection shown by border + background, not colour alone -- the same
   alignment-and-background rule the message bubbles below follow, applied
   to the list. */
.comms-view-thread-item-selected,
.comms-view-thread-item-selected:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-highlight);
}
.comms-view-thread-item-top {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  align-items: baseline;
}
.comms-view-thread-item-subject {
  font-weight: 600;
  font-size: var(--text-sm, 0.875rem);
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.comms-view-thread-item-count {
  flex: 0 0 auto;
  font-size: var(--text-xs, 0.75rem);
  /* --color-text-muted, not --color-text-faint: this is real readable text
     (a message count), and --color-text-faint fails WCAG AA for text
     (~2.6:1) -- it is reserved for borders/icons/decorative accents only. */
  color: var(--color-text-muted);
}
.comms-view-thread-item-preview {
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.comms-view-thread-item-when {
  font-size: 10.5px;
  color: var(--color-text-muted);
}
.comms-view-detail {
  flex: 1 1 auto;
  min-width: 0;
}
.comms-view-empty {
  padding: 24px 12px;
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--text-sm, 0.875rem);
}
.comms-view-thread {
  display: flex;
  flex-direction: column;
}
.comms-view-messages {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Message bubbles per the #757 wireframe: outbound right-aligned on a tinted
   background, inbound left-aligned on plain -- direction shown by ALIGNMENT
   AND BACKGROUND, not colour alone. The sender name in the meta line (below)
   is the fallback when alignment is lost -- print, a narrow viewport, or a
   screen reader that does not convey visual position. */
.comms-view-msg {
  max-width: 72%;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: var(--text-sm, 0.875rem);
}
.comms-view-msg-inbound {
  align-self: flex-start;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-bottom-left-radius: 2px;
}
.comms-view-msg-outbound {
  align-self: flex-end;
  background: var(--color-primary-highlight);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  border-bottom-right-radius: 2px;
}
@media (max-width: 820px) {
  .comms-view-msg {
    max-width: 90%;
  }
}
.comms-view-msg-meta {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 6px;
  font-size: 10.5px;
  color: var(--color-text-muted);
}
.comms-view-msg-who {
  font-weight: 700;
  color: var(--color-text);
}
.comms-view-channel-pill {
  font-size: 9.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 10px;
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
}
.comms-view-msg-body {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.comms-view-msg-truncated-note {
  margin: 4px 0 0;
  font-size: 10.5px;
  font-style: italic;
  color: var(--color-text-muted);
}
/* #796: the sandboxed "view as sent" disclosure, carried over from the
   Tenant Portal's flat message-history view (formerly .message-history-html/
   .message-history-frame, which had no dedicated rules of their own -- this
   inherits the same plain-<details> posture rather than inventing new chrome
   for it). The iframe itself carries sandbox="" with no allow-* tokens; this
   block is visual only and grants nothing. */
.comms-view-msg-html {
  margin: 6px 0 0;
  font-size: 10.5px;
}
.comms-view-msg-html summary {
  cursor: pointer;
  color: var(--color-text-muted);
}
.comms-view-msg-frame {
  display: block;
  width: 100%;
  min-height: 240px;
  margin-top: 6px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
}
.comms-view-msg-status {
  margin: 6px 0 0;
  font-size: 10.5px;
  color: var(--color-text-muted);
}
/* Test-send badge -- same posture as .tenant-comms-test (#336): a diagnostic
   send is a real delivery with a caller-chosen event type, so it must never
   be visually indistinguishable from a genuine notification. */
.comms-view-test-chip {
  padding: 1px 8px;
  border-radius: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border: 1px solid var(--color-warning);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.rr-market-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-start;
  /* #546: the estimate line is "Est. $1,260" + "Use it", which needs ~124px at
     0.7rem. Without a floor the column collapses under that and the adopt
     action drops to its own line. Measured against the live Rent Roll after
     left-aligning the line was not enough on its own. .rr-market-estimate
     keeps flex-wrap: wrap so a longer figure still degrades gracefully rather
     than overflowing the cell. */
  min-width: 132px;
}
.rr-freshness {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 500;
}
.rr-fresh-recent {
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
}
.rr-fresh-stale {
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
}
.rr-fresh-old {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.rr-fresh-missing {
  background: transparent;
  color: var(--color-text-muted);
  font-style: italic;
}
/* 52d -- per-unit Refresh button in the Market rent cell. */
/* ID 266: the three .rr-* buttons and the rent-roll summary card paired a
   hardcoded light background (`white` / `#f5f6f8`) with `color: var(--color-text)`,
   which inverts in dark mode -- the authenticated axe scan measured 1.21:1 on
   "Refresh market rent (all units)" and 1.12:1 on the summary figures. The
   `--color-surface-alt` and `--color-muted` fallbacks they relied on were never
   defined anywhere in this file, so those literals were always in force.
   The three hover states landed on --color-surface-2, which is 1.03:1 against
   the button's own --color-surface in light mode -- effectively no hover at
   all, where the `#f5f6f8` it replaced gave 1.08:1. They use
   --color-surface-offset now (1.19:1 light, 1.12:1 dark), which is what the
   other seven neutral-button hovers in this file already use. */
.rr-refresh-btn {
  padding: 1px 8px;
  border-radius: 4px;
  border: 1px solid var(--color-border, #cbd5e0);
  background: var(--color-surface);
  color: var(--color-text, #2d3748);
  font-size: 0.7rem;
  cursor: pointer;
  margin-top: 2px;
}
.rr-refresh-btn:hover { background: var(--color-surface-offset); }
.rr-refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.rr-refresh-btn.rr-refresh-spinning { opacity: 0.6; }

/* #533 -- editable per-unit market rent in the Rent Roll, with the RentCast
   estimate shown alongside an owner's override.

   The value renders as a button rather than a div so it is reachable by
   keyboard and announces itself; it is styled to read as text until hovered,
   because a row of button chrome in a dense table reads as noise. The manual
   variant carries a left rule rather than a different text colour -- colour
   alone would not survive the dark-mode contrast pass that ID 266 forced on
   the rest of this block.

   #546: the value carries a PERSISTENT dotted underline. It previously had a
   hover background and nothing else, so at rest nothing said it could be
   clicked -- the only affordance in the cell was "Use it", which is a
   different action entirely. A dotted rule is the conventional "editable in
   place" mark and is deliberately NOT the solid navy underline "Use it" uses,
   so the two do not read as the same control. It is muted rather than navy so
   a column of them does not turn the Rent Roll into a wall of links. */
.rr-market-value {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-text, #2d3748);
  font: inherit;
  text-align: left;
  cursor: text;
  border-radius: 3px;
  text-decoration: underline dotted var(--color-text-muted);
  text-underline-offset: 3px;
}
.rr-market-value:hover {
  background: var(--color-surface-offset);
  box-shadow: 0 0 0 3px var(--color-surface-offset);
}
.rr-market-value:focus-visible {
  outline: 2px solid var(--color-brand-navy);
  outline-offset: 2px;
}
.rr-market-value-manual {
  padding-left: 6px;
  border-left: 2px solid var(--color-brand-navy);
}
.rr-market-input {
  width: 82px;
  padding: 1px 4px;
  border: 1px solid var(--color-brand-navy);
  border-radius: 4px;
  background: var(--color-surface);
  color: var(--color-text, #2d3748);
  font: inherit;
}
.rr-market-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 500;
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
}
/* #3385: confirms a manually-entered market rent as correct without
   discarding it for the RentCast estimate. Deliberately understated (text
   button, not a filled button) -- it sits right next to the Unverified
   badge it's meant to clear, not competing with Refresh/Use it for
   attention. */
.rr-market-verify-btn {
  border: none;
  background: none;
  padding: 0;
  margin-left: 4px;
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--color-brand-navy);
  text-decoration: underline;
  cursor: pointer;
}
.rr-market-verify-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
.rr-market-estimate {
  /* The Market Rent column is narrow. Let the two pieces fall onto separate
     lines if they must, but never let either one break internally -- without
     the nowrap, "Use it" split across two lines with "it" alone underneath.

     #546: flex-start, not flex-end. Every other element in .rr-market-cell is
     left-aligned (the cell is align-items: flex-start), so a right-aligned
     estimate line put "Use it" on its own line hard against the right edge,
     reading as a detached action rather than as something attached to the
     figure it adopts. The label is also "Est." now, and .rr-market-cell
     carries a min-width so the pair actually fits on that one line. */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-start;
  gap: 2px 6px;
  margin-top: 2px;
  font-size: 0.7rem;
  color: var(--color-text-muted);
}
.rr-market-estimate > * { white-space: nowrap; }
.rr-market-use-estimate {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-brand-navy);
  font: inherit;
  font-weight: 500;
  text-decoration: underline;
  cursor: pointer;
}
.rr-market-use-estimate:hover { text-decoration: none; }
.rr-market-use-estimate:disabled { opacity: 0.5; cursor: not-allowed; }
/* Keep the row's own Refresh from reading as part of the estimate control. */
.rr-market-estimate + .rr-refresh-btn { margin-top: 6px; }
.rr-summary-actions {
  display: inline-flex;
  margin-left: auto;
  align-items: center;
}
.rr-refresh-all-btn {
  padding: 6px 14px;
  border-radius: 6px;
  border: 1px solid var(--color-border, #cbd5e0);
  background: var(--color-surface);
  color: var(--color-text, #2d3748);
  font-size: var(--text-sm, 0.875rem);
  cursor: pointer;
}
.rr-refresh-all-btn:hover:not(:disabled) { background: var(--color-surface-offset); }
.rr-refresh-all-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* 52d -- rent-roll refresh toast (self-contained). */
.rr-toast {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 9999;
  padding: 10px 14px;
  border-radius: 6px;
  background: #2d3748;
  color: white;
  font-size: var(--text-sm, 0.875rem);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  max-width: 360px;
}
.rr-toast-visible { opacity: 1; transform: translateY(0); }
.rr-toast-ok { background: #276749; }
.rr-toast-warn { background: #9c4221; }
.rr-actions-col { width: 1%; white-space: nowrap; }
/* #868: keep the Actions column visible at common laptop widths.
   position: sticky on the last column ensures inline-edit Save/Cancel
   buttons stay reachable without horizontal scrolling. */
#rentRollTable { min-width: 960px; }
/* #4365: shadow signals the sticky Actions column is a pinned overlay; see .rr-portal-col for the clearance fix. */
#rentRollTable thead th.rr-actions-col,
#rentRollTable tbody td:last-child {
  position: sticky;
  right: 0;
  background: var(--color-surface, #fff);
  z-index: 1;
  box-shadow: -8px 0 8px -4px rgba(0, 0, 0, 0.16);
}
#rentRollTable thead th.rr-actions-col {
  z-index: 2;
}
.rr-row-actions {
  display: inline-flex;
  gap: 6px;
}
.rr-row-btn {
  padding: 2px 8px;
  border-radius: 4px;
  border: 1px solid var(--color-border, #cbd5e0);
  background: var(--color-surface);
  color: var(--color-text, #2d3748);
  font-size: var(--text-xs, 0.75rem);
  cursor: pointer;
}
.rr-row-btn:hover { background: var(--color-surface-offset); }
/* ID 266 moved the base rule's background off the `white` literal but left
   .danger on its light-mode red, taking it from 5.47:1 on white to 3.13:1 on
   the dark surface -- and its hover still painted a white button in dark mode.
   6.02:1 light / 5.89:1 dark now, with the tint doing the hover's job. */
.rr-row-btn.danger { color: var(--color-error-text); border-color: var(--color-error); }
.rr-row-btn.danger:hover { background: var(--color-error-highlight); }
/* #2955: action-area redesign. .rr-row-btn.accent is the one call-to-action
   weight class -- used by the context-aware primary button in its "Start
   lease" state, and by the "Start incoming lease" second button on a
   notice_given row with no incoming lease yet (the one row state that earns
   a second visible button, per the redesign wireframe). */
.rr-row-btn.accent {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
.rr-row-btn.accent:hover { background: var(--color-primary-hover); }
.rr-row-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.rr-edit-input {
  width: 100%;
  min-width: 60px;
  padding: 2px 4px;
  font-size: var(--text-sm, 0.875rem);
  border: 1px solid var(--color-border, #cbd5e0);
  border-radius: 4px;
}
.rr-edit-input.rr-edit-label { min-width: 100px; }
/* #1297: source / observed date / confidence stacked into the Label cell,
   same reasoning as the Sq ft cell below -- a detail of the record, not a
   peer column, in an already eleven-column-wide table. */
.rr-provenance-edit { display: grid; gap: 3px; margin-top: 4px; text-align: left; }
.rr-provenance-edit .rr-provenance-input { min-width: 0; width: 100%; font-size: var(--text-xs, 0.75rem); }

/* #1298 -- record-level provenance confidence badge, shown wherever a unit
   (and eventually asset) value is displayed. Deliberately a distinct class
   from .capex-forecast-conf-* (a different, pre-existing confidence scale
   for CapEx forecasting -- estimated/verified -- not #1296's
   assumed/observed/verified) and from .systems-badge-* (condition, not
   confidence). 'assumed' reads as Unverified (#1300 convention) and gets a
   warning treatment so it does not carry the same visual weight as a
   verified figure -- the whole point of the epic. */
.rr-provenance-badge {
  display: inline-block;
  font-size: var(--text-xs);
  line-height: 1;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
  vertical-align: middle;
}
.rr-provenance-badge-assumed {
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}
.rr-provenance-badge-observed {
  color: var(--color-text-muted);
}
.rr-provenance-badge-verified {
  color: var(--color-success-text);
}
.rr-provenance-badge-title {
  font-size: var(--text-sm);
  vertical-align: middle;
  margin-left: var(--space-2, 8px);
}
/* #283: the Sq ft edit cell stacks the total over the above/below grade pair.
   The two grade inputs get a smaller min-width than .rr-edit-input's 60px
   because they sit side by side inside one already-narrow column. */
/* 210px, not the 118px the single input needed: the disagreement note below is
   a full sentence, and in a narrow cell it wrapped to seven lines and doubled
   the height of the row being edited. */
.rr-sqft-edit { display: grid; gap: 3px; min-width: 210px; text-align: left; }
.rr-sqft-grade { display: flex; gap: 3px; }
.rr-sqft-grade .rr-edit-input { min-width: 0; }
.rr-sqft-note {
  margin: 0;
  text-align: left;
  font-size: var(--text-xs, 0.75rem);
  line-height: 1.3;
  color: var(--color-warning-text);
}
.rr-sqft-note[hidden] { display: none; }
/* The read-only split under the total. Muted and smaller: it is a detail of
   the footage above it, not a second figure competing with it. */
.rr-sqft-split {
  display: block;
  /* nowrap is load-bearing: the Sq ft column is narrow, and without it the
     sub-line breaks after every word and the row grows to four lines. */
  white-space: nowrap;
  font-size: var(--text-xs, 0.75rem);
  line-height: 1.3;
  color: var(--color-text-muted);
}
.rr-sqft-split-warn { color: var(--color-warning-text); }
/* #283: the "here is what Analyze has for the building" hint under the empty
   grade inputs. Muted, because it is context and not a value. */
.rr-sqft-hint {
  margin: 0;
  text-align: left;
  font-size: var(--text-xs, 0.75rem);
  line-height: 1.3;
  color: var(--color-text-muted);
}
/* #491: marks a vacant unit that has a saved draft lease. Deliberately NOT a
   pill -- the occupancy pill states a fact about the unit (it is vacant, and a
   draft does not change that), while this is a note about work in progress.
   Styling it as a second pill would read as a competing occupancy state. */
.rr-draft-note {
  display: block;
  white-space: nowrap;
  margin-top: 2px;
  font-size: var(--text-xs, 0.75rem);
  line-height: 1.3;
  color: var(--color-text-muted);
}
/* #1339: a signed lease after a current occupant is context for the turnover,
   not a second occupancy state and never a component of the Actual-rent cell. */
.rr-incoming-lease {
  display: block;
  margin-top: 4px;
  font-size: var(--text-xs, 0.75rem);
  line-height: 1.3;
  color: var(--color-text-muted);
}
.unit-page-incoming-lease {
  margin-top: var(--space-4, 1rem);
  padding-top: var(--space-4, 1rem);
  border-top: 1px solid var(--color-border, #ddd);
}
/* #4365: the sticky Actions column (.rr-actions-col, #868) is opaque and
   sized to its widest per-row state -- a turnover row needing Open lease +
   Open incoming + the ⋯ overflow menu measures ~272px, well past the 1%
   width the table's own layout budgeted for it. Because sticky removes it
   from that width accounting, at rest (scrollLeft 0) it silently painted
   over the tail end of Portal's Balance/Activity content instead of being
   clipped -- reachable by scrolling right, but looked broken and was easy
   to miss since nothing hinted more content continued underneath. Reserve
   real clearance here (widest observed Actions width + a small margin) so
   Portal's own content never physically extends under the overlay, instead
   of only hinting at the boundary via the shadow above. */
.rr-portal-col { min-width: 150px; padding-right: 96px; }
.rr-portal-status {
  display: grid;
  justify-items: start;
  gap: 4px;
  color: var(--color-text-muted);
  font-size: var(--text-xs, 0.75rem);
}
.rr-portal-status strong { color: var(--color-text); font-size: var(--text-sm, 0.875rem); }
.rr-autopay-pill {
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 2px 7px;
  border-radius: 999px;
  color: var(--color-text-muted);
  background: var(--color-surface-offset);
  font-size: 0.7rem;
  font-weight: 600;
}
/* Last members of the rent-roll chip family still on hardcoded light literals
   over a translucent tint: in dark mode that tint sits on a near-black surface
   and the dark green/amber/red text drops to 1.9-2.4:1. Same tint/-text pairing
   as .rr-occ-* and .phase-badge. */
.rr-autopay-active { color: var(--color-success-text); background: var(--color-success-highlight); }
.rr-autopay-pending { color: var(--color-warning-text); background: var(--color-warning-highlight); }
.rr-autopay-failed { color: var(--color-error-text); background: var(--color-error-highlight); }
.rr-insurance-badge {
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
}
.rr-insurance-insured { color: var(--color-success-text); background: var(--color-success-highlight); }
.rr-insurance-uninsured { color: var(--color-error-text); background: var(--color-error-highlight); }
.rr-portal-activity-btn {
  padding: 2px 7px;
  border: 0;
  color: var(--color-primary);
  background: transparent;
  font-size: 0.72rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.portal-activity-card { width: min(560px, calc(100vw - 32px)); }
#adapterErrorDetailModal .modal-card {
  width: min(680px, calc(100vw - 32px));
  max-width: none;
  max-height: calc(100vh - 64px);
  overflow-y: auto;
}
.json-pretty {
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: var(--space-3);
  font-family: 'Consolas', 'Courier New', monospace;
  font-size: 0.8rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 260px;
  overflow-y: auto;
}
.portal-activity-list { display: grid; gap: var(--space-2); }
.portal-activity-item {
  display: grid;
  gap: 3px;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-2);
}
.portal-activity-item strong { text-transform: capitalize; }
.portal-activity-item span { color: var(--color-text-muted); font-size: var(--text-xs, 0.75rem); }

/* Tenant detail edit form */
.tenant-edit-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-3);
  max-width: 640px;
  margin: var(--space-3) 0;
}
/* #871: the class sets display:grid which beats the browser's [hidden]
   { display: none } default at equal specificity. Without this companion
   rule, form.hidden = true in JS has no visual effect and the form is
   permanently visible — the same bug class already fixed for
   .data-feed-sync-card (line 643). */
.tenant-edit-form[hidden] {
  display: none;
}
.tenant-edit-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted);
}
.tenant-edit-form label input,
.tenant-edit-form label textarea {
  padding: 6px 8px;
  border: 1px solid var(--color-border, #cbd5e0);
  border-radius: 4px;
  font-size: var(--text-sm, 0.875rem);
  color: var(--color-text, #111);
}
.tenant-edit-form label.wide { grid-column: 1 / -1; }
.tenant-edit-form-actions {
  grid-column: 1 / -1;
  display: flex;
  gap: var(--space-2);
}
.tenant-archive-note {
  color: #c53030;
  background: #fff5f5;
  border-left: 3px solid #fc8181;
  padding: var(--space-2) var(--space-3);
  border-radius: 4px;
  margin: var(--space-3) 0;
}

.ld-section {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.ld-section:last-of-type {
  border-bottom: none;
}
.ld-section-heading {
  margin: 0 0 var(--space-3);
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
}
.ld-section-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
  flex-wrap: wrap;
}
.ld-section-heading-row .ld-section-heading {
  margin: 0;
}
.ld-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
}
/* #1684 fix (post-QA): the class above sets display:grid which beats the
   browser's [hidden] { display: none } default at equal specificity, same
   bug class as .tenant-edit-form[hidden] (#871) and the many other
   instances documented throughout this file. Without this companion rule,
   el.hidden = true in JS has no visual effect on any .ld-grid element --
   caught via #imRequiredFieldsWrap (Renters insurance popup: toggling
   "Required" to "No" set hidden but the Carrier/Policy/Expiry grid stayed
   visible), but this is a general .ld-grid problem, not specific to that
   one id, so the rule is scoped to the class rather than one element. */
.ld-grid[hidden] {
  display: none;
}
/* #1884: fanoutNoticeUpdateSourceModeUI hides the filled-PDF label with the
   native hidden attribute. As in #1617, this flex rule must not override that
   state or the upload row remains visibly rendered in template mode. */
.ld-field:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}
.ld-field > span {
  font-weight: 500;
}
/* #6540: excludes radio/checkbox -- this descendant selector reaches into any
   nested .ld-void-field radio option label too (e.g. #fanoutNoticeSourceModeFieldset's
   "How are you sending this notice?" choices), and width:100% on a flex-row radio
   input blows its flex box out to the full row width, shoving the sibling label
   text far to the right instead of sitting right next to the (visually unchanged,
   still small) radio glyph. A radio/checkbox should never be width-stretched. */
.ld-field input:not([type="radio"]):not([type="checkbox"]),
.ld-field select,
.ld-field textarea {
  width: 100%;
  padding: 6px 10px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 0.9rem;
  font-family: inherit;
  background: var(--color-surface);
  color: var(--color-text);
}
/* #4177 QA fix (5a0bee96): a per-lease override field showing its inherited
   org-default placeholder needs a visual tell distinct from a real saved
   value -- dashed border + muted placeholder text, same dashed language
   .ld-subsection already uses elsewhere on this screen for "derived, not
   entered" content. Placeholder color is set explicitly because browsers
   default ::placeholder to a lighter tint than --color-text-muted, which
   would otherwise undercut the intended contrast with a real value. */
.ld-field input.ld-field-inherited {
  border-style: dashed;
}
.ld-field input.ld-field-inherited::placeholder {
  color: var(--color-text-muted);
  opacity: 1;
}
.ld-status-badge {
  align-self: flex-start;
  font-size: 0.85rem;
}
.ld-field-wide {
  grid-column: 1 / -1;
}
/* #1561: divider between the edit-lease inline form's field groups (term,
   rent, late fee, notes), same simple hr treatment used elsewhere on the
   record page rather than a full section boundary. */
.ld-form-divider {
  border: none;
  border-top: 1px solid var(--color-border, #e0e0e0);
  margin: var(--space-3, 12px) 0;
}
/* #1052 -- small text-link button under the holding-account select, to open
   the Settings > Deposit holding accounts manager without leaving the
   lease drawer. */
.ld-field-link {
  align-self: flex-start;
  margin-top: 2px;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-primary, #2563eb);
  font-size: 0.8rem;
  font-weight: 500;
  cursor: pointer;
  text-decoration: underline;
}
.ld-field-link:hover {
  opacity: 0.8;
}
/* #439 -- per-lease late fee override controls. */
.ld-late-fee-controls {
  gap: 6px;
}
.ld-checkbox-row {
  /* #4958: this is also used on labels that are not .ld-field, so keep the
     base row layout here. The combined .ld-field.ld-checkbox-row selector
     below handles the higher-specificity .ld-field:not([hidden]) rule. */
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  font-weight: 500;
  cursor: pointer;
}
/* #6554: .ld-field:not([hidden]) has specificity (0,2,0), which beats the
   standalone .ld-checkbox-row selector (0,1,0) and otherwise stacks the
   checkbox above its own label text. Match that specificity so this row
   declaration wins for the combined class used by filters and forms. */
.ld-field.ld-checkbox-row {
  flex-direction: row;
}
.ld-checkbox-row input[type="checkbox"] {
  width: auto;
  margin: 0;
  flex: 0 0 auto;
}
/* #4958: a full-width row for one or more .ld-checkbox-row controls that
   need to sit BELOW an .ld-grid of text/select fields rather than occupying
   one of the grid's own equal-width cells -- the bug this replaces (lease
   record's Add tenant / New tenant forms) was a lone checkbox+label dropped
   into .ld-grid as if it were a fourth text field, where .ld-field's
   flex-direction:column and grid cell width left it floating, undersized,
   and visually disconnected from the row of real fields beside it. */
.ld-checkbox-group-row {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4, 16px);
  padding-top: 4px;
}
.ld-field-note {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.45;
  font-weight: 400;
  color: var(--color-text-muted);
}
.ld-subsection {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border);
}
.ld-subsection-heading {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.ld-subsection-heading h4 {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 600;
}
.ld-rent-history-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  font-size: 0.85rem;
  color: var(--color-text-muted);
}
.ld-rent-history-item {
  padding: 6px 10px;
  background: var(--color-surface-2);
  border-radius: 4px;
}
.ld-rent-bump-form {
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface-2);
  border-radius: 6px;
}
.ld-inline-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
  margin-top: var(--space-2);
}
.ld-tenants-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.ld-tenant-row {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr auto;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border-radius: 6px;
  font-size: 0.9rem;
}
.ld-tenant-row.removed {
  opacity: 0.55;
}
/* #254: "Liable for" readout, in the column the share-of-rent input used to
   occupy. Muted because it is derived from the lease, not editable. */
.ld-tenant-liable {
  font-size: var(--text-sm, 0.85rem);
  color: var(--color-text-muted, #6b7280);
  font-variant-numeric: tabular-nums;
}
.ld-tenant-name {
  font-weight: 600;
  color: var(--color-text);
}
.ld-tenant-name a {
  color: inherit;
  text-decoration: none;
}
.ld-tenant-name a:hover {
  text-decoration: underline;
}
.ld-tenant-meta {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}
.ld-tenant-picker,
.ld-tenant-create,
.ld-tenant-invite,
.ld-give-notice-form,
.ld-renew-form,
.ld-end-form {
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface-2);
  border-radius: 8px;
  border: 1px solid var(--color-border);
}
/* #1054: expired-with-tenant-in-place banner. Sits above the Term section so
   it is the first thing an owner sees on a lease that needs a decision, using
   the same AA-audited error pair as .badge-expired-action for the same
   "this is the one that's actually overdue" urgency. */
.ld-expired-action-banner {
  margin-bottom: var(--space-3);
  padding: var(--space-3);
  background: var(--color-error-highlight);
  border: 1px solid var(--color-error);
  border-radius: 8px;
}
.ld-expired-action-text {
  margin: 0 0 var(--space-2) 0;
  color: var(--color-error-text);
}
.ld-picker-search {
  width: 100%;
  padding: 6px 10px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  margin-bottom: var(--space-2);
}
.ld-picker-list {
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: #fff;
  margin-bottom: var(--space-3);
}
.ld-picker-item {
  padding: 6px 10px;
  cursor: pointer;
  border-bottom: 1px solid var(--color-border);
  font-size: 0.9rem;
}
.ld-picker-item:last-child { border-bottom: none; }
.ld-picker-item:hover,
.ld-picker-item.selected {
  background: var(--color-primary-highlight);
}
.ld-picker-item .ld-picker-item-meta {
  font-size: 0.78rem;
  color: var(--color-text-muted);
}
.ld-picker-empty {
  padding: var(--space-3);
  text-align: center;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}
.ld-notes {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.9rem;
  resize: vertical;
}
.ld-actions-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-4);
  margin-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  flex-wrap: wrap;
}
.ld-actions-right {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}
.ld-status {
  font-weight: 500;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}
.ld-error {
  background: #fef2f2;
  border-left: 3px solid #fc8181;
  padding: var(--space-2) var(--space-3);
  border-radius: 4px;
}
.ld-document-upload {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-2);
}
.ld-document-visible {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--color-text-muted);
  font-size: 0.85rem;
}
.ld-document-visible input { width: 16px; height: 16px; }
.ld-document-upload .btn-export { justify-self: start; }
.ld-document-list { display: grid; gap: var(--space-2); margin-bottom: var(--space-3); }
.ld-document-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}
.ld-document-row > div:first-child { display: grid; gap: 3px; min-width: 0; }
.ld-document-row strong, .ld-document-row span { overflow-wrap: anywhere; }
.ld-document-row span { color: var(--color-text-muted); font-size: 0.78rem; }
/* Rent Roll unit-row More menu popover */
.rr-unit-more-wrap {
  position: relative;
  display: inline-block;
}
/* #2953: this used to be `position: absolute` relative to .rr-unit-more-wrap,
   which sits inside <div class="grid-scroll">. .grid-scroll sets
   overflow-x: auto with no explicit overflow-y, and per the CSS spec the
   browser computes overflow-y as auto too whenever overflow-x is anything
   but visible -- so the scroll container clipped the dropdown vertically any
   time it extended below the visible scroll area, leaving only the first
   item clickable. Fixed-position, computed from the trigger button's real
   getBoundingClientRect() at open time (see the moreBtn click handler in
   app.js), escapes that ancestor clipping entirely -- the standard pattern
   for a dropdown inside a scrolling table wrapper. `right`/`top` custom
   properties are set inline by that same handler; the declarations below are
   just the fallback before JS has positioned it once.
*/
.rr-unit-more-menu {
  position: fixed;
  top: var(--rr-more-menu-top, 0px);
  left: var(--rr-more-menu-left, 0px);
  min-width: 200px;
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.08);
  padding: 4px 0;
  z-index: 1000;
}
.rr-unit-more-menu button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 14px;
  border: none;
  background: transparent;
  font-size: 0.88rem;
  color: var(--color-text);
  cursor: pointer;
}
.rr-unit-more-menu button:hover {
  background: var(--color-surface-2);
}
.rr-unit-more-menu button.danger {
  color: #c53030;
}
.rr-unit-more-menu button[disabled] {
  color: var(--color-text-muted);
  cursor: not-allowed;
}
/* #2955: two labeled groups -- lease-lifecycle actions above a divider
   (most used), unit-level actions below (rare). Inapplicable items are
   simply not rendered for that row's state, never disabled-with-a-tooltip. */
.rr-unit-more-menu .rr-unit-more-group-label {
  padding: 6px 14px 2px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
}
.rr-unit-more-menu hr {
  margin: 4px 0;
  border: none;
  border-top: 1px solid var(--color-border);
}

/* CapEx & RUL Report items table: rows are clickable and jump to the
   owning property's Repairs modal focused on that specific item. Give
   them a clear affordance + keyboard focus ring. */
.capex-rul-table tr.capex-rul-row {
  cursor: pointer;
  transition: background 0.12s ease;
}
.capex-rul-table tr.capex-rul-row:hover {
  background: var(--color-surface-2);
}
.capex-rul-table tr.capex-rul-row:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

.phase-mix-donut-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  flex-wrap: wrap;
}
.phase-mix-donut {
  position: relative;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  flex-shrink: 0;
}
.phase-mix-donut::after {
  content: '';
  position: absolute;
  top: 28px;
  left: 28px;
  width: 84px;
  height: 84px;
  border-radius: 50%;
  background: var(--color-surface);
}
.phase-mix-donut-total {
  position: absolute;
  top: 0;
  left: 0;
  width: 140px;
  height: 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  z-index: 1;
}
.phase-mix-donut-total-count {
  font-size: 1.4rem;
  line-height: 1.1;
}
.phase-mix-donut-total-label {
  font-size: 0.7rem;
  font-weight: 500;
  opacity: 0.7;
}
.phase-mix-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  min-width: 160px;
}
.phase-mix-legend li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
}
.phase-mix-legend-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex-shrink: 0;
}
.phase-mix-legend-count {
  margin-left: auto;
  font-weight: 600;
}
.capex-row-sold {
  opacity: 0.6;
}
.capex-row-sold td {
  font-style: italic;
}

/* ID 462: the Sold Properties rows are clickable (they open the saved record).
   This used to be an inline style="cursor:pointer" on each <tr>; ListKit's
   rowAttrs deliberately refuses a `style` attribute, so the affordance lives
   here instead. */
.sold-properties-row {
  cursor: pointer;
}

/* #1568: property names and whole rows that open report detail views need a
   visible pointer affordance as well as a persistent keyboard-focus outline. */
.report-keyboard-target {
  color: var(--color-primary);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.report-clickable-row {
  cursor: pointer;
}
.sell-history-row.report-clickable-row .note-author,
.adapter-error-row.report-clickable-row > td:first-child,
.sold-properties-row.report-clickable-row > td:first-child {
  color: var(--color-primary);
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.report-keyboard-target:focus-visible,
.report-clickable-row:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* #3741: Reports Center landing page. Left-rail category filter + a
   ListKit-backed searchable middle list, mirroring the Zoho Books Reports
   Center layout Tony referenced. Stacks to a single column on narrow
   viewports since the category rail would otherwise crowd out the list. */
.reports-center-layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: var(--space-5);
  align-items: start;
}
@media (max-width: 720px) {
  .reports-center-layout {
    grid-template-columns: 1fr;
  }
}
.reports-center-categories {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  position: sticky;
  top: var(--space-3);
}
.reports-center-category-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
.reports-center-category-item:hover {
  background: var(--color-bg);
}
.reports-center-category-item.is-active {
  background: var(--color-primary-highlight, var(--color-bg));
  color: var(--color-primary);
  font-weight: 600;
}
.reports-center-row > td:first-child {
  color: var(--color-primary);
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}

/* #706: overdue deposits sort first in the queue, but density is not the same
   as visibility -- a warning-tinted row background makes the worst offenders
   obvious at a glance instead of relying only on the Days Over column. Same
   --color-warning-highlight token as the #705 balance-owed banner. */
.deposits-queue-row-overdue {
  background: var(--color-warning-highlight);
}
.deposits-queue-row-overdue td {
  font-weight: 600;
}
/* The sticky first column has its own opaque background (see
   .grid-scroll .mini-table td:first-child above) that would otherwise leave
   a plain, untinted gap over an overdue row. */
.grid-scroll .mini-table tr.deposits-queue-row-overdue td:first-child {
  background: var(--color-warning-highlight);
}

/* #3699: highlights the one row named by ?mappingId= on the Zoho GL
   Mapping screen (deep link from elsewhere in the app), same
   --color-warning-highlight token and same row-tint reasoning as
   .deposits-queue-row-overdue just above -- this one is a temporary
   "you were sent here" cue rather than a standing data-condition tint. */
.zoho-gl-mappings-row-highlight {
  background: var(--color-warning-highlight);
}

/* Tenant Portal branding editor */
#tenant-branding-main {
  max-width: min(96vw, 1600px);
}
.branding-included-note {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-5);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.branding-locked-note {
  margin: calc(var(--space-5) * -0.4) 0 var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.branding-locked-note h2 {
  margin: var(--space-1) 0 var(--space-2);
}
.branding-locked-note p:last-child {
  max-width: 920px;
  margin-bottom: 0;
  color: var(--color-text-muted);
}
.branding-included-note h2,
.branding-preview-heading h2,
.branding-form .settings-card h2 {
  margin: var(--space-1) 0 var(--space-2);
}
.branding-included-note p:last-child {
  max-width: 760px;
  margin-bottom: 0;
  color: var(--color-text-muted);
}
.branding-editor-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(390px, .95fr);
  gap: var(--space-6);
  align-items: start;
}
.branding-form {
  min-width: 0;
}
.branding-form .settings-card {
  margin-bottom: var(--space-4);
}
.branding-form .field {
  margin-top: var(--space-4);
}
.branding-url-input {
  display: flex;
  align-items: center;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.branding-url-input:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.branding-url-input span {
  align-self: stretch;
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  border-right: 1px solid var(--color-border);
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  font-size: var(--text-sm);
  white-space: nowrap;
}
.branding-url-input input {
  min-width: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.branding-logo-current {
  display: grid;
  gap: var(--space-4);
}
.branding-logo-stage {
  display: grid;
  place-items: center;
  min-height: 132px;
  padding: var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  background: var(--color-surface-2);
}
.branding-logo-stage img {
  width: auto;
  max-width: min(100%, 360px);
  max-height: 96px;
  object-fit: contain;
}
.branding-logo-stage img[hidden] {
  display: none;
}
.branding-file-label {
  cursor: pointer;
}
.branding-color-fieldset {
  margin: var(--space-4) 0;
  padding: 0;
  border: 0;
}
.branding-color-fieldset legend {
  margin-bottom: var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.branding-color-presets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.branding-color-swatch {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 3px solid var(--color-surface);
  border-radius: 50%;
  outline: 1px solid var(--color-border);
  background: var(--swatch);
}
.branding-color-swatch:hover {
  transform: translateY(-1px);
}
.branding-color-swatch.is-selected {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}
.branding-custom-color {
  display: grid;
  grid-template-columns: auto 52px minmax(120px, 180px);
  gap: var(--space-3);
  align-items: center;
}
.branding-custom-color label {
  margin: 0;
}
.branding-custom-color input[type='color'] {
  width: 52px;
  height: 42px;
  padding: 3px;
  cursor: pointer;
}
.branding-save-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.branding-preview-column {
  position: sticky;
  top: var(--space-4);
  min-width: 0;
}
.branding-preview-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.branding-preview-frame {
  overflow: hidden;
  height: min(760px, calc(100vh - 180px));
  min-height: 560px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
}
.branding-preview-frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: #eef3f7;
}
#tenantBrandingMessage.field-success {
  padding: var(--space-3);
  border-left: 3px solid var(--color-success);
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 8%, var(--color-surface));
}
#tenantBrandingMessage.field-error {
  padding: var(--space-3);
  border-left: 3px solid var(--color-error);
  background: color-mix(in srgb, var(--color-error) 8%, var(--color-surface));
}
#brandingContrastNote.field-success {
  padding: var(--space-3);
  border-left: 3px solid var(--color-success);
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 8%, var(--color-surface));
}
#brandingContrastNote.field-error {
  padding: var(--space-3);
  border-left: 3px solid var(--color-error);
  color: var(--color-error);
  background: color-mix(in srgb, var(--color-error) 8%, var(--color-surface));
}
.branding-form .visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}
@media (max-width: 1050px) {
  .branding-editor-layout {
    grid-template-columns: 1fr;
  }
  .branding-preview-column {
    position: static;
  }
  .branding-preview-frame {
    height: 700px;
  }
}
@media (max-width: 620px) {
  .branding-included-note,
  .branding-preview-heading {
    display: grid;
  }
  .branding-included-note .status-pill,
  .branding-preview-heading .status-pill {
    justify-self: start;
  }
  .branding-url-input {
    display: grid;
  }
  .branding-url-input span {
    min-height: 42px;
    border-right: 0;
    border-bottom: 1px solid var(--color-border);
  }
  .branding-custom-color {
    grid-template-columns: 1fr 52px;
  }
  .branding-custom-color input[type='text'] {
    grid-column: 1 / -1;
  }
  .branding-preview-frame {
    height: 620px;
    min-height: 520px;
  }
}

/* ============================================================
   ID 140: Stitched multi-loan amortization view
   ============================================================
   Shown on the Financing sub-tab's Amortization panel only when
   the property has 1+ refis (in financed-active state). Includes:
     - view toggle chip (Stitched | By loan segment)
     - summary strip (total interest paid / principal paid / months)
     - segment-boundary row styling on the stitched table
     - collapsible per-segment cards for the segmented view
*/
.amort-view-toggle {
  display: inline-flex;
  gap: 0;
  margin: 0 0 var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface-2);
}
/* ID 431: explicit [hidden] rules for the three containers below. Each sets
   `display` via its class, and author styles beat the browser's
   `[hidden] { display: none }` at any specificity -- same reason
   .data-feed-sync-card[hidden] exists above. renderAmortTable() hides all
   three on the classic single-loan path by setting .hidden, so without these
   the toggle and summary strip would render on every financed property. */
.amort-view-toggle[hidden] {
  display: none;
}
.amort-view-toggle-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  padding: 6px 14px;
  font-size: var(--text-sm, 0.875rem);
  font-weight: 500;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
.amort-view-toggle-btn + .amort-view-toggle-btn {
  border-left: 1px solid var(--color-divider);
}
.amort-view-toggle-btn:hover:not(.is-active) {
  color: var(--color-text);
}
.amort-view-toggle-btn.is-active {
  background: var(--color-surface, #fff);
  color: var(--color-text);
  font-weight: 600;
}
.amort-view-toggle-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

.amort-summary-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin: 0 0 var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
}
.amort-summary-strip[hidden] {
  display: none;
}
.amort-summary-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 140px;
}
.amort-summary-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.amort-summary-value {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* Segment column + boundary marker on the stitched table. */
#amortTable td.amort-segment-cell {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}
#amortTable tr.amort-row-segment-boundary td {
  border-top: 2px solid var(--color-primary);
}
#amortTable tr.amort-row-segment-boundary td.amort-segment-cell {
  font-weight: 600;
  color: var(--color-text);
}

/* Per-segment collapsible cards (By loan segment view). */
.amort-segments {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
}
.amort-segments[hidden] {
  display: none;
}
.amort-segment-card {
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface, #fff);
  overflow: hidden;
}
.amort-segment-card > summary {
  list-style: none;
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  background: var(--color-surface-2);
  border-bottom: 1px solid transparent;
}
.amort-segment-card[open] > summary {
  border-bottom-color: var(--color-divider);
}
.amort-segment-card > summary::-webkit-details-marker { display: none; }
.amort-segment-card > summary::before {
  content: "\25B8"; /* right-pointing triangle */
  display: inline-block;
  width: 0.9em;
  transition: transform 120ms ease;
  color: var(--color-text-muted);
}
.amort-segment-card[open] > summary::before { transform: rotate(90deg); }
.amort-segment-card summary .amort-segment-title {
  font-weight: 600;
  color: var(--color-text);
  font-size: var(--text-sm, 0.875rem);
}
.amort-segment-card summary .amort-segment-meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.amort-segment-card summary .amort-segment-badge {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
  padding: 1px 6px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.amort-segment-body {
  padding: var(--space-2) var(--space-3);
}
.amort-segment-body .mini-table { margin: 0; }

/* #5065: the Visits tab's collapsed accordion(s) holding the unchanged old
   #927 panel content (schedules under Site Inspections; property/matching
   prospects under Prospect Showings) below each new flat table -- modeled on
   .amort-segment-card above rather than introducing a third accordion
   pattern, but with a plain <summary> (no title/meta/badge sub-spans; these
   accordions just need a label and a chevron). Deliberately NOT
   .showing-accordion, which is #5067's separate unit-picker accordion. */
.visits-legacy-accordion {
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface, #fff);
  overflow: hidden;
  margin-top: var(--space-3);
}
.visits-legacy-accordion + .visits-legacy-accordion {
  margin-top: var(--space-2);
}
.visits-legacy-accordion > summary {
  list-style: none;
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
  color: var(--color-text);
  font-size: var(--text-sm, 0.875rem);
  background: var(--color-surface-2);
  border-bottom: 1px solid transparent;
}
.visits-legacy-accordion[open] > summary {
  border-bottom-color: var(--color-divider);
}
.visits-legacy-accordion > summary::-webkit-details-marker { display: none; }
.visits-legacy-accordion > summary::before {
  content: "\25B8"; /* right-pointing triangle, closed by default */
  display: inline-block;
  width: 0.9em;
  transition: transform 120ms ease;
  color: var(--color-text-muted);
}
.visits-legacy-accordion[open] > summary::before { transform: rotate(90deg); }
.visits-legacy-accordion-body {
  padding: var(--space-3);
}

/* ID 86 — Deposit lifecycle wizard */
.dw-stepper { display: flex; gap: 8px; margin: 10px 0 20px; flex-wrap: wrap; }
.dw-step { padding: 4px 10px; border-radius: 4px; background: #eee; color: #666; font-size: 0.85em; }
.dw-step.is-active { background: #0d47a1; color: #fff; }
.dw-summary { padding: 12px; background: #f9f9f9; border-radius: 6px; margin: 10px 0; }
.dw-summary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 20px; }
.dw-summary-grid > div { padding: 2px 0; }
.dw-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; padding-top: 12px; border-top: 1px solid #eee; }
.dw-items-list { margin-bottom: 10px; }
.dw-totals { margin-top: 10px; }
.dw-final-total { margin-top: 20px; text-align: center; color: #0d47a1; }
.ld-deposit-banner, .ld-deposit-countdown, .ld-deposit-returned { margin-top: 10px; }

/* ---- ID 105 (P1.a): Rental Listings + Applications (Owner Portal) ----
   Reuses .grid-scroll/.mini-table (NOT .data-table/.table-wrap/.form-row,
   which are only styled in admin.css and unstyled here -- see custom-domains
   for the pre-existing gap). Status pills follow the .repair-status-pill
   tone pattern rather than the unstyled .status-pill-success/-warn/-error
   convention referenced (but never defined) by Custom Domains. */
.rg-status-pill {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  text-transform: capitalize;
  white-space: nowrap;
  display: inline-block;
}
.rg-status-pill.rg-tone-success {
  color: var(--color-success);
  border-color: var(--color-success);
}
.rg-status-pill.rg-tone-warn {
  color: var(--color-warning);
  border-color: var(--color-warning);
}
.rg-status-pill.rg-tone-error {
  color: var(--color-error);
  border-color: var(--color-error);
}
.rg-status-pill.rg-tone-neutral {
  color: var(--color-brand-navy);
  border-color: var(--color-brand-navy);
}
/* #4703: applicant completeness dot + sub-section rows on the Application
   Detail page's Applicants card. */
.rg-completeness-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: var(--space-2);
  background: var(--color-success);
  flex-shrink: 0;
}
.rg-completeness-dot.is-partial { background: var(--color-warning); }
.rg-completeness-dot.is-empty { background: var(--color-text-muted); }
.rg-applicant-heading {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.rg-applicant-completeness-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: 400;
  margin-left: var(--space-2);
}
.rg-applicant-field-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.rg-applicant-sections {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.rg-applicant-section-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--color-border);
}
.rg-applicant-section-label {
  font-size: var(--text-sm);
  font-weight: 600;
}
.rg-applicant-section-summary {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: var(--space-1) 0 0;
}
.rg-applicant-section-list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  font-size: var(--text-sm);
}
.rg-detail-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0;
}
.rg-detail-list dt {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.rg-detail-list dd {
  margin: 0;
  font-size: var(--text-sm);
}
.rg-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.rg-photo-tile {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-divider);
  aspect-ratio: 4 / 3;
  background: var(--color-surface-2);
}
.rg-photo-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.rg-photo-tile .rg-photo-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  border: none;
  border-radius: var(--radius-full);
  width: 22px;
  height: 22px;
  line-height: 1;
  cursor: pointer;
  font-size: 14px;
}
.rg-listing-card-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-divider);
}
.rg-listing-statuses {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}
.rg-listing-card-row:last-child {
  border-bottom: none;
}
.rg-inline-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* #4581: this [hidden] rule is load-bearing, not defensive. The class above
   sets `display`, and an author-level `display` declaration beats the browser's
   own `[hidden] { display: none }` at any specificity -- so without this rule
   the `hidden` attribute is silently inert on every .rg-inline-actions row and
   any JS doing `row.hidden = true` is a no-op. That is exactly how the
   adverse-action "Send now" button (applicationDetailLetterResendRow) and
   "Convert to tenant" (applicationDetailConvertRow) stayed on screen for every
   application regardless of status: appRenderDenialLetterStatus() was setting
   .hidden correctly and the CSS was throwing it away. Same reasoning as the
   .data-feed-sync-card[hidden] rule earlier in this file. */
.rg-inline-actions[hidden] {
  display: none;
}
/* #4412: multi-unit listings owner editor. */
.listings-units-summary {
  margin: 0;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-divider);
  background: var(--color-surface-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.listings-preview-btn {
  margin-left: auto;
}
/* Lightweight contenteditable rich-text field shared by the property
   description and the per-unit description modal. No third-party editor --
   a toolbar of three document.execCommand buttons plus a plain
   contenteditable div, matching the wireframe's B/I/bullet-list toolbar. */
.rg-rte-toolbar {
  display: flex;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}
.rg-rte-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  cursor: pointer;
}
.rg-rte-btn:hover {
  background: var(--color-surface-2);
}
.rg-rte-btn[aria-pressed="true"] {
  background: var(--color-surface-2);
  border-color: var(--color-text-muted);
}
.rg-rte-body {
  min-height: 6rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  line-height: 1.5;
  overflow-y: auto;
}
.rg-rte-body:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.rg-rte-body ul {
  margin: 0;
  padding-left: 1.25rem;
}
.field-counter {
  margin-top: var(--space-1);
  text-align: right;
  font-size: var(--text-xs);
}
.field-counter.is-over-limit {
  color: var(--color-danger);
}
/* Units table cells -- description column shows a plain-text truncated
   preview (real formatting only visible inside the modal), toggle columns
   reuse the existing .toggle-label checkbox pattern at table scale. */
.listings-unit-description-cell {
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  color: var(--color-primary);
}
.listings-unit-description-cell:hover {
  text-decoration: underline;
}
.listings-unit-photo-count {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.listings-unit-add-photos-link {
  display: inline-block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
}

/* #4573 -- always-visible back-to-listings control on #listingsFormCard.
   Deliberately OUTSIDE the max-width:640px query below: #4491 scoped its
   sticky back bar to mobile only, leaving desktop with no back control at all
   (the bottom "Cancel" button read as discard, not navigate). This one sits
   inline with the eyebrow/heading block rather than replacing it, so the
   header still reads as a title. */
.listings-form-heading-group {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.listings-form-back-btn {
  flex: 0 0 auto;
  white-space: nowrap;
}
/* ...but stand down under 640px, where #4491's sticky bar already provides a
   back control. Measured at 375px the 139px button squeezed the heading into
   three wrapped lines, and two back controls in one card is redundant anyway.
   This is the one width where the header button is NOT the answer. */
@media (max-width: 640px) {
  .listings-form-back-btn {
    display: none;
  }
  .listings-form-heading-group {
    display: block;
  }
}

/* =============================================================================
   #4491 -- Owner Portal mobile Edit Listing redesign
   =============================================================================
   Three pieces, scoped to #listingsFormCard only (not a global .field-row/
   .rg-inline-actions change) so nothing outside the listing editor is
   affected: (1) a mobile-only sticky top bar with a back-to-list link,
   since the only other way back was the bottom Cancel button, unreachable
   without scrolling a ~2878px form; (2) single-column field stacking on
   mobile, since the 2-col .field-row grid left ~145px per input at 375px;
   (3) #listingsUnitsTable converts to per-unit .mobile-card cards (the
   #4377/#4382 convention) instead of a 7-column table needing horizontal
   scroll, and the bottom Save/Cancel/Delete/Preview row becomes a sticky
   bottom bar. Desktop/tablet (>640px) are unaffected -- every rule below
   is inside the same max-width: 640px query already used elsewhere in this
   file for this breakpoint. */
.listings-form-mobile-topbar {
  display: none;
}
@media (max-width: 640px) {
  .listings-form-mobile-topbar {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 20;
    margin: calc(var(--space-5) * -1) calc(var(--space-5) * -1) var(--space-3);
    padding: var(--space-3) var(--space-5);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-divider);
  }
  .listings-form-mobile-back {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    font-weight: 600;
  }
  /* Property/Headline, Pet policy/Email, Phone/Laundry, Parking/Fee --
     every .field-row inside the listing form stacks to one full-width
     column instead of the cramped 2-up grid (~145px/field at 375px). */
  #listingsFormCard .field-row {
    grid-template-columns: 1fr;
  }
  /* #listingsUnitsTable itself is hidden by .mobile-cards-table-scroll
     (style.css "#4377" block); this only widens its remaining .table-wrap
     border box to the full mobile width so it doesn't visually clip while
     hidden, and gives the units field-note breathing room to match. */
  #listingsFormUnitsField .table-wrap {
    width: 100%;
  }
  /* Sticky bottom Save/Cancel bar. #listingsFormCard's own padding is
     preserved for everything else -- only this row is pulled to the card's
     full bleed width and pinned, mirroring the top bar's negative-margin
     technique above.
     #4491 follow-up: this row can hold up to 4 buttons (Save, Cancel,
     Delete listing, Preview public page) when editing an existing listing.
     `overflow-x: auto` here never actually scrolls -- html/body carry a
     sitewide `overflow-x: clip` (style.css ~189) that clips the overflow
     before this element's own scrollbar can ever appear, so the 4th button
     was silently ~70% off-screen and unreachable at 375px (measured live).
     Fixed by wrapping onto a second row instead of relying on horizontal
     scroll inside a clipped ancestor: Save/Cancel take the primary (first)
     row at equal width, Delete/Preview wrap below at equal width. */
  #listingsFormCard .listings-form-actions {
    position: sticky;
    bottom: 0;
    z-index: 20;
    margin: var(--space-3) calc(var(--space-5) * -1) calc(var(--space-5) * -1);
    padding: var(--space-3) var(--space-5);
    background: var(--color-surface);
    border-top: 1px solid var(--color-divider);
  }
  #listingsFormCard .listings-form-actions > button {
    flex: 1 1 calc(50% - var(--space-2) / 2);
    min-height: 44px;
    min-width: 0;
    white-space: nowrap;
    margin-left: 0 !important;
  }
}

/* Per-unit mobile card refinements on top of the generic .mobile-card/
   .mobile-card-row pattern (style.css "#4377" block) -- live inputs and
   toggles need larger tap targets than the plain label/value text rows
   the base pattern was built for. */
.listings-unit-card-desc {
  /* Overrides the table cell's max-width:220px/nowrap-ellipsis -- the card
     column is already narrow and full-width; truncating further here would
     just as often clip a short preview as leave excess room. */
  max-width: none;
  white-space: normal;
  text-align: right;
}
.listings-unit-card-input {
  width: 9rem;
  min-height: 44px;
  box-sizing: border-box;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
}
.listings-unit-card-toggle-row {
  justify-content: flex-start;
  gap: var(--space-4);
}
/* #4636 -- render as a sliding track/knob switch instead of a native
   checkbox, per the #4491 wireframe. The checkbox stays the real input
   (visually hidden via opacity, not display:none, so it keeps its
   focus/keyboard/click behavior and test-id hooks); the track and knob
   are drawn on the label's own ::before/::after so no extra markup is
   needed. Tap target stays >=44px via min-height below; the switch
   graphic itself is a smaller iOS-style 44x24 track. */
.listings-unit-card-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-1) 0 var(--space-1) 52px;
}
.listings-unit-card-toggle input[type="checkbox"] {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 24px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.listings-unit-card-toggle::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 24px;
  border-radius: 12px;
  background: var(--color-border);
  transition: background-color 0.15s ease;
  pointer-events: none;
}
.listings-unit-card-toggle::after {
  content: "";
  position: absolute;
  left: 2px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--color-surface);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: left 0.15s ease;
  pointer-events: none;
}
.listings-unit-card-toggle:has(input[type="checkbox"]:checked)::before {
  background: var(--color-primary);
}
.listings-unit-card-toggle:has(input[type="checkbox"]:checked)::after {
  left: 22px;
}
.listings-unit-card-toggle:has(input[type="checkbox"]:focus-visible)::before {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.listings-unit-card-photo-row {
  align-items: center;
}
.listings-unit-card-photo-actions {
  display: flex;
  gap: var(--space-2);
}
.listings-unit-card-btn {
  min-height: 44px;
  min-width: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  cursor: pointer;
}
.listings-unit-card-btn-danger {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

.rg-feed-url-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.rg-feed-url-row code,
.rg-feed-url-row a.rg-feed-url-link {
  flex: 1 1 24rem;
  min-width: 0;
  overflow-wrap: anywhere;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
}
.rg-feed-url-row a.rg-feed-url-link {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-primary);
  text-decoration: none;
}
.rg-feed-url-row a.rg-feed-url-link:hover {
  text-decoration: underline;
}
/* #4650: labeled-row preview of an org's Tenant Portal branding contact
   fields, on Public listings settings. Deliberately mirrors
   .rent-contact-modal-row/-label in rent.css (the applicant-facing Contact
   modal this panel is a preview of) rather than inventing a new pattern --
   same label-over-value shape, just laid out for a settings card instead of
   a modal. Read-only: no inputs, this only ever links out to the Tenant
   Portal branding editor to make a change. */
.rg-contact-preview-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-2) 0 var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
}
.rg-contact-preview-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  font-size: var(--text-sm);
}
.rg-contact-preview-label {
  color: var(--color-text-muted);
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.rg-contact-preview-value {
  color: var(--color-text);
  overflow-wrap: anywhere;
}
.rg-feed-warnings {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
}
.rg-feed-warnings p {
  margin: 0;
}
.rg-feed-warnings p + p {
  margin-top: var(--space-2);
}
@media (max-width: 700px) {
  .rg-listing-card-row {
    align-items: flex-start;
    flex-direction: column;
  }
  .rg-listing-statuses {
    justify-content: flex-start;
  }
}

/* Task List ID 208: Repairs -> Duration & Aging tab. Section headings
   between the stacked tables, plus the "Unknown" treatment for items
   whose date_started was never recorded. Those are deliberately visible
   rather than hidden: the whole point of the tab is that a missing start
   date means "we don't know", not "zero days". */
.timing-section-heading {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  margin: 20px 0 8px;
}
.timing-section-heading:first-of-type {
  margin-top: 12px;
}
.timing-unknown {
  color: var(--color-text-muted);
  font-style: italic;
}
.timing-suspect {
  color: var(--color-warning);
}

/* Task List ID 208: keep date and day-count cells on one line so
   "2026-01-20" doesn't wrap to "2026-01-" / "20". The item description
   column is left wrapping, since it's free text and can be long. */
#repairsTimingPanel .mini-table td {
  white-space: nowrap;
}
#repairsTimingPanel .mini-table td:first-child {
  white-space: normal;
  min-width: 180px;
}

/* Task List ID 208: these tables are wide (up to 8 columns) and the cells
   no longer wrap, so on a phone the duration columns sit off-screen.
   Let each table scroll horizontally inside its own container rather than
   pushing the whole modal sideways. */
#timingCompletedContainer,
#timingOpenContainer,
#timingByCategoryContainer,
#timingByPriorityContainer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Task List ID 220: portfolio Repair Duration & Aging report. Same rules as
   the per-property tab above -- day counts and dates stay on one line, the
   property/description columns keep wrapping, and each wide table scrolls
   inside its own container on narrow screens. */
#repairTimingTableContainer .mini-table td,
#repairTimingOldestContainer .mini-table td {
  white-space: nowrap;
}
#repairTimingTableContainer .mini-table td:first-child,
#repairTimingOldestContainer .mini-table td:first-child,
#repairTimingOldestContainer .mini-table td:nth-child(2) {
  white-space: normal;
  min-width: 180px;
}
#repairTimingTableContainer .mini-table .modal-note {
  white-space: normal;
  margin: 2px 0 0;
}
#repairTimingTableContainer,
#repairTimingOldestContainer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* The default `.field label` is a space-between flex row, which is right for
   a stacked label + hint but pushes a checkbox and its caption to opposite
   edges of the fieldset. Pull them back together. */
.repair-timing-check label {
  justify-content: flex-start;
}
/* ID 281 trend bars: a plain scaled block so a month's median turnaround is
   comparable at a glance without pulling in a charting dependency. */
#repairTimingTrendContainer .mini-table td,
#repairTimingTrendContainer .mini-table th {
  white-space: nowrap;
}
/* Bars must share a left edge, otherwise the eye compares right-aligned end
   points and every month looks the same length. Scoped through the container
   id to beat the table's own last-column alignment. */
#repairTimingTrendContainer .repair-timing-bar-cell,
#repairTimingTrendContainer th.repair-timing-bar-head {
  min-width: 140px;
  width: 24%;
  text-align: left;
}
.repair-timing-bar {
  display: inline-block;
  height: 10px;
  border-radius: 3px;
  background: var(--color-primary);
  vertical-align: middle;
}
#repairTimingTrendContainer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* Four summary cards, so give them their own row instead of letting the
   shared 3-up grid orphan the last one onto a second line. */
@media (min-width: 901px) {
  #repair-timing-main .kpi-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ==========================================================================
   Document AI insights banner (Task List ID 184 -- listing-import Phase 3)
   Amber sibling of .import-warnings-banner. That one is informational ("this
   field came back blank because the adapter can't read it"); this one is a
   warning ("the documents disagree with the listing / disclose a problem"),
   so it borrows the same geometry with the warning palette.
   ========================================================================== */
.doc-insights-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--color-warning-highlight);
  border: 1px solid var(--color-warning);
  border-left-width: 4px;
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text);
  margin-bottom: var(--space-4);
}
.doc-insights-banner[hidden] {
  display: none;
}
.doc-insights-banner-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1px solid #b45309;
  color: #b45309;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
}
.doc-insights-banner-body {
  flex: 1 1 auto;
  min-width: 0;
}
.doc-insights-banner-title {
  margin: 0;
  font-weight: 600;
}
.doc-insights-banner-list {
  margin: var(--space-2) 0 0 0;
  padding-left: 20px;
}
.doc-insights-banner-list li {
  margin: 2px 0;
}
.doc-insights-banner .action-btn {
  flex-shrink: 0;
}

/* Per-document AI recap card inside the Documents tab. */
.doc-ai-card {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 8px;
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  font-size: 13px;
  color: #374151;
}
.doc-ai-card h4 {
  margin: 0 0 4px 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #6b7280;
}
.doc-ai-card ul {
  margin: 4px 0 0 0;
  padding-left: 18px;
}
.doc-ai-card li {
  margin: 2px 0;
}
.doc-ai-section {
  margin-top: 8px;
}
.doc-ai-gotchas {
  border-left: 3px solid #f59e0b;
  padding-left: 8px;
}
.doc-ai-discrepancies {
  border-left: 3px solid #dc2626;
  padding-left: 8px;
}
.doc-ai-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.6;
  white-space: nowrap;
}
.doc-ai-badge-gotchas {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
  border: 1px solid var(--color-warning);
}
.doc-ai-badge-discrepancy {
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  border: 1px solid var(--color-error);
}
.doc-ai-badge-clean {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  border: 1px solid var(--color-success);
}
.doc-ai-badge-pending {
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
  border: 1px solid var(--color-brand-navy);
}

/* Today's important dates widget (Task List 196c) */
.important-dates-windows {
  display: inline-flex;
  gap: 2px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 2px;
}
.important-dates-window {
  border: none;
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 3px 9px;
  border-radius: calc(var(--radius-md) - 3px);
  cursor: pointer;
}
.important-dates-window:hover {
  color: var(--color-text);
}
/* ID 265: hard-coded #fff was 2.81:1 on the dark-theme green (axe caught it on
   the landing page). --color-text-inverse flips with the theme: 6.40:1 light,
   6.50:1 dark. It is also what every other primary-filled control uses. */
.important-dates-window.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
/* ID 275: the header is space-between, and adding a third child would have
   stranded the chips in the middle of the row. Pin the chip group and the
   link together on the right, scoped to this widget so no other landing
   panel header shifts. */
.landing-widget[data-widget-id="important_dates"] .landing-panel-header {
  gap: var(--space-2);
}
.landing-widget[data-widget-id="important_dates"] .important-dates-windows {
  margin-left: auto;
}
/* ID 275: "View all" into the Overview dashboard's cross-phase dates panel.
   Sized to sit beside the window chips without competing with them -- the
   chips are the in-place filter, this is the way out of the 5-row cap. It
   also renders inline inside the truncation note, hence the shared class. */
.important-dates-viewall {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-primary);
  text-decoration: none;
  white-space: nowrap;
}
.important-dates-viewall:hover,
.important-dates-viewall:focus-visible {
  text-decoration: underline;
}
.important-dates-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--color-error-highlight);
  /* ID 265: --color-error is 4.47:1 on its own tint here -- just under AA. */
  color: var(--color-error-text);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 700;
  vertical-align: 2px;
}
.important-dates-when {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  flex-shrink: 0;
  text-align: right;
}
.important-dates-date {
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text);
  white-space: nowrap;
}
.important-dates-rel {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.important-dates-item.is-overdue .important-dates-date,
.important-dates-item.is-overdue .important-dates-rel {
  color: var(--color-error);
  font-weight: 700;
}
.important-dates-item.is-today .important-dates-date {
  color: var(--color-primary);
}

/* ---- Action-needed landing widgets (Task List 196b) ---------------------
   Compact, half-width panels that only render when they have something in
   them. Tone tint is a left accent border only -- deliberately not a filled
   background, so five of them stacked next to each other don't turn the
   landing page into a wall of colour. */
.landing-action-panel {
  border-left: 3px solid var(--color-border);
}
.landing-action-panel[data-tone='alert'] {
  border-left-color: var(--color-error);
}
.landing-action-panel[data-tone='warn'] {
  border-left-color: var(--color-warning);
}
.landing-action-panel[data-tone='info'] {
  border-left-color: var(--color-primary);
}
.landing-action-badge {
  display: inline-block;
  min-width: 1.5rem;
  padding: 0 var(--space-2);
  margin-left: var(--space-2);
  border-radius: 999px;
  background: var(--color-surface-offset);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1.5rem;
  text-align: center;
}
.landing-action-panel[data-tone='alert'] .landing-action-badge {
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}
.landing-action-panel[data-tone='warn'] .landing-action-badge {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.landing-action-summary {
  margin: 0 0 var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.landing-action-tag {
  flex: none;
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}
.landing-recent-item--static {
  cursor: default;
}
.landing-panel-note {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.landing-snapshot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-3);
}

/* =========================================================================
   196d -- Phase-tabbed Dashboards page
   One destination with a tab per lifecycle phase. The tab strips reuse the
   shared .tabs treatment (aria-selected drives the active state), so this
   block only adds layout, the Important Dates panel and the phase-mix donut.
   ========================================================================= */
.dash-phase-tabs {
  margin-bottom: var(--space-5);
  flex-wrap: wrap;
}
.dash-sub-tabs {
  margin-bottom: var(--space-5);
  flex-wrap: wrap;
}
.dash-pane[hidden],
.dash-sub-pane[hidden] {
  display: none;
}
.dash-section-title {
  font-size: var(--text-lg);
  font-weight: 600;
  margin: 0 0 var(--space-2);
}
/* The folded-in report sections used to be their own pages with their own
   page-intro spacing; give them consistent breathing room as panes. */
.dash-sub-pane > .page-intro {
  margin-bottom: var(--space-5);
}

/* #1636 — Own dashboard's cross-property maintenance queue. The report keeps
   its server-defined urgency bands separate rather than forcing a single flat
   table, while reusing the existing dashboard cards, toolbar, scroll wrapper,
   table, and button treatments. */
.upcoming-maintenance-breadcrumb {
  margin: 0 0 var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.upcoming-maintenance-summary {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-bottom: var(--space-5);
}
.upcoming-maintenance-summary .kpi-card {
  padding: var(--space-4);
}
.upcoming-maintenance-summary .kpi-card .kpi-value:not([data-funding]) {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.3;
}
.upcoming-maintenance-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.upcoming-maintenance-controls .field {
  margin: 0;
  flex: 1 1 12rem;
}
.upcoming-maintenance-controls input,
.upcoming-maintenance-controls select {
  width: 100%;
}
.upcoming-maintenance-overdue-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.5rem;
  white-space: nowrap;
  color: var(--color-text);
  font-size: var(--text-sm);
}
.upcoming-maintenance-bands {
  display: grid;
  gap: var(--space-5);
}
.upcoming-maintenance-band {
  margin: 0;
}
.upcoming-maintenance-band > h3,
.upcoming-maintenance-band > summary {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-divider);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 650;
}
.upcoming-maintenance-band > summary {
  cursor: pointer;
}
.upcoming-maintenance-band-content {
  margin-top: var(--space-2);
}
.upcoming-maintenance-empty {
  margin: 0;
}
.upcoming-maintenance-table {
  min-width: 760px;
}
.upcoming-maintenance-table th,
.upcoming-maintenance-table td {
  text-align: left;
  vertical-align: top;
}
.upcoming-maintenance-table td:first-child {
  color: var(--color-text);
}
.upcoming-maintenance-table th:nth-child(1),
.upcoming-maintenance-table td:nth-child(1) {
  min-width: 11rem;
}
.upcoming-maintenance-table th:nth-child(2),
.upcoming-maintenance-table td:nth-child(2) {
  min-width: 14rem;
}
.upcoming-maintenance-table th:nth-child(3),
.upcoming-maintenance-table td:nth-child(3),
.upcoming-maintenance-table th:nth-child(4),
.upcoming-maintenance-table td:nth-child(4),
.upcoming-maintenance-table th:nth-child(5),
.upcoming-maintenance-table td:nth-child(5),
.upcoming-maintenance-table th:nth-child(6),
.upcoming-maintenance-table td:nth-child(6) {
  white-space: nowrap;
}
.upcoming-maintenance-due {
  min-width: 8rem;
}
.upcoming-maintenance-tenant {
  min-width: 11rem;
}
.upcoming-maintenance-tenant-cell {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
}
.upcoming-maintenance-tenant-badge {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
  font-size: var(--text-xs);
  font-weight: 700;
}
.upcoming-maintenance-tenant-status {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.upcoming-maintenance-tenant-photo {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  overflow: hidden;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-divider);
}
.upcoming-maintenance-tenant-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.upcoming-maintenance-action-cell {
  min-width: 8.5rem;
}
.upcoming-maintenance-action {
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}
.upcoming-maintenance-subrow td {
  padding-top: 0;
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-size: var(--text-xs);
}
.upcoming-maintenance-subline {
  display: block;
}
.upcoming-maintenance-recent {
  opacity: 0.72;
}
.upcoming-maintenance-recent[open] {
  opacity: 1;
}
@media (max-width: 900px) {
  .upcoming-maintenance-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 560px) {
  .upcoming-maintenance-summary {
    grid-template-columns: 1fr;
  }
  .upcoming-maintenance-controls .field {
    flex-basis: 100%;
  }
}

/* ---- Important dates panel (one per phase tab) ---- */
.dash-dates {
  margin-top: var(--space-6);
}
.dash-dates-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.dash-dates-windows {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.dash-dates-window {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
  border-right: 1px solid var(--color-border);
  background: none;
  cursor: pointer;
}
.dash-dates-window:last-child {
  border-right: none;
}
.dash-dates-window.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
.dash-dates-window:not(.is-active):hover {
  background: var(--color-surface-offset);
  color: var(--color-text);
}
.dash-dates-badge {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 2px var(--space-2);
  border-radius: 999px;
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}
.dash-dates-badge[hidden] {
  display: none;
}
.dash-dates-overdue {
  color: var(--color-error);
  font-weight: 600;
}
.dash-dates-today {
  font-weight: 600;
}

/* ---- Sell tab: portfolio Offers panel (Task List 264) ---- */
.dash-offer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  white-space: nowrap;
}
/* Offer amounts, dates and expiry countdowns read badly when they wrap
   mid-value, so only the property column wraps; the panel scrolls instead. */
#dashOffersTable .mini-table th,
#dashOffersTable .mini-table td {
  white-space: nowrap;
}
#dashOffersTable .mini-table th:first-child,
#dashOffersTable .mini-table td:first-child {
  white-space: normal;
  min-width: 11rem;
}

/* ---- Overview tab: phase mix donut + headline cards ---- */
.dash-overview-split {
  display: grid;
  grid-template-columns: minmax(260px, 340px) 1fr;
  gap: var(--space-5);
  align-items: start;
}
.dash-mix {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}
.dash-donut {
  position: relative;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: conic-gradient(var(--color-border) 0 100%);
}
.dash-donut-hole {
  position: absolute;
  inset: 26px;
  border-radius: 50%;
  background: var(--color-surface);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
.dash-donut-total {
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: 1;
}
.dash-donut-caption {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.dash-mix-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  min-width: 150px;
}
.dash-mix-legend li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.dash-mix-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.dash-mix-label {
  flex: 1 1 auto;
}
.dash-mix-count {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.dash-overview-kpis {
  margin: 0;
}

@media (max-width: 860px) {
  .dash-overview-split {
    grid-template-columns: 1fr;
  }
}

/* When the tab strips wrap (narrow viewports), the shared .tabs container
   border leaves an empty bordered cell at the end of the first row. Below the
   wrap point, give each tab its own outline instead of one shared box. */
@media (max-width: 640px) {
  .dash-phase-tabs,
  .dash-sub-tabs {
    display: flex;
    border: none;
    border-radius: 0;
    overflow: visible;
    gap: var(--space-2);
  }
  .dash-phase-tabs button,
  .dash-sub-tabs button {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
  }
}

/* Task List ID 291 — saved-analysis load failure.
   #analysisLoadError lives inside #calculator-main so it does not need its own
   <main> section (and therefore no hide-line in all ~35 show*Page functions —
   see ID 269). Error mode simply suppresses every other child of the
   calculator, so the previously loaded property's numbers cannot show through
   underneath the message. Colours are inherited from .panel/.field-note rather
   than redeclared, so the ID 223 contrast guard still covers this panel. */
#calculator-main.calculator-error-mode > *:not(#analysisLoadError) {
  display: none !important;
}
.analysis-load-error {
  max-width: 46rem;
  margin: var(--space-8) auto;
}
.analysis-load-error .analysis-load-error-id code {
  font-size: var(--text-xs);
  word-break: break-all;
}
.analysis-load-error-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
/* .btn-export-primary carries margin-left:auto for the toolbar rows it was
   written for; inside this centred action row that would shove it to the
   right edge, away from the message it belongs to. */
.analysis-load-error-actions .btn-export-primary {
  margin-left: 0;
}

/* Task List ID 307 — in-panel load-failure states for the Showings modal and
   the Repairs item form. These are inner panels rather than a whole <main>,
   so they cannot reuse #calculator-main's .calculator-error-mode from ID 291;
   their panel switchers (setShowingsPanel / setRepairsPanel) already hide
   siblings, so the error just becomes another named panel. Colours and type
   come from .field-note and the existing button classes, so no new tokens are
   introduced and the ID 223 contrast guard still applies. */
.panel-load-error {
  max-width: 34rem;
  margin: var(--space-8) auto;
  text-align: center;
}
.panel-load-error-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
}
.panel-load-error-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
/* Same toolbar margin-left:auto neutralisation as .analysis-load-error-actions. */
.panel-load-error-actions .btn-export-primary {
  margin-left: 0;
}

/* Task List ID 317 — export-library load failure. Not a panel: the calculator
   behind it is perfectly usable, only the export is unavailable, so this is a
   compact notice under the property actions row. Reuses .field-note for the
   body copy so the ID 223 contrast guard still applies. The [hidden] rule is
   explicit because the element carries a class and author styles beat the
   browser's `[hidden] { display: none }` regardless of specificity. */
.export-load-error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  max-width: 34rem;
  margin: 0 0 var(--space-6);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-error);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.export-load-error[hidden] {
  display: none;
}
.export-load-error-title {
  margin: 0;
  font-weight: 600;
}
.export-load-error .field-note {
  margin: 0;
}

/* ============ Under Contract (buy-side) acquisition card -- ID 276 ==========
   Property-page card shown only in the Analyze phase at analyze_status
   offer_accepted. Deliberately reuses the Repairs summary stats and the Sell
   Tracker offer-form vocabulary so the two lifecycle trackers read as the
   same product; only the pieces with no existing equivalent (the deadline
   checklist rows, the two-column terms/earnest split, the alert strip) get
   new rules here. */

.under-contract-card .uc-summary-bar {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 900px) {
  .under-contract-card .uc-summary-bar {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.uc-status-badge {
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 0.2rem 0.7rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  background: var(--color-primary-highlight);
  white-space: nowrap;
}
.uc-status-badge[data-tone='closed'] {
  border-color: var(--color-success);
  color: var(--color-success-text);
  background: var(--color-success-highlight);
}
.uc-status-badge[data-tone='terminated'] {
  border-color: var(--color-error);
  color: var(--color-error-text);
  background: var(--color-error-highlight);
}

/* Alert strip: earnest-at-risk countdown and the "needs dates" nudge. */
.uc-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.uc-alert[hidden] {
  display: none;
}
.uc-alert[data-tone='danger'] {
  border-left-color: var(--color-error);
}
.uc-alert[data-tone='info'] {
  border-left-color: var(--color-brand-navy);
}
.uc-alert-icon {
  flex-shrink: 0;
  line-height: 1.4;
  color: var(--color-text-muted);
}

.uc-two-col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
@media (max-width: 900px) {
  .uc-two-col {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* Nested panels inside a panel: flatten the shadow so the card doesn't look
   like three stacked cards. */
.uc-subpanel {
  margin: 0;
  padding: var(--space-4);
  box-shadow: none;
  background: var(--color-surface-2);
}
.uc-subpanel .panel-heading {
  margin-bottom: var(--space-3);
}
.uc-subpanel .panel-heading h2 {
  font-size: var(--text-base);
}
.uc-subpanel .field {
  margin-bottom: var(--space-2);
}

.uc-checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: var(--space-2);
  cursor: pointer;
}
.uc-checkbox-row input {
  width: auto;
  margin: 0;
  flex-shrink: 0;
}

.uc-deadlines-section {
  margin-top: var(--space-2);
}
.uc-deadline-list {
  display: flex;
  flex-direction: column;
}
.uc-deadline-row {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) 10.5rem 9.5rem minmax(0, 1.2fr) auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-divider);
}
.uc-deadline-row:last-child {
  border-bottom: none;
}
@media (max-width: 1000px) {
  .uc-deadline-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .uc-deadline-title-cell {
    grid-column: 1 / -1;
  }
}
.uc-deadline-title-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.uc-deadline-title {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  overflow-wrap: anywhere;
}
.uc-deadline-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.uc-deadline-row input[type='date'],
.uc-deadline-row select,
.uc-deadline-row input[type='text'] {
  margin: 0;
  font-size: var(--text-xs);
  padding: 0.3rem 0.45rem;
  min-width: 0;
  width: 100%;
}
.uc-deadline-row[data-tone='overdue'] .uc-deadline-title {
  color: var(--color-error);
}
.uc-deadline-row[data-tone='done'] .uc-deadline-title,
.uc-deadline-row[data-tone='done'] .uc-deadline-meta {
  color: var(--color-text-muted);
}
.uc-deadline-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  justify-content: flex-end;
}
.uc-deadline-flag {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.uc-deadline-flag[data-tone='overdue'] {
  color: var(--color-error);
  border-color: var(--color-error);
}
.uc-deadline-flag[data-tone='soon'] {
  color: var(--color-warning-text, var(--color-warning));
  border-color: var(--color-warning);
}
.uc-deadline-flag[data-tone='protecting'] {
  color: var(--color-brand-navy);
  border-color: var(--color-brand-navy);
}
.uc-deadline-delete {
  background: none;
  border: none;
  padding: 0.1rem 0.25rem;
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1;
}
.uc-deadline-delete:hover {
  color: var(--color-error);
}

.uc-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) 0 var(--space-1);
}
.uc-empty[hidden] {
  display: none;
}
.uc-empty-text {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.uc-notes-field {
  margin-top: var(--space-4);
}
.uc-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}

/* ---------------------------------------------------------------------------
   Under Contract portfolio panel + org deadline template editor
   (Task List ID 276 step 4). Tones reuse the balloon panel's variables so the
   two dashboard panels read as the same family in both light and dark mode.
   --------------------------------------------------------------------------- */
.dashboard-uc-panel .dash-uc-totals {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.dashboard-uc-panel .dash-uc-chip {
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.dashboard-uc-panel .dash-uc-chip[data-tone="warn"] {
  color: var(--color-warning-text);
  border-color: var(--color-warning);
  font-weight: 600;
}
.dashboard-uc-panel .dash-uc-chip[data-tone="danger"] {
  color: var(--color-error-text);
  border-color: var(--color-error);
  font-weight: 600;
}
.dashboard-uc-panel .dash-uc-table td {
  vertical-align: top;
}
.dashboard-uc-panel .dash-uc-sub {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.dashboard-uc-panel .dash-uc-sub[data-tone="warn"] { color: var(--color-warning-text); }
.dashboard-uc-panel .dash-uc-sub[data-tone="danger"] { color: var(--color-error-text); }
.dashboard-uc-panel .dash-uc-flag {
  display: inline-block;
  margin-right: var(--space-2);
  font-weight: 700;
}
.dashboard-uc-panel .dash-uc-flag[data-tone="danger"] { color: var(--color-error-text); }

.uc-template-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}
.uc-template-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
}
.uc-template-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.uc-template-cell--title { flex: 1 1 14rem; }
.uc-template-cell--order {
  flex-direction: row;
  align-items: center;
  gap: 2px;
}
.uc-template-cell--flags { gap: var(--space-1); }
.uc-template-label {
  font-size: var(--text-xs, 0.75rem);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}
.uc-template-row .uc-template-title { width: 100%; }
.uc-template-row .uc-template-offset { width: 5.5rem; }
.uc-template-check {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-sm);
  white-space: nowrap;
}
.uc-template-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.uc-template-actions .uc-template-save { margin-left: auto; }
.uc-template-savednote { margin: 0; }
.uc-template-error { margin: 0 0 var(--space-2); }

/* The editor can hold up to 40 rows, so the LIST scrolls rather than the whole
   card -- otherwise Save/Add/Reset sit below the fold and read as missing.
   #3401: this used to also cap the modal card at 60rem; now it's a plain
   page panel (#closingSetupPanelDeadlines) with no such cap needed. */
.uc-template-list {
  max-height: 46vh;
  overflow-y: auto;
  padding-right: 2px;
}
/* Push Remove to the far right so it never reads as part of the field group
   next to it when the row wraps. */
.uc-template-cell--remove { margin-left: auto; align-self: flex-end; }

/* ===========================================================================
   Showings — dense component-row layout (Task List ID 386)

   The old form rendered every observation as a labelled .field stacked two
   per row, so one exterior section was ~14 boxes tall and a 2-unit
   walkthrough ran past 2,900px. This replaces that with a table-like grid:
   one component per line (name | col A | col B | notes | photo), plus a
   compact "chip" grid for the section's loose scalars.

   Everything below is built on semantic tokens, so dark mode (ID 21/24)
   comes for free with no separate [data-theme='dark'] block.
   =========================================================================== */

:root {
  /* One place to retune density. */
  --showing-row-h: 30px;
  --showing-row-gap: 4px;
  --showing-name-w: 168px;
  --showing-col-w: 104px;
  --showing-photo-w: 34px;
}

.srows {
  display: flex;
  flex-direction: column;
  gap: var(--showing-row-gap);
  width: 100%;
}
.srow {
  display: grid;
  grid-template-columns:
    var(--showing-name-w)
    var(--showing-col-w)
    var(--showing-col-w)
    minmax(0, 1fr)
    var(--showing-photo-w);
  align-items: center;
  gap: var(--showing-row-gap);
}
/* Repairs have no component-name column; on a wide viewport this is a real
   table -- description, the #1939 B2 "Tied to" asset picker, priority,
   location (free text, or read-only from the tied asset -- see
   showingRepairRowHtml), rough cost, then the photo/remove cluster. Below
   640px it collapses to a card, same technique as .srow's own mobile rules
   further down -- see that media query for the repair-specific overrides. */
.srow-repair {
  grid-template-columns:
    minmax(0, 1.4fr)
    minmax(0, 1.2fr)
    minmax(0, 0.9fr)
    minmax(0, 1fr)
    minmax(0, 0.7fr)
    calc(var(--showing-photo-w) * 2);
}
/* #1940: the Repairs & Improvements list is a read-only display table, not
   a row of form controls like .srow-repair above (the "Tied to" asset
   picker) -- own column set: description, source badge, priority/location,
   assigned-to, cost, status. Reuses the same .srows/.srow flex+grid and
   data-label mobile-card mechanism rather than a third responsive scheme. */
/* The row element keeps its .repair-item class because the click/keyboard
   delegation in app.js selects on it (and #866's focus-visible ring lives
   there), but .repair-item's own card look -- border, radius, surface fill,
   padding -- would turn a wide-browser table into a stack of cards, which is
   exactly what this ticket moved away from. Strip the card here and let the
   mobile block below put it back, where cards are the point. .srow's
   display:grid already wins over .repair-item's display:flex on source
   order alone; this is the rest of that override, made explicit. */
.repair-item.srow-repairs-list {
  border: 0;
  border-radius: 0;
  background: none;
  border-bottom: 1px solid var(--color-divider);
  padding: var(--space-2) var(--space-1);
}
.repair-item.srow-repairs-list:hover {
  background: var(--color-surface-2);
  border-color: transparent;
  border-bottom-color: var(--color-divider);
}
.srow-repairs-list {
  grid-template-columns:
    minmax(0, 2fr)
    minmax(0, 0.9fr)
    minmax(0, 1.3fr)
    minmax(0, 1fr)
    minmax(0, 0.7fr)
    minmax(0, 0.8fr);
  cursor: pointer;
}
.repairs-list-desc-cell {
  min-width: 0;
}
/* Quiet small pill, existing tokens only -- .repair-status-pill's own
   neutral (no status-* modifier) look, reused rather than inventing a new
   colour per the brief. */
.repair-source-badge {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.srow-head {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  padding-bottom: 2px;
  border-bottom: 1px solid var(--color-divider);
  margin-bottom: 2px;
}
.srow-head > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.srow-photo-head {
  text-align: center;
}
.srow-name {
  font-size: var(--text-sm);
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.srow-cell {
  min-width: 0;
  display: block;
}
.srow-control {
  width: 100%;
  height: var(--showing-row-h);
  min-height: var(--showing-row-h);
  padding: 2px var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.2;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  box-sizing: border-box;
}
textarea.srow-control {
  resize: vertical;
  padding-top: 5px;
  overflow: hidden;
}
.srow-control:focus {
  outline: none;
  border-color: var(--color-primary);
}
.srow-control:disabled,
.srow-control:read-only {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}
/* An empty cell still needs to occupy its column so rows stay aligned. */
.srow-cell:empty::before {
  content: '';
  display: block;
  height: var(--showing-row-h);
}

.srow-photo {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
.srow-photo-btn,
.srow-remove-btn {
  position: relative;
  height: var(--showing-row-h);
  min-width: var(--showing-row-h);
  padding: 0 4px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
}
.srow-photo-btn:hover,
.srow-remove-btn:hover,
.srow-photo-btn.is-open {
  border-color: var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
}
.srow-photo-btn.has-photos {
  color: var(--color-primary);
}
.srow-photo-count {
  font-size: 10px;
  font-weight: 700;
  margin-left: 1px;
  vertical-align: super;
}
.srow-remove-btn {
  font-size: var(--text-base);
}
.srow-remove-btn:hover {
  color: var(--color-error-text);
}

/* Per-component photo drawer, injected under its row on demand rather than
   every row carrying an always-open upload strip. */
.showing-row-photos {
  grid-column: 1 / -1;
  margin: 2px 0 var(--space-2);
  padding: var(--space-2);
  border: 1px dashed var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
}

/* ===========================================================================
   #1938: inspection condition capture (app.js's inspectionCapture* block).
   ONE component, reflowed by CSS at 900px -- a table row at and above that
   width, a card below it -- same technique as .srow's own mobile collapse
   just above (grid-template-areas + a data-label::before caption), but at
   a DIFFERENT breakpoint (900px, not .srow's 640px): most inspections
   happen on a phone (spec), so the card layout is the one that has to work,
   and 900px hands it a wider range of real phone/tablet widths than .srow's
   640px does. Do not "fix" this to match .srow's number -- they are
   deliberately different components with different width targets.
   =========================================================================== */
.inspect-topbar {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.inspect-progress-line {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.inspect-progress-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: var(--color-surface-2);
  overflow: hidden;
}
.inspect-progress-bar > i {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  height: 100%;
  background: var(--color-primary);
  border-radius: 999px;
  transition: width 0.2s ease;
}
/* #2776: bulk "condition for all shown" stamp above the per-asset rows --
   wireframe page-2 item 1 (per-asset overrides). */
.inspect-bulk-condition {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.inspect-bulk-condition label {
  color: var(--color-text-muted);
  white-space: nowrap;
}
.inspect-bulk-condition select {
  min-height: 32px;
  padding: 4px var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  font: inherit;
  font-size: var(--text-sm);
}
.inspect-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.inspect-controls-spacer {
  flex: 1 1 auto;
}
.inspect-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 30px;
  padding: 4px var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: 999px;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
}
.inspect-filter-chip.is-on {
  color: var(--color-primary);
  border-color: var(--color-primary);
  font-weight: 650;
  background: var(--color-surface);
}
.inspect-find {
  height: 30px;
  min-width: 160px;
  padding: 2px var(--space-2);
  font-size: var(--text-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  box-sizing: border-box;
}
.inspect-find:focus {
  outline: none;
  border-color: var(--color-primary);
}
.inspect-next-unchecked {
  display: none; /* desktop gets the find box instead -- shown back at 900px below */
}
.inspect-error {
  margin: 0;
  padding: var(--space-2);
  border: 1px solid var(--color-error-text);
  border-radius: var(--radius-sm);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
  font-size: var(--text-sm);
}
/* #2210: the condition panel's own save confirmation, next to its Save
   button in .inspect-footer -- mirrors .inspect-error's contrast-safe
   border/background/text triad, success-toned instead of error-toned. */
.inspect-save-success {
  margin: var(--space-2) 0 0;
  padding: var(--space-2);
  border: 1px solid var(--color-success-text);
  border-radius: var(--radius-sm);
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  font-size: var(--text-sm);
}
.inspect-loading,
.inspect-empty {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  padding: var(--space-3) 0;
}

.inspect-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* #2726: this IS "the panel's scroll container" .inspect-group-head's
     sticky rule below anchors to -- the condition panel scrolls as its own
     block within the visit form rather than the whole modal scrolling, so
     group headers only need to stick within this element, not the page. */
  overflow-y: auto;
}
/* #4321: bordered card per checklist category, same treatment as
   .showing-accordion/.unit-card elsewhere on this screen -- the sticky
   header inside still sticks to .inspect-groups (the real scroll container,
   per the #2726 note below), not to this card, so adding a border/padding
   here doesn't change that behavior. */
.inspect-group {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0 var(--space-3) var(--space-2);
  background: var(--color-surface);
}
.inspect-group-head {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  padding-bottom: 4px;
  border-bottom: 1px solid var(--color-divider);
  margin-bottom: var(--space-2);
  /* #2726 spec section 5: "sticky within the panel's scroll container" --
     a solid background is required for a sticky header or the item rows
     scrolling underneath show through it. */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--color-surface);
  padding-top: 4px;
  margin-left: calc(var(--space-3) * -1);
  margin-right: calc(var(--space-3) * -1);
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}
.inspect-group-count {
  text-transform: none;
  font-weight: 500;
  letter-spacing: normal;
}

/* #2726: whole-panel autosave status, next to the existing progress line
   (inspectionCapturePanelStatusHtml). */
.inspect-progress-sep {
  color: var(--color-text-muted);
}
.inspect-panel-status.is-saved { color: var(--color-success-text); }
.inspect-panel-status.is-saving { color: var(--color-text-muted); }
.inspect-panel-status.is-retry { color: var(--color-warning-text); font-weight: 650; }

/* #2726: per-row autosave status, in the note cell next to each item's own
   note input (inspectionCaptureRowStatusHtml). */
.inspect-row-status {
  display: inline-block;
  margin-top: 2px;
  font-size: var(--text-xs);
}
.inspect-row-status.is-saved { color: var(--color-success-text); }
.inspect-row-status.is-saving { color: var(--color-text-muted); }
.inspect-row-status.is-retry { color: var(--color-warning-text); font-weight: 650; }

/* #2726 spec section 5: an answered row collapses to a single compact
   summary line; tapping it re-expands the full set of controls. Both the
   summary and the full controls are always in the DOM (inspectionCaptureItemHtml
   renders both) -- only visibility toggles, so there is nothing to
   re-render or re-fetch on expand/collapse. */
.inspect-item-collapsed-summary {
  display: none;
  /* .inspect-item is a grid (desktop) / grid (card, 900px) container, and
     this button is a lone sibling of .inspect-item-full rather than one of
     the six named cells, so it needs to span every column/area itself
     instead of landing in just the first one. */
  grid-column: 1 / -1;
  width: 100%;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border: none;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.inspect-item.is-collapsed .inspect-item-collapsed-summary {
  display: flex;
}
.inspect-item.is-collapsed .inspect-item-full {
  display: none;
}
.inspect-scope-pill {
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.inspect-item-collapsed-summary .inspect-name {
  flex: 1 1 auto;
}

/* Table layout, 900px and above: one grid row per item, the same idea as
   .srow but with this screen's own six columns (name, register, last
   known, condition chips, note, photos) instead of .srow's four. */
.inspect-item {
  display: grid;
  grid-template-columns: 1.4fr 1.2fr 1fr 1.6fr 1.4fr var(--showing-photo-w);
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider);
}
.inspect-item.is-done {
  background: color-mix(in srgb, var(--color-primary) 4%, transparent);
}
.inspect-cell {
  min-width: 0;
}
.inspect-name {
  font-size: var(--text-sm);
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
}
.inspect-sub {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.inspect-cell-register,
.inspect-cell-last {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.inspect-tick {
  color: var(--color-primary);
  font-weight: 650;
  font-size: var(--text-xs);
  margin-left: var(--space-2);
}
.inspect-note-input {
  width: 100%;
  height: 30px;
  padding: 2px var(--space-2);
  font-size: var(--text-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  box-sizing: border-box;
}
.inspect-note-input:focus {
  outline: none;
  border-color: var(--color-primary);
}
.inspect-note-input.is-required-missing {
  border-color: var(--color-error-text);
}
.inspect-cell-photos {
  display: flex;
  justify-content: center;
}
.inspect-photo-drawer {
  /* Reuses .showing-row-photos's own box styling (see the click listener
     in app.js, which sets classList to "showing-row-photos
     inspect-photo-drawer" on the same element) -- this class exists only
     so the 900px media query below can target ITS grid-column placement
     without also having to know .showing-row-photos exists. */
}
.inspect-item-ghost {
  grid-template-columns: 1fr;
}
.inspect-ghost-action {
  display: flex;
  align-items: center;
}

/* Card layout, below 900px -- same data-label::before technique as .srow's
   own 640px collapse, just at this component's own breakpoint. */
@media (max-width: 900px) {
  .inspect-bulk-condition {
    flex-wrap: wrap;
  }
  .inspect-bulk-condition select {
    flex: 1 1 auto;
  }
  .inspect-controls-spacer {
    display: none;
  }
  .inspect-find {
    display: none; /* desktop-only affordance (spec) -- mobile gets Next unchecked -> instead */
  }
  .inspect-next-unchecked {
    display: inline-flex;
    flex: 1 1 100%;
    justify-content: center;
  }
  .inspect-item {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      'name    photos'
      'register register'
      'last     last'
      'condition condition'
      'note     note';
    padding: var(--space-2);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
  }
  .inspect-cell-name { grid-area: name; }
  .inspect-cell-register { grid-area: register; }
  .inspect-cell-last { grid-area: last; }
  .inspect-cell-condition { grid-area: condition; }
  .inspect-cell-note { grid-area: note; }
  .inspect-cell-photos { grid-area: photos; justify-content: flex-end; }
  .inspect-cell[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    margin-bottom: 2px;
  }
  .inspect-item-ghost {
    grid-template-columns: 1fr;
    grid-template-areas: 'name' 'photos';
  }
  /* #2726 spec section 5: on the card layout the three condition chips
     must be at least 44px tall (a real touch target for maintenance staff
     on a phone) and share the row width evenly -- scoped to this panel's
     own condition cell only, NOT a global change to .showing-choice-chip,
     which several other, non-touch-first pickers (basement chips, other
     visit-type fields) also use at their existing 28px. */
  .inspect-cell-condition .showing-choice-grid {
    flex-wrap: nowrap;
  }
  .inspect-cell-condition .showing-choice-chip {
    flex: 1 1 0;
    justify-content: center;
    min-height: 44px;
  }
}

/* Loose scalars that aren't component rows: a wrapping chip grid, ~4 across
   on desktop instead of 2 full-width labelled fields. */
/* #2726 follow-up: labels the boundary between a section's component-row
   table and its chips block (see showingFieldRowHtml in app.js) so single
   -value attributes like "Floor level"/"Dishwasher" read as an intentional
   second part of the section instead of loose fields dumped below the
   table. Same uppercase/muted treatment as .srow-head, own top border
   rather than reusing .srow-head's bottom-border rule so it visually caps
   the table above it while introducing the chips below. Chips-only
   sections (no row table -- e.g. Laundry) never render this:
   showingFieldRowHtml only inserts it when both halves exist. */
.sfield-divider {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-divider);
}
.schips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.schip {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.schip > label,
.snote > label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.showing-choice-grid,
.showing-basement-chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.showing-choice-chip,
.showing-basement-chip > span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 28px;
  padding: 3px var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: 999px;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-xs);
  line-height: 1.2;
  cursor: pointer;
}
/* #4321: solid fill for the selected state, not just an outline -- the
   audit's core complaint was that a user couldn't tell what was already
   picked at a glance. */
.showing-choice-chip.is-on,
.showing-basement-chip input:checked + span {
  /* --color-text-inverse, not a #fff literal or an invented --color-on-primary
     token -- see the #266 note above on .repairs-chip.is-active for why: it
     flips per theme and meets contrast on both the light and dark primary. */
  color: var(--color-text-inverse);
  background: var(--color-primary);
  border-color: var(--color-primary);
  font-weight: 650;
}
/* The check mark conveys a selected state even when colour is unavailable. */
.showing-choice-chip.is-on::before,
.showing-basement-chip input:checked + span::before {
  content: '\2713';
  font-weight: 700;
}
.showing-choice-chip:focus-visible,
.showing-basement-chip input:focus-visible + span {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.showing-basement-chip {
  position: relative;
}
.showing-basement-chip input {
  position: absolute;
  opacity: 0;
}
.snotes {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.snote {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.snote .srow-control {
  height: auto;
  min-height: var(--showing-row-h);
}

/* #2861: label-left/control-right "Room details" list replacing the
   .schips tap-chip cluster for the inspection-capture screen (Kitchen,
   Bathroom, Bedroom, Laundry, unit-level chips). Deliberately its own
   class names rather than restyling .schips/.schip/.snotes/.snote in
   place -- those classes are still used unchanged by the prospect/showing
   response-feedback screens (see showingChipsHtml's own call sites), and
   changing their layout here would have changed that other screen too.
   One row per chip: fixed-width label column (same muted/xs treatment as
   .srow-head/.sfield-divider above it), control takes the rest of the
   row's width. .is-full spans both columns for what used to be the
   .snotes wide-chip case (a longer text field) -- one list, not two. */
.sattr-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.sattr-row {
  display: grid;
  grid-template-columns: minmax(110px, 40%) 1fr;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--showing-row-h);
}
.sattr-row.is-full {
  grid-template-columns: 1fr;
}
.sattr-row.is-full .sattr-row-label {
  margin-bottom: 2px;
}
.sattr-row-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sattr-row.is-full .sattr-row-label {
  white-space: normal;
}
.sattr-row-control {
  min-width: 0;
}
.sattr-row-control .srow-control {
  width: 100%;
}
.sattr-row-hint {
  grid-column: 1 / -1;
}

/* Section-level photo widget, collapsed unless it already holds something
   (legacy pre-ID-386 photos were stored against the bare section key). */
.showing-section-photos {
  margin-top: var(--space-3);
}
.showing-section-photos > summary {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
}
.showing-section-photos > summary:hover {
  color: var(--color-primary);
}

/* "3/12" completion badge in each accordion summary. */
.showing-section-badge {
  margin-left: auto;
  margin-right: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
  border-radius: 999px;
  padding: 0 var(--space-2);
}
.showing-section-badge:empty {
  display: none;
}
.showing-section-badge.is-empty {
  opacity: 0.55;
}
.showing-section-badge.is-complete {
  color: var(--color-primary);
  border-color: var(--color-primary);
}

/* Sticky jump-nav across the visible sections. */
.showing-section-nav {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: var(--space-2) 0;
  margin-bottom: var(--space-2);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-divider);
}
.showing-nav-chip {
  font-size: var(--text-xs);
  padding: 3px var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: 999px;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  cursor: pointer;
}
.showing-nav-chip:hover {
  color: var(--color-primary);
  border-color: var(--color-primary);
}
.showing-nav-chip-count {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
/* Collapse-all sits with the section/unit chips but reads as an action,
   not a jump target -- underline instead of the pill outline (#932). */
.showing-nav-chip.showing-nav-collapse-all {
  border-color: transparent;
  background: none;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Nothing is ever deleted when the showing type changes; this says so. */
.showing-hidden-data-banner {
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  border: 1px solid var(--color-divider);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-sm);
}

/* #1593: ad-hoc Inspection notice-acknowledgment choices, rendered into
   #genericConfirmModalOptions via showGenericConfirmModal's optionsHtml.
   Radio-style so the confirm button's enabled state has one clear source
   of truth (a checked input) rather than tracking clicked-button state
   redundantly in two places. */
.adhoc-notice-reason-choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  font-size: var(--text-sm);
  cursor: pointer;
}
.adhoc-notice-reason-choice input {
  margin-top: 3px;
}
.adhoc-notice-warning {
  margin: 0 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-warning-text);
  background: var(--color-warning-highlight);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-sm);
}

#showingSharedDetails.is-readonly {
  opacity: 0.85;
}

/* Prospect showing form (#3179). */
.sgroup + .sgroup {
  margin-top: var(--space-4);
}
.sgroup-title {
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-1);
}
/* #5067: was repeat(4, ...) -- Date (dead markup, always read-only text
   pointing back at #showingDate above) and Units (the search/chip picker)
   both came out of this grid. Date was removed outright; Units now renders
   as its own full-width block after this row (.prospect-event-units-full
   below) instead of being squeezed into 1-of-4 columns, which is what left
   its search dropdown only ~180px wide on desktop. */
.prospect-event-header {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.prospect-event-field {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.prospect-event-label {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.prospect-time-range {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
}
.prospect-time-range input {
  width: 100%;
  min-width: 0;
}
.time-chip-field {
  min-width: 0;
  flex: 1;
}
.time-chip-sep {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  padding-top: 6px;
}
.time-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.time-chip {
  padding: 4px var(--space-2);
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  font-size: var(--text-xs);
  cursor: pointer;
  white-space: nowrap;
}
.time-chip:hover {
  background: var(--color-surface-2);
}
.time-chip.is-selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
.time-chip-custom-input {
  margin-top: var(--space-1);
}
.time-chip-custom-input[hidden] {
  display: none;
}
/* #3430: the "+ Custom time" fallback now reveals two native time inputs
   side by side (start/end) under the single range-chip row, instead of one
   input per field column. */
.time-chip-custom-range {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.time-chip-custom-range input {
  flex: 1;
  min-width: 0;
}
/* #5067: full-width sibling of .prospect-event-header, not one of its grid
   columns -- deliberately not named .prospect-event-units (the old class
   this replaces) so nothing still assuming the old 1-of-4-column layout
   silently keeps matching. */
.prospect-event-units-full {
  min-width: 0;
  margin-bottom: var(--space-4);
}
.prospect-event-units-full > #showingProspectUnitsMount {
  min-width: 0;
}
.prospect-event-units-full #showingUnitsAccordion {
  margin: 0;
}
.prospect-event-units-full #showingScopeUnits {
  margin: var(--space-2) 0 0;
}
.scope-unit-picker {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.scope-unit-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.scope-unit-chips:empty {
  display: none;
}
.scope-unit-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-1) 2px var(--space-2);
  border-radius: 999px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  font-size: var(--text-xs);
  line-height: 1.6;
}
.scope-unit-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: none;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 0;
  border-radius: 50%;
}
.scope-unit-chip-remove:hover {
  background: var(--color-surface-offset);
  color: var(--color-text);
}
.scope-unit-search {
  width: 100%;
}
/* #5067: position:absolute here is the un-portalled baseline (still what
   applies when JS hasn't portalled the element -- e.g. before first open,
   or in an environment with JS disabled). positionShowingScopeUnitSearchResults
   (app.js) overrides position to fixed with inline top/left/width once
   this is reparented to <body> to escape #showingUnitsAccordion's
   overflow:hidden in the prospect flow; z-index raised past the accordion
   card and modal-adjacent chrome so the portalled popup still paints on
   top of everything on the page, not just its original stacking context. */
.scope-unit-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 900;
  margin-top: 2px;
  max-height: 220px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
}
/* #5067: the floor quick-filter chip row rendered as the first child of the
   results popup, above the matching rows. */
.scope-unit-floor-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-divider);
}
.scope-unit-floor-chip {
  padding: 2px var(--space-2);
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  font-size: var(--text-xs);
  cursor: pointer;
  white-space: nowrap;
}
.scope-unit-floor-chip:hover {
  background: var(--color-surface-2);
}
.scope-unit-floor-chip.is-selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
/* #5067: was a single-line label -- now a two-line row (label bold, a
   bed/bath/floor metadata line below it in muted/smaller text), built by
   showingScopeUnitResultRowHtml(). */
.scope-unit-search-result {
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: 100%;
  text-align: left;
  padding: var(--space-2);
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text);
}
.scope-unit-result-label {
  font-weight: 600;
}
.scope-unit-result-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.scope-unit-search-result:hover,
.scope-unit-search-result:focus,
.scope-unit-search-result.is-active {
  background: var(--color-surface-2);
}
.prospect-host-options {
  display: flex;
}
.prospect-host-options select {
  width: 100%;
  min-width: 0;
}
.prospect-table-section {
  display: grid;
  gap: var(--space-2);
}
.prospect-table-scroll {
  overflow-x: auto;
}
.prospect-table {
  min-width: 970px;
}
.prospect-table-head,
.prospect-table-row {
  display: grid;
  grid-template-columns: minmax(110px, 1.25fr) minmax(150px, 1.35fr) minmax(115px, 1fr) minmax(90px, 0.75fr) minmax(80px, 0.7fr) minmax(78px, 0.65fr) minmax(150px, 1.5fr) minmax(230px, 2fr) 34px;
  column-gap: var(--space-1);
  align-items: center;
}
.prospect-table-head {
  padding: 0 var(--space-1) var(--space-1);
  border-bottom: 1px solid var(--color-divider);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.prospect-table-row {
  min-height: 46px;
  padding: var(--space-1);
  border-bottom: 1px solid var(--color-divider);
}
.prospect-table-control {
  width: 100%;
  min-width: 0;
  min-height: 34px;
  box-sizing: border-box;
  padding: 5px var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
}
textarea.prospect-table-control {
  display: block;
  resize: vertical;
}
.prospect-table-control:focus {
  outline: none;
  border-color: var(--color-primary);
}
.prospect-applicant-badge {
  display: inline-flex;
  padding: 2px var(--space-1);
  border-radius: 999px;
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: 650;
}
.prospect-empty-value {
  color: var(--color-text-muted);
}
.prospect-attendance {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 3px;
}
.prospect-attendance-btn {
  min-height: 34px;
  padding: 3px var(--space-1);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--text-xs);
}
.prospect-attendance-btn.is-selected {
  border-color: var(--color-primary);
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  font-weight: 650;
}
.prospect-row-actions {
  display: flex;
  justify-content: center;
}
.prospect-remove-btn {
  width: 30px;
  min-height: 30px;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: var(--text-lg);
  line-height: 1;
}
.prospect-remove-btn:hover {
  color: var(--color-error-text);
  border-color: var(--color-error-text);
}
.prospect-table-empty {
  margin: var(--space-3) 0;
  color: var(--color-text-muted);
}
.prospect-add-row {
  justify-self: start;
}
.prospect-table-status {
  min-height: 1.2em;
  margin: 0;
}
.showing-conversion-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Mobile cards keep the prospect's name/rating prominent and make attendance
   the easiest control to tap while standing at the showing. */
@media (max-width: 640px) {
  .srow {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) var(--showing-photo-w);
    grid-template-areas:
      'name name photo'
      'a    b    photo'
      'notes notes notes';
    padding: var(--space-2);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
  }
  .srow-name { grid-area: name; font-weight: 600; }
  .srow-cell[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    margin-bottom: 1px;
  }
  /* nth-of-type counts every child div, and .srow-name is child 1 -- so the
     two data cells are children 2 and 3. Getting this wrong silently swapped
     the Age and Condition columns on mobile.
     #4366: scoped away from .srow-repairs-list/.srow-repair -- both define
     their OWN grid-template-areas: none + per-cell grid-area: auto reset
     further down, but a bare class+pseudo-class selector here is (0,3,0)
     specificity, which beats their two-class (0,2,0) reset regardless of
     source order. Without the :not() those two row types silently kept
     inheriting named areas 'a'/'b' that don't exist in their own
     grid-template-areas, so their cell 2 and cell 3 collapsed into the
     same auto-placed grid cell instead of separate ones -- confirmed via a
     live computed-style dump: .srow-repairs-list rendered with 4 implicit
     columns instead of its intended 2, and Source/Priority-location (cells
     2 and 3) shared virtually the same bounding box, rendering as
     illegible overlapping text. */
  .srow:not(.srow-repairs-list):not(.srow-repair) > .srow-cell:nth-of-type(2) { grid-area: a; }
  .srow:not(.srow-repairs-list):not(.srow-repair) > .srow-cell:nth-of-type(3) { grid-area: b; }
  .srow-notes { grid-area: notes; }
  .srow-photo { grid-area: photo; }
  .srow-cell:empty::before { height: 0; }
  .srow-head { display: none; }
  .srow-repair {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas: none;
  }
  .srow-repair > .srow-cell { grid-area: auto; }
  .srow-repair > .srow-cell:first-child { grid-column: 1 / -1; }
  /* #1940: the repairs list card -- description spans full width (it's the
     row's identity, same treatment as .srow-repair's first cell above),
     everything else stacks two-up with its data-label caption. */
  .srow-repairs-list,
  .repair-item.srow-repairs-list {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas: none;
    padding: var(--space-2);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    background: var(--color-surface-2);
  }
  .srow-repairs-list > .srow-cell { grid-area: auto; }
  .srow-repairs-list > .repairs-list-desc-cell { grid-column: 1 / -1; }
  .schips {
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  }
  /* #4321: Overall assessment's Rating + Recommendation selects still fit two
     to a 130px-minmax row at 375px, crowding Recommendation's longer option
     labels against the right edge. That section specifically reads better
     full-width; scoped to #showingOverallFields rather than every .schips
     instance, most of which (e.g. the condition-picker chip clusters) are
     fine at two-up. */
  #showingOverallFields .schips {
    grid-template-columns: minmax(0, 1fr);
  }
  /* #4321 QA re-test (2026-09-03): the rule above only fixes which FIELD
     goes where -- .schips is the outer grid deciding whether Rating and
     Recommendation sit in separate columns or stacked rows. It never
     touched the chip row INSIDE each field: .showing-choice-grid is
     `display:flex; flex-wrap:wrap`, so Rating's five "1"-"5" chips and
     Recommendation's three-to-four option chips still wrap onto a shared
     row instead of stacking one-per-line, which was the actual QA-FAILED
     symptom (crowded chip labels, not crowded fields). QA's same pass
     found the identical wrap behaviour in Tenant & occupancy's
     Cleanliness/care chips (#showingTenantFields) and called it systemic
     rather than isolated to Overall assessment, so both containers get the
     one-per-line treatment here rather than just the one the ticket named.
     Scoped to these two containers, not .showing-choice-grid globally --
     the condition-picker chip clusters elsewhere on this screen already
     read fine at their existing wrapped width and were never part of this
     ticket's scope or QA's finding. */
  #showingOverallFields .showing-choice-grid,
  #showingTenantFields .showing-choice-grid {
    flex-direction: column;
    align-items: stretch;
  }
  #showingOverallFields .showing-choice-chip,
  #showingTenantFields .showing-choice-chip {
    justify-content: center;
  }
  .prospect-event-header {
    grid-template-columns: 1fr;
  }
  .prospect-table-scroll {
    overflow: visible;
  }
  .prospect-table {
    min-width: 0;
  }
  .prospect-table-head {
    display: none;
  }
  .prospect-table-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr);
    grid-template-areas:
      'name rating'
      'attendance attendance'
      'applicant applicant'
      'email phone'
      'time time'
      'notes notes'
      'actions actions';
    gap: var(--space-2);
    margin-bottom: var(--space-2);
    padding: var(--space-3);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    background: var(--color-surface-2);
  }
  .prospect-table-cell::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 2px;
    color: var(--color-text-muted);
    font-size: var(--text-xs);
  }
  .prospect-table-row > .prospect-table-cell:nth-child(1) { grid-area: name; }
  .prospect-table-row > .prospect-table-cell:nth-child(2) { grid-area: email; }
  .prospect-table-row > .prospect-table-cell:nth-child(3) { grid-area: phone; }
  .prospect-table-row > .prospect-table-cell:nth-child(4) { grid-area: time; }
  .prospect-table-row > .prospect-table-cell:nth-child(5) { grid-area: applicant; }
  .prospect-table-row > .prospect-table-cell:nth-child(6) { grid-area: rating; }
  .prospect-table-row > .prospect-table-cell:nth-child(7) { grid-area: notes; }
  .prospect-table-row > .prospect-table-cell:nth-child(8) { grid-area: attendance; }
  .prospect-attendance-btn {
    min-height: 46px;
    font-size: var(--text-sm);
  }
  .prospect-row-actions {
    grid-area: actions;
    justify-content: flex-end;
  }
}

/* #2164: the repair record's own Documents section (app.js's
   refreshRepairDocumentsWidget). Same responsive idiom as .srow/.srow-cell
   above -- a real grid "table" at desktop width, stacked cards under the
   640px breakpoint via .srow-cell[data-label]::before -- reusing that exact
   mechanism (and the existing .srows/.srow-head wrapper/header classes)
   rather than inventing a third responsive pattern. .repair-docs-row itself
   is a new class, not a repurposing of .srow-repair, because the column set
   is different: title/type/size/uploaded-by/actions, not the in-visit
   repair row's description/asset/priority/location/cost. */
.repair-docs-row {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 0.9fr) minmax(0, 0.7fr) minmax(0, 1.1fr) auto;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider);
}
.repair-docs-row:last-child {
  border-bottom: none;
}
.repair-docs-title {
  font-size: var(--text-sm);
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.repair-docs-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.repair-docs-actions {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
}
@media (max-width: 640px) {
  .repair-docs-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* Four labelled cells, not three: Document / Type / Size / Uploaded. An
       unplaced cell would auto-flow into an implicit row BELOW the actions
       row, putting "Uploaded" under the buttons -- so Uploaded gets its own
       full-width area. */
    grid-template-areas:
      'title title'
      'a b'
      'c c'
      'actions actions';
    padding: var(--space-2);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    border-bottom: 1px solid var(--color-divider);
    margin-bottom: var(--space-2);
  }
  .repair-docs-row .repair-docs-title { grid-area: title; font-weight: 600; }
  .repair-docs-row > .srow-cell:nth-of-type(2) { grid-area: a; }
  .repair-docs-row > .srow-cell:nth-of-type(3) { grid-area: b; }
  .repair-docs-row > .srow-cell:nth-of-type(4) { grid-area: c; }
  .repair-docs-row .repair-docs-actions { grid-area: actions; justify-content: flex-start; }
  .repair-docs-row.srow-head { display: none; }
}

/* #4277 QA follow-up: Vendor documents and Linked documents now share one
   outer widget card (#repairDocumentsAndBillingWidget) instead of two
   separately-headed .field cards. Each subsection keeps its own heading and
   a divider from the one above it, so the two upload flows stay visually
   distinguishable without reading as two unrelated features. */
.repair-docs-subsection + .repair-docs-subsection {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-divider);
}
.repair-docs-subsection-title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}

/* One-line outcome summary under each showings-list row (ID 389). */
.showing-item-summary {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text);
}

/* Compare per-column basis (Task List ID 351) --------------------------------
   Lives in the column header, under the property name/address. Small and
   low-contrast on purpose: it is a qualifier on the column, not a headline. */
.compare-col-basis {
  margin-top: 6px;
}
.compare-basis-select {
  font-size: 0.78rem;
  padding: 2px 6px;
  max-width: 100%;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
  color: inherit;
}
.compare-basis-static {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  opacity: 0.7;
}
/* Window + month count for a realized column. Never omitted, so it gets its
   own line rather than competing with the basis control for space. */
.compare-col-window {
  margin-top: 3px;
  font-size: 0.72rem;
  font-weight: 400;
  opacity: 0.75;
}
/* Under the documented low-data threshold (6 months). Colour only -- the month
   count itself is already on screen, this just makes it register. */
.compare-col-window-low {
  color: var(--color-warning-text);
  opacity: 1;
}
/* A metric with no realized figure available. Muted so it reads as "not
   applicable on this basis" rather than as a zero. */
.compare-basis-pending {
  opacity: 0.45;
}

/* CapEx group separator in the Compare table (Task List ID 352). The CapEx rows
   sit outside NOI by design, so they get a labelled band rather than blending
   into the headline metrics above them. */
.compare-group-row th {
  background: var(--color-surface-offset);
  border-top: 2px solid var(--color-border);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-align: left;
  padding-top: 10px;
  padding-bottom: 10px;
}

.compare-footnote {
  margin: 14px 2px 0;
  max-width: 78ch;
  font-size: 12px;
  line-height: 1.55;
  color: var(--color-text-muted);
}

/* Compare column badges and guardrails (Task List ID 354).
   Phase and basis ride together under the property name: what stage the
   property is at, and what the numbers below it are measuring. */
.compare-col-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  margin: 4px 0 2px;
}
.compare-basis-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 10px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  border: 1px solid transparent;
}
/* Underwriting is a projection; realized is a measurement. Deliberately
   different tones so the distinction survives a glance at a screenshot. */
.compare-basis-badge.compare-badge-underwriting {
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
  border-color: var(--color-border);
}
.compare-basis-badge.compare-badge-realized {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
  border-color: var(--color-success);
}
.compare-guardrail {
  margin: 8px 0 0;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 12.5px;
  line-height: 1.45;
  border: 1px solid;
}
.compare-guardrail[hidden] { display: none; }
.compare-guardrail-mixed {
  background: var(--color-surface-offset);
  border-color: var(--color-border);
  color: var(--color-text);
}
.compare-guardrail-lowdata {
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}
/* In the picker the badge is a leading marker, so it needs breathing room
   from the property name that follows it. */
.suggestion-phase {
  margin-right: 6px;
  vertical-align: 1px;
}

/* ================= "Add a property I already own" modal =================
   Task List ID 348. The shared .field-row is a hard 2-column grid; three of
   the rows in this form are logical triplets (beds/full/half baths, rent/
   taxes/insurance, down payment/rate/term) which would otherwise wrap with a
   lone orphan cell on a second line. Scoped to this modal so nothing else
   shifts. Collapses to a single column on narrow screens, same as the rest
   of the modal forms. */
#addOwnedModal .field-row-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}
@media (max-width: 640px) {
  #addOwnedModal .field-row-3,
  #addOwnedModal .field-row {
    grid-template-columns: 1fr;
  }
}
/* Section dividers inside the form. Small, muted and uppercase rather than a
   real heading weight -- they group fields, they aren't page structure. */
#addOwnedModal .add-owned-section-heading {
  /* space-4 above to separate it from the previous group, space-2 below so it
     doesn't butt straight into the first label underneath it. */
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted, #6b7280);
}
/* The default `.field label` is a space-between flex row -- right for a
   stacked label plus an "optional" hint, wrong for a checkbox, which it pushes
   to the opposite edge of the row from its own caption. Same fix as
   .repair-timing-check label. */
#addOwnedModal .add-owned-cash-toggle {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  cursor: pointer;
}
#addOwnedModal .add-owned-cash-toggle input {
  width: auto;
  margin: 0;
}
#addOwnedModal #addOwnedLoanFields[hidden] {
  display: none;
}
#addOwnedModal #addOwnedError {
  color: var(--color-error, #b91c1c);
}
/* The #addOwnedModal .export-row override that used to live here is gone: the
   modal now uses .modal-actions (see that rule near .export-row), which is the
   shared fix rather than a per-modal patch. Task List ID 424. */

/* #3012 -- Add Owned Property wizard shell (Basics / Units / Tenants /
   Review). Everything here is scoped with a wizard- or add-owned- prefix per
   the implementation brief, and reuses .modal-card-wide / .field / .field-row
   / .btn-export / .action-btn wholesale rather than duplicating them. */
.wizard-steps {
  display: flex;
  list-style: none;
  margin: 0 0 var(--space-4);
  padding: 0;
  gap: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.wizard-step-tab {
  flex: 1;
}
.wizard-step-btn {
  width: 100%;
  border: none;
  background: none;
  padding: var(--space-2) var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
}
.wizard-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-weight: 650;
  font-size: 0.7rem;
}
.wizard-step-tab.is-active .wizard-step-btn {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}
/* ID 266: --color-text-inverse is the theme-aware on-primary/on-success
   text token (6.9:1 light / 6.5:1 dark) -- a hardcoded #fff clears the
   light theme's --color-primary/--color-success but fails WCAG AA against
   their lighter dark-theme values (contrastPairs.unit.js caught this). */
.wizard-step-tab.is-active .wizard-step-num {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
/* A completed step's label is clickable (jump back without losing data); a
   step ahead of the furthest one reached yet is not, since its dependent
   data may not exist. Not `[disabled]` -- the button stays discoverable in
   the tab order, it just declines the click. */
.wizard-step-tab.is-completed .wizard-step-btn {
  color: var(--color-text);
  cursor: pointer;
}
.wizard-step-tab.is-completed .wizard-step-num {
  background: var(--color-success, #1a7f4b);
  color: var(--color-text-inverse);
}
/* #3527: was `opacity: 0.55`, which composited --color-text-muted down to
   #a2a7ae on the modal's white surface -- 2.42:1, a serious WCAG 2.1 AA
   color-contrast failure on all three future-step buttons. These steps keep
   their place in the tab order and are not `[disabled]`, so the disabled-control
   exemption does not apply and they owe the full 4.5:1. Opacity cannot buy that
   here: even 0.8 only reaches 4.04:1 light / 4.45:1 dark.

   The "not reached yet" cue moves onto the step NUMBER instead -- outlined
   rather than filled, against active (primary fill) and completed (green fill).
   That is a UI-component boundary at a 3:1 bar, not body text at 4.5:1, so the
   distinction survives without dimming any label: both the label and the number
   now sit at --color-text-muted, 6.46:1 light / 6.17:1 dark.

   This dialog only started being scanned in #3527 -- the spec could not open it
   (see the #3142 note in a11yAuthenticatedDialogs.spec.js), which is why a
   violation this plain went unreported. */
.wizard-step-tab.is-future .wizard-step-btn {
  cursor: default;
}
.wizard-step-tab.is-future .wizard-step-num {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--color-border);
  color: var(--color-text-muted);
}
.wizard-step[hidden] {
  display: none;
}
.wizard-step-actions {
  margin-top: var(--space-4);
}
/* Per-unit cards reuse .unit-card/.unit-card-head/.unit-card-grid/.unit-cell
   wholesale (same convention #234's common-areas cards already followed) --
   the only wizard-specific addition is the occupancy field and the narrower
   field set (no garage/grade-split, which don't matter for a quick add). */
.wizard-unit-list {
  margin-bottom: var(--space-3);
}
.wizard-unit-card-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.wizard-unit-occupancy-badge {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 1px 8px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}
.wizard-unit-occupancy-badge.is-occupied {
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
  color: var(--color-primary);
}
/* Tenants step: one panel per occupied Step-2 row, or a one-line vacant
   note. Reuses .unit-card for the panel shell so it reads as a sibling of
   the Units-step cards rather than a different kind of object. */
.wizard-tenant-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.wizard-tenant-vacant-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.wizard-tenant-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.wizard-tenant-skip-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
  text-transform: none;
  font-weight: 400;
}
.wizard-tenant-body[hidden] {
  display: none;
}
.wizard-cotenant-row {
  border-top: 1px dashed var(--color-border);
  padding-top: var(--space-3);
  margin-top: var(--space-3);
}
.wizard-add-cotenant-btn {
  margin-top: var(--space-2);
}
/* Review step: plain label/value rows, grouped like .field-note but with no
   inputs -- this step is read-only by design. */
.wizard-review-group {
  margin-bottom: var(--space-4);
}
.wizard-review-group h3 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted, #6b7280);
}
.wizard-review-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 3px 0;
  font-size: var(--text-sm);
}
.wizard-review-row-label {
  color: var(--color-text-muted);
}

/* #3013 -- CSV bulk import preview/results. Reuses .unit-card and
   .wizard-review-row wholesale (per-property card, per-unit detail rows)
   rather than inventing a parallel set of primitives. */
.csv-import-preview-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.csv-import-preview-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
}
.csv-import-preview-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
/* Uses --color-warning-highlight, the same tinted-background token every
   other warning badge in this file already uses (e.g. the pipeline chip) --
   there is no --color-warning-bg token, so that name silently fell back to
   a static light hex in both themes and failed WCAG AA in dark mode
   (tests/contrastPairs.unit.js). Do not introduce a second background-token
   name for the same tone (ID 738). */
.csv-import-dupe-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
.csv-import-preview-card summary {
  cursor: pointer;
  margin-top: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.csv-import-results-table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-3) 0;
}
.csv-import-results-table th,
.csv-import-results-table td {
  text-align: left;
  padding: 6px 8px;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.csv-import-result-error {
  color: var(--color-error, #b91c1c);
}

/* ID 686: KPI tiles have no loading state of their own, so between a filter
   change and the response landing they keep painting the PREVIOUS scope's
   number. In production, picking a single property on CapEx & RUL showed the
   portfolio-wide reserve position (-$8,540) for a beat before settling to that
   property's -$8,921 -- a real, plausible, wrong-scope dollar figure that was
   visually indistinguishable from a settled one.

   The value stays readable (often it is unchanged, and blanking it on every
   keystroke of a date filter is worse) but is clearly provisional: dimmed, with
   the pointer withheld from any interactive tile. Set by kpiScopePending() in
   app.js alongside aria-busy on the grid, which is what a screen reader gets --
   opacity alone announces nothing.

   Opacity rather than a colour override on purpose: the funding chip carries
   meaning in its hue and border ([data-funding] above), and repainting it grey
   would swap one misleading state for another. */
.kpi-grid-pending .kpi-value,
.kpi-grid-pending .kpi-sub {
  opacity: 0.4;
  transition: opacity 120ms ease-out;
}
.kpi-grid-pending .kpi-card {
  cursor: progress;
}
@media (prefers-reduced-motion: reduce) {
  .kpi-grid-pending .kpi-value,
  .kpi-grid-pending .kpi-sub { transition: none; }
}

/* ---------------------------------------------------------------------------
   ID 814 -- unsigned-lease age + chase history in the lease drawer's e-sign bar.

   The bar itself (ID 107) shipped with no CSS rule at all, so it inherited
   nothing and sat flush against the section above it. Giving it the same
   top border every .ld-section already has keeps the new block from reading as
   part of the Notes textarea above it.
   --------------------------------------------------------------------------- */
.ld-esign-bar {
  padding: var(--space-4) 0;
  border-top: 1px solid var(--color-border);
}
/* #1706: the six-state Signing card. One label, one status line, one action
   row -- every state hides all but its own action(s) via the [hidden]
   attribute on the buttons/links themselves, so this wrapper needs no
   per-state modifier classes. */
.ld-signing-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.ld-signing-label {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.ld-esign-activity {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.ld-esign-age {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
/* An envelope past the last owner-alert bucket is the case this whole feature
   exists to make visible, so it is coloured rather than left to blend in. */
.ld-esign-age.is-stale {
  color: var(--color-warning-text);
}
.ld-esign-reminder-list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ld-esign-reminder-list li {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  line-height: 1.45;
}
.ld-esign-reminder-list .ld-esign-audience {
  font-weight: 600;
  color: var(--color-text);
}

/* ---------------------------------------------------------------------------
   #464: the void-signature form.

   Bordered in the error colour rather than the plain border colour because the
   action is irreversible at the provider -- once it fires the envelope is
   recalled and the signers have been emailed, and there is no undo to offer.
   Everything else in this bar is a send, so the void needs to not look like one.
   --------------------------------------------------------------------------- */
.ld-void-panel {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-error);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.ld-void-warning {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-error);
  line-height: 1.45;
}
.ld-void-field {
  display: block;
  margin-bottom: 4px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.ld-void-field + textarea {
  width: 100%;
  margin-bottom: var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  resize: vertical;
}
.ld-void-req {
  font-weight: 400;
  color: var(--color-error);
}
.ld-void-opt {
  font-weight: 400;
  color: var(--color-text-muted);
}
/* The hint carries the whole point of having two fields, so it is part of the
   label rather than a tooltip -- an owner who does not read it is the one who
   types "failed screening" into the field the applicant receives. */
.ld-void-hint {
  display: block;
  margin-top: 2px;
  font-weight: 400;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.45;
}
.ld-void-confirm {
  border-color: var(--color-error);
  color: var(--color-error);
}
.ld-void-confirm:hover:not(:disabled) {
  border-color: var(--color-error);
  color: var(--color-error);
  opacity: 0.8;
}

/* ---------------------------------------------------------------------------
   Per-screen documentation deep link (Task List ID 793)

   The "?" that sits beside a screen's <h1> and opens the guide section for
   THAT screen. PR #38's nav entry opens the top of the guide; this lands on
   the anchor. Every href is generated from the SURFACES registry in
   scripts/dev/build-docs-coverage.js and pinned by
   tests/docs-checks/portalHelpAnchors.unit.js -- do not hand-edit one in the
   markup.

   The :has() rule is what keeps the "?" on the heading's line: the anchor is a
   SIBLING of the <h1>, not a child, so that it stays out of the heading's
   accessible name. Scoped to headings that are actually followed by one, so no
   other <h1> in the portal changes box type.
   --------------------------------------------------------------------------- */
h1:has(+ .help-anchor) {
  display: inline-block;
  vertical-align: middle;
}
.help-anchor {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-left: var(--space-2);
  vertical-align: middle;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  transition: border-color var(--transition-interactive), color var(--transition-interactive);
}
.help-anchor:hover,
.help-anchor:focus-visible {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* -------------------------------------------------------------------------
   #213 / spec 5.3.1 -- Loan tab event-timeline badges.

   Deliberately a new pair of classes rather than a reuse of .status-pill:
   that one is a clickable filter control (border, cursor:pointer) and these
   are non-interactive labels inside a table cell. Scoped to the timeline so
   they cannot leak into the Rent Roll chips or the phase pill.

   The -warn tone carries real meaning here (#210 finding 4): it marks a row
   the #214 backfill RECONSTRUCTED from underwriting assumptions because no
   closing record existed. It must read as a caveat, not as decoration.
   ------------------------------------------------------------------------- */
#loanTimelineSection .loan-event-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: var(--radius-full);
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  vertical-align: baseline;
  background: var(--color-surface-offset);
  color: var(--color-text-muted);
}
#loanTimelineSection .loan-event-badge-warn {
  background: color-mix(in srgb, var(--color-warning, #b45309) 16%, transparent);
  color: var(--color-warning, #b45309);
}
#loanTimelineSection .loan-event-badge + .loan-event-badge {
  margin-left: 4px;
}
#loanTimelineSection .loan-event-action {
  margin-top: 4px;
}

/* #213: the loan timeline is a .mini-table, which right-aligns every column
   after the first because its usual payload is a numeric grid. Four of these
   columns are prose (event, lender, notes) or a date, and right-aligned prose
   in a wide table is genuinely hard to scan -- the eye loses the left edge. */
#loanTimelineTable th[data-align='left'],
#loanTimelineTable td[data-align='left'] {
  text-align: left;
  font-family: var(--font-body);
  font-variant-numeric: normal;
}
#loanTimelineTable td:first-child {
  white-space: nowrap;
  color: var(--color-text-muted);
}
#loanTimelineTable td:last-child {
  max-width: 30ch;
  white-space: normal;
}
#loanTimelineTable th,
#loanTimelineTable td:not([data-align='left']) {
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   #244 / spec 5.4.6 -- Own-phase Unit Info readout.

   Deliberately NOT styled like the disabled inputs elsewhere in Own. A greyed
   input still looks like a control that has been taken away; these are card and
   table readouts, which look like what they are: facts maintained somewhere
   else. The "Manage in Rent Roll" button is the only interactive element in
   either block.
   --------------------------------------------------------------------------- */
.own-readout[hidden] {
  display: none;
}
.own-readout-source {
  margin: 0 0 var(--space-3);
}
.own-readout-grid {
  display: grid;
  /* auto-fit rather than a fixed 4-up plus a viewport media query: the Unit
     Info panel is a narrow single form column, so the card count has to react
     to the CONTAINER width, which a max-width breakpoint on the viewport
     cannot see. At the panel's usual ~530px this lands on 2-up. */
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
  /* Caps the row at four cards on a wide panel -- eight cards spread across
     seven columns reads as a broken layout rather than a summary. */
  max-width: 920px;
}
.own-readout-card {
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.own-readout-card-label {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}
.own-readout-card-value {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  line-height: 1.2;
  color: var(--color-text);
}
.own-readout-card-sub {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: var(--space-1);
}
/* Thirteen columns will overflow a narrow viewport no matter what. Scrolling the
   table is the right answer -- wrapping the header cells instead produces a
   three-line header on every column and makes the block taller than the data. */
.own-readout-tablewrap {
  overflow-x: auto;
  /* A fieldset does not shrink below its content by default, so without this
     the 13-column table paints straight through the fieldset's right edge
     instead of scrolling inside it. */
  min-inline-size: 0;
  max-width: 100%;
}
fieldset.own-readout {
  min-inline-size: 0;
}
.own-readout-table {
  width: 100%;
  /* Below this the columns collapse into each other; scroll instead. */
  min-width: 880px;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.own-readout-table th,
.own-readout-table td {
  padding: var(--space-2);
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--color-border);
}
.own-readout-table th {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  text-align: right;
}
.own-readout-table th:first-child,
.own-readout-table td:first-child,
.own-readout-table th:last-child,
.own-readout-table td:last-child {
  text-align: left;
}
.own-readout-table td {
  font-family: var(--font-mono);
}
.own-readout-table td:first-child,
.own-readout-table td:last-child {
  font-family: var(--font-body);
}
.own-readout-table tbody tr:last-child td {
  border-bottom: 0;
}
/* #216: the Rent position table is six columns, not thirteen, so it fits without
   the horizontal scroll the wide per-unit-detail table needs. Inheriting that
   880px floor pushed "Lease ends" off the visible edge -- present in the DOM,
   invisible on screen, which is the worst of both. */
.own-readout-table.own-readout-table-narrow {
  min-width: 0;
}
.own-readout-foot {
  margin-top: var(--space-3);
}
.own-readout-empty[hidden] {
  display: none;
}

/* ---------------------------------------------------------------------------
   #248 -- the component CapEx reference line under the flat CapEx input
   (spec 8.7.3a). This is the deliberately QUIET one. It carries no accent
   border, no amber, no badge: the flat number differing from the component sum
   is the normal, correct state, because the owner includes things the catalog
   does not model. Compare .systems-suggestions above, which DOES get an accent
   because a source disagreement genuinely wants an answer.
   --------------------------------------------------------------------------- */
/* #531: the small flag button that replaces the always-expanded card as the
   default state. Deliberately plain -- same visual weight as the `?` help
   buttons beside it, not a callout, because a component-based estimate
   sitting next to the owner's number is informational, same as the note text
   it used to auto-show (spec 8.7.3a: never amber, never urgent). Clicking it
   toggles `.capex-forecast-line[hidden]` exactly like `.help-toggle-btn`
   toggles a `.field-note`; see the click handler in app.js. */
.capex-forecast-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
  width: auto;
}
.capex-forecast-flag[hidden] {
  display: none;
}
.capex-forecast-flag:hover,
.capex-forecast-flag[aria-expanded='true'] {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.capex-forecast-line {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-offset);
}
.capex-forecast-line[hidden] {
  display: none;
}
.capex-forecast-text {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.capex-forecast-caveat {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.capex-forecast-caveat[hidden] {
  display: none;
}
/* The ONE case worth a note (spec 8.7.3a): the flat number sitting BELOW the
   component sum. Informational, not an error -- under-reserving is a real
   risk, but it can also be a deliberate call, so this states both figures and
   stops. */
.capex-forecast-under {
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  /* Deliberately NOT warning amber (spec 8.7.3a). Being under the component
     projection is a legitimate underwriting choice, not an error, and the copy
     says so out loud. Amber here made a neutral observation read as a problem
     the user had to fix. Regular body colour keeps it a statement while still
     separating it from the muted caveat line above. */
  color: var(--color-text);
}
.capex-forecast-under[hidden] {
  display: none;
}
.capex-forecast-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.capex-forecast-actions button[hidden] {
  display: none;
}

/* The Metrics half of #248: the same computation as a table (spec 8.7.4).
   `.capex-forecast-conf-estimated` is spec 8.7.2 -- "an estimated roof age
   driving an $18k reserve line has to READ as estimated", so the marker is on
   every row rather than in a footnote. */
.capex-forecast-total {
  margin: 0 0 var(--space-2);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.capex-forecast-conf {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.capex-forecast-conf-verified {
  color: var(--color-success-text);
}
.capex-forecast-conf-estimated {
  color: var(--color-warning-text);
}
.capex-forecast-excluded {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.capex-forecast-excluded[hidden] {
  display: none;
}
.capex-forecast-excluded ul {
  margin: var(--space-1) 0 0;
  padding-left: var(--space-4);
}

/* The age / expected-life / projected-replacement cells are all short numeric
   strings, and letting "30 yr" wrap to two lines makes an otherwise scannable
   table jump around row to row. Nowrap them specifically -- the component and
   location columns are still allowed to wrap, because a long custom label has
   nowhere else to go. */
#capexForecastTable td:nth-child(3),
#capexForecastTable td:nth-child(4),
#capexForecastTable td:nth-child(5),
#capexForecastTable td:nth-child(6),
#capexForecastTable td:nth-child(7),
#capexForecastTable td:nth-child(8) {
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   #271 -- Deal Inbox review queue cards.

   `.badge` is defined HERE rather than in the card block because #375 already
   emits <span class="badge">Revoked</span> on the addresses table and nothing
   in any stylesheet defined it -- that pill has been rendering as bare inline
   text since it shipped. Fixed in passing rather than filed (fix-it-now).

   Every colour below is a --color-* token, not a literal. This file's tail is
   policed by tests/showingsOverhaul.unit.js precisely because appended CSS is
   where light-mode-only colours keep sneaking in.
   --------------------------------------------------------------------------- */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}
.badge.is-warn {
  border-color: var(--color-warning);
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
/* #4894: success/error modifiers, added so the 5 status-badge call sites
   this issue found rendering completely unstyled (deposit ledger GL, GL
   reconciliation, Zoho sync status, vacancy/turnover, loans/escrow) could
   move onto this already-global .badge family instead of a second
   .status-badge system that was only ever styled on the Zoho recon page
   (#3371). Same success/error tokens that family used, so no visual change
   for the one screen (Zoho recon) that was already correctly styled. */
.badge.is-success {
  border-color: var(--color-success);
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}
.badge.is-error {
  border-color: var(--color-error);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}
/* #6596: neutral "informational marker" modifier -- reuses the same
   brand-navy pair .badge-lease-active already uses for its own informational
   (neither success nor error) state, rather than inventing a new token. */
.badge.is-info {
  border-color: var(--color-brand-navy);
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
}
/* #6596: a badge that is ALSO a real <button> (the lease Notices card's
   Served ... (N) pill, now clickable through to Service history) -- resets
   the button chrome the base .badge span never had to fight, keeps the same
   visual weight, and adds a focus-visible ring for keyboard users since this
   is the first .badge variant that is interactive. */
.badge-btn {
  font: inherit;
  cursor: pointer;
  appearance: none;
}
.badge-btn:hover {
  filter: brightness(0.95);
}
.badge-btn:focus-visible {
  outline: 2px solid var(--color-brand-navy);
  outline-offset: 1px;
}
/* #6596: one card per service event in the Service history modal -- same
   bordered-card language the modal system already uses elsewhere (e.g.
   .provider-actions' sibling blocks), not a new pattern. */
.notice-service-history-card {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 10px;
}
.notice-service-history-card:last-child {
  margin-bottom: 0;
}

/* #412. The notify-emails field shipped (#271) with no styling at all, so in
   dark mode it fell back to the UA's own input colours -- a #3b3b3b box the
   theme knows nothing about. --color-text-muted clears 4.5:1 on every app
   SURFACE, but on that UA grey the placeholder measured 4.28:1 and axe failed
   it under WCAG 1.4.3. Putting the field on theme tokens is the fix; do not
   "fix" this by lightening the muted token, which is audited against the app's
   real surfaces (see the --color-text-faint note at the top of this file). */
#diNotifySection input[type='text'] {
  padding: 0.45rem var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
}
#diNotifySection input[type='text']:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

.di-card-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 12px;
}
.di-card {
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 14px 16px;
  background: var(--color-surface);
}
/* A held card must not read as an ordinary one: D4 says it cannot be enriched
   until a human vouches for the sender, and a card that looks identical to its
   neighbours makes that refusal feel like a bug. */
.di-card-held {
  border-color: var(--color-warning);
  background: var(--color-warning-highlight);
}
.di-card.is-muted { opacity: 0.6; }
.di-card-title {
  margin: 0 0 6px;
  font-size: 1rem;
  font-weight: 700;
}
.di-card-badges { margin: 0 0 6px; display: flex; flex-wrap: wrap; gap: 6px; }
.di-card-facts { margin: 0 0 4px; font-weight: 600; }
.di-card-meta { margin: 0 0 4px; font-size: 0.85rem; color: var(--color-text-muted); }
/* #4301 -- the Search Watch unit-count caveat. A lighter-weight inline note,
   not the boxed .di-card-warning treatment: that box is reserved for the
   auth-override security warning, and reusing it here for "verify unit
   count" would overstate the severity of a data-completeness gap. Still
   readably distinct from plain grey meta text via --color-warning-text. */
.di-card-meta-caveat { color: var(--color-warning-text); font-weight: 600; }
/* #449 -- the auth-override warning.

   Styled as an amber notice rather than as red: an auth failure is usually a
   forwarding chain breaking SPF, not an attack, and painting every one of
   them as a danger is how a warning stops being read. It still has to be
   visibly a warning rather than another line of grey meta text, since it is
   the thing standing between the owner and a button that bypasses a check.

   Text uses --color-warning-text, not --color-warning: the base amber is
   4.13:1 on white, which is under AA for body copy at this size, and CI runs
   axe at WCAG 2.1 AA. Do not "simplify" this to --color-warning. */
.di-card-warning {
  margin: 8px 0 4px;
  padding: 8px 10px;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--color-warning-text);
  background: var(--color-warning-highlight);
  border-left: 3px solid var(--color-warning);
  border-radius: 4px;
}
.di-card-preview {
  margin: 8px 0 4px;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  /* Inbound text is untrusted and can be a single 400-character word. */
  overflow-wrap: anywhere;
}
.di-card-actions {
  margin: 10px 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}
/* #430 -- the failed-link block and the manual paste box.
   Both only ever appear on an `enrich_failed` card, so they are styled as a
   quiet sub-panel rather than as another row of the card proper: the point is
   to explain what went wrong without making a failure look more important than
   the listings above it in the queue. */
.di-card-links { margin: 8px 0 4px; }
.di-link-list {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.di-link-list li {
  font-size: 0.85rem;
  /* Inbound URLs are untrusted and routinely 300+ characters with no spaces. */
  overflow-wrap: anywhere;
}
.di-link-list a { font-weight: 600; }
.di-link-why {
  display: block;
  color: var(--color-text-muted);
}
.di-paste { margin: 10px 0 0; }
.di-paste-label {
  display: block;
  margin-bottom: 4px;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}
.di-paste-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.di-paste-input {
  flex: 1 1 260px;
  min-width: 0;
  padding: 7px 10px;
  font-size: 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text);
}

/* #431 -- "What you can forward". A <details> under the queue, so it is
   discoverable on a first visit without pushing the owner's actual deals
   down the page on every subsequent one. */
.di-sources {
  margin-top: 18px;
}

.di-sources-intro,
.di-sources-foot {
  margin: 8px 0;
}

.di-sources-foot {
  color: var(--color-text-muted);
}

.di-sources-list {
  list-style: none;
  margin: 10px 0;
  padding: 0;
  display: grid;
  gap: 8px;
}

.di-sources-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
}

.di-source-name {
  font-weight: 600;
  /* Wide enough for "Paragon Collaboration Center" to keep the how-to text
     aligned across rows, but it is a flex basis rather than a fixed width so
     a narrow screen wraps instead of truncating. */
  flex: 0 0 auto;
  min-width: 210px;
}

.di-source-how {
  flex: 1 1 260px;
  min-width: 0;
  color: var(--color-text-muted);
}

.checkbox-inline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.9rem;
}

/* ================= #767 Custom Domains (Owner Portal) =================
   Redesign of ?tab=property-info&view=custom-domains per
   docs/wireframes/custom-domains.html. Everything here is scoped with a
   `cd-` prefix except the three .status-pill tone modifiers, which the
   Custom Domains markup has referenced since Task List ID 98 without any
   definition ever existing in this file (see the note above .rg-status-pill).
   Defining them here closes that gap rather than inventing a fourth pill
   convention. */
.status-pill-success,
.status-pill-warn,
.status-pill-error {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0.15rem 0.6rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
}
.status-pill-success {
  background: var(--color-success-highlight);
  border-color: var(--color-success);
  color: var(--color-success-text);
}
.status-pill-warn {
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}
.status-pill-error {
  background: var(--color-error-highlight);
  border-color: var(--color-error);
  color: var(--color-error-text);
}
/* #6000: Deposit Interest's "No interest due" status is not a warning, a
   success, or an error -- it's a plain non-event (nothing accrued, nothing
   to settle), and none of the three existing tones fit it honestly. Muted
   text on the page background, same shape/sizing as the other three so it
   still reads as a status pill next to them. */
.status-pill-neutral {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0.15rem 0.6rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
  background: transparent;
  color: var(--color-text-muted);
}

/* #5875: a fully-cancelled reversal pair (or an asymmetric leg whose
   counterpart fell outside the report window) shown in the GL account
   detail drilldown -- only ever visible at all because "Show reversed
   entries" is on. Muted + strikethrough on the money columns so it reads
   as historical noise, not a live balance, without hiding the numbers a
   reader may still need. */
.chart-of-accounts-reversed-row {
  /* --color-text-muted, not a --color-text-tertiary/-secondary pair: neither
     of those tokens exists in this stylesheet, so both legs of that fallback
     chain resolved to nothing and the row fell back to inheriting its colour
     (tests/contrast.unit.js catches exactly this). --color-text-muted is the
     established de-emphasis tier for real readable text; --color-text-faint
     is NOT an option here -- it measures ~2.6:1 and is reserved for borders,
     icons and decorative accents (see the token comment at the top of this
     file, and Task List ID 265, which moved all 48 text rules off it). */
  color: var(--color-text-muted);
}
/* The money columns are Debit and Credit. These indices are positional and
   therefore FRAGILE (#5875) wrote them as 3 and 4, correct at the time, and
   #5876 then inserted a Reference column at position 3, which silently moved
   the strikethrough onto Reference and Debit. Nothing failed -- a positional
   selector cannot notice a column being inserted to its left, and no test
   asserts which cells are struck through.

   Current column order (index.html, #chartOfAccountsDrilldownTable):
     1 Date | 2 Description | 3 Reference | 4 Debit | 5 Credit | 6 Balance

   IF YOU ADD OR REORDER A COLUMN IN THAT TABLE, UPDATE THESE TWO NUMBERS. */
.chart-of-accounts-reversed-row td:nth-child(4),
.chart-of-accounts-reversed-row td:nth-child(5) {
  text-decoration: line-through;
}

/* #3184: prospective tenant pipeline pills reuse the shared status-pill
   geometry, with semantic tones that remain legible in the dense ListKit
   table and the record detail view. */
.prospect-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.6rem;
  border: 1px solid transparent;
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
}
.prospect-status-new,
.prospect-status-shown {
  background: var(--color-surface-2);
  border-color: var(--color-border);
  color: var(--color-text-muted);
}
.prospect-status-showing_scheduled,
.prospect-status-waiting_for_unit {
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}
.prospect-status-applied {
  background: var(--color-success-highlight);
  border-color: var(--color-success);
  color: var(--color-success-text);
}
.prospect-status-found_another_place,
.prospect-status-not_interested {
  background: var(--color-error-highlight);
  border-color: var(--color-error);
  color: var(--color-error-text);
}
.prospect-detail-card {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.prospect-detail-card:first-child {
  margin-top: 0;
}
.prospect-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-height: 1.75rem;
  margin-bottom: var(--space-3);
}
.prospect-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 100%;
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: var(--text-sm);
}
.prospect-chip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prospect-chip-remove {
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  line-height: 1;
}
.prospect-chip-remove:hover {
  background: var(--color-bg);
  color: var(--color-text);
}
/* #3306: the City field became a chip multi-select. The input and its Add
   button sit on one row; selected cities render below as .prospect-chip rows,
   reusing the chip styles above so the two prospect forms look identical. */
.prospect-city-picker {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
.prospect-city-picker input {
  flex: 1 1 auto;
  min-width: 0;
}
.prospect-city-picker .action-btn {
  flex: 0 0 auto;
}
.prospect-city-chips:empty {
  min-height: 0;
  margin-bottom: 0;
}

.prospect-picker-row {
  align-items: end;
}
.prospect-picker-row .field {
  min-width: min(100%, 16rem);
}
.prospect-picker-action {
  flex: 0 0 auto;
}
.prospect-picker-action .action-btn {
  margin-bottom: 0;
}
@media (max-width: 640px) {
  .prospect-picker-row {
    align-items: stretch;
  }
  .prospect-picker-row .field {
    min-width: 0;
  }
}

.cd-mono {
  font-family: var(--font-mono, monospace);
  overflow-wrap: anywhere;
}

/* ---- empty state (wireframe screen 1) ---- */
.cd-empty-panel {
  text-align: center;
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
}
.cd-empty-icon {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  background: var(--color-primary-highlight);
  color: var(--color-primary);
  display: grid;
  place-items: center;
  margin: 0 auto var(--space-4);
}
.cd-empty-panel h2 {
  margin-bottom: var(--space-3);
}
.cd-empty-panel .field-note {
  margin-left: auto;
  margin-right: auto;
  max-width: 48ch;
}
.cd-empty-example {
  display: inline-block;
  margin: var(--space-3) 0 var(--space-4);
  padding: 8px 12px;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  font-family: var(--font-mono, monospace);
  font-size: var(--text-xs);
}

/* ---- add-domain panel (wireframe screen 2) ---- */
.cd-add-panel h2 {
  margin-bottom: var(--space-3);
}
.cd-field {
  max-width: 480px;
  margin-bottom: var(--space-4);
}
.cd-field label {
  display: block;
  font-weight: 600;
  font-size: var(--text-xs);
  margin-bottom: var(--space-2);
}
.cd-field input {
  display: block;
  width: 100%;
  font-family: var(--font-mono, monospace);
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
}
.cd-field .field-note {
  margin-top: var(--space-2);
  max-width: 56ch;
}
.cd-form-actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
}

/* ---- per-domain card (wireframe screens 3/4/6) ---- */
.cd-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 0.625rem);
  margin-bottom: var(--space-4);
  overflow: hidden;
}
.cd-card.is-pending {
  border-color: var(--color-warning);
}
.cd-card.is-failed {
  border-color: var(--color-error);
}
.cd-card-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-divider);
}
.cd-card-title {
  flex: 1 1 240px;
  min-width: 0;
}
.cd-domain-name {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-family: var(--font-mono, monospace);
  font-weight: 700;
  font-size: var(--text-base, 1rem);
  overflow-wrap: anywhere;
}
.cd-card-meta {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.cd-card-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  flex: 0 0 auto;
}

/* ---- stepper ---- */
.cd-stepper {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-surface-2);
  list-style: none;
  margin: 0;
}
.cd-step {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  position: relative;
}
.cd-step-line {
  position: absolute;
  top: 13px;
  left: -50%;
  width: 100%;
  height: 2px;
  background: var(--color-border);
  z-index: 0;
}
.cd-step:first-child .cd-step-line {
  display: none;
}
.cd-step-dot {
  position: relative;
  z-index: 1;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  border: 2px solid var(--color-border);
  background: var(--color-surface);
  /* Upcoming steps read as muted, not faint: the number inside the dot is real
     text, so it has to clear WCAG AA (ID 265 / tests/contrast.unit.js). */
  color: var(--color-text-muted);
  display: grid;
  place-items: center;
  font-size: var(--text-xs);
  font-weight: 700;
  margin-bottom: var(--space-2);
}
.cd-step-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
}
.cd-step-sub {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.cd-step.is-done .cd-step-dot {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-surface);
}
.cd-step.is-done .cd-step-line,
.cd-step.is-current .cd-step-line,
.cd-step.is-error .cd-step-line {
  background: var(--color-primary);
}
.cd-step.is-done .cd-step-label {
  color: var(--color-text);
}
.cd-step.is-current .cd-step-dot {
  border-color: var(--color-warning);
  color: var(--color-warning-text);
  box-shadow: 0 0 0 4px var(--color-warning-highlight);
}
.cd-step.is-current .cd-step-label {
  color: var(--color-warning-text);
  font-weight: 700;
}
.cd-step.is-current .cd-step-sub {
  color: var(--color-warning-text);
}
.cd-step.is-error .cd-step-dot {
  background: var(--color-error);
  border-color: var(--color-error);
  color: var(--color-surface);
}
.cd-step.is-error .cd-step-label,
.cd-step.is-error .cd-step-sub {
  color: var(--color-error-text);
  font-weight: 700;
}

/* ---- card body: DNS records, disclosure, failure comparison ---- */
.cd-card-body {
  padding: var(--space-4);
}
.cd-instr {
  margin: 0 0 var(--space-3);
  max-width: 70ch;
}
.cd-dns-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-2);
}
.cd-dns-table {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.cd-dns-table th {
  text-align: left;
  padding: 8px 12px;
  background: var(--color-surface-2);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.cd-dns-table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--color-divider);
  font-size: var(--text-xs);
  vertical-align: middle;
}
.cd-dns-table tr:last-child td {
  border-bottom: 0;
}
.cd-dns-value {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  justify-content: space-between;
}
.cd-copy-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.cd-copy-btn:hover:not(:disabled) {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.cd-statusline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-divider);
}
.cd-statusline .field-note {
  margin: 0;
}
.cd-card-foot {
  display: flex;
  justify-content: flex-end;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-divider);
  background: var(--color-surface-2);
}
.cd-disclosure {
  border-top: 1px solid var(--color-divider);
  padding-top: var(--space-3);
  margin-top: var(--space-3);
}
.cd-card-body > .cd-disclosure:first-child {
  border-top: 0;
  padding-top: 0;
  margin-top: 0;
}
.cd-disclosure-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.cd-disclosure-label {
  font-weight: 600;
  font-size: var(--text-xs);
}
.cd-disclosure-body {
  margin-top: var(--space-3);
}
.cd-errline {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  border: 1px solid var(--color-error);
  border-radius: var(--radius-sm);
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}
.cd-errline p {
  margin: 0;
  font-size: var(--text-xs);
}
.cd-compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.cd-compare-box {
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
}
.cd-compare-box.is-expected {
  background: var(--color-surface-2);
}
.cd-compare-box.is-found {
  background: var(--color-error-highlight);
  border-color: var(--color-error);
}
.cd-compare-head {
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  font-weight: 700;
  margin-bottom: var(--space-1);
}
.cd-compare-value {
  font-family: var(--font-mono, monospace);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}
@media (max-width: 640px) {
  .cd-compare {
    grid-template-columns: 1fr;
  }
}

/* ---- many-domains list (wireframe screen 7) ---- */
.cd-list-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.cd-list-toolbar[hidden] {
  display: none;
}
.cd-list-toolbar label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
}
.cd-list-search {
  flex: 1 1 200px;
  min-width: 160px;
  padding: 8px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-xs);
}
.cd-list-filter {
  padding: 8px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-xs);
  font-weight: 600;
}
.cd-rowcard {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  margin-bottom: var(--space-2);
}
.cd-rowcard-name {
  flex: 1 1 200px;
  min-width: 0;
  font-family: var(--font-mono, monospace);
  font-weight: 700;
  overflow-wrap: anywhere;
}
.cd-rowcard-meta {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.cd-rowcard-actions {
  flex: 0 0 auto;
  display: flex;
  gap: var(--space-2);
}
.cd-pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
.cd-pagination[hidden] {
  display: none;
}
.cd-page-btn {
  min-width: 30px;
  height: 30px;
  padding: 0 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 700;
  cursor: pointer;
}
.cd-page-btn[aria-current='true'] {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-surface);
}
.cd-pagination-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* #859: Unit page styles */
#unit-main .card {
  background: var(--color-bg);
  border: 1px solid var(--color-border, #e2e8f0);
  border-radius: 8px;
  padding: 1.25rem;
  margin-bottom: 1rem;
}
#unit-main .card h2 {
  margin: 0 0 0.75rem;
  font-size: var(--text-lg, 1.125rem);
}
#unit-main .unit-page-secondary-section > summary {
  margin: 0;
  font-size: var(--text-lg, 1.125rem);
  font-weight: 650;
  cursor: pointer;
}
#unit-main .unit-page-secondary-section[open] > summary {
  margin-bottom: 0.75rem;
}
#unit-main .card-subtitle {
  margin: 0.75rem 0 0.5rem;
  font-size: var(--text-sm, 0.875rem);
  font-weight: 600;
  color: var(--color-text-muted, #64748b);
}
#unit-main .detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1rem;
}
#unit-main .detail-grid > div {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
#unit-main .field-label {
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  color: var(--color-text-muted, #64748b);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* #991: secondary AVM-estimate line nested under the Rent position section's
   Market rent value, shown only when that value is a manual override that
   disagrees with the current estimate. Deliberately quieter than the value
   above it -- same muted/small treatment as .rr-market-estimate in the Rent
   Roll (style.css ~9166), so the two surfaces read as the same convention. */
#unit-main .rent-position-estimate {
  margin-top: 2px;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--color-text-muted, #64748b);
}
#unit-main .repair-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
#unit-main .repair-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#unit-main .repair-row:last-child {
  border-bottom: none;
}
#unit-main .repair-desc {
  font-weight: 500;
}
/* #1563: repair rows are click-through (role="button") to the repair
   detail. Same hover/focus convention as .repair-item in the Repairs
   modal list (~style.css 8877) -- a border-color swap to --color-primary,
   not a background swap, so this reuses tokens already defined above
   instead of introducing new undefined custom properties (guarded by
   tests/contrast.unit.js). */
/* #4723: the row is now a real <a>, not a div -- reset the browser's
   default link color/underline so it keeps reading as a plain row (color
   and weight come from .repair-desc/.field-note like before) rather than
   turning the whole row blue-and-underlined. */
#unit-main .unit-page-repair-row {
  cursor: pointer;
  border: 1px solid transparent;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--transition-interactive);
}
#unit-main .unit-page-repair-row:visited {
  color: inherit;
}
#unit-main .unit-page-repair-row:hover,
#unit-main .unit-page-repair-row:focus-visible {
  border-color: var(--color-primary);
}
/* #938: current-lease link and lease-history list. Scoped to #unit-main
   because .lease-link is otherwise only styled under #leases-list-main /
   #lease-detail-main, neither of which wraps the Unit page. */
#unit-main .lease-link {
  font-weight: 500;
  text-decoration: none;
}
#unit-main .lease-link:hover {
  text-decoration: underline;
}
#unit-main .lease-history-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
#unit-main .lease-history-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#unit-main .lease-history-row:last-child {
  border-bottom: none;
}
/* #1087: the Unit page reuses the property Visits row treatment, but these
   summaries are deliberately read-only and should not look clickable. */
#unit-main .unit-page-visits-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
#unit-main .unit-page-visit-history {
  margin-top: var(--space-5);
}
#unit-main .unit-page-visit-item,
#unit-main .unit-page-visit-item:hover {
  cursor: default;
  border-color: var(--color-border);
}
/* #1087: this unit's own All / Condition / Prospect toggle reuses the
   .segmented base look and the .showings-family-count badge style (#927)
   -- only the spacing below the heading is scoped separately here. */
#unit-main .unit-page-visits-family-toggle {
  margin-bottom: var(--space-3);
}
/* #1335: a property-scoped unit record keeps the same #unit-main and ids,
   merely reparented into this large dialog. */
.unit-record-popup-card,
.tenant-record-popup-card {
  width: min(72rem, 100%);
  max-width: 72rem;
  height: min(92vh, 64rem);
  max-height: 92vh;
  display: flex;
  flex-direction: column;
}
.unit-record-popup-card > .modal-header,
.tenant-record-popup-card > .modal-header {
  flex: 0 0 auto;
}
.unit-record-popup-body,
.tenant-record-popup-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.unit-record-popup-body #unit-main {
  padding: 0;
}
.unit-record-popup-body #unitPageBackBtn {
  display: none;
}
.tenant-record-popup-body #tenant-detail-main {
  padding: 0;
}
.tenant-record-popup-body #tenantDetailBackBtn {
  display: none;
}
.unit-record-popup-back {
  font-size: 1.25rem;
}
.unit-record-popup-lease-notices {
  margin-top: 1rem;
}
@media (max-width: 640px) {
  #unit-main .detail-grid {
    grid-template-columns: 1fr 1fr;
  }
  #unit-main .repair-row {
    flex-direction: column;
    gap: 0.25rem;
  }
  .unit-record-popup-overlay {
    padding: 0;
    background: var(--color-surface);
  }
  .tenant-record-popup-overlay {
    padding: 0;
    background: var(--color-surface);
  }
  .unit-record-popup-card {
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  .tenant-record-popup-card {
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}

/* #860: Units list page styles */
#units-list-main .units-list-property {
  margin-bottom: 1.5rem;
}
#units-list-main .units-list-table {
  width: 100%;
  border-collapse: collapse;
}
#units-list-main .units-list-table th {
  text-align: left;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  color: var(--color-text-muted, #64748b);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#units-list-main .units-list-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#units-list-main .units-list-table tr.shared-row {
  background: var(--color-surface-2, #f8fafc);
}
#units-list-main .units-list-table tr.shared-row td {
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#units-list-main .shared-label {
  display: inline-block;
  font-weight: 700;
  font-size: var(--text-xs, 0.75rem);
  letter-spacing: 0.05em;
  color: var(--color-text-muted, #64748b);
  margin-right: 0.5rem;
}
#units-list-main .building-row .shared-label {
  color: var(--color-primary, #2563eb);
}
#units-list-main .badge {
  display: inline-block;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  padding: 0.125rem 0.5rem;
  border-radius: 4px;
}
#units-list-main .badge-vacant {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
#units-list-main .badge-occupied {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}
#units-list-main .badge-committed {
  background: var(--color-brand-navy-highlight);
  color: var(--color-brand-navy);
}
/* A notice remains occupied today, but needs a distinct visual treatment so
   it is not conflated with either a normal occupancy or a future commitment. */
#units-list-main .badge-notice_given {
  background: var(--color-warning-highlight);
  color: var(--color-warning-text);
}
/* #1054: a fixed-term lease past end_date with the tenant still in place and
   no notice given. Uses the AA-audited error pair (not warning) -- this is a
   step more urgent than "lease ending soon": the paper term is already over. */
#units-list-main .badge-expired-action {
  background: var(--color-error-highlight);
  color: var(--color-error-text);
}

/* #865: dedicated lease-status badge classes for the Leases list. Previously
   that list reused badge-occupied/badge-committed/badge-vacant (the
   OCCUPANCY vocabulary) for lease-status semantics -- badge-committed meant
   "draft" there, but on the Units list badge-committed correctly means a
   signed lease within the rent-commitment horizon. Separate classes for all
   7 leases.status values removes that same-class-different-meaning clash.
   Theme tokens only (not literal hex) -- style.css's tail is policed by
   tests/showingsOverhaul.unit.js for exactly this, since a literal hex here
   is a light-mode-only colour and silently breaks dark mode (the ID 21/24
   pattern). --color-success/--color-warning/--color-error already have
   light+dark pairs defined in :root / [data-theme='dark'] above. */
.badge-lease-draft { background: var(--color-surface-2); color: var(--color-text-muted); }
.badge-lease-signed { background: var(--color-success-highlight); color: var(--color-success-text); }
.badge-lease-active { background: var(--color-brand-navy-highlight); color: var(--color-brand-navy); }
.badge-lease-month_to_month { background: var(--color-warning-highlight); color: var(--color-warning-text); }
.badge-lease-notice_given { background: var(--color-warning-highlight); color: var(--color-warning-text); }
.badge-lease-expired { background: var(--color-surface-2); color: var(--color-text-muted); }
.badge-lease-terminated_early { background: var(--color-error-highlight); color: var(--color-error-text); }
/* #3174: Lease Renewal Outcomes report -- same --color-*-highlight/-text
   token pairs as badge-lease-* just above, so this follows dark mode for
   free instead of risking the literal-hex mistake tests/showingsOverhaul
   guards against. 'pending' (in-flight, including the ambiguous fallthrough
   from classifyLeaseRenewalOutcome) is deliberately the neutral/muted
   treatment, not a color that reads as good or bad news either way. */
.badge-renewal-renewed { background: var(--color-success-highlight); color: var(--color-success-text); }
.badge-renewal-vacated { background: var(--color-error-highlight); color: var(--color-error-text); }
.badge-renewal-holdover { background: var(--color-warning-highlight); color: var(--color-warning-text); }
.badge-renewal-pending { background: var(--color-surface-2); color: var(--color-text-muted); }
/* #3174: whole-row click affordance for report tables where the row (not
   just one cell's link) opens a record panel. Generic on purpose so any
   future report's clickable rows can reuse it. */
.has-row-click { cursor: pointer; }
.has-row-click:hover { background: var(--color-surface-2); }
/* #3371 originally scoped status-badge rules here for this report only;
   #4894 migrated every status-badge consumer (this page included, plus 4
   others that had been rendering completely unstyled -- .status-badge never
   had an unscoped base rule) onto the already-global .badge family instead,
   so this scoped block is now dead and removed rather than left as an
   orphaned second badge system. */
#units-list-main .repair-count {
  display: inline-block;
  font-weight: 600;
  color: var(--color-error, #dc2626);
}
#units-list-main .unit-link {
  font-weight: 500;
  text-decoration: none;
}
#units-list-main .unit-link:hover {
  text-decoration: underline;
}
@media (max-width: 640px) {
  #units-list-main .units-list-table {
    font-size: var(--text-xs, 0.75rem);
  }
  #units-list-main .units-list-table th,
  #units-list-main .units-list-table td {
    padding: 0.375rem 0.5rem;
  }
}

/* #861: Leases list + lease detail page styles */
/* #2317: .leases-list-table rendered directly into #leasesListBody with no
   scroll container, so at narrow widths it silently shrank font/padding
   (below) but never gained horizontal scroll -- html/body compute
   overflow-x:clip (see the comment above that rule), so the table's 8
   columns simply ran off-screen on mobile with the Status/Signing/View
   Lease cells completely unreachable and no scrollbar hinting they existed.
   Same failure mode #2227 already fixed for message templates/asset
   register/maintenance intervals via .table-wrap + table.data-table;
   #leasesListBody gets its own scroll container here since this table
   predates that shared class and has its own dedicated selectors below. */
#leasesListBody {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
#leases-list-main .leases-list-table {
  min-width: 640px;
}
#leases-list-main .leases-list-table,
#lease-detail-main .lease-detail-table {
  width: 100%;
  border-collapse: collapse;
}
/* #3228 -- Rent ledger card on the lease record. Reuses .detail-grid for the
   header stat strip, .ld-grid/.ld-field/.ld-checkbox-row for the filter row,
   and .lease-detail-table/.list-pagination-bar for the table + pager, same
   as every other card on this page -- the additions below are only the bits
   those don't already cover: the entry-type dot and the reversed/waived
   strike-through treatment. */
.rent-ledger-type-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 6px;
  flex: 0 0 auto;
}
.rent-ledger-type-dot.rent-ledger-dot-debit {
  background: var(--color-error);
}
.rent-ledger-type-dot.rent-ledger-dot-credit {
  background: var(--color-success);
}
.rent-ledger-type-dot.rent-ledger-dot-neutral {
  background: var(--color-text-muted);
}
/* A reversed row and the reversal row that undoes it both stay visible
   (the ledger is append-only, #3205) but read as settled history rather
   than a live balance line. */
.rent-ledger-row-reversed td {
  text-decoration: line-through;
  opacity: 0.6;
}
.rent-ledger-zoho-pending {
  color: var(--color-text-muted);
}
#leases-list-main .leases-list-table th,
#lease-detail-main .lease-detail-table th {
  text-align: left;
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  color: var(--color-text-muted, #64748b);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#leases-list-main .leases-list-table td,
#lease-detail-main .lease-detail-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
#leases-list-main .lease-link {
  font-weight: 500;
  text-decoration: none;
}
#leases-list-main .lease-link:hover {
  text-decoration: underline;
}
/* #4948: Tenants list Property/Unit/Lease columns reuse the .lease-link
   class Leases/Unit already style -- same convention, new scope, since
   .lease-link is otherwise only styled under #leases-list-main/#unit-main. */
#tenants-main .lease-link {
  font-weight: 500;
  text-decoration: none;
}
#tenants-main .lease-link:hover {
  text-decoration: underline;
}
#lease-detail-main .card {
  background: var(--color-bg);
  border: 1px solid var(--color-border, #e2e8f0);
  border-radius: 8px;
  padding: 1.25rem;
  margin-bottom: 1rem;
}
#lease-detail-main .card h2 {
  margin: 0 0 0.75rem;
  font-size: var(--text-lg, 1.125rem);
}
#lease-detail-main .card-subtitle {
  margin: 0.75rem 0 0.5rem;
  font-size: var(--text-sm, 0.875rem);
  font-weight: 600;
  color: var(--color-text-muted, #64748b);
}
/* #4958: the Add tenant form's heading now shares .ld-section-heading-row
   with the "+ New tenant" button -- that row's own align-items:center
   already vertically centers both children, but without this the heading's
   own 0.75rem/0.5rem margin still pads the row taller than the button next
   to it, same fix .ld-section-heading-row .ld-section-heading already
   applies to its own heading class above. */
#lease-detail-main .ld-section-heading-row .card-subtitle {
  margin: 0;
}
#lease-detail-main .detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1rem;
}
#lease-detail-main .detail-grid > div {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
#lease-detail-main .field-label {
  font-size: var(--text-xs, 0.75rem);
  font-weight: 600;
  color: var(--color-text-muted, #64748b);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* #1336: the same lease record is rendered in the full route and the #1335
   unit popup, so lifecycle controls use a shared class rather than route ids. */
.lease-record-content .card {
  background: var(--color-bg);
  border: 1px solid var(--color-border, #e2e8f0);
  border-radius: 8px;
  padding: 1.25rem;
  margin-bottom: 1rem;
}
.lease-record-content .card h2 {
  margin: 0 0 0.75rem;
  font-size: var(--text-lg, 1.125rem);
}
.lease-record-actions,
.lease-record-inline-form .ld-inline-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 0.75rem 0;
}
.lease-record-inline-form {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border, #e2e8f0);
}
.lease-record-message-error {
  color: var(--color-danger, #b91c1c);
}
/* #1413: the Tenants section had no interactive control at all -- a row
   layout so the per-tenant Remove button sits beside the name/role instead
   of the field-label/value pair wrapping around it. */
.lease-record-tenant-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border, #e2e8f0);
}
.lease-record-tenant-row:last-child {
  border-bottom: none;
}
.lease-record-tenant-removed {
  opacity: 0.6;
}
.lease-record-document-list {
  margin: 0;
  padding-left: 1.25rem;
}
@media (max-width: 640px) {
  #leases-list-main .leases-list-table {
    font-size: var(--text-xs, 0.75rem);
  }
  #leases-list-main .leases-list-table th,
  #leases-list-main .leases-list-table td {
    padding: 0.375rem 0.5rem;
  }
  #lease-detail-main .detail-grid {
    grid-template-columns: 1fr 1fr;
  }
  .lease-record-actions {
    align-items: stretch;
  }
}

/* #862: Navigation restructure — top-level nav + global search */
.nav-top-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  background: transparent;
  border: none;
  color: var(--color-text, #1e293b);
  cursor: pointer;
  padding: 0.375rem 0.625rem;
  border-radius: 6px;
  font-size: var(--text-sm, 0.875rem);
  font-weight: 500;
}
.nav-top-btn:hover {
  background: var(--color-bg-alt, #f1f5f9);
}
.nav-top-btn svg {
  width: 16px;
  height: 16px;
}
.nav-search {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* #1165: this div matched none of qa-487-nav-reflow.js's item selectors
     (:scope > .nav-dropdown > .nav-dropdown-toggle, .btn-export, button), so
     the single widest addition to the nav since the #487 budget was measured
     was invisible to the guard. min-width: 0 lets it actually shrink inside
     the flex row instead of forcing the row (and therefore .brand) wider
     than the viewport. */
  width: min(220px, 20vw);
  min-width: 0;
  flex: 0 0 auto;
}
.nav-search-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  flex: 0 0 34px;
}
.nav-search-toggle:hover {
  background: var(--color-bg);
}
.nav-search-toggle svg {
  width: 18px;
  height: 18px;
}
.nav-search-input {
  /* #1165: a hard 220px never shrank, so once .header-actions exceeded the
     #487 budget (four new top-level controls plus this box) the overflow had
     nowhere to go except squeezing .brand. width -> 100% of an explicitly
     capped, shrinkable parent (see .nav-search-input max-width below and the
     <=1060px stage-1 rule) so this box gives ground before anything clips. */
  width: 100%;
  min-width: 0;
  max-width: 220px;
  padding: 0.375rem 0.625rem;
  border: 1px solid var(--color-border, #e2e8f0);
  border-radius: 6px;
  font-size: var(--text-sm, 0.875rem);
  background: var(--color-bg, #fff);
  color: var(--color-text, #1e293b);
}
.nav-search-input:focus {
  outline: 2px solid var(--color-primary, #2563eb);
  outline-offset: -1px;
}
.nav-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  background: var(--color-bg, #fff);
  border: 1px solid var(--color-border, #e2e8f0);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  max-height: 320px;
  overflow-y: auto;
  z-index: 100;
}
.search-result-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  text-decoration: none;
  color: var(--color-text, #1e293b);
  border-bottom: 1px solid var(--color-border, #f1f5f9);
}
.search-result-item:last-child {
  border-bottom: none;
}
.search-result-item:hover {
  background: var(--color-bg-alt, #f1f5f9);
}
.search-result-icon {
  font-size: 1rem;
}
.search-result-label {
  font-weight: 500;
  flex: 1;
}
.search-result-sub {
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted, #64748b);
}
.search-result-type {
  font-size: var(--text-xs, 0.75rem);
  color: var(--color-text-muted, #64748b);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.search-no-results {
  color: var(--color-text-muted, #64748b);
  font-style: italic;
}
@media (max-width: 1080px) {
  .nav-search {
    width: 34px;
  }
  .nav-search-toggle {
    display: inline-flex;
  }
  .nav-search-input {
    display: none;
  }
  .nav-search.is-expanded .nav-search-input {
    display: block;
    width: min(220px, 40vw);
  }
  .nav-search.is-expanded {
    width: min(220px, 40vw);
  }
  .nav-search.is-expanded .nav-search-toggle {
    display: none;
  }
}
@media (max-width: 768px) {
  /* #1165: this used to hard-set width: 140px, which -- appearing later in
     the cascade than the stage-1/stage-2 rules above -- clobbered their
     shrinkability right back to a fixed value between 780px and 768px.
     max-width keeps it in the same shrink-to-fit family as those stages
     instead of re-fixing it. */
  .nav-search-input {
    max-width: 140px;
  }
}

/* #863: Repairs UI location-aware — structured target picker */
#repairAreaPicker,
#repairUnitPicker {
  transition: opacity 0.15s ease;
}
#repairAreaPicker[hidden],
#repairUnitPicker[hidden] {
  display: none;
}
#repairAreaEmpty {
  display: block;
  margin-top: 0.25rem;
  color: var(--color-text-muted, #64748b);
}

/* #782: MFA setup required banner */
.alert-banner {
  background: var(--color-warning-highlight, #f0e3c8);
  color: var(--color-warning-text, #805609);
  padding: 0.75rem 1.5rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  font-size: 0.9rem;
}
/* #922: display:flex on .alert-banner overrides the browser's default
   [hidden] { display: none }, so the hidden attribute alone does not hide
   the MFA setup banner. This rule restores it. */
.alert-banner[hidden] {
  display: none;
}

/* ===========================================================================
   #1553: Zoho Books Pairing rebuild -- one list driven by leases.
   docs/wireframes/zoho-books-pairing-rebuild-spec.md + the wireframe PDF
   alongside it are the visual source of truth. New, scoped classes only --
   no existing rule touched.
   =========================================================================== */

.zoho-books-pairing-progress-line {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.zoho-books-pairing-progress-bar {
  margin-top: var(--space-2);
  width: 100%;
  max-width: 26rem;
  height: 8px;
  border-radius: 999px;
  background: var(--color-border);
  overflow: hidden;
}
.zoho-books-pairing-progress-fill {
  height: 100%;
  background: var(--color-primary);
  border-radius: 999px;
  transition: width 0.2s ease;
}

.zoho-books-pairing-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
}
.zoho-books-pairing-filters {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.zoho-books-filter-btn {
  border: 1px solid var(--color-border);
  background: var(--color-surface, transparent);
  color: var(--color-text);
  border-radius: 6px;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.zoho-books-filter-btn:hover {
  border-color: var(--color-primary);
}
.zoho-books-filter-btn.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}

.zoho-books-lease-list-head {
  display: grid;
  grid-template-columns: 1.3fr 1.6fr 0.8fr 2.2fr;
  gap: var(--space-3);
  padding: 0 var(--space-3) var(--space-2);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border);
}

.zoho-books-lease-row {
  display: grid;
  grid-template-columns: 1.3fr 1.6fr 0.8fr 2.2fr;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-3);
  border-bottom: 1px solid var(--color-border);
  border-left: 4px solid transparent;
}
.zoho-books-lease-row.is-matched {
  border-left-color: var(--color-success);
  align-items: center;
}
.zoho-books-lease-row.is-unmatched {
  border-left-color: var(--color-border);
}
.zoho-books-lease-row .zoho-books-lease-unit {
  font-weight: 600;
}
.zoho-books-lease-row .zoho-books-lease-address {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-weight: 400;
}
.zoho-books-lease-row .zoho-books-lease-tenants {
  font-size: var(--text-sm);
}
.zoho-books-lease-row .zoho-books-lease-tenants span {
  display: block;
}

/* #2831: card layout below 640px -- same data-label::before mechanism as
   .srow-cell/.asset-register-table (see ~5551/~14552 above) rather than a
   third responsive scheme. This list is div-based (like .srow), not a real
   <table>, so it's grid-template-columns/areas, not display:block on table
   parts. The header row (.zoho-books-lease-list-head) has no per-column
   meaning once stacked, so it's hidden, matching .srow-head's treatment.
   The customer/pairing cell (typeahead, guesses, matched summary -- the
   widest and most interactive column) spans full width, same treatment
   .srow-repairs-list gives its identity cell, rather than being squeezed
   two-up with the simple Unit/Tenants/Rent cells. */
@media (max-width: 640px) {
  .zoho-books-lease-list-head {
    display: none;
  }
  .zoho-books-lease-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      'unit unit'
      'tenants rent'
      'customer customer';
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
  }
  .zoho-books-lease-row [data-label='Unit'] {
    grid-area: unit;
  }
  .zoho-books-lease-row [data-label='Tenants on the lease'] {
    grid-area: tenants;
  }
  .zoho-books-lease-row [data-label='Rent'] {
    grid-area: rent;
  }
  .zoho-books-lease-row .zoho-books-lease-customer-cell {
    grid-area: customer;
  }
  .zoho-books-lease-row [data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    margin-bottom: 1px;
  }
}

.zoho-books-typeahead-wrap {
  position: relative;
}
.zoho-books-typeahead-input {
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: var(--text-sm);
}
.zoho-books-typeahead-results {
  position: absolute;
  z-index: 20;
  left: 0;
  right: 0;
  top: calc(100% + 2px);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  max-height: 14rem;
  overflow-y: auto;
}
.zoho-books-typeahead-result {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2);
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
  font-size: var(--text-sm);
}
.zoho-books-typeahead-result:hover,
.zoho-books-typeahead-result:focus {
  background: var(--color-success-highlight);
}
.zoho-books-typeahead-result .zoho-books-typeahead-result-meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs, 0.75rem);
  white-space: nowrap;
}

.zoho-books-guesses {
  margin-top: var(--space-2);
}
.zoho-books-guesses-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--space-1);
}
.zoho-books-guesses-label {
  font-size: var(--text-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.zoho-books-guess-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2);
  margin-bottom: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: none;
  cursor: pointer;
  font-size: var(--text-sm);
  text-align: left;
}
.zoho-books-guess-row:hover,
.zoho-books-guess-row:focus {
  border-color: var(--color-primary);
}
.zoho-books-guess-name {
  font-weight: 600;
}
.zoho-books-guess-reason {
  color: var(--color-text-muted);
  font-size: var(--text-xs, 0.75rem);
  white-space: nowrap;
}

.zoho-books-matched-customer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.zoho-books-matched-name {
  font-weight: 600;
}
.zoho-books-matched-meta {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* #1660: starting-point promotion modal */
.modal-dialog {
  border: none;
  border-radius: var(--radius-lg);
  padding: 0;
  max-width: 600px;
  width: 90vw;
  box-shadow: var(--shadow-lg, 0 10px 30px rgba(0,0,0,0.2));
}
.modal-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}
.modal-dialog-content {
  padding: var(--space-4);
}
.modal-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}
.modal-dialog-header h3 {
  margin: 0;
}
.modal-dialog-body {
  display: grid;
  gap: var(--space-3);
}

/* ================= #2227: .data-table / .table-wrap / .btn had NO rules
   anywhere the Owner Portal loads (base.css/style.css/listkit.css) -- those
   two table classes and the bare .btn class only exist in admin.css, a
   different stylesheet for the Admin Portal. The custom-domains comment
   above .rg-status-pill already flagged half of this gap; this closes it
   for real rather than leaving every .data-table/.table-wrap/.btn usage in
   the Owner Portal (message templates, asset-register intervals,
   maintenance intervals, late-fee overrides, late fees) rendering with
   zero styling. Mobile gets its own overflow-x:auto scroll container --
   the previous behaviour computed overflow-x:visible on the wrapper while
   html/body compute overflow-x:clip, so overflow content was silently
   unreachable with no scrollbar rather than just ugly (confirmed via live
   DOM measurement: Status/Last Edited/Edit columns sat off-screen at
   x=428-455px on a 375px viewport with no way to scroll to them). */
.table-wrap {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.table-wrap[hidden] {
  display: none;
}
table.data-table {
  width: 100%;
  min-width: 640px;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--text-sm);
}
table.data-table th,
table.data-table td {
  padding: var(--space-3) var(--space-3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-divider);
  white-space: normal;
}
table.data-table thead th {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
  background: var(--color-surface-2);
  border-bottom: 1px solid var(--color-divider);
}
table.data-table tbody tr:last-child td {
  border-bottom: none;
}
table.data-table tbody tr:hover {
  background: var(--color-surface-2);
}
table.data-table .field-note {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
@media (max-width: 640px) {
  table.data-table {
    min-width: 560px;
  }
  table.data-table th,
  table.data-table td {
    padding: var(--space-2);
  }
}

/* #4528: the Rule log carries two free-text columns (Rule, What it did) plus a
   200px minimum on Ticket and ~310px of fixed columns. The generic 640px table
   minimum would leave those two sharing barely any room, trading the old
   one-word-per-line Ticket wrap for the same wrap in the neighbouring columns.
   Raise this one table's minimum instead; .table-wrap already scrolls
   horizontally below it, with the #2325/#4443 edge fades to signal it. */
table#workflowRuleLogTable {
  min-width: 860px;
}

/* #2325/#4443: scroll-hint edge fades. .grid-scroll/.table-wrap/.maintenance-board
   are all genuinely horizontally scrollable (overflow-x:auto works and has
   been confirmed reachable via real scrolling), but gave zero visual signal
   that more content sits off-screen -- no scrollbar on touch devices, no
   gradient, nothing. A container gets one or both fade edges toggled by JS
   (scrollHint() below) based on real scrollLeft/scrollWidth measurement --
   a pure-CSS attempt (background-attachment tricks) is too unreliable
   across browsers for something this load-bearing. Opt-in via
   .scroll-hint-container so this never silently applies to an unrelated
   overflow-x:auto element.
   `pointer-events: none` so the fade never blocks the real scroll/click
   underneath it -- it is a visual cue, not a hit target.
   #4443 QA-FAILED (2026-08-29): the fade used to go var(--color-surface) ->
   transparent, but every .grid-scroll container lives inside a .panel whose
   own background IS --color-surface -- so the "fade" was always fading from
   the surface color to transparent atop that exact same surface color, i.e.
   pixel-identical to the surface, invisible in both themes. Confirmed via
   direct pixel sampling on the Loan tab's amortization table at 375px.
   Fixed by mixing toward --color-text instead of --color-surface: --color-text
   is dark in light mode and light in dark mode -- the opposite polarity of
   --color-surface in both themes -- so the fade edge now always has real
   contrast against whatever surface it sits on, without needing to know the
   surface color at the call site. Capped at 16% mix so it reads as a soft
   edge shadow, not a hard bar. */
.scroll-hint-container {
  position: relative;
}
.scroll-hint-container::before,
.scroll-hint-container::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 24px;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-interactive);
  z-index: 1;
}
.scroll-hint-container::before {
  left: 0;
  background: linear-gradient(to right, color-mix(in oklab, var(--color-text) 16%, transparent), transparent);
}
.scroll-hint-container::after {
  right: 0;
  background: linear-gradient(to left, color-mix(in oklab, var(--color-text) 16%, transparent), transparent);
}
.scroll-hint-container.has-scroll-start::before {
  opacity: 1;
}
.scroll-hint-container.has-scroll-end::after {
  opacity: 1;
}

/* ================= Button system =================
   Primary (.btn-primary; legacy .btn-export-primary): use for a page's main
   forward action. Secondary outlined (.btn-outline; legacy .btn-export): use
   for standard supporting actions. Ghost (.btn-ghost): use for quiet text-like
   actions. Destructive (.btn-destructive; legacy .btn-export-danger): use only
   for actions that remove or end something. */

/* Secondary outlined action: supports an icon and standard supporting actions. */
.btn-outline,
.btn-export {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  transition: border-color var(--transition-interactive), color var(--transition-interactive);
}

/* Destructive action: the error-toned outlined action for irreversible work. */
.btn-destructive,
.btn-export-danger {
  border-color: var(--color-error);
  color: var(--color-error);
}
.btn-destructive:hover:not(:disabled),
.btn-export-danger:hover:not(:disabled) {
  border-color: var(--color-error);
  color: var(--color-error);
}
.btn-destructive:focus-visible,
.btn-export-danger:focus-visible {
  outline-color: var(--color-error);
}
/* #4465: spec says use var(--color-danger) for Remove specifically, if that
   token exists -- it does (see :root above) -- rather than defaulting to
   the .btn-export-danger/.btn-destructive convention's --color-error. Both
   clear WCAG AA on this button's plain --color-surface background (danger:
   4.83:1 light / 4.54:1 dark; checked, not assumed), so this is a scoped
   token override on the one button the spec calls out, not a change to the
   shared class -- every other .btn-export-danger/.btn-destructive consumer
   keeps --color-error unchanged. */
#assetAddItemRemoveBtn {
  border-color: var(--color-danger);
  color: var(--color-danger);
}
#assetAddItemRemoveBtn:hover:not(:disabled) {
  border-color: var(--color-danger);
  color: var(--color-danger);
}
#assetAddItemRemoveBtn:focus-visible {
  outline-color: var(--color-danger);
}

/* #2751: first use of a row-actions dropdown in the app -- searched for an
   existing kebab/menu pattern before writing this and found none, so this is
   the reference for any future table that wants to consolidate its row
   actions the same way (was previously one .btn-export + two bare .link-btn
   elements per template row, an inconsistency Tony flagged directly). A
   native <details>/<summary> element, not a JS-built popover -- no extra
   dependency, works with the keyboard and screen readers for free, and
   closes on outside click via the browser's own <details> semantics in every
   evergreen browser this app supports. */
.row-actions-menu {
  position: relative;
  display: inline-block;
}
.row-actions-menu > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  list-style: none;
}
.row-actions-menu > summary::-webkit-details-marker { display: none; }
.row-actions-menu > summary:hover,
.row-actions-menu > summary:focus-visible {
  border-color: var(--color-primary);
}
.row-actions-menu[open] > summary {
  border-color: var(--color-primary);
}
.row-actions-menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-1));
  z-index: 20;
  min-width: 160px;
  display: flex;
  flex-direction: column;
  padding: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.12));
}
.row-actions-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}
.row-actions-menu-item:hover,
.row-actions-menu-item:focus-visible {
  background: var(--color-border);
}
.row-actions-menu-item.row-actions-menu-item-danger {
  color: var(--color-error);
}

.btn-outline svg,
.btn-export svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.btn-outline svg[hidden],
.btn-export svg[hidden] {
  display: none;
}
.btn-outline:hover:not(:disabled),
.btn-export:hover:not(:disabled) {
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.btn-outline:disabled,
.btn-export:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Primary action: the prominent, filled choice for a page's main forward action. */
.btn-primary,
.btn-export-primary {
  flex-shrink: 0;
  margin-left: auto;
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
.btn-primary:hover:not(:disabled),
.btn-export-primary:hover:not(:disabled) {
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
  opacity: 0.9;
}

/* Ghost action: a quiet text-like action when a bordered button is too strong. */
.btn-ghost {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-decoration: underline;
  text-decoration-color: var(--color-border);
  text-underline-offset: 3px;
}
.btn-ghost:hover {
  color: var(--color-primary);
  text-decoration-color: currentColor;
}

/* Compact row action: use in dense tables rather than a 44px page action. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.4;
  cursor: pointer;
  text-decoration: none;
  transition: border-color var(--transition-interactive), background var(--transition-interactive), color var(--transition-interactive);
}
.btn:hover:not(:disabled) {
  border-color: var(--color-primary);
  background: var(--color-surface);
  color: var(--color-primary);
}
.btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Compact secondary row action: add to .btn without changing its dimensions. */
.btn-secondary {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  line-height: 1.4;
}

/* .status-pill (style.css ~1059) only ever carried border-radius/border-width/
   cursor -- no colour, no background, no padding -- so every .status-pill
   with a data-state attribute (message templates' row.status: default /
   customized / draft) rendered as invisible bare text. Tone follows the same
   success/warning semantic the rest of the app already uses for
   published-vs-draft (.status-pill-success/-warn/-error, .rg-status-pill). */
.status-pill[data-state] {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.6rem;
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
  background: var(--color-surface-2);
  border-color: var(--color-border);
  color: var(--color-text-muted);
}
.status-pill[data-state='customized'] {
  background: var(--color-success-highlight);
  border-color: var(--color-success);
  color: var(--color-success-text);
}
.status-pill[data-state='draft'] {
  background: var(--color-warning-highlight);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}

/* #3092 -- Closing Task tab. Mirrors .inspect-progress-bar for the two
   master-task progress cards, and reuses .repairs-toolbar-like spacing for
   the search/filter/add-task row. Scoped under .ct- to avoid colliding with
   any existing progress-bar-shaped class from another feature. */
.ct-progress-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: var(--color-surface-2);
  overflow: hidden;
  margin: 6px 0;
}
.ct-progress-bar > i {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  height: 100%;
  background: var(--color-primary);
  border-radius: 999px;
  transition: width 0.2s ease;
}
.closing-task-tab .repairs-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.closing-task-tab .repairs-toolbar #ctSearch {
  flex: 1 1 220px;
  min-width: 160px;
}
.ct-group {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  margin-top: 10px;
  overflow: hidden;
}
.ct-group-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  background: var(--color-surface-2);
  cursor: pointer;
  font-weight: 500;
}
.ct-group-header .ct-group-count {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 400;
}
.ct-group-rows {
  padding: 4px 12px 8px;
}
.ct-group[data-collapsed="true"] .ct-group-rows {
  display: none;
}
.ct-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--color-divider, var(--color-border));
}
.ct-row:first-child {
  border-top: none;
}
.ct-row-label {
  flex: 1 1 220px;
  min-width: 160px;
}
.ct-row-sub {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.ct-row-assignee,
.ct-row-due {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.ct-status-pill {
  border: none;
  border-radius: 999px;
  padding: 4px 10px;
  font-size: var(--text-sm);
  cursor: pointer;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
}
.ct-status-pill[data-status="done"] {
  background: var(--color-success-highlight);
  color: var(--color-success-text);
}

/* #3434: compact ticket history rows used by lease, unit, and tenant records. */
.owner-maintenance-history-list { display:grid; gap:var(--space-2, 8px); }
.owner-maintenance-history-row { display:flex; justify-content:space-between; gap:var(--space-3, 12px); width:100%; padding:10px; border:1px solid var(--color-border); border-radius:6px; background:var(--color-surface, #fff); color:inherit; text-align:left; cursor:pointer; }
.owner-maintenance-history-row:hover, .owner-maintenance-history-row:focus-visible { border-color:var(--color-primary); outline:none; }
.owner-maintenance-history-row span { color:var(--color-text-muted, #666); font-size:.9em; }

/* #3462 (epic #3446) -- GL statement account-type groups on the trial
   balance. Composes on .showing-accordion for the collapsible card itself;
   this only fixes the numbers so the debit/credit subtotals in each summary
   line up column-wise down the page instead of jittering with digit width. */
.tb-group > summary .field-note {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* =============================================================================
   #4377 -- Shared redesign components (property-analysis-page redesign)
   =============================================================================
   CSS/markup-pattern layer ONLY, built for the tab-specific redesign tickets
   that follow (Overview/Loan/Rentals/Repairs/Metrics -- each is its own
   ticket and none of them are wired up here). Ported from the approved
   wireframe (rg_wireframe/styles.css + index.html) and adapted onto this
   app's real design tokens (--color-*, --space-*, --text-*, --radius-*,
   --shadow-*, --font-*) instead of the wireframe's standalone hex/px values.
   Nothing here is referenced by any live tab yet -- these classes are new
   and additive. They are meant to visually REPLACE .kpi-card boxes on the
   property-analysis page in later tickets; .kpi-card itself is untouched and
   keeps working everywhere else in the app (landing dashboard, Compare,
   etc.) that this ticket does not touch.

   Contents:
     1. Stat-strip       -- compact horizontal row of small stat blocks
     2. Ledger rows       -- label-left / tabular-number-right income lines
     3. kv-list / term-grid -- 2-4 col label/value grid (loan terms, specs)
     4. Table-scroll convention -- two documented mobile table behaviors
     5. Notes-as-popup modal -- reuses the EXISTING .modal-overlay/.modal-card
        system (see "Modal (Notes popup)" below for why no new modal system
        was introduced)
   ========================================================================== */

/* ---- 1. Stat-strip: replaces 4-6 .kpi-card boxes with one compact row.
   Each .rg-stat is a label+value pair; the strip scrolls horizontally
   instead of wrapping so it never breaks to a second row on narrow
   viewports. Prefixed rg- (not just .stat/.stat-strip) to avoid collision
   with any existing single-word class in this 500KB+ stylesheet. */
.stat-strip {
  display: flex;
  gap: 0;
  margin-top: var(--space-3);
  border-top: 1px solid var(--color-divider);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* Never let this strip itself become the thing that widens the page --
     it scrolls internally instead. */
  max-width: 100%;
}
/* Inline variant: used mid-content (e.g. a subtab's own summary row) rather
   than the sticky property header -- no top border, has its own bottom
   rule and spacing instead. */
.stat-strip-inline {
  border-top: none;
  margin: 0 0 var(--space-4);
  border-bottom: 1px solid var(--color-divider);
  padding-bottom: var(--space-3);
}
.rg-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-right: var(--space-4);
  margin-right: var(--space-4);
  border-right: 1px solid var(--color-divider);
  min-width: fit-content;
  /* Belt-and-suspenders against the grid/flex min-width bug documented in
     section 4 below: a stat block never contains a wide nested table, but
     min-width:0 here costs nothing and keeps this item from ever being the
     thing that forces its flex parent wider than intended. */
  min-width: 0;
  flex-shrink: 0;
}
.rg-stat:last-child {
  border-right: none;
  margin-right: 0;
  padding-right: 0;
}
.rg-stat-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
  white-space: nowrap;
}
.rg-stat-value {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Tone matches the flag vocabulary .kpi-flag / .results-strip-value already
   use elsewhere (data-tone='good'|'warn'|'bad'), so a figure carried over
   from a .kpi-card into a stat-strip keeps the same color meaning. Uses the
   *-text token variants (not the bare tone tokens) for the same WCAG AA
   reason .results-strip-value and .kpi-flag already do -- see ID 265/266
   notes elsewhere in this file. */
.rg-stat-value[data-tone='good'] {
  color: var(--color-success-text);
}
.rg-stat-value[data-tone='warn'] {
  color: var(--color-warning-text);
}
.rg-stat-value[data-tone='bad'] {
  color: var(--color-error-text);
}
/* #4381 QA fix: primary-emphasis parity with .kpi-card[data-tone='primary']
   .kpi-value -- carried over so a stat moved from a .kpi-card into a
   .rg-stat (e.g. Overview's Monthly cash flow, the strip's lead figure)
   doesn't silently lose its color emphasis. data-tone='primary' lives on the
   .rg-stat wrapper, matching how .kpi-card[data-tone] is applied. */
.rg-stat[data-tone='primary'] .rg-stat-value {
  color: var(--color-primary);
}
/* Under ~420px (this app's existing narrow-mobile breakpoint, see the
   @media (max-width: 420px) rules elsewhere in this file): keep the strip a
   single non-wrapping row that scrolls, per spec -- never let it wrap. */
@media (max-width: 420px) {
  .stat-strip {
    flex-wrap: nowrap;
  }
  .rg-stat {
    min-width: 108px;
  }
}

/* #4381 QA fix: .rg-stat sub-line and flag pill. .kpi-sub/.kpi-flag are
   otherwise scoped to .kpi-card (see "Overview redesign" section far above)
   -- these two rules give a .rg-stat consumer (the Overview "How it's
   performing" strip) the same sub-line/flag-pill treatment without
   duplicating a new class name, since the value carried over is identical
   and .kpi-flag[data-level=...]'s tone-color rules apply regardless of
   ancestor. */
.rg-stat .kpi-sub {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.rg-stat .kpi-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}

/* ---- 2. Ledger rows: income-statement-style label-left / value-right
   lines, e.g. Gross rent / Operating expenses / NOI / Debt service / Cash
   flow. Numeric values are tabular-nums so a column of dollar figures
   aligns vertically even with mixed digit widths.
   NAMESPACED .rg-ledger* (NOT bare .ledger/.ledger-row/.ledger-total): this
   file already has an unrelated, actively-used `table.ledger` component
   (Overview/Compare income tables, e.g. #ownUwComparisonTable,
   #rentComparisonTable -- see the earlier `.ledger { width: 100%; ... }`
   block) built on <table>/<tr>/<td>, plus its own `.ledger-total`-shaped
   selectors (`tr.subtotal`, `tr.result`). Reusing the bare `.ledger` class
   name for this new DIV-based row layout would silently redefine that
   existing selector (last rule in the file wins) and break every one of
   its live call sites. `rg-` (Return Grid redesign, matching `.rg-stat`
   above) keeps this new pattern from ever colliding with an existing or
   future single-word class in this 500KB+ stylesheet. */
.rg-ledger {
  display: flex;
  flex-direction: column;
}
.rg-ledger-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider);
  font-size: var(--text-sm);
}
.rg-ledger-row:last-child {
  border-bottom: none;
}
.rg-ledger-row span {
  color: var(--color-text-muted);
}
.rg-ledger-row strong {
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Negative / deducted line (e.g. "- Operating expenses", "- Debt service").
   Uses --color-error-text (the WCAG-AA-on-surface text variant), matching
   the --color-error/-text split already established for .kpi-flag etc. */
.rg-ledger-neg strong {
  color: var(--color-error-text);
}
/* Subtotal / total line: heavier rule above instead of below, bold. */
.rg-ledger-total {
  border-top: 1px solid var(--color-border);
  border-bottom: none;
  font-weight: 700;
  padding-top: var(--space-2);
}
.rg-ledger-total span {
  color: var(--color-text);
  font-weight: 700;
}
/* Final grand-total line (e.g. "Cash flow") -- larger figure. */
.rg-ledger-final strong {
  font-size: var(--text-lg);
}
.rg-ledger-final strong.text-positive,
.rg-ledger-row strong.text-positive {
  color: var(--color-success-text);
}

/* ---- 3. kv-list / term-grid: label/value pairs for loan terms, property
   specs, and similar fact sheets. kv-list is a single-column stacked list
   (sidebar-style); term-grid is a responsive 2-4 col CSS grid (main-column
   fact sheet, e.g. "Current loan terms"). Both reuse .rg-ledger-row's
   tabular-nums convention for the value side. Confirmed `.kv-list`/`.kv-row`
   have no existing definition anywhere else in this file before adding
   these (unlike `.ledger` above). */
.kv-list {
  display: flex;
  flex-direction: column;
}
.kv-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider);
  font-size: var(--text-sm);
}
.kv-row:last-child {
  border-bottom: none;
}
.kv-row span {
  color: var(--color-text-muted);
}
.kv-row strong {
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* Tighter vertical rhythm for a sidebar card where the kv-list is one of
   several stacked cards rather than the main content. */
.kv-list-tight .kv-row {
  padding: var(--space-1) 0;
  font-size: var(--text-xs);
}

/* term-grid: CSS grid, not flex -- explicitly minmax(0, 1fr) tracks (see the
   min-width bug note in section 4) even though a .term cell is never itself
   a wide table, so the pattern stays correct if a future ticket nests
   something wider inside one. */
.term-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3) var(--space-4);
}
.term-cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.term-cell-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}
.term-cell-value {
  font-size: var(--text-sm);
  font-weight: 600;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}
.term-cell-value small {
  color: var(--color-text-muted);
  font-weight: 500;
  font-family: var(--font-body, inherit);
}
/* Collapse to 2 columns well before mobile so 4-up terms don't get cramped;
   collapse again to 1 column under the app's existing ~420px narrow-mobile
   breakpoint per spec. */
@media (max-width: 720px) {
  .term-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 420px) {
  .term-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- 4. Table-scroll convention -----------------------------------------
   TWO distinct table behaviors on mobile. Which one a given table gets
   depends on whether it has a dedicated mobile card alternative:

   (a) HAS a mobile card alternative (e.g. a future Units or Repairs table
       redesign) -- the desktop table is hidden on mobile and a separate
       `.mobile-cards-list` of `.mobile-card` elements is shown instead, each
       card repeating the same fields as one table row. Convention:
         <div class="table-wrap mobile-cards-table-scroll">...table...</div>
         <div class="mobile-cards-list">...cards...</div>
       `.mobile-cards-table-scroll` is a marker class (composes with the
       app's existing `.table-wrap`) meaning "this specific table has a
       mobile-card sibling and should disappear on mobile", vs. plain
       `.table-wrap` which has no such sibling and must stay visible+scroll.
       `.mobile-cards-list` is hidden by default and only shown at the
       narrow breakpoint, mirroring how `.mobile-cards-table-scroll` is only
       hidden at that same breakpoint -- neither ever fights the other on
       desktop.

   (b) has NO mobile alternative (e.g. an amortization schedule or the
       sensitivity grid -- reformatting either into cards would lose the
       row/column relationship that IS the content) -- the table stays
       visible on mobile inside its own `.table-wrap` (already
       `overflow-x:auto` at every width, see the #2227 block above) and
       scrolls horizontally within that container. It must never widen the
       page itself.

   THE BUG THIS GUARDS AGAINST (confirmed the hard way once already, see the
   ticket): a CSS grid/flex ancestor will NOT let its track shrink below a
   nested table's intrinsic min-width (e.g. `.data-table`'s `min-width:
   640px`) unless BOTH of the following hold:
     1. the grid track itself is `minmax(0, 1fr)`, not bare `1fr`
        (a bare `1fr` track has an automatic minimum size equal to its
        content's min-content size -- i.e. the table's min-width -- so the
        column refuses to shrink at all);
     2. every intermediate wrapper div between the grid/flex container and
        the table has `min-width: 0` set explicitly (block-level children
        default to `min-width: auto`, which also propagates the child's
        intrinsic width up to its flex/grid parent).
   Missing either one lets the table push its ancestor grid/flex track wide,
   which pushes the whole page wide, which is a horizontal page scrollbar on
   a phone. This was verified with a live measurement -- see the temporary
   #rgComponentsSelfTest markup below and the report accompanying this
   change for the exact document.body.scrollWidth vs. clientWidth numbers. */
.mobile-cards-list {
  display: none;
}
@media (max-width: 640px) {
  .mobile-cards-table-scroll {
    display: none;
  }
  .mobile-cards-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }
}
/* Any wrapper placed between a grid/flex layout and a .table-wrap must carry
   this, per the bug note above. Opt-in utility rather than a blanket rule so
   it never silently overrides an unrelated wrapper's intended min-width. */
.min-w-0 {
  min-width: 0;
}
.mobile-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.mobile-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  flex-wrap: wrap;
}
.mobile-card-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-sm);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-divider);
}
.mobile-card-row:last-of-type {
  border-bottom: none;
}
.mobile-card-row span {
  color: var(--color-text-muted);
}
.mobile-card-row strong {
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---- 5. Modal (Notes popup) ----------------------------------------------
   The wireframe proposed a new .modal-overlay/.modal/.modal-head/.modal-body
   pattern for a "record an event with notes" popup (e.g. loan history's
   "+ Add event"). This app ALREADY has a real, extensively-used modal system
   -- .modal-overlay / .modal-card / .modal-header / .modal-body /
   .modal-close / .modal-note / .modal-actions (see e.g. #landingCustomizeModal,
   #duplicatePropertyModal in index.html, and the .modal-overlay/.modal-card/
   .modal-header/.modal-close/.modal-note/.modal-actions rules above in this
   file). Per the ticket's own instruction to check for and reuse an existing
   modal pattern rather than introduce a second one: NO new modal CSS is
   added here. A later ticket wiring up a real "add loan event" (or similar)
   popup should compose the existing classes, e.g.:

     <div id="loanEventModal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="loanEventHeading" hidden>
       <div class="modal-card">
         <div class="modal-header">
           <h2 id="loanEventHeading">Add loan event</h2>
           <button type="button" class="modal-close" aria-label="Close">...</button>
         </div>
         <div class="modal-body">
           <div class="field">...</div>
           <div class="modal-actions">
             <button type="button" class="btn-outline">Cancel</button>
             <button type="button" class="btn-primary">Save event</button>
           </div>
         </div>
       </div>
     </div>

   Only new, additive rule below: a compact field-label + full-width control
   pairing for a short modal form (event type / date / notes), reusing
   .field/.field label sizing already used elsewhere, added because the
   existing .field styling assumes a wider form context than a 420px modal
   card typically needs -- this rule is scoped to .modal-body so it never
   changes any existing page's fields. */
.modal-body .field-compact {
  margin-bottom: var(--space-3);
}
.modal-body .field-compact label {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: var(--space-1);
}
/* #4479: box-sizing: border-box added before this class saw its first real
   caller (grepped index.html/app.js at the time -- zero usages), so this is
   a no-visible-impact correction, not the reviewable resize #4479 first
   assumed: without it, border+padding get added on top of width: 100%
   instead of absorbed by it, the same arithmetic #4465/#4468 found and fixed
   for .asset-modal-sheet. Fixed here before anything adopts .field-compact,
   so the first real usage starts from correct sizing instead of inheriting
   this bug. */
.modal-body .field-compact select,
.modal-body .field-compact input,
.modal-body .field-compact textarea {
  width: 100%;
  box-sizing: border-box;
  max-width: 100%;
  font-family: inherit;
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  resize: vertical;
}

/* =============================================================================
   #4382 -- Rent roll (Rentals > Units) mobile cards
   =============================================================================
   First real consumer of the #4377 .mobile-cards-list/.mobile-card pattern
   (see the #4377 block above, section 4) -- #rentRollTable/#rentRollWrap now
   carries .mobile-cards-table-scroll and #rentRollMobileCards is the sibling
   .mobile-cards-list, built/kept in sync by renderRentRollMobileCards() in
   app.js. Only small additive refinements on top of the generic
   .mobile-card/.mobile-card-row rules are needed here -- the base pattern
   already handles the show/hide breakpoint, spacing, and label/value row
   styling. */
.mobile-card-specs-line strong {
  font-family: inherit;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.mobile-card-action-row {
  border-bottom: none;
  padding-top: var(--space-2);
  justify-content: flex-end;
}
.mobile-card-action-btn {
  width: 100%;
}
#rentRollMobileCards .rr-occ-pill {
  flex-shrink: 0;
}

/* =============================================================================
   #4442 -- Building tab restructure: contacts as an even 2x2 grid, physical
   characteristics as a compact 4-across field grid, matching the stat-strip/
   two-column-card idiom the rest of the property analysis page uses after
   epic #4376. Wireframe: https://www.perplexity.ai/computer/a/return-grid-property-analysis-pGTgTasAT8a6dRI7gbM2Og
   (agreed with Tony 2026-08-29, see issue #4442 comment).

   Purely a layout change -- no field ids, no JS wiring, no removed markup.
   Every existing #propertyManagersRow/#maintenanceRow/#propertyOwnerRow/
   #operatingEntityRow and every field-row inside #unitInfoInputPanel keeps
   its id, listeners, and validation exactly as-is; only the surrounding
   containers/headings changed. ============================================= */

/* Contacts: 4 uneven flex-wrap rows -> even 2x2 grid on the Building tab
   specifically (scoped to #mainPanelUnitInfo so the shared
   .overview-department-rows shell -- also reused elsewhere per the #3160
   comment above -- keeps its original flex-wrap behavior anywhere else). */
#mainPanelUnitInfo .overview-department-rows {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
#mainPanelUnitInfo .overview-department-rows .status-card-row {
  flex: initial;
  margin-bottom: 0;
}
@media (max-width: 640px) {
  #mainPanelUnitInfo .overview-department-rows {
    grid-template-columns: 1fr;
  }
}

/* Physical characteristics: compact 4-across grid for every static per-unit
   field (Type through Sqft below grade, including the Basement multiselect
   and its conditional split-inputs sibling) -- same idiom as Loan's
   .term-grid but for LIVE inputs rather than read-only values, so it wraps
   .field (not .term-cell) and keeps every existing input/select/label
   untouched. minmax(0, 1fr) tracks let the Basement multiselect and its
   conditional split field size down to fit the fixed 4-up track without
   overflowing. */
.building-char-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3) var(--space-4);
}
.building-char-grid .field {
  margin-bottom: 0;
  min-width: 0;
}
@media (max-width: 720px) {
  .building-char-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 420px) {
  .building-char-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Card heading rhythm for the 4 new Building-tab sub-cards -- reuses the
   site's existing .panel-heading/h2 pattern (see e.g. #ownUwComparisonTable's
   panel) rather than inventing a new heading style. Only additive spacing
   tweak: these headings sit inside what used to be one continuous fieldset,
   so they need a top margin the first .panel-heading elsewhere doesn't. */
.building-subcard-heading {
  margin: var(--space-5) 0 var(--space-3);
}
.building-subcard-heading:first-child {
  margin-top: 0;
}

/* #5359: the former #4442 sidebar 2-col grid (.building-tab-grid /
   .building-tab-grid-main / .building-tab-grid-sidebar) is retired -- every
   card on the Building tab is now a full-width single column, per Tony's
   wireframe approval 2026-09-04. Rules removed outright rather than left
   dead: no markup references these classes any more. */
/* Live "N units, M common areas" summary line on the Units & common areas
   card -- filled in by renderBuildingUnitsAreasSummary() in app.js. Empty
   text node renders nothing until JS populates it, so it never flashes an
   empty line before first paint. */
.building-units-areas-summary {
  margin: 0 0 var(--space-3);
}

/* =============================================================================
   #4380 -- Loan tab layout (rebuild on #4377 shared components)
   =============================================================================
   CSS/markup-layout ONLY. Restyles the existing Loan tab subtree
   (#resultsPanelFinancing's children, promoted at runtime into
   #loanTabReparentTarget inside #mainPanelLoan by promoteFinancingPanelToLoanTab()
   in app.js -- see the comment on #mainPanelLoan in index.html).

   #4377 FOLLOW-UP (2026-09-05, Tony's decision): despite the original wording
   below, this tab does NOT use the #4377 .kv-list/.kv-row or .term-grid
   classes -- #currentLoanStatusGrid intentionally keeps .kpi-grid/.kpi-card
   (documented a few hundred lines below, to preserve the id contract with the
   Analyze-phase Financing sub-tab) and loanPaidOffSummarySection uses the
   pre-existing .metric-grid/dl/dt/dd, not .term-grid. This tab's one real
   #4377 deliverable is the notes-as-popup modal reuse (#loanEventModal, added
   under #4471, reusing .modal-overlay/.modal-card). The actual first consumer
   of .kv-list/.term-grid is the Renewals & Expenses tab (#4654 and later --
   see renewalsInsuranceKv/renewalsTaxKv/renewalsManagementFeeKv/
   renewalsHoaFeeKv below). #4377 was closed on that basis rather than on this
   tab; see #4377 for the written QA record. What #4380 actually did use from
   #4377 is .table-wrap/.grid-scroll (pre-existing before #4377, not new) and
   .min-w-0. No new component classes were needed here; #4380 is purely the
   grid scaffold that arranges the existing sections.

   Original claim (kept for history, now corrected above): this used ONLY the
   #4377 component classes (.kv-list/.kv-row, .term-grid, .table-wrap/.grid-scroll
   convention, .min-w-0). No new component classes were needed; #4380 is purely
   the grid scaffold that arranges the existing sections.

   WHY display:contents on #loanTabReparentTarget: promoteFinancingPanelToLoanTab()
   moves #resultsPanelFinancing's children (currentLoanStatusSection,
   financingTermsSection, loanPaidOffSummarySection, refinanceHistorySection,
   amortReparentTarget) to be direct children of #loanTabReparentTarget, in
   that fixed document order, via plain appendChild -- see the function's own
   comment for why it moves nodes instead of re-rendering markup (id contract
   with render functions). The JS never reorders them and must not be changed
   for this CSS-only ticket. `display: contents` removes the wrapper from the
   box tree entirely, so its children become direct grid items of
   #loanTabGrid and each can be assigned a row/column independently --
   without ever touching the DOM order app.js relies on. If `display:
   contents` is ever removed, this whole layout degenerates to a single
   stacked column (graceful, not broken) since #loanTabReparentTarget would
   just become a normal block-level grid item spanning one track.

   #5373 -- RETIRES THE FIXED-320PX-SIDEBAR MODEL (#4380/#4471) ENTIRELY, at
   every width down to the ~720px mobile stack point, per Tony's approved
   wireframe (attached to #5373). Two separate live bugs traced back to that
   fixed 320px column:
     1. Between ~720-1000px the two-column grid still forced two tracks the
        main column's amortization table (640px intrinsic min-width, #4377
        note) couldn't shrink to fit, pushing the fixed-width sidebar past
        the viewport's right edge -- Tony's original screenshots.
     2. At EVERY width, including full desktop, the Loan history card's
        table (DATE / EVENT / LENDER / ... columns) is far wider than any
        reasonable fixed sidebar column and was clipped rather than
        reflowed -- caught only after a first fix pass (#5377) narrowly
        patched bug 1's breakpoint without touching the base desktop grid,
        so bug 2 was still live above ~1000px and a same-day QA pass missed
        it by reading the clipped LENDER column as an intentional
        `.grid-scroll` pattern rather than the reported bug.
   The fix: no card in this tab gets a permanently-narrow fixed-width
   column. Approved wireframe order, now the ONE layout from 720px up:
     1. financingTermsSection (Current loan terms) -- full width
     2. loanTimelineSection (Loan history) + currentLoanStatusSection
        (Current loan status) -- side by side, each a full 1fr track, so
        Loan history's table gets real width instead of a fixed 320px;
        loanPaidOffSummarySection / refinanceHistorySection join the
        SAME row (second column) since they are sidebar-family, status-
        adjacent cards and only one of the three ever renders at a time
        alongside loan history
     3. amortReparentTarget (Loan amortization schedule) -- full width,
        LAST -- moved here from position 2 in the original #4380/#4471
        order at Tony's explicit direction on this ticket
   Below 720px (the app's usual mobile step, matching .term-grid,
   .renewals-cards-grid, etc.) the row-2 pair itself collapses to single
   column, same as every other two-up row in this file. */
.loan-tab-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-5, 20px);
  align-items: start;
}
/* See the big comment above .loan-tab-grid for why this is `contents`. */
#loanTabReparentTarget {
  display: contents;
}

/* Explicit numeric grid-row on every item, not `grid-row: auto` -- these
   sections reach the grid through two different paths (loanTimelineSection
   is a real sibling grid item of #loanTabGrid in index.html; everything
   else arrives via the `display:contents` #loanTabReparentTarget wrapper,
   each in app.js's fixed appendChild order), and grid auto-placement does
   NOT pair two `grid-row: auto` items into the same row just because they
   share `order` and sit in different explicit columns -- each auto item
   claims the next free row in its own column independently, which is
   exactly why an earlier draft of this fix put Current loan status in row
   2 while Loan history landed in row 3, leaving the two visibly
   staggered instead of side by side (caught by browser-rendered QA at
   1400px/850px, not by the text-only CSS assertions in
   tests/loanTabClipFix5373.unit.js, which can't see actual row placement).
   Numbering the rows explicitly removes any dependence on DOM/appendChild
   order or auto-placement's per-column bookkeeping. */
#financingTermsSection {
  order: 1;
  grid-row: 1;
  grid-column: 1 / -1;
}
#loanTimelineSection {
  order: 2;
  grid-row: 2;
  grid-column: 1;
}
#currentLoanStatusSection,
#loanPaidOffSummarySection,
#refinanceHistorySection {
  order: 2;
  grid-row: 2;
  grid-column: 2;
}
#amortReparentTarget {
  order: 3;
  grid-row: 3;
  grid-column: 1 / -1;
}

/* ---- Current-loan-status card: 4 .kpi-card boxes -> compact .kv-list.
   Reuses the EXISTING #currentLoanStatusGrid markup (still `.kpi-grid` >
   `.kpi-card` > `.kpi-label`/`.kpi-value`/`.kpi-sub`, per the index.html
   comment on #currentLoanStatusGrid: ids are never touched and the
   Analyze-phase Financing sub-tab keeps rendering the same markup as 4 real
   boxes). Scoped to `#loanTabGrid #currentLoanStatusGrid` so this ONLY
   applies once the section is promoted into the Loan tab -- the un-promoted
   Financing results sub-tab (`#resultsPanelFinancing #currentLoanStatusGrid`,
   no `#loanTabGrid` ancestor) is untouched. */
#loanTabGrid #currentLoanStatusGrid {
  display: flex;
  flex-direction: column;
  grid-template-columns: none;
}
#loanTabGrid #currentLoanStatusGrid .kpi-card {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-divider);
  /* Cancel .kpi-card's own box styling (background/border/shadow/radius) so
     each row reads as a kv-list row, not a nested card-in-a-card. */
  background: none;
  border-width: 0 0 1px 0;
  border-radius: 0;
  box-shadow: none;
}
#loanTabGrid #currentLoanStatusGrid .kpi-card:last-child {
  border-bottom: none;
}
#loanTabGrid #currentLoanStatusGrid .kpi-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: none;
  font-weight: 500;
  order: 1;
}
#loanTabGrid #currentLoanStatusGrid .kpi-value:not([data-funding]) {
  /* ID 589: any .kpi-value rule that sets type must exclude [data-funding],
     or it competes with the funding-chip block on specificity and wins by
     source order alone -- this kv-list has no funding chips of its own, so
     the exclusion costs nothing and keeps the invariant real. */
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  order: 2;
  text-align: right;
}
#loanTabGrid #currentLoanStatusGrid .kpi-sub {
  /* Sub-value (e.g. delta / methodology aside) drops to a second line under
     the label so the row stays a clean two-column kv-row on top; still
     always present as it is contract for renderCurrentLoanStatus(). */
  flex-basis: 100%;
  order: 3;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-align: left;
}

/* ---- Loan-paid-off summary + refinance history: compact cards.
   .metric-grid (loanPaidOffSummarySection) already renders as label/value
   pairs (dt/dd) via existing CSS elsewhere in this file; in its own 1fr
   column (row 2, alongside Loan history) force it to the single-column
   stacked form so labels and tabular values don't get cramped into a 2-up
   grid at that column's width. */
#loanTabGrid #loanPaidOffSummaryGrid {
  grid-template-columns: minmax(0, 1fr);
}
/* Refinance history's table has no mobile-card alternative (#4377 section 4,
   case (b)) even in its own 1fr column -- it still scrolls horizontally
   within its own .grid-scroll rather than reflowing, same as everywhere
   else in this file. No override needed beyond the .min-w-0 guard already
   on the markup; this comment documents the deliberate non-override. */

/* #5373 -- #loanTimelineTable (Loan history) is a bare `.mini-table`, which
   carries `width: 100%` and NO min-width of its own (unlike `.data-table`'s
   640px or `.heatmap`'s 560px). With 8 columns (Date, Event, Lender/product,
   Amount, Rate, Term, Cash out, Notes) squeezed into a single ~1fr grid
   track, the browser shrank the table to fit instead of the surrounding
   `.grid-scroll` picking up a horizontal scrollbar, so the rightmost
   columns (Term, Notes) got crushed to unreadable widths -- the same
   "columns cut off" complaint this whole ticket started from, just at a
   different width than the zone PR #5377 fixed. Following the existing
   `table#workflowRuleLogTable` pattern in this file ("raise this one
   table's minimum instead"): give this ONE table its own floor so
   `.grid-scroll`'s existing `overflow-x: auto` (plus the #2325/#4443 edge
   fades) does its job, rather than raising `.mini-table`'s own min-width
   globally and risking every other `.mini-table` usage in this file (rent
   rolls, amortization, tenants, renewals, etc.). */
table#loanTimelineTable {
  min-width: 600px;
}

/* #5785 -- same root cause as #5373 directly above, hit on the Trial
   Balance/P&L/Balance Sheet GL reports instead: each is a bare `.mini-table`
   (2-3 columns -- Account/Debit/Credit or Account/Amount) with no min-width
   of its own, so at narrow viewports the browser shrank the Account column
   (which carries a code plus a full account name, e.g. "1200 Accounts
   Receivable - Tenant Deposits Held") to an unreadable clip instead of the
   surrounding `.grid-scroll` picking up a horizontal scrollbar -- reported
   as the rightmost (Debit/Credit/Amount) column clipping with no visible
   scroll affordance. Opt-in class (applied by
   financialStatementRenderTrialBalance/ProfitLoss/BalanceSheet in app.js)
   rather than raising `.mini-table`'s own min-width globally, same
   reasoning as #5373: don't risk every other bare `.mini-table` usage in
   this file. 480px is enough for a long account name plus 2-3 money
   columns without being wide enough to force a scrollbar on an ordinary
   phone screen when the content doesn't actually need one. */
.financial-statement-table {
  min-width: 480px;
}

/* #5785 QA-FAIL follow-up -- the fix above made `.grid-scroll` itself
   overflow-scroll correctly (scrollWidth === clientWidth on the wrapper
   at every width tested), but QA found the rightmost column still
   clipped at EVERY width, including normal desktop. Root cause is one
   level up: `.showing-accordion > summary ~ *` (this file, ~L11654)
   gives the `.grid-scroll` div `margin-left`/`margin-right: var(--space-3)`
   for the card's inner breathing room, but `.grid-scroll` is also
   `width: 100%` of the accordion's own content box -- so the margin
   pushes its border-box exactly `var(--space-3)` (12px) past the
   accordion's right inner edge. `.showing-accordion`'s `overflow: hidden`
   (needed to keep the <summary> header's background inside the card's
   rounded corners) then silently clips that 12px, with no scrollbar and
   nothing to reveal it -- matching QA's measured 1128/1116 scrollWidth/
   clientWidth gap exactly. Scope the width correction to this one
   composition (`.tb-group`, i.e. the GL trial-balance/P&L/balance-sheet
   accordion groups) rather than every `.showing-accordion` user, since
   other callers may rely on the existing `width: 100%` + margin sizing
   and this is the only one wrapping a `.grid-scroll` directly. */
.tb-group > .grid-scroll {
  width: calc(100% - (2 * var(--space-3)));
}

/* ---- Responsive collapse: single column, at the app's usual ~720px step
   (matches .term-grid, .renewals-cards-grid, and every other two-up row in
   this file). The row-2 pair (Loan history / Current loan status /
   paid-off summary / refinance history) falls back to full width --
   financingTermsSection and amortReparentTarget are already full width
   (grid-column: 1 / -1) at every width, so they need no column override
   here. They DO all still share the base layout's `grid-row: 2`, though,
   and grid-column alone going 1/-1 does not change that -- four full-width
   items pinned to the identical explicit row overlap on top of each other
   instead of stacking (visually broken exactly like two panels sharing one
   cell, caught by rendered-browser QA on this fix's own first pass: Current
   loan status's text rendered bleeding across Loan history's table). Reset
   to `grid-row: auto` here so they fall back to ordinary in-flow stacking,
   and give each a distinct `order` (they all share `order: 2` in the base
   layout, which is fine there since grid-column already separates them)
   so the stack sequence matches the wireframe: history, then status, then
   whichever of paid-off-summary/refinance-history is visible. Loan
   amortization schedule (#amortReparentTarget) must still land LAST in the
   mobile stack, matching the wireframe -- its base order (3) sits BETWEEN
   this group's old shared order (2) and nothing after it, so once the
   group below is spread across distinct order values 2-5 to stop them
   colliding, amortization's unchanged order: 3 would cut into the middle
   of that sequence instead of trailing it (caught before shipping by
   cross-checking the base rule block above, not by rendered QA this
   time). Bump it to 6 here so it stays last regardless of how many
   sidebar-family cards are visible. `order` alone is not enough, though --
   #amortReparentTarget's BASE rule still carries an explicit `grid-row: 3`
   (see the block above), and an explicit grid-row wins over `order` for
   placement regardless of how high `order` is set. Left un-reset, it kept
   rendering pinned to row 3 -- ahead of Current loan status, which had no
   explicit row of its own here and so flowed into whatever row auto-
   placement gave it -- landing amortization third instead of last (caught
   by a from-disk Playwright render reproducing the exact 650px QA
   harness result: computed grid-row was still "3", not "auto", even
   though computed order correctly read "6"). Reset grid-row: auto here
   too, matching the sidebar-family group above. */
@media (max-width: 720px) {
  #loanTimelineSection,
  #currentLoanStatusSection,
  #loanPaidOffSummarySection,
  #refinanceHistorySection,
  #amortReparentTarget {
    grid-column: 1 / -1;
    grid-row: auto;
  }
  #loanTimelineSection { order: 2; }
  #currentLoanStatusSection { order: 3; }
  #loanPaidOffSummarySection { order: 4; }
  #refinanceHistorySection { order: 5; }
  #amortReparentTarget { order: 6; }
  #loanTabGrid #currentLoanStatusGrid .kpi-value:not([data-funding]) {
    font-size: var(--text-base);
  }
}

/* ---- #4654: Renewals & Expenses tab -- two summary cards side by side
   (Insurance, Property tax), collapsing to one column at the same ~720px
   breakpoint every other two-column tab layout in this file uses. Plain
   .panel/.kv-list components already exist elsewhere (see .loan-tab-grid
   above); this is only the grid wrapper, no new card component. */
.renewals-cards-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-5, 20px);
  align-items: start;
}
@media (max-width: 720px) {
  .renewals-cards-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* #4582 -- "Send approval email" row on the application-detail Status card.
   Deliberately its own class instead of reusing .rg-inline-actions: that class
   sets `display` and (before #4581) had no `[hidden]` companion, so the hidden
   attribute was inert on it and the button would have shown on every
   application regardless of status. The [hidden] rule below is load-bearing for
   exactly that reason -- an author-level `display` beats the browser's own
   `[hidden] { display: none }` at any specificity. */
.application-approval-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin: 0 0 var(--space-2);
}
.application-approval-actions[hidden] {
  display: none;
}

/* #4586 -- [hidden] guards, burning down the baseline #4581 left behind.
   Each class below sets `display`, and any author-level `display` beats the
   browser's own `[hidden] { display: none }` at any specificity -- so without
   these companions the `hidden` attribute is silently inert and every
   `el.hidden = true` in JS is a no-op that fails loudly nowhere (#4581).
   Kept together at the end of the file deliberately: `.cls[hidden]` ties with a
   `.ancestor .cls` setter on specificity, so source order is what breaks the tie,
   and last wins. Moving one of these up next to its class can silently disarm it. */
.asset-register-summary[hidden] {
  display: none;
}
.badge[hidden] {
  display: none;
}
.branding-editor-layout[hidden] {
  display: none;
}
.bulk-action-bar[hidden] {
  display: none;
}
.ct-collapsed-summary[hidden] {
  display: none;
}
.important-dates-badge[hidden] {
  display: none;
}
.input-link-action[hidden] {
  display: none;
}
.input-wrap[hidden] {
  display: none;
}
.inventory-groups[hidden] {
  display: none;
}
.landing-layout-editor[hidden] {
  display: none;
}
.ledger-subnote[hidden] {
  display: none;
}
.lifecycle-buy-extras[hidden] {
  display: none;
}
.listing-links-list[hidden] {
  display: none;
}
.lock-divider[hidden] {
  display: none;
}
.lock-sso-btn[hidden] {
  display: none;
}
.maintenance-load-more[hidden] {
  display: none;
}
.property-header-edit-form[hidden] {
  display: none;
}
.quick-links-row[hidden] {
  display: none;
}
.repair-form-grid[hidden] {
  display: none;
}
.rr-occ-pill[hidden] {
  display: none;
}
.rr-stat[hidden] {
  display: none;
}
.scope-unit-search-results[hidden] {
  display: none;
}
.share-with-users-list[hidden] {
  display: none;
}
.showing-section-nav[hidden] {
  display: none;
}
.status-pill-warn[hidden] {
  display: none;
}
.tabs[hidden] {
  display: none;
}
.tenant-overview-dl-row[hidden] {
  display: none;
}
.user-admin-toggle[hidden] {
  display: none;
}

/* #4608 -- blanket close of the whole [hidden]-inert bug class (#4581, #4586).
   tenant.css, rent.css and intake.css already carry this exact rule; style.css
   and admin.css were the only two stylesheets missing it, and every offender
   #4581 and #4586 ever found lived on a page loaded by one of these two.
   `!important` outranks every ordinary declaration AND inline `style="display:"`
   attributes, so this closes the bug shape structurally rather than one class
   at a time: no future descendant selector, media-query rule, or inline style
   can make `hidden` inert on this stylesheet's pages again. The per-class
   `.cls[hidden]` rules above are now redundant but are left in place --
   deleting 137 hand-authored rules is a separate, much larger review this
   ticket does not call for, and leaving them is harmless: this rule already
   wins over all of them via `!important`, whether they stay or go. */
[hidden] {
  display: none !important;
}

/* #4684 -- Tier 1 (photo) AI Damage Review list + detail panel. Scoped
   under .ai-review- to avoid colliding with any existing feature; layout
   only, no new colour tokens (borrows --color-border/--color-surface-2 etc.
   already used throughout this file). */
.ai-review-thumb-placeholder {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-sm, 4px);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
}
/* #4778 -- Evidence column: real move-in/move-out thumbnails, or the
   existing placeholder swatch (above) when no photo exists for that side.
   Two side-by-side, same 48px box as the placeholder so a row never
   reflows depending on which one it got. */
.ai-review-evidence-cell {
  display: flex;
  gap: var(--space-1, 4px);
}
.ai-review-evidence-thumb {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-sm, 4px);
  border: 1px solid var(--color-border);
  object-fit: cover;
  display: block;
}
.ai-review-evidence-none {
  color: var(--color-text-muted);
  font-size: 12px;
}
/* #4778 -- row states from the wireframe. Untagged (fell back to room-level
   evidence rather than a tagged checklist item) uses the same
   --color-warning-highlight amber tint as .zoho-gl-mappings-row-highlight
   and .deposits-queue-row-overdue elsewhere in this file -- same
   "needs a second look" meaning, same token, not a new colour. Reviewed
   rows are dimmed/muted rather than removed, since #4684's own decision was
   every flagged item stays visible, reviewed or not -- opacity is a
   read/visited cue only, never a confidence-based hide. */
.ai-review-row-untagged {
  background: var(--color-warning-highlight);
}
.ai-review-row-reviewed {
  opacity: 0.6;
}
.ai-review-detail-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2, 8px);
  margin-bottom: var(--space-3, 12px);
}
.ai-review-photo-pair {
  display: flex;
  gap: var(--space-4, 16px);
  flex-wrap: wrap;
  margin-bottom: var(--space-3, 12px);
}
.ai-review-photo-col {
  flex: 1 1 280px;
  min-width: 220px;
}
.ai-review-photo-caption {
  font-weight: 600;
  margin-bottom: 4px;
}
.ai-review-photo-col img {
  max-width: 100%;
  border-radius: var(--radius-sm, 4px);
  border: 1px solid var(--color-border);
}
.ai-review-ai-panel {
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 4px);
  padding: var(--space-3, 12px);
  margin-bottom: var(--space-3, 12px);
}
.ai-review-decision-panel {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3, 12px);
}
.ai-review-decision-buttons {
  display: flex;
  gap: var(--space-2, 8px);
  margin: var(--space-2, 8px) 0;
}
/* No default/pre-selected state ever renders here on load (#4684 hard
   rule) -- .is-selected is toggled only after a decision is actually saved,
   never set from the AI suggestion. */
/* --color-text-inverse, not a #fff literal -- see the ID 266 note above
   .repairs-chip.is-active for why: it flips per theme and meets 4.5:1 on
   both the light and dark --color-primary (caught by contrastPairs.unit.js
   and showingsOverhaul.unit.js's dark-mode token guard on PR #4700). */
.ai-review-decision-btn.is-selected {
  background: var(--color-primary, #2563eb);
  color: var(--color-text-inverse);
  border-color: var(--color-primary, #2563eb);
}

/* #4685 -- Tier 2 (video/audio) walkthrough recap panel. Same .ai-review-
   namespace and token reuse as the #4684 block above; layout only. */
.ai-review-recap-room {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3, 12px);
  margin-top: var(--space-3, 12px);
}
.ai-review-recap-room:first-child {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}
.ai-review-recap-room h3 {
  /* No --text-md token exists in this sheet; use the literal rather than a
     var() reference that would always fall through. */
  font-size: 1rem;
  font-weight: 650;
  margin-bottom: var(--space-2, 8px);
}
.ai-review-recap-item-list {
  margin: 0;
  padding-left: var(--space-5, 20px);
}
.ai-review-recap-item-list li {
  margin-bottom: var(--space-2, 8px);
}

/* #6328: the send wizard modal. Moves the lease record page's send flow
   (previously three sets of inline panels on a ~4,000-5,300px-tall page)
   into a single modal shared by all three entry points -- "Send an
   addendum", "Renew lease" and the plain "Send for signature..." button.
   Reuses the existing .modal-overlay/.modal-card shell (see the
   .modal-overlay/.modal-card rules above) and the app's own custom
   properties throughout; no new tokens are introduced. */
.ld-send-wizard-card {
  max-width: 40rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  max-height: 85vh;
  overflow: hidden;
}
.ld-send-wizard-head {
  padding: var(--space-4) var(--space-5) 0;
  flex: 0 0 auto;
}
.ld-send-wizard-head-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
}
.ld-send-wizard-title {
  margin: 0;
  font-size: var(--text-lg, 1.125rem);
}
.ld-send-wizard-lease {
  margin: 0.15rem 0 0;
  font-size: var(--text-sm, 0.875rem);
  color: var(--color-text-muted, #6b7280);
}
.ld-send-wizard-close {
  width: 1.75rem;
  height: 1.75rem;
  line-height: 1;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  flex: 0 0 auto;
}
.ld-send-wizard-rail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
}
.ld-send-wizard-rail li {
  font-size: 0.72rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted, #6b7280);
  white-space: nowrap;
}
.ld-send-wizard-rail li > button {
  border: 0;
  padding: 0.1rem 0.55rem;
  border-radius: inherit;
  color: inherit;
  background: transparent;
  font: inherit;
}
.ld-send-wizard-rail li.is-done > button {
  cursor: pointer;
}
.ld-send-wizard-rail li > button:disabled {
  cursor: default;
}
.ld-send-wizard-rail li.is-current {
  border-color: var(--color-primary);
  color: var(--color-primary);
  background: var(--color-surface-2);
}
.ld-send-wizard-rail li.is-done {
  border-color: var(--color-success);
  color: var(--color-success-text, var(--color-success));
  background: var(--color-success-highlight);
}
.ld-send-wizard-rail li.is-done::before {
  content: "\2713\00a0";
}
.ld-send-wizard-body {
  padding: var(--space-4) var(--space-5) 0;
  overflow-y: auto;
  flex: 1 1 auto;
  border-top: 1px solid var(--color-border);
  margin-top: var(--space-3);
}

/* THE FIX -- #6328. Every step's action row must stay visible no matter
   how long the list above it is, so the button that used to render
   2,167px below the viewport with no scroll (looking exactly like a dead
   button) is now always reachable without scrolling the modal body. */
.ld-send-wizard-body .ld-inline-actions,
.ld-send-wizard-body .provider-actions {
  position: sticky;
  bottom: 0;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  padding: var(--space-3) 0;
  margin-bottom: 0;
  z-index: 1;
}

/* The relocated panels lose their inline-page chrome once they render
   inside the modal body -- that chrome (top margin/border) existed only
   to separate them from the rest of the long page, which no longer exists
   inside the modal. */
.ld-send-wizard-body .lease-record-inline-form {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.ld-send-wizard-body .ld-void-panel {
  margin-top: 0;
  padding: 0;
  border: none;
  background: transparent;
}
.ld-send-wizard-body .ld-send-picker-bundle-list {
  max-height: 15rem;
  overflow-y: auto;
}

/* #6565: "Door access" card -- Unit page, Tenant page, Lease record. Same
   masked/reveal posture as the Building Systems lock-codes modal, just
   rendered inline in a table row instead of a modal field. */
.door-access-scope-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.55rem;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}
.door-access-scope-unit {
  background: var(--color-surface-2);
  border-color: var(--color-border);
  color: var(--color-text-muted);
}
.door-access-scope-common {
  background: var(--color-success-highlight);
  border-color: var(--color-success);
  color: var(--color-success-text);
}
.door-access-dots {
  font-family: var(--font-mono);
  letter-spacing: 0.15em;
  color: var(--color-text-muted);
}
.door-access-code-value {
  font-family: var(--font-mono);
}
.door-access-reveal-btn {
  margin-left: 0.4rem;
  padding: 0.1rem 0.5rem;
  font-size: var(--text-xs);
}
