/* =============================================================================
   V2 Theme — layout positioning only. BS5 handles everything else.
   ============================================================================= */

body.theme-v2 { overflow: hidden; }

:root {
    --v2-sidebar-width: 240px;
    --v2-sidebar-collapsed-width: 60px;
    --v2-topnav-height: 46px;
    --v2-statusbar-height: 28px;
    --v2-primary: #4a90d9;
    /* Hover/active shades of the primary — derived so they re-resolve when the
       theme picker overrides --v2-primary. The SINGLE source for every V2
       primary button's interaction states (all V2 buttons are Bootstrap .btn). */
    --v2-primary-hover:  color-mix(in srgb, var(--v2-primary) 82%, #000);
    --v2-primary-active: color-mix(in srgb, var(--v2-primary) 72%, #000);
    --v2-primary-soft:   color-mix(in srgb, var(--v2-primary) 16%, #fff); /* tints: active rail, ghost hover */
    --v2-sidebar-bg: #1e293b;

    /* ── Surface / field palette — SHARED SINGLE SOURCE OF TRUTH ──────────
       The container is a tinted "chrome" surface; editable fields are pure
       white so they read as the place to type (vs. a gray fill, which reads
       as disabled). Read-only fields stay tinted. Consumed by BOTH the V2
       modals (.v2-modal) and the V2 Settings pages (.setv2-*). Retune the
       whole V2 form palette HERE — do not hardcode these colors in rules. */
    --v2-surface:        #f6f7f9;   /* container chrome (modal body / cluster) */
    --v2-field-bg:       #fff;      /* editable input & select background      */
    --v2-field-bg-focus: #fff;      /* focused field (kept white)              */
    --v2-readonly-bg:    #e9ebef;   /* non-editable display fields             */

    /* ── V2 BUTTON SYSTEM — SINGLE SOURCE OF TRUTH ───────────────────────
       Color + shape for EVERY V2 button. There is one button system — the
       Bootstrap .btn-* variants, used by modals, list pages, family detail,
       AND Settings. Change a button's look for the whole app HERE. */
    --v2-btn-radius: 8px;           /* shape — shared by both button systems  */

    /* Danger (delete / destructive) */
    --v2-danger:        #c8322f;
    --v2-danger-hover:  color-mix(in srgb, var(--v2-danger) 86%, #000);
    --v2-danger-soft:   #fce8e7;    /* outline-danger hover fill              */

    /* Neutral / secondary (the default "Cancel" / plain button) */
    --v2-btn-neutral-bg:           #fff;
    --v2-btn-neutral-bg-hover:     #f1f3f6;
    --v2-btn-neutral-border:       #d3d6dc;
    --v2-btn-neutral-border-hover: #8a909c;
    --v2-btn-neutral-text:         #1a1d22;

    /* ── Neutral palette — text, lines, page tone, radius ─────────────────
       Promoted from the former Settings-only --setv2-* block so any V2
       surface can reuse them. Single source — do not redefine per page. */
    --v2-ink:         #1a1d22;   /* primary text                            */
    --v2-ink-2:       #5b6270;   /* secondary text                          */
    --v2-ink-3:       #8a909c;   /* tertiary / muted text                   */
    --v2-line:        #e6e8ec;   /* hairline divider / border               */
    --v2-line-strong: #d3d6dc;   /* stronger divider / control border       */
    --v2-page-bg:     #edf0f4;   /* page tone behind surfaces (< --v2-surface) */
    --v2-radius-card: 12px;      /* large container radius                  */

    /* ── Semantic status palette (danger already defined above) ──────────── */
    --v2-good:      #1aa37a;
    --v2-good-soft: #e8f7f1;
    --v2-warn:      #c98217;
    --v2-warn-soft: #fff3df;
    --v2-warn-ink:  #7a5300;   /* readable warn text on warn-soft           */
}

/* ── Sidebar ── */
.v2-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: var(--v2-sidebar-width);
    height: 100vh; /* fallback */
    height: 100dvh; /* mobile: tracks visible viewport, not largest */
    background: var(--v2-sidebar-bg);
    color: #cbd5e1;
    z-index: 1042;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.v2-sidebar-link {
    color: #cbd5e1 !important;
    font-size: 13px;
    padding: 7px 10px;
}
.v2-sidebar-link:hover {
    background: rgba(255,255,255,0.08) !important;
    color: #cbd5e1 !important;
}
.v2-sidebar-link:focus {
    box-shadow: none !important;
}
.v2-sidebar-link.active {
    background: var(--v2-primary) !important;
    color: #fff !important;
}
.v2-sidebar-link.active .v2-si { opacity: 1; }
.v2-sub { font-size: 12px; padding: 5px 10px 5px 28px !important; }

/* Chevron rotation */
.v2-chev { transition: transform 200ms; }
[aria-expanded="true"] .v2-chev { transform: rotate(180deg); }

/* Sidebar overlay (mobile) */
.v2-sidebar-overlay {
    display: none;
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.4);
    z-index: 1041;
}

/* ── Topnav ── */
.v2-topnav {
    height: var(--v2-topnav-height) !important;
    min-height: 0 !important;
    padding: 0 !important;
    margin-left: var(--v2-sidebar-width);
    z-index: 1040;
    overflow: visible;
}
.v2-topnav .navbar-toggler:focus { box-shadow: none; }

/* Quick search (name / company). Below md it grows to fill the remaining
   topnav width; at >= md it's a fixed, reasonable width (the right-hand
   group's ms-md-auto pushes the icons to the edge). */
.v2-quicksearch {
    flex: 1 1 auto;
    min-width: 0;
}
@media (min-width: 768px) {
    .v2-quicksearch {
        /* Prefer ~320px and hold it — the breadcrumb (non-essential) gives up
           width first; the search only shrinks once the breadcrumb is gone. */
        flex: 0 1 320px;
        max-width: 320px;
        min-width: 200px;
    }
}

/* Keep the topnav on ONE row. Bootstrap's .navbar defaults to flex-wrap: wrap,
   so at certain widths (the points shift with the sidebar's collapsed/expanded
   margin) the search/icons dropped to a second line AND the breadcrumb wrapped.
   Force a single row and never wrap the breadcrumb text. */
.v2-topnav > .container-fluid { flex-wrap: nowrap; }
.v2-topnav nav[aria-label="breadcrumb"] { min-width: 0; overflow: hidden; }
.v2-topnav .breadcrumb { flex-wrap: nowrap; }
.v2-topnav .breadcrumb-item,
.v2-topnav .breadcrumb-item > a { white-space: nowrap; }

/* The breadcrumb is non-essential: hide it entirely (never truncate/wrap)
   when the row would get cramped. The threshold depends on how much room the
   sidebar leaves — expanded (~240px) needs a wider viewport than collapsed
   (~60px) before the breadcrumb fits. */
@media (max-width: 1099.98px) {
    body:not(.v2-sidebar-collapsed) .v2-topnav nav[aria-label="breadcrumb"] { display: none !important; }
}
@media (max-width: 859.98px) {
    .v2-topnav nav[aria-label="breadcrumb"] { display: none !important; }
}

/* ── Main content ── */
.v2-main {
    margin-left: var(--v2-sidebar-width);
    margin-top: var(--v2-topnav-height);
    height: calc(100vh - var(--v2-topnav-height) - var(--v2-statusbar-height)); /* fallback */
    height: calc(100dvh - var(--v2-topnav-height) - var(--v2-statusbar-height)); /* mobile: tracks visible viewport */
    overflow-y: auto;
    overscroll-behavior: contain; /* prevent rubber-band leaving non-zero scrollTop on iOS */
    background: #f8fafc;
}

/* ── Account past-due bar — fixed strip above the whole shell ──
   Shown on every page (body gets .v2-has-pastdue). The fixed sidebar/topnav
   and the scrollable main all shift down by --v2-pastdue-height so nothing
   is covered. Height covers the single-line message; on phones it wraps to
   two lines (the height var bumps via the media query below). */
:root { --v2-pastdue-height: 36px; }
.v2-pastdue-bar {
    display: none; /* hidden by default (CSS loads in <head>) so the server-rendered bar never flashes before Angular boots */
    position: fixed;
    top: 0; left: 0; right: 0;
    min-height: var(--v2-pastdue-height);
    z-index: 1060;
    font-size: 13px;
    font-weight: 500;
    border: 0;
    padding: 4px 14px;
    text-align: center;
    line-height: 1.3;
}
/* Shown only once Angular confirms a balance (body gets .v2-has-pastdue). */
.v2-has-pastdue .v2-pastdue-bar { display: flex; }
.v2-has-pastdue .v2-sidebar {
    top: var(--v2-pastdue-height);
    height: calc(100vh - var(--v2-pastdue-height));
    height: calc(100dvh - var(--v2-pastdue-height));
}
.v2-has-pastdue .v2-topnav { top: var(--v2-pastdue-height); }
.v2-has-pastdue .v2-main {
    margin-top: calc(var(--v2-topnav-height) + var(--v2-pastdue-height));
    height: calc(100vh - var(--v2-topnav-height) - var(--v2-statusbar-height) - var(--v2-pastdue-height));
    height: calc(100dvh - var(--v2-topnav-height) - var(--v2-statusbar-height) - var(--v2-pastdue-height));
}
@media (max-width: 575.98px) {
    :root { --v2-pastdue-height: 52px; } /* message wraps to two lines on phones */
}

/* ── Make a Payment page (one-off — constrained, compact card) ── */
.v2-makepayment { max-width: 600px; margin: 0 auto; padding: 28px 16px 56px; }
.v2-mp-card { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 24px 26px; box-shadow: 0 1px 3px rgba(0,0,0,.05); }
.v2-mp-head h1 { font-size: 20px; font-weight: 600; margin: 0 0 2px; }
.v2-mp-head p { color: #6b7280; font-size: 13px; margin: 0 0 18px; }
.v2-mp-row { margin-bottom: 13px; }
.v2-mp-row > label { display: block; font-size: 12px; font-weight: 600; color: #374151; margin-bottom: 5px; }
.v2-mp-row .form-control { font-size: 14px; }
.v2-mp-row .hint { display: block; color: #9ca3af; font-size: 11px; margin-top: 4px; }
.v2-mp-row .err, .v2-makepayment .err { display: block; color: #c0392b; font-size: 12px; margin-top: 4px; }
.v2-mp-grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
.v2-mp-grid3 .v2-mp-row { margin-bottom: 13px; }
/* Card number · exp · cvv on one line */
.v2-mp-cardline { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 13px; }
.v2-mp-cardline label { display: block; font-size: 12px; font-weight: 600; color: #374151; margin-bottom: 5px; }
.v2-mp-cardline .err { display: block; color: #c0392b; font-size: 12px; margin-top: 4px; }
.v2-mp-cardline .form-control { font-size: 14px; }
.v2-mp-cardnum { flex: 1 1 auto; min-width: 0; }
.v2-mp-cardexp { flex: 0 0 88px; }
.v2-mp-cardcvv { flex: 0 0 78px; }
.v2-mp-hr { border: 0; border-top: 1px solid #eef0f3; margin: 18px 0 16px; }
.v2-mp-section-title { font-size: 13px; font-weight: 600; color: #374151; margin: 0 0 8px; display: flex; align-items: center; gap: 6px; }
.v2-mp-cardpick { display: flex; align-items: center; gap: 10px; padding: 6px 0; margin: 0; cursor: pointer; font-size: 14px; }
.v2-mp-brand { min-width: 56px; justify-content: center; background: #f8f9fa; }
.v2-mp-pay { margin-top: 18px; padding: 9px; font-weight: 600; }
.v2-mp-secure { text-align: center; color: #9ca3af; font-size: 11px; margin-top: 10px; display: flex; align-items: center; justify-content: center; gap: 5px; }
.v2-mp-receipt { text-align: center; padding: 6px 0 4px; }
.v2-mp-receipt h2 { font-size: 18px; font-weight: 600; margin: 8px 0 16px; }
.v2-mp-receipt dl { text-align: left; max-width: 340px; margin: 0 auto 14px; }
.v2-mp-receipt dl > div { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-bottom: 1px dashed #eef0f3; font-size: 14px; }
.v2-mp-receipt dt { color: #6b7280; font-weight: 400; }
.v2-mp-receipt dd { margin: 0; font-weight: 500; text-align: right; word-break: break-all; }
.v2-mp-receipt .v2-mp-note { color: #4b5563; font-size: 13px; margin: 4px auto 18px; max-width: 380px; }
@media (max-width: 480px) { .v2-mp-grid3 { grid-template-columns: 1fr; gap: 0; } }

/* ── Status bar ── */
.v2-status-bar {
    position: fixed;
    bottom: 0;
    left: var(--v2-sidebar-width);
    right: 0;
    height: var(--v2-statusbar-height);
    z-index: 1000;
    background: var(--v2-sidebar-bg);
    color: #cbd5e1;
    border-left: 1px solid rgba(255,255,255,0.3);
}
.v2-status-bar,
.v2-status-bar a,
.v2-status-bar span,
.v2-status-bar small,
.v2-status-bar .text-muted {
    color: #cbd5e1 !important;
}
.v2-status-bar a:hover {
    color: #fff !important;
}
.v2-status-section {
    height: 100%;
    display: flex;
    align-items: center;
}
.v2-status-section + .v2-status-section {
    border-left: 1px solid rgba(255,255,255,0.3);
}
/* Single status indicator — toggles between dot (idle) and spinner (busy) via class.
   No element swap, so no ngAnimate flicker. */
.v2-status-indicator {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #6c757d;
    flex-shrink: 0;
    box-sizing: border-box;
}
.v2-status-indicator.v2-busy {
    width: 12px;
    height: 12px;
    background: transparent;
    border: 2px solid rgba(255,255,255,0.3);
    border-top-color: #fff;
    animation: v2-status-spin 0.75s linear infinite;
}
@keyframes v2-status-spin { to { transform: rotate(360deg); } }
.v2-export-stripe {
    background-image: linear-gradient(45deg, rgba(255,255,255,.2) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.2) 50%, rgba(255,255,255,.2) 75%, transparent 75%, transparent);
    background-size: 20px 20px;
    animation: v2-stripe-move 1s linear infinite;
}
@keyframes v2-stripe-move {
    from { background-position: 20px 0; }
    to { background-position: 0 0; }
}

/* Primary (Save) buttons across ALL V2 surfaces — ONE central rule.
   Bootstrap's .btn-primary hardcodes #0d6efd and ignores the V2 theme, so
   we re-point its button-level custom properties at the shared --v2-primary
   palette. Every V2 primary button (modals, list pages, family detail, etc.)
   now renders in the themed accent and follows the color picker — including
   Settings, which uses these same .btn classes. One button system, one
   source. Retune in :root. */
/* Shape — applied via Bootstrap's own button-scoped radius var, so it changes
   only buttons (not button-group inner corners, badges, etc.). */
body.theme-v2 .btn { --bs-btn-border-radius: var(--v2-btn-radius); }

body.theme-v2 .btn-primary {
    --bs-btn-bg:                 var(--v2-primary);
    --bs-btn-border-color:       var(--v2-primary);
    --bs-btn-hover-bg:           var(--v2-primary-hover);
    --bs-btn-hover-border-color: var(--v2-primary-hover);
    --bs-btn-active-bg:          var(--v2-primary-active);
    --bs-btn-active-border-color:var(--v2-primary-active);
    --bs-btn-disabled-bg:        var(--v2-primary);
    --bs-btn-disabled-border-color: var(--v2-primary);
}

/* Neutral / secondary — the plain "Cancel" button, unified with Settings. */
body.theme-v2 .btn-secondary,
body.theme-v2 .btn-outline-secondary {
    --bs-btn-color:                 var(--v2-btn-neutral-text);
    --bs-btn-bg:                    var(--v2-btn-neutral-bg);
    --bs-btn-border-color:          var(--v2-btn-neutral-border);
    --bs-btn-hover-color:           var(--v2-btn-neutral-text);
    --bs-btn-hover-bg:              var(--v2-btn-neutral-bg-hover);
    --bs-btn-hover-border-color:    var(--v2-btn-neutral-border-hover);
    --bs-btn-active-color:          var(--v2-btn-neutral-text);
    --bs-btn-active-bg:             var(--v2-btn-neutral-bg-hover);
    --bs-btn-active-border-color:   var(--v2-btn-neutral-border-hover);
}

/* Danger — solid (destructive confirm). */
/* Selected segment in a segmented control (status chips, view switchers).
   Bootstrap .active on an outline button is only a slightly darker fill, which
   does not read as "this one is chosen" in a row of three. Written once here
   rather than per page — it was already duplicated in two places. */
body.theme-v2 .v2-segmented .btn.active {
    background-color: var(--v2-primary-soft);
    border-color: var(--v2-primary);
    color: var(--v2-primary);
    font-weight: 600;
}

body.theme-v2 .btn-danger {
    --bs-btn-bg:                  var(--v2-danger);
    --bs-btn-border-color:        var(--v2-danger);
    --bs-btn-hover-bg:            var(--v2-danger-hover);
    --bs-btn-hover-border-color:  var(--v2-danger-hover);
    --bs-btn-active-bg:           var(--v2-danger-hover);
    --bs-btn-active-border-color: var(--v2-danger-hover);
    --bs-btn-disabled-bg:         var(--v2-danger);
    --bs-btn-disabled-border-color: var(--v2-danger);
}

/* Danger — outline (destructive in a row of options). */
body.theme-v2 .btn-outline-danger {
    --bs-btn-color:               var(--v2-danger);
    --bs-btn-border-color:        var(--v2-danger);
    --bs-btn-hover-color:         var(--v2-danger);
    --bs-btn-hover-bg:            var(--v2-danger-soft);
    --bs-btn-hover-border-color:  var(--v2-danger);
    --bs-btn-active-color:        #fff;
    --bs-btn-active-bg:           var(--v2-danger);
    --bs-btn-active-border-color: var(--v2-danger);
}

/* Ghost — borderless, accent-text action (e.g. inline "Edit"/"+ Add" links
   that should read as buttons). The single home for the ghost style across
   V2; use as `class="btn v2-btn-ghost"` (or `btn btn-sm v2-btn-ghost`). */
body.theme-v2 .btn.v2-btn-ghost {
    --bs-btn-color:                 var(--v2-primary);
    --bs-btn-bg:                    transparent;
    --bs-btn-border-color:          transparent;
    --bs-btn-hover-color:           var(--v2-primary);
    --bs-btn-hover-bg:              var(--v2-primary-soft);
    --bs-btn-hover-border-color:    transparent;
    --bs-btn-active-color:          var(--v2-primary);
    --bs-btn-active-bg:             var(--v2-primary-soft);
    --bs-btn-active-border-color:   transparent;
    --bs-btn-box-shadow:            none;
    font-weight: 600;
}

/* ── Old (angular-strap) modals above BS5 modals ── */
body.theme-v2 > .modal:not([id]) { z-index: 1080 !important; }
body.theme-v2 > .modal-backdrop.am-fade { z-index: 1075 !important; }

/* ── QB Export Modal accordion ── */
.v2-qb-accordion .accordion-button {
    padding: 8px 12px;
    font-weight: 600;
    font-size: 13px;
    color: var(--v2-sidebar-bg);
    background: #f8f9fa;
}
.v2-qb-accordion .accordion-button:not(.collapsed) {
    background: #e9ecef;
    color: var(--v2-sidebar-bg);
    box-shadow: inset 0 -1px 0 rgba(0,0,0,0.08);
}
.v2-qb-accordion .accordion-button:focus {
    box-shadow: none;
    border-color: rgba(0,0,0,0.125);
}
.v2-qb-accordion .accordion-button svg { color: var(--v2-primary); flex-shrink: 0; }
.v2-qb-count {
    margin-left: auto;
    background: var(--v2-primary);
    color: #fff;
    border-radius: 10px;
    padding: 1px 8px;
    font-size: 11px;
    font-weight: 500;
}
.v2-qb-accordion .accordion-button .v2-qb-count { margin-right: 12px; }
.v2-qb-table { font-size: 13px; }
.v2-qb-table thead th {
    font-size: 11px;
    font-weight: 500;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    text-transform: uppercase;
    letter-spacing: 0.3px;
    padding: 6px 8px;
    border-bottom: 1px solid #e9ecef;
}
.v2-qb-table tbody td { padding: 6px 8px; }
.v2-qb-table tbody tr:hover { background: rgba(0,0,0,0.02); }

/* QB customer search (typeahead) */
.v2-qb-search-wrap {
    position: relative;
}
.v2-qb-search-input { padding-right: 28px; }
.v2-qb-matched .v2-qb-search-input {
    background: #e6f6ee;
    border-color: #a3d9b8;
}
.v2-qb-clear {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: 0;
    padding: 2px;
    cursor: pointer;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    display: inline-flex;
}
.v2-qb-clear:hover { color: #dc3545; }
.v2-qb-search-spinner {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    border-width: 2px;
}
.v2-qb-search-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 100;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.15);
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    list-style: none;
    margin: 2px 0 0;
    padding: 4px 0;
    max-height: 240px;
    overflow-y: auto;
}
.v2-qb-search-results li {
    padding: 6px 12px;
    cursor: pointer;
    font-size: 13px;
}
.v2-qb-search-results li:hover,
.v2-qb-search-results li.active { background: var(--v2-primary); color: #fff; }
/* When floated by v2-floating-dropdown the list is position:fixed in
   viewport coords (escapes the modal scroll-clip). The directive sets
   top/left/width inline; we just need to bump z-index above the modal. */
.v2-qb-search-results.is-floating { z-index: 2000; max-height: 240px; overflow-y: auto; }

/* Stat card — small "headline number + label" tile reusable across v2 */
.v2-stat-card {
    background: #f1f5f9;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 6px 14px;
    min-width: 120px;
}
.v2-stat-card .v2-stat-label {
    font-size: 11px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #64748b), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    text-transform: uppercase;
    letter-spacing: 0.4px;
    line-height: 1.2;
}
.v2-stat-card .v2-stat-value {
    font-size: 18px;
    font-weight: 700;
    color: var(--v2-sidebar-bg);
    line-height: 1.3;
}

/* Highlight a pending QB row when the user clicks the footer hint */
@keyframes v2-qb-pending-flash {
    0%   { background: #fee2e2; }
    100% { background: transparent; }
}
.v2-qb-pending-flash > td { animation: v2-qb-pending-flash 1.5s ease-out; }
.v2-qb-no-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.15);
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    margin-top: 2px;
    padding: 8px 12px;
    font-size: 12px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    z-index: 100;
}

/* QB Add Item modal — radio list */
.v2-qb-radio-list {
    max-height: 240px;
    overflow-y: auto;
    border: 1px solid #e9ecef;
    border-radius: 4px;
    padding: 6px 8px;
}
.v2-qb-radio-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 6px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 13px;
}
.v2-qb-radio-item:hover { background: rgba(0,0,0,0.04); }
.v2-qb-radio-item input[type="radio"] { margin: 0; }

/* ── Blocking overlay (invisible, prevents all interaction) ── */
.v2-blocking-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: transparent;
    z-index: 1050; /* below modal (1055) but above page content */
    cursor: wait;
}

/* ── Draggable modal header (4-headed arrow cursor) ── */
.v2-modal-drag-handle { cursor: move; user-select: none; }
@media (max-width: 767.98px) {
    .v2-modal-drag-handle { cursor: default; }
}

/* ── V2 Modal header — dark sidebar color ── */
.v2-modal-header {
    background: var(--v2-sidebar-bg);
    color: #fff;
}
.v2-modal-header .btn-close { filter: invert(1) grayscale(100%) brightness(200%); }

/* ── Row hover actions ── */
.v2-hover-actions-cell {
    width: 1%;
    white-space: nowrap;
    padding-right: 16px;
    text-align: right;
    text-align: right;
}
.v2-hover-actions {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: 8px;
    opacity: 0.4;
    transition: opacity 0.15s ease;
    vertical-align: middle;
}
.v2-hover-actions .dropdown {
    display: inline-flex;
    align-items: center;
    line-height: 1;
}
.v2-hover-actions .dropdown > .v2-hover-action {
    line-height: 1;
}
.v2-hover-action svg { display: block; }
tr:hover .v2-hover-actions,
.v2-hover-actions-pinned {
    opacity: 1 !important;
}
.v2-hover-action {
    color: var(--v2-sidebar-bg);
    display: inline-flex;
    align-items: center;
    padding: 2px;
    border-radius: 3px;
    transition: background 0.1s;
    text-decoration: none;
}
.v2-hover-action:hover {
    background: rgba(0,0,0,0.08);
    color: var(--v2-sidebar-bg);
}

/* Colorful variants — append any of these to .v2-hover-action for a
   tinted row action. Centralized here so every v2 list uses the same
   accent palette without per-page overrides. */
/* Fixed blue, NOT --v2-primary, so Edit-type actions don't render in the
   user's V2 theme accent (a "red" theme would otherwise turn them red and
   make them indistinguishable from the danger/Delete variant). */
.v2-hover-action.v2-hover-action-primary { color: #2563eb; }
.v2-hover-action.v2-hover-action-primary:hover { color: #2563eb; background: rgba(37, 99, 235, 0.12); }
.v2-hover-action.v2-hover-action-success { color: var(--bs-success); }
.v2-hover-action.v2-hover-action-success:hover { color: var(--bs-success); background: color-mix(in srgb, var(--bs-success) 15%, transparent); }
.v2-hover-action.v2-hover-action-danger  { color: var(--bs-danger); }
.v2-hover-action.v2-hover-action-danger:hover  { color: var(--bs-danger);  background: color-mix(in srgb, var(--bs-danger) 15%, transparent); }
.v2-hover-action.v2-hover-action-warning { color: var(--bs-warning-text, #997404); }
.v2-hover-action.v2-hover-action-warning:hover { color: var(--bs-warning-text, #997404); background: color-mix(in srgb, var(--bs-warning) 18%, transparent); }

/* #84219 Duplicates workspace — text-label row actions (Matched "Not a
   duplicate", Merged "Undo", Dismissed "Restore") that sit inside an
   otherwise fully-clickable list row (e.g. ng-click="selectPair(c)" on the
   Matched <tr>), so a labeled .btn is used instead of the icon-only
   .v2-hover-action affordance above. Plain .btn.btn-sm alone runs ~30px
   tall — under the CLAUDE.md ≥44px mobile touch-target minimum — so this
   only bumps the tap height, not the button's visual footprint. */
.dup-list-action-btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}

/* #84219 code-review finding #4 — the Dismissed row's action cell holds
   TWO full-text buttons ("Restore to Matched" + "Merge now"). The cell also
   carries the shared .v2-hover-actions-cell (width:1% shrink-to-fit +
   white-space:nowrap), which at desktop/tablet grows the column just enough
   to hold both buttons on ONE line. At phone widths that single line would
   overflow the viewport (banned horizontal scroll, CLAUDE.md), so ONLY there
   do we switch the cell to a full-width vertical stack.
   NOTE: an earlier version set `display:flex; flex-wrap:wrap; white-space:normal`
   at ALL widths — that fought the shared cell's width:1% and collapsed the two
   buttons into a squished, text-wrapped stack on desktop. Do not reintroduce it. */
.dup-actions-cell .dup-list-action-btn {
    white-space: nowrap;   /* labels never wrap — "Restore to Matched" stays one line */
}
.dup-actions-cell .dup-list-action-btn + .dup-list-action-btn {
    margin-left: 6px;      /* gap between the inline buttons at desktop/tablet */
}
@media (max-width: 480px) {
    /* Phone: give the Actions column real width (override the shared width:1%)
       and stack the buttons as full-width blocks — each a comfortable tap
       target, no horizontal scroll. Keep the <td> a normal table-cell: a
       `display:flex` <td> drops out of table layout and collapses to ~0 width
       beside the sibling table-cell columns (that squished the buttons into
       tiny circles). So flex/full-width lives on the BUTTONS, not the cell. */
    .dup-actions-cell {
        width: 45%;
        white-space: normal;
    }
    .dup-actions-cell .dup-list-action-btn {
        display: flex;
        width: 100%;
        margin: 0 0 6px 0;
        justify-content: center;
        text-align: center;
        white-space: normal;   /* allow the label to wrap inside the full-width button */
    }
    .dup-actions-cell .dup-list-action-btn:last-child {
        margin-bottom: 0;
    }
    /* neutralize the desktop inline gap (same 0,3,0 specificity, later in source) */
    .dup-actions-cell .dup-list-action-btn + .dup-list-action-btn {
        margin-left: 0;
    }
}

.v2-qb-action { vertical-align: middle; }
.v2-qb-disabled { opacity: 0.3; cursor: not-allowed; pointer-events: none; }

/* Row-actions kebab — used in v2 list rows on mobile (< md) where the
   desktop hover-icons can't be discovered via touch. The button is a
   borderless 28×28 hit target; the dropdown carries the same row actions
   that .v2-hover-actions exposes on desktop. */
.v2-row-kebab-wrap { display: inline-block; }
.v2-row-kebab {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
}
.v2-row-kebab:hover,
.v2-row-kebab:focus,
.v2-row-kebab[aria-expanded="true"] {
    background-color: rgba(0,0,0,.06);
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #212529), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
}
.v2-row-kebab-wrap .dropdown-menu {
    font-size: 13px;
    min-width: 180px;
    box-shadow: 0 4px 12px rgba(0,0,0,.12);
    border-color: rgba(0,0,0,.1);
}
.v2-row-kebab-wrap .dropdown-item {
    padding: 8px 12px;
}

/* ── Deposit modal table columns ── */
.v2-dep-table { table-layout: fixed; width: 100%; }
.v2-dep-col-chk { width: 20px; padding: 4px 0 4px 4px !important; }
.v2-dep-col-family { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-dep-col-data { width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-dep-col-cat { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Width-only — alignment/padding now come from .v2-hover-actions-cell
   on both <th> and <td>. Width must live here because the deposit
   table uses table-layout:fixed (for sticky-header alignment) which
   reads the column width from the <th>. 90px holds three hover-action
   icons (edit / circle-plus or circle-minus) + the mobile kebab. */
.v2-dep-col-act { width: 90px; }

/* ── Modal list area (fixed height based on page size, prevents layout shift) ──
   Usage: <div class="v2-modal-list-area" style="--v2-list-rows: 10;">
   The list area uses flex to fill available space within the modal-content,
   with a desired size based on page size + density, but always leaves room
   for header, form, toolbar, totals, and footer above/below.
*/
.v2-modal-list-area {
    --v2-list-rows: 10;
    --v2-row-height: 32px;
    --v2-list-header-height: 36px;
    /* Desired size = (rows × row-height) + table header */
    flex: 0 1 auto;
    height: calc(var(--v2-list-rows) * var(--v2-row-height) + var(--v2-list-header-height));
    /* Cap so footer/header always fit on shorter screens (modal chrome ≈ 280px) */
    max-height: calc(100vh - 320px); /* fallback */
    max-height: calc(100dvh - 320px); /* mobile: tracks visible viewport */
    min-height: 120px;
    overflow-y: auto;
}
/* Density variants — adjust row height to match table density */
[data-v2-density="compact"] .v2-modal-list-area { --v2-row-height: 26px; }
[data-v2-density="comfortable"] .v2-modal-list-area { --v2-row-height: 38px; }

/* Striping + hover for any list inside a modal — mirrors the .v2-list-card
   rules (lines 880–881) so modal lists look/act like main lists without
   needing the .v2-list-card wrapper. */
.v2-modal-list-area .table > tbody > tr:nth-of-type(odd) > td { background-color: var(--bs-table-striped-bg); }
.v2-modal-list-area .table > tbody > tr:hover > td { background-color: rgba(0, 0, 0, 0.08); }

/* Focused/selected row — mirrors the .v2-list-card focus rules (lines
   ~1080) so modal lists get the same blue outline + tint when a row is
   focused via mouse click or v2-list-row arrow-key nav. Without these,
   the focus state was invisible in the deposit modal (and any other
   modal-hosted list using v2-list-row). */
.v2-modal-list-area .table > tbody > tr:focus { outline: 2px solid var(--bs-primary, #0d6efd); outline-offset: -2px; }
.v2-modal-list-area .table > tbody > tr:focus { background-color: rgba(13, 110, 253, 0.08); }
.v2-modal-list-area .table > tbody > tr:focus > td { background-color: transparent; }

/* Edge padding on first/last cells — mirrors .v2-list-card so modal lists
   don't run flush against the modal walls. !important for the same reason
   (see .v2-list-card edge padding rule). */
.v2-modal-list-area .table th:first-child,
.v2-modal-list-area .table td:first-child { padding-left: 16px !important; }
.v2-modal-list-area .table th:last-child,
.v2-modal-list-area .table td:last-child  { padding-right: 16px !important; }

/* Refreshing state — applied while a paged/sorted/filtered list is reloading
   in place. Keeps the frame mounted and visually signals the transition
   without collapsing the modal content. */
.v2-list-refreshing { opacity: 0.55; pointer-events: none; transition: opacity 0.12s; }

/* Stacked modal hide — when a modal opens on top of another, the
   underlying one is hidden entirely so it doesn't bleed through. Added
   by controllers via show.bs.modal on the top modal, removed on
   hidden.bs.modal so the underlying modal reappears intact (same
   scroll position, form state, etc). Generic pattern — use from any
   controller that needs stacked-modal hiding. */
.v2-modal-hidden .modal-content { visibility: hidden; }

/* ── Modal filter panel (slides inside modal-content) ── */
.v2-modal-filter-panel {
    position: absolute;
    top: 0;
    right: -280px;
    width: 280px;
    height: 100%;
    background: #fff;
    border-left: 1px solid rgba(0,0,0,0.1);
    display: flex;
    flex-direction: column;
    transition: right 0.25s ease;
    z-index: 10;
    box-shadow: -2px 0 8px rgba(0,0,0,0.08);
}
.v2-modal-filter-panel.v2-modal-filter-open {
    right: 0;
}

/* ── Color schemes — accent + matching dark sidebar ── */
[data-v2-color="blue"]   { --v2-primary: #4a90d9; --v2-sidebar-bg: #1e293b; }
[data-v2-color="green"]  { --v2-primary: #10b981; --v2-sidebar-bg: #162a23; }
[data-v2-color="purple"] { --v2-primary: #8b5cf6; --v2-sidebar-bg: #1f1835; }
[data-v2-color="red"]    { --v2-primary: #ef4444; --v2-sidebar-bg: #2a1418; }
[data-v2-color="teal"]   { --v2-primary: #14b8a6; --v2-sidebar-bg: #142a28; }

/* Logo color shift to match accent (logo cloud is blue ~210°) */
[data-v2-color="green"]  .v2-sidebar-logo img { filter: hue-rotate(-70deg) saturate(1.2); }
[data-v2-color="purple"] .v2-sidebar-logo img { filter: hue-rotate(60deg) saturate(1.1); }
[data-v2-color="red"]    .v2-sidebar-logo img { filter: hue-rotate(150deg) saturate(1.3); }
[data-v2-color="teal"]   .v2-sidebar-logo img { filter: hue-rotate(-35deg) saturate(1.1); }

/* ══════════════════════════════════════════════════════════════════════
   COLLAPSED — hide labels, center icons, hide logo, show expand btn
   ══════════════════════════════════════════════════════════════════════ */
.v2-sidebar-collapsed .v2-sidebar {
    width: var(--v2-sidebar-collapsed-width);
}
/* Hide text labels, chevrons, logo */
.v2-sidebar-collapsed .v2-sidebar .v2-sl,
.v2-sidebar-collapsed .v2-sidebar .v2-chev,
.v2-sidebar-collapsed .v2-sidebar .v2-sidebar-logo,
.v2-sidebar-collapsed .v2-sidebar .btn-close {
    display: none !important;
}
/* Center icons */
.v2-sidebar-collapsed .v2-sidebar .v2-sidebar-link {
    justify-content: center;
    padding: 10px 0 !important;
}
/* Dashboard uses a wrapper div > a; strip div padding and push centering into the inner <a> instead */
.v2-sidebar-collapsed .v2-sidebar div.v2-sidebar-link {
    padding: 0 !important;
}
.v2-sidebar-collapsed .v2-sidebar div.v2-sidebar-link > .nav-link {
    justify-content: center;
    padding: 10px 0 !important;
}
.v2-sidebar-collapsed .v2-sidebar .v2-si {
    margin-right: 0 !important;
}
/* Hide submenus */
.v2-sidebar-collapsed .v2-sidebar .collapse,
.v2-sidebar-collapsed .v2-sidebar .collapsing,
.v2-sidebar-collapsed .v2-sidebar .v2-sub {
    display: none !important;
}
/* Shrink header to just the expand button */
.v2-sidebar-collapsed .v2-sidebar .d-flex.align-items-center.justify-content-between {
    justify-content: center !important;
    padding: 10px 8px !important;
}
/* Adjust layout offsets */
.v2-sidebar-collapsed .v2-topnav,
.v2-sidebar-collapsed .v2-main {
    margin-left: var(--v2-sidebar-collapsed-width) !important;
}
.v2-sidebar-collapsed .v2-status-bar {
    left: var(--v2-sidebar-collapsed-width) !important;
}

/* ══════════════════════════════════════════════════════════════════════
   RESPONSIVE: <992px — JS adds v2-sidebar-collapsed class automatically
   Collapse button remains visible so user can expand
   ══════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════
   RESPONSIVE: <768px — hide sidebar completely, hamburger opens full
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {
    .v2-sidebar {
        width: var(--v2-sidebar-width);
        left: calc(-1 * var(--v2-sidebar-width));
        transition: none; /* no transition by default — prevents flash when crossing breakpoint */
    }
    /* Only animate when opening via hamburger */
    .v2-mobile-sidebar-open .v2-sidebar {
        transition: left 250ms ease;
    }
    /* Restore full expanded sidebar when shown on mobile */
    .v2-sidebar .v2-sl,
    .v2-sidebar .v2-chev,
    .v2-sidebar .v2-sidebar-logo,
    .v2-sidebar .btn-close { display: initial !important; }
    .v2-sidebar .v2-sidebar-link { justify-content: flex-start !important; padding: 7px 10px !important; }
    .v2-sidebar .v2-sidebar-link.v2-sub { padding-left: 28px !important; }
    .v2-sidebar .v2-si { margin-right: 0.5rem !important; }
    /* Don't force collapses open — let BS5 handle sub-menu toggle */
    .v2-sidebar .v2-sub { display: initial !important; }
    .v2-sidebar .d-flex.align-items-center.justify-content-between {
        justify-content: space-between !important;
        padding: 10px 16px !important;
    }
    /* Hide collapse toggle on mobile */
    .v2-sidebar .d-none.d-md-inline-block { display: none !important; }

    .v2-topnav, .v2-main, .v2-status-bar { margin-left: 0 !important; }
    .v2-sidebar-collapsed .v2-topnav,
    .v2-sidebar-collapsed .v2-main,
    .v2-sidebar-collapsed .v2-status-bar { margin-left: 0 !important; }
    .v2-status-bar, .v2-sidebar-collapsed .v2-status-bar { left: 0 !important; }

    /* Mobile open */
    .v2-mobile-sidebar-open .v2-sidebar { left: 0; }
    .v2-mobile-sidebar-open .v2-sidebar-overlay { display: block; }
}

/* ── Force expand (overrides auto-collapse at <992) ── */
.v2-sidebar-force-expand .v2-sidebar {
    width: var(--v2-sidebar-width) !important;
}
.v2-sidebar-force-expand .v2-sidebar .v2-sl,
.v2-sidebar-force-expand .v2-sidebar .v2-chev,
.v2-sidebar-force-expand .v2-sidebar .v2-sidebar-logo { display: initial !important; }
.v2-sidebar-force-expand .v2-sidebar .v2-sidebar-link {
    justify-content: flex-start !important;
    padding: 7px 10px !important;
}
.v2-sidebar-force-expand .v2-sidebar .v2-si { margin-right: 0.5rem !important; }
.v2-sidebar-force-expand .v2-sidebar .v2-sub { display: initial !important; }
.v2-sidebar-force-expand .v2-sidebar .d-flex.align-items-center.justify-content-between {
    justify-content: space-between !important;
    padding: 10px 16px !important;
}
.v2-sidebar-force-expand .v2-topnav,
.v2-sidebar-force-expand .v2-main {
    margin-left: var(--v2-sidebar-width) !important;
}
.v2-sidebar-force-expand .v2-status-bar {
    left: var(--v2-sidebar-width) !important;
}

/* ══════════════════════════════════════════════════════════════════════
   TOOLTIPS for collapsed sidebar icons
   ══════════════════════════════════════════════════════════════════════ */
.v2-sidebar-collapsed .v2-sidebar .v2-sidebar-link[title] {
    position: relative;
}

/* ══════════════════════════════════════════════════════════════════════
   SHARED LIST PAGE STYLES
   ══════════════════════════════════════════════════════════════════════ */

/* Gradient header — matches dashboard widget style */
.v2-gradient-header {
    background: linear-gradient(135deg, var(--v2-primary), color-mix(in srgb, var(--v2-primary) 60%, #000));
    color: #fff;
    padding: 10px 16px;
    border-radius: 0;
}
.v2-gradient-header .btn {
    background: rgba(255,255,255,0.95);
    color: var(--v2-text-color, #212529);
    border-color: rgba(255,255,255,0.95);
}
.v2-gradient-header .btn svg { vertical-align: -2px; }
.v2-gradient-header .btn:hover {
    background: #fff;
    color: var(--v2-text-color, #212529);
    border-color: #fff;
}

/* Solid primary action button on gradient headers — high contrast, draws the eye */
.v2-gradient-header .v2-btn-header-solid {
    background: #fff;
    color: var(--v2-sidebar-bg, #1a2b3c);
    border-color: #fff;
    font-weight: 600;
}
.v2-gradient-header .v2-btn-header-solid:hover {
    background: #f1f3f5;
    color: var(--v2-sidebar-bg, #1a2b3c);
    border-color: #f1f3f5;
}
.v2-gradient-header .v2-btn-header-solid:disabled,
.v2-gradient-header .v2-btn-header-solid[disabled] {
    background: rgba(255,255,255,0.5);
    color: rgba(0,0,0,0.4);
    border-color: rgba(255,255,255,0.5);
}

/* 3-dot overflow icon button on gradient headers (for secondary actions) */
.v2-gradient-header .v2-header-kebab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    color: #fff;
    border-radius: 4px;
    text-decoration: none;
    cursor: pointer;
}
.v2-gradient-header .v2-header-kebab:hover {
    background: rgba(255,255,255,0.18);
    color: #fff;
}
.v2-gradient-header .v2-header-kebab[aria-expanded="true"] {
    background: rgba(255,255,255,0.25);
}
/* .v2-toggle-active removed — its one user (Transactions Payments|Bills) now uses
   .v2-segmented like every other header toggle. It marked the chosen segment with a
   25% white wash, which read as fainter than the unchosen ones rather than stronger. */

/* .v2-hdr-toggle--active / --inactive and .v2-toggle-active USED TO LIVE HERE.
   They were two more answers to "mark the chosen segment on a gradient header",
   alongside the one in .v2-gradient-header .v2-segmented above — three treatments
   of one control, disagreeing on both the fill and the text colour, and only the
   segmented one took its blue from a token.

   The reports variant's lightened-blue INACTIVE state was the best of the three
   and has been folded into the shared rule; the segmented ACTIVE state (white
   chip, primary text) was kept. Every header toggle now uses .v2-segmented.

   The icon/label helpers below are layout, not colour, and are still in use —
   they collapse a labelled segment to its icon at narrow widths. */

/* List card — fixed height, header+toolbar fixed, data scrolls */
.v2-list-card {
    height: calc(100vh - 74px); /* fallback */
    height: calc(100dvh - 74px); /* mobile: tracks visible viewport */
    display: flex;
    flex-direction: column;
}
.v2-list-card > .card-body {
    overflow-y: auto;
    overflow-x: hidden;   /* Never produce a page-level horizontal scroll bar.
                             Use v2-hide-* on columns to drop them at narrow widths. */
    flex: 1;
}
.v2-list-card > .card-body thead {
    position: sticky;
    top: 0;
    z-index: 2;
    background: #fff;
}

/* Tab-content delivered through an `ng-include` wrapper (e.g. the
   chai-club tab inside pending-charges.html). The ng-include's host
   div sits between .v2-list-card and the tab's card-body, breaking
   the direct-child `> .card-body` selector above. Treat the host div
   as a transparent flex column so the inner card-body still scrolls
   and a sticky thead still works exactly like a top-level list.

   OPT-IN via the `.v2-tab-include` marker class — do NOT match bare
   `[ng-include]`. A previous broad `.v2-list-card > [ng-include]`
   selector also caught OVERLAY include hosts (modal/offcanvas) placed
   inside a list card, giving them flex:1 and splitting the card height
   so the real list collapsed (regressed sent-items + outbox). Overlays
   must live outside the card; tab-content wrappers opt in with the class. */
.v2-list-card > .v2-tab-include {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 0;
}
.v2-list-card > .v2-tab-include > .card-body {
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1;
    min-height: 0;   /* allow shrink below content height — required for
                        nested flex scroll containers to actually scroll
                        instead of expand */
}
.v2-list-card > .v2-tab-include > .card-body thead {
    position: sticky;
    top: 0;
    z-index: 2;
    background: #fff;
}

/* Nested .v2-list-card — when the shared list-body partial is included
   inside a detail page (e.g. family-detail Transactions tab), its own
   .v2-list-card would otherwise apply `height: calc(100vh - 74px)` on
   top of the outer detail-page card and the auto-height ui-view +
   ng-include divs prevent `height: 100%` from propagating, producing
   two stacked scrollbars (outer card-body scrolls because the inner
   overflows it). Flex-fill the chain so the inner card fits the outer
   .card-body exactly and only its own .card-body (the row list) scrolls.
   :has() scopes this to detail-page card-bodies that actually contain a
   nested list — leaves other tabs (e.g. Categories) untouched. */
.v2-list-card.v2-detail-page > .card-body:has(.v2-list-card) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.v2-list-card.v2-detail-page > .card-body:has(.v2-list-card) > [ui-view] {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.v2-list-card.v2-detail-page > .card-body:has(.v2-list-card) > [ui-view] > div {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.v2-list-card .v2-list-card {
    flex: 1 1 0;
    min-height: 0;
    height: auto;
}

/* Selected row (blue highlight for master-detail pattern) */
.v2-selected-row > td { background-color: var(--bs-primary-bg-subtle) !important; }

/* Batch detail panel (right side on wide screens) */
.v2-batch-layout {
    display: flex;
    gap: 0;
    height: calc(100vh - 74px); /* fallback */
    height: calc(100dvh - 74px); /* mobile: tracks visible viewport */
}
.v2-batch-layout > .v2-list-card { flex: 1; max-width: none; }
.v2-batch-layout > .v2-list-card + .v2-batch-detail-panel::after {
    content: ''; position: absolute; left: 0; top: 50px; bottom: 0; width: 4px; background: #c0c7cf; z-index: 25;
}
.v2-batch-detail-panel {
    position: relative;
    flex: 0 0 480px;
    height: calc(100vh - 74px); /* fallback */
    height: calc(100dvh - 74px); /* mobile: tracks visible viewport */
    display: flex;
    flex-direction: column;
    background: #fff;
    overflow: hidden;
    margin-right: 16px;
}
@media (min-width: 1600px) { .v2-batch-detail-panel { flex: 0 0 550px; } }

.v2-batch-detail-panel .v2-detail-header {
    padding: 10px 16px;
    font-weight: 600;
    font-size: 14px;
    display: flex;
    align-items: center;
    min-height: 50px;
    box-sizing: border-box;
    color: var(--v2-primary, #1e3a5f);
    background: #fff;
    border: 1px solid #dee2e6;
    border-radius: 6px 6px 0 0;
    border-bottom: 2px solid var(--v2-primary, #1e3a5f);
    flex-shrink: 0;
    letter-spacing: 0.3px;
}
.v2-batch-detail-panel .v2-detail-summary {
    padding: 12px 16px;
    font-size: 13px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #475569), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    border-bottom: 1px solid #e5e7eb;
    flex-shrink: 0;
}
.v2-detail-summary .v2-summary-primary {
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid #e5e7eb;
}
.v2-detail-summary .v2-summary-primary:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.v2-summary-badge {
    display: inline-block; padding: 3px 10px; border-radius: 4px; font-size: 12px; font-weight: 600;
}
.v2-summary-badge-primary { background: var(--v2-primary, #1e3a5f); color: #fff; }
.v2-summary-badge-muted { background: #f3f4f6; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #555), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-weight: 500; }
.v2-detail-summary .v2-summary-filters {
    display: flex; flex-wrap: wrap; gap: 4px; font-size: 12px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #64748b), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
}
.v2-detail-summary .v2-summary-filters > div { display: flex; gap: 4px; }
.v2-detail-summary .v2-summary-filters .v2-sf-label { font-weight: 500; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #888), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
.v2-detail-summary .v2-summary-advanced {
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    padding-top: 8px; margin-top: 8px; border-top: 1px solid #e5e7eb;
}

.v2-batch-detail-panel .v2-detail-body {
    flex: 1;
    overflow: hidden;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.v2-batch-detail-panel .v2-detail-table-wrap {
    flex: 1;
    overflow-y: auto;
}

/* Detail table */
.v2-batch-detail-panel .table { font-size: 13px; margin-bottom: 0; }
.v2-batch-detail-panel thead {
    position: sticky; top: 0; z-index: 10;
    background: #f8f9fa; border-bottom: 2px solid #dee2e6;
}
.v2-batch-detail-panel th {
    font-weight: 600; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #475569), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-size: 12px;
    padding: 8px 12px !important;
}
.v2-batch-detail-panel tbody tr { transition: background-color 150ms ease; }
.v2-batch-detail-panel tbody tr:nth-child(odd) td { background-color: #fafbfc; }
.v2-batch-detail-panel tbody tr:hover td { background-color: rgba(37, 99, 235, 0.04) !important; }
.v2-batch-detail-panel td, .v2-batch-detail-panel th { white-space: nowrap; }
.v2-batch-detail-panel td:first-child { overflow: hidden; text-overflow: ellipsis; max-width: 120px; }
.v2-batch-detail-panel .table { table-layout: fixed; width: 100%; }
.v2-batch-detail-panel .v2-detail-table-wrap { overflow-x: hidden; }
.v2-batch-detail-panel td.text-end { font-variant-numeric: tabular-nums; }

/* Detail search */
.v2-detail-search {
    position: relative; padding: 10px 16px; border-bottom: 1px solid #eee; flex-shrink: 0;
}
.v2-detail-search input { font-size: 13px; padding-left: 32px; }
.v2-detail-search .v2-search-icon {
    position: absolute; left: 24px; top: 50%; transform: translateY(-50%); color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #94a3b8), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); pointer-events: none;
}

/* Detail pagination */
.v2-detail-pagination {
    display: flex; align-items: center; justify-content: space-between;
    padding: 10px 16px; border-top: 1px solid #eee; flex-shrink: 0; font-size: 12px;
}
.v2-detail-pagination .v2-pag-info { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-weight: 500; }
.v2-detail-pagination .v2-pag-nav { display: flex; align-items: center; gap: 4px; }
.v2-detail-pagination .v2-pag-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; padding: 0; background: #f3f4f6;
    border: 1px solid #d1d5db; border-radius: 4px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #374151), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); cursor: pointer;
    transition: all 150ms ease;
}
.v2-detail-pagination .v2-pag-btn:hover:not(:disabled) { background: var(--v2-primary, #1e3a5f); color: #fff; border-color: var(--v2-primary, #1e3a5f); }
.v2-detail-pagination .v2-pag-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.v2-detail-pagination .v2-pag-page { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #475569), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-weight: 600; padding: 0 6px; }

/* Detail empty/loading states */
.v2-detail-empty {
    flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 40px 16px; text-align: center;
}
.v2-detail-loading {
    position: absolute; top: 50px; left: 0; right: 0; bottom: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.85); z-index: 20;
}
.v2-detail-empty .v2-empty-icon { color: #cbd5e1; margin-bottom: 16px; }
.v2-detail-empty .v2-empty-title { font-size: 14px; font-weight: 600; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #334155), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); margin: 0 0 6px; }
.v2-detail-empty .v2-empty-text { font-size: 13px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #94a3b8), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); margin: 0; max-width: 220px; }
.v2-detail-loading .spinner-border { width: 16px; height: 16px; border-width: 2px; color: var(--v2-primary, #1e3a5f); }
.v2-detail-loading .v2-loading-text { font-size: 13px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #94a3b8), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }

/* Smooth state transitions — opacity only, no movement */
.v2-detail-body > *, .v2-detail-empty, .v2-detail-loading { animation: v2FadeIn 150ms ease; }
@keyframes v2FadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Hide Paid column on smaller screens to prevent horizontal scroll */
@media (max-width: 1599px) {
    .v2-hide-paid { display: none !important; }
}

/* Hide detail panel on narrow screens */
@media (max-width: 1199px) {
    .v2-batch-detail-panel { display: none; }
}
/* When sidebar is collapsed, show detail panel at a narrower breakpoint */
@media (min-width: 1020px) and (max-width: 1199px) {
    .v2-sidebar-collapsed .v2-batch-detail-panel { display: flex; }
}

/* Letter editor — contenteditable area */
/* Dim the batch modal when letter editor is stacked on top */
#v2LetterEditorModal { background: rgba(0, 0, 0, 0.5); }

/* Dark tab styling (shared by batch modal + letter editor) */
.v2-batch-tabs .nav-link,
#v2LetterEditorModal .nav-tabs .nav-link { color: var(--v2-sidebar-bg); background: #fff; }
.v2-batch-tabs .nav-link.active,
#v2LetterEditorModal .nav-tabs .nav-link.active { background: var(--v2-sidebar-bg); color: #fff; border-color: var(--v2-sidebar-bg); }
.v2-batch-tabs .nav-link.disabled { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #adb5bd), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); cursor: not-allowed; }

/* Editable body — mirrors `.v2-modal .form-control`: white field on the
   tinted modal surface so it reads as the place to type (editable = white).
   Falls back to white when rendered outside a `.v2-modal` scope. */
.v2-letter-editor {
    border: 1px solid #bbb;
    border-radius: 4px;
    padding: 12px;
    flex: 1 1 auto;
    min-height: 200px;
    overflow-y: auto;
    overflow-x: hidden;
    word-wrap: break-word;
    overflow-wrap: break-word;
    width: 100%;
    box-sizing: border-box;
    font-size: 14px;
    line-height: 1.6;
    outline: none;
    background: var(--v2-field-bg, #fff);
}
#v2PdfEditor { flex: 1 1 auto; min-height: 200px; resize: none; }
.v2-letter-editor:focus {
    border-color: var(--v2-primary);
    background: var(--v2-field-bg-focus, #fff);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--v2-primary) 20%, transparent);
}

/* Formatting toolbar — distinct horizontal strip with its own background and
   borders, sitting between the header and the editor body. */
.v2-editor-toolbar {
    background: #f1f3f5;
    border-top: 1px solid #dee2e6;
    border-bottom: 1px solid #dee2e6;
    padding: 6px 12px !important;
    margin: 0 !important;
}
.v2-editor-toolbar .btn { min-width: 30px; padding: 2px 6px; font-size: 13px; }

/* Letter editor email header — darker tinted block so the "to/from/subject"
   meta block reads clearly distinct from the editor body. Position-relative so
   the collapse-toggle button can anchor to the top-right corner. */
.v2-le-header { position: relative; padding: 8px 12px; border-bottom: 1px solid #dee2e6; background: #e9ecef; font-size: 13px; }
.v2-le-header-row { cursor: pointer; }
.v2-le-header-row:hover .v2-le-header-value { background: rgba(0,0,0,0.04); border-radius: 3px; }
.v2-le-header-value { padding: 2px 6px; }
.v2-le-row-readonly { cursor: default; }
.v2-le-row-readonly:hover .v2-le-header-value { background: transparent; }
.v2-le-header-row { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.v2-le-header-row:last-child { margin-bottom: 0; }
.v2-le-header-label { min-width: 52px; font-weight: 600; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #555), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); flex-shrink: 0; }
.v2-le-header-field { display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0; }
.v2-le-header-input { border: 1px solid #ced4da; border-radius: 3px; padding: 2px 6px; font-size: 13px; height: 26px; flex: 1; min-width: 0; }
.v2-le-header-input.v2-le-from-user { max-width: 140px; flex: 0 0 auto; }
.v2-le-at { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #888), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-size: 13px; }
.v2-le-header-select { border: 1px solid #ced4da; border-radius: 3px; padding: 2px 4px; font-size: 13px; height: 26px; flex: 1; min-width: 0; }
.v2-le-header-value { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #333), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Collapse toggle — small chevron in the top-right corner of the header.
   Rotates 180° when the header is in collapsed mode. Reserves rightmost
   ~28px of header padding so the chevron never overlaps content. */
.v2-le-header { padding-right: 32px; }
.v2-le-header-toggle {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: 0;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    cursor: pointer;
    border-radius: 4px;
    z-index: 1;
}
.v2-le-header-toggle:hover { background: rgba(0,0,0,0.06); color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #212529), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
/* Chevron base SVG points down. Expanded → up arrow (rotate 180°) so it reads
   "click to collapse up". Collapsed → down arrow (no rotation) so it reads
   "click to expand down". */
.v2-le-header-toggle svg { transition: transform 0.18s ease; transform: rotate(180deg); }
.v2-le-header-collapsed .v2-le-header-toggle svg { transform: rotate(0deg); }

/* Compact one-line summary shown when the header is collapsed. Truncates with
   ellipsis on narrow viewports — every section can shrink. The Subject is
   bolded so it's easy to scan. */
.v2-le-header-collapsed { padding: 4px 32px 4px 12px; }
.v2-le-header-summary {
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    overflow: hidden;
    white-space: nowrap;
    line-height: 1.6;
}
.v2-le-header-summary > span { overflow: hidden; text-overflow: ellipsis; }
.v2-le-header-summary-arrow { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); flex-shrink: 0; }
.v2-le-header-summary-from { flex: 0 1 auto; min-width: 0; }
.v2-le-header-summary-to   { flex: 0 1 auto; min-width: 0; }
.v2-le-header-summary-subj { flex: 1 1 auto; min-width: 0; font-weight: 600; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #212529), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }

/* All list card tables get striped rows and hover highlight */
/* First column indent so rows don't sit flush against the card edge */
/* Edge padding on first/last cells. !important is intentional: density rules
   (further down in this file) use the `padding` shorthand which sets all four
   sides, otherwise overriding our edge rule on tie. Edge padding is structural
   layout (don't run flush against the card/modal walls), not a theming knob. */
.v2-list-card .table th:first-child,
.v2-list-card .table td:first-child { padding-left: 16px !important; }
.v2-list-card .table th:last-child,
.v2-list-card .table td:last-child { padding-right: 16px !important; }
.v2-list-card .table { --bs-table-striped-bg: rgba(0, 0, 0, 0.04); }
/* Flat lists (single tbody) — stripe by row */
.v2-list-card .table > tbody > tr:nth-of-type(odd) > td { background-color: var(--bs-table-striped-bg); }
.v2-list-card .table > tbody > tr:hover > td { background-color: rgba(0, 0, 0, 0.08); }
/* Keyboard focus ring — applied to <tr v2-list-row> rows when focused via Tab/arrows. */
.v2-list-card .table > tbody > tr:focus { outline: 2px solid var(--bs-primary, #0d6efd); outline-offset: -2px; }
.v2-list-card .table > tbody > tr:focus { background-color: rgba(13, 110, 253, 0.08); }
.v2-list-card .table > tbody > tr:focus > td { background-color: transparent; }
/* Grouped lists (tbody per item via .v2-row-group) — stripe and hover by tbody */
.v2-list-card .table > tbody.v2-row-group > tr > td { background-color: transparent; }
.v2-list-card .table > tbody.v2-row-group > tr:first-child > td { border-bottom: none !important; }
.v2-list-card .table > tbody.v2-row-group > tr + tr > td { border-top: none !important; border-bottom: none !important; }
.v2-list-card .table > tbody.v2-row-group:nth-of-type(odd) > tr > td { background-color: var(--bs-table-striped-bg); }
.v2-list-card .table > tbody.v2-row-group:hover > tr > td { background-color: rgba(0, 0, 0, 0.08); }

/* Shared expand-row chevron — rotates 90° when the row is open. Used by
   Families list, Dashboard Yahrtzeit detail, family-detail Submissions
   tab, and any future v2 inline-expand surface. */
.v2-expand-chevron { transition: transform .2s ease; }
.v2-expand-chevron.is-open { transform: rotate(90deg); }

/* Avatar stack — overlapping photos */
.v2-avatar-stack { display: inline-flex; align-items: center; }
/* Avatar — sized to stay within the row's text line-height so it never
   forces rows to be taller than other v2 lists. Scales with density. */
.v2-avatar,
.v2-avatar-svg { width: 18px; height: 18px; vertical-align: middle; }
.v2-avatar { border-radius: 50%; object-fit: cover; border: 1px solid #fff; }
[data-v2-density="compact"]  .v2-avatar,
[data-v2-density="compact"]  .v2-avatar-svg { width: 14px; height: 14px; }
[data-v2-density="spacious"] .v2-avatar,
[data-v2-density="spacious"] .v2-avatar-svg { width: 24px; height: 24px; }
.v2-avatar-back { position: relative; z-index: 1; }
.v2-avatar-front { position: relative; z-index: 2; margin-left: -10px; }
.v2-avatar ~ .v2-avatar-fallback { display: none; }
.v2-primary-badge {
    position: absolute;
    bottom: -2px;
    right: -2px;
    background: var(--v2-primary);
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    width: 14px;
    height: 14px;
    line-height: 14px;
    text-align: center;
    border-radius: 50%;
}

/* List toolbar */
.v2-list-toolbar {
    background: #fff;
    border-bottom: 1px solid #dee2e6;
}
.v2-list-toolbar > ng-include {
    display: flex;
    align-items: center;
    gap: .5rem;
}

/* Sortable column headers */
.v2-sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.v2-sortable:hover {
    color: var(--v2-primary);
}
.v2-sort-icon {
    display: inline-block;
    width: 12px;
    height: 12px;
    vertical-align: middle;
    margin-left: 2px;
}
/* Single glyph rotates 180° for descending sort. Lives here (not in
   v2-family-detail.css) so any V2 list table inherits it. */
.v2-sort-icon.v2-sort-desc { transform: rotate(180deg); transition: transform 0.15s ease; }

/* Page info text */
.v2-page-info {
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    white-space: nowrap;
    font-size: 13px;
}
[data-v2-size="sm"] .v2-page-info { font-size: 12px; }
[data-v2-size="lg"] .v2-page-info { font-size: 14px; }

/* Toolbar dropdown menus — size-responsive */
.v2-list-toolbar .dropdown-menu .dropdown-item { font-size: 13px; }
[data-v2-size="sm"] .v2-list-toolbar .dropdown-menu .dropdown-item { font-size: 12px; padding: 3px 10px; }
[data-v2-size="lg"] .v2-list-toolbar .dropdown-menu .dropdown-item { font-size: 14px; padding: 6px 14px; }

/* List pagination — compact */
.v2-list-pagination .pagination {
    margin: 0;
}
.v2-list-pagination .page-link {
    padding: 2px 8px;
}
.v2-page-input-wrap { display: inline-flex; align-items: center; gap: 3px; white-space: nowrap; flex-wrap: nowrap; }
.v2-page-input {
    width: 32px;
    border: 1px solid #ccc;
    border-radius: 3px;
    text-align: center;
    font-size: inherit;
    padding: 0 2px;
    outline: none;
}
.v2-page-input:focus { border-color: var(--v2-primary); }
.v2-page-input::-webkit-outer-spin-button,
.v2-page-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.v2-page-input[type=number] { -moz-appearance: textfield; }

/* Cell link — same color as text, underline on hover */
.v2-cell-link {
    color: inherit !important;
    text-decoration: none !important;
}
.v2-cell-link:hover {
    text-decoration: underline !important;
}

/* Name link — clickable name in list rows */
.v2-name-link {
    color: inherit !important;
    text-decoration: none !important;
    cursor: pointer;
}
.v2-name-link:hover {
    text-decoration: underline !important;
    color: var(--v2-primary) !important;
}

/* ─────────────────────────────────────────────────────────────────────── */
/* Text Color picks the base hue; Contrast (1–10) mixes it with black.     */
/* Final color = color-mix(base, black, --v2-c-blk%) — both controls       */
/* always do something. They compose.                                      */
/* ─────────────────────────────────────────────────────────────────────── */

/* Step 1 — Text Color sets the base (same palette as before). */
body[data-v2-text="default"]  { --v2-text-base: #6c757d; --v2-muted-base: #8a96a8; }
body[data-v2-text="black"]    { --v2-text-base: #000000; --v2-muted-base: #6f6f6f; }
body[data-v2-text="charcoal"] { --v2-text-base: #2c3e50; --v2-muted-base: #5d7082; }
body[data-v2-text="navy"]     { --v2-text-base: #1b2a4a; --v2-muted-base: #546a8e; }
body[data-v2-text="forest"]   { --v2-text-base: #1a3a2a; --v2-muted-base: #4a7a5a; }

/* Step 2 — Contrast: 5 = today's exact look. 1–4 lighten (mix with white).
   6–10 darken (mix with black). */
body[data-v2-contrast="1"]  { --v2-c-wht: 60%; --v2-c-blk: 0%;  }
body[data-v2-contrast="2"]  { --v2-c-wht: 45%; --v2-c-blk: 0%;  }
body[data-v2-contrast="3"]  { --v2-c-wht: 30%; --v2-c-blk: 0%;  }
body[data-v2-contrast="4"]  { --v2-c-wht: 15%; --v2-c-blk: 0%;  }
body[data-v2-contrast="5"]  { --v2-c-wht: 0%;  --v2-c-blk: 0%;  }
body[data-v2-contrast="6"]  { --v2-c-wht: 0%;  --v2-c-blk: 15%; }
body[data-v2-contrast="7"]  { --v2-c-wht: 0%;  --v2-c-blk: 30%; }
body[data-v2-contrast="8"]  { --v2-c-wht: 0%;  --v2-c-blk: 45%; }
body[data-v2-contrast="9"]  { --v2-c-wht: 0%;  --v2-c-blk: 60%; }
body[data-v2-contrast="10"] { --v2-c-wht: 0%;  --v2-c-blk: 75%; }

/* Step 3 — Compose final colors at body. Cascades to every descendant. */
body.theme-v2 {
    --v2-text-color:  color-mix(in srgb, color-mix(in srgb, var(--v2-text-base,  #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    --v2-muted-color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #8a96a8), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    color: var(--v2-text-color);
}

/* Step 4 — Override the spots BS5 hard-codes color on; each cell now
   reads the variable instead of a literal hex, so contrast composes. */
body.theme-v2 .table,
body.theme-v2 .table td,
body.theme-v2 .table th,
body.theme-v2 .table-striped > tbody > tr > td,
body.theme-v2 .card-body,
body.theme-v2 .v2-list-toolbar { color: var(--v2-text-color) !important; }

body.theme-v2 .text-muted { color: var(--v2-muted-color) !important; }


/* Size — affects font size throughout */
[data-v2-size="sm"] { font-size: 12px !important; }
[data-v2-size="sm"] .table,
[data-v2-size="sm"] .table td,
[data-v2-size="sm"] .table th { font-size: 12px !important; }
[data-v2-size="sm"] .v2-list-toolbar,
[data-v2-size="sm"] .btn { font-size: 12px !important; }
[data-v2-size="sm"] .card-header { font-size: 14px !important; }

[data-v2-size="md"] .table,
[data-v2-size="md"] .table td,
[data-v2-size="md"] .table th { font-size: 13px !important; }
[data-v2-size="md"] .card-header { font-size: 16px !important; }

[data-v2-size="lg"] { font-size: 16px !important; }
[data-v2-size="lg"] .table,
[data-v2-size="lg"] .table td,
[data-v2-size="lg"] .table th { font-size: 15px !important; }
[data-v2-size="lg"] .v2-list-toolbar,
[data-v2-size="lg"] .btn { font-size: 14px !important; }
[data-v2-size="lg"] .card-header { font-size: 18px !important; }

/* Density — affects table row padding */
[data-v2-density="compact"] .table td,
[data-v2-density="compact"] .table th { padding: 2px 6px; font-size: 12px; }
[data-v2-density="compact"] .v2-list-toolbar { padding-top: 4px !important; padding-bottom: 4px !important; }

[data-v2-density="comfort"] .table td,
[data-v2-density="comfort"] .table th { padding: 5px 8px; }

[data-v2-density="spacious"] .table td,
[data-v2-density="spacious"] .table th { padding: 10px 8px; }
[data-v2-density="spacious"] .v2-list-toolbar { padding-top: 8px !important; padding-bottom: 8px !important; }

/* Email button alignment in list rows (v2 only) */
.v2-page .table td .btn svg {
    vertical-align: middle;
    display: block;
}

/* ══════════════════════════════════════════════════════════════════════
   V2 MODAL — page-level data modals (add .v2-modal to .modal-content)
   ══════════════════════════════════════════════════════════════════════ */

/* Surface/field colors come from the shared :root palette (--v2-surface,
   --v2-field-bg, --v2-field-bg-focus, --v2-readonly-bg) so modals and the
   Settings pages stay in lockstep. Retune them in :root, not here. */

/* Header — solid primary */
.v2-modal .modal-header { background: var(--v2-primary); color: #fff; padding: 10px 16px; border-bottom: 0; }
.v2-modal .modal-header .modal-title { font-size: 15px; font-weight: 600; color: #fff; }
.v2-modal .modal-header .btn-close { filter: invert(1); opacity: 0.8; }
.v2-modal .modal-header .btn-close:hover { opacity: 1; }

/* Body */
.v2-modal .modal-body { padding: 16px 20px; font-size: 14px; background: var(--v2-surface); }

/* Form labels — bold */
.v2-modal .form-label { font-weight: 600; font-size: 13px; margin-bottom: 4px; }

/* Read-only display value (replacement for .form-control-plaintext, which
   triggers the legacy formControl directive that requires ng-model) */
.v2-modal .v2-readonly-field {
    display: block;
    padding: 6px 10px;
    font-size: 13px;
    background: var(--v2-readonly-bg);
    border: 1.5px solid #bbb;
    border-radius: 4px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #333), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    min-height: 31px;
}

/* ───────────────────────────────────────────────────────────────
   Bill modal — section structure
   ─────────────────────────────────────────────────────────────── */

/* Reserve full body height during the loading state so the modal doesn't
   "pop" from a small spinner-only footprint to the full form once the
   bill loads. The fully-rendered form is taller than this; min-height
   only governs while loading/error states are showing. */
.v2-bill-modal .modal-body,
.v2-payment-modal .modal-body {
    min-height: 480px;
}

/* (Tighter row-rhythm + payment-modal section overrides live in the
   consolidated payment-modal block further down — see line ~1480.) */

/* Each major section gets a clear top border + breathing room so the
   modal reads as distinct chapters rather than a wall of inputs. */
.v2-bill-section {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid #dee2e6;
}
.v2-bill-section.v2-bill-section-first {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* Section heading: stronger than text-muted-uppercase. Reads as a chapter
   title, not a form-field label. */
.v2-bill-section-header {
    font-size: 13px;
    font-weight: 700;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    margin-bottom: 10px;
    text-transform: none;
    letter-spacing: 0;
    line-height: 1.4;
}

/* Collapsible section heading (Additional Info) — anchor styling */
.v2-bill-section-toggle,
.v2-bill-section-toggle:hover,
.v2-bill-section-toggle:focus {
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    text-decoration: none;
    cursor: pointer;
}
.v2-bill-section-toggle:hover { color: var(--v2-primary, #0d6efd); }


/* Family field display: name on the left, Him/Her/Both pills anchored
   at the RIGHT edge of the column. min-height matches a form-control so
   the row aligns with the Address dropdown sitting next to it. */
.v2-modal .v2-bill-family-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 10px;
    background: #f6f8fa;
    border: 1px solid #e3e6ea;
    border-radius: 4px;
    min-height: 31px;
    font-size: 13px;
}
.v2-modal .v2-bill-family-name {
    font-weight: 600;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-modal .v2-bill-family-himher .btn-group .btn {
    padding: 1px 8px;
    font-size: 11px;
    line-height: 1.4;
}

/* ───────────────────────────────────────────────────────────────
   Bill modal line-items grid (CSS Grid, not <table>).
   Switched away from HTML table because table-layout:fixed +
   colspan re-render kept reflowing column widths when the
   inline note editor toggled. CSS Grid keeps every row's cell
   widths identical and keeps the note row from disturbing them.
   ─────────────────────────────────────────────────────────────── */

/* Default columns: Subcategory | Item | Qty | Unit | Discount | Ext | Actions
   Fixed-column budget is sized so the grid fits the modal-xl body even when
   both Due Date and Class are present AND the body's vertical scrollbar is
   active (the worst case — eats ~15px of horizontal width). Subcat keeps
   its 220px min; financial columns are right-sized for typical amounts. */
.v2-bill-grid {
    --c-due:    0px;
    --c-class:  0px;
    --c-subcat: minmax(220px, 1fr);
    --c-item:   140px;
    --c-qty:    60px;
    --c-unit:   80px;
    --c-disc:   80px;
    --c-ext:    80px;
    --c-act:    90px;
}
.v2-bill-grid.v2-bill-grid-with-due   { --c-due:   115px; }
.v2-bill-grid.v2-bill-grid-with-class { --c-class: 140px; }

.v2-bill-grid .v2-bill-grid-row {
    display: grid;
    grid-template-columns:
        var(--c-subcat) var(--c-item)
        var(--c-qty) var(--c-unit) var(--c-disc) var(--c-ext) var(--c-act);
    column-gap: 8px;
    padding: 4px 0;
    align-items: center;
}
.v2-bill-grid.v2-bill-grid-with-due .v2-bill-grid-row {
    grid-template-columns:
        var(--c-due) var(--c-subcat) var(--c-item)
        var(--c-qty) var(--c-unit) var(--c-disc) var(--c-ext) var(--c-act);
}
.v2-bill-grid.v2-bill-grid-with-class .v2-bill-grid-row {
    grid-template-columns:
        var(--c-subcat) var(--c-item) var(--c-class)
        var(--c-qty) var(--c-unit) var(--c-disc) var(--c-ext) var(--c-act);
}
.v2-bill-grid.v2-bill-grid-with-due.v2-bill-grid-with-class .v2-bill-grid-row {
    grid-template-columns:
        var(--c-due) var(--c-subcat) var(--c-item) var(--c-class)
        var(--c-qty) var(--c-unit) var(--c-disc) var(--c-ext) var(--c-act);
}

.v2-bill-grid .v2-bill-grid-head {
    border-bottom: 1px solid #dee2e6;
    padding-bottom: 6px;
    font-weight: 600;
}
/* Striped lines: each line + its inline note share the stripe color, so
   the visual grouping is implicit without needing a horizontal divider. */
.v2-bill-grid .v2-bill-grid-line {
    /* no border-bottom — stripes do the work */
}
.v2-bill-grid .v2-bill-stripe {
    background-color: #f5f7fa;
}

/* Cell content gets clipped to its grid track to prevent overflow.
   The Subcategory typeahead cell is the exception — its popup must escape. */
.v2-bill-grid .v2-bill-grid-row > * {
    min-width: 0;            /* lets grid item shrink, allow ellipsis on inputs */
}

/* Breathing room on the right of the row-action buttons (note + delete)
   so they don't sit flush against the modal-body edge. */
.v2-bill-grid .v2-bill-actions,
.v2-payment-grid .v2-payment-actions {
    padding-right: 8px;
}

/* ───────────────────────────────────────────────────────────────
   Payment modal — line items grid
   Simpler than the bill grid (no Qty/Unit/Discount/Ext columns):
   Category | (Class) | Amount | Actions. Reuses bill-grid's stripe,
   typeahead, and note-row styles via shared classes in the template.
   ─────────────────────────────────────────────────────────────── */
.v2-payment-grid {
    --c-class:  0px;
    --c-cat:    minmax(220px, 1fr);
    --c-amt:    120px;
    --c-act:    90px;
}
.v2-payment-grid.v2-payment-grid-with-class { --c-class: 140px; }

.v2-payment-grid .v2-payment-grid-row {
    display: grid;
    grid-template-columns: var(--c-cat) var(--c-amt) var(--c-act);
    column-gap: 8px;
    padding: 4px 0;
    align-items: center;
}
.v2-payment-grid.v2-payment-grid-with-class .v2-payment-grid-row {
    grid-template-columns: var(--c-cat) var(--c-class) var(--c-amt) var(--c-act);
}

.v2-payment-grid .v2-payment-grid-head {
    /* Match form-label styling — same weight/size/color as field labels
       elsewhere in the modal so column headers read as labels, not as a
       muted table header. */
    padding-bottom: 4px;
    font-weight: 600;
    font-size: 13px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
}

/* Tighter section spacing inside the V2 Payment modal — the bill modal's
   default 18px+14px+border between sections feels too airy here because
   payment is a flatter form (no Schedule, no per-line Qty/Unit). Halve
   the breathing room without removing the divider line. */
.v2-payment-modal .v2-bill-section {
    margin-top: 8px;
    padding-top: 8px;
}
.v2-payment-modal .v2-bill-section.v2-bill-section-first {
    margin-top: 0;
    padding-top: 0;
}
/* Smaller row gutter for payment rows — when columns wrap onto a new
   line (mobile / md and below), the default 1rem (g-3) or 0.5rem (g-2)
   gutter creates visible empty bands. Tighten across the board for
   anything tagged `.v2-payment-row-tight`. */
.v2-payment-modal .row.v2-payment-row-tight {
    --bs-gutter-y: 0.5rem;
}

/* Mobile (≤md): force a SINGLE uniform vertical rhythm everywhere —
   between fields, between sections, between rows in nested gray boxes,
   between the Department/Card box and the Receipt-info box. The bill-
   modal-inherited section borders + asymmetric margins create visible
   "all over the place" gaps when stacked, so flatten them here. */
@media (max-width: 767.98px) {
    .v2-bill-modal .row[class*="g-"],
    .v2-payment-modal .row[class*="g-"] {
        --bs-gutter-y: 0.5rem !important;
    }
    /* Override a global leak from blackout-templates.css that adds
       `margin-bottom: 20px` to .col-md-6 below 768px. That extra 20px
       is what made the gap between Honor/Memory and Solicitor (and any
       other stacked col on mobile) feel uneven and oversized. Same
       leak hits the bill modal so we cover both. */
    .v2-bill-modal .row > [class*="col-"],
    .v2-payment-modal .row > [class*="col-"] {
        margin-bottom: 0 !important;
    }
    /* No HR/border separators on mobile — keep the field rhythm even.
       Section gap = row gutter (8px total). */
    .v2-bill-modal .v2-bill-section,
    .v2-payment-modal .v2-bill-section {
        margin-top: 4px;
        padding-top: 4px;
        border-top: none;
    }
    .v2-bill-modal .v2-bill-section.v2-bill-section-first,
    .v2-payment-modal .v2-bill-section.v2-bill-section-first {
        margin-top: 0;
        padding-top: 0;
    }
    /* Drop the modal-footer top divider too. */
    .v2-bill-modal .modal-footer,
    .v2-payment-modal .modal-footer { border-top: none; }
    /* And the gray boxed borders around Department/Card + Totals/Receipt
       info — on mobile they read as visual noise once the boxes stack. */
    .v2-bill-modal .v2-bill-charge-box,
    .v2-bill-modal .v2-bill-totals,
    .v2-payment-modal .v2-bill-charge-box,
    .v2-payment-modal .v2-bill-totals {
        border: none;
        background: transparent;
        padding: 0;
    }
    .v2-bill-modal .v2-bill-summary-row,
    .v2-payment-modal .v2-bill-summary-row {
        flex-direction: column;
        gap: 8px;
    }
    .v2-bill-modal .v2-bill-charge-box,
    .v2-bill-modal .v2-bill-totals,
    .v2-payment-modal .v2-bill-charge-box,
    .v2-payment-modal .v2-bill-totals {
        flex: 1 1 auto;
        width: 100%;
        min-width: 0;
    }
    /* Receipt/totals info inner rows — match the universal 8px rhythm. */
    .v2-bill-modal .v2-bill-totals .d-flex.mb-1,
    .v2-bill-modal .v2-bill-totals > .mb-1,
    .v2-payment-modal .v2-bill-totals .d-flex.mb-1,
    .v2-payment-modal .v2-bill-totals > .mb-1 {
        margin-bottom: 8px !important;
    }
}

/* Typeahead dropdown styling inside the V2 Payment modal — covers both
   the desktop grid (`.v2-payment-grid .v2-bill-typeahead-cell`) and the
   mobile stacked-card inputs (which now use `.v2-bill-typeahead-cell`
   wrappers too). Mirrors the bill-modal popup. */
.v2-payment-modal .v2-bill-typeahead-cell .dropdown-menu {
    display: block;
    background: #fff;
    border: 1px solid rgba(0,0,0,.15);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgba(0,0,0,.15);
    padding: 4px 0;
    margin-top: 2px;
    max-height: 280px;
    overflow-y: auto;
    font-size: 13px;
    min-width: 280px;
    z-index: 1080;
}
.v2-payment-modal .v2-bill-typeahead-cell .dropdown-menu > li > a {
    display: block;
    padding: 4px 12px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #212529), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    text-decoration: none;
    white-space: normal;
}
.v2-payment-modal .v2-bill-typeahead-cell .dropdown-menu > li.active > a,
.v2-payment-modal .v2-bill-typeahead-cell .dropdown-menu > li > a:hover {
    background: #e9ecef;
    color: #0d6efd;
}
/* Trim the gap below labels too. */
.v2-payment-modal .form-label {
    margin-bottom: 2px;
}

/* Department/Card + Receipt info row — tighter padding and margins so
   the two gray boxes wear closer to their content (less wasted vertical
   space below the inputs and around the receipt checkboxes). */
.v2-payment-modal .v2-bill-summary-row {
    margin-top: 8px !important;
}
.v2-payment-modal .v2-bill-charge-box,
.v2-payment-modal .v2-bill-totals {
    padding: 6px 10px;
}
.v2-payment-modal .v2-bill-totals .form-check {
    margin-bottom: 4px;
    min-height: 0;          /* override Bootstrap's 1.5rem default */
}
.v2-payment-modal .v2-bill-totals .form-check:last-child { margin-bottom: 0; }
.v2-payment-modal .v2-bill-totals > .mb-2 { margin-bottom: 4px !important; }

/* Receipt-number pill — sits inline next to "Issue Receipt" checkbox.
   Editable (server assigns when blank), but visually compact like a chip. */
.v2-payment-modal .v2-receipt-pill {
    width: 64px;
    height: 22px;
    padding: 0 8px;
    font-size: 12px;
    line-height: 1;
    border: 1px solid rgba(0,0,0,.15);
    border-radius: 999px;
    background: #fff;
    color: #0d6efd;
    text-align: center;
    -moz-appearance: textfield;
}
.v2-payment-modal .v2-receipt-pill::-webkit-outer-spin-button,
.v2-payment-modal .v2-receipt-pill::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.v2-payment-modal .v2-receipt-pill::placeholder { color: #9aa0a6; font-style: italic; }
.v2-payment-modal .v2-receipt-pill:focus {
    outline: none;
    border-color: #0d6efd;
    box-shadow: 0 0 0 2px rgba(13,110,253,.18);
}
.v2-payment-grid .v2-payment-stripe { background-color: #f5f7fa; }
.v2-payment-grid .v2-payment-row-charged { background-color: #f4faf6; }
/* Applied (locked) line in the parent payment grid — soft blue tint so
   it reads as a different category of row from free splits. */
.v2-payment-grid .v2-payment-row-applied,
.v2-payment-modal .v2-bill-line-card.v2-payment-row-applied {
    background-color: #f5f8ff;
}

/* ───────────────────────────────────────────────────────────────
   Apply-bill banner — sits above the PaymentDetails section in the
   parent payment modal. Click opens the stacked apply dialog.
   Native <button> so it inherits all the right keyboard/SR semantics
   for free; we just strip the button chrome and keep our own look.
   ─────────────────────────────────────────────────────────────── */
.v2-apply-banner {
    background: #e7f3ff;
    border: 1px solid #cfe2ff;
    border-radius: 6px;
    padding: 8px 12px;
    margin-bottom: 10px;
    cursor: pointer;
    display: flex;
    align-items: center;
    flex-wrap: wrap;       /* ensure the trailing arrow doesn't get clipped on narrow viewports */
    gap: 8px;
    font-size: 13px;
    color: #084298;
    transition: background 0.15s, box-shadow 0.15s;
    user-select: none;
    width: 100%;
    text-align: left;
    line-height: 1.4;
}
.v2-apply-banner:hover {
    background: #d6e7fb;
}
.v2-apply-banner:focus,
.v2-apply-banner:focus-visible {
    background: #d6e7fb;
    outline: none;
    box-shadow: 0 0 0 3px rgba(13, 110, 253, .35);
}
.v2-apply-banner-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #198754;
    color: #fff;
    flex-shrink: 0;
}
.v2-apply-banner-icon-info { background: #0d6efd; }
.v2-apply-banner-arrow { display: inline-flex; align-items: center; }

/* Locked banner — the payment was auto-charged for its bill, so the
   application is read-only. Reads as a neutral label, not an action:
   no pointer, no hover/focus lift. */
.v2-apply-banner-locked,
.v2-apply-banner-locked:hover,
.v2-apply-banner-locked:focus,
.v2-apply-banner-locked:focus-visible {
    background: #f1f3f5;
    border-color: #dee2e6;
    color: #495057;
    cursor: default;
    box-shadow: none;
}
.v2-apply-banner-locked .v2-apply-banner-icon { background: #6c757d; }
.v2-apply-banner-lock { display: inline-flex; align-items: center; opacity: .7; }

/* Locked applied row — desktop grid + mobile card share these. The
   chip carries the invoice number; the lock icon reinforces "edit via
   the apply dialog, not here". */
.v2-payment-grid .v2-pd-applied {
    background: #f5f8ff;
    border-radius: 4px;
    padding: 6px 8px;
    display: flex;
    align-items: center;
    gap: 6px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    font-size: 13px;
    min-height: 30px;
}
.v2-payment-grid .v2-pd-applied-cat {
    flex-grow: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-payment-grid .v2-pd-applied-class {
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #5b626a), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));       /* darkened from #6c757d for AA contrast vs #f5f8ff */
    font-size: 12px;
    padding: 6px 8px;
}
.v2-payment-grid .v2-pd-applied-amt {
    background: #f5f8ff;
    padding: 6px 8px;
    border-radius: 4px;
    font-weight: 500;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    font-size: 13px;
}
.v2-payment-grid .v2-pd-lock {
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    margin-left: auto;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
}
.v2-pd-chip {
    background: #cfe2ff;
    color: #084298;
    border-radius: 10px;
    padding: 2px 8px;     /* +1px vertical for better optical centering inside the 30px row */
    font-size: 11px;
    font-weight: 600;
    flex-shrink: 0;
    white-space: nowrap;
}

/* ───────────────────────────────────────────────────────────────
   Apply-bill modal — stacked on top of the payment modal. Uses the
   default `.v2-modal .modal-header` style (background:
   var(--v2-primary)), so the header picks up the user's theme color
   from [data-v2-color="…"] just like bill + payment modals.
   ─────────────────────────────────────────────────────────────── */

/* Apply table — column widths tuned for desktop modal-xl. */
.v2-apply-bill-table {
    font-size: 13px;
    margin-bottom: 0;
}
.v2-apply-bill-table th {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    padding: 6px 8px;
    border-bottom: 1px solid #dee2e6;
    background: #f8f9fa;
    position: sticky;
    top: 0;
    z-index: 1;
}
.v2-apply-bill-table td {
    padding: 8px;
    vertical-align: middle;
    border-top: 1px solid #f0f0f0;
}
.v2-apply-bill-table .v2-apply-col-date  { width: 90px; }
.v2-apply-bill-table .v2-apply-col-amt   { width: 110px; }
.v2-apply-bill-table .v2-apply-col-due   { width: 110px; }
.v2-apply-bill-table .v2-apply-col-inv   { width: 100px; }
/* Apply cell holds the checkbox + amount input side-by-side. Wider than
   the old amount-only column to fit the checkbox without crowding. */
.v2-apply-bill-table .v2-apply-col-apply { width: 170px; }
.v2-apply-bill-table .v2-apply-cell {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}
.v2-apply-bill-table .v2-apply-cell .v2-apply-check {
    flex: 0 0 auto;
    margin: 0;
}
/* Truncate long Category / Class strings with ellipsis (full text in
   `title=""`) so a wide row never wraps to two lines and breaks the
   table's visual rhythm. */
.v2-apply-bill-table .v2-apply-col-cat,
.v2-apply-bill-table .v2-apply-col-class {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 0;          /* let table layout compute, but allow ellipsis */
}
.v2-apply-bill-table .v2-apply-input {
    max-width: 110px;
    margin-left: auto;
}
.v2-apply-bill-table .v2-apply-row-applied {
    background-color: #f5f8ff;
}
.v2-apply-bill-table .v2-apply-row-applied td {
    border-top-color: #cfe2ff;
}
/* Transparent (not white) so the row separator doesn't paint a faint
   stripe over an applied row's blue tint. */
.v2-apply-bill-group:not(:first-of-type) {
    border-top: 4px solid transparent;
}

/* Mobile per-bill card. */
.v2-apply-bill-card {
    background: #fff;
}
.v2-apply-bill-line {
    padding: 8px 0;
    border-top: 1px solid #f0f0f0;
}
.v2-apply-bill-line:first-child { border-top: none; }
.v2-apply-bill-line.v2-apply-row-applied {
    background-color: #f5f8ff;
    border-radius: 4px;
    padding: 8px;
    margin: 0 -4px;
}
/* Mobile touch targets — 44×44 per WCAG 2.5.5. Default Bootstrap
   form-check-input is 16×16 and form-control-sm is ~31px tall. */
.v2-apply-check-mobile {
    width: 24px !important;
    height: 24px !important;
    margin-right: 4px;
}
.v2-apply-input-mobile {
    min-height: 44px;
    font-size: 15px;
    padding: 8px 12px;
}
.v2-apply-bill-line .v2-apply-check-mobile {
    /* Pad the surrounding row so the actual hit area reaches 44px. */
    box-sizing: content-box;
}
/* Mobile pencil button on locked applied rows (parent payment modal):
   same 44px hit area for the edit-applied-bills affordance. */
.v2-payment-modal .v2-bill-line-card .v2-pd-edit-mobile {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Summary block at the bottom of the apply dialog — right-aligned and
   bounded so labels sit next to their values rather than stretching the
   full modal width. */
.v2-apply-summary {
    background: #f8f9fa;
    border: 1px solid #e8ebef;
    border-radius: 6px;
    padding: 10px 14px;
    font-size: 13px;
    min-width: 280px;
    max-width: 360px;
}
.v2-apply-summary > div { padding: 2px 0; }

.v2-payment-grid .v2-payment-grid-row > * { min-width: 0; }
.v2-payment-grid .v2-payment-actions { text-align: right; white-space: nowrap; }
.v2-bill-typeahead-cell { position: relative; }

/* Subcategory / Class typeahead popup. Keyed off .v2-bill-typeahead-cell ALONE:
   this used to list every ancestor scope that happened to use it (bill grid,
   payment grid, donation wizard, payment modal) with identical declarations in
   each, so a new consumer rendered as raw blue <a> links until someone
   remembered to add another selector. One rule, any wrapper. */
.v2-bill-typeahead-cell .dropdown-menu {
    display: block;
    background: #fff;
    border: 1px solid rgba(0,0,0,.15);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgba(0,0,0,.15);
    padding: 4px 0;
    margin-top: 2px;
    max-height: 280px;
    overflow-y: auto;
    font-size: 13px;
    min-width: 280px;
    z-index: 1080;
}
.v2-bill-typeahead-cell .dropdown-menu > li > a {
    display: block;
    padding: 4px 12px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #212529), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    text-decoration: none;
    white-space: normal;
}
.v2-bill-typeahead-cell .dropdown-menu > li.active > a,
.v2-bill-typeahead-cell .dropdown-menu > li > a:hover {
    background: #e9ecef;
    color: #0d6efd;
}
.v2-bill-grid .v2-bill-typeahead-input,
.v2-payment-grid .v2-bill-typeahead-input {
    text-overflow: ellipsis;
}

/* Charged-row visual: subtle green tint on the line wrapper */
.v2-bill-grid .v2-bill-row-charged,
.v2-bill-line-card.v2-bill-row-charged {
    background-color: #f4faf6;
}

/* Note editor row — sits below its line, slightly indented. The row
   inherits its parent .v2-bill-grid-line's stripe color naturally so the
   note visually belongs to the line above. Subtle left indent + accent
   reinforces the connection. */
.v2-bill-grid .v2-bill-note-row {
    padding: 4px 10px 8px 16px;
    margin-left: 12px;
    border-left: 3px solid #c0c5cc;
}
.v2-bill-grid .v2-bill-note-row .form-label { font-weight: 500; }

/* Summary row: Department/Card box at 75% on the left, Totals box at
   25% on the right. Both boxes share the same border/padding/background
   so they read as a coherent pair. Totals aligns to the right edge of
   the row (not to the Ext Price column above). */
.v2-bill-summary-row {
    display: flex;
    align-items: stretch;
    gap: 16px;
}
.v2-bill-charge-box,
.v2-bill-totals {
    background: #e7ebf0;          /* darker than section bg for clear demarcation */
    border: 1px solid #c1c8d1;
    border-radius: 6px;
    padding: 10px 14px;
}
/* Plain read-only value display — like the Ext Price cell but not bold.
   No box, no background, transparent. Matches the vertical size of a
   form-control-sm so it aligns neatly in a horizontal row. */
.v2-modal .v2-bill-readonly-value {
    font-size: 13px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    padding: 4px 2px;
    min-height: calc(1.5em + .5rem + 2px);  /* matches form-control-sm */
    line-height: 1.5;
}
.v2-bill-charge-box {
    flex: 0 0 calc(75% - 8px);    /* 75% width minus half the flex gap */
    min-width: 0;                 /* lets selects inside truncate cleanly */
}
.v2-bill-totals {
    flex: 0 0 calc(25% - 8px);    /* 25% width */
    font-size: 14px;
    line-height: 1.5;
}
.v2-bill-total-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 24px;
}
.v2-bill-total-row > :last-child {
    font-weight: 700;
    font-variant-numeric: tabular-nums;  /* digit columns line up vertically */
}
.v2-bill-total-row-final {
    border-top: 1px solid #b5bcc6;
    margin-top: 4px;
    padding-top: 4px;
}
.v2-bill-total-billed { color: #dc3545; }
.v2-bill-total-paid   { color: #0d6efd; }
.v2-bill-total-due    { color: #c48700; }

/* Mobile: stack the boxes, drop the actions-column padding */
@media (max-width: 767.98px) {
    .v2-bill-summary-row {
        flex-direction: column;
        padding-right: 0;
    }
    .v2-bill-totals { min-width: 0; }
}

/* Form inputs — pronounced borders + sunken/tinted background so editable
   data fields read as data, not decoration. Background is darker than the
   modal body, with a subtle inset shadow to suggest depth. Focus brightens
   the field to white and adds a primary-color ring. */
/* Shared editable-field look — ONE definition consumed by V2 modals AND any
   page wearing .setv2-page (Settings, Forms). A page opts in by using
   that class on its root, not by adding its own selector here. */
.v2-modal .form-control,
.v2-modal .form-select,
.setv2-page .form-control,
.setv2-page .form-select {
    border: 1.5px solid #bbb;
    background-color: var(--v2-field-bg);   /* not `background` — keep the .form-select chevron image */
    font-size: 13px;
}
.v2-modal .form-control:focus,
.v2-modal .form-select:focus,
.setv2-page .form-control:focus,
.setv2-page .form-select:focus {
    border-color: var(--v2-primary);
    background-color: var(--v2-field-bg-focus);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--v2-primary) 20%, transparent);
}

/* Checkboxes vs. switches/radios — deliberately different controls:
   • A plain CHECKBOX is a data field → render it like the white inputs
     (white box, neutral border, dark tick). NOT theme-colored.
   • A SWITCH (the on/off slider) is a stateful apparatus → its ON state
     reads as the accent (themed). Radios likewise stay themed.
   Focus ring matches the input-field accent ring. Single source in :root. */

/* Native controls (Settings uses these). accent-color only reaches natively
   rendered controls: plain checkboxes → neutral white; radios → accent.
   (Switches are Bootstrap-custom, so accent-color never touches them.) */
body.theme-v2 input[type="radio"]    { accent-color: var(--v2-primary); }
body.theme-v2 input[type="checkbox"] { accent-color: var(--v2-field-bg); }

/* Bootstrap custom controls. Default checked = themed (covers radios + the
   switch re-assert below). */
body.theme-v2 .form-check-input:checked {
    background-color: var(--v2-primary);
    border-color: var(--v2-primary);
}
/* Plain checkbox (square — NOT a switch): white field look + dark tick. The
   tick color is fixed (#1a1d22 = --v2-ink) because SVG data-URIs can't read
   CSS vars; it's a neutral mark, not a theme color. */
body.theme-v2 .form-check-input[type="checkbox"]:checked {
    background-color: var(--v2-field-bg);
    border-color: #bbb;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%231a1d22' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
/* Switch (slider) stays themed — re-asserted after the checkbox rule (the
   switch input is also type=checkbox). Restores the accent track + white knob. */
body.theme-v2 .form-switch .form-check-input:checked {
    background-color: var(--v2-primary);
    border-color: var(--v2-primary);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
}
body.theme-v2 .form-check-input:focus {
    border-color: var(--v2-primary);
    box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--v2-primary) 25%, transparent);
}

/* Radio / check labels */
.v2-modal .form-check-label { font-size: 13px; }

/* Footer */
.v2-modal .modal-footer { padding: 8px 16px; border-top: 1px solid #eee; }
.v2-modal .modal-footer .btn { min-width: 80px; }

/* Renew Bill — info header (Family, Subcategory) */
.v2-renew-modal .v2-renew-info {
    background: #f7f9fc;
    border-bottom: 1px solid #e5e7eb;
    font-size: 13px;
}
.v2-renew-modal .v2-renew-info .small { font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }
/* End Date — shown as a computed value, not an editable input */
.v2-renew-modal .v2-renew-enddate {
    padding: 6px 10px;
    font-size: 13px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #333), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    background: #f7f9fc;
    border: 1px dashed #d0d4dc;
    border-radius: 4px;
    min-height: calc(1.5em + 12px);
}

/* ══════════════════════════════════════════════════════════════════════
   V2 CARD MODAL — unified Add Payment Method (CC | eCheck)
   ══════════════════════════════════════════════════════════════════════ */

/* Mode toggle (pill-row: Credit Card | eCheck) */
.v2-card-modal .v2-card-modetoggle {
    gap: 6px;
    border-bottom: 1px solid #e5e7eb;
    padding-bottom: 12px;
}
.v2-card-modal .v2-card-modebtn {
    background: #f3f5f8;
    border: 1px solid #dbe0e6;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #555), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    font-size: 13px;
    font-weight: 600;
    padding: 8px 10px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.v2-card-modal .v2-card-modebtn:hover:not(.active) {
    background: #eaeef3;
    border-color: #c9d0d9;
}
.v2-card-modal .v2-card-modebtn.active {
    background: var(--v2-primary);
    border-color: var(--v2-primary);
    color: #fff;
}

/* Card number input w/ brand logo inside input-group */
.v2-card-modal .v2-cc-number { letter-spacing: 1px; font-variant-numeric: tabular-nums; }
.v2-card-modal .v2-cc-brand {
    min-width: 70px;
    justify-content: center;
    padding: 2px 8px;
    background: #f8f9fa;
}
.v2-card-modal .v2-cc-brand svg { display: block; }
.v2-card-modal .v2-cc-brand-empty { color: #bbb; font-size: 16px; }

/* Loading overlay covering the modal body during the gateway fetch (edit
   mode). position:absolute positions against .modal-body (which has
   .position-relative). Keeps the form visible underneath (faded) so the
   chrome doesn't collapse-then-expand as the data arrives. */
.v2-card-loading-overlay {
    position: absolute;
    inset: 0;
    background: rgba(255, 255, 255, 0.85);
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Cardholder-name spouse-suggestion dropdown */
/* Typeahead dropdown — generic pattern usable in any v2 modal.
   Attach to the <ul> inside a .position-relative wrapper beneath an <input>.
   Previously scoped to .v2-card-modal only; generalized so Chai Club enroll
   and any future modal can reuse it. */
.v2-card-namehint {
    position: absolute;
    z-index: 1055;
    top: 100%;
    left: 0;
    right: 0;
    list-style: none;
    margin: 2px 0 0;
    padding: 2px 0;
    background: #fff;
    border: 1px solid #d0d4dc;
    border-radius: 4px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    max-height: 160px;
    overflow-y: auto;
    min-width: 200px;
    font-size: 13px;
}
.v2-card-namehint li {
    padding: 6px 10px;
    cursor: pointer;
}
.v2-card-namehint li:hover,
.v2-card-namehint li.v2-namehint-active {
    background: color-mix(in srgb, var(--v2-primary) 10%, transparent);
}

/* ══════════════════════════════════════════════════════════════════════
   V2 ALERT — system dialogs (v2ModalService confirm/alert/warn/input)
   These are short, single-question prompts. NOT to be confused with
   the larger working modals (deposit picker, QB mapper, settings panel)
   which use .v2-modal-header (dark sidebar color, defined above).
   ══════════════════════════════════════════════════════════════════════ */
.v2-alert-header {
    padding: 12px 16px;
    border-bottom: 0;
}
.v2-alert-header .modal-title {
    font-size: 15px;
    font-weight: 600;
}
.v2-alert-question { background: #e8f0fe; color: #1a56db; }
.v2-alert-info     { background: #e0f2fe; color: #0369a1; }
.v2-alert-warning  { background: #fef3c7; color: #92400e; }
.v2-alert-success  { background: #d1fae5; color: #065f46; }

.v2-alert-icon svg { vertical-align: middle; }

#v2Alert .modal-body { padding: 16px; font-size: 14px; }
#v2Alert .modal-footer { padding: 8px 16px; }
#v2Alert .modal-footer .btn { min-width: 90px; }

/* ══════════════════════════════════════════════════════════════════════
   V2 TOAST — styled in one place, affects all toasts
   ══════════════════════════════════════════════════════════════════════ */
.v2-toast-success { background: #059669 !important; color: #fff !important; }
.v2-toast-error   { background: #dc2626 !important; color: #fff !important; }
.v2-toast-info    { background: #2563eb !important; color: #fff !important; }
/* Warning = YELLOW (not orange), with dark text for readability; the close button reverts to the
   default dark glyph so it stays visible on yellow. Palette is kept to blue/green/red/yellow. */
.v2-toast-warning { background: #f1c40f !important; color: #1a1d22 !important; }
.v2-toast-warning .btn-close-white { filter: none !important; }
/* Pending = light yellow for work in progress (e.g. a CSV export being built). Same palette as the
   builder's .v2-preview-banner; dark close glyph like warning. */
.v2-toast-pending { background: #fff7de !important; color: #6d4b00 !important; border: 1px solid #e6c34a !important; }
.v2-toast-pending .v2-toast-icon { color: #b98700; }
.v2-toast-pending .btn-close-white { filter: none !important; }

/* (Older v2-create-* rules removed — superseded by the shared
   v2-toolbar-btn / v2-toolbar-label / v2-toolbar-divider classes
   above. Same toolbar pattern is now reusable across surfaces.) */

/* Row highlight after a save — flashes a soft yellow background that
   fades out over ~2s. Used by the V2 transactions list to draw the eye
   to a newly-created row (especially when the create flipped the list
   mode from Payments → Bills or vice versa, so the user doesn't think
   "I just entered something and don't see it"). */
@keyframes v2-row-highlight-flash {
    0%   { background-color: #fff3cd; }
    100% { background-color: transparent; }
}
.v2-row-highlight, .v2-row-highlight > td {
    animation: v2-row-highlight-flash 2s ease-out;
}

.v2-toast-icon svg { vertical-align: middle; }
.v2-toast-body    { flex: 1; min-width: 0; }
.v2-toast-message { font-size: 13px; font-weight: 500; white-space: pre-line; }
.v2-toast-subline { display: flex; align-items: flex-start; gap: 6px; margin-top: 4px; font-size: 12px; opacity: .95; }
.v2-toast-subicon { flex-shrink: 0; padding-top: 1px; }
.v2-toast-subtext { word-break: break-word; }

/* Responsive column hiding */
@media (max-width: 1024px) { .v2-hide-lg { display: none !important; } }
@media (max-width: 991px)  { .v2-hide-md { display: none !important; } }
@media (max-width: 767px)  { .v2-hide-sm { display: none !important; } }
@media (max-width: 479px)  { .v2-hide-xs { display: none !important; } }
@media (max-width: 399px)  { .v2-hide-xxs { display: none !important; } }
/* Narrowest phones (≤399, portrait) — "bunch" breakpoint, complements v2-hide-xxs.
   .v2-narrow-hide → hidden ≤399 (natural display above); .v2-narrow-only → shown
   ONLY ≤399 (hidden above). Used by the transactions list: Due bunched under
   Amount + "Amount / Due" header, P|B toggle, compact pagination. Wider phones
   (≥400) keep the full layout with a separate Due column. Tune the boundary here. */
@media (max-width: 399px)  { .v2-narrow-hide { display: none !important; } }
@media (min-width: 400px)  { .v2-narrow-only { display: none !important; } }

/* Save & Send split button.
   - caret is just the arrow (tight padding) with a hairline divider, so it
     reads as one button you can click directly;
   - the primary label is width-locked to the wider "Save & Send" (both labels
     stacked in one grid cell; the inactive one is hidden but still reserves
     width) so swapping Save <-> Save & Send never resizes the button;
   - the menu is only as wide as its one item and drops UP (the group uses
     `dropup` + the toggle uses data-bs-display="static") — a menu dropping DOWN
     from a footer near the screen bottom lands off-screen, so up is deliberate. */
.v2-save-split-caret {
    border-left: 1px solid rgba(255, 255, 255, 0.5) !important;
    padding-left: 6px !important;
    padding-right: 6px !important;
    min-width: 0 !important;   /* the app's global .btn { min-width: 80px } would otherwise
                                  bloat the caret; shrink it to just the arrow */
}
.v2-save-primary .v2-save-label-wrap { display: inline-grid; vertical-align: middle; }
.v2-save-primary .v2-save-label { grid-area: 1 / 1; visibility: hidden; }
.v2-save-primary .v2-save-label.v2-active { visibility: visible; }
.v2-save-split-menu { min-width: max-content; }
/* Builder header uses a light Save button, so the caret's white divider (meant
   for the payment modal's primary-colored button) would be invisible — give it a
   subtle dark divider instead. */
.v2-builder-save-split .v2-save-split-caret { border-left-color: rgba(0, 0, 0, 0.15) !important; }

/* ── V2 New Family modal — "Header Bands" design (mockup B) ───────────────
   Separation comes from tinted HEADER STRIPS + whitespace + hairline rules,
   NOT boxes/rails. Four bands: Him, Her (side by side), Address, Family Info
   (side by side). Each member gets its OWN band. Fields sized to content.
   All scoped under #v2NewFamilyModal / .nfb-* so nothing else is touched. */

/* Section = header band + airy body, no surrounding border */
.nfb-section { margin-top: 26px; }
.nfb-section.nfb-section-first { margin-top: 0; }
/* Hairline rule + spacing between the members block and the address/family block */
.nfb-two-col { border-top: 1px solid #eceff3; padding-top: 22px; margin-top: 22px; }
.nfb-two-col > .nfb-section { border-top: 0; padding-top: 0; margin-top: 0; }

/* Tinted header strip spanning the section width */
.nfb-band {
    display: flex; align-items: center; gap: 8px;
    background: color-mix(in srgb, var(--v2-primary) 9%, transparent);
    color: color-mix(in srgb, var(--v2-primary) 78%, #1b2733);
    border-radius: var(--v2-radius-card, 8px);
    padding: 7px 12px; margin-bottom: 16px;
    font-size: 13px; font-weight: 700; line-height: 1.3;
}
.nfb-band svg { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.9; }
/* Sub-band (Contact) — lighter wash + trailing hairline */
.nfb-subband {
    display: flex; align-items: center; gap: 6px;
    color: color-mix(in srgb, var(--v2-primary) 70%, #33414f);
    font-size: 12px; font-weight: 700; letter-spacing: 0.2px;
    margin: 18px 0 10px;
}
.nfb-subband svg { width: 13px; height: 13px; opacity: 0.85; flex-shrink: 0; }
.nfb-subband::after { content: ""; flex: 1; height: 1px; background: color-mix(in srgb, var(--v2-primary) 16%, transparent); }

/* Him + Her side by side (each its own band); Address + Family side by side */
.nfb-members { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; }
.nfb-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }

/* Identity — reuses the Member-Edit page grid (.v2-form-grid-primary +
   .v2-form-stack): Title/First/Last stacked in one column, DOB/Hebrew/After-Dark
   in the adjacent column. Each member is HALF the modal width, so the photo sits
   on top (full width) and the two stacks sit side by side; they collapse to a
   single column when the members themselves stack (<768px). */
#v2NewFamilyModal .v2-form-grid.v2-form-grid-primary { grid-template-columns: 1fr 1fr; gap: 12px 16px; }
#v2NewFamilyModal .v2-form-grid .v2-form-pic-wrap { grid-column: 1 / -1; padding-top: 0; justify-content: flex-start; }
/* At tablet (<=768) each member's DOB column drops below the name column — one
   column per member — while the two members stay side by side (see below). */
@media (max-width: 768px) {
    #v2NewFamilyModal .v2-form-grid.v2-form-grid-primary { grid-template-columns: 1fr; }
}
#v2NewFamilyModal .v2-pic-uploader { width: 64px; height: 64px; font-size: 18px; background: linear-gradient(135deg, #c2d3e6, #9bb3cd); color: #fff; }
#v2NewFamilyModal .v2-pic-uploader.her { background: linear-gradient(135deg, #e6c2d8, #c98fb2); }

/* Contact rows: field(s) + trailing pill aligned to the input bottom */
.nfb-cx { display: flex; align-items: flex-end; gap: 8px; }
.nfb-cx + .nfb-cx { margin-top: 10px; }
.nfb-cx .v2-flag-pill { margin-bottom: 6px; white-space: nowrap; }

/* Field sizing — label+control stacked in a capped cell; rows wrap + left-align */
.nfb-row { display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: flex-start; }
.nfb-f { display: flex; flex-direction: column; min-width: 0; }
.nfb-f > .form-label { margin-bottom: 4px; }
.nfb-f .form-control, .nfb-f .form-select, .nfb-f .v2-readonly-field { width: 100%; }
.nfb-w-hdob    { max-width: 160px; flex: 1 1 150px; }
.nfb-w-phone   { max-width: 180px; flex: 1 1 160px; }
.nfb-w-ext     { max-width: 70px;  flex: 0 0 70px; }
.nfb-w-email   { max-width: 260px; flex: 1 1 220px; }
.nfb-w-atype   { max-width: 140px; flex: 1 1 140px; }
.nfb-w-company { max-width: 240px; flex: 1 1 200px; }
.nfb-w-street  { max-width: 420px; flex: 1 1 300px; }
/* City takes the leftover space; State/ZIP/+4 are sized to their content (+ a
   little) so City|State|ZIP|+4 sit on ONE line even in the half-width Address
   column. City shrinks (min-width:0 on .nfb-f) rather than wrapping the row. */
.nfb-w-city    { flex: 1 1 90px; min-width: 0; }
.nfb-w-state   { max-width: 64px; flex: 0 0 64px; }
.nfb-w-zip     { max-width: 88px; flex: 0 0 88px; }
.nfb-w-plus4   { max-width: 58px; flex: 0 0 58px; }
/* ZIP + +4 travel together: a single flex item in the row, so when space runs
   out the pair wraps to the next line as a unit (+4 never orphans from ZIP). */
.nfb-zip4      { display: flex; gap: 10px; flex: 0 0 auto; align-items: flex-start; }
.nfb-w-country { max-width: 190px; flex: 1 1 170px; }
/* Label + Salutation settings sit side by side within the Address column. */
.nfb-w-setting { max-width: 260px; flex: 1 1 150px; }
.nfb-w-anniv   { max-width: 160px; flex: 1 1 150px; }
.nfb-full { width: 100%; }

/* Street + Line-2 chevron */
/* position:relative anchors the Google autocomplete dropdown (position:absolute,
   top:100%) to this wrapper — otherwise it bubbles up to .modal-content and
   renders too high. Matches .v2-address-street-wrap. */
.nfb-street-wrap { display: flex; align-items: stretch; gap: 8px; position: relative; }
.nfb-line2-chev { border: 1px solid #ccd3dc; background: #fff; border-radius: 6px; width: 34px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: #6c7a8c; cursor: pointer; }

/* Duplicate-match banner — V2 warning palette */
.nf-dup-banner { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 6px; font-size: 13px; background: #fdebc7; border: 1px solid #f5d68a; color: #8a5a00; }
.nf-dup-banner strong { color: #6b4600; }
.nf-dup-why { color: #a07a2a; }
.nf-dup-link { margin-left: auto; white-space: nowrap; font-weight: 600; color: #8a5a00; text-decoration: none; }
.nf-dup-link:hover { text-decoration: underline; }

/* Responsive: keep both two-up layouts side-by-side down to tablet (>=768px);
   stack only on phones (<768px, where the modal is fullscreen). The narrower
   ~860px modal is applied from md up (below 768 modal-fullscreen-md-down wins). */
/* Him|Her and Address|Family stay side by side down through tablet; they stack
   one-above-the-other only on phones (<480px). Each member's internal name|DOB
   grid already collapsed to one column at <=768 (above). */
@media (max-width: 479.98px) { .nfb-members, .nfb-two-col { grid-template-columns: 1fr; } }
@media (min-width: 768px) { #v2NewFamilyModal .modal-dialog { max-width: min(880px, 96vw); } }
@media (max-width: 419.98px) {
    #v2NewFamilyModal .nfb-w-first, #v2NewFamilyModal .nfb-w-last,
    #v2NewFamilyModal .nfb-w-email, #v2NewFamilyModal .nfb-w-street,
    #v2NewFamilyModal .nfb-w-company, #v2NewFamilyModal .nfb-w-phone { flex-basis: 100%; }
}

/* ══════════════════════════════════════════════════════════════════════
   CATEGORIES — two-panel layout + tree
   ══════════════════════════════════════════════════════════════════════ */

/* Two-panel container */
.v2-categories-layout {
    display: flex;
    height: calc(100vh - 74px); /* fallback */
    height: calc(100dvh - 74px); /* mobile: tracks visible viewport */
}
.v2-categories-sidebar {
    width: 280px;
    flex-shrink: 0;
    border-right: 1px solid #dee2e6;
    display: flex;
    flex-direction: column;
    background: #fff;
    overflow: hidden;
}
.v2-categories-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.v2-categories-main .v2-list-card {
    height: 100%;
}

/* Mode toggle bar */
.v2-cat-mode-bar {
    display: flex;
    align-items: center;
    padding: 8px;
    border-bottom: 1px solid #dee2e6;
}

/* Tree toolbar */
.v2-cat-toolbar {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px 8px;
    border-bottom: 1px solid #dee2e6;
}

/* Tree container — scrollable area */
.v2-cat-tree-wrap {
    flex: 1;
    overflow-y: auto;
    padding: 4px 8px 4px 28px;
}

/* sortable-tree overrides — integrate with v2 theme */
.v2-cat-tree-wrap sortable-tree-node > :first-child {
    padding: 0 8px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 13px;
    height: 30px;
}
.v2-cat-tree-wrap sortable-tree-node > :first-child:hover {
    background: rgba(0,0,0,0.04);
}
.v2-cat-tree-wrap sortable-tree-node > :first-child.v2-cat-selected {
    background: color-mix(in srgb, var(--v2-primary) 15%, transparent);
    font-weight: 600;
}
/* Collapse icon — explicit height to match label row so align-items:center lines up */
.v2-cat-tree-wrap sortable-tree-node > :nth-child(3) {
    height: 30px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
}
/* Drop indicators */
.v2-cat-tree-wrap .tree__node--drop-inside > :first-child {
    background: color-mix(in srgb, var(--v2-primary) 25%, transparent) !important;
}
.v2-cat-tree-wrap .tree__node--drop-before {
    border-top: 2px solid var(--v2-primary);
}
.v2-cat-tree-wrap .tree__node--drop-after {
    border-bottom: 2px solid var(--v2-primary);
}
.v2-cat-tree-wrap .tree__node--dragging {
    opacity: 0.4;
}

/* Category name in label */
.v2-cat-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-cat-priority {
    font-size: 11px;
    opacity: 0.55;
    font-weight: normal;
}

/* Mobile category tree list */
.v2-mobile-cat-item {
    display: flex;
    align-items: center;
    padding: 6px 8px 6px 0;
    cursor: pointer;
    font-size: 13px;
    border-radius: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2-mobile-cat-item:hover { background: rgba(0,0,0,0.04); }
.v2-mobile-cat-item.v2-mobile-cat-selected {
    background: color-mix(in srgb, var(--v2-primary) 15%, transparent);
    font-weight: 600;
}
.v2-mobile-chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    flex-shrink: 0;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
}
.v2-mobile-chevron-spacer { display: inline-block; width: 20px; flex-shrink: 0; }

/* Categories offcanvas (mobile) */
.v2-cat-offcanvas {
    width: 280px !important;
}
.v2-cat-offcanvas .v2-cat-tree-wrap {
    flex: 1;
    overflow-y: auto;
}

/* Typeahead results — default is in-flow (used inside modals where
   pushing surrounding fields is fine). Add `.v2-typeahead-floating`
   when the dropdown sits in a toolbar over a list — it then positions
   absolutely under the input without nudging anything below. The host
   wrapper must be `position:relative` (or have its own positioning). */
.v2-typeahead-results {
    max-height: 200px;
    overflow-y: auto;
    border: 1px solid #dee2e6;
    border-radius: 4px;
    margin-top: 4px;
    background: #fff;
}
.v2-typeahead-results.v2-typeahead-floating {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 2px;
    z-index: 1050;          /* over standard page content / cards */
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.v2-typeahead-item {
    padding: 6px 10px;
    cursor: pointer;
    font-size: 13px;
}
.v2-typeahead-item:hover {
    background: rgba(0,0,0,0.04);
}
.v2-typeahead-item.v2-typeahead-selected {
    background: color-mix(in srgb, var(--v2-primary) 15%, transparent);
    font-weight: 600;
}

/* Empty state for categories */
.v2-cat-empty-state {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    font-size: 15px;
}

/* Compact row checkboxes (categories item list) */
.v2-check-col { width: 28px; padding-right: 0 !important; vertical-align: middle !important; text-align: center; }
.v2-row-check {
    transform: scale(0.8);
    margin: 0;
    cursor: pointer;
    display: block;
}
/* Row action buttons (edit / delete) */
.v2-action-col { vertical-align: middle !important; }
.v2-action-btn svg { width: 18px; height: 18px; }
[data-v2-size="sm"] .v2-action-btn svg { width: 16px; height: 16px; }
[data-v2-size="lg"] .v2-action-btn svg { width: 20px; height: 20px; }

/* Size-responsive checkboxes */
[data-v2-size="sm"] .v2-row-check { transform: scale(0.7); }
[data-v2-size="sm"] .v2-check-col { width: 24px; }
[data-v2-size="lg"] .v2-row-check { transform: scale(0.95); }
[data-v2-size="lg"] .v2-check-col { width: 32px; }

/* Size-responsive hover action icons */
.v2-hover-action svg { width: 18px; height: 18px; }
[data-v2-size="sm"] .v2-hover-action svg { width: 16px; height: 16px; }
[data-v2-size="lg"] .v2-hover-action svg { width: 20px; height: 20px; }
[data-v2-size="sm"] .v2-hover-action { padding: 3px; border-radius: 3px; }
[data-v2-size="lg"] .v2-hover-action { padding: 5px; border-radius: 5px; }

/* Header checkbox dropdown */
.v2-check-dropdown > span { cursor: pointer; display: inline-flex; align-items: center; gap: 1px; }
.v2-check-dropdown > span::after { display: none; } /* hide BS5 default caret */
.v2-check-caret { opacity: 0.5; margin-top: 1px; }
.v2-check-menu { font-size: 13px; min-width: 140px; }
.v2-check-menu .dropdown-item { padding: 4px 12px; }
[data-v2-size="sm"] .v2-check-menu { font-size: 12px; min-width: 120px; }
[data-v2-size="sm"] .v2-check-menu .dropdown-item { padding: 3px 10px; }
[data-v2-size="lg"] .v2-check-menu { font-size: 14px; min-width: 160px; }
[data-v2-size="lg"] .v2-check-menu .dropdown-item { padding: 5px 14px; }

/* Settings gear button */
.v2-settings-btn { padding: 3px 6px; line-height: 1; position: relative; }
.v2-settings-btn svg { vertical-align: middle; }
.v2-settings-badge { font-size: 9px; padding: 1px 4px; position: relative; top: -1px; }

/* Settings panel tabs */
.v2-settings-tabs { gap: 0; }
.v2-settings-tabs .nav-link { font-size: 13px; font-weight: 600; padding: 6px 16px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); border: none; border-bottom: 2px solid transparent; border-radius: 0; }
.v2-settings-tabs .nav-link:hover { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); border-bottom-color: #dee2e6; }
.v2-settings-tabs .nav-link.active { color: var(--v2-primary); border-bottom-color: var(--v2-primary); background: transparent; }
.v2-settings-tabs .badge { font-size: 9px; padding: 1px 5px; vertical-align: middle; }
[data-v2-size="sm"] .v2-settings-tabs .nav-link { font-size: 12px; padding: 5px 12px; }
[data-v2-size="lg"] .v2-settings-tabs .nav-link { font-size: 14px; padding: 7px 20px; }

/* Columns list inside settings panel */
.v2-columns-list { padding: 8px 0; }
.v2-columns-item { padding: 6px 16px; cursor: pointer; font-size: 13px; margin: 0; }
.v2-columns-item:hover { background: #f8f9fa; }
.v2-columns-item .form-check-input { width: 14px; height: 14px; cursor: pointer; }
.v2-columns-item .form-check-input:disabled { cursor: default; opacity: 0.4; }
.v2-columns-item.text-muted .form-check-input { cursor: default; }
[data-v2-size="sm"] .v2-columns-item { font-size: 12px; padding: 5px 12px; }
[data-v2-size="sm"] .v2-columns-item .form-check-input { width: 12px; height: 12px; }
[data-v2-size="lg"] .v2-columns-item { font-size: 14px; padding: 7px 18px; }
[data-v2-size="lg"] .v2-columns-item .form-check-input { width: 16px; height: 16px; }
[data-v2-density="compact"] .v2-columns-item { padding: 3px 16px; }
[data-v2-density="spacious"] .v2-columns-item { padding: 9px 16px; }

/* Filter dropdowns (toolbar) */
.v2-filter-menu { font-size: 13px; }
.v2-filter-menu .dropdown-item { font-size: 13px; padding: 4px 10px; }
.v2-filter-menu .form-label { font-size: 12px; }
.v2-filter-menu .form-control { font-size: 13px; }
.v2-filter-menu .btn { font-size: 13px; }
[data-v2-size="sm"] .v2-filter-menu { font-size: 12px; }
[data-v2-size="sm"] .v2-filter-menu .dropdown-item { font-size: 12px; padding: 3px 8px; }
[data-v2-size="sm"] .v2-filter-menu .form-label { font-size: 11px; }
[data-v2-size="sm"] .v2-filter-menu .form-control { font-size: 12px; }
[data-v2-size="sm"] .v2-filter-menu .btn { font-size: 12px; }
[data-v2-size="lg"] .v2-filter-menu { font-size: 14px; }
[data-v2-size="lg"] .v2-filter-menu .dropdown-item { font-size: 14px; padding: 5px 12px; }
[data-v2-size="lg"] .v2-filter-menu .form-label { font-size: 13px; }
[data-v2-size="lg"] .v2-filter-menu .form-control { font-size: 14px; }
[data-v2-size="lg"] .v2-filter-menu .btn { font-size: 14px; }

/* Filter offcanvas panel */
.v2-filter-panel { width: 320px !important; }
.v2-filter-panel .offcanvas-header { background: #f8f9fa; }
.v2-filter-panel .offcanvas-title { font-weight: 600; }
.v2-filter-panel .offcanvas-body { font-size: 13px; }
.v2-filter-panel .offcanvas-footer { background: #f8f9fa; }
.v2-filter-section-label { font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.3px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); display: block; margin-bottom: 6px; }
.v2-filter-preset { font-size: 12px; padding: 2px 8px; border-radius: 20px; }
[data-v2-size="sm"] .v2-filter-panel { width: 280px !important; }
[data-v2-size="sm"] .v2-filter-panel .offcanvas-body { font-size: 12px; }
[data-v2-size="sm"] .v2-filter-section-label { font-size: 11px; }
[data-v2-size="sm"] .v2-filter-preset { font-size: 11px; padding: 1px 6px; }
[data-v2-size="sm"] .v2-filter-panel .form-control,
[data-v2-size="sm"] .v2-filter-panel .form-select,
[data-v2-size="sm"] .v2-filter-panel .form-label { font-size: 12px; }
[data-v2-size="lg"] .v2-filter-panel { width: 360px !important; }
[data-v2-size="lg"] .v2-filter-panel .offcanvas-body { font-size: 14px; }
[data-v2-size="lg"] .v2-filter-section-label { font-size: 13px; }
[data-v2-size="lg"] .v2-filter-preset { font-size: 13px; padding: 3px 10px; }
[data-v2-size="lg"] .v2-filter-panel .form-control,
[data-v2-size="lg"] .v2-filter-panel .form-select,
[data-v2-size="lg"] .v2-filter-panel .form-label { font-size: 14px; }
.v2-filter-accordion .accordion-button { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); padding: 10px 16px; background: #fff; }
.v2-filter-accordion .accordion-button:not(.collapsed) { color: var(--v2-primary); background: #f8f9fa; box-shadow: none; }
.v2-filter-accordion .accordion-button:focus { box-shadow: none; }
.v2-filter-accordion .accordion-button::after { width: 12px; height: 12px; background-size: 12px; }
[data-v2-size="sm"] .v2-filter-accordion .accordion-button { font-size: 12px; padding: 8px 12px; }
[data-v2-size="sm"] .v2-filter-accordion .accordion-body { font-size: 12px; }
[data-v2-size="lg"] .v2-filter-accordion .accordion-button { font-size: 14px; padding: 12px 18px; }
[data-v2-size="lg"] .v2-filter-accordion .accordion-body { font-size: 14px; }

/* Filter checklist (multi-select) */
.v2-filter-checklist { max-height: 200px; overflow-y: auto; }
.v2-filter-check-item { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 13px; cursor: pointer; margin: 0; }
.v2-filter-check-item .form-check-input { width: 14px; height: 14px; cursor: pointer; margin: 0; flex-shrink: 0; }
[data-v2-size="sm"] .v2-filter-checklist { max-height: 160px; }
[data-v2-size="sm"] .v2-filter-check-item { font-size: 12px; padding: 2px 0; }
[data-v2-size="sm"] .v2-filter-check-item .form-check-input { width: 12px; height: 12px; }
[data-v2-size="lg"] .v2-filter-checklist { max-height: 240px; }
[data-v2-size="lg"] .v2-filter-check-item { font-size: 14px; padding: 4px 0; }
[data-v2-size="lg"] .v2-filter-check-item .form-check-input { width: 16px; height: 16px; }

/* Filter tree (category/class with expand/collapse) */
.v2-tree-toggle { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; font-size: 14px; font-weight: 700; cursor: pointer; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); flex-shrink: 0; line-height: 1; user-select: none; }
.v2-tree-toggle:hover { color: var(--v2-primary); }
.v2-tree-children { padding-left: 38px; }
[data-v2-size="sm"] .v2-tree-toggle { width: 14px; height: 14px; font-size: 12px; }
[data-v2-size="sm"] .v2-tree-children { padding-left: 32px; }
[data-v2-size="lg"] .v2-tree-toggle { width: 18px; height: 18px; font-size: 16px; }
[data-v2-size="lg"] .v2-tree-children { padding-left: 42px; }

/* Split transaction detail rows */
.v2-detail-row td { padding-top: 1px !important; padding-bottom: 1px !important; font-size: 12px; border-top: none !important; }
.v2-split-badge { vertical-align: middle; }
[data-v2-size="sm"] .v2-detail-row td { font-size: 11px; }
[data-v2-size="lg"] .v2-detail-row td { font-size: 13px; }

/* Inline stats bar */
.v2-stats-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    font-size: 12px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #495057), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    background: #f8f9fa;
    border-bottom: 1px solid #dee2e6;
    flex-wrap: wrap;
}
.v2-stats-bar strong { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #212529), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
.v2-stats-sep { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #adb5bd), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
[data-v2-size="sm"] .v2-stats-bar { font-size: 11px; padding: 3px 10px; }
[data-v2-size="lg"] .v2-stats-bar { font-size: 13px; padding: 5px 14px; }

/* Batch action bar */
.v2-batch-bar {
    background: color-mix(in srgb, var(--v2-primary) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--v2-primary) 20%, transparent);
    border-radius: 4px;
    padding: 2px 8px;
}
.v2-batch-count {
    font-size: 12px;
    font-weight: 600;
    color: var(--v2-primary);
    white-space: nowrap;
}
/* Shared toolbar action button — used by V2 toolbars throughout (batch
   actions on the Categories page, "Create" + "View" groups on the
   Transactions list, etc.). Pair with Bootstrap `btn btn-sm btn-outline-*`
   for the color variant. v2-batch-btn is kept as an alias for legacy
   callers that haven't been migrated yet. */
.v2-toolbar-btn,
.v2-batch-btn { font-size: 12px; padding: 2px 8px; }

/* Toolbar form controls — shared sizing for any checkbox/radio that sits
   directly in a `.v2-list-toolbar` filter strip ("Show expired", "Show
   inactive", "Show notes from all users", "Show full path", etc.). One
   rule here keeps every toolbar checkbox visually identical across tabs
   so flipping tabs doesn't make the row jump or the checkbox resize.
   Scoped to the toolbar so it does NOT shrink in-row table checkboxes,
   modal checkboxes, or the offcanvas column-toggle checkboxes.

   Default (md) width/height are set here; sm and lg overrides live in
   the `data-v2-size` block at the bottom of this section. We size by
   explicit `width`/`height` rather than `transform: scale()` so the
   checkbox advances every neighboring element in flex layout (a scaled
   checkbox keeps its original box for layout, which would let big-mode
   labels overlap the box and small-mode leave a gap). */
.v2-list-toolbar .form-check-input {
    width: 14px;
    height: 14px;
    margin-top: 0;
    flex-shrink: 0;
}

/* Small uppercase label that anchors a related group of toolbar buttons
   (e.g. "Create" before the new-payment/new-bill pair, "View" before
   the mode toggle). Muted weight; sits to the LEFT of the button group
   on the same flex row. */
.v2-toolbar-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: #6c757d;
    white-space: nowrap;
}

/* Vertical hairline divider that visually separates two logical button
   groups in a toolbar (e.g. Create | View). */
.v2-toolbar-divider {
    width: 1px;
    align-self: stretch;
    min-height: 22px;
    background: #dee2e6;
    margin: 0 4px;
}

/* Shared <label> wrapper for a "Show xxx" toolbar checkbox — used on
   every V2 list toolbar (Notes "Show notes from all users", Gifts "Show
   inactive", Cards "Show expired", Categories "Show expired" / "Show
   full path"). Replaces ad-hoc `small text-muted` and the
   `.v2-cat-show-expired` one-off so every checkbox label looks
   identical across tabs.

   Color uses --v2-muted-color so it responds to the user's
   `data-v2-contrast` preference (lower contrast → lighter, higher
   contrast → darker — and at contrast 7 the label hits full black).
   Font-size is `inherit`, so the toolbar's `data-v2-size` rule above
   propagates through and the label scales sm/md/lg in lockstep with
   everything else in the toolbar. */
.v2-toolbar-check {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 0;
    cursor: pointer;
    user-select: none;
    color: var(--v2-muted-color);
    white-space: nowrap;
}
.v2-toolbar-check input { cursor: pointer; }

/* ── Toolbar size-responsive overrides ────────────────────────────────
   Pair every toolbar element with the user's [data-v2-size] preference
   so sm / md / lg scale in lockstep. Without these rules, only `.btn`,
   `.v2-page-info`, and `.dropdown-item` responded to size — which is why
   `md → lg` made the checkbox / page-jump input / label text appear to
   SHRINK (md inherited the body's ~16px font, while lg forced the
   toolbar back to 14px). The most important rule below is the explicit
   md anchor on `.v2-list-toolbar` — that's the line that was missing.

   Targeted font/height/padding values per size:
     sm: 12px text, 12×12 checkbox, 20px divider
     md: 13px text, 14×14 checkbox, 22px divider
     lg: 14px text, 16×16 checkbox, 26px divider                       */

/* — md anchor (the missing rule that caused the inversion) — */
[data-v2-size="md"] .v2-list-toolbar { font-size: 13px !important; }

/* — toolbar action button (`+ New Note`, `+ Payment`, etc.) — */
[data-v2-size="sm"] .v2-toolbar-btn,
[data-v2-size="sm"] .v2-batch-btn { font-size: 12px; padding: 2px 8px; }
[data-v2-size="md"] .v2-toolbar-btn,
[data-v2-size="md"] .v2-batch-btn { font-size: 13px; padding: 3px 10px; }
[data-v2-size="lg"] .v2-toolbar-btn,
[data-v2-size="lg"] .v2-batch-btn { font-size: 14px; padding: 4px 12px; }

/* — uppercase group label ("Create" / "View") — */
[data-v2-size="sm"] .v2-toolbar-label { font-size: 10px; }
[data-v2-size="md"] .v2-toolbar-label { font-size: 11px; }
[data-v2-size="lg"] .v2-toolbar-label { font-size: 12px; }

/* — vertical group divider — */
[data-v2-size="sm"] .v2-toolbar-divider { min-height: 20px; }
[data-v2-size="md"] .v2-toolbar-divider { min-height: 22px; }
[data-v2-size="lg"] .v2-toolbar-divider { min-height: 26px; }

/* — toolbar checkbox ("Show expired", "Show inactive", …) — */
[data-v2-size="sm"] .v2-list-toolbar .form-check-input { width: 12px; height: 12px; }
[data-v2-size="md"] .v2-list-toolbar .form-check-input { width: 14px; height: 14px; }
[data-v2-size="lg"] .v2-list-toolbar .form-check-input { width: 16px; height: 16px; }

/* — page-info text fills the md gap so it doesn't visually equal lg — */
[data-v2-size="md"] .v2-page-info { font-size: 13px; }

/* — page-jump input (the `__/N` box) — width + font both scale, since
     `font-size: inherit` was previously pulling the body's 16px at md. */
[data-v2-size="sm"] .v2-page-input { width: 28px; font-size: 12px; }
[data-v2-size="md"] .v2-page-input { width: 32px; font-size: 13px; }
[data-v2-size="lg"] .v2-page-input { width: 38px; font-size: 14px; }

/* — pagination chevron SVGs (inline `width/height` attrs are overridable
     by CSS — these win) — */
[data-v2-size="sm"] .v2-list-pagination .page-link svg { width: 11px; height: 11px; }
[data-v2-size="md"] .v2-list-pagination .page-link svg { width: 12px; height: 12px; }
[data-v2-size="lg"] .v2-list-pagination .page-link svg { width: 14px; height: 14px; }

/* — settings (gear) button icon — */
[data-v2-size="sm"] .v2-list-toolbar .v2-settings-btn svg { width: 13px; height: 13px; }
[data-v2-size="md"] .v2-list-toolbar .v2-settings-btn svg { width: 14px; height: 14px; }
[data-v2-size="lg"] .v2-list-toolbar .v2-settings-btn svg { width: 16px; height: 16px; }

/* — search-box clear / search icons inside `.input-group` buttons — */
[data-v2-size="sm"] .v2-list-toolbar .input-group .btn svg { width: 11px; height: 11px; }
[data-v2-size="md"] .v2-list-toolbar .input-group .btn svg { width: 12px; height: 12px; }
[data-v2-size="lg"] .v2-list-toolbar .input-group .btn svg { width: 14px; height: 14px; }

/* — filter chip badges ("active filter" pills next to the search box).
     Many templates set `style="font-size: 12px"` inline; `!important`
     wins over normal-specificity inline styles. — */
[data-v2-size="sm"] .v2-list-toolbar .badge { font-size: 11px !important; }
[data-v2-size="md"] .v2-list-toolbar .badge { font-size: 12px !important; }
[data-v2-size="lg"] .v2-list-toolbar .badge { font-size: 13px !important; }

/* ── Pending Charges ──────────────────────────────────── */

/* Charge status bar (bottom of card during active charging) */
.v2-charge-status-bar {
    display: flex; align-items: center; gap: 12px;
    padding: 8px 16px; background: #f1f5f9;
    border-top: 1px solid #dee2e6; font-size: 13px;
    flex-shrink: 0;
}
.v2-charge-status-bar .progress { flex: 1; height: 6px; }
.v2-charge-stat { white-space: nowrap; }
.v2-charge-stat-value { font-weight: 600; }

/* Job status icon cell (tab 1) — no animation overlap on transitions */
.v2-status-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; }
.v2-status-icon > svg.ng-leave, .v2-status-icon > span.ng-leave { display: none !important; }
.v2-status-icon > svg.ng-enter, .v2-status-icon > span.ng-enter { opacity: 1 !important; }
.v2-status-col { width: 32px; }

/* Auto-charge checkbox column */
.v2-pending-checkbox { width: 32px; text-align: center; }
.v2-pending-checkbox .form-check-input { margin: 0; }

/* Charge message column — allow wrapping */
.v2-charge-msg { max-width: 250px; white-space: normal; font-size: 12px; }

/* Snooze dropdown */
.v2-snooze-dropdown { min-width: 200px; }

/* Tab pills in toolbar */
.v2-toolbar-pills .nav-link { font-size: 13px; padding: 4px 12px; }
.v2-toolbar-pills .nav-link.active { font-weight: 600; }

/* Color-coded status pills — used by the Chai Club / Recurring Givers
   tab to make the status filter match the row-badge color scheme
   (cc.statusClass mapping). Inactive pill: colored text + matching
   border on white bg. Active (selected): filled with the color, white
   text. Use the "All" pill without any v2-pill-* modifier — neutral
   catch-all. The five variants below mirror Bootstrap contextual colors
   (success / warning / danger / primary / secondary) so any future re-
   mapping happens in the cc.statusClass switch + the pill class — one
   place each. */
.v2-toolbar-pills .nav-link.v2-pill-success   { color: var(--bs-success);  border: 1px solid var(--bs-success); }
.v2-toolbar-pills .nav-link.v2-pill-success.active   { background: var(--bs-success);  color: #fff; }
.v2-toolbar-pills .nav-link.v2-pill-warning   { color: var(--bs-warning-text, #997404); border: 1px solid var(--bs-warning); }
.v2-toolbar-pills .nav-link.v2-pill-warning.active   { background: var(--bs-warning);  color: #000; }
.v2-toolbar-pills .nav-link.v2-pill-danger    { color: var(--bs-danger);   border: 1px solid var(--bs-danger); }
.v2-toolbar-pills .nav-link.v2-pill-danger.active    { background: var(--bs-danger);   color: #fff; }
.v2-toolbar-pills .nav-link.v2-pill-primary   { color: var(--bs-primary);  border: 1px solid var(--bs-primary); }
.v2-toolbar-pills .nav-link.v2-pill-primary.active   { background: var(--bs-primary);  color: #fff; }
.v2-toolbar-pills .nav-link.v2-pill-secondary { color: var(--bs-secondary);border: 1px solid var(--bs-secondary); }
.v2-toolbar-pills .nav-link.v2-pill-secondary.active { background: var(--bs-secondary);color: #fff; }

/* Bottom action bar */
.v2-action-bar {
    display: flex; align-items: center; justify-content: flex-end; gap: 8px;
    padding: 10px 16px; border-top: 1px solid #dee2e6; flex-shrink: 0;
}

/* ── Batch shared inline-style replacements ────────────── */

/* Sortable table headers */
.v2-th-sort { cursor: pointer; }
.v2-th-sort-narrow { cursor: pointer; width: 1%; white-space: nowrap; }

/* Table cell text truncation */
.v2-td-truncate { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-td-nowrap { white-space: nowrap; }

/* Flex column container for detail panel content */
.v2-flex-panel { display: flex; flex-direction: column; flex: 1; overflow: hidden; }

/* Filter badges in detail summary and modal filter sections */
.v2-filter-badge { font-weight: 500; font-size: 11px; }
.v2-filter-badge-modal { font-weight: 500; font-size: 12px; }

/* Small button for "View" filters link in detail panel */
.v2-btn-filter-view { font-size: 10px; padding: 1px 6px; }

/* Detail panel column widths */
.v2-col-date { width: 80px; }
.v2-col-date-sm { width: 70px; }
.v2-col-date-md { width: 110px; }
.v2-col-num-xs { width: 40px; }
.v2-col-num-sm { width: 55px; }
.v2-col-num-md { width: 60px; }

/* Cell left padding (detail panel uses 12px, modal preview uses 16px) */
.v2-cell-pad { padding-left: 12px; }
.v2-cell-pad-lg { padding-left: 16px; }

/* Bulk-send modal — recipient list with per-row Edit. Preview snippet stays
   on two lines so each row reads as one email at a glance. */
.v2-bulk-recip-table thead th {
    font-size: 11px; text-transform: uppercase; letter-spacing: .03em;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c757d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); background: #f8f9fa; border-bottom: 1px solid #dee2e6;
}
.v2-bulk-recip-table tbody tr { border-bottom: 1px solid #f0f1f3; }
.v2-bulk-preview {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.4;
}
/* Failure-row error text — wraps freely (no clamp) so the full reason is
   readable on mobile where a title-only tooltip is useless. */
.v2-bulk-error {
    white-space: pre-wrap;
    word-break: break-word;
    line-height: 1.4;
}

/* Batch modal: preview search input */
.v2-search-max { max-width: 220px; }

/* Batch modal: sticky preview table header */
.v2-thead-sticky { position: sticky; top: 0; z-index: 2; background: #fff; }
.v2-tr-compact { font-size: 12px; }

/* Accordion filter panels */
.v2-accordion-btn { font-size: 13px; }
.v2-accordion-label { font-size: 13px; }
.v2-accordion-scroll { max-height: 200px; overflow-y: auto; }
.v2-accordion-scroll-lg { max-height: 250px; overflow-y: auto; }
.v2-cat-toggle { width: 16px; text-align: center; font-size: 12px; text-decoration: none; }

/* Dropdown items in overflow menus (outside list toolbar) */
.v2-dropdown-item { font-size: 13px; }

/* ─── Relations Manage modal ─────────────────────────────────────────
   Two-column layout: list of relations on the left, opposite-checkbox
   grid on the right. On narrow viewports the right pane drops below.

   Height plumbing — without these, modal-body sizes to content and the
   modal overflows the viewport (no scrollbars). Chain:
     modal-content : max-height = viewport (so chrome ≠ pushed off-screen)
     modal-body    : flex column + overflow:hidden (so grid can fill it)
     grid          : flex:1 + min-height:0 (so it shrinks to fit body)
     pane bodies   : flex:1 + overflow-y:auto (so list/opposites scroll)
*/
#v2RelationsManageModal .modal-content { max-height: calc(100vh - 60px); }
#v2RelationsManageModal .modal-body    {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.v2-relations-manage {
    flex: 1 1 auto;
    min-height: 0;
    display: grid;
    grid-template-columns: 320px 1fr;
}
@media (max-width: 768px) {
    .v2-relations-manage { grid-template-columns: 1fr; }
}

.v2-relations-list {
    display: flex; flex-direction: column;
    border-right: 1px solid #dde3eb;
    background: #fafbfc;
    min-height: 0;
}
.v2-relations-list-head {
    padding: 10px 14px; font-weight: 600; font-size: 13px;
    border-bottom: 1px solid #dde3eb; background: #fff;
}
.v2-relations-list-body { flex: 1 1 auto; overflow-y: auto; }
.v2-relations-list-foot {
    padding: 10px 14px; border-top: 1px solid #dde3eb; background: #fff;
    display: flex; align-items: center;
}
.v2-relations-list-row {
    display: flex; align-items: center;
    padding: 6px 14px; font-size: 13px;
    border-bottom: 1px solid #eef1f5;
    cursor: pointer;
    transition: background .1s;
}
.v2-relations-list-row:hover { background: #f0f4f9; }
.v2-relations-list-row.is-selected {
    background: rgba(13, 110, 253, 0.08);
    border-left: 3px solid var(--v2-primary);
    padding-left: 11px;
}
.v2-relations-list-row.is-inactive .v2-relations-name { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #99a3b1), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
.v2-relations-name { line-height: 1.4; }

.v2-icon-btn {
    background: transparent; border: 0; padding: 3px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c7689), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); cursor: pointer; border-radius: 3px;
    line-height: 0;
}
.v2-icon-btn:hover { color: var(--v2-primary); background: rgba(0,0,0,0.04); }
.v2-icon-btn.text-danger:hover { color: var(--bs-danger); }

.v2-relations-opposites {
    display: flex; flex-direction: column;
    min-height: 0;
}
.v2-relations-opposites-head {
    padding: 10px 14px; font-weight: 600; font-size: 13px;
    border-bottom: 1px solid #dde3eb;
}
.v2-relations-opposites-body { flex: 1 1 auto; overflow-y: auto; padding: 12px 14px; }
.v2-relations-opposites-foot {
    padding: 10px 14px; border-top: 1px solid #dde3eb;
    display: flex; align-items: center;
}
.v2-relations-opposite-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 4px 12px;
}
.v2-relations-opposite-cell {
    display: flex; align-items: center; gap: 6px;
    font-size: 13px; cursor: pointer; padding: 4px 6px;
    border-radius: 3px;
}
.v2-relations-opposite-cell:hover { background: #f0f4f9; }
.v2-relations-opposite-cell.is-inactive { color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #99a3b1), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
.v2-relations-opposite-cell input { margin: 0; }

/* ─── Address Edit modal ─────────────────────────────────────────────
   Country typeahead — anchored relative to the country field cell so
   the suggestions list overlays fields below without affecting layout
   height. Same pattern as the yahrtzeit member typeahead.
*/
.v2-address-country-wrap { position: relative; }
.v2-address-country-list {
    position: absolute;
    top: 100%; left: 12px; right: 12px;        /* match Bootstrap col gutter */
    z-index: 1080;
    max-height: 240px; overflow-y: auto;
    background: #fff;
    border: 1px solid #c8d0db;
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.10);
    margin-top: 1px;
}
.v2-address-country-item {
    display: block; width: 100%; text-align: left;
    background: transparent; border: 0;
    padding: 6px 12px; font-size: 13px;
    cursor: pointer;
}
.v2-address-country-item:hover,
.v2-address-country-item.is-active { background: #f0f4f9; }

/* Address-modal flags sit right-aligned (top of the modal, like an
   action toolbar). Distinct from the centered pill group inside the
   contact-methods rows of the member modal. The combined-class
   selector has higher specificity than the base .v2-flag-group rule
   that lives further down in this file (which sets center) so source
   order doesn't matter. */
.v2-flag-group.v2-address-flags { justify-content: flex-end; gap: 6px; }

/* Wraps Street Address input + Address-Line-2 chevron together. Needs
   position:relative so the v2-place-autocomplete-dropdown (a sibling of
   the input, position:absolute) anchors to this wrapper rather than
   bubbling up to modal-content. */
.v2-address-street-wrap { position: relative; }

/* Tiny chevron button anchored to the end of the Street Address row
   that toggles Address Line 2. Square, no label, just the arrow —
   stays visually subordinate to the main field. Rotates on open so
   the affordance reads as "expand / collapse". */
.v2-address-line2-chevron {
    background: transparent;
    border: 1px solid #c8d0db;
    border-radius: 4px;
    padding: 0 6px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #6c7689), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    cursor: pointer;
    line-height: 0;
    flex: 0 0 auto;
    transition: transform .15s, color .1s, background .1s;
}
.v2-address-line2-chevron:hover { color: var(--v2-primary); background: rgba(0,0,0,0.04); }
.v2-address-line2-chevron.is-open { transform: rotate(180deg); color: var(--v2-primary); }

/* ─── Stacked-modal z-index bumps ────────────────────────────────────
   These two modals are routinely opened from inside another modal
   (v2Alert from member-edit's "Save changes first?" prompt; the
   relations-manage modal from inside Yahrtzeit / Move-Member).
   Bootstrap 5 sets z-index inline on the modal element when stacking,
   which beats class selectors — so we need !important to win. Backdrop
   z-indexes match (modal_z - 5) so each modal's backdrop sits on top
   of the previous modal but below its own dialog. 1090 sits above the
   standard modal layer (1055) and the standard backdrop (1050) without
   colliding with v2-floating-dropdown (2000). */
#v2Alert.modal               { z-index: 1090 !important; }
#v2RelationsManageModal.modal{ z-index: 1090 !important; }

/* ─── Just-filled flash ─────────────────────────────────────────────
   Brief background-fade applied to inputs that were auto-populated by
   Google Places (City / State / ZIP / Country). Class is added by
   v2AddressEditService.flashFilledFields(...) and removed after the
   animation completes, so subsequent autocompletes can re-trigger.
   Soft amber → transparent so the change is noticeable without being
   alarming. */
@keyframes v2JustFilledFlash {
    0%   { background-color: #fff3c4; box-shadow: 0 0 0 2px #fde68a; }
    60%  { background-color: #fff8df; box-shadow: 0 0 0 1px #fde68a; }
    100% { background-color: transparent; box-shadow: none; }
}
.v2-just-filled {
    animation: v2JustFilledFlash 1.4s ease-out;
}

/* ─── v2-place-autocomplete dropdown ─────────────────────────────────
   Rendered by the v2-place-autocomplete directive as a sibling of the
   input. The hosting input must sit inside a position:relative wrapper
   so this dropdown anchors to it. Visually consistent with the
   country-typeahead list (same shadow / border / rounding).
   `top: 100%` anchors below the wrapper bottom (works inside flex
   containers where static positioning is unreliable).
*/
.v2-place-autocomplete-dropdown {
    position: absolute;
    top: 100%; left: 0; right: 0;
    z-index: 1080;
    margin-top: 2px;
    max-height: 240px; overflow-y: auto;
    background: #fff;
    border: 1px solid #c8d0db;
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.10);
}
.v2-place-autocomplete-item {
    display: block; width: 100%; text-align: left;
    background: transparent; border: 0;
    padding: 6px 12px; font-size: 13px;
    cursor: pointer;
    color: inherit;
    line-height: 1.4;
}
.v2-place-autocomplete-item:hover,
.v2-place-autocomplete-item.is-active {
    background: #f0f4f9;
}

/* ─── Address map embed — modal + cards ─────────────────────────────
   Used in two places:
     - In the address-edit modal, full-width edge-to-edge via the
       v2-address-map-fullbleed modifier (negative horizontal margin
       cancels modal-body's inner padding).
     - In the address tile/card on the family detail page (no modifier;
       sits flush inside the tile).
   Iframe always fills its parent. Background fallback keeps a neutral
   color while Google's tiles load (or if it fails to render).
*/
.v2-address-map {
    border-top: 1px solid #dde3eb;
    border-bottom: 1px solid #dde3eb;
    overflow: hidden;
    height: 220px;
    background: #f0f4f9;
}
.v2-address-map iframe {
    border: 0;
    width: 100%;
    height: 100%;
    display: block;
}
/* Modal modifier — break out of .modal-body's horizontal padding so
   the map runs edge-to-edge of the modal frame. The -16px / -1rem
   matches Bootstrap 5's default modal-body padding. */
.v2-address-map-fullbleed {
    margin-left:  -1rem;
    margin-right: -1rem;
    margin-top:   12px;
    border-radius: 0;
}
/* Card variant — shorter, rounded bottom corners only (top edge sits
   under the address text). */
.v2-address-map-card {
    height: 140px;
    border-top: 1px solid #dde3eb;
    border-bottom: 0;
    margin-top: 8px;
    border-radius: 0 0 4px 4px;
}

/* Letter editor modal — outer body is a fixed-size flex column so the chrome
   (sandbox banner / template selector / email header / tabs / toolbar) sits at
   intrinsic height and the active tab pane flexes to fill remaining space.
   Only the editor itself scrolls — no double scrollbar. */
.v2-le-body,
#v2PendingLetterModal .v2-le-body {
    height: calc(100vh - 200px);
    max-height: 700px;
    min-height: 480px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
/* Tab panes (.p-3 children of .v2-le-body) flex to fill. ng-show hides the
   inactive ones, so only the active tab actually claims the space. */
.v2-le-body > .p-3 {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}
.v2-le-body > .p-3 > .v2-editor-toolbar { flex-shrink: 0; }
.v2-le-pdf-textarea { font-family: monospace; font-size: 13px; flex: 1 1 auto; min-height: 0; }
.v2-le-preview-wrap { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.v2-le-preview-iframe { width: 100%; height: 100%; border: 0; }
#v2PendingLetterModal { background: rgba(0, 0, 0, 0.5); }
.v2-batch-letter-select { max-width: 260px; }

/* Completed tab column widths (Family | Date Due | Category | Inv# | Amount | Notified | Snoozed | Actions).
   Actions column needs to be wide enough for up to 5 inline icons (Letter, Edit,
   Snooze, Dismiss, Renew) + edge padding — sized in px so it doesn't get squeezed
   at wide viewports. Other columns split the remaining space proportionally. */
.v2-pending-completed-cols { table-layout: fixed; }
.v2-pending-completed-cols th:nth-child(1), .v2-pending-completed-cols td:nth-child(1) { width: 22%; }
.v2-pending-completed-cols th:nth-child(2), .v2-pending-completed-cols td:nth-child(2) { width: 10%; }
.v2-pending-completed-cols th:nth-child(3), .v2-pending-completed-cols td:nth-child(3) { width: 19%; }
.v2-pending-completed-cols th:nth-child(4), .v2-pending-completed-cols td:nth-child(4) { width: 7%; }
.v2-pending-completed-cols th:nth-child(5), .v2-pending-completed-cols td:nth-child(5) { width: 10%; }
.v2-pending-completed-cols th:nth-child(6), .v2-pending-completed-cols td:nth-child(6) { width: 7%; }
.v2-pending-completed-cols th:nth-child(7), .v2-pending-completed-cols td:nth-child(7) { width: 9%; }
.v2-pending-completed-cols th:nth-child(8), .v2-pending-completed-cols td:nth-child(8) { width: auto; min-width: 170px; }
.v2-gradient-header .v2-btn-header-primary { background: rgba(0,0,0,0.35); color: #fff; border-color: rgba(255,255,255,0.4); }
.v2-gradient-header .v2-btn-header-primary:hover { background: rgba(0,0,0,0.55); color: #fff; border-color: rgba(255,255,255,0.6); }
.v2-gradient-header .v2-btn-header-primary:disabled { background: rgba(0,0,0,0.15); color: rgba(255,255,255,0.5); border-color: rgba(255,255,255,0.2); }

/* ══ Rich text editor — paragraph spacing ══════════════════════════════
   Quill ships `.ql-editor p { margin: 0 }`, so Enter (a new <p>) and
   Shift+Enter (a <br>) render identically and everything reads as one dense
   block. Restoring the margin makes the two behave the way anyone typing
   expects, and matches how the text renders once it is sent.

   Applies to every rich Quill in V2 — the Communications letter editor and the
   Forms email / thank-you editors. NOT to the plain PDF editor
   (.comm-quill-plain), which keeps tight line breaks on purpose. */
.comm-quill:not(.comm-quill-plain) .ql-editor p,
.fb-quill .ql-editor p { margin: 0 0 1em; }

.comm-quill:not(.comm-quill-plain) .ql-editor h1,
.comm-quill:not(.comm-quill-plain) .ql-editor h2,
.comm-quill:not(.comm-quill-plain) .ql-editor h3,
.fb-quill .ql-editor h1,
.fb-quill .ql-editor h2,
.fb-quill .ql-editor h3 { margin: 0 0 .4em; }

/* ══ Editor chrome: foldable details · one scrolling body · fixed footer ══
   The shape every "write a message" editor in V2 uses — the Communications
   letter editor and the Forms email tab today, anything similar next.

   Lives here rather than in a page stylesheet because it was written twice
   before this: settings-v2.css had it as .comm-*, and the form builder grew a
   hand-copied .fb-* lookalike that immediately started to drift. One
   definition, two names — the .comm-* selectors are kept so the Communications
   markup did not have to change, and its duplicate block is deleted. */
.v2-collapse-bar,
.comm-collapse-bar {
    display: flex; align-items: center; gap: 6px; width: 100%;
    background: transparent; border: 0; border-bottom: 1px solid var(--setv2-line, var(--v2-line));
    padding: 2px 0 8px; margin: 0 0 12px; cursor: pointer; color: var(--setv2-ink, #1f2733);
    font-size: 12.5px; font-weight: 600; text-align: left;
}
.v2-collapse-bar:hover,
.comm-collapse-bar:hover { color: var(--v2-primary, #4f7cff); }

.v2-collapse-chev,
.comm-collapse-chev { flex: 0 0 auto; transition: transform .15s ease; transform: rotate(-90deg); }
.v2-collapse-chev.is-open,
.comm-collapse-chev.is-open { transform: rotate(0deg); }

.v2-collapse-label,
.comm-collapse-label { flex: 0 0 auto; }

.v2-collapse-summary,
.comm-collapse-summary { flex: 1 1 auto; min-width: 0; font-weight: 400; color: #6b7280; }

/* Says WHY a section auto-expanded, so it doesn't read as a forgotten setting. */
.v2-collapse-warn { flex: 0 0 auto; font-weight: 400; color: var(--v2-warn, #b7791f); }

/* The body: ONE scrolling band. A fixed height in a modal (Communications), a
   flex child when the tab owns the viewport (Forms) — either way every
   pane fills the same box, so switching tab or Edit/Preview never changes the
   height and the footer cannot slide under the pointer. The Quill toolbar is a
   child of the pane, so it eats into the editor area instead of adding to it. */
.comm-body-zone { height: 262px; }
.comm-body-zone-tall { height: calc(min(460px, 58vh) + 42px); }
.v2-editor-zone { flex: 1 1 auto; min-height: 120px; }

.v2-editor-pane,
.comm-body-pane { height: 100%; display: flex; flex-direction: column; min-height: 0; }
.v2-editor-pane .ql-container,
.comm-body-pane .comm-quill-fill.ql-container { flex: 1 1 auto; height: auto !important; min-height: 0; }
.comm-body-pane.comm-quill-preview { height: 100%; }

.comm-body-foot { padding-top: 2px; }
/* flex:0 0 auto — the footer never shrinks and never scrolls away. */
.v2-editor-foot {
    flex: 0 0 auto;
    display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
    padding-top: 10px; margin-top: 10px;
    border-top: 1px solid var(--v2-line);
    background: var(--v2-surface);
}

/* Segmented control ON a gradient header. The plain .v2-segmented active state
   is primary-soft on primary — a light tint that vanishes against the header's
   own colour — and .v2-gradient-header .btn paints every segment solid white,
   so nothing distinguishes the chosen one. Same language as
   .v2-btn-header-primary above: unchosen segments recede into the gradient,
   the chosen one comes forward in solid white.

   The body.theme-v2 prefix is not decoration: v2-dashboard.css carries
   `.v2-main .card .card-header .btn-outline-secondary` (0,4,0), which reaches
   into every card header on the page and would otherwise win. The prefix takes
   these to (0,4,1) without an !important.

   That same dashboard rule is `border: none`, which zeroes the border STYLE.
   So the border here is restated as a shorthand — a border-color on its own
   paints nothing against a style of none. */
body.theme-v2 .v2-gradient-header .v2-segmented .btn {
    /* Lightened theme blue, not a dark wash. Both recede against the header, but
       this one stays part of it — a near-black segment reads as a separate, half
       -disabled control sitting on top of the header rather than a tab within it.
       color-mix keeps it tied to --v2-primary, so it re-resolves with the theme
       picker instead of drifting once someone changes the brand colour. */
    background: color-mix(in srgb, var(--v2-primary) 72%, #fff);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.85);
    /* That same dashboard rule sets padding:2px — right for a bare icon glyph
       in a card header, far too tight for a real button with a label. */
    padding: 4px 12px;
}
body.theme-v2 .v2-gradient-header .v2-segmented .btn:hover {
    background: var(--v2-primary);
    color: #fff;
    border-color: #fff;
}
body.theme-v2 .v2-gradient-header .v2-segmented .btn.active {
    background: #fff;
    color: var(--v2-primary);
    border: 1px solid #fff;
    font-weight: 600;
}
/* Single icon buttons alongside it (undo/redo) match the unchosen segments,
   so the group reads as one cluster rather than two sets of chrome. */
body.theme-v2 .v2-gradient-header .fb-head-tools > .btn {
    background: rgba(0,0,0,0.28);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.4);
    /* Narrower than the labelled segments: these are a single glyph each. */
    padding: 4px 9px;
}
body.theme-v2 .v2-gradient-header .fb-head-tools > .btn:hover:not(:disabled) {
    background: rgba(0,0,0,0.5);
    color: #fff;
    border-color: rgba(255,255,255,0.6);
}
body.theme-v2 .v2-gradient-header .fb-head-tools > .btn:disabled {
    background: rgba(0,0,0,0.12);
    color: rgba(255,255,255,0.45);
    border-color: rgba(255,255,255,0.2);
}

/* Category picker (Update Category modal) */
.v2-cat-picker-list {
    max-height: 250px;
    overflow-y: auto;
    border: 1px solid #dee2e6;
    border-radius: 4px;
}
.v2-cat-picker-item {
    padding: 5px 8px;
    cursor: pointer;
    font-size: 13px;
    border-bottom: 1px solid #f0f0f0;
}
.v2-cat-picker-item:last-child { border-bottom: none; }
.v2-cat-picker-item:hover { background: rgba(0,0,0,0.04); }
.v2-cat-picker-item.v2-cat-picker-selected {
    background: color-mix(in srgb, var(--v2-primary) 15%, transparent);
    font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════
   V2 OUTBOX — row states + status badges
   ══════════════════════════════════════════════════════════════════════ */
.v2-list-card .v2-outbox-row--frozen > td {
    background-color: #eff6ff !important;
}
.v2-list-card .v2-outbox-row--sent > td {
    background-color: #dcfce7 !important;
    transition: background-color 0.35s ease;
}
.v2-list-card .v2-outbox-row--error > td {
    background-color: #fef2f2 !important;
}
.v2-outbox-error {
    color: #b91c1c;
    font-size: 12px;
    max-width: 220px;
    vertical-align: middle;
}
.v2-outbox-status-badge {
    display: inline-block;
    font-size: 11px;
    padding: 1px 6px;
    border-radius: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.v2-outbox-status-badge--pending  { background: #e0e7ff; color: #3730a3; }
.v2-outbox-status-badge--failed   { background: #fee2e2; color: #991b1b; }
.v2-outbox-status-badge--sent     { background: #dcfce7; color: #166534; }
.v2-outbox-status-badge--frozen   { background: #dbeafe; color: #1e40af; }

/* Actions column: inline icons on desktop, collapsed three-dot dropdown on tablet+mobile.
   Collapses BEFORE field columns hide — preserves more info at tablet widths by giving
   the ~140px that the inline bar took back to Scheduled/Type/Status. */
.v2-actions-col { width: 180px; }
.v2-actions-inline { display: inline-flex; align-items: center; }
.v2-actions-collapsed { display: none; }
@media (max-width: 991px) {
    .v2-actions-col { width: 44px; }
    .v2-actions-inline { display: none !important; }
    .v2-actions-collapsed { display: inline-block !important; }
}

/* To + Subject cells — canonical auto-layout table ellipsis:
   `max-width: 0` tricks the browser into giving these cells only the leftover width after
   the fixed-width columns (icon/checkbox/date/scheduled/type/status/error/actions) take
   theirs. Combined with nowrap + overflow + ellipsis, text truncates to whatever space
   remains — from 1920px desktop down to 320px phone — with no dead space and no scroll. */
.v2-outbox-flex-cell {
    max-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════
   SHARED DETAIL-PAGE STYLES — for pages that show one record split into
   sections (Family Detail, future Member Detail, etc.). Distinct from
   list-page styles above.
   ══════════════════════════════════════════════════════════════════════ */

/* Page-level tab bar that sits below a v2-gradient-header. Distinct from
   .v2-batch-tabs (dark-pill, used inside batch modals). */
.v2-page-tabs {
    background: #fff;
    border-bottom: 1px solid #dde3eb;
    padding: 0 14px;
    display: flex;
    gap: 2px;
    overflow-x: auto;
    overflow-y: hidden;   /* Per CSS spec, setting overflow-x to a non-
                             visible value silently makes overflow-y
                             compute to auto — and a 1px sub-pixel rounding
                             on the tab strip then triggers a vertical
                             scrollbar with arrow buttons. Explicitly
                             hiding the Y axis suppresses that artifact. */
    flex-wrap: nowrap;
    flex-shrink: 0;
}
.v2-page-tabs .v2-page-tab {
    padding: 10px 14px;
    font-size: 13.5px;
    font-weight: 500;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    text-decoration: none;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    cursor: pointer;
}
.v2-page-tabs .v2-page-tab:hover { color: var(--v2-primary); }
.v2-page-tabs .v2-page-tab.active {
    color: var(--v2-primary);
    border-bottom-color: var(--v2-primary);
    font-weight: 600;
}
.v2-page-tabs .v2-page-tab .badge {
    font-size: 10px; padding: 2px 6px; vertical-align: 2px; margin-left: 4px;
    background: #e7eaf0; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-weight: 500;
}

/* Tab-count pill — small count badge next to a tab label,
   e.g. "Categories (12)". Used by any V2 page-tabs strip whose host
   exposes a `tab.count()` getter. */
.v2-tab-count {
    display: inline-block; min-width: 18px;
    padding: 0 6px; margin-left: 4px;
    font-size: 11px; font-weight: 500; line-height: 16px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); background: #e7ebf1; border-radius: 9px;
    text-align: center;
}
.v2-page-tab.active .v2-tab-count {
    color: #fff; background: rgba(255,255,255,0.25);
}

/* Mobile tab picker — replaces the horizontal tab strip when the viewport is
   too narrow to fit all tabs. The strip is technically scrollable but most
   users don't discover the swipe; a dropdown is more discoverable. Shown
   width-based (≤991px) via the media query further down — not pointer-based,
   so it also collapses on a narrow desktop window, not only touch devices. */
.v2-page-tabs-mobile { display: none; }
.v2-page-tabs-mobile-btn {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 16px;
    background: #fff;
    border: 0;
    border-bottom: 1px solid #dde3eb;
    color: var(--v2-primary);
    font-weight: 600;
    font-size: 14px;
    cursor: pointer;
}
.v2-page-tabs-mobile-btn:focus-visible { outline: 2px solid var(--v2-primary); outline-offset: -2px; }

/* Dropdown menu — V2-styled to match the rest of the family-detail
   chrome (matches the topnav and Help dropdowns). Full-width below the
   button, generous tap targets, subtle separators, primary-colored
   active row, and consistent with the desktop tab strip's accent. */
.v2-page-tabs-mobile .dropdown-menu {
    z-index: 1080;
    margin-top: 0;
    border: 1px solid #dde3eb;
    border-top: 0;
    border-radius: 0 0 6px 6px;
    box-shadow: 0 6px 18px rgba(0,0,0,0.08);
    padding: 4px 0;
    font-size: 14px;
}
.v2-page-tabs-mobile .dropdown-item {
    display: flex;
    align-items: center;
    padding: 12px 16px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    font-weight: 500;
    line-height: 1.3;
    border-left: 3px solid transparent;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.v2-page-tabs-mobile .dropdown-item:hover,
.v2-page-tabs-mobile .dropdown-item:focus {
    background: rgba(0,0,0,0.04);
    color: var(--v2-primary);
}
.v2-page-tabs-mobile .dropdown-item.active {
    color: var(--v2-primary);
    background: rgba(74,144,217,0.08);
    border-left-color: var(--v2-primary);
    font-weight: 600;
}
.v2-page-tabs-mobile .dropdown-item + .dropdown-item {
    border-top: 1px solid #f1f3f6;
}
.v2-page-tabs-mobile .dropdown-item .v2-tab-count {
    margin-left: auto;
    background: #e7eaf0;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    border-radius: 10px;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 500;
}
.v2-page-tabs-mobile .dropdown-item.active .v2-tab-count {
    background: var(--v2-primary);
    color: #fff;
}

/* Tabs collapse to the mobile dropdown below 992px — comfortably above the
   ~790px the 8 family-detail tabs occupy, so they never silently overflow into
   a horizontal scroll strip. Width-based (was hover-based): this must also fire
   on a narrow *desktop* window, not only on touch devices. */
@media (max-width: 991px) {
    .v2-page-tabs        { display: none; }
    .v2-page-tabs-mobile { display: block; }
}

/* Detail-page modifier on .v2-list-card — constrains width so multi-section
   read-only content doesn't sprawl across wide monitors. List pages fill the
   viewport (no modifier); detail pages opt in. */
.v2-list-card.v2-detail-page {
    max-width: 1400px;
    margin-left: auto;
    margin-right: auto;
    width: 100%;
}

/* Wrapper around the stack of sections inside .v2-list-card > .card-body */
.v2-detail-body { padding: 16px 18px 60px; background: #f3f5f8; }

/* A major content block on a detail page (Primary Contacts, Members, etc.) */
.v2-detail-section {
    background: #fff;
    border: 1px solid #e2e7ee;
    border-radius: 6px;
    margin-bottom: 18px;
}
.v2-detail-section:last-child { margin-bottom: 0; }
.v2-detail-section-head {
    padding: 10px 16px;
    border-bottom: 1px solid #dbe2eb;
    display: flex; align-items: center; gap: 8px;
    font-size: 13.5px; font-weight: 600; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #1f2d3d), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    /* Soft light-blue tint header band — echoes the dashboard tile
       gradient (blue family) at a much lower saturation so it reads as
       a header without competing with the body content. Top-radius
       matches the .v2-detail-section's 6px so the band sits flush. */
    background: linear-gradient(180deg, #eef3f9 0%, #f5f8fc 100%);
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
}
.v2-detail-section-head .v2-detail-section-count {
    display: inline-block;
    font-weight: 500; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-size: 11px;
    background: #e7eaf0;
    padding: 2px 7px;
    border-radius: 10px;
    line-height: 1.4;
    vertical-align: 2px;
}
.v2-detail-section-head .ms-auto { margin-left: auto; }
.v2-detail-section-body { padding: 14px 16px; }
.v2-detail-section-body.p-0 { padding: 0; }

/* Tile — a clickable read-only summary block (member, address, etc.).
   Hovers like a row, opens a modal on click. Variants set internal layout. */
.v2-tile {
    border: 1px solid #e2e7ee; border-radius: 6px; padding: 12px 14px;
    background: #fff; cursor: pointer; position: relative;
    transition: border-color .15s, box-shadow .15s;
}
.v2-tile:hover {
    border-color: var(--v2-primary);
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
}
.v2-tile .v2-tile-pencil {
    position: absolute; right: 10px; top: 10px;
    opacity: 0; color: var(--v2-primary); transition: opacity .15s;
}
.v2-tile:hover .v2-tile-pencil { opacity: 1; }

/* Tile-corner action group — reuses .v2-hover-actions / .v2-hover-action so
   styling lives in one place. Anchored to the top-right corner. Inherits
   the default .v2-hover-actions opacity (0.4) so icons are always visible
   at a faded weight (matching the V2 list pattern); tile hover bumps to
   full opacity. */
.v2-tile .v2-hover-actions {
    position: absolute;
    top: 8px;
    right: 8px;
    margin-left: 0;
    z-index: 2;
}
.v2-tile:hover .v2-hover-actions { opacity: 1; }

/* Touch devices have no hover state, so the hover-revealed UI above
   would be permanently invisible / unreachable. Force it visible on
   anything without a hover-capable pointer (phones, most tablets).
   The pencil overlay on tiles + the dim row actions on tables also
   get pinned full-opacity so taps are obvious targets. */
@media (hover: none) {
    .v2-hover-actions,
    .v2-tile .v2-hover-actions,
    .v2-tile .v2-tile-pencil,
    .v2-family-name .pencil { opacity: 1 !important; }
}

/* Shared stack point for tile grids on a detail page so columns collapse
   together regardless of the tile variant. */
.v2-tile-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 12px;
}

/* Avatar for detail pages (initials circle or member photo). Distinct from
   the smaller list-page .v2-avatar (defined above for v2-avatar-stack), which
   is photo-only at 18px. This one supports both initials text and an <img>. */
.v2-detail-avatar {
    width: 56px; height: 56px; border-radius: 50%;
    background: linear-gradient(135deg, #c2d3e6, #9bb3cd);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 18px; font-weight: 600; color: #fff; user-select: none;
    flex-shrink: 0;
    object-fit: cover;
}
.v2-detail-avatar.v2-detail-avatar-female { background: linear-gradient(135deg, #efc6d6, #d490a8); }

/* SVG silhouette variant — used when there is no photo. Outlined or filled
   in a gender-distinguishing color (set inline by the avatar partial), no
   border-radius/background — the silhouette IS the visual. */
.v2-detail-avatar-svg {
    width: 56px; height: 56px; flex-shrink: 0;
}

/* Small (table-row) variant — sized to NOT push row height beyond what the
   font-size already drives, and scales with [data-v2-size] so the avatar
   keeps proportion with the row text. Mirrors the list-page .v2-avatar
   convention but explicitly tied to the size setting (not density). */
.v2-detail-avatar.v2-detail-avatar-sm,
.v2-detail-avatar-svg.v2-detail-avatar-svg-sm {
    width: 18px; height: 18px;
}
.v2-detail-avatar.v2-detail-avatar-sm { font-size: 9px; }

[data-v2-size="sm"] .v2-detail-avatar.v2-detail-avatar-sm,
[data-v2-size="sm"] .v2-detail-avatar-svg.v2-detail-avatar-svg-sm { width: 16px; height: 16px; }

[data-v2-size="lg"] .v2-detail-avatar.v2-detail-avatar-sm,
[data-v2-size="lg"] .v2-detail-avatar-svg.v2-detail-avatar-svg-sm { width: 22px; height: 22px; }

/* Small status/role chip — neutral, primary, or warn */
.v2-chip {
    display: inline-block; font-size: 10.5px; padding: 1.5px 7px; border-radius: 3px;
    background: #eef2f7; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #5a6675), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); font-weight: 500; line-height: 1.5;
}
.v2-chip-primary { background: var(--v2-primary); color: #fff; }
.v2-chip-warn { background: #fdebc7; color: #8a5a00; }
/* Matches the .v2-flag-pill.v2-flag-info palette used in the address-edit
   modal so a "Current" chip on an address card and the same-state pill
   inside the modal read as the same colour. */
.v2-chip-info { background: #dbeafe; color: #1d4ed8; }

/* Good / muted state chips — built from the shared status tokens rather than
   fresh hex, so they track the palette. Added for the Forms list
   (Live / Inactive) but deliberately generic: any list needing a positive or
   a de-emphasised state chip should use these instead of rolling its own. */
.v2-chip-good { background: var(--v2-good-soft); color: var(--v2-good); }
.v2-chip-muted { background: #eef2f7; color: var(--v2-ink-3); }
/* Switched off entirely — the Forms list's Disabled state, one step past muted. */
.v2-chip-bad { background: #fde2e1; color: var(--v2-danger, #b42318); }

/* "Updated via USPS" NCOA stamp — a clickable chip that opens the address
   history popup. Green to read as a verified/maintained state, distinct from
   the neutral/info/warn chips. Rendered as a <button>, so reset button chrome. */
.v2-chip-ncoa {
    border: 0; cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
    background: #dcfce7; color: #15803d; font-family: inherit;
}
.v2-chip-ncoa:hover { background: #bbf7d0; color: #166534; }
.v2-chip-ncoa svg { flex: 0 0 auto; }

/* History-popup subtitle — muted line under the white .v2-modal title, on the
   primary header bar (hence a translucent-white tone, not the grey body muted). */
.v2-modal .modal-header .v2-ncoa-hist-subtitle {
    font-size: 11.5px; font-weight: 400; line-height: 1.35; margin-top: 2px;
    color: rgba(255, 255, 255, 0.82);
}

/* Type pills — colors mirror the NCOA review page's .ncoa-pill-* palette
   (ncoa.css: Standardized=primary, Move=#7a3ff2 purple, Undeliverable=#c8322f
   red). Those vars are scoped to .ncoa-page, so the literals are repeated here. */
.v2-ncoa-hist-pill { font-weight: 600; }
.v2-ncoa-hist-pill-std  { background: color-mix(in srgb, var(--v2-primary, #4a90d9) 14%, #fff); color: var(--v2-primary, #4a90d9); }
.v2-ncoa-hist-pill-move { background: #f0eaff; color: #7a3ff2; }
.v2-ncoa-hist-pill-bad  { background: #fce8e7; color: #c8322f; }

/* NCOA address-history popup — one card per applied change (on the .v2-modal
   surface body), each with its own Before / After panel. Stacks on phones. */
.v2-ncoa-hist-entry {
    padding: 12px 14px; border: 1px solid var(--v2-border, #e5e9f0); border-radius: 8px;
    background: #fff; margin-bottom: 12px;
}
.v2-ncoa-hist-entry:last-child { margin-bottom: 0; }
.v2-ncoa-hist-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.v2-ncoa-hist-boxes { display: flex; align-items: stretch; gap: 10px; }
.v2-ncoa-hist-box {
    flex: 1 1 0; min-width: 0; padding: 9px 11px; border-radius: 6px;
    background: var(--v2-surface, #f6f8fb); border: 1px solid var(--v2-border, #e5e9f0);
}
.v2-ncoa-hist-box-nomail { background: #fdebc7; border-color: #f0d089; }
.v2-ncoa-hist-box-label {
    font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--v2-muted-base, #5a6675); margin-bottom: 4px;
}
.v2-ncoa-hist-line { font-size: 13px; line-height: 1.35; word-break: break-word; }
.v2-ncoa-hist-csz  { font-size: 13px; line-height: 1.35; word-break: break-word; }
.v2-ncoa-hist-arrow {
    display: flex; align-items: center; align-self: center;
    color: var(--v2-muted-base, #8a909c); flex: 0 0 auto;
}
@media (max-width: 575px) {
    .v2-ncoa-hist-boxes { flex-direction: column; }
    .v2-ncoa-hist-arrow { transform: rotate(90deg); align-self: center; margin: 1px 0; }
}

/* ══════════════════════════════════════════════════════════════════════
   FORM PRIMITIVES FOR V2 EDIT MODALS — used by Member Edit, Yahrtzeit
   Edit, and any future per-record edit modal. Goal: single source for
   form layout, flag pills, picture uploader, info banner, contact-row.
   ══════════════════════════════════════════════════════════════════════ */

/* Read-only context strip at the top of an edit modal body. */
.v2-modal-info-banner {
    display: flex; align-items: center; gap: 12px;
    padding: 8px 12px; background: #f6f8fa;
    border: 1px solid #e3e6ea; border-radius: 4px;
    font-size: 12.5px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    margin-bottom: 14px;
}
.v2-modal-info-banner strong { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }

/* 4-column responsive form grid — picture + 3 equal field stacks. Used
   in the Member Edit modal's first section. Drops to 2-col below 900px,
   single column below 768px. */
.v2-form-grid {
    display: grid;
    grid-template-columns: 96px 1fr 1fr 1fr;
    gap: 14px 18px; align-items: start;
}
/* Primary-member variant — Gender/Relation/His-Her column is hidden, so
   collapse the grid to 3 columns. (Tablet/mobile breakpoints below already
   collapse further regardless.) */
.v2-form-grid.v2-form-grid-primary {
    grid-template-columns: 96px 1fr 1fr;
}
.v2-form-grid .v2-form-stack { display: flex; flex-direction: column; gap: 12px; }
.v2-form-grid .v2-form-stack > div { display: flex; flex-direction: column; }
.v2-form-grid .v2-form-pic-wrap { display: flex; justify-content: center; padding-top: 18px; }
@media (max-width: 900px) {
    /* Both selectors here so .v2-form-grid-primary (specificity 0,2,0)
       collapses too — without this it kept its 3-column 96px-1fr-1fr
       template, which squeezed the Title/First/Last column to 96px and
       left the DOB column with empty trailing space. */
    .v2-form-grid,
    .v2-form-grid.v2-form-grid-primary { grid-template-columns: 1fr 1fr; }
    .v2-form-grid .v2-form-pic-wrap { grid-column: 1 / -1; padding-top: 0; justify-content: flex-start; }
}
@media (max-width: 768px) {
    .v2-form-grid,
    .v2-form-grid.v2-form-grid-primary { grid-template-columns: 1fr; }
    .v2-form-grid .v2-form-pic-wrap { justify-content: flex-start; }
}

/* Picture uploader — circular avatar with a small pencil overlay.
   `overflow: visible` lets the corner badges (pencil/remove/spinner)
   extend outside the circle; the image itself is clipped via its own
   border-radius so the photo still respects the round shape. */
.v2-pic-uploader {
    width: 80px; height: 80px; border-radius: 50%;
    background: linear-gradient(135deg, #c2d3e6, #9bb3cd);
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-size: 22px; font-weight: 600; cursor: pointer;
    position: relative; flex-shrink: 0; user-select: none;
    border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.08);
}
.v2-pic-uploader img {
    width: 100%; height: 100%; object-fit: cover;
    border-radius: 50%;
}
.v2-pic-uploader::after {
    content: '\270E';
    position: absolute; right: -3px; bottom: -3px;
    background: var(--v2-primary); color: #fff;
    font-size: 11px; width: 22px; height: 22px;
    border-radius: 50%; border: 2px solid #fff;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.v2-pic-uploader.is-disabled { cursor: not-allowed; opacity: 0.7; }
.v2-pic-uploader.is-disabled::after { display: none; }
.v2-pic-uploader.is-uploading { cursor: progress; }
.v2-pic-uploader.is-uploading::after { display: none; }
.v2-pic-spinner {
    position: absolute; inset: 0; border-radius: 50%;
    background: rgba(255,255,255,0.55);
    display: flex; align-items: center; justify-content: center;
    pointer-events: none;
}
.v2-pic-spinner::before {
    content: ''; width: 26px; height: 26px;
    border: 3px solid rgba(74,144,217,0.25);
    border-top-color: var(--v2-primary);
    border-radius: 50%;
    animation: v2-pic-spin 0.7s linear infinite;
}
@keyframes v2-pic-spin { to { transform: rotate(360deg); } }
.v2-pic-remove {
    position: absolute; top: -3px; right: -3px;
    width: 22px; height: 22px; border-radius: 50%;
    background: #dc3545; color: #fff; border: 2px solid #fff;
    font-size: 14px; line-height: 1; padding: 0;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; z-index: 2;
    box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.v2-pic-remove:hover { background: #b02a37; }
.v2-pic-file-input { display: none; }

/* Photo crop modal — Cropper.js stage + tool row. The stage sets a
   bounded height so the cropper's internal canvas can size itself
   correctly; the img must be display:block per Cropper.js requirements. */
.v2-photo-crop-stage {
    width: 100%; height: 360px;
    background: #f3f5f8; border-radius: 4px; overflow: hidden;
}
.v2-photo-crop-stage img { display: block; max-width: 100%; }
.v2-photo-crop-tools {
    display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px;
    justify-content: center;
}
.v2-photo-crop-tools .btn {
    min-width: 36px; min-height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 4px 10px; font-size: 14px;
}
@media (max-width: 768px) {
    .v2-photo-crop-stage { height: 70vh; }
}

/* Expandable inline sub-section — used for Hebrew names. Click toggle
   reveals body. Chevron rotates on open. */
.v2-form-expandable-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12.5px; font-weight: 500; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    cursor: pointer; user-select: none; padding: 4px 0;
    text-decoration: none; background: transparent; border: 0;
}
.v2-form-expandable-toggle:hover { color: var(--v2-primary); }
.v2-form-expandable-toggle svg { width: 12px; height: 12px; transition: transform 0.15s; }
.v2-form-expandable.is-open .v2-form-expandable-toggle svg { transform: rotate(90deg); }
.v2-form-expandable-body { display: none; padding-top: 8px; }
.v2-form-expandable.is-open .v2-form-expandable-body { display: block; }

/* Contact row — multi-row contact list inside Contact Methods section.
   Mobile (≤768px) shows only Type + Value. */
.v2-contact-row {
    display: grid;
    /* Flags column: fixed width so header + data rows (each its own grid)
       compute identical column tracks — `auto` was sizing the header's
       Flags cell to the text width and the data row's to the pill-group
       width, knocking the labels off their fields. */
    grid-template-columns: 130px 1fr 80px 240px 28px;
    gap: 10px; align-items: center;
}
/* Yahrtzeit relationship picker — typeahead dropdown of matching members. */
.v2-yahrtzeit-rel-picker { position: relative; }

/* Mobile only — give each relationship row a soft border so multiple
   stacked rows don't visually merge into one giant column. Desktop
   keeps the dense table-like row layout (no border). */
@media (max-width: 767px) {
    .v2-yahrtzeit-rel-row {
        padding: 10px 0 4px;
        border-bottom: 1px solid #eef1f5;
    }
    .v2-yahrtzeit-rel-row:last-of-type { border-bottom: 0; }
}
.v2-yahrtzeit-rel-results {
    position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
    background: #fff; border: 1px solid #cfd6df; border-top: 0;
    border-radius: 0 0 4px 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.08);
    max-height: 220px; overflow-y: auto;
}
.v2-yahrtzeit-rel-result {
    display: block; width: 100%; text-align: left;
    padding: 6px 10px; font-size: 13px;
    background: transparent; border: 0; border-bottom: 1px solid #f0f3f7;
    cursor: pointer;
}
.v2-yahrtzeit-rel-result:hover,
.v2-yahrtzeit-rel-result.is-active { background: #e6effa; }
.v2-yahrtzeit-rel-result:last-child { border-bottom: 0; }
.v2-contact-row + .v2-contact-row { margin-top: 6px; }
.v2-contact-row .btn-del {
    background: transparent; border: 0; padding: 0;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #999), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); line-height: 1; font-size: 18px;
}
.v2-contact-row .btn-del:hover { color: #b54545; }

/* Row-end kebab — replaces the bare × button. Same column slot (28px),
   carries both Note + Remove via dropdown. */
.v2-contact-row-actions { position: relative; }
.v2-contact-row-kebab {
    background: transparent; border: 0; padding: 2px 4px;
    color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #8a96a6), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); cursor: pointer; line-height: 0;
    border-radius: 3px;
    position: relative;
}
.v2-contact-row-kebab:hover { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); background: rgba(0,0,0,0.04); }
.v2-contact-row-kebab[aria-expanded="true"] { color: var(--v2-primary); background: rgba(0,0,0,0.04); }
/* Small dot in the upper-right of the kebab when the row has a Note —
   so the user knows there's content to read without expanding the menu. */
.v2-contact-row-note-dot {
    position: absolute; top: 2px; right: 2px;
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--v2-primary);
    pointer-events: none;
}
/* Inline note editor — sits flush under the contact row, indented
   past the type column so it visually belongs to the row above. */
.v2-contact-row-note {
    margin: 4px 0 8px 140px;   /* 130 (type col) + 10 (gap) */
    max-width: 720px;
}

/* Family-edit "Whose number/email is this?" picker — uses the same
   grid template as .v2-contact-row so the cell sits directly under
   the Value (Number) column above. The cell stacks a small label
   ("Whose mobile number is this?") on top of the select; both
   inherit the column's width so the select width matches the Number
   input above exactly. */
.v2-family-claim-row {
    display: grid;
    grid-template-columns: 130px 1fr 80px 240px 28px;
    gap: 10px;
    margin: 4px 0 8px;
}
.v2-family-claim-row > .v2-family-claim-cell { grid-column: 2; }
@media (max-width: 768px) {
    /* Mobile: contact row collapses to "100px 1fr 28px" — line up here too. */
    .v2-family-claim-row { grid-template-columns: 100px 1fr 28px; gap: 6px; }
}

@media (max-width: 768px) {
    /* Mobile: Type / Value / Kebab only. Ext + Flags hidden. */
    .v2-contact-row { grid-template-columns: 100px 1fr 28px; gap: 6px; }
    .v2-contact-row > :nth-child(3),  /* Ext */
    .v2-contact-row > :nth-child(4) { /* Flags */
        display: none;
    }
    /* On mobile the inline note moves under the value column. */
    .v2-contact-row-note { margin-left: 106px; }
}

/* Flag pills — toggleable chip-style indicators (Primary, No Send, No Trans, NoSMS) */
.v2-flag-group {
    display: flex; gap: 4px; align-items: center;
    justify-content: center; flex-wrap: wrap;
}
.v2-flag-pill {
    display: inline-flex; align-items: center;
    font-size: 10.5px; font-weight: 500; line-height: 1.5;
    padding: 1.5px 7px; border-radius: 3px;
    border: 1px solid #d4dae3; background: #fff; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #8a96a6), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%));
    cursor: pointer; user-select: none;
    transition: all 0.1s;
}
.v2-flag-pill:hover { border-color: #8a96a6; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #5a6675), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
.v2-flag-pill.is-on.v2-flag-primary {
    background: #2563eb; color: #fff; border-color: #2563eb;
}
.v2-flag-pill.is-on.v2-flag-warn {
    background: #fdebc7; color: #8a5a00; border-color: #f5d68a;
}
.v2-flag-pill.is-on.v2-flag-danger {
    background: #fde4e4; color: #b54545; border-color: #f5b8b8;
}
/* Info / neutral-positive on-state — used for the Current address chip
   in the address modal. Light blue (positive but not "this is THE
   primary" red). */
.v2-flag-pill.is-on.v2-flag-info {
    background: #dbeafe; color: #1d4ed8; border-color: #bfdbfe;
}
.v2-flag-pill.v2-flag-readonly { cursor: default; }
.v2-flag-pill.v2-flag-readonly:hover { border-color: #d4dae3; color: color-mix(in srgb, color-mix(in srgb, var(--v2-muted-base, #8a96a6), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }
.v2-flag-pill.v2-flag-readonly.is-on.v2-flag-warn:hover {
    background: #fdebc7; color: #8a5a00; border-color: #f5d68a;
}

/* ── Clickable checkbox / radio labels ─────────────────────────────
   Native HTML already toggles the input when you click an associated
   <label for="..."> — but Bootstrap's default doesn't put `cursor:pointer`
   on those labels, so users don't realize they can click. This rule fixes
   it globally for every V2 checkbox/radio: the input AND its label both
   show the pointer cursor, and clicking either toggles the input.
   Standard Bootstrap markup pattern is required:
       <input class="form-check-input" type="checkbox" id="X" ...>
       <label class="form-check-label" for="X">Text</label>                                                     */
.theme-v2 .form-check-input,
.theme-v2 .form-check-label { cursor: pointer; }
.theme-v2 .form-check-input:disabled,
.theme-v2 .form-check-input:disabled ~ .form-check-label,
.theme-v2 .form-check-input[disabled] ~ .form-check-label { cursor: not-allowed; }

/* Yahrtzeit summary block (shown inside Member Edit modal when Deceased) */
.v2-yahrtzeit-summary {
    background: #f6f8fa; border: 1px solid #e3e6ea; border-radius: 4px;
    padding: 12px 14px; margin-top: 10px;
}
.v2-yahrtzeit-summary-row {
    display: flex; align-items: center; gap: 8px;
    font-size: 12.5px; color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #4a5666), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); padding: 2px 0;
}
.v2-yahrtzeit-summary-row strong { color: color-mix(in srgb, color-mix(in srgb, var(--v2-text-base, #2c3e50), white var(--v2-c-wht, 0%)), black var(--v2-c-blk, 0%)); }

/* ─── Shared BETA badge ───────────────────────────────────────────────
   Small pill that sits inline with a heading to mark a feature as beta.
   Same orange color as the global top-nav BETA pill (assets/tpl/v2/
   partials/topnav.html) so users associate it with "new + still being
   refined". Use as a sibling of the heading text:
     <h1>Studio <span class="v2-beta-badge">BETA</span></h1> */
.v2-beta-badge {
    display: inline-block;
    margin-left: 10px;
    padding: 3px 10px;
    border-radius: 999px;
    background: #f39c12;
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    vertical-align: middle;
    line-height: 1.4;
    user-select: none;
}

/* ============================================================
   V2 Reports — landing-page styles
   Used by contact-reports.html + transaction-reports.html and
   their shared list-body / toolbar / folder-pane partials.
   ============================================================ */

/* Two-column body: folder pane on the left, list on the right.
   Lives inside .v2-list-card > .card-body. */
.v2-reports-layout { display: flex; align-items: stretch; }
.v2-reports-layout > .v2-reports-list-col { flex: 1; min-width: 0; }

/* Folder pane */
.v2-folder-pane {
    width: 220px;
    flex-shrink: 0;
    border-right: 1px solid #e8ecf2;
    background: #fafbfc;
    display: flex;
    flex-direction: column;
    min-height: 360px;
}
.v2-folder-pane-head {
    padding: 8px 10px;
    border-bottom: 1px solid #e8ecf2;
    display: flex; align-items: center; gap: 6px;
    font-size: 12.5px; font-weight: 600; color: #4a5666;
    text-transform: uppercase; letter-spacing: .04em;
}
.v2-folder-pane-head .btn-link { padding: 0; line-height: 0; opacity: .6; color: inherit; border: none; background: transparent; }
.v2-folder-pane-head .btn-link:hover { opacity: 1; color: var(--v2-primary); }
.v2-folder-list { list-style: none; margin: 0; padding: 6px 0; flex: 1; overflow-y: auto; }
.v2-folder-item {
    display: flex; align-items: center; gap: 6px;
    padding: 5px 10px 5px 14px;
    font-size: 13.2px;
    color: #2c3a4b;
    cursor: pointer;
    border-left: 3px solid transparent;
}
.v2-folder-item:hover { background: #eef2f7; }
.v2-folder-item.active {
    background: #e7f1fb;
    border-left-color: var(--v2-primary);
    color: var(--v2-primary);
    font-weight: 500;
}
.v2-folder-item .folder-icon { color: #f0c75e; flex-shrink: 0; }
.v2-folder-item .count { margin-left: auto; font-size: 11.5px; color: #8896a8; }
.v2-folder-item.is-drop-target { background: #d4e7f9; border-left-color: var(--v2-primary); }

/* Below 992px the folder pane is hidden — the toolbar's Folder chip
   opens an offcanvas instead. (Per Phase 1 plan: narrow widths = offcanvas.) */
@media (max-width: 991.98px) {
    .v2-folder-pane { display: none; }
}

/* Row chips: folder, custom, shared, last-pdf age */
.v2-report-chip {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 1px 6px; border-radius: 10px;
    font-size: 11px; font-weight: 500; line-height: 1.5;
    background: #eef2f7; color: #4a5666;
    white-space: nowrap;
}
.v2-report-chip.chip-folder { background: #eef1f5; color: #52606d; }
.v2-report-chip.chip-folder svg { color: #8a93a0; }
.v2-report-chip.chip-custom { background: #f3e9ff; color: #5b3aa1; }
.v2-report-chip svg { flex-shrink: 0; }

/* State-aware PDF chip — single icon-only button per row that morphs through
   Idle / In-progress / Ready / Outdated / Error. Tooltip drives meaning. */
.v2-pdf-state {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 4px;
    width: 26px; height: 26px;
    border-radius: 4px;
    background: #eef2f7; color: #4a5666;
    border: 1px solid #d5dce5;
    cursor: pointer;
    margin-right: 2px;
}
.v2-pdf-state:hover { background: #e0e6ee; }
.v2-pdf-state svg { width: 14px; height: 14px; }
/* Secondary "regenerate" ↻ next to a ready/error PDF state. */
.v2-pdf-regen {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 3px;
    border-radius: 4px; color: #8a93a0; cursor: pointer;
    transition: background .12s, color .12s;
}
.v2-pdf-regen:hover { background: #e0e6ee; color: #2c3a4b; }
.v2-pdf-regen svg { width: 13px; height: 13px; }
.v2-pdf-state.is-ready    { background: #e6f4ea; color: #1f7a3c; border-color: #bfe3c8; }
.v2-pdf-state.is-ready:hover { background: #d4ecdd; }
.v2-pdf-state.is-progress { background: #fff8e1; color: #8a6500; border-color: #f0d97d; cursor: default; }
.v2-pdf-state.is-outdated { background: #fef2e0; color: #8a4d00; border-color: #f3c98e; }
.v2-pdf-state.is-error    { background: #fde7e7; color: #a52828; border-color: #f3b9b9; }
.v2-pdf-state .v2-mini-spin {
    width: 12px; height: 12px;
    border: 2px solid currentColor; border-right-color: transparent;
    border-radius: 50%;
    animation: v2-pdf-spin .8s linear infinite;
}
@keyframes v2-pdf-spin { to { transform: rotate(360deg); } }

/* Shared cell — checkbox is source of truth; share icon appears only when checked. */
.v2-share-cell { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.v2-share-cell input[type=checkbox] { transform: scale(0.9); margin: 0; cursor: pointer; }
.v2-share-cell .v2-share-icon { color: var(--v2-primary); width: 16px; height: 16px; visibility: hidden; }
.v2-share-cell input[type=checkbox]:checked ~ .v2-share-icon { visibility: visible; }
.v2-share-cell input[type=checkbox]:disabled { cursor: not-allowed; }

/* Reports — only the per-row meta bits that aren't covered by the
   shared .v2-list-card / .table styles. Header band, striping, hover
   row, sort cursor + indicators all come from the shared V2 list
   styling pattern (matches gifts / deposits / outbox / outbox). */
.v2-report-name { font-weight: 500; color: #2c3a4b; display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; min-width: 0; max-width: 100%; }
/* Report name never wraps — truncate with an ellipsis; chips stay visible. */
.v2-report-name > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.v2-report-name .v2-report-chip { flex-shrink: 0; }
/* Name column takes the leftover width (width:100%) and truncates its content
   inside it (max-width:0 is the auto-layout ellipsis trick). Other columns keep
   their template widths. */
#reportTable td:first-child, #reportTable th:first-child { max-width: 0; width: 100%; overflow: hidden; }
/* Density: the V2 reports list reuses id="reportTable", whose legacy
   reports.css rule (#reportTable > tbody > tr > td { padding: 2px }) has id
   specificity that beats the shared [data-v2-density] .table td rules — so the
   list ignored the density setting (unlike the Transactions list, which has no
   such id rule). Re-assert density here with matching id specificity so the
   reports rows track the setting just like every other V2 list. */
[data-v2-density="compact"]  #reportTable > tbody > tr > td { padding: 2px 6px; font-size: 12px; }
[data-v2-density="comfort"]  #reportTable > tbody > tr > td { padding: 5px 8px; }
[data-v2-density="spacious"] #reportTable > tbody > tr > td { padding: 10px 8px; }
[data-v2-density="compact"]  #reportTable > thead > tr > th { padding-top: 2px; padding-bottom: 2px; }
[data-v2-density="spacious"] #reportTable > thead > tr > th { padding-top: 8px; padding-bottom: 8px; }
.v2-report-meta { color: #6b7787; font-size: 12.2px; margin-top: 2px; }
.v2-row-disabled { opacity: .65; }

/* ============================================================
   V2 Reports Builder (Phase 4)
   Used by contact-builder.html + transaction-builder.html and
   their shared partials. Reuses .v2-list-card, .v2-gradient-header,
   .v2-page-tabs from the shared V2 stack — only adds builder-
   specific bits below.
   ============================================================ */

/* Builder card — same outer chrome as a list card but the body grows
   instead of scrolling internally (builder forms vary in length). */
.v2-builder-card { background: #fff; }
.v2-builder-body { min-height: 360px; background: #f7fafd; }

/* Toolbar — Name / Folder / Sort / Use For Filter row. */
.v2-builder-toolbar { background: #fff; border-bottom: 1px solid #e8ecf2; }
.v2-builder-field { display: flex; flex-direction: column; }
.v2-builder-field-label {
    font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;
    color: #6b7787; font-weight: 600; margin-bottom: 3px;
}
.v2-builder-folder-btn {
    font-size: 13px; line-height: 1.4;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Reports V2 page container — mirrors .ncoa-page so the list + builder
   read as centered content cards instead of full-bleed rails on wide
   screens. NCOA cap is 1080px; using the same figure so the two Tools
   pages sit at identical widths. */
/* The reports page wrapper itself is full-bleed — the 1080px cap lives on the
   *content* column, not here, so the folder rail can sit BESIDE a full-width
   content area instead of eating into it. Two consumers:
     • Builder — `.v2-builder-card` is the content column → capped + centered
       below, so it reads exactly like the NCOA page (1080, centered).
     • List — the folder rail + `.v2-reports-layout__list` share this wrapper;
       the cap is on the list column (below) and the rail is additive, so
       opening folders never shrinks the list while there's room for both. */
.v2-reports-page {
    max-width: none;
    margin: 0;
}

/* Builder content column — centered 1080 card, matching NCOA. */
.v2-reports-page .v2-builder-card {
    max-width: 1080px;
    margin-left: auto;
    margin-right: auto;
}

/* Compact form controls inside the builder body — mirrors the
   `.setv2-page .form-control/.form-select` rule so setv2-styled sections
   render at the same 13px density as the Settings V2 pages (the builder
   isn't wrapped in `.setv2-page`, so without this the fields default to
   Bootstrap's 16px and read as oversized). */
.v2-builder-body .form-control,
.v2-builder-body .form-select {
    border: 1.5px solid #bbb;
    background-color: var(--v2-field-bg);
    font-size: 13px;
}
.v2-builder-body .form-control:focus,
.v2-builder-body .form-select:focus {
    border-color: var(--v2-primary);
    background-color: var(--v2-field-bg-focus);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--v2-primary) 20%, transparent);
}
.v2-builder-body .form-check-label { font-size: 13px; }

/* Two-column date-range group (Created | Modified in the V2 Reports
   Families criteria). Each column has a subtle header + From/To fields
   stacked underneath. Collapses to a single column at ≤ 640px so it
   works on phones. */
.v2-datecol-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
}
@media (max-width: 640px) { .v2-datecol-grid { grid-template-columns: 1fr; gap: 8px; } }
.v2-datecol__head {
    font-size: 13px;
    font-weight: 700;
    color: var(--v2-ink);
    padding-bottom: 6px;
    border-bottom: 1px dashed var(--v2-line);
    margin-bottom: 4px;
}
/* Inside a datecol the From/To fields sit tight — no dashed divider
   between them, minimal vertical padding so To sits right under From. */
.v2-datecol .setv2-field { border-bottom: 0; padding: 4px 0; }

/* Uppercase-typed input — value goes uppercase, placeholder stays as
   the author wrote it (so "e.g. A-F" doesn't render as "E.G. A-F"). */
.v2-uppercase-value { text-transform: uppercase; }
.v2-uppercase-value::placeholder { text-transform: none; }

/* Family/Member Categories two-column layout: "Limit to" and "Exclude"
   side-by-side. Collapses to one column at ≤ 900px so it stays usable
   on tablets and phones. */
.v2-catpair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
    margin-bottom: 18px;
}
@media (max-width: 900px) { .v2-catpair { grid-template-columns: 1fr; } }
.v2-catpair__side { margin-bottom: 0; }
.v2-catpair__list {
    /* Flex-fill within .v2-catpair__side; the max-height / calc that
       lived here is replaced by the flex chain below. */
    overflow-y: auto;
    padding-top: 6px;
    min-height: 0;
    flex: 1 1 0;
}

/* Kill the 80px bottom padding that .setv2-detail inherits from the
   Settings V2 stylesheet — reports builder has no sticky footer. */
.v2-builder-rail-body .setv2-detail { padding-bottom: 12px; }

/* Full flex chain from the rail body down to the category tree lists
   whenever the two-column category pair is showing. Only the two lists
   scroll; nothing else does. Scoped with :has() so other sections
   (short forms) keep their normal auto-scroll fallback.
   SIDE-BY-SIDE ONLY (≥ 901px): this "nothing scrolls but the two lists"
   chain only makes sense while the pair sits in two columns. Once the pair
   stacks (≤ 900px) there's no bounded pane to fill — the lists get a fixed
   height and the pane (or page, on phones) scrolls as one instead. */
@media (min-width: 901px) {
    .v2-builder-rail-body:has(.v2-catpair) {
        overflow: hidden;
        display: flex;
        flex-direction: column;
        min-height: 0;
    }
    .v2-builder-rail-body:has(.v2-catpair) > :not(.v2-mobile-back-bar) {
        flex: 1 1 0;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }
    .v2-builder-rail-body:has(.v2-catpair) .setv2-detail {
        flex: 1 1 0;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }
    .v2-builder-rail-body:has(.v2-catpair) .v2-catpair {
        flex: 1 1 0;
        min-height: 0;
        margin-bottom: 0;
    }
}
.v2-catpair__side {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Stacked pair (≤ 900px): once the two sides sit one above the other there's
   no bounded pane to flex-fill, so each list keeps a decent fixed height and
   the pane (tablet) or page (phone) scrolls as one. dvh keeps the height
   honest on iOS. */
@media (max-width: 900px) {
    .v2-catpair__list {
        flex: 0 0 auto;
        min-height: 200px;
        max-height: 50vh;
        max-height: 50dvh;
    }
}

/* Phone (≤ 600px): reclaim vertical space in EVERY builder section — drop
   the section description line and shorten "Clear section" to just "Clear".
   The full-screen section overlay is tight on room; the heading (plus the
   count badge back on the rail) is enough context. */
@media (max-width: 600px) {
    .v2-builder-rail-body .setv2-detail__head p { display: none; }
    .v2-clear-word { display: none; }
}

/* Cluster header — allow wrap so the controls row falls to a second
   line when the pair sits narrow. */
.v2-catpair__head { flex-wrap: wrap; row-gap: 6px; }
.v2-catpair__head-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.v2-catpair__head-controls .btn-group { flex: 0 0 auto; }
.v2-catpair__sublabel {
    font-size: 11.5px;
    font-weight: 500;
    color: var(--v2-ink-2);
    margin: 0;
}
.v2-catpair__date { max-width: 130px; }

/* ── Address section ─────────────────────────────────────────────────
   Tri-state filter rows (Primary / No mail / Current), the Complete
   mailing address block, and the City/State/Country/AddressType pickers
   (each a Limit/Exclude toggle + search + a scrollable checkbox list). */
.v2-addr-tri {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 7px 0;
    flex-wrap: wrap;
}
.v2-addr-tri__label {
    font-weight: 600;
    font-size: 13px;
    color: var(--v2-ink);
    min-width: 96px;
}
.v2-addr-complete { padding: 10px 0 2px; }
.v2-addr-details-toggle { margin: 2px 0 4px 26px; font-size: 12px; text-decoration: none; }
.v2-addr-details-toggle:hover { text-decoration: underline; }
.v2-addr-exists {
    margin: 2px 0 6px 24px;
    padding: 6px 0 6px 12px;
    border-left: 2px solid var(--v2-line, #e4e9f0);
}
.v2-addr-exists .form-check { padding-top: 3px; padding-bottom: 3px; }

/* Location pickers flow into columns as width allows (like the Family
   Categories Limit/Exclude pair), collapsing to one column on phones.
   auto-fit + minmax keeps each field readable; align-items:start lets the
   short Zip field sit at its natural height next to the taller lists. */
.v2-addr-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 14px;
    align-items: start;
}
.v2-addr-pick {
    border: 1px solid var(--v2-line, #e4e9f0);
    border-radius: 9px;
    background: #fbfcfe;
    padding: 10px 12px;
}
.v2-addr-pick--ex { border-color: #f0c9c9; background: #fdf7f7; }
.v2-addr-pick__head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    row-gap: 6px;
    margin-bottom: 8px;
}
.v2-addr-pick__title { font-weight: 600; font-size: 13px; color: var(--v2-ink); }
.v2-addr-pick__count { font-size: 11.5px; color: var(--v2-ink-3, #8a95a1); }
/* The picker list scrolls on its own — fixed height, not part of a flex
   chain (unlike the two-column category pair). */
.v2-addr-list {
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid var(--v2-line, #e4e9f0);
    border-radius: 7px;
    background: #fff;
    padding: 2px 0;
}
/* Filters row: the Primary/No-mail/Current controls, with Address type as
   a second column on wide screens. flex-wrap drops Address type below the
   controls when the row is too narrow to hold both. */
.v2-addr-filters-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    align-items: flex-start;
}
.v2-addr-filters-main { flex: 1 1 320px; min-width: 260px; }
.v2-addr-filters-type { flex: 1 1 260px; min-width: 240px; max-width: 340px; }

/* Transactions section — category / subcategory checkbox tree (reuses the
   .v2-addr-list scroll box). Categories are bold; subcategories indent. */
.v2-txcat-list { max-height: 300px; }
.v2-txcat-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 5px 12px;
    margin: 0;
    font-size: 13px;
    cursor: pointer;
}
.v2-txcat-row:hover { background: #f4f8ff; }
.v2-txcat-row input { width: 15px; height: 15px; margin: 0; accent-color: var(--v2-primary); }
.v2-txcat-cat { font-weight: 400; }  /* match the family/member picker rows (chevron + indent already mark the parent) */
.v2-txcat-sub { padding-left: 34px; font-weight: 400; }
/* Wraps ONLY the checkbox + name inside a parent row so a text-click toggles
   the checkbox — never the sibling chevron button. (A <label> targets its
   first labelable descendant; keeping the chevron button outside the label is
   what makes text-click behavior consistent across all category trees.) */
.v2-txcat-rowlabel {
    flex: 1 1 auto; min-width: 0; margin: 0; cursor: pointer;
    display: flex; align-items: center; gap: 9px;
}

/* Columns → Sort: slim inline bar (compact; replaced the full cluster card). */
.v2-cols-sortbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.v2-cols-sortbar .setv2-icn { color: var(--v2-primary); }
.v2-cols-sortbar__label { font-weight: 600; font-size: 13px; color: #4a5666; }
.v2-cols-sortbar__select { max-width: 220px; }
.v2-cols-sortbar__desc { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; cursor: pointer; }

/* Deposits picker (transaction report) — reuses the shared .v2-list-card
   list component inside the standard cluster chrome. Limit/Exclude lives in
   the cluster head (top-right) like every other picker; the account filter +
   pagination sit in a toolbar above the table. */
.v2-dep-account { max-width: 240px; }
.v2-dep-showclosed { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 12px; color: #4a5666; cursor: pointer; white-space: nowrap; }

/* Other section — tab bar (Families / Solicitors / Departments / Receipt). */
.v2-other-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--v2-border, #e0e4ea); margin-bottom: 14px; flex-wrap: wrap; }
.v2-other-tab { background: none; border: 0; border-bottom: 2px solid transparent; padding: 8px 14px; font-size: 13px; font-weight: 600; color: #6b7583; cursor: pointer; margin-bottom: -1px; display: inline-flex; align-items: center; gap: 6px; }
.v2-other-tab:hover { color: #2c3340; }
.v2-other-tab.active { color: var(--v2-primary); border-bottom-color: var(--v2-primary); }
.v2-other-tab__badge { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--v2-primary); color: #fff; font-size: 11px; font-weight: 700; }
/* Bounded scroll for the Other pickers' lists. */
.v2-other-list { max-height: 300px; overflow-y: auto; }
/* Embedded variant: the list-card normally fills the page (height:100vh);
   inside a cluster it must be a bounded, borderless block that scrolls its own
   body. The bound + scroll live on the CARD-BODY (not the card) — the card is
   auto-height, so a flex:1/basis:0 body would collapse to 0px. Sticky thead
   still works via the base .v2-list-card > .card-body thead rule. */
/* Two classes to out-specify `.v2-list-card .v2-list-card` (the nested-list-card
   rule, specificity 0,2,0) — the deposits card sits INSIDE the builder shell's
   own .v2-list-card, so that rule's `flex: 1 1 0; min-height: 0` was collapsing
   it to 0px. flex:none + display:block make it wrap the table naturally; the
   card-body is the scroll box (sticky thead still works). */
.v2-list-card.v2-list-card--embedded {
    display: block;
    flex: none;
    height: auto;
    box-shadow: none !important;
    border: none;
}
.v2-list-card.v2-list-card--embedded > .card-body {
    max-height: 55vh;
    max-height: 55dvh;
    overflow-y: auto;
    padding-bottom: 8px !important;   /* breathing room below the last row (card-body is p-0) */
}

/* Filter input + "Show inactive" toggle share one row above the tree.
   The input flexes; the checkbox hugs the right and wraps below on narrow. */
.v2-txcat-filterrow {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}
.v2-txcat-filterrow > input[type="text"] { flex: 1 1 160px; min-width: 140px; }
.v2-txcat-showinactive {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-size: 12.5px;
    color: var(--v2-text-muted, #6b7280);
    white-space: nowrap;
    cursor: pointer;
}
.v2-txcat-showinactive input { width: 15px; height: 15px; margin: 0; accent-color: var(--v2-primary); }

/* Two columns where the pane allows: Categories (left) and Date / Amount /
   Type / Match stacked (right). flex-wrap responds to the pane width, so it
   drops to a single column on phones / the mobile full-screen overlay. */
.v2-tx-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    align-items: stretch;   /* Categories column matches the right column's height */
}
.v2-tx-cats {
    flex: 1 1 320px;
    min-width: 300px;
    margin-bottom: 0;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
/* Category list fills the stretched cluster so the left column is ~as tall as
   the right (Type / Match / Date range / Amount) instead of a short list with
   dead space. Capped so an org with hundreds of categories can't run the
   column absurdly long. */
.v2-tx-cats .v2-txcat-list {
    flex: 1 1 auto;
    min-height: 220px;
    max-height: 65vh;
}
.v2-tx-side {
    flex: 1 1 320px;
    min-width: 300px;
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.v2-tx-side .setv2-cluster { margin-bottom: 0; }

/* Members section — same two-column shape as Transactions: Relations
   (left, stretches to match) | Age / Demographics / Next-Occurrence
   (right). Collapses to one column on phones / the mobile overlay. */
.v2-mem-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    align-items: stretch;
}
.v2-mem-rels {
    flex: 1 1 320px;
    min-width: 300px;
    margin-bottom: 0;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.v2-mem-rels .v2-mem-rel-list {
    flex: 1 1 auto;
    min-height: 200px;
    max-height: 65vh;
}
.v2-mem-side {
    flex: 1 1 320px;
    min-width: 300px;
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.v2-mem-side .setv2-cluster { margin-bottom: 0; }

/* Gifts section — same two-column shape: gift-type list (left, stretches) |
   Match / Date / Count (right). Collapses to one column on phones. */
.v2-gift-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    align-items: stretch;
}
.v2-gift-list-col {
    flex: 1 1 320px;
    min-width: 300px;
    margin-bottom: 0;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.v2-gift-list-col .v2-gift-list {
    flex: 1 1 auto;
    min-height: 200px;
    max-height: 65vh;
}
.v2-gift-note { color: var(--v2-ink-3); font-size: 12px; }
.v2-gift-side {
    flex: 1 1 320px;
    min-width: 300px;
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.v2-gift-side .setv2-cluster { margin-bottom: 0; }

/* ═══════════════════════════════════════════════════════════════════════
   V2 Report Folders — left rail on the reports list. Outline icons in
   neutral/primary tones (never the yellow jsTree glyphs). The whole rail
   collapses to a slim strip; the report list takes the freed width.
   ═══════════════════════════════════════════════════════════════════════ */
/* The list column's RIGHT edge is pinned where a centered 1080 card would sit,
   and it never moves. The trailing spacer is exactly that centered card's right
   gutter — `calc(50% - 552px)` = (page − 1080)/2 − 12px gap — so with
   `justify-content: flex-end` the group hugs the right and the list sits at the
   centered position while there's room. The folder rail then expands into the
   LEFT gutter; only once the rail fills that gutter does the list shrink
   (flex-shrink), and it shrinks from the LEFT — the right edge is never pushed.
   On wide pages the free space lands left of the rail, so the list reads as
   centered (like NCOA) with the rail floating in the left margin. */
.v2-reports-layout { display: flex; align-items: stretch; gap: 12px; justify-content: flex-end; }
.v2-reports-layout__list { flex: 0 1 1080px; min-width: 0; }
.v2-reports-layout__spacer { flex: 0 0 calc(50% - 552px); align-self: stretch; pointer-events: none; }

/* The folder pane eats ~240px of horizontal room, so the report table can
   overflow its column even on wide-ish viewports — and the shared list-card
   `.card-body { overflow-x: hidden }` would CLIP it (cut off). Two guards so
   the list is NEVER cut off:
   1. When the pane is open, drop the optional (v2-hide-sm) columns earlier —
      at ≤1050px viewport instead of ≤767 — accounting for the pane's width.
   2. Hard fallback: let the reports card body scroll horizontally instead of
      clipping. Scoped to `.v2-reports-page` so it never creates a page-level
      scrollbar (overrides the global overflow-x:hidden for this list only). */
@media (max-width: 1050px) {
    .v2-reports-layout--folders .v2-hide-sm { display: none !important; }
}
.v2-reports-page .v2-list-card > .card-body { overflow-x: auto; }

/* The pane (host controls width via inline style; collapses to the strip) */
.folder-pane {
    flex: 0 0 auto;
    background: #fff;
    border: 1px solid #e6e8ec;
    border-radius: 12px;
    display: flex;
    position: relative;
    overflow: hidden;
}

/* Collapse/expand must be an INSTANT, simultaneous swap of pane ↔ strip.
   ngAnimate is loaded, so any transition/animation on these ng-if elements
   makes it hold the leaving pane in the DOM while the entering strip appears
   beside it (the flash). Opt both out of enter/leave animation. (The pane's
   old `transition: width .15s` was the trigger — removed above too.) */
.folder-pane.ng-enter,  .folder-pane.ng-leave,
.v2-folder-strip.ng-enter, .v2-folder-strip.ng-leave,
.v2-folder-mobilebar.ng-enter, .v2-folder-mobilebar.ng-leave {
    transition: none !important;
    animation: none !important;
}
.folder-pane__resize {
    flex: 0 0 5px; cursor: col-resize; align-self: stretch;
    margin-left: -5px;
}
.folder-pane__resize:hover { background: var(--v2-primary-soft); }

/* Collapsed strip — a slim vertical handle that re-opens the rail */
.v2-folder-strip {
    flex: 0 0 auto;
    width: 34px;
    background: #fff;
    border: 1px solid #e6e8ec;
    border-radius: 12px;
    display: flex; flex-direction: column; align-items: center;
    padding: 8px 0; gap: 8px; cursor: pointer;
    color: var(--v2-ink-3, #6b7280);
}
.v2-folder-strip:hover { background: var(--v2-primary-soft); color: var(--v2-primary); }
.v2-folder-strip svg { width: 18px; height: 18px; }
.v2-folder-strip__label {
    writing-mode: vertical-rl; transform: rotate(180deg);
    font-size: 12px; font-weight: 600; letter-spacing: .02em; margin-top: 2px;
}

.v2-folder-rail { display: flex; flex-direction: column; width: 100%; min-height: 0; }
.v2-folder-rail__head {
    display: flex; align-items: center; gap: 6px;
    padding: 8px 6px 8px 12px; border-bottom: 1px solid #f0f1f4;
}
.v2-folder-rail__title { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--v2-ink-3, #6b7280); flex: 1 1 auto; }
.v2-folder-rail__actions { display: flex; gap: 2px; }
.v2-folder-rail__body { flex: 1 1 auto; overflow-y: auto; padding: 6px 4px 10px; min-height: 0; }

.v2-folder-iconbtn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border: 0; background: transparent; border-radius: 7px;
    color: var(--v2-ink-3, #6b7280); cursor: pointer; padding: 0;
}
.v2-folder-iconbtn svg { width: 17px; height: 17px; }
.v2-folder-iconbtn:hover { background: #eef1f5; color: var(--v2-primary); }
.v2-folder-iconbtn--xs { width: 24px; height: 24px; }
.v2-folder-iconbtn--xs svg { width: 14px; height: 14px; }
.v2-folder-iconbtn--danger:hover { color: #dc3545; background: #fdeaec; }
.v2-folder-iconbtn[disabled] { opacity: .35; cursor: default; }
.v2-folder-iconbtn[disabled]:hover { background: transparent; color: var(--v2-ink-3, #6b7280); }
.v2-folder-kebab { display: none; }   /* desktop uses hover actions; kebab only ≤600 (see media query) */

.v2-folder-row {
    display: flex; align-items: center; gap: 4px;
    padding: 5px 8px 5px 4px; border-radius: 8px; cursor: pointer;
    color: #384250; font-size: 13.5px; min-height: 34px;
    user-select: none;
}
.v2-folder-row:hover { background: #f4f6f9; }
.v2-folder-row.is-selected { background: var(--v2-primary-soft); color: var(--v2-primary); font-weight: 600; }
.v2-folder-row.drag-over { background: var(--v2-primary-soft); box-shadow: inset 0 0 0 2px var(--v2-primary); }
.v2-folder-row.is-dragging { opacity: .45; }
.v2-folder-dndtip {
    position: fixed; z-index: 100000; pointer-events: none; white-space: nowrap;
    padding: 5px 10px; font-size: 12.5px; color: #fff;
    background: rgba(31,41,55,.92); border-radius: 6px;
    box-shadow: 0 4px 14px rgba(0,0,0,.2);
}
/* the report row being dragged (source lives in the list) */
tr.dragging-report { opacity: .4; }
.v2-folder-row--special { color: #4b5563; }

.v2-folder-chev {
    flex: 0 0 18px; width: 18px; height: 18px;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--v2-ink-3, #9aa1ac);
}
.v2-folder-chev.has-children { cursor: pointer; }
.v2-folder-chev svg { width: 13px; height: 13px; transition: transform .12s ease; }
.v2-folder-chev.is-open svg { transform: rotate(90deg); }

.v2-folder-icn { flex: 0 0 20px; display: inline-flex; align-items: center; color: #8a93a0; }
.v2-folder-row.is-selected .v2-folder-icn { color: var(--v2-primary); }
.v2-folder-icn svg { width: 17px; height: 17px; }

.v2-folder-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-folder-count {
    flex: 0 0 auto; font-size: 11px; font-weight: 600; color: var(--v2-ink-3, #6b7280);
    background: #eef1f5; border-radius: 10px; padding: 1px 7px; min-width: 20px; text-align: center;
}
.v2-folder-row.is-selected .v2-folder-count { background: #fff; color: var(--v2-primary); }

.v2-folder-rowactions { flex: 0 0 auto; display: none; gap: 0; margin-left: 2px; }
.v2-folder-row:hover .v2-folder-rowactions { display: inline-flex; }
/* hide the count on hover so actions have room */
.v2-folder-row:hover .v2-folder-count { display: none; }

.v2-folder-node__children { padding-left: 15px; }
.v2-folder-divider { height: 1px; background: #f0f1f4; margin: 6px 6px; }
.v2-folder-hint { font-size: 12px; color: var(--v2-ink-3, #6b7280); padding: 8px 10px; }
.v2-folder-empty a { color: var(--v2-primary); cursor: pointer; }

/* Inline name modal (create / rename) */

/* Move-to-folder picker rows (inside the established BS modal) */
.v2-movepick-body { max-height: 340px; overflow-y: auto; }
.v2-movepick {
    display: flex; align-items: center; gap: 8px;
    padding: 7px 10px; border-radius: 8px; cursor: pointer;
    font-size: 13.5px; color: #384250;
}
.v2-movepick svg { width: 16px; height: 16px; color: #8a93a0; flex: 0 0 auto; }
.v2-movepick:hover { background: #f4f6f9; }
.v2-movepick.is-selected { background: var(--v2-primary-soft); color: var(--v2-primary); font-weight: 600; }
.v2-movepick.is-selected svg { color: var(--v2-primary); }

/* Mobile folder bar (list mode) — hidden on desktop */
.v2-folder-mobilebar { display: none; }

/* Phone: DRILL-DOWN, not a split. You see EITHER the folder tree OR the
   report list — never both sharing vertical space. folderPaneOpen is the
   mode: open = folders full-screen (list hidden); closed = list + a compact
   "selected folder" bar that reopens the tree. Selecting a folder collapses
   back to the list (see onSelectFolder). */
@media (max-width: 600px) {
    .v2-reports-layout { flex-direction: column; gap: 8px; }
    .v2-reports-layout__spacer { display: none; }  /* drill-down mode: no centering mirror */
    .folder-pane__resize { display: none; }   /* no drag-resize on phones */
    .v2-folder-strip { display: none; }        /* desktop strip → replaced by the mobile bar */
    .v2-list-folder-dd { display: none !important; } /* folder lives in the top bar, not the paging row */
    #reportTable tr[data-report-id] { -webkit-user-drag: none; } /* no report drag — nowhere to drop (drill-down) */
    /* Folder actions: hover inline set → kebab (no hover on touch) */
    .v2-folder-rowactions { display: none !important; }
    .v2-folder-kebab { display: inline-flex; }
    /* The kebab dropdown must not be clipped by the rail's scroll container.
       In folders-mode the pane grows with content (min-height:75vh, no fixed
       height), so the inner scroll isn't needed here — let the menu escape. */
    .v2-reports-layout--folders .folder-pane,
    .v2-reports-layout--folders .v2-folder-rail__body { overflow: visible !important; }

    /* FOLDERS MODE — the tree owns the screen; the list is hidden. */
    .v2-reports-layout--folders .v2-reports-layout__list { display: none; }
    .v2-reports-layout--folders .folder-pane {
        width: 100% !important; max-height: none;
        min-height: 75vh; min-height: 75dvh;
    }

    /* LIST MODE — compact bar shows the selected folder; tap to browse. */
    .v2-folder-mobilebar {
        display: flex; align-items: center; gap: 8px;
        padding: 9px 12px; margin-bottom: 8px;
        background: #fff; border: 1px solid #e6e8ec; border-radius: 10px;
        cursor: pointer; color: #384250; font-weight: 600; font-size: 14px;
    }
    .v2-folder-mobilebar:active { background: var(--v2-primary-soft); }
    .v2-fmb-icn { width: 18px; height: 18px; color: var(--v2-primary); flex: 0 0 auto; }
    .v2-fmb-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .v2-fmb-hint { font-size: 12px; font-weight: 500; color: var(--v2-ink-3, #6b7280); }
    .v2-fmb-chev { width: 15px; height: 15px; color: var(--v2-ink-3, #9aa1ac); flex: 0 0 auto; }
}

/* Phones: keep the reports toolbar on ONE row (search + pagination) — vertical
   space is precious. The search drops its 250px cap and flexes to fill; the
   spacer is removed so pagination sits right beside it instead of wrapping. */
@media (max-width: 600px) {
    /* nowrap must be !important — the toolbar carries Bootstrap's `.flex-wrap`
       utility (flex-wrap: wrap !important), which otherwise wins and drops the
       pagination onto a second row. */
    .v2-reports-page .v2-list-toolbar { flex-wrap: nowrap !important; }
    .v2-reports-page .v2-list-toolbar > .input-group { max-width: none !important; flex: 1 1 auto; min-width: 90px; }
    .v2-reports-page .v2-list-toolbar > .flex-grow-1 { display: none; }
}

/* Ultra-narrow phones (<400px): one row gets cramped, so drop the page-size
   selector (the `25 / page` dropdown) — the count + chevrons are what matter.
   Page size keeps its current value; only the picker is hidden. Scoped to the
   pagination ng-include's page-size dropdown so nothing else is affected. */
@media (max-width: 399px) {
    .v2-reports-page .v2-list-toolbar ng-include > .dropdown { display: none !important; }
}

/* Explainer sits between the header and the filter input, spanning
   the cluster's full width. */
.v2-catpair__explainer {
    font-size: 11.5px;
    color: var(--v2-ink-3);
    padding: 4px 0 8px;
    line-height: 1.4;
}

/* Family/Member Categories "Match options" cluster — Match / Enrolled /
   Current-only / Exclude all on one row at desktop, stack on mobile. */
.v2-match-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 20px;
    padding: 8px 0;
}
.v2-match-cell {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.v2-match-cell.v2-match-check { padding-bottom: 6px; }
.v2-match-cell.v2-match-check .form-check { margin: 0; }
@media (max-width: 576px) {
    .v2-match-row { flex-direction: column; align-items: stretch; gap: 12px; }
    .v2-match-cell.v2-match-check { padding-bottom: 0; }
}

/* Preview meta banner — amber summary above the results list so the
   "N matches" and Update Category action don't read as another row of
   table data. The banner sits ABOVE the scrolling .v2-list-card so it
   stays visible without needing custom sticky behavior — the shared
   list-card CSS does the header-fixed / body-scrolls plumbing. */
.v2-preview-banner {
    background: #fff7de;
    border: 1px solid #e6c34a;
    color: #6d4b00;
    border-radius: 8px;
    padding: 8px 12px;
    font-size: 13px;
    line-height: 1.4;
    flex: 0 0 auto;
}
.v2-preview-banner svg { color: #b98700; flex: 0 0 auto; }

/* Criteria summary — compact, one-line recap in the builder toolbar (top-right,
   right of Folder). Yellow chrome matches the Preview banner; truncates with an
   ellipsis and shows the full text on hover (title attr). */
/* Summary takes ~half the header row. */
/* Basis 0 + grow: the summary fills whatever row-1 space is left after the
   Report Name / Type fields (≈ half a typical header) and RIGHT to the edge —
   never forces a wrap onto a second row the way a fixed 50% basis did. No
   ms-auto (an auto margin would eat the free space instead of the box growing). */
.v2-criteria-toolbar { flex: 1 1 0; min-width: 240px; }
/* The box is absolutely positioned inside the wrapper (which reserves its
   collapsed height via the v2-criteria-clamp directive) so that on hover the
   SAME box can grow downward and overlay the content below — no reflow, and no
   separate popup bubble. */
.v2-criteria-sum-wrap { position: relative; }
.v2-criteria-sum-box {
    position: absolute; top: 0; left: 0; right: 0;
    background: #fff7de; border: 1px solid #e6c34a; border-radius: 7px;
    min-height: 31px; padding: 5px 10px; overflow: hidden;
}
/* Grows to a 2nd row if needed, then ellipsis (line-clamp). */
.v2-criteria-sum-txt {
    color: #6d4b00; font-size: 12px; line-height: 1.35;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    overflow: hidden; text-overflow: ellipsis;
}
.v2-criteria-seg-k { color: #9a7a2e; }
.v2-criteria-sep { color: #e6c34a; margin: 0 6px; }
.v2-criteria-note { font-style: italic; }

/* Hover-expand — ONLY when the text actually overflows the 2-line clamp
   (the directive adds `.is-clampable`). The same box un-clamps and grows
   downward with a drop shadow; if the summary already fits, hover does
   nothing (no bubble at all). */
.v2-criteria-sum-wrap.is-clampable:hover .v2-criteria-sum-box {
    z-index: 1080; max-height: 60vh; overflow-y: auto;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
}
.v2-criteria-sum-wrap.is-clampable:hover .v2-criteria-sum-txt {
    -webkit-line-clamp: unset; display: block;
}
/* Expanded: one section per line (drop the inline · separators). Collapsed
   stays inline so the 2-line clamp still packs segments together. */
.v2-criteria-sum-wrap.is-clampable:hover .v2-criteria-seg { display: block; padding: 1px 0; }
.v2-criteria-sum-wrap.is-clampable:hover .v2-criteria-sep { display: none; }

/* Folder-as-icon prepended to the Report Name field (replaces the Folder field). */
.v2-builder-folder-icon { display: inline-flex; align-items: center; padding: 0 9px; color: #6b7583; }
.v2-builder-folder-icon:hover { color: #2c3340; }
.v2-builder-folder-icon--set { color: var(--v2-primary); }         /* filled/colored when the report is in a folder */
.v2-builder-name-group .form-control { border-left: 0; border-right: 0; } /* join the icons to the name input */

/* Use-for-filter as a pressable icon at the RIGHT of the name (mirrors the
   folder icon). "On" = pressed-in look: filled primary background, white icon. */
.v2-builder-filter-icon { display: inline-flex; align-items: center; padding: 0 9px; color: #6b7583; }
.v2-builder-filter-icon:hover { color: #2c3340; }
.v2-builder-filter-icon--on,
.v2-builder-filter-icon--on:hover {
    background: var(--v2-primary); border-color: var(--v2-primary); color: #fff;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25);              /* pressed-in */
}

/* Inline "report name required" message under the Report Name field. */
.v2-builder-name-error {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 3px;
    color: #dc3545;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.2;
}
.v2-builder-name-error svg { flex: 0 0 auto; }

/* The Departments tab abbreviates to "Depts" on phones (keeps the row on one line). */
.v2-tablabel-abbr { display: none; }

/* Cluster-header suffix (e.g. "Limit to[ Family Categories]") — shown on phones
   only, where the section's own title header is hidden to save space. */
.v2-hdr-suffix { display: none; }

@media (max-width: 600px) {
    /* Reports list + builder embedded lists: drop the "X–Y of Z" range text on
       phones, keeping only the ‹ page/total › nav. */
    .v2-reports-page .v2-page-info { display: none; }
    /* Deposits: the account dropdown gets its own full-width row so the selected
       account name shows (it was squished to just the caret on the cramped row).
       min-width:0 lets it clip a long name to the row width instead of growing. */
    /* The reports-LIST toolbar is forced nowrap (search + pagination on one row);
       that rule also matches the builder's toolbars (the builder is inside
       .v2-reports-page), so re-enable wrapping for builder toolbars. */
    .v2-builder-rail-body .v2-list-toolbar { flex-wrap: wrap !important; }
    .v2-builder-rail-body .v2-dep-account { flex: 0 0 100% !important; width: auto; max-width: none; min-width: 0; }
    /* Pagination keeps its natural width — the search input shrinks instead — so
       the "‹ page/total ›" indicator isn't squished onto two lines. */
    .v2-reports-page .v2-list-toolbar > ng-include { flex: 0 0 auto; }
    /* Other-tab strip stays on one line — compact, with "Departments" → "Depts". */
    .v2-other-tabs { flex-wrap: nowrap; gap: 2px; }
    .v2-other-tab { padding: 8px 9px; font-size: 12.5px; }
    .v2-tablabel-full { display: none; }
    .v2-tablabel-abbr { display: inline; }
    /* Report Type + Member Type share one row (each grows equally) instead of the
       fixed 160/170px widths that don't both fit on a phone. */
    .v2-builder-typefield { flex: 1 1 0 !important; min-width: 0; }
    /* Family/Member Categories: hide the section title header on phones (the
       cluster headers gain a " …Categories" suffix to keep the context). */
    .v2-detail-title { display: none; }
    .v2-hdr-suffix { display: inline; }
}

/* Method tree — collapsible Method Type row with expand chevron. Reuses the
   .v2-txcat list/row styling (shared with the Categories tree). */
.v2-method-typerow { display: flex; align-items: center; gap: 8px; }
.v2-tree-toggle {
    background: none; border: 0; padding: 0; margin: 0;
    width: 18px; height: 18px; flex: 0 0 auto; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center; color: #6b7583;
}
.v2-tree-toggle:hover { color: #2c3340; }
.v2-tree-toggle svg { transition: transform .12s ease; }
.v2-tree-toggle svg.v2-tree-open { transform: rotate(90deg); }
/* Phone: the toolbar wraps; let the summary take its own full-width row. */
@media (max-width: 768px) {
    .v2-criteria-toolbar { flex: 1 1 100%; max-width: none; margin-left: 0 !important; }
}

/* Content-sized columns: every column collapses to its widest cell
   except the last, which absorbs remaining table width. Prevents a
   2-column preview from spreading each column across half the card. */
.v2-preview-list th:not(:last-child),
.v2-preview-list td:not(:last-child) { width: 1px; white-space: nowrap; }

/* Preview horizontal scroll: put it on the height-constrained card-body so the
   x-scrollbar is at the bottom of the VIEWPORT (reachable), not the bottom of
   the full-height table. Wrapper doesn't scroll; sticky thead still pins on top
   and scrolls sideways with the columns. */
.v2-list-card > .v2-tab-include > .card-body.v2-preview-body { overflow-x: auto; }
.v2-preview-body > .table-responsive { overflow: visible; }

/* Output → Columns section — 3 vertical lists at desktop, stacking on
   narrow screens. Each group renders as a proper vertical list of
   checkboxes (no horizontal grid within a group). */
.v2-cols-groups {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}
@media (max-width: 900px) { .v2-cols-groups { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .v2-cols-groups { grid-template-columns: 1fr; } }
.v2-cols-group { margin: 0; }
.v2-cols-list {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column; gap: 2px;
}
.v2-cols-item {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 0; cursor: pointer; margin: 0;
    font-size: 13.5px; line-height: 1.35;
}
.v2-cols-item input[type="checkbox"] { flex: 0 0 auto; }
.v2-cols-item--locked { cursor: not-allowed; opacity: .8; }
.v2-cols-item--locked span { color: var(--v2-ink-2); font-style: italic; }
/* Header-only parent row (Deposit, Solicitor): a sub-header, not a checkbox. */
.v2-cols-item--header { cursor: default; font-weight: 600; color: var(--v2-ink-2, #4b5563); padding-top: 8px; }

/* Rail + edit pane — two-column body inside the Parameters tab. */
.v2-builder-rail {
    display: grid;
    grid-template-columns: 220px 1fr;
    min-height: 420px;
    /* Fill the parent .card-body so nav and body can be independent
       scroll containers. min-height:0 lets grid children shrink below
       their content height (required for overflow scroll to work). */
    height: 100%;
    min-height: 0;
}

/* Back-to-sections link — hidden on desktop. Shown only under the
   mobile drill-down class rules below. */
.v2-mobile-back-bar { display: none; }

/* Checkbox/picker rows follow the display DENSITY setting (like the table rows
   already do) — compact tightens them, spacious loosens them. Covers the tx /
   member / gift checkbox lists (.v2-txcat-row), the Columns list (.v2-cols-item),
   and the shared category/address tree (.v2-cat-tree-row). Font size follows the
   `size` setting via the global [data-v2-size] rules. */
[data-v2-density="compact"]  .v2-txcat-row,
[data-v2-density="compact"]  .v2-cat-tree-row { padding-top: 3px; padding-bottom: 3px; }
[data-v2-density="compact"]  .v2-cols-item     { padding-top: 2px; padding-bottom: 2px; }
[data-v2-density="spacious"] .v2-txcat-row,
[data-v2-density="spacious"] .v2-cat-tree-row,
[data-v2-density="spacious"] .v2-cols-item     { padding-top: 8px; padding-bottom: 8px; }

/* Touch targets on phones: keep rows tappable, but NOT taller than a desktop row
   feels (the phone shouldn't read as MORE spacious than the desktop). A modest
   min-height, tightened further under compact density. */
@media (max-width: 600px) {
    .v2-txcat-row,
    .v2-cols-item,
    .v2-cat-tree-row { min-height: 34px; }
    [data-v2-density="compact"] .v2-txcat-row,
    [data-v2-density="compact"] .v2-cols-item,
    [data-v2-density="compact"] .v2-cat-tree-row { min-height: 28px; }
}

/* The view-toggle icon shows on phones only (desktop uses the text label). */
.v2-hdr-toggle-icon { display: none; }

/* Cancel + Save as two equal-width columns — so they're the SAME size, and the
   Save width counts its dropdown caret. Applies on every viewport. */
.v2-builder-actions {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 8px;
}
.v2-builder-actions > .btn,
.v2-builder-actions > .v2-builder-save-split { width: 100%; }
.v2-builder-save-split { display: flex; }
.v2-builder-save-split .v2-save-primary { flex: 1 1 auto; min-width: 0; }
.v2-builder-save-split .v2-save-split-caret { flex: 0 0 auto; }

/* Phone header: drop the breadcrumb (back-link + report title) — Cancel runs the
   same cancel()/"back to list" action — and squeeze all four controls
   (Parameters | Preview | Cancel | Save) onto ONE row: the toggle goes icon-only,
   and Cancel/Save shrink to their text. */
@media (max-width: 600px) {
    .v2-builder-crumbs { display: none !important; }
    .v2-builder-viewtoggle { margin-left: 0 !important; }
    .v2-builder-card .v2-gradient-header { flex-wrap: nowrap; }
    .v2-builder-card .v2-gradient-header .btn { min-width: 0 !important; }
    .v2-builder-viewtoggle .btn { padding-left: 11px; padding-right: 11px; }
    .v2-hdr-toggle-label { display: none; }
    .v2-hdr-toggle-icon { display: inline-flex; align-items: center; }
    /* Phone Save button always shows the short "Save" label — never "Save & Close",
       which is too long for the one-row header (it wraps and blows up the button
       width). The Save-&-Close choice lives in the caret dropdown; tapping Save
       still honors the remembered default. */
    .v2-save-primary .v2-save-label-wrap { display: inline-block; }
    .v2-save-primary .v2-save-label { white-space: nowrap; }
    .v2-save-primary .v2-save-label--full { display: none !important; }
    .v2-save-primary .v2-save-label--short { display: inline !important; visibility: visible !important; }
}

/* Mobile drill-down: at phone widths (≤ 600px) the rail nav and the
   section pane can't fit side-by-side, so switch to a two-mode layout:
     • no `--drill` class → rail nav visible, section pane hidden
     • `.v2-builder-rail--drill` → rail nav hidden, section pane full-width
   Toggled by the controller when a rail item is tapped / Back is tapped. */
@media (max-width: 600px) {
    .v2-builder-rail {
        display: block;
        min-height: 0;
    }
    .v2-builder-rail-nav {
        display: block;
        width: 100%;
        border-right: 0;
        border-bottom: 1px solid #e8ecf2;
    }
    .v2-builder-rail-body { display: none; }
    .v2-builder-rail--drill .v2-builder-rail-nav  { display: none; }
    /* Drilled-in section takes over the ENTIRE screen — no gradient header,
       no Report Name/Scope/Folder toolbar, no tabs. position:fixed escapes
       the builder card's overflow:hidden and covers the whole viewport, so
       the section (category lists, any criteria form) is the only thing on
       screen. "Back to sections" is the way out. Same for every section. */
    .v2-builder-rail--drill .v2-builder-rail-body {
        display: flex;
        flex-direction: column;
        width: 100%;
        position: fixed;
        inset: 0;
        z-index: 1080;
        background: #fff;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        padding: 0;
        min-height: 0;
    }
    /* Section content supplies its own padding now that the overlay itself
       is flush to the edges. */
    .v2-builder-rail--drill .v2-builder-rail-body .setv2-detail {
        padding: 10px 16px 14px;
    }
    /* Full-width bar (background/border/sticky) — but NOT clickable itself. Back
       link on the left, Clear on the right. */
    .v2-builder-rail--drill .v2-mobile-back-bar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        border-bottom: 1px solid #e8ecf2;
        background: #f7fafd;
        /* Stick to the top of the overlay so Back is always reachable
           while the section scrolls. */
        position: sticky;
        top: 0;
        z-index: 1;
    }
    /* Only the link / Clear button are clickable (content-width tap areas). */
    .v2-builder-rail--drill .v2-mobile-back {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        padding: 12px 16px;
        font-size: 13px;
        font-weight: 600;
        color: var(--v2-primary);
        text-decoration: none;
    }
    .v2-builder-rail--drill .v2-mobile-clear {
        display: inline-flex;
        align-items: center;
        padding: 12px 16px;
        background: none;
        border: 0;
        font-size: 13px;
        font-weight: 600;
        color: var(--v2-primary);
        text-decoration: underline;
        cursor: pointer;
    }
    /* Per-section Clear moves into the bar on mobile — hide it in the section
       head, and collapse the now-empty head of the header-stripped sections.
       !important beats Bootstrap's .d-flex { display:flex !important }. */
    .setv2-detail__head .btn-link { display: none !important; }
    .setv2-detail__head.justify-content-end { display: none !important; }
    /* No double scrollbars: the drilled-in section is a full-screen overlay that
       already scrolls, so release every inner list/tree/embedded-card scroll
       container — the whole section scrolls as one. (.v2-catpair__list is the
       family/member category picker; the others are the checkbox lists, the
       Other-tab list, and the embedded Deposits card.) */
    .v2-builder-rail-body .v2-addr-list,
    .v2-builder-rail-body .v2-txcat-list,
    .v2-builder-rail-body .v2-other-list,
    .v2-builder-rail-body .v2-catpair__list,
    .v2-builder-rail-body .v2-list-card--embedded > .card-body {
        max-height: none;
        overflow-y: visible;
    }
}

.v2-builder-rail-nav {
    background: #fafbfc;
    border-right: 1px solid #e8ecf2;
    padding: 8px 0;
    /* Rail nav gets its own scroll — but only if the rail item list
       grows past the pane height. Normal cases stay put. */
    overflow-y: auto;
    min-height: 0;
}
.v2-builder-rail-group-label {
    font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
    color: #6b7787;
    padding: 8px 14px 4px;
}
.v2-builder-rail-item {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 8px 14px;
    font-size: 13.4px;
    color: #2c3a4b;
    cursor: pointer;
    border-left: 3px solid transparent;
    user-select: none;
}
.v2-builder-rail-item:hover { background: #eef2f7; }
.v2-builder-rail-item.active {
    background: #e7f1fb;
    color: var(--v2-primary);
    border-left-color: var(--v2-primary);
    font-weight: 600;
}
.v2-rail-divider {
    margin: 8px 14px;
    border: 0; border-top: 1px solid #e2e7ee;
    opacity: 1;
}

/* Section count pill on rail items. */
.v2-criterion-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px;
    background: var(--v2-primary); color: #fff;
    border-radius: 9px;
    font-size: 11px; font-weight: 600;
    line-height: 1;
}
.v2-criterion-count.is-zero { background: #c4cdd9; color: #fff; }

/* Edit pane — right side of the rail. Scrolls independently of the
   rail nav so long section forms don't drag the nav out of view. */
.v2-builder-rail-body {
    padding: 18px 22px;
    background: #fff;
    min-height: 420px;
    overflow-y: auto;
    /* min-height:0 as a companion so this grid child can shrink and
       scroll instead of expanding the outer .card-body. The 420px
       above stays as a soft floor for small screens where the outer
       card is already taller than 420px. */
}
@media (min-height: 500px) { .v2-builder-rail-body { min-height: 0; } }

/* ── AI Assistant chat (✨ Assistant rail pane, <ai-report-chat>) ──────────
   The pane fills the rail body so the chat can cap at the pane height: the
   box grows downward with the conversation and, once it would exceed the
   pane (≈ the viewport), it stops growing and the LOG scrolls internally
   while the input stays pinned just below it. Font follows the size setting. */
.v2-ai-assistant-pane { height: 100%; min-height: 0; }
/* Fill the pane so the footer (Powered by / balance) pins to the bottom, like the popup. */
.v2-ai-assistant-pane .ai-chat { height: 100%; }

/* AI Assistant popup — opened from the "✨ Assistant" button in the Transaction Reports
   list header. Real Bootstrap modal hosting the shared <ai-report-chat>. Give the chat a
   bounded height so its OWN log scrolls (input bar stays pinned); on phones the modal is
   fullscreen (modal-fullscreen-sm-down) and the chat fills it. dvh + vh per mobile rules. */
.v2-ai-modal-body { padding: 12px; }
.v2-ai-modal-body .ai-chat { height: 62vh; height: 62dvh; }
@media (max-width: 575.98px) {
    #v2AiAssistantModal .modal-content { height: 100%; }
    #v2AiAssistantModal .modal-body { display: flex; flex: 1 1 auto; min-height: 0; padding: 10px; }
    .v2-ai-modal-body .ai-chat { height: auto; max-height: none; flex: 1 1 auto; min-height: 0; }
}

.ai-chat {
    display: flex;
    flex-direction: column;
    max-height: 100%;              /* grow with content, then cap + scroll the log */
    min-height: 0;
    border: 1px solid #d5dbdb;
    border-radius: 6px;
    overflow: hidden;
    background: #fff;
    font-size: 13px;               /* match the app's default content text (md) */
}
[data-v2-size="sm"] .ai-chat { font-size: 12px; }
[data-v2-size="lg"] .ai-chat { font-size: 15px; }   /* same sizes as the app's lists/tables */

/* "Assistant is thinking" hint line — the sparkle spins while a turn is pending
   (same line/place as the old text; not the modal-header sparkle). */
.ai-chat-thinking { display: flex; align-items: center; gap: 0.4em; }
.ai-chat-spark { display: inline-block; animation: ai-chat-spark-spin 1.1s linear infinite; }
@keyframes ai-chat-spark-spin { to { transform: rotate(360deg); } }

.ai-chat-log {
    flex: 0 1 auto;                /* hug content; shrink + scroll once the box caps */
    min-height: 0;
    overflow-y: auto;
    padding: 0.85em;
    background: #fbfcfc;
}
.ai-chat-row { display: flex; flex-wrap: wrap; margin-bottom: 0.5em; }
.ai-chat-row--user { justify-content: flex-end; }
.ai-chat-row--bot  { justify-content: flex-start; }
/* Per-response usage chip (tokens, + cost on the shared key) — drops below the bot bubble. */
.ai-chat-usage { flex-basis: 100%; margin: 0.15em 0 0 0.15em; font-size: 0.78em; color: #b0b8bf; line-height: 1.2; }
.ai-chat-bubble {
    display: inline-block;
    max-width: 85%;
    padding: 0.5em 0.83em;
    border-radius: 12px;
    line-height: 1.4;
    word-break: break-word;
    white-space: pre-wrap;   /* render the model's paragraph breaks + '- ' bullet lines */
}
.ai-chat-bubble--user { background: #2980b9; color: #fff; }
.ai-chat-bubble--bot  { background: #eaeded; color: #2c3e50; }
.ai-chat-hint { color: #7f8c8d; }

/* Starter prompt chips — empty-state discoverability. */
.ai-chat-starters {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4em;
    margin-top: 0.7em;
}
.ai-chat-starter {
    border: 1px solid #d5dbdb;
    border-radius: 14px;
    background: #fff;
    color: #2980b9;
    padding: 0.3em 0.75em;
    font-size: 0.92em;
    line-height: 1.3;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.ai-chat-starter:hover { background: #f4f9fd; border-color: #2980b9; }
/* ↻ cycle to a fresh set of starter ideas (no repeat of the current four). */
.ai-chat-starter-refresh {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid #d5dbdb;
    border-radius: 50%;
    background: #fff;
    color: #9ca3af;
    cursor: pointer;
}
.ai-chat-starter-refresh svg { transition: transform 0.3s ease; }
.ai-chat-starter-refresh:hover { color: #2980b9; border-color: #2980b9; }
.ai-chat-starter-refresh:hover svg { transform: rotate(-90deg); }
.ai-chat-starter-refresh:active svg { transform: rotate(-180deg); }

/* Footer — dynamic "Powered by X" attribution + the admin AI Settings link. */
.ai-chat-footer {
    flex: 0 0 auto;
    margin-top: auto;             /* only the footer (Powered by / balance) drops to the bottom;
                                     the log + input row stay up with the conversation */
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5em;
    padding: 0.35em 0.83em;
    border-top: 1px solid #f0f3f7;
    font-size: 0.85em;
    color: #9ca3af;
}
.ai-chat-settings-link { color: #9ca3af; text-decoration: none; }
.ai-chat-settings-link:hover { color: #2980b9; text-decoration: underline; }
/* Prepaid balance in the footer (shared key only; hidden for BYO). Red once overdrawn. */
.ai-chat-balance { color: #7f8c8d; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ai-chat-balance--low { color: #c0392b; font-weight: 600; }

.ai-chat-options {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5em;
    padding: 0.6em 0.83em;
    border-top: 1px solid #eee;
}
.ai-chat-proposal {
    flex: 0 0 auto;
    padding: 0.83em;
    border-top: 1px solid #eee;
    background: #f4f9fd;
}
.ai-chat-proposal-title { color: #2c3e50; font-weight: 600; }
.ai-chat-assumptions { margin: 0.5em 0 0.7em 1.4em; color: #7f8c8d; font-size: 0.92em; }
.ai-chat-hintline { margin-left: 0.7em; color: #7f8c8d; font-size: 0.92em; }
.ai-chat-error { flex: 0 0 auto; padding: 0.6em 0.83em; color: #c0392b; }

/* Overdraft notice — soft red banner above the input once the balance goes negative
   (and while blocked at the floor). "Add funds" links to the shared Account balance page. */
.ai-chat-overdraft {
    flex: 0 0 auto;
    padding: 0.5em 0.83em;
    background: #fdecea;
    border-top: 1px solid #f5c6c2;
    color: #c0392b;
    font-size: 0.9em;
}
.ai-chat-overdraft-link { color: #c0392b; font-weight: 600; text-decoration: underline; }
.ai-chat-overdraft-link:hover { color: #a93226; }

.ai-chat-inputbar {
    flex: 0 0 auto;
    display: flex;
    gap: 0.5em;
    padding: 0.6em 0.83em;
    border-top: 1px solid #eee;
}
.ai-chat-input.form-control { flex: 1 1 auto; min-width: 0; font-size: 1em; }
.ai-chat-mic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 38px;
    border: 1px solid #ccc;
    border-radius: 6px;
    background: #fff;
    color: #34495e;
}
.ai-chat-mic--on { background: #c0392b; border-color: #c0392b; color: #fff; }
.ai-chat-send { min-height: 38px; }

/* Section template internals (Columns, Pivot, criteria-stub). */
.v2-builder-section-head { margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #f0f3f7; }
.v2-builder-section-head h5 { font-weight: 600; color: #2c3a4b; }
.v2-builder-section-subhead {
    font-size: 11.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
    color: #6b7787; margin: 14px 0 6px;
}
.v2-builder-section-body { font-size: 13.5px; color: #2c3a4b; }

/* Grouping & Totals — View toggle (Show details / Totals only). */
.v2-grouping-view { display: inline-flex; }
.v2-grouping-view .btn { min-width: 108px; }
@media (max-width: 600px) {
    /* Touch targets — CLAUDE.md requires ≥40px tall on phones. */
    .v2-grouping-view { display: flex; width: 100%; }
    .v2-grouping-view .btn { flex: 1 1 0; min-height: 40px; }
}

/* Column-row inside the Columns section. */
.v2-builder-col-row {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 6px; border-radius: 3px;
    font-size: 13.2px; color: #2c3a4b;
    cursor: pointer;
}
.v2-builder-col-row:hover { background: #f0f6fc; }

/* Criteria-stub summary card. */
.v2-builder-summary {
    background: #fafbfc;
    border: 1px solid #e8ecf2;
    border-radius: 4px;
    padding: 10px 12px;
}
.v2-builder-summary-head {
    font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
    color: #6b7787; margin-bottom: 4px;
}
.v2-builder-summary-body { color: #2c3a4b; }

/* ═══════════════════════════════════════════════════════════════════════
   V2 Member Detail tab bodies — Categories · Relationships · Yahrtzeits ·
   Notes. Every visual is a shared primitive (list toolbar/table, chips,
   avatars); only these two layout-glue classes are local.
   ═══════════════════════════════════════════════════════════════════════ */

/* Each tab pane fills the width; wide tables scroll inside their own wrap so
   the page body never scrolls sideways. */
.v2-mdetail-pane { display: block; }
.v2-mdetail-list-wrap { overflow-x: auto; }

/* Deceased badge shown next to the member name (header + General tab). */
.v2-mdetail-deceased-chip { background: #6b7280; color: #fff; }

/* ═══════════════════════════════════════════════════════════════════════
   V2 Member Detail PAGE (routed) reuses the shared Family-detail primitives
   wholesale — header (.v2-gh-row / .v2-family-name / .gh-chip info box /
   .gh-pop popover / v2HeaderFit), General tab (.v2-detail-section /
   .v2-tile-contact / _primary-tile-body), tab strip and list rows. Nothing
   member-specific is needed here. */

/* ═══════════════════════════════════════════════════════════════════════
   v2-copy-btn — tiny inline copy-to-clipboard affordance (shared directive).
   Sits just after a value (email, phone, id). Unobtrusive: muted by default,
   accent on hover, success-green for a brief "copied" flash. currentColor
   outline icons match the V2 list icon language. Drop in anywhere.
   ═══════════════════════════════════════════════════════════════════════ */
.v2-copy-btn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0; margin-left: 5px;
    border: 0; background: transparent; border-radius: 3px;
    color: var(--v2-muted-color, #6b7280); cursor: pointer;
    vertical-align: middle; line-height: 0; flex-shrink: 0;
    opacity: .7; transition: opacity .12s ease, color .12s ease, background .12s ease;
}
.v2-copy-btn:hover  { opacity: 1; color: var(--v2-primary); background: rgba(0, 0, 0, .05); }
.v2-copy-btn:focus-visible { outline: 2px solid var(--v2-primary); outline-offset: 1px; opacity: 1; }
/* icon tracks the surrounding text size so the button is the same height as
   the email characters it sits next to (no oversized box). */
.v2-copy-btn svg { width: 1em; height: 1em; display: block; }
.v2-copy-btn .v2-copy-icn-done { display: none; }
.v2-copy-btn.is-copied { opacity: 1; color: var(--v2-good, #1aa37a); }
.v2-copy-btn.is-copied .v2-copy-icn-copy { display: none; }
.v2-copy-btn.is-copied .v2-copy-icn-done { display: inline; }

/* ── V2 Notify modal ──────────────────────────────────────────────────────
   Recipient list for a saved payment transaction (#v2NotifyModal). Desktop
   renders a 5-column table; below md the same rows stack as cards, because
   Family + Letter + Note + Sent + actions cannot survive a 375px viewport.
   Column widths are fixed so the Note field gets the leftover space. */
.v2-notify-table {
    table-layout: fixed;
    width: 100%;
}
.v2-notify-table th {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .3px;
    color: #7b8794;
    border-bottom: 1px solid #e8ecf2;
    padding: 6px 8px;
}
.v2-notify-table td {
    padding: 8px;
    vertical-align: top;
    border-bottom: 1px solid #f2f4f7;
}
.v2-notify-table .v2-notify-col-family  { width: 34%; }
.v2-notify-table .v2-notify-col-letter  { width: 24%; }
.v2-notify-table .v2-notify-col-note    { width: auto; }
.v2-notify-table .v2-notify-col-sent    { width: 56px; }
.v2-notify-table .v2-notify-col-actions { width: 96px; white-space: nowrap; }

/* Him / Her / Both under the family typeahead — same control language as
   the payment modal header, just tighter to fit inside a table cell. */
.v2-notify-himher .btn {
    font-size: 11px;
    padding: 1px 8px;
}

/* Mobile: one card per recipient. */
.v2-notify-card {
    background: #fff;
    border: 1px solid #e8ecf2;
    border-radius: 8px;
    padding: 12px;
    margin-bottom: 10px;
}
.v2-notify-card-line { padding: 6px 0; }
.v2-notify-card-line .form-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .3px;
    color: #7b8794;
    margin-bottom: 4px;
}
.v2-notify-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #f2f4f7;
}
.v2-notify-card-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Family search uses the shared .v2-typeahead-* pattern. The results panel
   is absolutely positioned inside the scrollable modal body, so a one-row
   dialog would clip it — reserve enough body height for the panel to open
   fully without the user having to scroll. */
.v2-notify-modal .modal-body { min-height: 320px; }
.v2-notify-modal .v2-typeahead-results { z-index: 1085; }
.v2-notify-noresult {
    padding: 6px 10px;
    font-size: 13px;
}

/* Touch targets — WCAG 2.5.5. Bootstrap's form-check-input is 16px and
   form-control-sm is ~31px tall; both are too small for a thumb. */
@media (max-width: 767.98px) {
    .v2-notify-card .form-control,
    .v2-notify-card .form-select {
        min-height: 44px;
        font-size: 15px;
    }
    .v2-notify-himher .btn {
        min-height: 40px;
        font-size: 13px;
        padding: 6px 14px;
    }
    .v2-notify-card-actions .btn {
        min-width: 44px;
        min-height: 44px;
    }
    .v2-notify-sent-check {
        width: 24px;
        height: 24px;
        margin-right: 6px;
    }
}
