:root {
  --bos-navy: #0f1729;
  --bos-navy-light: #1a2540;
  --bos-blue: #2f6feb;
  --bos-blue-light: #eaf1fe;
  --bos-green: #16a34a;
  --bos-green-light: #e8f8ee;
  --bos-orange: #f59e0b;
  --bos-orange-light: #fef3e2;
  --bos-purple: #8b5cf6;
  --bos-purple-light: #f2edfe;
  --bos-red: #ef4444;
  /* Added for ContactStatusDefinition::CUSTOM_COLOR_PALETTE - a custom contact status (anything
     beyond the 4 starter ones above, which keep their own existing colors) cycles through these,
     arbitrarily chosen but each visually distinct from every other badge color already in use,
     "danger" included (that one stays reserved for actual warnings/overdue items elsewhere in
     the app, never an arbitrary status). */
  --bos-teal: #0d9488;
  --bos-teal-light: #e6f6f4;
  --bos-pink: #db2777;
  --bos-pink-light: #fce7f3;
  --bos-indigo: #4f46e5;
  --bos-indigo-light: #eef2ff;
  --bos-amber: #b45309;
  --bos-amber-light: #fef0e0;
  --bos-slate: #475569;
  --bos-slate-light: #eceff2;
  --bos-text: #1f2937;
  --bos-muted: #6b7280;
  --bos-border: #e5e7eb;
  --bos-bg: #f5f7fb;
  /* Typography scale - 6 steps covering the range of sizes actually
     in use across the app (previously 15+ ad-hoc values, some in px,
     some in rem, for what were mostly the same few intended sizes).
     xs: captions/tiny badges, sm: secondary text, base: body text
     (the default), md: emphasized/subheads, lg: section headings,
     xl: big numbers (KPI values). */
  --bos-fs-xs: 0.75rem;
  --bos-fs-sm: 0.875rem;
  --bos-fs-base: 1rem;
  --bos-fs-md: 1.125rem;
  --bos-fs-lg: 1.25rem;
  --bos-fs-xl: 1.5rem;
  /* Border-radius scale - 3 steps (previously 9 distinct px values in
     the 4-14px range with no consistent logic behind which was used
     where). sm: small controls (buttons, inputs), md: cards/panels,
     pill: fully-rounded badges/status pills. */
  --bos-radius-sm: 6px;
  --bos-radius-md: 10px;
  --bos-radius-pill: 999px;
}

* { box-sizing: border-box; }

/* Applies to every placeholder in the app - inputs, textareas, search
   fields - not just specific ones, since this is a global visual
   preference, not tied to any one form or component. */
::placeholder {
  opacity: 0.4 !important;
}
::-webkit-input-placeholder {
  opacity: 0.4 !important;
}
::-moz-placeholder {
  opacity: 0.4 !important;
}
:-ms-input-placeholder {
  opacity: 0.4 !important;
}

/* Placeholder disappears while the field is focused (being actively
   clicked into/typed in), and reappears on its own once focus moves
   away - :focus already reverts automatically on blur, so this needs
   no JS at all despite the show/hide behavior. */
:focus::placeholder {
  opacity: 0 !important;
}
:focus::-webkit-input-placeholder {
  opacity: 0 !important;
}
:focus::-moz-placeholder {
  opacity: 0 !important;
}
:focus:-ms-input-placeholder {
  opacity: 0 !important;
}

/* A file input's "No file chosen" text is native browser UI, not a real
   HTML placeholder attribute - ::placeholder above can't reach it in
   any browser. This reproduces the same 0.4-opacity look by dimming the
   input's own text color instead, toggled by initFileInputs() in app.js
   based on whether a file is actually selected (never permanently,
   since a real filename should stay at full opacity once chosen). The
   button gets its own explicit color here so it's never dimmed by the
   parent input's color, for every file input in the app - not just the
   ones already using .pm-file-input's own separate button styling. */
input[type="file"]::file-selector-button {
  color: var(--bos-text);
}
input[type="file"].js-file-empty {
  color: rgba(31, 41, 55, 0.4);
}

html {
  font-size: var(--bos-fs-base);
}

body {
  background: var(--bos-bg);
  color: var(--bos-text);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* ---------- Layout ---------- */
.bos-app { display: flex; min-height: 100vh; }

/* ---------- Sidebar (collapsible on hover, pinnable) ----------
   Default state: a narrow 68px icon-only rail. Hovering (or pinning
   via the button in the brand row) expands it to 250px, revealing
   labels. On touch devices there's no hover state, so a media query
   falls back to the sidebar's original always-open behavior instead
   of leaving it stuck as an unusable icon-only rail.

   Bootstrap's nav/nav-pills classes provide the base .nav-link
   structure (used by both regular links and the Property Management
   submenu toggle <button>) - these rules define the app-specific
   sizing, colors, collapse/expand states, and the submenu behavior. */
.bos-sidebar {
  width: 68px;
  flex-shrink: 0;
  background: var(--bos-navy);
  color: #cbd5e1;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  overflow-x: hidden;
  overflow-y: hidden;
  z-index: 1030;
  transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: width;
}
.bos-sidebar:hover,
body.bos-sidebar-pinned-open .bos-sidebar {
  width: 250px;
  overflow-y: auto;
  box-shadow: 6px 0 28px rgba(15, 23, 41, 0.22);
}

.bos-sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
}
.bos-sidebar-logo {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  border-radius: var(--bos-radius-md);
  background: var(--bos-blue);
  color: #fff;
  font-size: var(--bos-fs-md);
  display: flex;
  align-items: center;
  justify-content: center;
}
.bos-sidebar-subtitle {
  font-size:var(--bos-fs-xs);
  letter-spacing: .08em;
}

/* Shared by the brand's text block AND the user card's name/role at
   the bottom - both fade in only once the sidebar is wide enough to
   show them without wrapping awkwardly. */
.bos-brand-text {
  opacity: 0;
  transition: opacity 0.12s ease;
  overflow: hidden;
  min-width: 0;
}
.bos-sidebar:hover .bos-brand-text,
body.bos-sidebar-pinned-open .bos-brand-text {
  opacity: 1;
  transition: opacity 0.22s ease 0.08s;
}

.bos-sidebar-pin {
  margin-left: auto;
  background: transparent;
  border: none;
  color: #8a97b5;
  font-size:var(--bos-fs-base);
  line-height: 1;
  cursor: pointer;
  padding: 5px 7px;
  border-radius: var(--bos-radius-sm);
  opacity: 0;
  transition: opacity 0.12s ease, color 0.15s ease, background 0.15s ease;
  flex-shrink: 0;
}
.bos-sidebar:hover .bos-sidebar-pin,
body.bos-sidebar-pinned-open .bos-sidebar-pin {
  opacity: 1;
  transition: opacity 0.22s ease 0.08s, color 0.15s ease, background 0.15s ease;
}
.bos-sidebar-pin:hover { background: rgba(255,255,255,0.08); color: #fff; }
.bos-sidebar-pin.active { color: var(--bos-blue); }

.bos-sidebar .bos-nav {
  padding: 1rem !important;
}

.bos-sidebar .bos-nav > .nav-link,
.bos-sidebar .bos-nav > button.nav-link,
.bos-sidebar .bos-nav > form > button.nav-link {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 42px;
  margin: 0 0 2px;
  padding: .65rem 1.25rem .65rem 0.35rem;
  /*padding: .62rem .75rem;*/ /* by admin */
  border: 0;
  border-radius: var(--bos-radius-sm);
  /*border-radius: var(--bos-radius-md);*/ /* by admin */
  color: rgba(255,255,255,.78);
  background: transparent;
  font-size:var(--bos-fs-base);
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
}
/* Log Out is the one nav item that should visually read as a distinct
   kind of action, not blend into the same muted color as regular
   navigation - restored here specifically, since fixing the flex
   layout above (see that rule's own comment) also made this button
   correctly fall under the shared .nav-link color for the first time,
   which was never the intended look for this one item. */
.bos-sidebar .bos-nav > form > button.nav-link-logout {
  color: var(--bos-blue);
}
.bos-sidebar .bos-nav > .nav-link:hover,
.bos-sidebar .bos-nav > button.nav-link:hover,
.bos-sidebar .bos-nav > form > button.nav-link:hover {
  color: #fff;
  background: rgba(255,255,255,.08);
}
.bos-sidebar .bos-nav > .nav-link.active,
.bos-sidebar .bos-nav > button.nav-link.active {
  color: #fff;
  background: #234a83;
}
.bos-sidebar .bos-nav i {
  width: 20px;
  flex: 0 0 20px;
  text-align: center;
  font-size: var(--bos-fs-base);
}

/* Nav labels (including submenu item text and the toggle chevron)
   fade in with the same delayed timing as the brand text, so
   everything appears in sync rather than the rail visibly widening
   before its contents catch up. */
.bos-nav .nav-label {
  opacity: 0;
  transition: opacity 0.1s ease;
  white-space: nowrap;
}
.bos-sidebar:hover .bos-nav .nav-label,
body.bos-sidebar-pinned-open .bos-nav .nav-label {
  opacity: 1;
  transition: opacity 0.22s ease 0.08s;
}

.bos-nav-chevron {
  transition: transform .2s ease;
  margin-right: -0.875rem;
}
.bos-sidebar button[aria-expanded="true"] .bos-nav-chevron {
  transform: rotate(180deg);
}

/* The Property Management submenu only makes sense to show once the
   rail is wide enough to read it - force it closed while collapsed,
   regardless of its own Bootstrap collapse/show state, so it never
   awkwardly renders a wide indented list against a 68px icon rail.

   Structured as a low-specificity default plus two independent
   "show" overrides (hover, pinned) rather than a :not(:hover) rule -
   :not(:hover) has HIGHER specificity than body.bos-sidebar-pinned-open
   (pseudo-classes count the same as classes), so pinning could lose
   to "not currently hovering" the instant the mouse moved off the
   sidebar, causing the submenu to flicker open/closed while pinned. */
.bos-subnav-wrap {
  display: none !important;
}
.bos-sidebar:hover .bos-subnav-wrap,
body.bos-sidebar-pinned-open .bos-subnav-wrap {
  display: block !important;
}
.bos-sidebar .bos-subnav .nav-link {
  padding: .48rem .65rem;
  border-radius: var(--bos-radius-sm);
  color: rgba(255,255,255,.65);
  font-size:var(--bos-fs-base);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
}
.bos-sidebar .bos-subnav .nav-link:hover {
  color: #fff;
  background: rgba(255,255,255,.07);
}
.bos-sidebar .bos-subnav .nav-link.active {
  color: #fff;
  background: rgba(47,111,235,.35);
}

.bos-sidebar-user-avatar {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  border-radius: 50%;
  background: rgba(255,255,255,.12);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.bos-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  margin-left: 68px;
  transition: margin-left 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
body.bos-sidebar-pinned-open .bos-main {
  margin-left: 250px;
}

/* Hover-to-expand relies on a real hover state, which touch devices don't
   have - on small screens, fall back to the sidebar's original always-open
   behavior rather than leaving it stuck as an unusable icon-only rail. */
@media (max-width: 767.98px) {
  .bos-topbar {
    flex-wrap: wrap;
    row-gap: 8px;
  }
  .bos-topbar > div {
    flex-wrap: wrap;
    row-gap: 8px;
  }
  .bos-sidebar {
    position: fixed;
    width: 250px;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: none;
  }
  body.bos-mobile-nav-open .bos-sidebar {
    transform: translateX(0);
    box-shadow: 6px 0 28px rgba(15, 23, 41, 0.22);
  }
  .bos-mobile-nav-backdrop {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(15, 23, 41, 0.5);
    z-index: 1025;
  }
  body.bos-mobile-nav-open .bos-mobile-nav-backdrop {
    display: block;
  }
  .bos-brand-text,
  .bos-sidebar-pin,
  .bos-nav .nav-label,
  .bos-subnav-wrap {
    opacity: 1;
    display: block !important;
  }
  .bos-main {
    margin-left: 0;
  }
  body.bos-sidebar-pinned-open .bos-main {
    margin-left: 0;
  }
  /* The property detail page's tab row + its "New X" action button
     (Manage Modules, New Analysis, Add Unit, etc.) sit side by side at
     desktop width. Below this breakpoint, min-width:0 on .pm-tabbar
     (see that rule's own comment) already makes the tabs properly
     horizontally scrollable instead of visually overlapping the
     button - but scrolling tabs UNDER a fixed, brightly-colored button
     that covers part of the active tab's own label still looks and
     feels broken even when every tab is technically still reachable.
     Stacking instead - tabs get the full row to themselves, the button
     drops to its own line below - means there's nothing to scroll
     under in the first place. */
  .pm-tabbar-row {
    flex-direction: column;
    align-items: stretch !important;
    gap: 10px;
  }
}


.bos-topbar {
  background: #fff;
  border-bottom: 1px solid var(--bos-border);
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  position: sticky;
  top: 0;
  z-index: 10;
}
.bos-topbar .search input {
  border: 1px solid var(--bos-border);
  border-radius: var(--bos-radius-md);
  padding: 8px 12px;
  width: 340px;
  font-size: var(--bos-fs-base);
  background: var(--bos-bg);
}
.bos-content { padding: 22px 24px; flex: 1; }

/* ---------- Cards ---------- */
.bos-card {
  background: #fff;
  border: 1px solid var(--bos-border);
  border-radius: var(--bos-radius-md);
  padding: 18px;
  min-height: auto !important;
}
.bos-card + .bos-card { margin-top: 18px; }
.bos-card-title {
  font-size: var(--bos-fs-base);
  font-weight: 700;
  color: var(--bos-text);
  margin-bottom: 4px;
}

.bos-kpi-label { color: var(--bos-muted); font-size: var(--bos-fs-base); font-weight: 600; text-transform: uppercase; letter-spacing: .02em; flex: 1; min-width: 0; }
.bos-kpi-value { font-size: var(--bos-fs-xl); font-weight: 600; margin-top: 4px; }

/* Wraps a .bos-card in a link (KPI metrics that navigate somewhere)
   without it looking/behaving like a normal blue underlined link. */
a.bos-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: box-shadow .12s ease, transform .12s ease;
}
a.bos-card-link:hover {
  box-shadow: 0 2px 10px rgba(15, 23, 41, 0.08);
  transform: translateY(-1px);
}
a.bos-card-link:hover .bos-kpi-value { color: var(--bos-blue); }

/* ---------- Icon badges (colored square, used on KPI cards) ---------- */
.bos-icon-badge {
  width: 40px; height: 40px;
  border-radius: var(--bos-radius-md);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--bos-fs-md);
  flex-shrink: 0;
}
.bos-icon-badge-blue   { background: var(--bos-blue-light);   color: var(--bos-blue); }
.bos-icon-badge-green  { background: var(--bos-green-light);  color: var(--bos-green); }
.bos-icon-badge-orange { background: var(--bos-orange-light); color: var(--bos-orange); }
.bos-icon-badge-purple { background: var(--bos-purple-light); color: var(--bos-purple); }
.bos-icon-badge-red    { background: #fee2e2; color: var(--bos-red); }

.bos-kpi-trend { font-size: var(--bos-fs-sm); font-weight: 600; margin-top: 2px; }
.bos-kpi-trend-up   { color: var(--bos-green); }
.bos-kpi-trend-down { color: var(--bos-red); }
.bos-kpi-trend-flat { color: var(--bos-muted); font-weight: 500; }

/* ---------- Donut chart with a value centered inside the ring ---------- */
.bos-donut-wrap { position: relative; }
.bos-donut-center {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  text-align: center; pointer-events: none;
}
.bos-donut-center .value { font-size: var(--bos-fs-xl); font-weight: 800; color: var(--bos-text); line-height: 1; }
.bos-donut-center .label { font-size: var(--bos-fs-sm); color: var(--bos-muted); margin-top: 2px; }

/* ---------- Ranked bar list (e.g. "Top Properties by Rent") ---------- */
.bos-ranked-item { display: flex; align-items: center; gap: 10px; padding: 8px 0; }
.bos-ranked-rank {
  width: 22px; height: 22px; border-radius: var(--bos-radius-sm);
  background: var(--bos-bg); color: var(--bos-muted);
  font-size:var(--bos-fs-base); font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.bos-ranked-body { flex: 1; min-width: 0; }
.bos-ranked-label-row { display: flex; justify-content: space-between; font-size: var(--bos-fs-xs); margin-bottom: 3px; }
.bos-ranked-bar-track { height: 6px; border-radius: var(--bos-radius-pill); background: var(--bos-bg); overflow: hidden; }
.bos-ranked-bar-fill { height: 100%; border-radius: var(--bos-radius-pill); background: var(--bos-blue); }

/* ---------- Avatar (initials circle, for tenants/contacts without a photo) ---------- */
.bos-avatar {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--bos-blue-light); color: var(--bos-blue);
  display: flex; align-items: center; justify-content: center;
  font-size:var(--bos-fs-base); font-weight: 700; flex-shrink: 0;
}

/* ---------- Badges ---------- */
.bos-badge {
  display: inline-block;
  padding: 4px 11px;
  border-radius: var(--bos-radius-pill);
  font-size: var(--bos-fs-base);
  font-weight: 700;
  white-space: nowrap;
}
.bos-badge-lead    { background: var(--bos-blue-light);  color: var(--bos-blue); }
/* Loan detail page only (#loanDynamicArea is that page's own content
   wrapper) - left/right padding on date inputs within its .bos-card
   sections, without affecting date inputs anywhere else. */
#loanDynamicArea .bos-card input[type="date"] {
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}
.bos-badge-active  { background: var(--bos-green-light); color: var(--bos-green); }
.bos-badge-prospect{ background: var(--bos-orange-light);color: var(--bos-orange); }
.bos-badge-inactive{ background: #f3f4f6; color: var(--bos-muted); }
.bos-badge-danger  { background: #fee2e2; color: #dc2626; }
.bos-badge-purple  { background: var(--bos-purple-light); color: var(--bos-purple); }
.bos-badge-teal    { background: var(--bos-teal-light);   color: var(--bos-teal); }
.bos-badge-pink    { background: var(--bos-pink-light);   color: var(--bos-pink); }
.bos-badge-indigo  { background: var(--bos-indigo-light); color: var(--bos-indigo); }
.bos-badge-amber   { background: var(--bos-amber-light);  color: var(--bos-amber); }
.bos-badge-slate   { background: var(--bos-slate-light);  color: var(--bos-slate); }

/* ---------- Tables ---------- */
.bos-table { width: 100%; border-collapse: collapse; font-size: var(--bos-fs-base); }
.bos-table th {
  text-align: left;
  color: var(--bos-muted);
  font-weight: 600;
  font-size:var(--bos-fs-base);
  text-transform: uppercase;
  letter-spacing: .03em;
  padding: 10px 12px;
  border-bottom: 1px solid var(--bos-border);
}
.bos-table td {
  padding: 12px;
  border-bottom: 1px solid var(--bos-border);
  vertical-align: middle;
}
.bos-table tr:last-child td { border-bottom: none; }
.bos-table tr:hover { background: #fafbfd; }
.js-clickable-row { cursor: pointer; }
.js-clickable-row > td:last-child { cursor: default; }
.js-clickable-row > td:last-child a,
.js-clickable-row > td:last-child button {
  cursor: pointer;
}

/* ---------- Buttons ---------- */
.btn-bos-primary {
  background: var(--bos-blue);
  border: 1px solid var(--bos-blue);
  color: #fff;
  font-weight: 600;
  font-size: var(--bos-fs-base);
  border-radius: var(--bos-radius-md);
  padding: 8px 16px;
  white-space: nowrap;
}
/* Bootstrap's own generic .btn-check:checked + .btn rule (not just
   its outline-variant mixin) sets background/border/color from its
   own --bs-btn-active-* variables on ANY checked btn-check radio's
   label, regardless of which custom class is on it - this is what
   made a selected Payment/Deposit toggle look gray instead of this
   app's own blue, even with btn-bos-primary correctly applied. */
.btn-check:checked + .btn-bos-primary {
  background-color: var(--bos-blue) !important;
  border-color: var(--bos-blue) !important;
  color: #fff !important;
}
.btn-bos-primary:hover { background: #2660d1; color: #fff; }
.btn-bos-primary:active,
.btn-bos-primary:focus,
.btn-bos-primary:focus-visible {
  background: var(--bos-blue);
  border-color: var(--bos-blue);
  color: #fff;
  box-shadow: none;
}
.btn-bos-primary:disabled,
.btn-bos-primary.disabled {
  background: var(--bos-blue);
  border-color: var(--bos-blue);
  color: #fff;
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-bos-outline {
  background: #fff;
  border: 1px solid var(--bos-border);
  color: var(--bos-text);
  font-weight: 600;
  font-size: var(--bos-fs-base);
  border-radius: var(--bos-radius-md);
  padding: 8px 16px;
  white-space: nowrap;
}
.btn-bos-outline:hover { background: var(--bos-bg); }
.btn-bos-outline:disabled {
  background: #fff;
  border-color: var(--bos-border);
  color: var(--bos-text);
  opacity: 0.5;
}
.btn-bos-outline:active,
.btn-bos-outline:focus,
.btn-bos-outline:focus-visible {
  background: var(--bos-bg);
  border-color: var(--bos-border);
  color: var(--bos-text);
  box-shadow: none;
}

/* Add Transaction's Payment/Deposit toggle (a btn-check + label pair styled as buttons, not a real
   toggle with its own selected-state background) - the app-wide outline-button hover above changes
   its background away from white on hover, which reads as the whole button flickering/losing its
   background the moment the pointer passes over whichever one ISN'T currently selected. Scoped by
   their own for="" attributes rather than touching every .btn-bos-outline in the app.
   !important is needed for the same reason the .btn-check:checked rule right above this one needs
   it - confirmed directly, a plain (higher-specificity, even) override alone did not win here
   either, the same Bootstrap-native .btn-check behavior this file already documents overriding
   custom classes on these exact toggle buttons. */
label[for="checkTypeDebit"]:hover,
label[for="checkTypeCredit"]:hover {
  background: #fff !important;
}

/* btn-outline-danger is raw Bootstrap, used throughout the app for
   destructive actions (Delete, etc.) - same reasoning as the
   .btn-bos-* overrides above, neutralizing Bootstrap's own default
   :active/:focus border-darkening rather than letting it show
   through, since nothing here had overridden it before. */
.btn-outline-danger:active,
.btn-outline-danger:focus,
.btn-outline-danger:focus-visible {
  background: transparent;
  border-color: #dc3545;
  color: #dc3545;
  box-shadow: none;
}

/* Our custom classes set a fixed padding/font-size, which otherwise
   silently overrides Bootstrap's .btn-sm sizing wherever the two are
   combined. Match Bootstrap's own small-button scale here so btn-sm
   actually shrinks things - keeps every .btn-bos-* + .btn-sm pairing
   in the app visually consistent (Edit/Delete pairs, table row
   actions, etc.) instead of only some of them respecting btn-sm. */
.btn-bos-primary.btn-sm,
.btn-bos-outline.btn-sm {
  padding: 4px 8px;
  font-size:var(--bos-fs-base);
  border-radius: var(--bos-radius-sm);
}

/* ---------- Misc ---------- */
.bos-empty { text-align: center; padding: 48px 20px; color: var(--bos-muted); }
.bos-avatar-sm {
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--bos-blue-light); color: var(--bos-blue);
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size:var(--bos-fs-base); letter-spacing: -0.5px;
}
.bos-login-wrap {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: var(--bos-navy);
}
.bos-login-card {
  background: #fff; border-radius: var(--bos-radius-md); padding: 36px; width: 380px; max-width: calc(100vw - 32px);
}

/* ============================================================
   Property Management - Bootstrap 5.x interface
   Bootstrap handles the grid, cards, spacing, tables and controls.
   These rules only provide the module-specific dimensions/colors.
   ============================================================ */
.pm-app-bg { background:#f7f9fc; font-size:var(--bos-fs-base); }
.pm-topbar { min-height:72px; z-index:1020; }
.pm-search-wrap { width:min(100%, 430px); }
.pm-search-wrap .input-group-text,.pm-search-wrap .form-control { min-height:44px; font-size:var(--bos-fs-base); }

/* Document upload file inputs (standalone Documents page, property
   Documents tab) - a fixed 350px rather than stretching to whatever
   the flex row leaves it. */
.pm-file-input { max-width: 350px; }
.pm-file-input::file-selector-button {
  font: inherit;
  border: none;
  border-right: 1px solid var(--bos-border);
  border-radius: 0;
  background: #f3f4f6;
  color: var(--bos-text);
  cursor: pointer;
}
.min-w-0 { min-width:0; }

/* Property list/detail/work-order components that cannot be expressed by a
   single Bootstrap utility remain here in app.css. */
.pm-page-toolbar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    margin-bottom: 16px;
    gap: 10px;
}

.pm-search-small {
    height: 40px;
    width: 280px;
    border: 1px solid #e6eaf0;
    border-radius: var(--bos-radius-sm);
    display: flex;
    align-items: center;
    padding: 0 11px;
    gap: 8px;
    color: #98a2b3;
    background: #fff;
}

.pm-search-small input {
    width: 100%;
    border: 0;
    outline: 0;
    font-size:var(--bos-fs-base);
}

.pm-search-clear {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    color: #98a2b3;
    text-decoration: none;
    line-height: 1;
}
.pm-search-clear:hover { color: var(--bos-muted); }

.pm-table-card {
    background: #fff;
    border: 1px solid #e6eaf0;
    border-radius: var(--bos-radius-md);
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(16, 24, 40, .04);
}

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

.pm-table th {
    height: 46px;
    background: #fafbfc;
    color: #475467;
    font-size:var(--bos-fs-base);
    font-weight: 600;
    text-align: left;
    padding: 0 14px;
    border-bottom: 1px solid #e6eaf0;
    white-space: nowrap;
}

.pm-table td {
    height: 58px;
    padding: 8px 14px;
    border-bottom: 1px solid #eef1f4;
    font-size:var(--bos-fs-base);
    color: var(--bos-text);
    vertical-align: middle;
}

.pm-table tbody tr:last-child td {
    border-bottom: 0;
}

.pm-property-cell {
    display: flex;
    align-items: center;
    gap: 10px;
}

.pm-property-thumb {
    width: 56px;
    height: 42px;
    border-radius: var(--bos-radius-sm);
    background: linear-gradient(135deg, #dbe5f0, #b9c8dc);
    overflow: hidden;
    flex: 0 0 auto;
}

.pm-property-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.pm-property-name {
    color: var(--bos-blue);
    text-decoration: none;
    font-weight: 600;
    white-space: nowrap;
}

.pm-status {
    display: inline-flex;
    align-items: center;
    border-radius: var(--bos-radius-pill);
    padding: 5px 9px;
    font-size:var(--bos-fs-base);
    font-weight: 600;
    white-space: nowrap;
}

.pm-status.green {
    background: #eaf8ee;
    color: #1b8b43;
}

.pm-status.orange {
    background: #fff4db;
    color: #b87500;
}

.pm-status.red {
    background: #feeeee;
    color: #d83a3a;
}

.pm-status.blue {
    background: #eaf1fe;
    color: #2f6feb;
}

.pm-status.gray {
    background: #f3f4f6;
    color: var(--bos-muted);
}

/* Added alongside ContactStatusDefinition::CUSTOM_COLOR_PALETTE - a custom contact status needs a
   .pm-status modifier here too, since this page (unlike crm/contacts/show.php) uses this class
   rather than .bos-badge-*. See ContactStatusDefinition::pmStatusClassFor() for how a status's
   own color_class (lead/prospect/active/inactive/purple/teal/...) maps to one of these. */
.pm-status.purple {
    background: var(--bos-purple-light);
    color: var(--bos-purple);
}

.pm-status.teal {
    background: var(--bos-teal-light);
    color: var(--bos-teal);
}

.pm-status.pink {
    background: var(--bos-pink-light);
    color: var(--bos-pink);
}

.pm-status.indigo {
    background: var(--bos-indigo-light);
    color: var(--bos-indigo);
}

.pm-status.amber {
    background: var(--bos-amber-light);
    color: var(--bos-amber);
}

.pm-status.slate {
    background: var(--bos-slate-light);
    color: var(--bos-slate);
}

/* Vertical divider between the Deal Analyzer's cost breakdown and
   criteria tables - desktop only (they sit side by side there); the
   two columns stack on narrower screens, where no divider is shown
   at all. */
@media (min-width: 768px) {
    .pm-analysis-divider {
        border-left: 1px solid #ccc;
        padding-left: 24px;
    }
}

.pm-pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 5px;
    padding: 12px 14px;
    border-top: 1px solid #e6eaf0;
}

.pm-pagination span,
.pm-pagination a {
    width: 32px;
    height: 32px;
    border: 1px solid #e6eaf0;
    border-radius: var(--bos-radius-sm);
    display: grid;
    place-items: center;
    text-decoration: none;
    color: var(--bos-muted);
    font-size:var(--bos-fs-base);
}

.pm-pagination .active {
    background: var(--bos-blue);
    color: #fff;
    border-color: var(--bos-blue);
}

.pm-property-hero {
    display: grid;
    grid-template-columns: 260px 1fr;
    gap: 20px;
    align-items: start;
    margin-bottom: 12px;
}

.pm-property-photo {
    height: 165px;
    border-radius: var(--bos-radius-md);
    overflow: hidden;
    background: linear-gradient(135deg, #dbe5f0, #b9c8dc);
}

.pm-property-photo img {
    width: 100%;
    height: 100%;
    object-fit: fill;
}

.pm-detail-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 9px 18px;
    font-size:var(--bos-fs-base);
}

.pm-detail-columns {
    display: flex;
    gap: 0;
}

.pm-detail-columns > .pm-detail-grid {
    flex: 1;
    align-content: start;
}

.pm-detail-columns > .pm-detail-grid:first-child {
    flex: 0 0 auto;
    grid-template-columns: max-content max-content;
    padding-right: 24px;
}

.pm-detail-columns > .pm-detail-grid + .pm-detail-grid {
    padding-left: 24px;
    border-left: 1px solid #e6eaf0;
}

.pm-detail-grid dt {
    color: var(--bos-muted);
    font-weight: 700;
}

.pm-detail-grid dd {
    margin: 0;
    color: var(--bos-text);
}

/* .pm-tabbar's own distinct properties, on top of the shared tab-bar
   treatment further down this file (see that block's own comment for
   why the shared rules aren't repeated here too). */
.pm-tabbar {
    display: flex;
    overflow-x: auto;
    min-width: 0;
}
.pm-tabbar button {
    font-size:var(--bos-fs-base);
    white-space: nowrap;
}

.pm-hero-divider {
    border: 0;
    border-top: 1px solid #e6eaf0;
    margin: 16px 0;
}

.pm-detail-kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-bottom: 16px;
}

.pm-detail-kpi {
    border: 1px solid #e6eaf0;
    border-radius: var(--bos-radius-md);
    background: #fff;
    padding: 13px;
}

.pm-detail-kpi small {
    display: block;
    font-size:var(--bos-fs-base);
    line-height: 1.3;
    /* Reserves space for 2 lines always, not just however many the
       label actually needs - a short label ("Active") and a long one
       that wraps ("Pending Change Orders") end up with the same amount
       of space above their number either way, so every number in the
       same row starts at the same vertical position regardless of
       label length. Works at any card width/breakpoint since it
       doesn't depend on preventing wrapping - it just doesn't matter
       anymore whether a label wraps to 1 or 2 lines. */
    min-height: 2.6em;
    color: var(--bos-muted);
}

.pm-detail-kpi strong {
    display: block;
    font-size: var(--bos-fs-lg);
    margin-top: 3px;
}

.contractor-scorecard {
    font-size: var(--bos-fs-base);
}

.contractor-scorecard .pm-detail-kpi small {
    font-size: var(--bos-fs-sm);
}

/* For a KPI row that only ever has 3 items (Budget/Spent/Variance),
   not the base class's generic 4 - forcing repeat(4, 1fr) here wasted
   a whole column's worth of width on nothing, leaving each of the 3
   real items narrower than it needed to be. */
.pm-detail-kpis-3col {
    grid-template-columns: repeat(3, 1fr);
}

/* Same reasoning as -3col above, for a row with only 2 items (e.g.
   Budget/Spent once Stage moves to its own label above them) - each
   of the 2 real items gets a full half-width column instead of being
   squeezed into the base class's generic 4. */
.pm-detail-kpis-2col {
    grid-template-columns: repeat(2, 1fr);
}

.pm-section-heading {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 18px 0 10px;
}

.pm-section-heading h3 {
    font-size: var(--bos-fs-md);
    margin: 0;
    font-weight: 700;
}

.pm-priority {
    font-size:var(--bos-fs-base);
    border-radius: var(--bos-radius-pill);
    padding: 4px 8px;
    white-space: nowrap;
}

.pm-priority.high {
    background: #feecec;
    color: #d93434;
}

.pm-priority.medium {
    background: #fff2d6;
    color: #b66c00;
}

.pm-priority.low {
    background: #edf2f7;
    color: #526273;
}

.pm-empty {
    padding: 45px;
    text-align: center;
    color: var(--bos-muted);
    font-size:var(--bos-fs-base);
}

@media (max-width: 991.98px) {
    .pm-property-hero {
        grid-template-columns: 1fr;
    }

    .pm-property-photo {
        height: 250px;
    }

    .pm-table-card {
        overflow-x: auto;
    }

    .pm-table {
        min-width: 850px;
    }

    .pm-detail-kpis {
        grid-template-columns: 1fr 1fr;
    }

    .pm-detail-kpis-3col {
        grid-template-columns: 1fr 1fr;
    }
}

@media (max-width: 575.98px) {
    .pm-page-toolbar {
        align-items: stretch;
        flex-direction: column;
    }

    .pm-search-small {
        width: 100%;
    }

    .pm-detail-kpis {
        grid-template-columns: 1fr;
    }

    .pm-detail-kpis-3col {
        grid-template-columns: 1fr;
    }
}

.pm-panel {
    background: #fff;
    border: 1px solid #e6eaf0;
    border-radius: var(--bos-radius-md);
    box-shadow: 0 1px 3px rgba(16, 24, 40, .04);
    padding: 18px;
}

.pm-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}

.pm-panel-title {
    font-size: var(--bos-fs-md);
    font-weight: 700;
}

.pm-dashboard-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}

/* Work Order Details' own weighted 2-column layout (details panel
   wider than the status panel) - previously an inline style on the
   page itself, which meant it could never respond to a media query at
   all and stayed 2-column even on a narrow phone, each column cramped
   into a sliver. A named class can be overridden below like any other
   rule; an inline style can't. */
.pm-dashboard-row-detail {
    grid-template-columns: 1.7fr .8fr;
}
@media (max-width: 767.98px) {
    .pm-dashboard-row-detail {
        grid-template-columns: 1fr;
    }
}

.pm-activity-meta {
    font-size:var(--bos-fs-base);
    color: var(--bos-muted);
}

/* ---------- Flex text overflow fix (Property Management) ----------
   Flex items default to min-width:auto, meaning they refuse to shrink
   below their content's natural width - so a long line of text (a KPI
   trend, an activity description, a task title) pushes right past its
   card boundary instead of wrapping. min-width:0 is the standard fix.
   This only affects the LAST child of a flex row (the text block that
   follows a fixed-size icon) - icons keep their explicit sizing since
   min-width:0 doesn't override flex:0 0 auto or an explicit width. */
.card-body .d-flex,
.pm-panel .d-flex {
  min-width: 0;
}
.card-body .d-flex > div:last-child,
.pm-panel .d-flex > div:last-child {
  min-width: 0;
  flex: 1 1 auto;
}
.card-body .d-flex > div:last-child > div,
.pm-panel .d-flex > div:last-child > div {
  white-space: normal;
  word-break: break-word;
}

/* Collapse toggle chevrons (Property Details page: Overview / Details
   sections) - same rotate-on-expand affordance as the sidebar's
   collapsible nav groups (.bos-nav-chevron), scoped separately since
   these buttons live outside the sidebar. */
.pm-collapse-toggle .bi-chevron-down {
  transition: transform .2s ease;
}
.pm-collapse-toggle[aria-expanded="true"] .bi-chevron-down {
  transform: rotate(180deg);
}

/* Prevents a child's margin (.pm-property-hero's margin-bottom;
   .pm-hero-divider/.pm-tabbar's margin-top) from collapsing through
   the panel edge, which otherwise makes Bootstrap's collapse-animation
   height measurement briefly wrong right as you click - visible as a
   settle/snap just before the smooth animation. */
#propertyHeroSection,
#propertyDetailsSection {
  overflow: hidden;
}

/* Dashboard drag-and-drop (SortableJS) - the ghost element left behind
   in the original position while dragging. */
/* Bootstrap's own default input/select border (#ced4da) is a
   different, slightly darker gray than --bos-border (#e5e7eb), which
   every custom-styled control in the app (the custom-select trigger,
   check-line inputs, etc.) already uses - without this, a plain
   <input> or native <select> sits right next to a custom-select
   dropdown with a visibly different border color, even though both
   are meant to look like part of the same form. */
.form-control,
.form-select {
  border-width: 1px;
  border-color: var(--bos-border);
}

/* Bootstrap's own default :focus color is a different blue entirely
   from this app's --bos-blue (its own default primary, not this
   app's palette) - without this, a native input/select's focus ring
   looked visibly different from the custom-select trigger's own
   :focus style just below, even though both share the same resting
   border. */
.form-control:focus,
.form-select:focus {
  border-color: var(--bos-blue);
  box-shadow: 0 0 0 0.2rem var(--bos-blue-light);
}

/* Bootstrap's own default checkbox border (#dee2e6) is too light
   against this app's background - barely visible, especially for an
   unchecked box. A couple of shades darker so every checkbox stays
   clearly visible, app-wide. */
/* Bootstrap's own default .dropdown-item:hover/:focus/:active/.active
   sets a solid blue background with white text on ANY dropdown item,
   app-wide - this clashes badly with a colored item like a red
   "Delete" action (text-danger has enough specificity to survive,
   so the red text ends up sitting on a blue background instead of
   turning white, which reads as broken/jarring). Replaced with the
   same light gray hover this app already uses elsewhere (table rows,
   etc.), and deliberately does not touch color at all, so every
   dropdown item's own text color - red for Delete, the default dark
   for everything else - stays exactly as authored regardless of
   hover/focus/active state. */
.dropdown-item:hover,
.dropdown-item:focus,
.dropdown-item:active,
.dropdown-item.active {
  background-color: #fafbfd;
}
.dropdown-item:not(.text-danger):hover,
.dropdown-item:not(.text-danger):focus,
.dropdown-item:not(.text-danger):active,
.dropdown-item.active:not(.text-danger) {
  color: var(--bos-text);
}

.form-check-input {
  border-color: #adb5bd;
}
.form-check-input:not(:checked):hover {
  border-color: #6c757d;
}

/* Contact form's Trades checklist - 4 columns wide, dropping to 2 (14
   total trades divides evenly, 2x7 with no leftover slots) at the lg
   breakpoint rather than md - this needs to trigger across tablets and
   smaller laptops too, not just phone-width screens, or it never
   actually narrows on the devices this was meant to fix. */
.bos-trades-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px 16px;
}
@media (max-width: 991.98px) {
  .bos-trades-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.bos-drag-ghost {
  opacity: 0.4;
}
/* Swap mode (per dashboard group) highlights whichever card - or
   empty placeholder slot - you're currently hovering over as the
   swap target. */
.bos-drag-swap-target {
  outline: 2px dashed var(--bos-blue);
  outline-offset: 2px;
}

/* Dashboard group sections - CRM Dashboard, Loan Dashboard, etc. Each
   is a collapsible section with its own header (name + chevron) and a
   row of cards below it. Matches .bos-card-title's own font-size and
   weight for visual consistency with every other section heading in
   the app, rather than introducing a new heading style just for this. */
.bos-dashboard-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 4px 0;
  margin-bottom: 20px;
  user-select: none;
}
.bos-dashboard-group-title {
  font-size: var(--bos-fs-base);
  font-weight: 700;
  color: var(--bos-text);
}
.bos-group-chevron {
  color: var(--bos-muted);
  font-size:var(--bos-fs-base);
  transition: transform 0.15s ease;
}
.bos-dashboard-group-header:hover .bos-dashboard-group-title {
  color: var(--bos-blue);
}

[data-widget-id] {
  cursor: grab;
}
[data-widget-id]:active {
  cursor: grabbing;
}
/* Reporting cards keep data-widget-id (still needed to target a
   specific card for hide/show), but have no drag support at all - the
   grab cursor above would be actively misleading here, so it's reset
   back to default for this page's own, differently-named card class. */
.bos-report-slot,
.bos-report-slot:active {
  cursor: default;
}

/* Header search dropdown results - clicking a result is a real link
   navigating away, and Bootstrap's default .dropdown-item:active
   background (blue, matching the theme's primary color) briefly renders
   during the moment between mousedown and the browser actually
   processing the navigation, reading as a "flash." No hover feedback
   either, per explicit preference. !important because app.css loads
   after Bootstrap's own CSS and this needs to reliably win regardless
   of whether Bootstrap's own rule happens to carry !important itself.
   Scoped to this one class so it never touches any other dropdown
   elsewhere in the app. */
.bos-search-result-item:hover,
.bos-search-result-item:focus,
.bos-search-result-item:active {
  background-color: transparent !important;
  color: inherit !important;
}

/* Anchors the absolutely-centered header search bar below (see
   .bos-header-search-wrap) - without this, "position:absolute" on the
   search wrapper would center against the nearest positioned ancestor
   further up the DOM (likely <body>), not the header itself. */
.pm-topbar {
  position: relative;
  container-type: inline-size;
}

/* Without this, a long page title (a contact's full name, a long
   property address) can't shrink below its own natural text width -
   flex items default to min-width:auto, refusing to shrink at all -
   which pushes the fixed-size icon group on the right past the
   viewport edge and forces the entire page to scroll horizontally on
   narrow screens. Truncating the title instead keeps the header (and
   the page) within the viewport regardless of how long the heading
   text is. */
.pm-topbar > div:first-child {
  min-width: 0;
  flex: 1 1 auto;
}
.pm-topbar h1 {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Keeps the header search bar visually centered on the header's own
   full width, regardless of how long the page title to its left is -
   the title and the icon area on the right have no matching width
   constraint (deliberately, since page titles vary a lot: "Search" vs
   "Property Management Onboarding"), so centering the search bar
   within the LEFTOVER flex space between them (the old approach) meant
   its position visibly shifted every time the title's length changed
   between pages. Absolute-centering against the header's own width
   sidesteps that entirely.

   Sized in cqw (container query width, against .pm-topbar's own
   container-type: inline-size above), not vw - vw is the raw browser
   viewport, which has no idea how much of that width the sidebar is
   currently taking (68px collapsed vs 250px pinned open), so a
   vw-based width would size the search bar the same either way and
   could run into the icon group once the sidebar took a bigger enough
   bite out of the header's own actual space. cqw always reflects the
   header's own real width after that's already been subtracted.

   The explicit max-width below is a second, harder safeguard on top
   of that - reserves genuine space for the icon group (up to 5 icons
   including the optional platform-admin one, ~220px) and a reasonable
   minimum for the title (~180px), so the search bar's own width is
   capped well before it could ever reach either one, not just
   generally smaller on average. */
.bos-header-search-wrap {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: clamp(220px, 38cqw, 420px);
  max-width: calc(100cqw - 400px);
  padding: 0 16px;
}

/* With the sidebar pinned open (250px, vs 68px collapsed), the header
   has genuinely less of its own width to work with at any given
   browser width - the search bar's own max-width above already keeps
   it clear of the icons, but below this point there just isn't
   enough room left for a search bar to be usefully wide at all, so it
   hides here instead of shrinking further. The collapsed-sidebar case
   isn't touched by this - it keeps using the existing d-md-flex
   breakpoint (768px) as its own, only hide condition, since 68px
   leaves the header enough width to not need this earlier cutoff. */
@media (max-width: 935px) {
  body.bos-sidebar-pinned-open .bos-header-search-wrap {
    display: none !important;
  }
}

/* Platform admin's Tenants search field - 100% width below 525px,
   30% at 525px and above. Bootstrap's own standard, prebuilt CDN
   distribution (what this app loads) has no native responsive width
   utility for this - w-sm-auto and similar only exist if Bootstrap's
   own Sass source is customized and recompiled with responsive
   sizing enabled, which this app doesn't do - so this is a small,
   custom class instead, following Bootstrap's own !important
   convention for utility-style overrides. */
.pm-org-search-input {
  width: 100% !important;
}
@media (min-width: 525px) {
  .pm-org-search-input {
    width: 30% !important;
  }
}

/* Messages/SMS tab rows on a contact's own page - a clear, visible
   divider between one message and the next (not after the last one,
   since that would just be a redundant line against whatever content
   follows), and a badge sized to match the row's own small text
   instead of the badge's normal, larger global size, which was
   inflating this row's own height well beyond the rest of its
   content. */
.pm-message-row {
  border-bottom: 1px solid var(--bos-border);
}
.pm-message-row:last-child {
  border-bottom: none;
}
.pm-message-badge {
  font-size: var(--bos-fs-base) !important;
  padding: 2px 8px !important;
}

/* Global "placeholder-style" treatment for any <select> whose currently-
   selected option has an empty value (e.g. "— None —", "— Not Specified —").
   Dims it the same way a text input's own placeholder text is dimmed,
   without needing every dropdown across the app to opt in individually. */
select:has(option[value=""]:checked) {
  opacity: 0.4;
}
select:has(option[value=""]:checked):focus {
  opacity: 1;
}

/* Widens the left/right breathing room inside every modal's body -
   Bootstrap's own default is 1rem on all four sides; this only touches
   left/right, leaving top/bottom at the default. Not yet applied to
   .modal-header or .modal-footer - pending how this looks first. */
/* Widens the Itemize Rehab Estimate modal to about 75% of the viewport
   on screens large enough for that to make sense - its own table has 7
   columns, cramped at Bootstrap's default .modal-xl width. Scoped to
   this breakpoint specifically so phones/small tablets keep Bootstrap's
   own mobile-friendly modal sizing (near-full-width, small margins)
   instead of being forced into a 75vw-wide dialog that would be far too
   narrow for this table on a small screen. */
@media (min-width: 768px) {
  .pm-itemize-rehab-dialog {
    max-width: 75vw;
  }
}

.modal-body {
  padding-left: 2rem;
  padding-right: 2rem;
}

/* A mobile fullscreen modal's footer was showing the page/backdrop
   through it instead of a solid background - Bootstrap's own
   .modal-header/.modal-footer never set their own background color,
   they only ever inherited .modal-content's, so this makes it
   explicit and guaranteed regardless of breakpoint. .modal-body never
   got the same treatment - same root cause, same fix, found later:
   a long Add Transaction split-row list (many waiting payments added
   at once) scrolling inside a mobile-fullscreen modal showed the same
   page/backdrop bleed-through behind the rows once scrolled, for the
   exact same reason. */
.modal-header,
.modal-footer,
.modal-body {
  background-color: #fff;
}

/* Shared tab treatment, used everywhere the app has a row of tabs
   (Contact/Property detail, Settings, Platform, Loans, Accounting,
   Portal pages) - previously just an underline on the active item,
   which read as a plain row of links rather than a set of tabs. The
   bar itself is a light track; each tab is its own block within it,
   with the active one lifted off the track by a filled background
   and shadow rather than only a colored line, so the boundary between
   tabs (and which one is selected) is visible without hovering. */
/* Shared tab-bar treatment - one definition covering every tab bar in
   the app (Contact/Property detail, Settings, Platform, Loans,
   Accounting, Portal pages, and the Property Management submenu's own
   .pm-tabbar further up this file) rather than the same rules
   previously repeated near-verbatim under three different class
   names. .pm-tabbar's own selectors point back to these same rules -
   see that class's own (now much shorter) definition. */
.nav-tabs,
.bos-accounting-tabs,
.pm-tabbar {
  border-bottom: 0;
  background: var(--bos-bg);
  border-radius: var(--bos-radius-md);
  padding: 4px;
  gap: 2px;
  flex-wrap: wrap;
}
.nav-tabs .nav-link,
.bos-accounting-tabs .nav-link,
.pm-tabbar button {
  border: 1px solid var(--bos-border);
  border-radius: var(--bos-radius-sm);
  color: var(--bos-muted);
  font-weight: 500;
  padding: 0.5rem 0.875rem;
  background: transparent;
}
.nav-tabs .nav-link:hover,
.bos-accounting-tabs .nav-link:hover,
.pm-tabbar button:hover {
  color: var(--bos-text);
  background: rgba(255, 255, 255, 0.6);
}
.nav-tabs .nav-link.active,
.bos-accounting-tabs .nav-link.active,
.pm-tabbar button.active {
  color: var(--bos-blue);
  font-weight: 600;
  background: #fff;
  border-color: #fff;
  box-shadow: 0 1px 2px rgba(15, 23, 41, 0.08);
}

/* Property Details' sub-nav (Units/Leases/Maintenance/... within a
   selected top-level group) - deliberately NOT .pm-tabbar's boxed/pill
   look, so a group's own sub-tabs read as a distinct, subordinate
   level rather than looking identical to the primary tabs above them.
   Plain text links separated by a vertical rule, underline-on-active
   instead of a filled background - a lighter-weight treatment that
   still makes the current selection obvious without competing
   visually with the primary tab row. Shared by both Property Details
   (buttons, client-side tab switching) and Accounting (real <a>
   links, real page navigations) - same look either way, so both
   element types are targeted throughout. */
.pm-subnav-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  padding: 2px 4px;
}
.pm-subnav-links button,
.pm-subnav-links a {
  border: 0;
  background: transparent;
  color: var(--bos-muted);
  font-weight: 500;
  font-size: var(--bos-fs-base);
  padding: 0.375rem 1.5rem;
  border-bottom: 2px solid transparent;
  text-decoration: none;
}
.pm-subnav-links button:hover,
.pm-subnav-links a:hover {
  color: var(--bos-text);
}
.pm-subnav-links button.active,
.pm-subnav-links a.active {
  color: var(--bos-blue);
  font-weight: 600;
  border-bottom-color: var(--bos-blue);
}
/* The "|" separators - a real border rather than a generated content
   character, so they never accidentally get selected/copied along
   with the tab labels, and so they cleanly disappear after the last
   button without needing an extra :last-child override. */
.pm-subnav-links button + button,
.pm-subnav-links a + a {
  border-left: 1px solid var(--bos-border);
}

/* Accounting's own sub-nav (Chart of Accounts/Journal/... within a
   selected group) - white background, scoped to this page only so
   Property Details' identical-looking sub-nav (which shares the same
   .pm-subnav-links base) is untouched. Previously had no explicit
   background at all - transparent, showing the page's own light-gray
   body background (--bos-bg) through it instead. */
.bos-accounting-subnav {
  background: #fff;
}

.accordion-button {
  font-weight: 700;
  background-color: #2660d1;
  color: #fff;
}
.accordion-button.collapsed {
  background-color: #2660d1;
  color: #fff;
}
.accordion-button:not(.collapsed) {
  background-color: #2660d1;
  color: #fff;
  box-shadow: none;
}
.accordion-button:hover {
  background-color: var(--bos-blue);
  color: #fff;
}
/* Bootstrap's own arrow icon is a dark SVG by default - invisible
   against the new blue background without this, in both the
   collapsed and expanded state alike. */
.accordion-button::after {
  filter: brightness(0) invert(1);
}
.accordion-button:focus {
  box-shadow: none;
  border-color: rgba(0, 0, 0, 0.125);
}

/* Marks every required field's label with a red asterisk, app-wide -
   a single global rule rather than hand-editing every form, so any
   future required field gets this automatically too. Two forms
   because a required field isn't always the label's own immediate
   next sibling - a currency field wraps its <input required> inside
   an .input-group container (for the "$" prefix), putting that
   wrapper, not the input itself, right after the label. The first
   selector covers the direct case; the second covers a required field
   nested inside whatever comes right after the label. */
label:has(+ [required])::after,
label:has(+ * [required])::after {
  content: " *";
  color: var(--bs-danger, #dc3545);
  font-weight: 700;
}

/* Suppresses the auto-generated required-field asterisk above for a
   label that's an intentional invisible spacer (e.g. an &nbsp;
   placeholder used only to align a field with a real label in an
   adjacent column) - without this, the asterisk rule above still
   fires off the required field that follows, leaving a red "*"
   floating with no visible label text next to it. */
label.js-no-required-asterisk::after {
  content: none !important;
}

/* Custom-styled replacement for every native <select> in the app - see
   initCustomSelects() in app.js. Sized/bordered to match Bootstrap's
   own .form-select/.form-control look, so it's visually consistent
   with every other input in the app, not just select elements. */
/* A small red circle with a white number inside, anchored to the
   top-right corner of whatever icon/link it's placed inside (which
   needs position:relative) - the same "app badge" style used for
   unread counts wherever they appear (header, sidebar, per-row icons). */
.bos-notification-badge {
  position: absolute;
  top: -8px;
  right: -12px;
  background: #dc3545;
  color: #fff;
  border-radius: 50%;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  min-width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 3px;
  box-sizing: border-box;
}

/* Same red, same anchor convention as .bos-notification-badge above,
   but smaller and number-less - for "there are comments, all already
   read" rather than "here's how many are new". */
.bos-notification-dot {
  position: absolute;
  top: -2px;
  right: -6px;
  width: 8px;
  height: 8px;
  background: #dc3545;
  border-radius: 50%;
}

/* Underlined-only input, matching a real check's blank lines (Pay to
   the Order Of, Date, Check #, Memo, Amount) - used only within the
   Add Transaction "check" form, not a general-purpose input style. */
/* Smooth show/hide for the Add Transaction form's single-vs-split
   toggle - d-none (display:none) can't be transitioned at all, it's
   an instant on/off, which is what made that toggle feel jerky.
   max-height needs a real target value to animate to/from (not
   "auto"), so 400px stands in as "tall enough for this content" -
   overflow:hidden clips anything while collapsed. */
/* When this modal's content height changes (split expanding or
   collapsing), the dialog box's own position on the page can shift -
   this eases that shift smoothly instead of it snapping instantly,
   matching the same smooth feel already applied to the content. */
/* Browser scroll anchoring (a built-in Chrome/Firefox feature meant
   to prevent jarring jumps by auto-adjusting scroll position when
   content shifts above the viewport) can itself cause an unexpected
   snap once a tracked, animated layout shift - the split section's
   height transition - finishes. Disabling it here removes that as a
   possible cause of the modal box appearing to jump. */
#addTransactionModal {
  overflow-anchor: none;
}

.bos-collapsible {
  overflow: hidden;
  max-height: 400px;
  padding-top: 6px;
  opacity: 1;
  transition: max-height 0.25s ease, opacity 0.2s ease;
}
/* The 6px top padding above is meant for .bos-collapsible's original use (an alert banner that
   wants a little breathing room above it) - an .accordion-item has none of that need, since its
   own .accordion-body already carries its own internal padding, and the extra 6px here only
   showed up as an unwanted gap before the accordion header button. Scoped narrowly so the alert
   banner use case keeps its padding untouched. */
.accordion-item.bos-collapsible {
  padding-top: 0;
  /* .bos-collapsible's own 400px cap (above) was sized for its original, shorter use case (an
     alert banner) - Contractor's Profile, with its full trades grid plus every field below it,
     is genuinely taller than that, so the cap was truly clipping real content once expanded, not
     just shaping the transition. A max-height trick still needs a finite number to animate
     smoothly (auto/none can't transition at all), so this raises the ceiling generously instead
     of removing it - comfortably past anything this section's actual content reaches. */
  max-height: 2000px;
}
.bos-collapsible.is-collapsed {
  max-height: 0;
  opacity: 0;
  /* !important is required, not optional, on all of padding/margin/border here - Bootstrap's
     own spacing utilities (.py-2/.px-3/.mb-3/etc, used on this element alongside
     .bos-collapsible) set spacing with !important by design, and .alert's own border is a
     separate component style neither of these targets - all three need to be beaten/zeroed the
     same way, or a "collapsed" element still renders a real, clickable, visible gap a few
     pixels tall instead of genuinely nothing. Confirmed directly via real box-model inspection,
     twice - first the padding+border (8+8+1+1=18px), then the still-remaining margin-bottom
     (16px, from mb-3) neither of those touched. */
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  margin-bottom: 0 !important;
  margin-top: 0 !important;
  border-width: 0 !important;
  pointer-events: none;
  transition: max-height 0.2s ease, opacity 0.15s ease, padding 0.2s ease, margin 0.2s ease;
}

/* Check form's subtle background pattern - a faint herringbone weave,
   evocative of the fine engraved patterns real checks use, without
   drawing attention to itself. Transparency is baked into the
   gradient lines' own rgba color (--bos-orange at 7% alpha) rather
   than applied via opacity on the element - opacity would have faded
   the check's own text and field lines sitting on top of it too,
   not just the decorative pattern underneath them. */
.bos-check-bg-pattern {
  background-color: var(--bos-orange-light);
  background-image:
    repeating-linear-gradient(45deg, rgba(245, 158, 11, 0.07) 0px, rgba(245, 158, 11, 0.07) 1px, transparent 1px, transparent 8px),
    repeating-linear-gradient(-45deg, rgba(245, 158, 11, 0.07) 0px, rgba(245, 158, 11, 0.07) 1px, transparent 1px, transparent 8px);
  background-size: 16px 16px;
  border: 1px solid var(--bos-orange);
  border-radius: var(--bos-radius-md);
}

.bos-check-line-input {
  border: none;
  border-bottom: 1px solid var(--bos-text);
  border-radius: 0;
  background: transparent;
  padding-left: 0;
  padding-right: 0;
}
.bos-check-line-input:focus {
  border-bottom-color: var(--bos-blue);
  box-shadow: none;
  background: transparent;
}

/* Matches the classic cursive look real paper checks print "Pay to
   the Order Of" in - a script font reads wrong in all-caps or at
   small form-label sizes, hence the larger size and no letter-spacing
   here despite this being a <label>. */
.bos-check-script-label {
  font-family: 'Dancing Script', cursive;
  font-size: 1.5rem;
  /*font-size: 1.125rem;*/ /* by Admin */
  font-weight: 600;
}

/* Date and Amount specifically (not Check #, Pay to the Order Of, or
   Memo) - a small left inset on top of the shared check-line style
   above. */
.bos-check-line-input-inset {
  padding-left: 0.5rem;
}

.bos-custom-select {
  position: relative;
}
.bos-custom-select-trigger {
  width: 100%;
  text-align: left;
  background: #fff;
  border: 1px solid var(--bos-border);
  border-radius: var(--bos-radius-sm);
  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
  font-size: var(--bos-fs-base);
  font-family: inherit;
  color: var(--bos-text);
  position: relative;
  cursor: pointer;
  /* A plain <button> can still carry browser/OS-specific default
     appearance (shadows, outlines, rendering quirks) even with an
     explicit border set - a native <input>/<select> never has to
     contend with this. Resetting it here is what makes the two
     genuinely match, rather than merely specifying the same border
     values on paper. */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  box-shadow: none;
}
/* A custom-select trigger used for a check-style field (the payee picker on Add/Edit Transaction,
   see .js-payee-select) needs to look exactly like the plain .bos-check-line-input fields next to
   it - an underline only, no box - but .bos-custom-select-trigger's own white background/full
   border above (same specificity, later in this file, so it normally wins) was overriding that.
   Scoped to this one combination so every other custom-select trigger elsewhere in the app keeps
   its normal boxed look untouched. */
.bos-custom-select-trigger.bos-check-line-input {
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--bos-text);
  border-radius: 0;
  padding-bottom: 9px;
}
.bos-custom-select-trigger.bos-check-line-input:focus {
  border-bottom-color: var(--bos-blue);
  box-shadow: none;
}
.bos-custom-select-trigger-text {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bos-custom-select-trigger::after {
  content: "";
  position: absolute;
  right: 0.75rem;
  top: 50%;
  width: 10px;
  height: 10px;
  transform: translateY(-65%) rotate(45deg);
  border-right: 2px solid #666;
  border-bottom: 2px solid #666;
  pointer-events: none;
}
.bos-custom-select-trigger:focus {
  outline: none;
  border-color: var(--bos-blue);
  box-shadow: 0 0 0 0.2rem var(--bos-blue-light);
}
.bos-custom-select-disabled .bos-custom-select-trigger {
  background: #e9ecef;
  cursor: not-allowed;
  opacity: 0.75;
}
/* Matches the same placeholder show/hide behavior every native
   ::placeholder gets (see the global rule near the top of this file) -
   the custom trigger is a styled div/button standing in for a real
   <select>, so it never inherited that behavior on its own. Only
   applies when the trigger is currently showing the placeholder-like
   empty-value option (see the is-placeholder class in
   initCustomSelects()), never a real selected value. */
.bos-custom-select-trigger.is-placeholder .bos-custom-select-trigger-text {
  opacity: 0.4;
}
.bos-custom-select-trigger.is-placeholder.is-open .bos-custom-select-trigger-text {
  opacity: 0;
}
/* The panel is what's actually shown/positioned/moved to <body> now (see initCustomSelects()) -
   wraps the optional search box and the option list together as one unit, so they move and
   position as a pair instead of the search box being left behind while only the list relocates. */
.bos-custom-select-panel {
  display: none;
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  z-index: 2000;
  min-width: 100%;
  width: max-content;
  max-width: 90vw;
  background: #fff;
  border: 1px solid var(--bos-border);
  border-radius: var(--bos-radius-sm);
  box-shadow: 0 4px 12px rgba(15, 23, 41, 0.12);
}
.bos-custom-select-panel.show {
  display: block;
}
.bos-custom-select-search {
  display: block;
  width: 100%;
  box-sizing: border-box;
  border: none;
  border-bottom: 1px solid var(--bos-border);
  border-radius: var(--bos-radius-sm) var(--bos-radius-sm) 0 0;
  padding: 0.5rem 0.75rem;
  font-size: var(--bos-fs-base);
  font-family: inherit;
  color: var(--bos-text);
}
.bos-custom-select-search:focus {
  outline: none;
  background: var(--bos-blue-light);
}
/* The app's global :focus::placeholder rule (near the top of this file) hides a placeholder the
   instant its input is focused - correct for a normal field, which the person clicked into
   themselves, so they already saw the hint first. This search box auto-focuses itself the moment
   the dropdown opens (see initCustomSelects() in app.js), before the person has had any chance to
   read it - so that global rule was hiding "Search..." before anyone could ever actually see it.
   Overridden here, narrowly, so this one input keeps its placeholder visible while focused; every
   other input in the app keeps the original hide-on-focus behavior untouched. */
.bos-custom-select-search:focus::placeholder {
  opacity: 0.4 !important;
}
.bos-custom-select-search:focus::-webkit-input-placeholder {
  opacity: 0.4 !important;
}
.bos-custom-select-search:focus::-moz-placeholder {
  opacity: 0.4 !important;
}
.bos-custom-select-search:focus:-ms-input-placeholder {
  opacity: 0.4 !important;
}
.bos-custom-select-list {
  max-height: 240px;
  overflow-y: auto;
  margin: 0;
  padding: 4px 0;
  list-style: none;
}
.bos-custom-select-empty {
  padding: 0.5rem 0.75rem;
  font-size: var(--bos-fs-base);
  color: var(--bos-muted);
  cursor: default;
}
.bos-custom-select-option {
  padding: 0.5rem 0.75rem;
  font-size: var(--bos-fs-base);
  cursor: pointer;
  white-space: nowrap;
}
.bos-custom-select-option:hover,
.bos-custom-select-option:focus {
  background: var(--bos-blue-light);
  outline: none;
}
.bos-custom-select-option.active {
  font-weight: 600;
  color: var(--bos-blue);
}

/* What Needs Attention (dashboard) - grows past its default col-lg-8 width when its own content
   (a long item line, e.g. "Contractor requested a $12,400 draw (3 requests) at...") genuinely
   needs more room, instead of wrapping awkwardly inside a fixed-fraction column while the rest of
   the row sits empty. max-width:100% lifts col-lg-8's own 66.67% cap so it CAN grow; min-width
   keeps that same 66.67% as a floor so it never shrinks smaller than it already was. flex:0 1 auto
   means it sizes to its own content rather than stretching to fill the row by default - only
   below lg, left as col-lg-8's own plain 100%-width behavior, unchanged. */
@media (min-width: 992px) {
  .bos-attention-widget-col {
    flex: 0 1 auto;
    width: fit-content;
    max-width: 100%;
    min-width: 66.66666667%;
  }
}

/* The "⋮" dropdown-trigger button used app-wide in place of the old "Actions" button (every row
   action menu, the Chart of Accounts page, etc.) - one shared class rather than an inline
   font-size repeated across 30+ buttons in 18 separate files. */
.bos-actions-dots {
  font-size: var(--bos-fs-lg);
}
/* Was fought here in CSS first (padding + a compensating negative margin) - didn't actually work,
   since Bootstrap's spacing utilities (.p-0 included) are defined with !important, which no
   same-specificity rule without !important of its own can override regardless of source order.
   The real fix was removing p-0 from the markup itself (every .bos-actions-dots button, app-wide)
   instead - without it, Bootstrap's own default .btn-sm padding already gives this a reasonable,
   standard hit area on its own, so nothing further needs adding here at all. */

/* A long value (an email address, most often) that would otherwise overflow its row or wrap
   awkwardly inside a narrow card - truncates with "..." once it runs out of room, with the full,
   untruncated value available as a native hover tooltip (title attribute, set alongside this
   class wherever it's used - no JS needed for either the truncation or the tooltip). Shared and
   reusable, not specific to Contact Information's own Email row. */
.bos-truncate-tooltip {
  display: inline-block;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
