/*
    Incandera Revolution
    Version: R5
    View overrides/fixes CSS
*/

/* ============================================================
   Visual language tokens and global tweaks.
   Shifts the original reference admin template toward a calmer,
   more current look: hairline borders instead of card shadows,
   warmer neutral border tone, desaturated brand hue, low-alpha
   soft badges, heavier headings, slightly larger body text.
   ============================================================ */

:root {
    /* Warm, slightly desaturated neutral border tone (replaces #eff2f7) */
    --bs-border-color: #e9eaec;
    --bs-gray-200: #e9eaec;

    /* Desaturated brand — keeps indigo identity, drops template-y saturation */
    --bs-primary: #5365cf;
    --bs-primary-rgb: 83, 101, 207;
    --bs-link-color: #5365cf;
    --bs-link-color-rgb: 83, 101, 207;
    --bs-link-hover-color: #424ea6;
    --bs-blue: #5365cf;

    /* Page bg: keep a meaningful delta from white card bg so hairline-bordered
       cards pop without shadows. */
    --bs-body-bg: #f6f7f9;
    --bs-body-bg-rgb: 246, 247, 249;

    /* Callout/emphasis border for sidebar "toolbox" cards and equivalent
       framed regions. Named for role, not color, so the hue can be retuned
       without renaming the token. */
    --accent-callout: #f1d5db;

    /* UI sans stack — Geist Sans is loaded by the layout files. Inter is the
       battle-tested fallback; system-ui covers the rest. Overrides Bootstrap's
       `--bs-font-sans-serif: "Poppins", sans-serif` (bootstrap.css). Hind
       Siliguri is kept for form-action / patient-facing views (see
       form-actions.css) — do not mass-replace. */
    --bs-font-sans-serif: "Geist", "Inter", system-ui, -apple-system,
        "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* Body: 14px is the modern admin baseline (Linear, Vercel, GitHub, shadcn).
   Up from 13.5px / Reference admin template default 13px. */
body {
    font-size: 0.875rem;
}

/* Heading scale tightened from Bootstrap default (32.5/26/22.75/19.5/16.25/13)
   to a five-step scale with clear jumps and weight 600 throughout. */
h1, .h1 { font-size: 1.75rem; font-weight: 600; }   /* 28px */
h2, .h2 { font-size: 1.375rem; font-weight: 600; }  /* 22px */
h3, .h3 { font-size: 1.125rem; font-weight: 600; }  /* 18px */
h4, .h4 { font-size: 0.9375rem; font-weight: 600; } /* 15px */
h5, .h5 { font-size: 0.8125rem; font-weight: 600; } /* 13px */
h6, .h6 { font-weight: 600; }

/* Modal titles — anchor every modal at h3 weight regardless of which heading
   element the markup uses (.modal-title is the stable Bootstrap hook). */
.modal-title { font-size: 1.125rem; font-weight: 600; }

/* Page title — Reference admin template's `.page-title-box h4` rule (in app.css) 
   sets weight 100 uppercase letterspaced on the page heading. Override to confident 
   sentence case at 26px / 600. Targets both h1 and h4 because views vary on which
   heading element they use inside `.page-title-box`. */
.page-title-box h1,
.page-title-box h4 {
    font-size: 1.625rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
}

/* Modal tabs (`.nav-tabs-custom`): keep the tab label and the validation-error
   count badge on a single line. TabValidationIndicator (views-common-utils.js)
   appends the badge after the title span; without nowrap on the link itself,
   the badge wraps below the title in narrower modals. */
.nav-tabs-custom .nav-link {
    white-space: nowrap;
}

/* Hairline border on cards. Bootstrap's `.card` has `--bs-card-border-width: 0`
   by default — this rule re-introduces a 1px border using the warm neutral
   from `--bs-border-color`. Card shadow rules are zeroed out at their source
   locations further down in this file (alpha set to 0.00). */
.card {
    border: 1px solid var(--bs-border-color);
}

/* Drop chrome-surface shadows too (sidebar, topnav, navbar-header,
   layout-wrapper). The hairline border on `--bs-border-color` carries the
   structure now. */
.vertical-menu,
.topnav,
.navbar-header,
#layout-wrapper {
    box-shadow: none;
}

/* Page topbar — remove the pink bottom-accent border. It read as personality
   on the old indigo palette but fights the warmer neutral. The default
   border-color on adjacent surfaces does the separation now. */
#page-topbar::after {
    display: none;
}

/* Soft badges — drop alpha from 18% to 10%, lighten hover from 40% to 18% */
.badge-soft-primary {
    background-color: rgba(83, 101, 207, 0.10);
    color: #5365cf;
}
.badge-soft-primary[href]:hover,
.badge-soft-primary[href]:focus {
    background-color: rgba(83, 101, 207, 0.18);
}
.badge-soft-secondary {
    background-color: rgba(116, 120, 141, 0.10);
}
.badge-soft-secondary[href]:hover,
.badge-soft-secondary[href]:focus {
    background-color: rgba(116, 120, 141, 0.18);
}
.badge-soft-success {
    background-color: rgba(52, 195, 143, 0.10);
}
.badge-soft-success[href]:hover,
.badge-soft-success[href]:focus {
    background-color: rgba(52, 195, 143, 0.18);
}
.badge-soft-info {
    background-color: rgba(80, 165, 241, 0.10);
}
.badge-soft-info[href]:hover,
.badge-soft-info[href]:focus {
    background-color: rgba(80, 165, 241, 0.18);
}
.badge-soft-warning {
    background-color: rgba(241, 180, 76, 0.10);
}
.badge-soft-warning[href]:hover,
.badge-soft-warning[href]:focus {
    background-color: rgba(241, 180, 76, 0.18);
}
.badge-soft-danger {
    background-color: rgba(244, 106, 106, 0.10);
}
.badge-soft-danger[href]:hover,
.badge-soft-danger[href]:focus {
    background-color: rgba(244, 106, 106, 0.18);
}
.badge-soft-pink {
    background-color: rgba(232, 62, 140, 0.10);
}
.badge-soft-pink[href]:hover,
.badge-soft-pink[href]:focus {
    background-color: rgba(232, 62, 140, 0.18);
}
.badge-soft-dark {
    background-color: rgba(52, 58, 64, 0.10);
}
.badge-soft-dark[href]:hover,
.badge-soft-dark[href]:focus {
    background-color: rgba(52, 58, 64, 0.18);
}

/* Page title token. Apply class="ir-page-title" to a page-heading element
   for the small-uppercase "admin template" treatment. Opt-in; not yet rolled
   out across all views. */
.ir-page-title {
    font-size: 0.75rem;        /* 12px */
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #74788d;
    margin-bottom: 0.5rem;
}

/* Acronym tag used in Practitioner Type / Company Type columns. Pairs with
   the full type name in the same cell — the tag does the categorization, the
   name does the disambiguation. Fixed min-width so the tags align in a
   vertical column at high row counts. */
.badge.type-abbr {
    min-width: 36px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-align: center;
}

/* Data tables: drop internal row dividers from `.table-bordered`.
   Bootstrap's `.table-bordered > :not(caption) > *` puts a 1px top+bottom
   border on every <tr>, and `.table-bordered > :not(caption) > * > *` puts a
   1px left+right border on every cell. Zero both — the header underline is
   already drawn by `.dataTables_wrapper thead { border-bottom: 2px ... }`
   later in this file, so list views still have row separation at the top. */
.table-bordered > :not(caption) > * {
    border-width: 0;
}
.table-bordered > :not(caption) > * > * {
    border-width: 0;
}

/* KPI mini-stat cards: bigger value, fainter sparkline.
   The card structure (see mini-charts.js buildCardHtml) is:
     .card.mini-stats-wid > .card-body > .d-flex > .flex-grow-1 > h4   ← value
                                                  > .flex-shrink-0     ← sparkline div
   The container-scoped rule (`#mini-charts-container .mini-stats-wid h4`)
   later in this file sets h4 to 1.15rem; this rule overrides it. */
.card.mini-stats-wid .card-body h4 {
    font-size: 1.875rem !important;  /* 30px — was 1.15rem (~18px) */
    font-weight: 600;
    line-height: 1.1;
}

.card.mini-stats-wid .flex-shrink-0 svg {
    opacity: 0.55;
}

/* Footer chip on KPI cards — strip the pill fill so it reads as plain text
   with a leading colored glyph instead of a saturated pill. The bx-* icon is
   colored per badge-soft-* variant and acts as the dot; the trailing label is
   muted so the icon carries the status signal. */
.card.mini-stats-wid > .card-body.border-top .badge {
    background-color: transparent !important;
    padding-left: 0;
    padding-right: 0;
    font-weight: 400;
    color: var(--bs-secondary-color, #74788d) !important;
}
/* Re-color the leading icon by badge-soft-* variant so it acts as the colored dot */
.card.mini-stats-wid > .card-body.border-top .badge-soft-primary i { color: var(--bs-primary); }
.card.mini-stats-wid > .card-body.border-top .badge-soft-success i { color: #34c38f; }
.card.mini-stats-wid > .card-body.border-top .badge-soft-info i    { color: #50a5f1; }
.card.mini-stats-wid > .card-body.border-top .badge-soft-warning i { color: #f1b44c; }
.card.mini-stats-wid > .card-body.border-top .badge-soft-danger i  { color: #f46a6a; }

/* Footer link strings ("Revolution Platform v5", "Incandera Core v5") read as
   muted gray rather than brand-blue — they're a status string, not a CTA. */
.footer a {
    color: var(--bs-secondary-color, #74788d);
    text-decoration: none;
}
.footer a:hover {
    color: var(--bs-body-color);
}

/* Ensure page content fills full viewport height (prevents gap on theme switch) */
.page-content {
    min-height: 100vh;
}

/* Global header — icon hover scale */
#page-topbar .noti-icon > i,
#page-topbar .right-bar-toggle > i {
    transition: transform 0.15s ease;
    display: inline-block;
}

#page-topbar .noti-icon:hover > i,
#page-topbar .right-bar-toggle:hover > i {
    transform: scale(1.30);
}

/* Notifications dropdown — height caps.
   Replaces JS-measured per-item height arithmetic that broke after the Geist/Lucide
   modernization (variable item heights from wrapped 2-line titles + post-render SVG
   icons made first-item measurement unreliable). The caps are pure CSS — let item
   content size itself, and let SimpleBar take over once the cap is reached.
   The expanded cap also clamps to viewport so the dropdown can never overflow. */
#notifications-list {
    max-height: 21rem; /* ~3 worst-case items, including 2-line title wrap */
    transition: max-height 0.2s ease;
}
#notifications-list.is-expanded {
    max-height: min(48rem, calc(100vh - 12rem));
}

/* The click target is the entire hovered band, not just the title. Practitioners kept
   clicking the preview text — which fell through to the backdrop and closed the
   dropdown instead of opening the notification. */
#notifications-list .notification-item-link {
    cursor: pointer;
}

#notifications-list .notification-item-link:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
}

/* Global header — bottom border (offset to avoid overlapping the sidebar brand-box) */
#page-topbar::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 250px;
    right: 0;
    height: 1px;
    background-color: #f1d5db;
}

.vertical-collpsed #page-topbar::after {
    left: 70px;
}

@media (max-width: 992px) {
    #page-topbar::after {
        left: 0;
    }
}

/* Dark Mode — header bottom border */
[data-layout-mode="dark"] #page-topbar::after {
    background-color: var(--bs-border-color);
}

/* Collapsed sidebar — remove theme's hardcoded 1760px min-height on body that creates massive empty space */
body[data-sidebar="dark"].vertical-collpsed {
    min-height: auto !important;
}

/* Collapsed sidebar — force submenu flyout on hover.
   MetisMenu adds .mm-collapse to submenus and hides them with display:none (via
   .mm-collapse:not(.mm-show)) plus inline height:0 + overflow:hidden during transitions.
   The SimpleBar wrapper div has inline overflow:hidden which clips the flyout panels;
   override it to visible when the sidebar is collapsed so flyouts can escape the sidebar. */
body.vertical-collpsed .vertical-menu [data-simplebar] {
    overflow: visible !important;
}

body.vertical-collpsed .vertical-menu #sidebar-menu > ul > li:hover > ul,
body.vertical-collpsed .vertical-menu #sidebar-menu > ul > li:hover > ul.mm-collapse,
body.vertical-collpsed .vertical-menu #sidebar-menu > ul > li:hover > ul.mm-collapse.sub-menu {
    display: block !important;
    height: auto !important;
    overflow: visible !important;
    visibility: visible !important;
}

/* …and the empty band that costs.
   ────────────────────────────────
   Collapsing the sidebar on a tablet left a tall dead strip below the footer,
   with the page ending mid-screen. The cause is a two-rule interaction, and
   neither rule is wrong on its own:

     1. the theme flips the rail to `position: absolute` when collapsed
        (`.vertical-collpsed .vertical-menu` in app.min.css). An ABSOLUTE box,
        unlike a fixed one, still contributes to its ancestor's scroll height;
     2. the rule directly above forces `overflow: visible` on the SimpleBar
        wrapper so hover flyouts can escape the 70px rail — which means the
        menu no longer clips its own content to the viewport.

   Together the rail reports its full natural content height. Measured on a
   1180x820 iPad in WebKit: `simplebar-content` is 1241px tall, so
   `document.scrollHeight` went 820 -> 1210 and the page gained ~390px of
   empty scroll below the footer. Confirmed by experiment rather than by
   reading: forcing the rail back to `position: fixed` in the live DOM dropped
   scrollHeight straight back to 820.

   Fixed rather than absolute is also what the EXPANDED rail already uses, so
   this restores consistency instead of introducing a special case: the rail is
   viewport-anchored in both states, and the flyouts — which are positioned
   against the rail, not the document — are unaffected. The `bottom: 0` pins it
   to the viewport so a long menu scrolls inside SimpleBar, as it does when
   expanded, rather than stretching the page. */
body.vertical-collpsed .vertical-menu {
    position: fixed;
    bottom: 0;
}

/* Maximize content rendering to available Viewport height */
.card-body-defaultheight {
    height: calc(100vh - 250px);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

/* DataTables ColVis checkmarks fix */
.dt-button-collection .dt-button.active::after,
.dt-button-collection .dt-button:not(.disabled).active::after {
    content: "\2713";
    position: absolute;
    right: 23px;
    color: #556ee6;
    font-weight: bold;
}

.dt-button-collection .dt-button {
    position: relative;
    padding-right: 30px;
}

/* DataTables ColVis dropdown positioning and styling */
.dt-buttons {
    position: relative;
    flex-shrink: 0;
    white-space: nowrap;
    display: flex;
    align-items: stretch;
}

/* Match icon-only DataTable buttons to Bootstrap btn height */
.dt-buttons .dt-control-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: .375rem .75rem;
    line-height: 1.5;
    font-size: 1rem;
}

/* Tiny indicator dot on colvis button when columns are hidden */
.dt-buttons .buttons-colvis.has-hidden-columns {
    position: relative;
}

.dt-buttons .buttons-colvis.has-hidden-columns::after {
    content: "";
    position: absolute;
    top: 6px;
    right: 6px;
    width: 7px;
    height: 7px;
    background-color: #f1b44c;
    border-radius: 50%;
    pointer-events: none;
}

/* The toolbar's primary action ("New") must not look like the neutral icon
   segments beside it.

   DataTables stamps `btn btn-secondary` on EVERY custom button and then appends
   the view's own className, so a button opted into btn-primary carries both
   classes at equal specificity — and `btn-secondary` wins on source order. The
   `.dt-buttons .btn.btn-primary` bump below is what breaks that tie.

   That bump used to live only in brand-incandescence.css, which Serenity never
   loads, so under Serenity the New button rendered rgb(116,120,141): pixel-
   identical to the colvis/copy/export segments next to it. Measured on
   /Patients/All — New and the icon buttons returned the same computed
   background. The primary action was invisible as a primary action.

   Driven by --bs-primary so it is correct in both brands without hardcoding a
   palette here. brand-incandescence.css still carries its own copy with the
   literal ember values and loads AFTER this file, so Incandescence is unchanged;
   this is the floor that Serenity was missing. */
.dt-buttons .btn.btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-primary) 85%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-primary) 85%, #000);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-primary) 75%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-primary) 75%, #000);
}

/* Toolbars with an extra action or two (Surveys, Forms/All) run past a phone's
   width and the group wraps to a second row. Bootstrap's .btn-group is built for
   a single row: it strips the interior radii by DOM position, so whichever button
   happens to land first on row 2 keeps a square left edge and whichever lands
   last on row 1 keeps a square right edge — the wrap reads as a broken seam
   rather than two rows of buttons. Measured on /Forms/All at 393px: the wrapped
   segment stretched to the full 288px and computed 0px/4px/4px/0px.

   The fix must NOT apply to the ordinary six-segment toolbar, which fits on one
   line and whose continuous bar is the intended look — rounding every segment
   there turns a single clean bar into a row of disconnected chips. So it is
   opted into per table by `.dt-buttons-wrapped`, a class the wrap helper adds
   only after measuring that the group actually occupies more than one row.

   `flex: 0 0 auto` stops the lone wrapped button from stretching to fill the
   row it landed on (measured: it grew to the full 288px). */
@media (max-width: 767px) {
    .dt-buttons.dt-buttons-wrapped {
        flex-wrap: wrap;
        gap: 3px;
    }

    .dt-buttons.dt-buttons-wrapped > .btn,
    .dt-buttons.dt-buttons-wrapped > .btn-group > .btn {
        border-radius: .25rem !important;
        flex: 0 0 auto;
    }
}

/* Round the right edge of the last visible toolbar button.
   Bootstrap's .btn-group strips border-radius from :not(:last-child) buttons,
   but when the "New" button is hidden (d-none) it's still :last-child in the DOM,
   so the last visible icon button loses its right-side rounding. Override with !important. */
.dt-buttons .dt-control-button:has(+ .d-none) {
    border-top-right-radius: .25rem !important;
    border-bottom-right-radius: .25rem !important;
}

div.dt-button-collection {
    right: 9px !important;
    left: auto !important;
}

/* Dark Mode Harmonization for DataTables Buttons */
[data-layout-mode="dark"] div.dt-button-collection {
    border-color: var(--bs-gray-300) !important; /* Seamless border */
    background-color: var(--bs-gray-300) !important;
}

[data-layout-mode="dark"] div.dt-button-collection .dt-button {
    background-color: var(--bs-gray-300) !important;
    color: var(--bs-body-color) !important;
    border: none !important;
}

[data-layout-mode="dark"] div.dt-button-collection .dt-button:hover,
[data-layout-mode="dark"] div.dt-button-collection .dt-button.active {
    background-color: var(--bs-gray-300) !important;
    color: var(--bs-body-color) !important;
}

/* Dark Mode — toolbar icon buttons sit in the filter area, not on it */
[data-layout-mode="dark"] .dt-buttons .dt-control-button {
    background-color: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.08);
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .dt-buttons .dt-control-button:hover {
    background-color: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.12);
}

/* jQuery Steps skipped step styling */
.steps li.skipped-step a,
.steps li.skipped-step a:hover {
    color: #ccc !important;
}

/* Dark Mode Harmonization */

/* Modal Footer */
.modal-footer-colored {
    background-color: #f2f2f2; /* Light mode default */
}

[data-layout-mode="dark"] .modal-footer-colored {
    background-color: var(--bs-gray-200); /* Dark mode card background */
    border-top-color: var(--bs-border-color);
}

/* Dark Mode: Modal form fields */
[data-layout-mode="dark"] .modal .form-control,
[data-layout-mode="dark"] .modal .form-select,
[data-layout-mode="dark"] .modal .input-group-text {
    background-color: var(--bs-gray-300);
    border-color: var(--bs-border-color);
    color: var(--bs-body-color);
}

/* Dark Mode: Disabled / read-only form fields — subtle, dimmer treatment */
[data-layout-mode="dark"] .modal .form-control:disabled,
[data-layout-mode="dark"] .modal .form-control[readonly],
[data-layout-mode="dark"] .modal .form-select:disabled,
[data-layout-mode="dark"] .modal .form-select[readonly],
[data-layout-mode="dark"] .modal .select2-container--disabled .select2-selection--single,
[data-layout-mode="dark"] .modal .select2-container--disabled .select2-selection--multiple {
    background-color: rgba(255, 255, 255, 0.04) !important;
    border-color: rgba(255, 255, 255, 0.08) !important;
    color: rgba(255, 255, 255, 0.65);
    opacity: 1;
}

[data-layout-mode="dark"] .modal .select2-container--disabled .select2-selection__rendered,
[data-layout-mode="dark"] .modal .select2-container--disabled .select2-selection__placeholder {
    color: rgba(255, 255, 255, 0.5) !important;
}

/* Dark Mode: Subdued page-level form fields (Incandera admin tools, etc.) */
[data-layout-mode="dark"] .dark-form-subdued .form-control,
[data-layout-mode="dark"] .dark-form-subdued .form-select,
[data-layout-mode="dark"] .dark-form-subdued .input-group-text,
[data-layout-mode="dark"] .dark-form-subdued .select2-container .select2-selection--single,
[data-layout-mode="dark"] .dark-form-subdued .select2-container .select2-selection--multiple {
    background-color: rgba(255, 255, 255, 0.06) !important;
    border-color: rgba(255, 255, 255, 0.10) !important;
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .dark-form-subdued .select2-container .select2-selection--single .select2-selection__rendered {
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .dark-form-subdued .select2-container .select2-selection--single .select2-selection__placeholder {
    color: rgba(255, 255, 255, 0.35);
}

[data-layout-mode="dark"] .dark-form-subdued .select2-container .select2-selection--single .select2-selection__arrow b {
    border-color: rgba(255, 255, 255, 0.35) transparent transparent transparent;
}

[data-layout-mode="dark"] .dark-form-subdued .form-control:disabled,
[data-layout-mode="dark"] .dark-form-subdued .form-control[readonly],
[data-layout-mode="dark"] .dark-form-subdued .form-select:disabled,
[data-layout-mode="dark"] .dark-form-subdued .form-select[readonly],
[data-layout-mode="dark"] .dark-form-subdued .select2-container--disabled .select2-selection--single,
[data-layout-mode="dark"] .dark-form-subdued .select2-container--disabled .select2-selection--multiple {
    background-color: rgba(255, 255, 255, 0.03) !important;
    border-color: rgba(255, 255, 255, 0.06) !important;
    color: rgba(255, 255, 255, 0.5);
    opacity: 1;
}

[data-layout-mode="dark"] .dark-form-subdued .form-control::placeholder {
    color: rgba(255, 255, 255, 0.35);
}

[data-layout-mode="dark"] .dark-form-subdued .form-text {
    color: rgba(255, 255, 255, 0.45);
}

/* Dark Mode: bg-light helper (used in notification text, referral cards, result panels) */
[data-layout-mode="dark"] .bg-light {
    background-color: rgba(255, 255, 255, 0.04) !important;
    color: var(--bs-body-color);
}

/* Dark Mode: Alert callouts — soften brightness */
[data-layout-mode="dark"] .alert-info {
    background-color: rgba(85, 110, 230, 0.12);
    border-color: rgba(85, 110, 230, 0.2);
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .alert-warning {
    background-color: rgba(241, 180, 76, 0.12);
    border-color: rgba(241, 180, 76, 0.2);
    color: #e8c97a;
}

[data-layout-mode="dark"] .alert-secondary {
    background-color: rgba(116, 120, 141, 0.15);
    border-color: rgba(116, 120, 141, 0.25);
    color: #a6aab4;
}

/* Dark Mode: list-group-item-action hover (news feed, form create modal) */
[data-layout-mode="dark"] .list-group-item-action:hover,
[data-layout-mode="dark"] .list-group-item-action:focus {
    background-color: rgba(255, 255, 255, 0.06);
    color: var(--bs-body-color);
}

/* DataTables / Tables */
[data-layout-mode="dark"] .table thead th {
    background-color: var(--bs-gray-200);
    color: var(--bs-body-color);
    border-color: var(--bs-border-color);
}

[data-layout-mode="dark"] .table {
    color: var(--bs-body-color);
    border-color: var(--bs-border-color);
}

[data-layout-mode="dark"] .table-bordered td,
[data-layout-mode="dark"] .table-bordered th {
    border-color: var(--bs-border-color);
}

/* DataTables Layout & Styling */
.dataTables_wrapper .top {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    background-color: #eee; /* Default Light Mode */
    padding: 8px;
    border-top: none;
}

[data-layout-mode="dark"] .dataTables_wrapper .top {
    background-color: rgba(255, 255, 255, 0.03); /* Subliminal block structure */
    border-top: none;
}

[data-layout-mode="dark"] .dataTables_filter input,
[data-layout-mode="dark"] .dataTables_length select,
[data-layout-mode="dark"] .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--multiple,
[data-layout-mode="dark"] .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--single,
[data-layout-mode="dark"] .dataTables_wrapper .filter-length .dt-inline-filter input.form-control {
    background-color: var(--bs-input-bg);
    border-color: var(--bs-border-color) !important;
}

/* Table header row — keep current bg, 2px subtle bottom border */
.dataTables_wrapper thead {
    border-bottom: 2px solid #eee;
}

[data-layout-mode="dark"] .dataTables_wrapper thead {
    border-bottom-color: rgba(255, 255, 255, 0.08);
}

.dataTables_wrapper table.dataTable thead th,
.dataTables_wrapper table.dataTable thead td {
    background-color: #f3f4f6;
    border-bottom: none !important;
}

[data-layout-mode="dark"] .dataTables_wrapper table.dataTable thead th,
[data-layout-mode="dark"] .dataTables_wrapper table.dataTable thead td {
    background-color: var(--bs-gray-200);
}

/* Table content area — subliminal bottom border */
.dataTables_wrapper table.dataTable {
    border-bottom: 2px solid #f0f0f0;
}

[data-layout-mode="dark"] .dataTables_wrapper table.dataTable {
    border-bottom-color: rgba(255, 255, 255, 0.04);
}

/* Internal cell borders — transparent 1px (preserves layout on hover swap) */
.dataTables_wrapper table.dataTable tbody tr > td {
    border-top: 1px solid transparent;
    border-bottom: 1px solid transparent;
}

/* Dark Mode — paginator borders tuned to match survey card tone */
[data-layout-mode="dark"] .dataTables_wrapper .dataTables_paginate .paginate_button .page-link,
[data-layout-mode="dark"] .dataTables_wrapper .dataTables_paginate .page-link {
    border-color: rgba(255, 255, 255, 0.08);
}

.dataTables_wrapper .filter-length {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    flex: 1 1 auto;
    min-width: 0;
    margin-right: 1.25em;
}

/* DataTables Toolbar — Inline filter containers (status, date range, etc.)
   Flex-shrink so filters compress at narrow viewports instead of wrapping. */
.dataTables_wrapper .filter-length .dt-inline-filter {
    flex: 0 1 auto;
    min-width: 0;
    display: inline-flex !important;
    align-items: center;
    margin-left: 1.25em;
}

/* Neutralize any theme margin on Select2 containers inside toolbar filters */
.dataTables_wrapper .filter-length .dt-inline-filter .select2-container {
    margin-top: 0;
    margin-bottom: 0;
}

/* Switch clusters read as a SET, not as unrelated controls.
   ─────────────────────────────────────────────────────────
   The switch wrappers are authored `form-check-inline ms-3`, which is 16px of
   margin on BOTH sides (Bootstrap's own `form-check-inline` margin-right plus
   the ms-3). Between two adjacent switches those margins sum: measured 32px on
   /Patients, which is wider than the gap separating the whole cluster from the
   filter beside it — so the pair read as two loose controls rather than one
   group.

   Collapse the interior spacing and let the container's own flex gap own it.
   The cluster keeps its separation from neighbouring filters (that comes from
   .dt-inline-filter's margin-left), so only the INTERNAL rhythm changes.
   Applies wherever the idiom is used, not just Patients. */
.dataTables_wrapper .filter-length .dt-inline-filter {
    gap: .75rem;
}

.dataTables_wrapper .filter-length .dt-inline-filter > .form-check-inline {
    margin-left: 0 !important;
    margin-right: 0 !important;
}

/* Status filter: give it a flex-basis so it has room to fill, then shrinks */
#referral-status-filter-container.dt-inline-filter {
    flex: 1 1 540px;
    max-width: 540px;
    min-width: 180px;
}

/* Date range filter: 3 inline elements (column select + 2 date inputs) */
#referral-date-range-container.dt-inline-filter {
    flex: 0 1 370px;
    max-width: 370px;
    min-width: 200px;
}

/* Patient status filter: Select2 only (the toggle switches are now their own
   toolbar block, so this no longer reserves room for them). */
#patient-status-filter-container.dt-inline-filter {
    flex: 1 1 280px;
    max-width: 280px;
    min-width: 180px;
}

/* Form instance status filter */
#form-status-filter-container.dt-inline-filter {
    flex: 1 1 300px;
    max-width: 300px;
    min-width: 140px;
}

/* Form instance billing status filter */
#form-billing-filter-container.dt-inline-filter {
    flex: 1 1 300px;
    max-width: 300px;
    min-width: 140px;
}

/* Company type filter */
#company-type-filter-container.dt-inline-filter {
    flex: 1 1 640px;
    max-width: 640px;
    min-width: 180px;
}

/* Practitioner type filter */
#practitioner-type-filter-container.dt-inline-filter {
    flex: 1 1 640px;
    max-width: 640px;
    min-width: 180px;
}

/* Notification type filter */
#notification-type-filter-container.dt-inline-filter {
    flex: 1 1 300px;
    max-width: 300px;
    min-width: 140px;
}

/* Intake invitation status filter. Every filter here needs its own width rule —
   without one the container inherits `flex: 0 1 auto` and sizes to its CONTENT,
   so the box starts collapsed and grows with each chip added. Pinning flex-basis
   and max-width to the same value keeps it a fixed box that chips fill up. */
#intake-status-filter-container.dt-inline-filter {
    flex: 1 1 250px;
    max-width: 250px;
    min-width: 140px;
}

/* Notification priority filter */
#notification-priority-filter-container.dt-inline-filter {
    flex: 1 1 260px;
    max-width: 260px;
    min-width: 130px;
}

/* Notification date range filter */
#notification-date-range-container.dt-inline-filter {
    flex: 0 1 370px;
    max-width: 370px;
    min-width: 200px;
}

/* Invoice status filter */
#status-filter-container.dt-inline-filter {
    flex: 1 1 300px;
    max-width: 300px;
    min-width: 140px;
}

/* Invoice date range filter */
#invoice-date-range-container.dt-inline-filter {
    flex: 0 1 370px;
    max-width: 370px;
    min-width: 200px;
}

/* FormSet discipline filter */
#discipline-filter-container.dt-inline-filter {
    flex: 1 1 640px;
    max-width: 640px;
    min-width: 180px;
}

/* Log level filter */
#log-level-filter-container.dt-inline-filter {
    flex: 1 1 480px;
    max-width: 480px;
    min-width: 180px;
}

/* Log date range filter */
#log-date-range-container.dt-inline-filter {
    flex: 0 1 260px;
    max-width: 260px;
    min-width: 160px;
}

/* Audit action filter */
#audit-action-filter-container.dt-inline-filter {
    flex: 1 1 480px;
    max-width: 480px;
    min-width: 180px;
}

/* Audit date range filter */
#audit-date-range-container.dt-inline-filter {
    flex: 0 1 260px;
    max-width: 260px;
    min-width: 160px;
}

/* Keep search box and length selector at fixed sizes — they're compact already */
.dataTables_wrapper .filter-length .dataTables_filter,
.dataTables_wrapper .filter-length .dataTables_length {
    flex-shrink: 0;
    margin-bottom: 0;
}

.dataTables_wrapper .bottom {
    display: flex;
    justify-content: space-between;
    padding: 5px 8px;
    background-color: #f5f5f5;
    border-top: 1px solid #e8e8e8;
    border-bottom: 2px solid #eee;
}

[data-layout-mode="dark"] .dataTables_wrapper .bottom {
    background-color: rgba(255, 255, 255, 0.01);
    border-top-color: rgba(255, 255, 255, 0.04);
    border-bottom-color: rgba(255, 255, 255, 0.05);
}

/* DataTables Filter Input */
.dataTables_filter input {
    width: 200px;
    height: 28px;
    background-color: #fff;
}

.dataTables_filter label {
    font-weight: 600; /* fw-semibold */
    margin-bottom: 0;
}

/* DataTables Length Select */
.dataTables_length {
    margin-left: 1.25em;
}

.dataTables_length select {
    height: 28px;
    background-color: #fff;
    padding-top: 5px; /* Nudge text down for better vertical centering */
}

[data-layout-mode="dark"] .dataTables_length select {
    background-color: var(--bs-input-bg);
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .dataTables_length select option {
    background-color: var(--bs-input-bg);
    color: var(--bs-body-color);
}

.dataTables_length label {
    margin-bottom: 0;
}

/* DataTables Toolbar — Select2 filter controls height (excludes datepicker inputs) */
.dataTables_wrapper .filter-length .dt-inline-filter .select2-container {
    height: 28px !important;
    font-size: 0.8125rem !important; /* Match theme's input font size */
}

.dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--multiple,
.dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--single {
    height: 28px !important;
    min-height: 28px !important;
    background-color: #fff;
    border-color: #ced4da !important;
}

/* Single-select specific arrow fix */
.dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--single .select2-selection__arrow {
    height: 26px !important;
    width: 20px !important;
    top: 1px !important;
    right: 1px !important;
    left: auto !important;
}

.dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--single .select2-selection__arrow b {
    margin-top: -2px !important;
}

/* Single-select rendered text — vertically centered within 28px (minus 2px border = 26px) */
    .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--single .select2-selection__rendered {
        line-height: 26px !important;
        padding-top: 0 !important;
        padding-right: 25px !important; /* Room for arrow */
    }

    /* Selected items (choices) in multi-select filters */
    .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--multiple .select2-selection__rendered {
        padding: 0 0 0 4px !important;
        line-height: 26px !important; /* Match container inner height */
        display: flex !important;
        align-items: center !important;
        flex-wrap: nowrap !important; /* Keep choices and clear on one line */
        overflow: hidden !important;
    }

    .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--multiple .select2-selection__choice {
        height: 22px !important;
        line-height: 20px !important; /* 22px minus 2px border = 20px inner → centered text */
        margin-top: 0.08em !important;
        margin-bottom: 0 !important;
        margin-right: 5px !important;
        padding: 0 4px 0 0 !important;
        font-size: 12px !important;
        background-color: var(--bs-gray-200) !important;
        border: 1px solid var(--bs-gray-300) !important;
    }

    /* Clear-all button ('×') — blue, right-aligned, matching native search clear */
    .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--multiple .select2-selection__clear,
    .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--single .select2-selection__clear {
        order: 999; /* Flex order: always rightmost */
        margin-left: auto !important; /* Push to far right of flex container */
        margin-right: 6px !important;
        margin-top: 0 !important;
        padding: 0 !important;
        color: #556ee6 !important; /* Theme primary blue, matching browser search clear */
        font-size: 14px !important;
        line-height: 26px !important; /* Vertically center within 28px - 2px border */
        cursor: pointer;
        float: none !important; /* Use flex ordering, not float */
        position: relative;
        z-index: 1;
    }

    .dataTables_wrapper .filter-length .dt-inline-filter .select2-selection--multiple .select2-search__field {
        margin-top: 0 !important;
        height: 22px !important;
        line-height: 22px !important;
        font-size: 12px !important;
        margin-left: 2px !important;
        /* Select2 inits the multi-select search field while its container is still
           d-none, so it computes a ~0px width and the placeholder text is clipped
           (invisible until a later select-then-clear forces a re-resize). Force the
           field to fill its parent so the placeholder shows on first render. This
           restores the width:100% that JS used to apply before that inline style
           was moved to CSS in 94227d5. */
        width: 100% !important;
    }

/* Clickable mini-chart cards (radial status rings / sparkline stat cards).
   Signals the card filters the page grid on click. */
.mini-chart-clickable {
    cursor: pointer;
    transition: background-color 0.12s ease-in-out, transform 0.12s ease-in-out;
}

.mini-chart-clickable:hover {
    background-color: rgba(0, 0, 0, 0.025);
}

/* Sparkline stat cards sit in a Bootstrap .card — signal hover with a shadow only.
   Do NOT use transform/translate here: it lifts the whole card (and the sparkline
   inside it) on hover, which reads as a layout shift on the chart. A static shadow
   gives the affordance without moving anything. */
.mini-stats-wid.mini-chart-clickable:hover {
    background-color: transparent;
    box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.08);
}

/* DataTables Toolbar — Datepicker inputs inside inline filters */
.dataTables_wrapper .filter-length .dt-inline-filter input.form-control {
    height: 28px !important;
    font-size: 12px !important;
    padding: 2px 6px !important;
    background-color: #fff;
    margin-left: 6px;
}

/* Authentication Pages */
.auth-full-bg {
    background-image: url('/images/bg-auth-light.jpg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

.auth-full-bg .bg-overlay {
    background-color: rgba(0, 0, 0, 0.21);
    opacity: 0.63;
}

/* Authentication Pages Dark Mode */
[data-layout-mode="dark"] .auth-body-bg {
    background-color: var(--bs-body-bg) !important;
}

[data-layout-mode="dark"] .auth-full-bg {
    background-color: var(--bs-card-bg) !important;
    background-image: url('/images/bg-auth.jpg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

[data-layout-mode="dark"] .auth-full-page-content {
    background-color: var(--bs-body-bg);
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .auth-logo .auth-logo-dark {
    display: none !important;
}

[data-layout-mode="dark"] .auth-logo .auth-logo-light {
    display: block !important;
}

[data-layout-mode="dark"] .text-muted {
    color: var(--bs-gray-400) !important;
}

[data-layout-mode="dark"] .text-primary,
[data-layout-mode="dark"] .auth-full-bg h4,
[data-layout-mode="dark"] .auth-full-bg h4 span,
[data-layout-mode="dark"] .auth-full-bg p {
    color: #fff !important;
}

[data-layout-mode="dark"] .auth-full-bg .bg-overlay {
    background-color: rgba(0, 0, 0, 0.0);
    opacity: 0.42;
}

/* Page Title Icons Styling */
h1 i.ph-duotone {
    position: relative;
    top: 0.15rem;
}

/*
   Override for vertical tabs (checkout-tabs) to be less overpowering.
   Changes the active state from solid primary fill to a light background with a border indicator.
*/
.checkout-tabs .nav-pills .nav-link.active {
    background-color: rgba(85, 110, 230, 0.1) !important;
    color: #556ee6 !important;
    border-left: 4px solid #556ee6;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* Sidebar "Toolbox" Styling (used in Referrals/New, Patients/New, and Forms) */
.card-toolbox {
    background-color: #f8f9fa; /* Light mode: Very light gray */
    border: 1px solid var(--accent-callout); /* Role-named token; see :root */
    overflow: hidden; /* Fixes "bitten off" corners by clipping child backgrounds */
}

[data-layout-mode="dark"] .card-toolbox {
    background-color: var(--bs-gray-300); /* Dark mode: Slightly lighter than standard card */
    border-color: var(--bs-border-color);
}

.card-toolbox .card-title {
    font-weight: 600;
}

/* Unified Sidebar Table Density (Matching /Referrals/New ideal) */
.card-toolbox .table-sm tbody td,
.card-toolbox .table-sm thead th {
    padding: 0.3rem 0.5rem !important;
}

/* DataTables Controls in Sidebar */
.card-toolbox .dataTables_wrapper .top {
    padding: 8px;
}

.card-toolbox .dataTables_filter input {
    width: 100% !important;
    max-width: 140px;
    font-size: 0.8rem;
}

.card-toolbox .dataTables_filter input::placeholder {
    font-size: 0.75rem;
}

/* Remove margin-bottom from search label to fix vertical centering */
.card-toolbox .dataTables_filter label {
    margin-bottom: 0;
}

.card-toolbox .dataTables_length label {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 0.75rem;
    margin-bottom: 0;
    white-space: nowrap;
}

/* Toolbox toolbar: let Search and "Show N entries" sit on separate lines.
   ──────────────────────────────────────────────────────────────────────
   These sidebar tables live in a `col-lg-3` beside a `col-lg-9`, and their
   own `dom` string puts the filter and the length selector side by side in a
   `<"d-flex align-items-center gap-2"fl>` — a flex container with no
   flex-wrap. On a wide desktop that fits. Below roughly 1400px the column
   narrows faster than its contents: a 140px search input, a nowrap
   "Show [10] entries", and the 0.5rem gap together exceed the card, and
   `.card-toolbox` carries `overflow: hidden` (see above), so the overflow is
   CLIPPED rather than scrollable — the entries selector simply disappears off
   the right edge, which is what the tablet screenshots show.

   Allowing the pair to wrap is the whole fix: each control takes a line when
   there is no room for two, and the desktop layout is untouched because a
   wrapping container with room to spare wraps nowhere.

   Capped at 1399.98px rather than applied unconditionally so the QHD/4K tiers
   keep the compact single-line toolbar they were designed with. The lower
   bound is deliberately absent — below 992px the sidebar column stacks under
   the main content at full width, where wrapping is harmless. */
@media (max-width: 1399.98px) {
    .card-toolbox .dataTables_wrapper .top > .d-flex {
        flex-wrap: wrap;
        row-gap: 6px;
    }

    /* Once wrapped, the input has a full column to use, so the 140px desktop
       cap is what would keep it looking stunted. */
    .card-toolbox .dataTables_filter input {
        max-width: 100%;
    }

    /* The TABLE half of the same squeeze. The toolbar fix above stopped the
       controls disappearing; the grid below them still gets clipped, because
       `.card-toolbox` carries `overflow: hidden` (see the base rule) and the
       column is ~294px at 1366px. Whether that bites depends on the DATA: the
       seeded fixture's short names happen to fit in 252px, while real patient
       names ("Duplicate"/"Forcible" in the reported case) push the grid wider
       than the card, and the Status column is then cut mid-badge — "New"
       rendering as "Nev", with the header showing "Sta". Nothing about the
       layout announces that a column is missing; it just looks broken.

       A scroller inside the card is the honest treatment: the wrapper clips on
       its own instead of the card doing it silently, and the content stays
       REACHABLE. Deliberately NOT DataTables Responsive (the (+) child-row
       treatment) — Responsive measures via a same-classed clone, and this card
       already carries bespoke `.card-toolbox` sizing that the clone would
       inherit; see [[reference_datatables_responsive_clone_measurement]] for
       what that cost us the last time. A plain overflow scroller has no
       measurement pass to fool.

       Scoped to the wrapper so the card's own rounded corners keep clipping
       their backgrounds, which is what the base `overflow: hidden` is for. */
    .card-toolbox .dataTables_wrapper {
        overflow-x: auto;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
    }

    /* Without a floor the grid keeps shrinking to fit and re-creates the
       squeeze this rule exists to end — the columns must be allowed to reach
       their natural width and let the wrapper scroll instead. */
    .card-toolbox .dataTables_wrapper table.dataTable {
        min-width: 240px;
    }

    /* The status badge is the specific casualty: it is the narrowest column and
       the last one, so it absorbs whatever width the names leave behind. Let it
       keep its own text intact rather than being sliced. */
    .card-toolbox .dataTables_wrapper table.dataTable td .badge {
        white-space: nowrap;
    }
}

.card-toolbox .dataTables_length select {
    padding: 0.2rem 1.5rem 0.2rem 0.5rem;
    font-size: 0.75rem;
}

.card-toolbox .dataTables_info {
    font-size: 0.75rem;
    margin-top: 8px !important;
    padding-top: 0 !important;
}

.card-toolbox .dataTables_length {
    margin-top: 1px !important;
}

.card-toolbox .dataTables_paginate {
    padding-top: 0 !important;
}

.card-toolbox .dataTables_paginate .paginate_button {
    padding: 0 !important;
}

.card-toolbox .dataTables_paginate .paginate_button .page-link {
    padding: 0.2rem 0.5rem !important;
    font-size: 0.75rem;
}

/* Fix for "doubled up" borders when an accordion is inside a toolbox card */
.card-toolbox .accordion,
.card-toolbox .accordion-item,
.card-toolbox .accordion-header,
.card-toolbox .accordion-button {
    border: none !important;
    background-color: transparent !important;
}

.card-toolbox .accordion-button:not(.collapsed) {
    box-shadow: none !important;
    background-color: rgba(85, 110, 230, 0.1) !important; /* Subtle light blue for active item */
    color: #556ee6 !important;
}

[data-layout-mode="dark"] .card-toolbox .accordion-button:not(.collapsed) {
    background-color: rgba(85, 110, 230, 0.15) !important;
    color: #7b8fda !important;
}

/* ============================================================================
   SIDEBAR-SPECIFIC OVERRIDES
   These apply ONLY to specific sidebar tables (Referrals/New, Patients/New, Forms/Index)
   using table ID selectors to avoid affecting other card-toolbox tables like Forms/New
   ============================================================================ */

/* Sidebar Quick Lookup DataTables - Specific table overrides */
#patients-available-table_wrapper .top,
#referrals-available-table_wrapper .top,
#available-forms-table_wrapper .top {
    padding-top: 8px;
}

/* Font size overrides for specific sidebar tables only */
#patients-available-table_wrapper .dataTables_length label,
#referrals-available-table_wrapper .dataTables_length label,
#available-forms-table_wrapper .dataTables_length label {
    font-size: 0.7rem;
}

#patients-available-table tbody td,
#referrals-available-table tbody td,
#available-forms-table tbody td {
    font-size: 0.7rem;
}

#patients-available-table_wrapper .dataTables_info,
#referrals-available-table_wrapper .dataTables_info,
#available-forms-table_wrapper .dataTables_info {
    font-size: 0.7rem;
    margin-top: 10px;
}

/* ============================================================================
   HEADER UNIVERSAL SEARCH DROPDOWN
   ============================================================================ */

.header-search-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 320px;
    width: max-content;
    max-width: 450px;
    max-height: 400px;
    overflow-y: auto;
    overflow-x: hidden;
    background-color: #fff;
    border: 1px solid #e0e0e0;
    border-radius: 0.375rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    z-index: 1050;
    margin-top: 4px;
}

/* Reset any inherited styles that might interfere */
.header-search-dropdown * {
    box-sizing: border-box;
}

/* Override .app-search span { position: absolute } from app.css */
.header-search-dropdown span {
    position: static !important;
    z-index: auto !important;
    right: auto !important;
    font-size: inherit !important;
    line-height: inherit !important;
}

[data-layout-mode="dark"] .header-search-dropdown {
    background-color: var(--bs-gray-200);
    border-color: var(--bs-border-color);
}

/* Category Headers */
.search-category-header {
    display: block;
    position: relative;
    padding: 8px 12px;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    color: #6c757d;
    background-color: #f8f9fa;
    border-bottom: 1px solid #e0e0e0;
}

[data-layout-mode="dark"] .search-category-header {
    background-color: var(--bs-gray-300);
    color: var(--bs-gray-400);
    border-color: var(--bs-border-color);
}

/* Result Items */
.search-result-item {
    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    position: relative !important;
    padding: 10px 12px;
    cursor: pointer;
    transition: background-color 0.15s ease;
    border-bottom: 1px solid #f0f0f0;
    background-color: #fff;
}

.search-result-item:last-child {
    border-bottom: none;
}

.search-result-item:hover,
.search-result-item.active {
    background-color: rgba(85, 110, 230, 0.1);
}

[data-layout-mode="dark"] .search-result-item {
    background-color: var(--bs-gray-200);
    border-color: var(--bs-border-color);
}

[data-layout-mode="dark"] .search-result-item:hover,
[data-layout-mode="dark"] .search-result-item.active {
    background-color: rgba(85, 110, 230, 0.2);
}

/* Result Icon */
.search-result-icon {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 10px;
    background-color: #f0f0f0;
    border-radius: 50%;
    color: #556ee6;
}

[data-layout-mode="dark"] .search-result-icon {
    background-color: var(--bs-gray-300);
}

.search-result-icon i {
    font-size: 1rem;
}

/* Result Content */
.search-result-content {
    flex: 1 1 auto;
    min-width: 150px;
    overflow: hidden;
}

.search-result-text {
    display: block;
    font-size: 0.875rem;
    font-weight: 500;
    color: #343a40 !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.4;
}

[data-layout-mode="dark"] .search-result-text {
    color: #e9ecef !important;
}

.search-result-subtext {
    display: block;
    font-size: 0.75rem;
    color: #6c757d !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.3;
}

[data-layout-mode="dark"] .search-result-subtext {
    color: #adb5bd !important;
}

/* No Results State */
.search-no-results {
    padding: 20px;
    text-align: center;
    color: #6c757d;
    font-size: 0.875rem;
}

[data-layout-mode="dark"] .search-no-results {
    color: var(--bs-gray-400);
}

/* Loading State */
.search-loading {
    padding: 20px;
    text-align: center;
    color: #6c757d;
}

.search-loading::after {
    content: '';
    display: inline-block;
    width: 16px;
    height: 16px;
    border: 2px solid #556ee6;
    border-right-color: transparent;
    border-radius: 50%;
    animation: spin 0.75s linear infinite;
    margin-left: 8px;
    vertical-align: middle;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* Position relative for the search container */
.app-search .position-relative {
    position: relative !important;
}

/* Wider search input field */
.app-search .form-control {
    width: 290px;
}

/* ============================================================================
   TABLE DENSITY PREFERENCES
   ============================================================================ */

body[data-table-density="dense"] .main-content table.dataTable tbody tr td,
body[data-table-density="dense"] .main-content table.dataTable thead tr th {
    padding: 0.25rem 0.5rem !important;
}

body[data-table-density="spacious"] .main-content table.dataTable tbody tr td,
body[data-table-density="spacious"] .main-content table.dataTable thead tr th {
    padding: 1.25rem 1rem !important;
}

/* ============================================================================
   QUILL RICH TEXT EDITOR
   Shared styling for all Quill editor instances (via RichTextEditor utility
   and the Notification-specific implementation).
   ============================================================================ */

/* --- Shared Quill Styles (apply to all editors) --- */

/* Override Quill's .ql-container { height: 100% } which breaks layout
   inside Bootstrap flex rows. These elements ARE the .ql-container. */
.quill-editor-container,
#notification-text-editor {
    height: auto !important;
}

.quill-editor-container .ql-toolbar.ql-snow,
#notification-text-editor .ql-toolbar.ql-snow {
    border-radius: 0.25rem 0.25rem 0 0;
    border-color: var(--bs-border-color);
    padding: 4px 8px;
}

.quill-editor-container .ql-container.ql-snow,
#notification-text-editor .ql-container.ql-snow {
    border-radius: 0 0 0.25rem 0.25rem;
    border-color: var(--bs-border-color);
    font-family: inherit;
}

.quill-editor-container .ql-editor,
#notification-text-editor .ql-editor {
    height: auto;              /* Override Quill's height:100% */
    min-height: 92px;
    max-height: 320px;
    overflow-y: auto;
    font-size: 0.875rem;
    line-height: 1.5;
    /* Native-textarea feel: users can drag the editor taller (clamped by max-height;
       tall-by-design fields lift the clamp via rows — see RichTextEditor.attach). */
    resize: vertical;
}

/* Fields authored with a large Rows value opt out of the auto-grow clamp entirely —
   RichTextEditor.attach stamps this class and a min-height from the rows attribute. */
.quill-editor-container .ql-editor.ql-tall-field {
    max-height: none;
}

.quill-editor-container .ql-editor.ql-blank::before,
#notification-text-editor .ql-editor.ql-blank::before {
    font-style: italic;
    color: var(--bs-secondary-color);
}

/* Read-only mode: no toolbar, subtle background */
.quill-editor-container.ql-readonly .ql-container.ql-snow,
#notification-text-editor.ql-readonly .ql-container.ql-snow {
    border-radius: 0.25rem;
}

/* Validation error state */
.quill-editor-container.ql-validation-error .ql-toolbar.ql-snow,
.quill-editor-container.ql-validation-error .ql-container.ql-snow,
#notification-text-editor.ql-validation-error .ql-toolbar.ql-snow,
#notification-text-editor.ql-validation-error .ql-container.ql-snow {
    border-color: var(--bs-danger);
}

/* --- Notification-specific: tighter dimensions for modal context --- */

#notification-text-editor,
#notification-compose-editor {
    margin-bottom: 4px;
}

#notification-text-editor .ql-editor,
#notification-compose-editor .ql-editor {
    height: auto !important;
    min-height: 92px !important;
    max-height: 200px !important;
}

/* --- Dark Mode --- */

[data-layout-mode="dark"] .ql-toolbar.ql-snow {
    background-color: var(--bs-gray-300);
    border-color: rgba(255, 255, 255, 0.1);
}

[data-layout-mode="dark"] .ql-container.ql-snow {
    background-color: var(--bs-input-bg);
    border-color: rgba(255, 255, 255, 0.1);
}

[data-layout-mode="dark"] .ql-editor {
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .ql-toolbar .ql-stroke {
    stroke: var(--bs-body-color);
}

[data-layout-mode="dark"] .ql-toolbar .ql-fill {
    fill: var(--bs-body-color);
}

[data-layout-mode="dark"] .ql-toolbar .ql-picker-label {
    color: var(--bs-body-color);
}

[data-layout-mode="dark"] .ql-toolbar button:hover .ql-stroke,
[data-layout-mode="dark"] .ql-toolbar button.ql-active .ql-stroke {
    stroke: #556ee6;
}

[data-layout-mode="dark"] .ql-toolbar button:hover .ql-fill,
[data-layout-mode="dark"] .ql-toolbar button.ql-active .ql-fill {
    fill: #556ee6;
}

/* Quill's own .ql-tooltip rule sets position:absolute with NO z-index, so the link
   popup sits in normal stacking order and sibling content below the editor (help
   text, validation messages) paints straight through it. Lift it above the editor's
   own content. Positioning is handled by passing `bounds` at construction — see the
   Quill init in notifications-common.js / views-common-richtext.js; without that it
   anchors to document.body and lands away from the link inside a modal. */
.ql-snow .ql-tooltip {
    z-index: 10;
}

[data-layout-mode="dark"] .ql-snow .ql-tooltip {
    background-color: var(--bs-gray-300);
    border-color: var(--bs-border-color);
    color: var(--bs-body-color);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

[data-layout-mode="dark"] .ql-snow .ql-tooltip input[type="text"] {
    background-color: var(--bs-input-bg);
    border-color: var(--bs-border-color);
    color: var(--bs-body-color);
}

/* Settings View Min-Height Fix */
.settings-view-min-height {
    min-height: calc(100vh - 180px);
}

/* Sidebar Menu Group Delineators */
.menu-title-line {
    display: flex !important;
    align-items: center;
    padding-right: 24px !important;
}

.menu-title-line::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(255, 255, 255, .06);
    margin-left: 10px;
}

.menu-title-centered-line {
    display: flex !important;
    align-items: center;
    justify-content: center;
    padding-right: 24px !important;
}

.menu-title-centered-line::before,
.menu-title-centered-line::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(255, 255, 255, .06);
}

.menu-title-centered-line::before {
    margin-right: 10px;
}

.menu-title-centered-line::after {
    margin-left: 10px;
}

/* The centered-line title is display:flex, which drops the whitespace-only
   text node between "Hey," and the <strong> name — restore the gap. The
   near-white pop is the serenity value; brand-incandescence.css lifts it
   to paper. */
.menu-title-centered-line strong {
    margin-left: 0.3em;
    color: #f8f9fa;
}

/* --- Sidebar section divider title brightness --- */
body[data-sidebar=dark] .menu-title {
    color: #8f97ad;
}

/* --- Sidebar divider line visibility --- */
.menu-title-line::after {
    background: rgba(255, 255, 255, .12);
}

.menu-title-centered-line::before,
.menu-title-centered-line::after {
    background: rgba(255, 255, 255, .12);
}

/* --- Sidebar tenant name: a quiet signature at the sidebar foot ---
   Light-blue accent is the serenity value; brand-incandescence.css
   restyles it amber with the same selector (later load wins the tie). */
body[data-sidebar=dark] .vertical-menu .sidebar-tenant-name {
    color: #50a5f1;
    letter-spacing: 0.05em;
}

/* The tenant name links to the company website when one is recorded. It must read as
   the same quiet signature, not as a link — so it inherits the color set above (in
   whichever theme won) and earns an underline only on hover. Color-agnostic on purpose:
   brand-incandescence recolors the parent and this follows without a parallel rule. */
/* Simplebar's wrapper is `width:inherit; height:inherit`, but the sidebar's
   [data-simplebar] is a flex:1 child with no explicit height — so height:inherit
   resolves against an ancestor and the wrapper overshoots its own flex-sized box,
   painting on top of the tenant footer BELOW it. The footer link then can't be
   clicked on the text itself (elementFromPoint returns .simplebar-wrapper), which
   reads as a link whose hit area is mysteriously offset. Pin the wrapper to its
   real container so it can't extend past the scroll region.
   Scoped to .vertical-menu: simplebar is used elsewhere with different sizing. */
.vertical-menu [data-simplebar] > .simplebar-wrapper {
    height: 100%;
    max-height: 100%;
}

.sidebar-tenant-name .sidebar-tenant-link {
    color: inherit;
    text-decoration: none;
}

.sidebar-tenant-name .sidebar-tenant-link:hover,
.sidebar-tenant-name .sidebar-tenant-link:focus-visible {
    color: inherit;
    text-decoration: underline;
}

/* --- Sidebar section icon tonal palette --- */
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-personal > a i {
    color: #7b8ec7;
}

body[data-sidebar=dark] #sidebar-menu ul li.nav-section-manage > a i {
    color: #6a9fb0;
}

body[data-sidebar=dark] #sidebar-menu ul li.nav-section-admin > a i {
    color: #b0a06a;
}

body[data-sidebar=dark] #sidebar-menu ul li.nav-section-incandera > a i {
    color: #b07a8a;
}

/* Preserve white icon on hover */
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-personal > a:hover i,
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-manage > a:hover i,
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-admin > a:hover i,
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-incandera > a:hover i {
    color: #ffffff;
}

/* Preserve active state (primary blue → white) */
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-personal.mm-active > a i,
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-manage.mm-active > a i,
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-admin.mm-active > a i,
body[data-sidebar=dark] #sidebar-menu ul li.nav-section-incandera.mm-active > a i {
    color: #ffffff !important;
}

/* Active section header — scale up the top-level parent menu item to 130% */
#sidebar-menu > ul > li > a.active-section-header {
    font-size: 16.9px;       /* 13px × 1.3 */
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
}
#sidebar-menu > ul > li > a.active-section-header i {
    font-size: 1.625rem;     /* 1.25rem × 1.3 */
}

/* ============================================================================
   PROFILE IMAGE UPLOAD
   ============================================================================ */

/* Cropper container */
.profile-image-cropper-container {
    max-height: 400px;
    overflow: hidden;
}

.profile-image-cropper-container img {
    display: block;
    max-width: 100%;
}

/* Dropzone */
.profile-image-dropzone {
    cursor: pointer;
    transition: border-color 0.2s ease, background-color 0.2s ease;
}

.profile-image-dropzone:hover,
.profile-image-dropzone-hover {
    border-color: #556ee6 !important;
    background-color: rgba(85, 110, 230, 0.05);
}

/* Profile avatar — larger image overlap into banner.

   The -46px lift is what makes the avatar sit over the welcome banner. It is a
   FIXED offset against a banner whose height is not fixed: the banner's left
   column is a `col-7`, so as the card narrows the aphorism wraps onto more lines
   and the banner grows DOWNWARD, while the avatar keeps rising the same 46px
   from a lower starting point. Measured on the practitioner profile: at 1920px
   the banner is 148px tall and the avatar clears the text; at an iPad's 1366px
   it is 116px and the avatar lands 14px INTO the aphorism's last line.

   The clamp keeps the intended overlap wherever there is room for it and gives it
   up before it can reach the text. Phones are excluded by the min-width below --
   there the banner column is full width, the aphorism has room, and the original
   lift is correct and already validated. */
#practitionerSummaryCard .profile-user-wid {
    margin-top: -46px;
}

/* Between sm and the desktop widths the banner is at its shortest relative to
   its text, which is exactly the band where the fixed lift collides. Halving it
   clears the aphorism while keeping the avatar visibly overlapping the banner. */
@media (min-width: 576px) and (max-width: 1399.98px) {
    #practitionerSummaryCard .profile-user-wid {
        margin-top: -22px;
    }
}

/* Camera overlay on avatar */
.profile-image-camera-overlay {
    position: absolute;
    bottom: 8px;
    right: 4px;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background-color: #556ee6;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    opacity: 0.8;
    transition: opacity 0.2s ease;
    text-decoration: none;
    border: 2px solid #fff;
}

.profile-image-camera-overlay:hover {
    opacity: 1;
    color: #fff;
}

[data-layout-mode="dark"] .profile-image-camera-overlay {
    border-color: var(--bs-gray-200);
}

[data-layout-mode="dark"] .profile-image-dropzone:hover,
[data-layout-mode="dark"] .profile-image-dropzone-hover {
    background-color: rgba(85, 110, 230, 0.1);
}

/* Clickable avatar images - view modal */
#practitioner-profile-avatar[data-viewable="true"],
#patient-profile-avatar {
    cursor: pointer;
    transition: opacity 0.2s ease;
}

#practitioner-profile-avatar[data-viewable="true"]:hover,
#patient-profile-avatar:hover {
    opacity: 0.8;
}

/* ============================================================================
   PROFILE IMAGE POPOVER (list views)
   ============================================================================ */

.popover.profile-image-popover {
    border: 1px solid rgba(0, 0, 0, 0.25) !important;
    border-radius: 0.5rem;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important;
    text-align:center;
}

.popover.profile-image-popover .popover-header {
    border-radius: 0.5rem 0.5rem 0 0;
    border-bottom: none;
    font-size: 0.75rem;
    padding: 0.35rem 0.5rem;
}

.popover.profile-image-popover .popover-body {
    padding: 0.4rem;
}

/* Fix arrow border gap — use BS5 CSS variables so the arrow matches the custom border.
   Also set per-placement selectors as fallback for older Bootstrap builds. */
.popover.profile-image-popover {
    --bs-popover-border-color: rgba(0, 0, 0, 0.25);
    --bs-popover-arrow-border: rgba(0, 0, 0, 0.25);
}

.popover.bs-popover-end.profile-image-popover .popover-arrow::before {
    border-right-color: rgba(0, 0, 0, 0.25) !important;
}

.popover.bs-popover-end.profile-image-popover .popover-arrow::after {
    border-right-color: #fff !important;
}

.popover.bs-popover-start.profile-image-popover .popover-arrow::before {
    border-left-color: rgba(0, 0, 0, 0.25) !important;
}

.popover.bs-popover-start.profile-image-popover .popover-arrow::after {
    border-left-color: #fff !important;
}

.popover.bs-popover-top.profile-image-popover .popover-arrow::before {
    border-top-color: rgba(0, 0, 0, 0.25) !important;
}

.popover.bs-popover-top.profile-image-popover .popover-arrow::after {
    border-top-color: #fff !important;
}

.popover.bs-popover-bottom.profile-image-popover .popover-arrow::before {
    border-bottom-color: rgba(0, 0, 0, 0.25) !important;
}

.popover.bs-popover-bottom.profile-image-popover .popover-arrow::after {
    border-bottom-color: #fff !important;
}

/* ============================================================================
   KEY LINKS FLYOUT
   ============================================================================ */

/* Flush the flyout against the header (no Popper positioning on manual toggle) */
#keylinks-menu.show {
    margin-top: 0;
}

/* Drop-target highlight during dragover */
.keylinks-drop-active {
    outline: 2px dashed #556ee6;
    outline-offset: -2px;
    background-color: rgba(85, 110, 230, 0.05);
    border-radius: 4px;
    min-height: 60px;
}

/* Link items — position relative for overlays */
.keylinks-item {
    position: relative;
}

/* Remove button — hidden by default, visible on hover */
.keylinks-item-remove {
    display: none;
    position: absolute;
    top: 2px;
    right: 2px;
    width: 18px;
    height: 18px;
    line-height: 16px;
    text-align: center;
    font-size: 14px;
    font-weight: bold;
    color: #dc3545;
    background-color: rgba(255, 255, 255, 0.9);
    border-radius: 50%;
    cursor: pointer;
    z-index: 1;
}

.keylinks-item:hover .keylinks-item-remove {
    display: block;
}

[data-layout-mode="dark"] .keylinks-item-remove {
    background-color: rgba(42, 48, 66, 0.9);
    color: #f46a6a;
}

/* Admin lock badge */
.keylinks-item-admin-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    font-size: 10px;
    color: #74788d;
    opacity: 0.6;
}

/* Empty state hint */
.keylinks-empty-hint {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 20px 10px;
    text-align: center;
    color: #74788d;
    font-size: 0.8rem;
    gap: 6px;
}

[data-layout-mode="dark"] .keylinks-empty-hint {
    color: var(--bs-gray-400);
}

/* Settings page — Key Links admin editor rows */
.keylinks-admin-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.keylinks-admin-row input {
    font-size: 0.85rem;
}

.keylinks-admin-row select {
    font-size: 0.85rem;
    width: auto;
    min-width: 140px;
}

.keylinks-admin-row .btn-remove-link {
    flex-shrink: 0;
}

/* ====================================================
   Notification Dropdown — Dark Mode Border Compensation
   The dark theme adds a 1px border to .dropdown-menu
   that doesn't exist in light mode. This narrows the
   content area, causing text to wrap and overflow.
   ==================================================== */

/* Widen the header notifications dropdown */
#notifications-container .dropdown-menu.dropdown-menu-lg {
    width: 335px;
}

[data-layout-mode="dark"] #notifications-container .dropdown-menu.dropdown-menu-lg {
    border-width: 0 !important;
}

/* MyNotifications / Invoices — white background on filter controls (light mode) */
#notification-type-filter-container .select2-selection--multiple,
#notification-priority-filter-container .select2-selection--multiple,
#status-filter-container .select2-selection--multiple,
#invoice-date-range-container .select2-selection--single {
    background-color: #fff;
}

[data-layout-mode="dark"] #notification-type-filter-container .select2-selection--multiple,
[data-layout-mode="dark"] #notification-priority-filter-container .select2-selection--multiple,
[data-layout-mode="dark"] #status-filter-container .select2-selection--multiple,
[data-layout-mode="dark"] #invoice-date-range-container .select2-selection--single {
    background-color: var(--bs-input-bg);
}

/* ====================================================
   DataTable Action Icons — Hover Scale
   Enlarges action-column icons on hover for clarity.
   ==================================================== */

table.dataTable td a:not(.btn) > i {
    transition: transform 0.15s ease;
    display: inline-block;
}

table.dataTable td a:not(.btn):hover > i {
    transform: scale(1.30);
}

/* ====================================================
   DataTable Cell Hyperlinks — Underline on Hover
   Applies only to text/badge navigation links, not to
   icon-only action buttons or Bootstrap .btn elements.
   ==================================================== */

table.dataTable td a.dt-link:hover,
table.dataTable td a.dt-link:hover .badge {
    text-decoration: underline !important;
}

/* ====================================================
   Forms Tree Grouping — Group Header Rows
   ==================================================== */

/* Base group header styling */
.group-header td {
    font-weight: 600;
    border-bottom: 1px solid #dee2e6;
    cursor: default;
    padding-top: 6px !important;
    padding-bottom: 6px !important;
    white-space: nowrap;
}

/* Patient-level header */
.group-patient td {
    background-color: #e8f0fe;
    font-size: 0.95rem;
    color: #1a3e72;
}

/* Referral-level header */
.group-referral td {
    background-color: #f0f4f8;
    font-size: 0.9rem;
    color: #2c5282;
}

/* Treatment Record-level header */
.group-tr td {
    background-color: #f7f9fb;
    font-size: 0.875rem;
    color: #3a6ea5;
}

/* Indentation spacers */
.group-indent-1 {
    display: inline-block;
    width: 20px;
}

.group-indent-2 {
    display: inline-block;
    width: 40px;
}

/* Toggle icon */
.group-toggle {
    cursor: pointer;
    display: inline-block;
    margin-right: 4px;
    transition: transform 0.15s ease;
}

.group-toggle i {
    font-size: 1.1rem;
    vertical-align: middle;
}

/* Collapsed data rows */
tr.group-collapsed {
    display: none !important;
}

/* Dark mode variants */
[data-layout-mode="dark"] .group-patient td {
    background-color: rgba(56, 103, 214, 0.15);
    color: #a4c4f4;
}

[data-layout-mode="dark"] .group-referral td {
    background-color: rgba(56, 103, 214, 0.10);
    color: #8ab4f0;
}

[data-layout-mode="dark"] .group-tr td {
    background-color: rgba(56, 103, 214, 0.06);
    color: #7aa8e8;
}

[data-layout-mode="dark"] .group-header td {
    border-bottom-color: var(--bs-gray-300);
}

/* ============================================================================
   SURVEY EDITOR — DARK MODE QUESTION CARDS
   Tone down the light-mode header/border colors so they blend with the dark theme.
   ============================================================================ */

[data-layout-mode="dark"] .question-card {
    border-color: rgba(255, 255, 255, 0.08);
}

[data-layout-mode="dark"] .question-card .card-header {
    background: rgba(255, 255, 255, 0.04);
}

[data-layout-mode="dark"] .question-card:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

[data-layout-mode="dark"] .question-type-config {
    border-top-color: rgba(255, 255, 255, 0.08);
}

[data-layout-mode="dark"] .drag-handle {
    color: rgba(255, 255, 255, 0.25);
}

[data-layout-mode="dark"] .drag-handle:hover {
    color: rgba(255, 255, 255, 0.5);
}

[data-layout-mode="dark"] .question-card .option-row .option-text[readonly] {
    background-color: rgba(255, 255, 255, 0.04) !important;
    border-color: rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.75);
}

/* ============================================================================
   PROFILE AVATAR LAZY LOADING
   Hide avatar images until loaded to prevent broken-image flash on slow networks.
   Images use inline onload/onerror handlers to add the .loaded class.
   ============================================================================ */

img.avatar-lazy {
    opacity: 0;
    transition: opacity 0.15s ease-in;
}

img.avatar-lazy.loaded {
    opacity: 1;
}

.centered-bx-control {
    position: relative;
    top: 1px;
}

/* Settings page bullet+label+input rows. Pre-modernization these used a
   flex `.row` with a Boxicon `<i class="bx bx-minus">` and negative margins
   to overlap the label; under Lucide that hack rendered the dash so small
   it disappeared. Replaced with a clean three-item flex layout: a fixed
   bullet column, a flexible label, and a sized input column. */
.setting-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.setting-bullet {
    flex: 0 0 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--bs-body-color);
}

.setting-bullet svg.lucide {
    width: 18px;
    height: 18px;
    vertical-align: 0;
}

.setting-label {
    flex: 0 0 360px;
    margin-bottom: 0;
}

/* Wide enough for the value AND the TouchSpin +/- stack that now sits inside
   this column rather than overhanging it (see the touchspin block below).
   The buttons take 2em ≈ 32px of the basis, so 106px leaves ~70px of field —
   the same usable width the bare 70px gave before the stack was brought back
   inside. The wide variant carries the Forms tab's 5-digit query limit. */
.setting-input {
    flex: 0 0 106px;
}

.setting-input-wide {
    flex-basis: 126px;
}

/* …and the phone. `flex: 0 0 360px` is shorthand for `flex-shrink: 0`, so
   the label is 360px wide at every viewport and the row's minimum width is
   28 + 12 + 360 + 12 + 70 = 482px. Against a 393px screen that is 89px of
   guaranteed overflow, which is why the numeric-input rows ran off the
   right edge while the toggle rows beside them (plain `.form-check
   .form-switch`, no fixed basis) wrapped correctly.

   The label becomes an ordinary flexible column here rather than stacking
   onto its own line: these are short-to-medium sentences, and keeping the
   input beside its label is what makes a settings list scannable. `1 1 auto`
   with `min-width: 0` lets it wrap to two or three lines and give the input
   its 70px; the bullet and input keep their fixed columns so the rows still
   line up with each other down the page.

   Applies to every tab — Referrals, Forms, Key Links, System and Modules all
   inline this same row shape (there is no shared partial), so a rule keyed
   on `.setting-row` is the only fix that reaches all of them at once. */
@media (max-width: 767px) {
    .setting-label {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* Align the bullet and input to the label's FIRST line once it wraps;
       centring a 3-line label against a 1-line input floats both mid-row. */
    .setting-row {
        align-items: flex-start;
    }

    .setting-bullet,
    .setting-input {
        margin-top: 0.25rem;
    }
}

/* ══════════════════════════════════════════════════════════════
   TouchSpin vertical buttons: give the stack a box of its own
   ══════════════════════════════════════════════════════════════

   The +/- stack hangs past the right edge on every touchspin in the app --
   Settings (Referrals / System / Forms tabs), the Practitioner create + edit
   modals, My Information, Form Sets. One cause, four surfaces.

   bootstrap-touchspin injects, in place, around the input:

       div.input-group.bootstrap-touchspin
         |-- input.form-control
         `-- span.input-group-btn-vertical      <- the +/- stack
               |-- button.bootstrap-touchspin-up
               `-- button.bootstrap-touchspin-down

   Three stylesheets disagree about where that span goes. This was measured in
   a harness at 393px rather than reasoned about, and the reasoning would have
   got it wrong:

     * the library sets `.input-group-btn-vertical { position: absolute;
       right: 0 }` and declares `position: relative` on NOTHING, so `right: 0`
       resolves against some distant positioned ancestor;
     * that span is absolutely positioned with no width and no `left`, and its
       only children are themselves absolutely positioned -- so it shrink-wraps
       to **width: 0**;
     * app.min.css adds `.input-group-btn-vertical .btn { left: 100% !important;
       right: 0 !important }`, an RTL mirror rule that leaked out of its
       `[dir=rtl]` block. `left: 100%` of a ZERO-width span is 0px, so each
       button starts exactly at the span origin and extends its own 27px past it.

   Measured before the fix: span at x=374 with width=0, sitting on the input
   group's right edge; the up button running from 374 to 401 against a 393px
   viewport. The buttons were never merely "a bit too far right" -- they were
   being laid out against a box that had collapsed to nothing.

   So the span gets a real box (definite width, pinned to the input group's own
   edges) and the leaked `left: 100%` is neutralised so the buttons fill that
   box instead of starting where it ends. The `!important` on left/right is
   required only because the declaration being overridden is itself
   `!important`; nothing else in this block needs it.

   Applied at every width, not inside a media query. The geometry is equally
   wrong on a desktop -- it just overhangs into card whitespace there instead
   of off a screen edge, so nobody filed it. Fixing it globally means one
   behaviour to reason about at all four resolutions, rather than a phone
   special case that silently disagrees with the tablet beside it. */
.bootstrap-touchspin.input-group {
    position: relative;

    /* One number, declared once, used by both the button box and the input's
       reserved padding below. It must be a LENGTH, not an em: the library's own
       `.btn { width: 2em }` resolves against the BUTTON's font-size, while any
       padding we add to the input resolves against the INPUT's. Those two are
       not the same (13px vs 16px on the practitioner modal), so an em-based
       pair silently disagrees -- which is exactly the bug that shipped: 2.5em
       of input padding reserved 32.5px for a stack that is actually 28px, and
       on a `col-sm-1` field that left 3.5px of usable text width. Measured. */
    --touchspin-buttons: 28px;

    /* Narrow columns need a floor, and this is where the regression actually
       bit. The "+/- miles" field is authored as a `col-sm-1` -- 66px, of which
       the input got 48px. Even with the padding corrected to 32px there is only
       ~4px of text left, so a floor is needed regardless of how tight the
       reservation is: the field must fit its digits AND the button stack, or
       the value is invisible however carefully the padding is calculated.

       On the input group rather than the column, so the control can outgrow a
       too-small column without disturbing the grid -- the column stays
       `col-sm-1` for every other purpose, and only this control refuses to
       shrink below a legible size. 2 digits + the stack + padding = ~72px. */
    min-width: 72px;
}

.bootstrap-touchspin.input-group > .input-group-btn-vertical {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: auto;
    width: var(--touchspin-buttons);
    height: auto;
}

.bootstrap-touchspin.input-group > .input-group-btn-vertical > .btn {
    left: 0 !important;
    right: 0 !important;
    width: 100% !important;
}

/* The buttons are absolutely positioned and so occupy no space in the flex
   flow: the input expands to fill its whole column, and its digits would
   render underneath them. Reserve exactly the stack's width plus a 4px gap --
   no more, because some of these fields are genuinely tiny.

   `padding-right` only; the left padding stays at Bootstrap's default so short
   values still sit where the eye expects them. */
.bootstrap-touchspin.input-group > .form-control {
    padding-right: calc(var(--touchspin-buttons) + 4px);
}

/* The "± miles" label and its input carry inline `margin-left: -26px` / `-6px`
   in three views (Practitioners _Edit + _Create, Patients _Create). Those are
   desktop nudges that pull the label toward the touchspin field mid-row, and
   they are harmless while the label is a `col-sm-2` sitting between two other
   columns.

   Below `sm` the columns collapse to full width, the label becomes a block
   starting at the row's left gutter, and −26px drags its left edge outside the
   row and off the screen — which is the clipped "± miles" in the field report.
   The input's −6px does the same on a smaller scale.

   Zeroed only below the breakpoint, and only for this control, so the desktop
   alignment those nudges were tuned for is untouched. `!important` is required
   because they are inline styles. Keyed on the aria-label rather than the
   per-view id, since the three views use three different ids for the same
   field. */
@media (max-width: 575.98px) {
    label[for$="-serviceareaextracoverage-input"],
    [aria-label="ServiceAreaExtraCoverage"] {
        margin-left: 0 !important;
    }
}


/* Repeater delete buttons: the legacy `padding: 9px 10px 5px` (asymmetric
   top/bottom) was a Boxicon-baseline-drop compensation that's no longer
   needed under Lucide. Normalize to a symmetric padding and reset the
   global svg.lucide vertical-align so the trash glyph centers in the
   button. The `.btn .centered-bx-control` reset covers the small "remove"
   buttons that used the legacy 1px-down nudge — they don't need it now. */
.btn[data-repeater-delete]:not(.btn-sm),
.btn[data-nested-repeater-delete]:not(.btn-sm),
.btn.remove-repeater-item:not(.btn-sm) {
    padding: 7px 10px;
}

.btn[data-repeater-delete]:not(.btn-sm) svg.lucide,
.btn[data-nested-repeater-delete]:not(.btn-sm) svg.lucide,
.btn.remove-repeater-item:not(.btn-sm) svg.lucide,
.btn:not(.btn-sm) .centered-bx-control {
    vertical-align: middle;
    top: 0;
}

/* Small repeater trash buttons (Emergency Contacts modal: phone/email/
   address sub-rows, POA clear-all). Nudge them 1px right so they line up
   with the column edges of fields above; the .me-1 (4px) gap stays. */
.btn-sm[data-nested-repeater-delete],
.btn-sm.poa-clear-all {
    position: relative;
    right: -0.05em;
}

/* ══════════════════════════════════════════════════════════════
   Repeater rows: let the columns actually stack on a phone
   ══════════════════════════════════════════════════════════════

   Contact / Address / Emergency-Contact repeaters render their row as

       <div class="d-flex flex-row mb-2">
           <div class="col-sm-6">…value…</div>
           <div class="col-sm-3 ps-4">…type…</div>
           <label class="mb-3 ps-2">&nbsp;</label>
           <div class="form-switch mt-2">…Preferred…</div>
           <div class="ms-auto">…trash…</div>
       </div>

   and that first class is the whole bug. `.d-flex.flex-row` is
   `flex-wrap: nowrap`, so the row is forbidden from ever breaking. Below
   576px the `col-sm-*` children DO collapse to `width: 100%` exactly as
   Bootstrap intends — but with wrapping off they cannot take that width,
   so all five children are crushed onto one line: the phone number and the
   type select each truncate mid-value ("1-(815)-600-15", "M⌄"), and the
   trailing switch + trash button are pushed past the modal's edge. The
   modal is `overflow: hidden` like the rest of the shell, so they are
   clipped rather than scrollable and the delete button is unreachable.

   The sibling repeater one card over — Edit Insurance (`_EditPayors.cshtml`)
   — is built on `<div class="row">` + `col-md-*` and stacks perfectly at the
   same width, with no CSS help at all. That is the proof this is a wrapper
   bug and not a column-width one: `.row` carries `flex-wrap: wrap`,
   `.flex-row` does not. So the fix is to give these rows the one property
   `.row` would have given them, rather than to re-pick every column width.

   The markup is duplicated per surface -- there is no shared row partial --
   so the rule is keyed on four row hooks that between them cover every copy:

     * `[data-repeater-item]`       jquery.repeater: Patients contact +
                                    address, Practitioners, Companies (Edit)
     * `[data-nested-repeater-item]` Emergency Contacts and its Portal twin
     * `.repeater-item`             Practitioners' My Information, which
                                    predates both engines and stamps its rows
                                    from `__INDEX__` templates in the same file
     * `.d-flex.flex-row:has(> [class*="col-sm-"])`
                                    the CREATE modals (New Practitioner, New
                                    Patient), which have NO repeater hook at
                                    all -- a new record has no existing
                                    contacts, so they render one static row
                                    each and never load the repeater engine.

   That fourth hook is why the create modals were missed the first time: every
   other selector here needs an ancestor that simply is not in their markup,
   and a descendant selector on an absent ancestor fails silently. Matching by
   SHAPE instead (a flex row whose direct children are grid columns) reaches
   them without inventing a class. It is narrow in practice -- across all of
   Views/ only three files carry a hookless `.d-flex.flex-row`, two of which
   are exactly these modals; the third (PractitionerSummary) has no `col-*`
   children and so cannot match.

   Two DOM shapes exist and both are matched: on the phone/email rows the item
   element IS the flex row, on the address rows it CONTAINS one (or two).

   Above the breakpoint nothing changes: the columns fit on one line, so a
   wrapping container wraps nowhere and the desktop row is byte-identical. */
@media (max-width: 575.98px) {
    /* Row hooks. `.d-flex.flex-row` is repeated in each selector because the
       repeaters also contain non-row flex children (button clusters, the PoA
       block) that must keep their own layout. */
    [data-repeater-item] > .d-flex.flex-row,
    [data-nested-repeater-item].d-flex.flex-row,
    [data-nested-repeater-item] > .d-flex.flex-row,
    .repeater-item > .d-flex.flex-row,
    .modal .d-flex.flex-row:has(> [class*="col-sm-"]) {
        flex-wrap: wrap;
        row-gap: 0.5rem;
    }

    /* Wrapping alone was not enough, and the address rows are where that shows.
       -----------------------------------------------------------------------
       Once the row can break, each child still claims its authored percentage
       -- `col-sm-2` for the type select, `col-sm-5` for line 1, `col-sm-3` for
       city -- and each carries a `ps-2` or `ps-4` desktop gutter. The result is
       the staircase in the field report: Type and Line 1 pair up on one line,
       Line 2 drops to its own line indented 1.5rem, City runs wide, State sits
       narrower beside it, Zip is indented again. Every field a different width
       and a different left edge.

       At this width there is no column model worth preserving -- one field per
       line, all flush to the same edge, is the only layout that reads. So the
       percentages and the gutters are both dropped for EVERY column child of a
       repeater row, not just the breakpoint-less `col-N` ones handled before:
       `col-sm-*` collapsing to 100% on its own is Bootstrap doing its job and
       would be fine, but it does nothing about `ps-2`/`ps-4`, which is half of
       what makes the staircase. Zeroing both in one rule is what squares the
       left edges.

       `[class*="col-"]` is broad on purpose -- it catches `col-sm-5`, bare
       `col-5`, and the `col-md-*` variants in My Information alike -- and it is
       confined to repeater rows, so nothing else in the app sees it. */
    [data-repeater-item] > .d-flex.flex-row > [class*="col-"],
    [data-nested-repeater-item].d-flex.flex-row > [class*="col-"],
    [data-nested-repeater-item] > .d-flex.flex-row > [class*="col-"],
    .repeater-item > .d-flex.flex-row > [class*="col-"],
    .modal .d-flex.flex-row:has(> [class*="col-sm-"]) > [class*="col-"] {
        flex: 0 0 100%;
        width: 100%;
        max-width: 100%;

        /* `!important` on the padding only, and not by preference: `.ps-2` and
           `.ps-4` are Bootstrap SPACING UTILITIES, and every utility in that
           family ships as `padding-left: .5rem !important`. A plain
           declaration here cannot win at any specificity, so the indents
           survived the stack — measured at 393px as line 1 flush, line 2 in by
           8px and the zip in by 24px, which is the staircase itself. The flex
           and width declarations above need no such help; nothing marks those
           important. */
        padding-left: 0 !important;
        padding-right: 0 !important;
    }

    /* The create modals additionally hard-code pixel widths INLINE
       (`style="width:371px"` on the address/phone value columns,
       `style="width:180px"` on the state select -- Practitioners _Create and
       Patients _Create). Element styles beat any stylesheet selector, so the
       `width: 100%` above cannot reach them and those two columns would stay
       at their desktop pixel width inside a 393px modal, re-creating the
       squeeze for the very fields the rule was meant to free.

       `max-width` rather than fighting `width` with `!important`: the inline
       width stays declared but can no longer exceed the row, which is the
       minimum intervention and leaves the desktop value untouched above the
       breakpoint. */
    .modal .d-flex.flex-row:has(> [class*="col-sm-"]) > [style*="width"] {
        max-width: 100% !important;
    }

    /* The Preferred switch and the trash button carry no column class, so the
       rule above cannot reach them. Pairing them on one line is the right
       shape -- a switch and a 40px icon button do not each deserve a full row
       -- and `order` is what puts that pair LAST.

       Without the ordering the address row reads: type, line 1, line 2,
       Preferred, city, state, zip, delete. The switch lands mid-address because
       source order is authored for a single-line desktop row, where left-to-
       right position is all that matters; stacked, that same order becomes
       READING order and has to be stated deliberately. Both get a high `order`
       so they follow every field regardless of how many fields the row has. */
    [data-repeater-item] > .d-flex.flex-row > .form-switch,
    [data-nested-repeater-item].d-flex.flex-row > .form-switch,
    [data-nested-repeater-item] > .d-flex.flex-row > .form-switch,
    .repeater-item > .d-flex.flex-row > .form-switch,
    .modal .d-flex.flex-row:has(> [class*="col-sm-"]) > .form-switch {
        order: 90;
        flex: 1 1 auto;
        min-width: 0;
        margin-top: 0 !important;   /* neutralise the inline -1px desktop nudge */
    }

    [data-repeater-item] > .d-flex.flex-row > .ms-auto,
    [data-nested-repeater-item].d-flex.flex-row > .ms-auto,
    [data-nested-repeater-item] > .d-flex.flex-row > .ms-auto,
    .repeater-item > .d-flex.flex-row > .ms-auto {
        order: 91;
        flex: 0 0 auto;
    }

    /* In the nested (Emergency Contacts / Portal) rows the switch and the
       delete button are each wrapped in a `col-1` instead of sitting bare, so
       the full-width column rule above has already given each one its own line.
       Pull them back to natural width and park them together on the last line,
       matching the shape the top-level rows get from the two rules above.
       `:has()` is Android Chrome/Edge 105+ (2022) and already relied on
       elsewhere in this file; where it is unsupported the pair simply keeps a
       line each -- stacked and usable, just taller. */
    [data-nested-repeater-item].d-flex.flex-row > [class*="col-"]:has(.form-switch),
    [data-nested-repeater-item] > .d-flex.flex-row > [class*="col-"]:has(.form-switch) {
        order: 90;
        flex: 1 1 auto;
        width: auto;
        max-width: none;
    }

    [data-nested-repeater-item].d-flex.flex-row > [class*="col-"]:has([data-nested-repeater-delete]),
    [data-nested-repeater-item] > .d-flex.flex-row > [class*="col-"]:has([data-nested-repeater-delete]) {
        order: 91;
        flex: 0 0 auto;
        width: auto;
        max-width: none;
        margin-left: auto;
    }

    /* The `<label class="mb-3 ps-2">&nbsp;</label>` spacer is a desktop
       alignment shim sitting between the type select and the switch. On a
       wrapped row it becomes a line of its own holding a single space, so it
       is dropped rather than left to open a gap nobody asked for.

       Matched as "a direct-child label with no `for`", not `:empty` -- the
       `&nbsp;` is a text node, so these labels are not empty and `:empty`
       would silently match nothing. Every real label in these rows either
       labels a control by `for` or is nested inside the switch/field wrapper
       rather than being a direct child of the flex row, so the negation is
       exact rather than merely close. */
    [data-repeater-item] > .d-flex.flex-row > label:not([for]),
    [data-nested-repeater-item] > .d-flex.flex-row > label:not([for]),
    .repeater-item > .d-flex.flex-row > label:not([for]),
    .modal .d-flex.flex-row:has(> [class*="col-sm-"]) > label:not([for]) {
        display: none;
    }

    /* One hard-coded desktop shim that survives the stack and would push the
       row ~175px off-screen on its own (`_EditContactPerson.cshtml` address
       row). It is inline, so it needs the specificity to match. */
    [data-nested-repeater-item] > .d-flex.flex-row > .col-1[style*="padding-left"] {
        padding-left: 0 !important;
    }

    /* NOTE: there was a `margin-top: -0.5rem` rule here, collapsing the seam
       between two sibling flex rows inside one repeater item (address line 1/2
       then city/state/zip). It was written to close what looked like a double
       gap where the first row's `mb-2` met the second's — and it was wrong.

       Measured at 393px, the gap between every pair of stacked fields WITHIN a
       row is 8px (the `row-gap: 0.5rem` above). Across the row boundary the
       negative margin made it 0px, so the seam was visibly tighter than the
       rhythm on either side of it — fields appeared glued together at exactly
       one point per group. Removing it restores 8px there, matching every
       other gap.

       The double gap the rule was correcting for is a DESKTOP condition: when
       the two rows sit side by side as authored, their margins do stack. Once
       the rows wrap, `row-gap` owns the spacing and the margins never meet.
       The correction was real, but it did not apply in the band it was
       scoped to.

       It also reached further than intended. The same two-sibling-rows shape
       carries genuine GROUP boundaries elsewhere — License # then its dates,
       Other Requirements then its dates (Practitioner Profile) — where the
       seam is meaningful and closing it ran the groups together. There is no
       structural difference between the two cases to select on, which is the
       second reason not to reintroduce this: any version of the rule that
       tightens the address seam necessarily tightens those too. */

    /* Power of Attorney block (Emergency Contacts modal).
       -------------------------------------------------------------------
       Same `d-flex flex-row` bug as the field rows, but this block sits in a
       `.row.mb-3.ms-2` wrapper two levels down, so none of the `>` rules above
       reach it. Measured on the live modal at 393px: the four checkboxes
       (General / Health / Financial / Living Will) squeezed to 73/64/79/59px
       with "Living Will" wrapping to two lines, and the clear-all button
       sitting exactly ON the modal's right edge as the widest thing in it.

       Scoped by CONTENT (`:has(.poa-check-label)`) rather than by a repeater
       ancestor. The first version of this rule keyed on
       `[data-nested-repeater-item]` and silently matched nothing: the PoA block
       belongs to the OUTER `[data-repeater-item]` -- whose class is
       `.contactperson-item` -- while `data-nested-repeater-item` marks only the
       phone/email/address sub-rows beside it. A descendant selector on the
       wrong ancestor fails quietly, which is exactly the kind of thing the
       live-DOM measurement catches and reading the markup does not.
       `.poa-check-label` is carried by every POA checkbox label in the app and by
       nothing else, so matching on it is both sufficient and exact. It now marks
       three blocks, not one: the Contact Person modal this rule was written for,
       plus the POA groups on Referrals/New and Patients/New, which were given the
       same flex-row + col-* + label hooks so they inherit this grid rather than
       growing a second copy of it.

       Two columns rather than one full-width stack: these are short binary
       labels, not fields, and four of them stacked is a lot of height in an
       already long modal. 50% with the gutters zeroed gives a tidy 2x2 grid. */
    .d-flex.flex-row:has(.poa-check-label) {
        flex-wrap: wrap;
        row-gap: 0.25rem;
    }

    .d-flex.flex-row:has(.poa-check-label) > [class*="col-"] {
        flex: 0 0 50%;
        width: 50%;
        max-width: 50%;
        padding-left: 0 !important;
        padding-right: 0.5rem !important;
    }

    /* The clear-all button's column holds no checkbox, so the 50% share above
       would strand it half-width on a line of its own. It takes the full width
       and keeps its `float-end`, landing under the grid at the right edge. */
    .d-flex.flex-row:has(.poa-check-label) > [class*="col-"]:has(.poa-clear-all) {
        flex: 0 0 100%;
        width: 100%;
        max-width: 100%;
        padding-right: 0 !important;
    }

    /* The per-type attachment strip (upload button, truncated filename link,
       remove button) sits inside each checkbox column. Its filename anchor is
       capped at an inline `max-width: 100px` for the desktop 16% column; at 50%
       of a phone there is more room than that, and the strip was running off
       the card because its three items could not wrap. */
    .poa-attachment-container {
        flex-wrap: wrap;
    }

    .poa-attachment-container .poa-doc-download {
        max-width: 100% !important;
        min-width: 0;
    }
}

/* ── POA checkbox grid: the same 2x2 up through the tablet band ──
   The rule above gives the four POA checkboxes a 2x2 grid below 576px. Between
   576px and the desktop widths they revert to `col-sm-3` — four across — and
   that is too tight long before the phone breakpoint catches it.

   The arithmetic: these blocks sit in a `col-lg-6` inside the wizard's
   `col-lg-9`. At an iPad's 1366px that half-column is ~470px, so each of the
   four cells gets ~117px. A `.form-check` spends ~24px of that on the checkbox
   and its indent, leaving ~93px for the label — and "Financial" alone needs
   more than that at the body font size. The labels then run under the NEXT
   checkbox, which is exactly what the report shows: "Gener☐Health☐Financ☐l
   Living Will", with Living Will wrapping to a second line.

   Two across at this width gives each label ~235px, which every one of the four
   clears comfortably. The upper bound is 1399.98px to match the sidebar-toolbox
   band directly above — the same "narrower than a real desktop, wider than a
   phone" range, kept in step deliberately so the two do not drift apart. From
   1400px the four-across row has the room it was designed for and is untouched.

   No lower bound is needed: below 576px the phone rule already applies the same
   50% treatment, so the two overlap harmlessly rather than fighting. */
@media (min-width: 576px) and (max-width: 1399.98px) {
    .d-flex.flex-row:has(.poa-check-label) {
        flex-wrap: wrap;
        row-gap: 0.25rem;
    }

    .d-flex.flex-row:has(.poa-check-label) > [class*="col-"] {
        flex: 0 0 50%;
        width: 50%;
        max-width: 50%;
    }

    /* The clear-all button's column holds no checkbox; full width keeps it from
       being stranded half-width on a line of its own. Mirrors the phone rule. */
    .d-flex.flex-row:has(.poa-check-label) > [class*="col-"]:has(.poa-clear-all) {
        flex: 0 0 100%;
        width: 100%;
        max-width: 100%;
    }
}

/* ── Practitioner "Other Requirements": keep the Insurance action pair together ──
   The Insurance Coverage repeater lays its second line out as a `d-flex flex-row`
   of two `col-sm-3` date pickers plus an `.ms-auto` div holding the attachment
   (paperclip) and delete (trash) buttons.

   Inside a flex row `col-sm-*` behaves as a flex BASIS, not a grid track, and
   flex items may shrink below their content by default -- so in this band the
   action pair was what gave, dropping the trash button onto a second line under
   the paperclip and doubling the row height.

   `flex-shrink: 0` on the action group is the fix: it is a fixed-size pair of
   icon buttons and should never be the thing that yields. `min-width: 0` on the
   date pickers beside it is what lets flex shrink THEM instead (a flex item's
   default min-width is auto, i.e. its content), so the row absorbs the pressure
   where absorbing it is harmless.

   The Health Requirement repeater is deliberately absent from this rule: it was
   re-laid-out in the markup as a real two-row `.row`/`.col-*` grid (see
   _MyInformation.cshtml), which removes the negotiation entirely rather than
   constraining it. Insurance keeps the flex layout, so it keeps this guard.

   Same 1399.98px ceiling as the sibling rules above: at real desktop widths the
   row has the room it was built for. */
@media (max-width: 1399.98px) {
    #insurance-repeater .d-flex.flex-row > .ms-auto {
        flex: 0 0 auto;
        flex-shrink: 0;
        white-space: nowrap;
    }

    #insurance-repeater .d-flex.flex-row > [class*="col-"] {
        min-width: 0;
    }
}

/* ── Practitioner summary card: recover the hardcoded indents ──
   The stats half of the summary card (Notifications / Calendar) is built as
   `.pt-4.ms-5` wrapping a flex row whose two blocks each carry `.ms-4`. That is
   3rem + 1.5rem + 1.5rem = 6rem of fixed left margin, spent inside whatever the
   card has — and the card is a `col-xl-4`, which commits at 1200px. At an iPad's
   1366px the card measures ~400px, so 96px of it is margin before any content is
   drawn, and the two stat blocks are squeezed against the card's right edge.

   The labels themselves were shortened at the source instead of here ("Calendar"
   rather than "Upcoming events", and the trailing arrows removed from both
   buttons — see PractitionerSummary/Default.cshtml). This rule handles the other
   half: the indents, which are decorative spacing on a card that has run out of
   room. Desktop keeps the original utilities untouched above the ceiling. */
@media (max-width: 1399.98px) {
    #practitionerSummaryCard .card-body .pt-4.ms-5 {
        margin-left: 1rem !important;
    }

    #practitionerSummaryCard .card-body .d-flex.flex-row > .ms-4 {
        margin-left: 0 !important;
    }

    /* With the indents gone the two stat blocks need their own separation, and a
       gap gives it without re-introducing a left margin on the first one. */
    #practitionerSummaryCard .card-body .d-flex.flex-row {
        gap: 1.25rem;
    }
}

/* ══════════════════════════════════════════════════════════════
   Horizontal form labels: stop right-aligning once they stop being a column
   ══════════════════════════════════════════════════════════════

   The app's modal forms are built on Bootstrap's horizontal-form idiom:

       <label class="col-sm-3 col-form-label text-end">Title</label>
       <div class="col-sm-9"><input class="form-control"></div>

   Right-alignment is correct while the label IS a 3-wide column — it puts
   the text against the gutter its input starts at. Below `sm` the column
   collapses to full width and the label sits ABOVE its input, at which point
   `text-end` strands it against the opposite edge of the screen from
   everything it labels: "Title", "Type", "Priority" and "Text" all reading
   flush right above left-aligned controls, which is the "labels have picked
   up a wide left margin" symptom on the View Notification modal.

   `text-end` is a Bootstrap utility with no responsive variant applied here
   (the responsive one, `text-sm-end`, is what the markup wanted), so the fix
   is to neutralise it below the same breakpoint the column collapses at.
   Scoped to labels that are BOTH `col-form-label` and a `col-sm-*` column, so
   it only fires where the right-alignment was column-relative in the first
   place — a deliberately right-aligned label elsewhere is untouched.

   One rule covers all 90 occurrences across the 14 view files that use this
   idiom (Notifications view + edit, Companies, Patients, Practitioners,
   Referrals, RolesAdmin, Reference), so the Notifications fix lands on every
   sibling modal that shares the shape rather than only the reported one. */
@media (max-width: 575.98px) {
    label.col-form-label.text-end[class*="col-sm-"] {
        text-align: left !important;
    }
}

/* Read-only rich-text panes: `.form-control` on a DIV is how the app renders
   saved HTML in a box that matches the inputs around it (View Notification's
   "Text", among others). Bootstrap styles `.form-control` for INPUTS, which
   cannot contain an unbreakable token wider than themselves — a div can, and
   these panes routinely hold one: an 837P batch filename
   (`837P_Availity_200000002_20260808002253.txt`) has no space, hyphen or
   other break opportunity anywhere in 43 characters, so it renders at full
   width and pushes out of the pane and the modal behind it.

   `anywhere` rather than `break-word`: the pane's content is mostly prose,
   and both settings leave prose alone — but only `anywhere` contributes to
   min-content, which is what stops the pane from being INFLATED by the token
   before any wrapping is attempted. Scoped to the div-as-control case; real
   inputs are unaffected (and unaffectable). */
.form-control.h-auto {
    overflow-wrap: anywhere;
}

/* Mini-chart stat cards (sparkline cards on list pages) */
#mini-charts-container .mini-stats-wid,
#submissions-charts-container .mini-stats-wid {
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.00);
}

#mini-charts-container .mini-stats-wid .card-body,
#submissions-charts-container .mini-stats-wid .card-body {
    padding: 0.85rem 1rem;
}

#mini-charts-container .mini-stats-wid h4,
#submissions-charts-container .mini-stats-wid h4 {
    font-size: 1.15rem;
}

/* Compact footer on horizontal sparkline cards */
.mini-stats-wid .border-top.py-3 {
    padding-top: 5px !important;
    padding-bottom: 5px !important;
}

/* Radial bar chart table — compact rows */
#mini-charts-container .table td,
#submissions-charts-container .table td {
    padding: 0.5rem 0.5rem;
}

/* TUI CALENDAR — Brighten icons in dark mode (edit, delete, etc. are dark PNGs) */
[data-layout-mode="dark"] .tui-full-calendar-icon {
    filter: invert(0.85);
}

/* Calendar detail popup — wider to reduce title wrapping on long patient names.
   TUI sets an inline width on .popup-container, so min-width is needed to override it. */
.tui-full-calendar-popup-detail {
    width: 420px !important;
}
.tui-full-calendar-popup-detail .tui-full-calendar-popup-container {
    min-width: 420px !important;
}

/* Calendar popup — more pronounced drop-shadow, rounded corners, arrow colors */
.tui-full-calendar-popup-container {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25) !important;
    border-color: #ced4da !important;
    border-radius: 6px !important;
}

/* Arrow border (outer triangle) — match popup border color per direction */
/* Nudge 1px toward popup so the base aligns with the popup's outer border */
.tui-full-calendar-arrow-top .tui-full-calendar-popup-arrow-border { border-bottom-color: #ced4da !important; top: -8px !important; }
.tui-full-calendar-arrow-bottom .tui-full-calendar-popup-arrow-border { border-top-color: #ced4da !important; bottom: -8px !important; }
.tui-full-calendar-arrow-left .tui-full-calendar-popup-arrow-border { border-right-color: #ced4da !important; left: -8px !important; }
.tui-full-calendar-arrow-right .tui-full-calendar-popup-arrow-border { border-left-color: #ced4da !important; right: -8px !important; }

/* Arrow fill (inner triangle) — match popup background */
.tui-full-calendar-arrow-top .tui-full-calendar-popup-arrow-fill { border-bottom-color: #fff !important; }
.tui-full-calendar-arrow-bottom .tui-full-calendar-popup-arrow-fill { border-top-color: #fff !important; }
.tui-full-calendar-arrow-left .tui-full-calendar-popup-arrow-fill { border-right-color: #fff !important; }
.tui-full-calendar-arrow-right .tui-full-calendar-popup-arrow-fill { border-left-color: #fff !important; }

/* Dark mode */
[data-layout-mode="dark"] .tui-full-calendar-popup-container {
    box-shadow: 0 4px 20px rgba(180, 190, 210, 0.25) !important;
    background-color: var(--bs-card-bg) !important;
    border-color: var(--bs-border-color) !important;
}

[data-layout-mode="dark"] .tui-full-calendar-arrow-top .tui-full-calendar-popup-arrow-border { border-bottom-color: var(--bs-border-color) !important; top: -6px !important; }
[data-layout-mode="dark"] .tui-full-calendar-arrow-bottom .tui-full-calendar-popup-arrow-border { border-top-color: var(--bs-border-color) !important; bottom: -6px !important; }
[data-layout-mode="dark"] .tui-full-calendar-arrow-left .tui-full-calendar-popup-arrow-border { border-right-color: var(--bs-border-color) !important; left: -6px !important; }
[data-layout-mode="dark"] .tui-full-calendar-arrow-right .tui-full-calendar-popup-arrow-border { border-left-color: var(--bs-border-color) !important; right: -6px !important; }

[data-layout-mode="dark"] .tui-full-calendar-arrow-top .tui-full-calendar-popup-arrow-fill { border-bottom-color: var(--bs-card-bg) !important; }
[data-layout-mode="dark"] .tui-full-calendar-arrow-bottom .tui-full-calendar-popup-arrow-fill { border-top-color: var(--bs-card-bg) !important; }
[data-layout-mode="dark"] .tui-full-calendar-arrow-left .tui-full-calendar-popup-arrow-fill { border-right-color: var(--bs-card-bg) !important; }
[data-layout-mode="dark"] .tui-full-calendar-arrow-right .tui-full-calendar-popup-arrow-fill { border-left-color: var(--bs-card-bg) !important; }

/* Calendar "N more" link — subtle badge so it stands out among colorful events */
.tui-full-calendar-weekday-grid-line .tui-full-calendar-weekday-grid-more-schedules {
    color: #556ee6 !important;
    background-color: #eff2f7 !important;
    border-radius: 3px !important;
    padding: 0 6px !important;
    font-size: 10.5px !important;
    line-height: 20px !important;
    height: 20px !important;
    margin-top: 2px !important;
}

[data-layout-mode="dark"] .tui-full-calendar-weekday-grid-line .tui-full-calendar-weekday-grid-more-schedules {
    color: #8da2e6 !important;
    background-color: rgba(85, 110, 230, 0.15) !important;
}

/* Calendar "N more" popup — dark mode */
[data-layout-mode="dark"] .tui-full-calendar-month-more {
    background-color: var(--bs-card-bg) !important;
    border: 1px solid var(--bs-border-color) !important;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) !important;
}

[data-layout-mode="dark"] .tui-full-calendar-month-more-title {
    background-color: var(--bs-card-bg) !important;
}

[data-layout-mode="dark"] .tui-full-calendar-month-more-title-day-label,
[data-layout-mode="dark"] .tui-full-calendar-month-more-title-day {
    color: var(--bs-body-color) !important;
}

[data-layout-mode="dark"] .tui-full-calendar-month-more-list {
    background-color: var(--bs-card-bg) !important;
}

[data-layout-mode="dark"] .tui-full-calendar-month-more-close {
    color: var(--bs-body-color) !important;
}

/* Calendar "today" indicator — must exceed specificity of app.css grid-date color rules
   (app.css uses 4-class selector with !important on .weekday-grid-date; the decorator
   shares that class on the same element, so we need higher specificity to win) */
.tui-full-calendar-weekday-grid-line.tui-full-calendar-near-month-day .tui-full-calendar-weekday-grid-header .tui-full-calendar-weekday-grid-date.tui-full-calendar-weekday-grid-date-decorator {
    color: #fff !important;
}

/* Calendar legend + new-item row — vertically center and align */
.lnb-new-schedule-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: #556ee6;
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: color 0.15s;
    white-space: nowrap;
    position: relative;
    top: -3px;
}
.lnb-new-schedule-link:hover {
    color: #3b4ec4;
    text-decoration: none;
}
.lnb-new-schedule-link .new-schedule-icon {
    font-size: 26px;
    line-height: 1;
}

/* DataTable row hover — desaturated brand at low opacity. Stays in the
   neutral family so pointer feedback doesn't compete with content emphasis
   or the sidebar callout color (--accent-callout). */
table.dataTable tbody tr:hover > td {
    background-color: rgba(83, 101, 207, 0.05);
    border-top: 1px solid rgba(83, 101, 207, 0.18);
    border-bottom: 1px solid rgba(83, 101, 207, 0.18);
}

[data-layout-mode="dark"] table.dataTable tbody tr:hover > td {
    border-top-color: var(--bs-border-color);
    border-bottom-color: var(--bs-border-color);
}

/* ============================================================================
   FORM CHECK INPUTS — Stronger Border
   Bootstrap 5 default radio/checkbox borders are very faint (#dee2e6).
   Darken them for better visibility.
   ============================================================================ */

.form-check-input {
    border-color: #adb5bd !important;
}

[data-layout-mode="dark"] .form-check-input {
    border-color: #6c757d !important;
}

/* ============================================================================
   SURVEY RENDERER — Vertical Centering of Answer Options
   The question container is a flex column. The question text stays at the top;
   the answer area (.survey-answer-area) fills remaining space and centers its
   content vertically.
   ============================================================================ */

#survey-question-container {
    display: flex;
    flex-direction: column;
    min-height: 350px;
}

#survey-question-container .survey-answer-area {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-top: -40px;
}

#survey-question-container .survey-answer-area.survey-answer-freetext {
    justify-content: flex-start;
    margin-top: 0;
}

#survey-question-container .survey-answer-area .d-inline-block {
    text-align: left;
}

/* Survey assessment collapse chevron rotation on Face Sheet */
.collapse-chevron {
    display: inline-block;
    transition: transform 0.2s ease;
}

[aria-expanded="true"] > .collapse-chevron {
    transform: rotate(90deg);
}

/* ============================================================================
   DASHBOARD / FACE SHEET — Elevated Card Shadows
   Subtle drop-shadow on cards for visual depth and section distinction.
   ============================================================================ */

.facesheet-section,
.dashboard-cards > .row .card,
.dashboard-cards > .card {
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.00);
}

[data-layout-mode="dark"] .facesheet-section,
[data-layout-mode="dark"] .dashboard-cards > .row .card,
[data-layout-mode="dark"] .dashboard-cards > .card {
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.00);
}

/* --- Spacing between major Face Sheet cards --- */
.facesheet-section {
    margin-bottom: 1.25rem;
}

/* Zeroing the last card's margin aligns the column bottoms — a side-by-side
   concern. Below the xl breakpoint the col-xl-* columns stack, and the zero
   would leave the next column's first card (e.g. Smart Drop) flush against
   this one, so it only applies at xl and up. */
@media (min-width: 1200px) {
    .facesheet-section:last-child {
        margin-bottom: 0;
    }
}

/* TUI CALENDAR — Darken the hourly gridlines in Daily/Weekly timegrid views */
.tui-full-calendar-timegrid-gridline {
    border-color: #d8d8d8 !important;
}
[data-layout-mode="dark"] .tui-full-calendar-timegrid-gridline {
    border-color: #3a3f51 !important;
}

/* TUI CALENDAR — Darken the Milestone / Task / All Day lane borders */
.tui-full-calendar-splitter,
.tui-full-calendar-left,
.tui-full-calendar-dayname-container,
.tui-full-calendar-time-date {
    border-color: #e6e6e6 !important;
}
[data-layout-mode="dark"] .tui-full-calendar-splitter,
[data-layout-mode="dark"] .tui-full-calendar-left,
[data-layout-mode="dark"] .tui-full-calendar-dayname-container,
[data-layout-mode="dark"] .tui-full-calendar-time-date {
    border-color: #414660 !important;
}

/* ============================================================================
   TUI CALENDAR — Wider calendar-type dropdown in creation popup.
   With up to 11 calendar types the dropdown menu needs more room.
   ============================================================================ */

/* Widen TUI calendar type selector text — prevent truncation/ellipsis */
.tui-full-calendar-section-calendar .tui-full-calendar-dropdown-button {
    white-space: nowrap !important;
}

.tui-full-calendar-section-calendar .tui-full-calendar-dropdown-button .tui-full-calendar-content {
    width: auto !important;
    overflow: visible !important;
    text-overflow: clip !important;
    white-space: nowrap !important;
}

.tui-full-calendar-dropdown-menu {
    min-width: 220px !important;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
    border: 1px solid #ced4da !important;
}

/* Survey editor — clinician-only question card */
.clinician-only-header {
    background-color: #fff3cd;
}

.question-card.clinician-only {
    border-left: 3px solid #f1b44c;
}

[data-layout-mode="dark"] .clinician-only-header {
    background-color: rgba(241, 180, 76, 0.15);
}

[data-layout-mode="dark"] .question-card.clinician-only {
    border-left-color: #f1b44c;
}

/* FaceSheet mini-chart datepicker inputs */
.fc-datepicker {
    border-radius: 3px !important;
    cursor: pointer;
}

/* ======================== */
/* Workflow Timeline         */
/* ======================== */

.wf-timeline {
    position: relative;
    padding: 0 20px 0 25px;
}

.wf-timeline-item {
    position: relative;
    display: flex;
    padding-bottom: 24px;
    min-height: 60px;
}

.wf-timeline-item-last {
    padding-bottom: 0;
}

/* Vertical connector line */
.wf-timeline-line {
    position: absolute;
    left: 17px;
    top: 36px;
    bottom: 0;
    width: 2px;
    opacity: 0.3;
}

/* Node icon circle */
.wf-timeline-node {
    flex-shrink: 0;
    width: 36px;
    margin-right: 16px;
    display: flex;
    justify-content: center;
    padding-top: 2px;
}

.wf-timeline-icon {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 16px;
}

/* Pulse animation for current/running step */
.wf-pulse {
    animation: wf-pulse-anim 2s ease-in-out infinite;
}

@keyframes wf-pulse-anim {
    0%, 100% { box-shadow: 0 0 0 0 rgba(var(--bs-primary-rgb), 0.4); }
    50% { box-shadow: 0 0 0 8px rgba(var(--bs-primary-rgb), 0); }
}

/* Content area */
.wf-timeline-content {
    flex: 1;
    padding-top: 4px;
    min-width: 0;
}

/* Workflow status badge in referral DataTable */
.wf-status-badge {
    cursor: pointer;
    transition: opacity 0.15s;
}

.wf-status-badge:hover {
    opacity: 0.8;
}

/* Dark mode adjustments */
[data-layout-mode="dark"] .wf-timeline-line {
    opacity: 0.2;
}

[data-layout-mode="dark"] #wf-json-content {
    background-color: var(--bs-dark) !important;
    color: var(--bs-light);
}

/* ── Icon vertical alignment fixes ── */
/* Icon font glyphs sit ~1-2px high relative to adjacent text.                   */
/* Nudge them down so they appear optically centred.                              */

/* Boxicons in <button> elements: 2px */
.btn > .bx,
.btn > i.bx {
    position: relative;
    top: 2px;
}

/* Boxicons in <a> styled as buttons: 1px is enough */
a.btn > .bx,
a.btn > i.bx {
    top: 1px;
}

/* MDI icons in input-group addons (calendar icon, etc.): 2px */
.input-group-text > .mdi {
    position: relative;
    top: 2px;
}

/* Phosphor icons in buttons/links: 1px */
.btn > .ph-duotone,
.btn > i[class*="ph-"] {
    position: relative;
    top: 1px;
}

/* Icon-only phosphor buttons in input-group addons (Practitioner profile
   "Manage Account" / "Edit Practitioner"): the larger glyph sits higher in its
   line box than the default-size icons above, so 1px leaves it visibly high.
   Scoped to the input-group + font-size-15 pairing rather than raising the
   shared rule, which is correct at 1px for the text-bearing buttons using it. */
.input-group > .btn > .ph-duotone.font-size-15,
.input-group > .btn > i[class*="ph-"].font-size-15 {
    top: 3px;
}

/* Workflow "View" badges rendered inside DataTable cells */
.badge-soft-info > .bx {
    position: relative;
    top: 1px;
}

/* Repeater trash buttons with custom padding — 1px only */
.btn[data-repeater-delete] > .bx,
.btn.remove-repeater-item > .bx {
    position: relative;
    top: 1px;
}

/* Tiny compact buttons (POA remove-file, etc.) — 1px only */
.btn.poa-doc-delete > .bx,
.btn.proof-of-insurance-delete > .bx {
    position: relative;
    top: 1px;
}

/* Exempt tiny icon-only buttons in the form editor */
.element-controls .btn > .bx,
.field-properties-group-btn > .bx {
    top: 0;
}

/* ══════════════════════════════════════════════════════════════
   DataTable Filter Collapse (mobile / desktop split)
   ══════════════════════════════════════════════════════════════

   The toggle is injected as the FIRST segment of the .dt-buttons group
   (see CommonUtils.wrapFiltersForMobile), so it is a peer of the colvis /
   export icons and the accent "New" button rather than a separate control
   floating on its own row. Bootstrap's .btn-group handles the segmentation;
   what these rules own is (a) when the segment exists at all and (b) the
   tint that distinguishes it from the neutral icon segments.

   Sizing note: the injected class list is `btn btn-sm dt-filter-toggle`,
   but the sibling icon buttons are plain `.btn` at .375rem/.75rem padding
   (see `.dt-buttons .dt-control-button` above). btn-sm would render this
   segment shorter than the ones beside it and break the group's baseline,
   so the geometry is restated to match rather than inherited from btn-sm. */

/* Desktop: no toggle segment; collapse wrapper is layout-transparent so the
   controls render inline exactly as if the wrapper had never been created. */
.dt-filter-toggle {
    display: none;
}

.dt-filter-collapse {
    display: contents !important;
}

/* Shared appearance for the toggle wherever it IS shown. The bands below own
   `display` (and therefore visibility); everything visual lives here once so
   the two bands cannot drift apart.

   Colour: one step back from the accent that "New" owns. The primary action
   has to stay the loudest thing in the group, so the disclosure reads as
   secondary-but-deliberate rather than competing for the same attention.

   The hue is a per-theme choice because no single Bootstrap token is distinct
   enough in both brands — measured as hue separation from that theme's own
   --bs-primary:

       Incandescence (primary #c8410a, hue 17)   info 164°   success 139°
       Serenity      (primary #556ee6, hue 230)  info  22°   success  72°

   Info is ideal against Incandescence's ember and nearly useless against
   Serenity's indigo — 22° apart reads as two shades of the same blue sitting
   side by side, which is exactly the "should these look different?" the field
   pass raised. Success clears both comfortably, so Serenity overrides to it
   below. Defined as a variable rather than duplicating the whole button block. */
.dt-buttons .dt-filter-toggle {
    --dt-filter-accent: var(--bs-info);

    --bs-btn-color: #fff;
    --bs-btn-bg: var(--dt-filter-accent);
    --bs-btn-border-color: var(--dt-filter-accent);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, var(--dt-filter-accent) 85%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--dt-filter-accent) 85%, #000);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, var(--dt-filter-accent) 75%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--dt-filter-accent) 75%, #000);

    /* Match the icon segments' box exactly (`.dt-buttons .dt-control-button`
       above), so the group keeps one baseline across all six segments. */
    padding: .375rem .75rem;
    line-height: 1.5;
    font-size: 1rem;
    justify-content: center;
    align-items: center;
    gap: .35rem;
    white-space: nowrap;
}

/* The word "Filters" costs ~55px, which is the difference between the group
   fitting on one line at 393px and the accent "New" segment wrapping onto a
   second row on its own. Below the tablet band the segment goes icon-only and
   matches its icon siblings; the title attribute and the aria-label below keep
   it named for both tooltips and assistive tech.

   Hidden with a media query rather than a JS width check on purpose: rotating
   a phone or resizing a window must not require re-running the wrap. */
@media (max-width: 767px) {
    .dt-buttons .dt-filter-toggle .dt-filter-toggle-label {
        display: none;
    }

    .dt-buttons .dt-filter-toggle {
        gap: 0;
    }
}

/* The lucide SVG is em-sized; the global `.btn > .bx { top: 2px }` nudge is
   for the icon font and would push this one off-centre. */
.dt-buttons .dt-filter-toggle > .bx {
    top: 0 !important;
}

/* Open state: a filter panel that is open but scrolled out of view is easy to
   forget, so the segment stays visibly "on" while it holds controls. */
.dt-buttons .dt-filter-toggle[aria-expanded="true"] {
    --bs-btn-bg: color-mix(in srgb, var(--dt-filter-accent) 75%, #000);
    --bs-btn-border-color: color-mix(in srgb, var(--dt-filter-accent) 75%, #000);
}

/* Serenity's --bs-primary is indigo (#556ee6) and its --bs-info is a sky blue
   only 22° of hue away, so the default above would put two near-identical blues
   side by side in the same button group. Its success green is 72° from primary
   and reads correctly as the secondary accent. Incandescence keeps info, which
   is 164° from its ember primary and needs no override.

   Keyed on the body[data-brand] attribute _Layout stamps, so this follows the
   user's saved theme preference and needs no per-theme stylesheet. */
body[data-brand="serenity"] .dt-buttons .dt-filter-toggle {
    --dt-filter-accent: var(--bs-success);
}

/* ══════════════════════════════════════════════════════════════
   DataTable Responsive child rows (all viewports)
   ══════════════════════════════════════════════════════════════

   Deliberately NOT inside a media query. A child row appears whenever
   Responsive hides a column — which happens on a 1024px tablet, and on a
   desktop where the user has hidden columns by hand — not just under 767px.
   These are alignment corrections, correct at every width.

   Stock responsive.dataTables 2.4.1 lays each detail row out as:

       span.dtr-title { display:inline-block; min-width:75px; font-weight:bold }

   so the value starts at max(75px, label width). Labels under 75px line up
   with each other; any label longer than 75px ("Locked out", "Treatment
   records") pushes its own value further right and breaks the column. Two
   labels of different lengths therefore never share a gutter.

   A grid fixes it, but the grid must live on the UL, not the LI. Grid tracks
   are scoped to a single container: making each LI its own two-column grid
   sizes every gutter to that row's own label and changes nothing (measured —
   grid-template-columns resolved to a different px pair per row). Putting the
   grid on the UL and giving the LIs `display:contents` dissolves them, so all
   the titles and values become items of ONE grid and share column tracks.
   `max-content` then sizes column 1 to the widest label in the whole group.

   The catch: `display:contents` removes the LI's box, so the four stock rules
   hung on it — border-bottom, padding, :first-child, :last-child — stop
   painting and the separator lines between details disappear. They are
   re-applied to the grid items below, which is where they have to live now. */
/* Two things here are load-bearing beyond the alignment story above, and both
   are about surviving a 393px phone rather than a desktop:

   `minmax(0, 1fr)` on the value column, not a bare `1fr`. A bare `1fr` is
   shorthand for `minmax(auto, 1fr)`, and that `auto` floor resolves to the
   content's MIN-CONTENT width. For ordinary prose min-content is one word and
   nothing is wrong — but a patient GUID, an email address, or a long logger
   name has no break opportunity at all, so its min-content is the entire
   string and the track flatly refuses to shrink below it. The row then pushes
   past the viewport and the page scrolls sideways, which is exactly the
   "number cut off, stretches offscreen" symptom. `minmax(0, ...)` removes the
   floor so the track can shrink and the wrapping rules below can do their job.
   (The `min-width: 0` already on .dtr-data is the same idea applied to the
   grid ITEM; it cannot help while the TRACK itself has a min-content floor.)

   `fit-content(45%)` on the label column, not bare `max-content`. max-content
   sizes column 1 to the longest label in the group, which is right on a desktop
   and ruinous on a phone: "Maximum forms per treatment cycle" claims most of
   the row and squeezes every value into the remainder. fit-content(45%) keeps
   exactly the max-content behaviour while the labels fit, and caps the column
   at 45% of the row once they don't — so both columns wrap and share the width,
   which is the balance the Form Sets screenshot needed.

   (Not `clamp(0px, max-content, 45%)` — clamp is a math function over LENGTHS,
   and max-content is an intrinsic keyword, not a length. The whole track
   declaration becomes invalid, grid-template-columns falls back to `none`, and
   the two cells stack into one implicit column instead of sitting side by side.
   Measured: the UL reported a single 277px track and every value dropped
   beneath its label.) */
table.dataTable > tbody > tr.child ul.dtr-details {
    width: 100%;
    display: grid;
    grid-template-columns: fit-content(45%) minmax(0, 1fr);
    column-gap: 0.75em;
    align-items: baseline;
}

table.dataTable > tbody > tr.child ul.dtr-details > li {
    display: contents;
}

/* Stock puts min-width:75px here (the misalignment) and text-align:center on
   the LAST title, which reads as a stray indent once everything else lines up.
   The grid column owns width now, so both are neutralised. */
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-title {
    min-width: 0;
    text-align: left;
    /* Labels are ordinary prose, so normal word wrapping is enough once the
       clamp above lets the column be narrower than the longest label. */
    overflow-wrap: break-word;
}

table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data {
    text-align: left !important;
    min-width: 0;   /* let long values wrap rather than overflow the grid */

    /* Values are NOT ordinary prose — they are as often a GUID, an email, or a
       hyphenated identifier as a sentence. `overflow-wrap: anywhere` is the
       one that breaks a single unbroken token, and unlike `break-all` it still
       prefers legitimate word boundaries when they exist, so a sentence wraps
       like a sentence and only the GUID gets broken mid-token.

       It also, unlike `break-word`, contributes to the min-content size — that
       is precisely what stops the value from re-inflating this grid track. */
    overflow-wrap: anywhere;
}

/* …and the reason the line above is not sufficient on its own.
   Every list table carries DataTables' `nowrap` class, which sets
   `white-space: nowrap` on the table and is CORRECT there: it is what keeps
   desktop columns on a single line. But white-space is inherited, so it
   reaches into the child rows and forbids wrapping outright — and a value that
   may not wrap cannot be broken by overflow-wrap at any setting. Measured on
   /Patients/All at 393px: cell 142px wide, content 245px, clipped.

   So the child row explicitly opts back OUT of nowrap. Scoped to
   `tr.child` only, which is the stacked mobile presentation where wrapping is
   what you want; the parent table's own cells are untouched and desktop
   behaviour is unchanged.

   The inner elements are re-targeted too, not just the cell: renderers wrap
   values in <span class="text-muted">, badges, and flex DIVs, and each of
   those inherits the same nowrap and would re-impose it on the text it
   actually contains. */
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-title,
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data,
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data * {
    white-space: normal;
}

/* Badges were originally excepted back to `white-space: nowrap` here, on the
   reasoning that they are short self-contained chips ("Progress Note",
   "Awaiting upload") which look broken when wrapped mid-phrase and are never
   the thing that overflows. The first half of that holds; the second half
   turned out not to.

   Several list tables use a badge as a display TREATMENT for an identifier
   rather than as a status label: System > Users renders the username in
   `badge-pill badge-soft-info` (an email address), System > Roles renders the
   Role ID the same way (a 36-char GUID). Those are the exact values the
   `overflow-wrap: anywhere` above exists to break, and the nowrap exception
   was re-freezing them — measured on a 393px phone as
   "evert.reinger@matr-thera.c…" and "0ba8f38f-8588-416b-a9f9-ec67…" running
   under the card's right edge, clipped rather than scrollable.

   Rather than enumerate the offending tables (they will keep appearing — any
   new list that dresses an id in a badge inherits the same bug), the
   exception is narrowed by what the badge CONTAINS. A status chip's text is
   words separated by spaces, so it has break opportunities and `anywhere`
   never needs to split a word — chips still render on one line exactly as
   before. An identifier has no break opportunity at all, which is precisely
   why it overflows, and is the only case where a break now happens.

   The chip's shape still holds: `inline-block` + `max-width` keep the pill
   background painted around however many lines the value ends up taking. */
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data .badge {
    display: inline-block;
    max-width: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Re-apply the row separator and padding lost to display:contents. Both grid
   items in a row get them so the border spans the full width; :first-child /
   :last-child are matched on the LI and applied to its children.

   `align-items: stretch` (overriding the UL's `baseline`) is what keeps the two
   halves of the separator on the same line once wrapping is in play. With
   baseline alignment each item is only as tall as its own content, so a label
   that wraps to three lines beside a one-line value draws its border ~2 lines
   lower than its partner and the rule visibly steps. Stretching both to the
   row's full height puts both borders on the row's bottom edge.

   The text itself still needs to sit on the shared first baseline, which the
   stretched box no longer does for it — hence the explicit padding-top pair
   below rather than relying on alignment. */
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-title,
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data {
    border-bottom: 1px solid #efefef;
    padding: 0.5em 0;
    align-self: stretch;
}

table.dataTable > tbody > tr.child ul.dtr-details > li:first-child > .dtr-title,
table.dataTable > tbody > tr.child ul.dtr-details > li:first-child > .dtr-data {
    padding-top: 0;
}

table.dataTable > tbody > tr.child ul.dtr-details > li:last-child > .dtr-title,
table.dataTable > tbody > tr.child ul.dtr-details > li:last-child > .dtr-data {
    border-bottom: none;
}

[data-layout-mode="dark"] table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-title,
[data-layout-mode="dark"] table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data {
    border-bottom-color: var(--bs-border-color);
}

/* Action icons in a child row.
   The cell's inner .text-center centres the icons across the full row width,
   which reads as "floating" once everything else is aligned to a gutter.

   These are controls, not data, so they get their own treatment: the row is
   already its own grid row, so `justify-self: end` slides the whole cell to
   the right edge of the value column, putting the icons under the toolbar's
   New/colvis cluster instead of adrift mid-row. Only this cell moves — the
   grid tracks are untouched, so every other value stays on its shared left
   edge. Measured 4px inside the New button's right edge, stable from 800px
   to 1366px.

   Identified by :has() rather than li:last-child: the actions column is not
   necessarily last (on Patients, Gender follows it), and the actions cell is
   reliably the one whose content is a .text-center wrapper. Safari gained
   :has() in 15.4; on anything older the rule is skipped and the icons stay
   left-aligned with the values — degraded, not broken.

   Two shapes have to be recognised, because the app writes action cells both
   ways. Most renderers emit `<div class="text-center">…icons…</div>`, which the
   first selector catches. A few (the form editor's Available Forms sidebar
   table among them) emit BARE anchors and get their centring from the column's
   own `className: "text-center"` instead — DataTables puts that class on the
   parent-table <td>, and Responsive does not carry it into the child row, so
   there is no wrapper for :has() to find and those icons were left stranded
   mid-row while every other table's sat flush right. Matching an action cell by
   its content (a bare icon link with no text of its own) covers that shape. */
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data:has(> .text-center),
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data:has(> a[href^="javascript"] > [data-lucide]),
table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data:has(> a[href^="javascript"] > svg.lucide) {
    justify-self: end;
    text-align: right !important;
}

table.dataTable > tbody > tr.child ul.dtr-details > li > .dtr-data .text-center {
    text-align: right !important;
}

/* Hybrid toolbar, OUTSIDE its band (≤1199 and ≥1900).
   The toggles are meaningless anywhere else: below 1200 the single Filters
   toggle owns everything, and from 1900 the toolbar fits inline. Hide the
   buttons and let the panels behave as plain containers (`display: contents`),
   so their controls sit in the toolbar exactly as they did before the hybrid
   existed — the panel becomes invisible to layout rather than hiding anything.
   Without this a collapsed panel would keep its controls hidden at 1920. */
.dt-hybrid-toggles {
    display: none;
}

/* ≥1900 only: the panels stop being boxes so their controls lay out inline,
   exactly as they did before the hybrid existed.
   !important is required, not lazy — Bootstrap's `.collapse:not(.show)` sets
   `display: none`, and these panels genuinely never open outside their band.

   Scoped to a min-width rather than applied globally. Below 1200 these panels
   sit INSIDE .dt-filter-collapse, and `display: contents` there would spill
   their controls out of the collapsed Filters panel — search, length and the
   date range would render in the toolbar while Filters was shut, re-creating
   the phone overflow this whole effort removed (measured: +33px at 393px). */
@media (min-width: 1900px) {
    .dt-hybrid-collapse {
        display: contents !important;
    }
}

/* Below 1200 the hybrid panels are SIBLINGS of .dt-filter-collapse (both are
   children of .filter-length — hybridiseToolbar prepends them to the toolbar,
   it does not nest them). So they cannot inherit the Filters panel's state;
   they have to follow it explicitly.

   A sibling combinator cannot help: the panels are PREPENDED, so they precede
   .dt-filter-collapse in DOM order and `~` only looks forward. The toggle is no
   use as a hook either — it lives in .dt-buttons, a SIBLING of .filter-length,
   so it is not a descendant of anything that contains the panels.

   Match on .filter-length containing an OPEN Filters panel instead. :has() is
   not scoped to descendants of the subject in the way a sibling combinator is,
   so it can ask "does this toolbar hold an open panel?" regardless of where the
   panels sit relative to it.

   Without this the panels stay `display: none` at phone widths and opening
   Filters reveals only the controls that were swept into it — search, page
   length and the date range would be unreachable on a phone.

   Scoped below 1200 so it cannot fight the hybrid band above it, where the
   Filters panel is not the container in charge. */
@media (max-width: 1199.98px) {
    .filter-length:has(> .dt-filter-collapse.show) .dt-hybrid-collapse,
    .filter-length:has(> .dt-filter-collapse.collapsing) .dt-hybrid-collapse {
        display: contents !important;
    }

    /* `display: contents` removes the panel's BOX, so its children become flex
       items of .filter-length itself — NOT of .dt-filter-collapse, which is
       their sibling, not their ancestor. That is why the mobile band's
       `.dt-filter-collapse .dt-inline-filter` clamps never reached them and the
       date row kept its natural width, running 33px past the toolbar at 393px.
       Re-assert the same clamps against the real parent. */
    .filter-length > .dt-hybrid-collapse > .dt-inline-filter,
    .filter-length > .dt-hybrid-collapse > .dataTables_filter,
    .filter-length > .dt-hybrid-collapse > .dataTables_length {
        flex: 1 1 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
        overflow: hidden;
        box-sizing: border-box;
    }

    .filter-length > .dt-hybrid-collapse > .dt-inline-filter {
        display: flex !important;
        flex-wrap: nowrap;
        gap: 6px;
    }

    /* Date row: cap the kind selector so the two pickers keep a usable share,
       mirroring the equivalent rule inside .dt-filter-collapse. */
    .filter-length > .dt-hybrid-collapse > .dt-inline-filter:has(> input.form-control) .select2-container {
        flex: 0 1 40% !important;
        max-width: 40% !important;
    }

    .filter-length > .dt-hybrid-collapse > .dt-inline-filter input.form-control {
        flex: 1 1 0 !important;
        min-width: 0 !important;
    }
}

/* Clearance for the Responsive (+) / (−) control, shared by every band.
   ───────────────────────────────────────────────────────────────────────
   The control is drawn by the CDN stylesheet as an absolutely positioned
   ::before at `left: 5px; width: 14px` with `box-sizing: content-box` AND a
   border — so the painted circle is wider than 14px, by an amount the engine
   decides. Chromium lands it around x=19; WebKit draws it wider, which is why
   an iPad showed "(+)ast, First" while the same width in Chromium showed a
   clean "(+) Last, First".

   Deriving the padding from the circle's own geometry rather than a tuned
   constant means the gap survives that disagreement: 5px offset + 14px content
   + up to 2×1px border + a 6px reading gap. The old hard-coded 28px happened to
   clear Chromium's circle and not WebKit's.

   One variable, three bands (mobile / tablet / hybrid) — they must not drift
   apart, since a row can cross bands just by rotating a tablet. */
:root {
    --dtr-control-clearance: calc(5px + 14px + 2px + 6px);
}

/* ══════════════════════════════════════════════════════════════
   Topbar: keep the header inside the screen on a phone
   ══════════════════════════════════════════════════════════════

   `.navbar-header` is `display:flex; justify-content:space-between; flex-wrap:nowrap`
   with two unclassed `div.d-flex` children — brand + sidebar toggle on the left, the
   icon cluster (search, environment badge, language, notifications, avatar, settings)
   on the right. Measured at 393px: the header itself is correctly 393px, but its
   children are 171px + 250px = 421px, so the right cluster runs 28px past the edge and
   the trailing settings icon is cut in half. `.main-content`'s `overflow:hidden` means
   it is CLIPPED rather than scrollable, so the control is simply unreachable.

   The 28px has to come from somewhere, and the brand box is where the slack actually
   is: `.navbar-brand-box` is authored at `width: 250px; padding: 0 1.5rem`, which is a
   desktop allowance for a wordmark that is not even rendered at this size (the phone
   shows the compact `.logo-sm` mark). Reclaiming that padding removes the overflow
   outright, which is much better than making the icon strip scroll — a horizontally
   scrolling row of 5 icons gives no hint that a 6th exists, and these are the only
   routes to search, notifications and settings on a phone.

   UPDATE: the 28px now comes from the language picker instead, which is hidden below
   lg in _HeaderPartial (unwired translation path, English-only user base). That buys
   back a whole header-item, so the brand box keeps its authored symmetric padding and
   the mark reads as the square badge it does everywhere else, rather than being jammed
   against the screen edge. Only `min-width: 0` is still needed here, so the box can
   yield first if a future tenant name or badge makes the row tight again.

   Nothing is hidden and no icon is dropped; the icons keep their full tap targets.
   Scoped to the phone band — above it the header has room and behaves as authored. */
@media (max-width: 767px) {
    .navbar-header .navbar-brand-box {
        width: auto;
        min-width: 0;
    }

    .navbar-header > .d-flex {
        min-width: 0;
    }

    /* Icon buttons keep their size while the row shrinks — otherwise the flex children
       absorb the shrink and the tap targets squash instead of the brand box yielding. */
    .navbar-header > .d-flex:last-child > * {
        flex: 0 0 auto;
    }
}

/* Invoice line items: a six-column editable grid inside a .table-responsive box.
   The percentage column widths in the markup need something to resolve against —
   without a floor the table shrinks to the wrapper and the Qty/Rate/Total inputs
   collapse to a few characters each. 640px is the width at which all six columns
   are still comfortably usable; below that the box scrolls instead. */
.line-items-scroll > table.line-items-table {
    min-width: 640px;
}

/* …but not on a phone, where that scroll was the wrong call.
   ─────────────────────────────────────────────────────────────
   The 640px floor above is right for a tablet: all six columns stay usable and
   a short horizontal scroll is a fair price. At 393px it is not a short scroll,
   it is a 250px one on a data-ENTRY grid, and it fails in a way a read-only
   table does not — the box carries no scrollbar or shadow on touch, so there is
   nothing to indicate the row continues, and the columns that fall off the right
   edge are Rate, Total and the delete button. A user can fill in a description
   and quantity and never discover the field that prices the line.

   So below the breakpoint the grid stops being a table and becomes one labelled
   card per line, which is the conventional treatment for an editable table and
   needs no scrolling at all. The <thead> goes (its labels move onto the cells
   via the `data-label` attributes invoices-new.js writes), each <tr> becomes a
   borderless labelled block under its own underlined "Line N" heading, and
   each <td> becomes a label/value row.

   Everything here is inside the media query, so the desktop and tablet
   presentations are byte-identical to before — including the 640px floor,
   which simply stops applying once the table is no longer laid out as a table.

   Scoped to `.line-items-scroll >` rather than to `.line-items-table` alone,
   which is the discriminator that matters: Invoices/Detail.cshtml renders a
   READ-ONLY line-items table under the same class but without the scroll
   wrapper and without `data-label` attributes (its rows are server-rendered).
   Keyed on the class alone this would hide that table's <thead> and leave five
   unlabelled values per row — strictly worse than the plain table it has now,
   which fits at this width because it carries no inputs. Only New and Edit
   wrap their grid in `.line-items-scroll`, so only they restack. */
@media (max-width: 575.98px) {
    .line-items-scroll > table.line-items-table {
        min-width: 0;
    }

    /* `display: block` on the table parts is what dissolves the column model.
       The percentage widths on the <th>s stop resolving against anything and
       become inert, which is why they need no separate override. */
    .line-items-scroll > .line-items-table,
    .line-items-scroll > .line-items-table > tbody,
    .line-items-scroll > .line-items-table > tbody > tr,
    .line-items-scroll > .line-items-table > tbody > tr > td {
        display: block;
        width: auto;
    }

    .line-items-scroll > .line-items-table > thead {
        display: none;
    }

    /* No borders at all here — not around the table (Bootstrap's
       `table-bordered` draws the enclosing box plus per-part borders via its
       `> :not(caption) > *` rules, hence the !important, same as the
       .stack-table neutralisation), not around each line, and — unlike the
       read-only stacked tables — not even BETWEEN lines: every record opens
       with its own underlined "Line N" heading (below), so the whitespace and
       the headings carry the structure and a separator would just double the
       heading's rule. The read-only Detail view keeps its separators; its
       stacked rows have no headings to do that job. Desktop keeps the full
       bordered grid — none of this exists above the breakpoint. */
    .line-items-scroll > .line-items-table,
    .line-items-scroll > .line-items-table > tbody,
    .line-items-scroll > .line-items-table > tbody > tr {
        border: 0 !important;
    }

    .line-items-scroll > .line-items-table > tbody > tr {
        padding: 0.5rem 0;
        margin-bottom: 0.75rem;
    }

    .line-items-scroll > .line-items-table > tbody > tr:last-child {
        margin-bottom: 0;
    }

    /* The line number leads the card as its heading rather than sitting in a
       label/value row of its own — "#" against a bare digit reads as noise when
       it is the first thing in the block. */
    .line-items-scroll > .line-items-table > tbody > tr > td[data-label="#"] {
        font-weight: 600;
        text-align: left !important;
        border-bottom: 1px solid var(--bs-border-color);
        padding: 0 0 0.4rem;
        margin-bottom: 0.5rem;
    }

    .line-items-scroll > .line-items-table > tbody > tr > td[data-label="#"]::before {
        content: "Line ";
    }

    /* Every other cell: label left, control right, on one line. The inputs are
       given a share rather than the full width because Qty and Rate hold 1-6
       characters and a full-width number input beside a three-letter label
       looks like a mistake; Description gets the same share and is the one that
       genuinely uses it. */
    .line-items-scroll > .line-items-table > tbody > tr > td:not([data-label="#"]) {
        display: flex;
        align-items: center;
        gap: 0.75rem;
        border: 0;
        padding: 0.25rem 0;
    }

    .line-items-scroll > .line-items-table > tbody > tr > td:not([data-label="#"])::before {
        content: attr(data-label);
        flex: 0 0 5.5rem;
        font-size: 0.8125rem;
        color: var(--bs-secondary-color);
    }

    /* `min-width: 0` needs !important here, and only here. New.cshtml and
       Edit.cshtml each carry `.line-items-table .form-control { min-width: 80px }`
       in their own @section Css, which is a floor for the scrolling desktop grid
       and renders in the <head> AFTER this file — so it wins on source order at
       equal specificity and would stop the inputs shrinking into the stacked
       row. The floor is still correct at every width where the grid is a grid;
       it is only wrong once the row is a flex card. */
    .line-items-scroll > .line-items-table > tbody > tr > td .form-control {
        flex: 1 1 auto;
        min-width: 0 !important;
    }

    /* The two cells that hold no input: Total is a value, the last is the
       delete button. Both align with the controls above them rather than with
       their own labels. */
    .line-items-scroll > .line-items-table > tbody > tr > td.line-total {
        justify-content: flex-start;
        text-align: left !important;
        font-weight: 600;
    }

    .line-items-scroll > .line-items-table > tbody > tr > td[data-label=""] {
        justify-content: flex-end;
        padding-top: 0.5rem;
    }

    .line-items-scroll > .line-items-table > tbody > tr > td[data-label=""]::before {
        content: none;
    }
}

/* Claim detail service lines: the same restack, for a read-only ten-column table.
   ─────────────────────────────────────────────────────────────────────────────
   `Service Lines (Box 24)` carries #, Date of Service, CPT, Modifiers, Dx Ptrs,
   POS, Units, Charge, Rendering NPI and Source Form. Its `.table-responsive`
   wrapper does scroll, so nothing is technically unreachable — but on a phone
   the columns left of the viewport are #, Date, CPT and Modifiers, which are
   precisely the ones that say WHICH line this is. The field report shows the
   table parked mid-scroll reading "ifiers / Dx Ptrs / POS …", with the CPT code
   the whole row is about off-screen to the left.

   Same treatment as the invoice grid above and for the same reason, with three
   differences that come from this table being read-only rather than editable:
   the cells hold values instead of inputs, so the label/value pair can sit on
   one line at natural widths; there is a `<tfoot>` carrying the Box 28 total,
   which becomes the card stack's footer rather than a row; and the `#` cell
   leads each card as its heading, as it does on the invoice.

   The tfoot's `colspan="7"` / `colspan="2"` are hard-coded to the ten-column
   shape. Nothing here changes the column count — the footer is restacked as a
   block, so the colspans stop participating in layout rather than needing to be
   kept in sync. They remain correct for every width above the breakpoint.

   The sibling `Diagnoses (Box 21)` table on the same page is deliberately NOT
   given this class: three columns fit a phone comfortably, and it is already
   capped at `max-width: 640px`. Restacking it would make a fine table taller
   for nothing. */
@media (max-width: 575.98px) {
    /* `tfoot > tr` is deliberately absent here — it becomes a flex row further
       down so its label and amount share a line. */
    .claim-lines-table,
    .claim-lines-table > tbody,
    .claim-lines-table > tbody > tr,
    .claim-lines-table > tbody > tr > td,
    .claim-lines-table > tfoot,
    .claim-lines-table > tfoot > tr > th {
        display: block;
        width: auto;
    }

    .claim-lines-table > thead {
        display: none;
    }

    .claim-lines-table > tbody > tr {
        border: 1px solid var(--bs-border-color);
        border-radius: 0.25rem;
        padding: 0.5rem 0.75rem;
        margin-bottom: 0.75rem;
    }

    /* The line number heads the card. `Line 1` reads as a title where a bare
       "1" under a "#" label would read as data. */
    .claim-lines-table > tbody > tr > td[data-label="#"] {
        font-weight: 600;
        border-bottom: 1px solid var(--bs-border-color);
        padding: 0 0 0.4rem;
        margin-bottom: 0.5rem;
    }

    .claim-lines-table > tbody > tr > td[data-label="#"]::before {
        content: "Line ";
    }

    /* Label left, value right on one line. `text-align` is forced back to left
       because three of these cells carry `text-center` / `text-end` utilities
       for the desktop column they no longer occupy — centred values in a
       stacked card read as accidental. */
    .claim-lines-table > tbody > tr > td:not([data-label="#"]) {
        display: flex;
        align-items: baseline;
        gap: 0.75rem;
        border: 0;
        padding: 0.2rem 0;
        text-align: left !important;
    }

    .claim-lines-table > tbody > tr > td:not([data-label="#"])::before {
        content: attr(data-label);
        flex: 0 0 7.5rem;
        font-size: 0.8125rem;
        color: var(--bs-secondary-color);
    }

    /* An empty Modifiers or Dx Ptrs cell would otherwise render as a label with
       nothing beside it. The row is still meaningful (the absence IS the value
       on a claim), so it stays — but a dash makes it read as "none" rather than
       as a rendering fault. */
    .claim-lines-table > tbody > tr > td:not([data-label="#"]):empty::after {
        content: "—";
        color: var(--bs-secondary-color);
    }

    /* Box 28 total: the stack's footer, not a card.
       The label and the amount are two separate <th> cells, so the row is made
       a flex line rather than left as two stacked blocks — otherwise the total
       renders under its own caption instead of beside it. `space-between` puts
       the amount at the right edge, which is where a total belongs and matches
       the desktop `text-end` it is losing. */
    .claim-lines-table > tfoot > tr {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 0.75rem;
        border-top: 2px solid var(--bs-border-color);
        padding-top: 0.5rem;
    }

    .claim-lines-table > tfoot > tr > th {
        border: 0;
        padding: 0.25rem 0;
        text-align: left !important;
    }

    /* The third footer cell is the empty `colspan="2"` spacer that balanced the
       desktop row; stacked it is just a blank line. */
    .claim-lines-table > tfoot > tr > th:empty {
        display: none;
    }
}

/* ══════════════════════════════════════════════════════════════
   Mobile / Narrow Viewport Overrides  (≤ 767px)
   ══════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {

    /* ── 1. DataTable toolbar: allow wrapping so buttons don't overwrite filters ── */
    .dataTables_wrapper .top {
        flex-wrap: wrap;
        gap: 6px;
        overflow: hidden;
        max-width: 100%;
        box-sizing: border-box;
    }

    /* Filter-length area: allow wrapping for toggle + collapse layout */
    .dataTables_wrapper .filter-length {
        flex-wrap: wrap;
        flex-basis: 100%;
        margin-right: 0;
        gap: 6px;
        overflow: hidden;
    }

    /* Zero out left margins on all filter-area children */
    .dataTables_wrapper .filter-length .dataTables_length,
    .dataTables_wrapper .filter-length .dataTables_filter,
    .dataTables_wrapper .filter-length .dt-inline-filter {
        margin-left: 0;
    }

    /* Fallback for DataTables without wrapFiltersForMobile — stack controls */
    .dataTables_wrapper .filter-length > .dataTables_filter {
        flex-basis: 100%;
        text-align: left;
    }

    .dataTables_wrapper .filter-length > .dataTables_filter input {
        width: 100%;
        margin-right: 0;
    }

    .dataTables_wrapper .filter-length > .dt-inline-filter {
        flex: 1 1 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
    }

    /* Toolbar buttons: auto-place after filters */
    .dt-buttons {
        margin-left: auto;
    }

    /* Show the filter toggle. It now rides inside the .dt-buttons group, so it
       needs no margin-left:auto of its own — the group owns its own placement
       (see `.dt-buttons { margin-left: auto }` above) and the segment simply
       leads it. Appearance is inherited from the shared rule outside the bands. */
    .dt-filter-toggle {
        display: inline-flex;
    }

    /* Collapse panel: override the desktop flex display; let Bootstrap control visibility */
    .dt-filter-collapse {
        display: none !important;
        flex-wrap: wrap;
        flex-basis: 100%;
        padding-top: 6px;
        overflow: hidden;
        max-width: 100%;
    }

    .dt-filter-collapse.show {
        display: flex !important;
        gap: 8px;
    }

    .dt-filter-collapse.collapsing {
        display: flex !important;
    }

    /* Controls inside the collapse panel stack vertically, left-aligned.
       ─────────────────────────────────────────────────────────────────
       The `text-align` here has to out-specify two stock rules that both
       centre these controls below 767px — the theme's own
       `div.dataTables_wrapper div.dataTables_filter { text-align: center }`
       (app.css:3677) and the equivalent in DataTables' CDN bootstrap5
       stylesheet, which covers `.dataTables_length` as well.

       Centring is a reasonable default for a bare search box floating above a
       table. It is wrong here: inside the Filters panel these sit in a stack
       with the date range, the status Select2 and the toggles, all of which
       are full-width and left-aligned, so a centred "Search:" and
       "Show 15 entries" read as two rows that have picked up a stray left
       margin — the reported symptom. Both selectors below are written at
       `div.dataTables_wrapper`-matching specificity so they win outright
       rather than relying on source order against a CDN file. */
    .dt-filter-collapse .dataTables_filter {
        flex-basis: 100%;
        max-width: 100%;
    }

    div.dataTables_wrapper .dt-filter-collapse div.dataTables_filter,
    div.dataTables_wrapper .dt-filter-collapse div.dataTables_length,
    div.dataTables_wrapper .dt-hybrid-collapse div.dataTables_filter,
    div.dataTables_wrapper .dt-hybrid-collapse div.dataTables_length {
        text-align: left;
    }

    .dt-filter-collapse .dataTables_filter label {
        display: flex;
        align-items: center;
        gap: 6px;
        max-width: 100%;
    }

    .dt-filter-collapse .dataTables_filter input {
        flex: 1;
        width: auto;
        max-width: 100%;
        margin-right: 0;
        margin-left: 0;
        box-sizing: border-box;
    }

    .dt-filter-collapse .dataTables_length {
        margin-left: 0;
        max-width: 100%;
    }

    .dt-filter-collapse .dt-inline-filter {
        flex: 1 1 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
        margin-left: 0;
        overflow: hidden;
        box-sizing: border-box;
        display: flex !important;
        flex-wrap: nowrap;
        gap: 6px;
    }

    /* Select2 inside collapse: fill parent by default (status filter, etc.) */
    .dt-filter-collapse .dt-inline-filter .select2-container {
        flex: 1 1 100% !important;
        width: auto !important;
        max-width: 100% !important;
        min-width: 0 !important;
    }

    /* Date-range filters: cap the column selector so date pickers share space.
       Targets containers that hold a Select2 + date inputs side-by-side.
       :has() is supported on Android Chrome/Edge 105+ (2022). */
    .dt-filter-collapse .dt-inline-filter:has(> input.form-control) .select2-container {
        flex: 0 1 40% !important;
        max-width: 40% !important;
    }

    /* Date/text inputs inside collapse: equal share of remaining space */
    .dt-filter-collapse .dt-inline-filter input.form-control {
        flex: 1 1 0 !important;
        width: 0 !important;
        min-width: 0 !important;
        max-width: 100% !important;
        box-sizing: border-box;
        margin-left: 0 !important;  /* neutralize JS inline margin */
    }

    /* Select elements inside collapse panel */
    .dt-filter-collapse .dataTables_length select,
    .dt-filter-collapse .dt-inline-filter select {
        max-width: 100%;
        box-sizing: border-box;
    }

    /* ── 2. DataTable responsive (+) control: prevent overlap with cell text ──
       DataTables Responsive CDN CSS positions the control on the LEFT (padding-left / left).
       Increase padding so the (+) circle doesn't overlap cell text.
       Clearance comes from --dtr-control-clearance (defined outside the bands) so
       every band agrees and there is one number to change. */
    .table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
    table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control {
        padding-left: var(--dtr-control-clearance) !important;
        position: relative;
    }

    /* ── 3. DataTable pagination: compact for narrow screens ── */
    .dataTables_wrapper .bottom {
        flex-wrap: wrap;
        gap: 6px;
    }

    .dataTables_wrapper .dataTables_info {
        font-size: 0.8rem;
    }

    /* Pagination on a long table (the reference browsers run to hundreds of pages)
       renders Previous + numbered buttons + Next on one unbreakable line — measured
       363px to 378px wide against a 393px viewport once the card's own padding is
       taken off, so the Next button lands outside the screen and is unreachable.

       Wrapping the list is enough; the buttons keep full size so they stay tappable,
       and a second row costs less than a horizontal scroll nobody would discover. */
    .dataTables_wrapper .dataTables_paginate {
        max-width: 100%;
    }

    .dataTables_wrapper .dataTables_paginate .pagination {
        flex-wrap: wrap;
        justify-content: center;
        row-gap: 4px;
    }

    /* ── 3b. Visible cells holding an unbreakable token ──
       Everything above about wrapping applies to `tr.child` — the stacked
       detail rows Responsive builds. The row that stays VISIBLE keeps the
       table's `nowrap`, which is right for a name or a date and wrong for the
       one column Responsive chose to leave on screen when that column holds
       an identifier. Billing Manager > Transmissions is the case in hand: the
       surviving column is the 837P batch filename
       (`837P_Availity_200000002_20260808002253.txt`), 43 characters with no
       space or hyphen to break on, so the cell renders at its full natural
       width and the table runs past the card — clipped, not scrollable.

       Only the first visible cell is treated, and only on a phone. That is
       deliberately narrow: Responsive keeps exactly one data column plus the
       control at this width, so this IS the surviving column, and confining
       the rule to it means no other table's carefully-sized columns start
       wrapping. `anywhere` breaks the token only when there is genuinely no
       word boundary available, so ordinary text columns are unchanged even
       where the rule does apply.

       The parent table's `nowrap` has to be lifted on the cell for the same
       reason it does in a child row: a cell that may not wrap cannot be
       broken by overflow-wrap at any setting.

       `.dataTables_wrapper >` anchors the rule to the REAL table and is
       load-bearing, not decorative. Responsive sizes columns by inserting an
       off-screen clone of the table (same id, same classes, cloned rows —
       dtr-control class included) and reading each column's single-line
       content width from it. Un-anchored, this rule reached that clone and
       let the first column's measurement wrap down to roughly one character,
       so Responsive believed the column needed no room and kept extra columns
       it had no space for — the mechanism dissected in the renegotiation
       block below. The clone's mounting div has no class, while every real
       table in the app is a direct child of its wrapper (all dom strings
       place `t` at top level), so the anchor cleanly splits the two. */
    .dataTables_wrapper > table.dataTable > tbody > tr:not(.child) > td.dtr-control,
    .dataTables_wrapper > table.dataTable > tbody > tr:not(.child) > td.dtr-control * {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    /* …and the case where breaking the token is the WRONG answer.
       ─────────────────────────────────────────────────────────────
       The rule above assumes the surviving column is the only data column, so
       giving a long token the full row width is a fair trade. Billing Manager >
       Transmissions breaks that assumption: Responsive is not enabled on that
       table, so File / Clearinghouse / Status all stay on screen and the File
       column is genuinely narrow. Wrapping there does stop the overflow — and
       replaces it with a 43-character filename broken across seven lines, one
       fragment per line, which reads worse than the clipping did.

       So this one column truncates instead. The full value is not lost: it is
       on the `title` attribute, and it is spelled out in the Responsive child
       row that the (+) control opens — which is where a user goes for a row's
       details anyway.

       `direction: rtl` with `text-overflow: ellipsis` puts the ellipsis at the
       START rather than the end. That is deliberate for this data: every 837
       filename begins with the same `837P_Availity_` prefix and is
       distinguished only by its trailing control number and timestamp, so
       truncating the tail would render every row identical. `unicode-bidi:
       plaintext` keeps the text itself in logical order — without it the RTL
       direction would also flip the punctuation runs and display the name
       backwards. */
    .bm-file-name {
        display: inline-block;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap !important;
        vertical-align: bottom;
        direction: rtl;
        unicode-bidi: plaintext;
        text-align: left;
    }

    /* `max-width: 100%` on the span alone is not enough, because the cell it
       sits in has no width of its own to be 100% OF — a table cell sizes to its
       content, so the column simply grows to fit the untruncated name and the
       span dutifully fills it. The cap has to go on the CELL, which is what
       gives the percentage something finite to resolve against and lets the
       table fit the screen. 48% leaves the Clearinghouse and Status columns a
       usable share of a 393px row. */
    #bm-transmissions-table > tbody > tr > td:first-child {
        max-width: 48vw;
    }

    /* ── 4. Forms/All submissions tree ──
       The grouped view (patient › referral › treatment record › forms) is a
       genuinely wide structure: each group header is one colspan cell carrying
       a label like "Referral #83 – 20 (Received 08/15/2026)" under
       `white-space: nowrap` (see .group-header td above), plus up to 40px of
       indent. Measured at 393px the table rendered 413px wide and the labels
       were simply cut off at the screen edge — not scrollable, because an
       ancestor clips them, which is why this never showed up as document
       overflow.

       Rather than force a horizontal scroll on the whole grid (the nesting is
       the point of this view, and a scrollbar would hide the hierarchy), the
       group headers wrap and the indents halve. The tree still reads as a tree
       — 10px and 20px are enough to see three levels apart at this size — and
       the labels stay whole. Desktop keeps the full 20/40px scale and its
       single-line headers untouched. */
    table.dataTable > tbody > tr.group-header > td {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .group-indent-1 {
        width: 10px;
    }

    .group-indent-2 {
        width: 20px;
    }

    /* The label wraps as ordinary inline text, continuing under the row's left
       edge rather than hanging under its own first line. That is deliberate:
       every alternative costs more than the alignment is worth at this size,
       and the three tint levels plus the indent on line 1 already carry depth.

       Measured alternatives, both rejected:

       `display: flex` on the <td> destroys the colspan — the cell collapses to
       the first column's width and every label wraps one character per line.
       A colspan cell cannot be a flex container and stay spanned.

       A negative `text-indent` hanging indent shifts the entire first line, and
       that line begins with the chevron and type icon, so both were dragged out
       of the cell and left floating in the table's left margin.

       `inline-block` + `max-width` on the label span wraps correctly but pushes
       the label onto its own line beneath the icons, costing a line of height
       on every group row for an alignment nicety. */
}

/* ══════════════════════════════════════════════════════════════
   Tablet / Intermediate Viewport Overrides  (768px – 1199px)
   ══════════════════════════════════════════════════════════════

   The band an iPad in landscape (1024px CSS) lands in, and the gap this
   stylesheet used to have: above 767px every toolbar control rendered inline
   at full width, so a filter-heavy table (Patients: search + length + status
   Select2 + two date inputs + two switches + five buttons) simply ran out of
   room and the right-aligned .dt-buttons group overlapped the switches.

   The fix is not new machinery — it is the mobile collapse, applied one tier
   up. Controls hide behind the "Filters" toggle exactly as they do on phones,
   but the toolbar itself stays HORIZONTAL (no flex-basis:100% stacking), which
   is what distinguishes this band from the ≤767px one below it.

   Top boundary is 1199px: 1920×1080 is the supported minimum spec, so every
   officially-supported desktop resolution keeps the full inline toolbar and
   this band is reached only by tablets and undersized windows. See
   [[reference_resolution_tiers]].

   Requires wrapFiltersForMobile() to have run for the table — CSS alone cannot
   collapse controls that were never moved into .dt-filter-collapse. */
@media (min-width: 768px) and (max-width: 1199px) {

    /* Toolbar stays on one line; the collapse panel wraps beneath it. */
    .dataTables_wrapper .top,
    .dataTables_wrapper .filter-length {
        flex-wrap: wrap;
        gap: 6px;
        max-width: 100%;
        box-sizing: border-box;
    }

    /* Show the toggle as the leading segment of the button group. Same rule as
       the mobile band — the group's own margin-left:auto (below) holds the right
       edge, and the shared appearance rule outside the bands does the rest. */
    .dt-filter-toggle {
        display: inline-flex;
    }

    /* Collapse panel: override the desktop `display:contents` so Bootstrap
       controls visibility. Full-width row beneath the toolbar when open. */
    .dt-filter-collapse {
        display: none !important;
        flex-basis: 100%;
        flex-wrap: wrap;
        gap: 8px;
        padding-top: 6px;
        max-width: 100%;
        box-sizing: border-box;
    }

    .dt-filter-collapse.show,
    .dt-filter-collapse.collapsing {
        display: flex !important;
    }

    /* Controls inside the panel: share the row rather than stacking. There is
       more width here than on a phone, so filters sit side by side. */
    .dt-filter-collapse .dataTables_filter,
    .dt-filter-collapse .dataTables_length,
    .dt-filter-collapse .dt-inline-filter {
        margin-left: 0;
        max-width: 100%;
        box-sizing: border-box;
    }

    .dt-filter-collapse .dt-inline-filter {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
        align-items: center;
    }

    /* Neutralise the inline ms-3 the filter JS stamps on switch wrappers —
       the flex gap owns spacing inside the panel now. */
    .dt-filter-collapse .dt-inline-filter .form-check.ms-3 {
        margin-left: 0 !important;
    }

    .dt-filter-collapse .dataTables_filter input {
        margin-left: 6px;
        margin-right: 0;
    }

    /* Buttons hold the right edge of the toolbar. */
    .dt-buttons {
        margin-left: auto;
    }

    /* Responsive (+) control: same overlap guard as the mobile band — child
       rows appear in this band too, so the control needs the same clearance. */
    .table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
    table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control {
        padding-left: var(--dtr-control-clearance) !important;
        position: relative;
    }
}

/* ══════════════════════════════════════════════════════════════
   Hybrid Toolbar  (1200px – 1899px)  — the "no-man's land"
   ══════════════════════════════════════════════════════════════

   Below 1200px the whole toolbar collapses behind one "Filters" toggle; at
   1900px+ it all fits inline. Between the two it did neither. Measured on
   /Patients/All, .filter-length content vs its own box:

     1200px  over by 258px      1536px  over by 100px
     1280px  over by 220px      1600px  over by  75px
     1366px  over by 180px      1700px  over by  43px
     1440px  over by 145px      1800px  over by  11px
                                1900px  fits (-16px)

   The shell's overflow:hidden CLIPS that overrun rather than scrolling it, so
   those controls were unreachable with nothing on screen to suggest they exist
   — the worst failure mode of the three, and it covered two officially
   supported resolutions (1366 laptop, 1440 laptop).

   The fix keeps the toolbar horizontal and gives the two widest, least-used
   control clusters a disclosure each:

     [S] [ ...primary filter... ] [D] (*)switches        [buttons][New]

   [S] = search + page length, [D] = date range. The primary filter and the
   switches stay inline because they carry the view's meaning and are flipped
   constantly; the disclosed pair are wide and occasional. Requires
   CommonUtils.hybridiseToolbar() for the table — CSS cannot move controls into
   panels that were never created.

   Boundaries are deliberate. 1200 is where the ≤1199 full collapse stops, so
   the two bands abut with no gap. 1899 is the last width that does not fit
   naturally; from 1900 the toolbar is simply inline, which keeps the supported
   1920×1080 minimum spec on the fully-expanded path. See
   [[reference_resolution_tiers]]. */
@media (min-width: 1200px) and (max-width: 1899px) {

    /* The toolbar is a wrapping flex row; the panels take a full row beneath it
       when open, exactly like the ≤1199 collapse does. */
    .dataTables_wrapper .top,
    .dataTables_wrapper .filter-length.dt-hybrid-toolbar {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 6px;
        max-width: 100%;
        box-sizing: border-box;
    }

    /* The toggle pair is a real .btn-group carrying .dt-control-button, so its
       fill, height and interior-seam behaviour all come from the same rules as
       the [colvis|copy|excel|pdf][New] cluster at the other end of the toolbar.
       Only the band-specific bits belong here. */
    .dt-hybrid-toggles {
        display: inline-flex;
        flex: 0 0 auto;
        align-self: flex-start;
    }

    /* Open state reads as pressed, so it is obvious WHICH disclosure is showing
       the row beneath — with two panels sharing one row that is not otherwise
       inferable. Bootstrap's .active would fight the group's own colouring, so
       the state is drawn from aria-expanded, which is already the truth source
       for the panel and needs no extra bookkeeping. */
    .dt-hybrid-toggles .dt-hybrid-toggle[aria-expanded="true"] {
        background-color: rgba(0, 0, 0, .18);
        box-shadow: inset 0 1px 3px rgba(0, 0, 0, .28);
    }

    [data-layout-mode="dark"] .dt-hybrid-toggles .dt-hybrid-toggle[aria-expanded="true"] {
        background-color: rgba(255, 255, 255, .16);
        box-shadow: inset 0 1px 3px rgba(0, 0, 0, .45);
    }

    /* In-band the panel becomes a real box again, so Bootstrap's collapse can
       show and hide it. Both rules carry !important to outrank the
       `display: contents !important` that governs outside the band. */
    .dt-hybrid-collapse {
        display: none !important;
        flex-basis: 100%;
        flex-wrap: wrap;
        align-items: center;
        gap: 8px;
        padding-top: 6px;
        max-width: 100%;
        box-sizing: border-box;
    }

    .dt-hybrid-collapse.show,
    .dt-hybrid-collapse.collapsing {
        display: flex !important;
    }

    /* Controls sit SIDE BY SIDE on the disclosed row, not stacked. There is
       ~1200px+ available here — stacking each control on its own line (the
       phone treatment) turned one disclosure into five rows and pushed the
       button group away from the toolbar it belongs to. `flex: 0 1 auto` lets
       each control take its natural width and shrink only if the row fills. */
    /* margin-left is zeroed deliberately. `.dataTables_wrapper .filter-length
       .dt-inline-filter` carries `margin-left: 1.25em` as the separator between
       filters sitting SIDE BY SIDE in the inline toolbar — correct there, but on
       a disclosed row the control is first on its line, so the separator becomes
       a stray indent that fails to line up with the row above it. Scoped to the
       panel, so the inline layout at ≥1900 keeps its spacing untouched. */
    .dt-hybrid-collapse .dataTables_filter,
    .dt-hybrid-collapse .dataTables_length,
    .dt-hybrid-collapse .dt-inline-filter {
        flex: 0 1 auto;
        margin-left: 0 !important;
        max-width: 100%;
        box-sizing: border-box;
    }

    .dt-hybrid-collapse .dt-inline-filter {
        display: flex;
        flex-wrap: nowrap;
        gap: 6px;
        align-items: center;
    }

    /* The date row's kind-selector has no intrinsic width (Select2 renders a
       span), so without a floor it collapses to nothing next to the pickers. */
    .dt-hybrid-collapse .dt-inline-filter .select2-container {
        flex: 0 0 auto;
        min-width: 11rem;
        width: auto !important;
    }

    .dt-hybrid-collapse .dt-inline-filter input.form-control {
        flex: 0 0 auto;
        width: 8.5rem;
    }

    .dt-hybrid-collapse .dataTables_filter input {
        margin-left: 6px;
        margin-right: 0;
    }

    /* The inline remainder must be allowed to shrink, or a wide Select2 keeps
       its natural width and re-creates the very overrun this band exists to
       fix. min-width:0 is what lets flex actually shrink a flex item. */
    .dt-hybrid-toolbar > .dt-inline-filter {
        min-width: 0;
        flex-shrink: 1;
    }

    .dt-hybrid-toolbar > .dt-inline-filter .select2-container {
        max-width: 100%;
    }

    /* Buttons hold the right edge, level with the toggles.
       .dt-buttons is a SIBLING of .filter-length inside .top, and .top wraps.
       Left alone, .filter-length grows to hold its open panel, fills the line,
       and pushes the button group onto a line of its own — [New] ends up
       stranded at the bottom of the toolbar, far from the controls it sits with
       at every other width.
       Clamping .filter-length to the space actually left over (`flex: 1 1 0`
       with min-width: 0) keeps both on ONE flex line: the toolbar's own rows
       come from .filter-length wrapping INTERNALLY, which is what the panels
       were built to do. align-self pins the group to the top of that taller
       line rather than centring it against three rows of controls. */
    .dataTables_wrapper .top > .filter-length.dt-hybrid-toolbar {
        flex: 1 1 0 !important;
        min-width: 0;
    }

    .dt-buttons {
        margin-left: auto;
        align-self: flex-start;
    }

    /* The disclosed panel takes its own line INSIDE .filter-length. */
    .dt-hybrid-toolbar > .dt-hybrid-collapse {
        order: 1;
    }

    /* Child rows exist in this band too (Responsive still collapses columns at
       1200–1899 on wide tables), so the (+) control needs the same clearance. */
    .table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
    table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control {
        padding-left: var(--dtr-control-clearance) !important;
        position: relative;
    }
}

/* ── Sidebar: mobile touch scrolling fix ──
   The base .vertical-menu is display:flex, and its SimpleBar host relies on
   inline `flex:1; min-height:0` to clamp to the fixed top:70px/bottom:0 box.
   app.min.css's mobile rule (body.sidebar-enable .vertical-menu{display:block})
   drops that flex context, so the host sizes to content, grows past the box,
   and never establishes a scrollport — touch then chains to the document.
   Re-assert flex here so the inline clamp is live again. */
@media (max-width: 992px) {
    body.sidebar-enable .vertical-menu {
        display: flex;
        flex-direction: column;
        z-index: 1005;
    }

    body.sidebar-enable .vertical-menu [data-simplebar] {
        overflow: auto !important;
        overscroll-behavior: contain;
    }

    body.sidebar-enable .vertical-menu .simplebar-content-wrapper {
        overflow: auto !important;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
    }
}

/* ── Collapsed rail: make the 70px icon rail scrollable, at every input type ──
   app.css collapses the rail with

       .vertical-collpsed .vertical-menu { position: absolute; }
       .vertical-collpsed .vertical-menu .simplebar-mask,
       .vertical-collpsed .vertical-menu .simplebar-content-wrapper {
           overflow: visible !important; }

   Both halves are deliberate: `absolute` lets the rail grow past the viewport and
   travel with the page scroll, and `overflow: visible` is what lets a hovered
   item's flyout submenu escape the 70px rail instead of being clipped by it.

   The `absolute` half only works while the PAGE is tall enough to scroll. On a
   short page, or on a touch device where the rail is taller than the screen,
   everything below the fold is simply unreachable -- there is nothing left to
   scroll. So the rail is pinned to the topbar/bottom box and given its own
   scrollport.

   WHERE that scrollport goes is the whole difficulty, and getting it wrong is
   how the first attempt regressed the flyouts. Putting `overflow-y: auto` on
   `.vertical-menu` works for scrolling but silently costs the x-axis: per spec an
   auto/visible pair computes BOTH axes to auto, so the menu became a clip box
   horizontally too and cut the 190px flyouts off at the rail. Measured against
   the pre-change baseline: one clipping ancestor before, two after.

   So the scroll goes on SimpleBar's own content wrapper -- the element the plugin
   is there to scroll -- and the two boxes ABOVE it in the chain are opened up so
   a flyout still has a path out:

     .simplebar-wrapper  hidden/hidden  ->  visible   (theme default; clips at 70px)
     .simplebar-mask     visible                      (already, from app.css)
     .simplebar-content-wrapper                       <- the scrollport lives here

   The flyout is `position: absolute` against the menu, so it is laid out OUTSIDE
   the scrolled content's flow and is unaffected by the wrapper's own scrolling.

   Not gated on pointer type: a short page strands the lower icons with a mouse
   exactly as it does with a finger, and the two cases want identical behaviour.
   Only >= 992px, because below that the rail is the full-width overlay menu with
   its own rules (see the sidebar-enable block above). */
@media (min-width: 992px) {
    body.vertical-collpsed .vertical-menu {
        position: fixed;
        top: 70px;
        bottom: 0;
    }

    /* The theme clips here at the rail's 70px. Opening it is what lets the flyout
       out; the scrollport below means nothing needs clipping at this level. */
    body.vertical-collpsed .vertical-menu [data-simplebar] > .simplebar-wrapper {
        overflow: visible !important;
    }

    body.vertical-collpsed .vertical-menu .simplebar-content-wrapper {
        overflow-x: visible !important;
        overflow-y: auto !important;
        max-height: calc(100vh - 70px);
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
        /* No visible scrollbar chrome: this is a 70px icon rail, and a native
           gutter both eats a fifth of its width and reads as a stray artefact
           against the dark rail. The rail scrolls by wheel, drag and touch
           regardless. */
        scrollbar-width: none;
    }

    body.vertical-collpsed .vertical-menu .simplebar-content-wrapper::-webkit-scrollbar {
        display: none;
    }

    /* Pinned to bottom:0 by app.css; must size to content for the scroll to
       have anything to measure. */
    body.vertical-collpsed .vertical-menu .simplebar-offset {
        bottom: auto !important;
    }

    /* The tenant signature is hidden in the collapsed rail. It is a text footer --
       `text-truncate` in a 70px box leaves an unreadable stub, and because the
       rail is only as tall as its icons the stub overlapped the last icon rather
       than sitting under it. Still present for the expanded rail, where it reads
       as intended. */
    body.vertical-collpsed .vertical-menu .sidebar-tenant-name {
        display: none;
    }

    /* The flyout has to leave the scrollport, or the rail's own scrolling clips
       it. `position: fixed` takes it out of every scrolling ancestor -- a fixed
       box is positioned against the viewport, so no ancestor's overflow can cut
       it. Verified by hit-test rather than by geometry alone: with this rule the
       flyout's own links are what document.elementFromPoint returns at the
       flyout's centre, which is the property that actually matters (it is
       clickable), and which the clipped version failed.

       `left: 70px` replaces the theme's `right: 70px`. That rule is written for
       the RTL build; in the LTR direction the app actually renders in, `right`
       against a viewport-sized containing block puts the panel off-screen. The
       rail is 70px, so the flyout starts exactly at its edge.

       `top` cannot come from CSS: once fixed, the panel no longer inherits the
       hovered item's position, and CSS has no way to read it. app.js sets it on
       mouseenter -- see positionCollapsedFlyouts(). The fallback here keeps the
       panel on screen for the first frame before that handler runs. */
    body.vertical-collpsed .vertical-menu #sidebar-menu > ul > li:hover > ul {
        position: fixed;
        left: 70px;
        right: auto;
        width: 190px;
        max-height: calc(100vh - 70px);
        overflow-y: auto;
        overscroll-behavior: contain;
    }
}

/* ── Dynamic Forms: Repeatable Fields ── */
.repeatable-container {
    position: relative;
}
.repeatable-item {
    position: relative;
    transition: border-color 0.2s;
}
.repeatable-item:not(:first-child) {
    border-left: 2px solid #0d6efd;
    padding-left: 10px;
    margin-top: 0.5rem;
}
.repeatable-item .repeatable-remove-btn {
    position: absolute;
    right: 4px;
    top: 4px;
    z-index: 1;
    padding: 0.15rem 0.35rem;
    line-height: 1;
}
.repeatable-add-btn {
    font-size: 0.8rem;
}
.repeatable-add-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ── Shepherd.js tour popover offset (11px gap from target) ── */
.shepherd-element[data-popper-placement^="bottom"] { transform: translateY(11px) !important; }
.shepherd-element[data-popper-placement^="top"]    { transform: translateY(-11px) !important; }
.shepherd-element[data-popper-placement^="left"]   { transform: translateX(-11px) !important; }
.shepherd-element[data-popper-placement^="right"]  { transform: translateX(11px) !important; }

/* Sidebar Menu Badges — Square shape with centered alignment */
#sidebar-menu ul li .badge {
    padding-top: 0.38em;
    padding-left: 0.48em;
    border-radius: 0.25rem; /* Standard Bootstrap rounded corner (not pill) */
}

/*
    Row-action outline buttons (Copy / Send link) in DataTables —
    soften the borders so they don't compete with the row content,
    and make the disabled state clearly faint (matching the inline
    View/Retract icon convention).
*/
.copy-link-btn.btn-outline-primary,
.send-link-btn.btn-outline-primary,
.btn-send-join-link.btn-outline-primary {
    border-color: rgba(108, 117, 125, 0.4); /* lighter blue-grey */
}

.copy-link-btn.btn-outline-primary:hover,
.send-link-btn.btn-outline-primary:hover,
.btn-send-join-link.btn-outline-primary:hover {
    border-color: rgba(108, 117, 125, 0.6);
}

.copy-link-btn.btn-outline-primary:disabled,
.send-link-btn.btn-outline-primary:disabled,
.btn-send-join-link.btn-outline-primary:disabled,
.copy-link-btn.btn-outline-primary.disabled,
.send-link-btn.btn-outline-primary.disabled,
.btn-send-join-link.btn-outline-primary.disabled {
    opacity: 0.2;
    pointer-events: none;
}

/*
    Logs — expanded log entry detail. Long messages (e.g. stack traces)
    used to overflow horizontally and produce a huge scrollbar. Keep
    original line breaks, wrap long tokens, and stay within the row.
*/
.entry-detail {
    max-width: 100%;
    overflow-x: hidden;
}

.entry-detail .wordwrap {
    display: block;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-wrap: anywhere;
    font-family: inherit;
}

/*
    Sidebar count badges — bring the `badge-soft-*` variant closer in hue
    to its solid sibling so paired items (Referrals/Patients, Visits/Forms)
    read as the same colour family. Default soft uses 0.18 alpha which
    drifts too far toward grey on the dark vertical-menu background.
*/
.vertical-menu .badge.badge-soft-success {
    background-color: rgba(52, 195, 143, 0.55);
    color: #ffffff;
}

.vertical-menu .badge.badge-soft-primary {
    background-color: rgba(85, 110, 230, 0.55);
    color: #ffffff;
}

/*
    Field-group repeater triggers: suppress the default focus ring / click outline
    on the inline (+) and (-) buttons that sit next to a repeatable group's title.
    They're rendered as .btn-link for styling only — there's no meaningful focus
    state to signal, so the box-shadow halo on click is just visual noise.
*/
.repeatable-group-add-btn,
.repeatable-group-remove-btn {
    box-shadow: none !important;
    outline: none !important;
    text-decoration: none;
}

.repeatable-group-add-btn:focus,
.repeatable-group-add-btn:focus-visible,
.repeatable-group-add-btn:active,
.repeatable-group-remove-btn:focus,
.repeatable-group-remove-btn:focus-visible,
.repeatable-group-remove-btn:active {
    box-shadow: none !important;
    outline: none !important;
}

/* ============================================================
   Select2 (bootstrap-5 theme) — match .form-control sizing.
   The vendor theme hard-codes font-size:1rem and a min-height
   computed from 1rem, which makes Select2 widgets visibly
   taller than sibling .form-control inputs (whose font-size
   inherits the page's 0.875rem body). Inheriting font-size and
   recomputing min-height from it puts Select2 flush with its
   neighbours and re-centers the placeholder vertically.
   ============================================================ */
.select2-container--bootstrap-5 .select2-selection {
    font-size: inherit;
    min-height: calc(1.5em + 0.75rem + 2px);
}

.select2-container--bootstrap-5 .select2-selection--single .select2-selection__rendered,
.select2-container--bootstrap-5 .select2-selection--single .select2-selection__rendered .select2-selection__placeholder {
    font-size: inherit;
    line-height: 1.5;
}

/* ============================================================
   Lucide iconography — design-system sizes.
   Lucide ships SVGs with width="24" height="24" attributes;
   we override to the design tokens:
     - 16px default (inline / table-action / buttons)
     - 20px in chrome (sidebar, topbar, page-title, avatar tiles)
   Buttons keep a subtle 130% hover bump in DataTables action
   columns, restoring the affordance the Boxicons era had.

   See DESIGN_SYSTEM.md §Iconography.
   ============================================================ */

/* Default sizing is em-based so Lucide SVGs follow the surrounding
   font-size. Bootstrap's `fs-X` utilities, `font-size-N` helpers, badge
   chrome, and headings then all scale icons naturally without per-context
   overrides. The 1em "default" is the same shape Boxicons had as a font.

   We deliberately do NOT clamp font-size here. Earlier I tried `font-size:
   1rem` as a "floor," but it overrode any parent font-size class on the SVG
   itself (e.g. `<i data-lucide class="display-3">` couldn't grow because
   svg.lucide and .display-3 both had 1-class specificity and svg.lucide,
   loaded later, won). Inheriting from the parent gives the right behavior
   in every context, including direct utility classes on the icon. */
svg.lucide {
    width: 1em;
    height: 1em;
    vertical-align: -0.18em;
    transition: transform 0.12s ease-out;
}

/* Sidebar menu icons — original Boxicons rule was on the <i>; re-state
   the relevant pieces for the post-Lucide <svg>. Kept here rather than as
   an `em` cascade because the sidebar's font-size belongs to the label
   text, not the icon (we want the icon to be larger than 13px label).
   The translateY(-1px) corrects a small baseline drop that vertical-align
   middle leaves on Lucide's 24x24 viewBox; the active rule below adds an
   extra -1px to compensate for the scale offset. */
#sidebar-menu ul li a svg.lucide {
    width: 20px;
    height: 20px;
    font-size: 20px;
    vertical-align: middle;
    min-width: 1.75rem;
    color: #7f8387;
    transform: translateY(-1px);
    transition: all 0.4s;
}

#sidebar-menu ul li a:hover svg.lucide {
    color: #383c40;
}

/* The currently-selected sidebar item: brand color + 1.30 scale pin, so it
   reads as "you are here" instead of just "darker." metisMenu writes
   `.mm-active` on the <li>; some active links also get `.active` directly.
   translateY(-2px) keeps the scaled-up icon visually aligned with the
   surrounding label text. */
#sidebar-menu ul li.mm-active > a svg.lucide,
#sidebar-menu ul li > a.active svg.lucide {
    color: var(--bs-primary);
    transform: translateY(-2px) scale(1.30);
    transform-origin: center;
}

/* Topbar / page-title — 20px chrome, vertical-align:middle so icons sit
   centered next to heading text rather than dropping to the baseline. */
#page-topbar svg.lucide,
.navbar-header svg.lucide,
.page-title-box svg.lucide {
    width: 20px;
    height: 20px;
    font-size: 20px;
    vertical-align: middle;
}

/* Avatar tiles hold a centered icon that should fill the inner area rather
   than sit at body-text size. Avatar tiles use flex centering, so vertical
   alignment is moot — reset to baseline. */
.avatar-title svg.lucide {
    width: 1.25rem;
    height: 1.25rem;
    font-size: 1.25rem;
    vertical-align: 0;
}

.avatar-xs .avatar-title svg.lucide {
    width: 1rem;
    height: 1rem;
    font-size: 1rem;
}

.avatar-sm .avatar-title svg.lucide {
    width: 1.375rem;
    height: 1.375rem;
    font-size: 1.375rem;
}

.avatar-lg .avatar-title svg.lucide,
.avatar-xl .avatar-title svg.lucide {
    width: 1.75rem;
    height: 1.75rem;
    font-size: 1.75rem;
}

/* Vertical pill-tabs whose icon sits ABOVE the label (Forms/New, Settings/Index,
   etc.). The legacy class .check-nav-icon set font-size:36px on the <i>;
   re-state for the SVG and add `display: block` + `margin: auto` so the
   icon centers horizontally within the tab pill. */
.check-nav-icon,
svg.lucide.check-nav-icon {
    width: 36px !important;
    height: 36px !important;
    font-size: 36px;
    display: block;
    margin-left: auto;
    margin-right: auto;
    vertical-align: 0;
}

/* Hover bump on icon buttons — restores the prior Boxicons-era affordance.
   Applies to .btn AND the <a> tags that DT-row action icons live in
   ("Face Sheet" / "Delete" / "Inspect" / "Update" — see e.g.
   patients-index.js renderActionButtons), since those are buttons in
   intent if not in tag. */
.btn svg.lucide,
td a > svg.lucide,
.dt-action svg.lucide {
    transition: transform 0.12s ease-out;
}

.btn:hover svg.lucide,
td a:hover > svg.lucide,
.dt-action:hover svg.lucide {
    transform: scale(1.30);
}

/* Badges: shrink the icon slightly and use vertical-align:middle so the
   line-box doesn't stretch to accommodate the SVG's descender area. Without
   this, my default vertical-align (-0.18em pushes the SVG below baseline)
   pads the badge taller than text-only sibling badges. */
.badge svg.lucide {
    width: 0.85em;
    height: 0.85em;
    vertical-align: -0.12em;
}

/* Form editor element-controls (the edit / delete / template overlay that
   appears on Section / Field hover). Position only — let icons inherit the
   global em-based sizing now that font-size cascades correctly. */
.element-controls {
    top: -0.06em;
    right: 4px;
}

/* Field-row indicator badges (Vertical stack, Repeatable, ...). Sizing for
   the badge chrome lives in form-editor.css under .field-stack-badge .badge
   (so its 2-class specificity wins over the global .badge svg.lucide rule);
   here we only size the icon inside and tighten line-height / vertical-align
   so the chip baseline-aligns with the adjacent .element-controls buttons.

   line-height is what sets these chips' height — the icon is the only content
   and its 1em box plus the badge's vertical padding leave the chip taller than
   the neighbouring edit / delete buttons at any line-height near 1. 0.7 pulls
   the chip down to match them. Verified in the browser; don't "correct" it back
   toward a conventional value without re-checking the rendered row. */
.field-indicator-badge {
    line-height: 0.7;
    vertical-align: top;
}

.field-indicator-badge svg.lucide {
    width: 1em;
    height: 1em;
    vertical-align: -0.10em;
}

/* Form editor toolbox — left-rail "Add Section / Text Field / ..." buttons.
   Original .toolbox-elements .btn i { margin-right: 6px } targeted <i> only;
   restate for the post-Lucide <svg> so the gutter between icon and label
   survives. The translateY hint applied to the sidebar icons isn't needed
   here — vertical-align:middle on the <svg> alongside text in a button
   gives a clean center on its own. */
.toolbox-elements .btn svg.lucide {
    margin-right: 6px;
    vertical-align: middle;
}

/* Search-box icon — the legacy rule (`.search-box .search-icon { font-size:
   16px; line-height: 38px; top: 0; }`) used line-height on the <i> as a
   vertical centering trick that doesn't apply to <svg>. Center via top/
   transform instead, and keep the gentle muted color the original had. */
.search-box .search-icon {
    line-height: 1;
    top: 50%;
    transform: translateY(-50%);
    color: #74788d;
}

/* === Dashboard / Practitioner Profile post-Dark-Mode tweaks === */

/* KPI mini-stat numeric value — switch the anchor from primary blue to white
   in dark mode for stronger contrast against the dark card body. Light mode
   keeps the default primary so the value still reads against a white card. */
[data-layout-mode="dark"] .card.mini-stats-wid .card-body h4 a,
[data-layout-mode="dark"] .card.mini-stats-wid .card-body h4 a:hover,
[data-layout-mode="dark"] .card.mini-stats-wid .card-body h4 a:focus {
    color: #fff;
}

/* Activities & Alerts card — drop the title-area bottom border and the
   pagination footer's top border for a cleaner card. */
#activities-alerts-card > .card-header {
    border-bottom: 0 !important;
}
#activities-alerts-card .card-body div.border-top {
    border-top: 0 !important;
}

/* Today in Healthcare (DashboardNewsFeed) — strip the inter-item separator
   borders that list-group-flush draws between rows. */
.card .list-group-flush > .list-group-item {
    border-top: 0 !important;
    border-bottom: 0 !important;
}

/* Patient Treatment by Discipline — remove the per-row table separators on
   the AJAX-rendered breakdown table. */
#discipline-breakdown table > tbody > tr > td,
#discipline-breakdown table > tbody > tr {
    border-bottom-width: 0 !important;
    border-top-width: 0 !important;
}

/* Practitioner Profile "Recent activity" — drop the top border above the
   pagination footer. The `_RecentActivity.cshtml` partial wraps that footer
   in a div with `pt-2 border-top`. */
.recent-activity-list + .d-flex.border-top {
    border-top: 0 !important;
}

/* === Patient Face Sheet post-Dark-Mode tweaks === */

/* Patient Summary — drop the divider below the profile-image / name block.
   The card itself draws a hairline at the card edges; the inner section
   border was redundant visual noise. The other border-bottom (alert-flag
   banner) is kept since it separates a colored banner from the body. */
#patientSummaryCard > .card-body.border-bottom {
    border-bottom: 0 !important;
}
/* Re-restore the alert-flag banner divider that the rule above just zeroed. */
#patientSummaryCard > #patient-alert-flag-banner.border-bottom {
    border-bottom: 1px solid var(--bs-border-color) !important;
}

/* Activities & Alerts (Face Sheet variant — no #activities-alerts-card id).
   Drop the title-area bottom border and the pagination footer's top border.
   Scoped to `.facesheet-section` cards that have a `.card-header`, which
   on this view is uniquely _PatientActivitiesCard. */
.facesheet-section > .card-header.border-bottom {
    border-bottom: 0 !important;
}
#patient-alerts-wrapper div.border-top {
    border-top: 0 !important;
}

/* Contact Information / Insurance / Emergency Contacts subcards — these
   render as `<div class="border p-3 rounded">` inside a parent
   `.facesheet-section > .card-body`. Strip the inner border so the parent
   card's hairline carries the organization, and zero the per-row `<li>`
   separators inside Emergency Contacts and Insurance. Padding is kept
   so the content still has breathing room. */
.facesheet-section > .card-body .border.p-3.rounded,
#facesheet-payors > .border.p-3.rounded {
    border: 0 !important;
}
.facesheet-section > .card-body .border.p-3.rounded ul.list-unstyled > li.border-bottom,
#facesheet-payors ul.list-unstyled > li.border-bottom {
    border-bottom: 0 !important;
    padding-bottom: 0 !important;
}

/* Forms & Visits (JS-rendered into `#facesheet-forms-chart` and
   `#facesheet-visits-chart`). Each card is a `.border.p-3.mini-stats-wid`
   stacked pair (Forms on top, Visits below with `border-top-0`), with an
   inner `.border-top.mt-2.pt-2` row separating headline from badges. The
   parent `.facesheet-section` card already provides the hairline frame —
   strip the outer subcard borders and the inner separator. */
#facesheet-forms-chart > .border,
#facesheet-visits-chart > .border {
    border: 0 !important;
}
#facesheet-forms-chart .border-top,
#facesheet-visits-chart .border-top {
    border-top: 0 !important;
}

/* Visit Stats / Form Stats sidebars — `MiniCharts.renderRadialCards()` builds
   a `<table class="table">` whose default Bootstrap `border-bottom-width: 1px`
   on `<td>` cells draws hairlines between every stat row. Zero those so the
   rows read as a clean stack inside the sidebar card. */
#mini-charts-container > .table-responsive > table.table > tbody > tr > td {
    border-bottom-width: 0 !important;
    border-top-width: 0 !important;
}

/* Referrals / Patients top mini-charts — the `buildCardHtml` helper renders
   each card's subtitle/badge footer as `<div class="card-body border-top py-3">`.
   Drop that internal divider; the `.mini-stats-wid` card's outer hairline
   already frames the card. */
.mini-stats-wid > .card-body.border-top {
    border-top: 0 !important;
}

/* `.nav-tabs` bottom border — drop sitewide. In dark mode the
   `--bs-nav-tabs-border-color: #3a425a` paints a clearly visible line that
   competes with the active-tab indicator and adds extraneous chrome; the
   active underline alone carries the affordance just fine. Removed in
   light mode too for consistency (the line was near-invisible there
   anyway). */
.nav-tabs,
.nav-tabs-custom {
    border-bottom: 0 !important;
}

/* Modal header — Bootstrap's default `.modal-header` border-bottom is
   distracting against the otherwise clean modal chrome. The header's
   typographic weight + the close button on the right are sufficient to
   delineate the title area from the body. */
.modal-header {
    border-bottom: 0 !important;
}

/* Draggable modals — the header is the grab handle (initModalDrag in app.js). Bootstrap
   ships no draggable option, so the affordance is ours to provide: the cursor is the only
   hint the header can be grabbed at all. Suppressed below 992px, matching the JS, where
   the dialog nearly fills the viewport and dragging only loses it. */
@media (min-width: 992px) {
    .modal-header {
        cursor: grab;
        user-select: none;
    }

    /* The controls in the header keep their own cursors — a grab hand over the close
       button reads as "drag me" on the one thing that isn't draggable. */
    .modal-header .btn-close,
    .modal-header button,
    .modal-header a {
        cursor: pointer;
        user-select: auto;
    }

    .modal-dialog.modal-dragging {
        cursor: grabbing;
        transition: none;   /* Bootstrap's open transform-transition would lag the drag */
    }

    .modal-dialog.modal-dragging .modal-header {
        cursor: grabbing;
    }
}

/* ============================================================
   Floating modals — user preference, Preferences > Modal style.

   Companion to the draggable modals above: dragging a dialog is only worth
   doing if you can see what you're dragging it over, and the 50% black scrim
   defeats that. So the scrim goes and elevation carries the separation
   instead — the dialog reads as lifted off the page rather than punched
   through a dark sheet. Spiritually the calendar's event modal, applied app-wide.

   Everything here is gated on `body[data-modal-style="floating"]`, set by
   initSettings() in app.js from the UiStateProperties_string_ModalStyle
   preference. Absent the attribute — which is the default, and what every
   user sees until they opt in — none of this matches and Bootstrap's stock
   dimmed backdrop is untouched. Same shape as data-table-density above.

   The backdrop element is NOT removed. Bootstrap owns its lifecycle, and it
   still does real work: it's the click-to-dismiss target, and the `modal-open`
   body class that locks page scroll is tied to the same machinery. Removing it
   in JS would mean re-implementing dismissal and scroll-lock by hand. Making
   it transparent keeps every behavior and changes only the paint.

   STACKING — the case you flagged last time:

   Bootstrap appends one backdrop PER open modal, and they accumulate. With the
   default scrim, two stacked modals darken twice (0.5 over 0.5 = 0.75). Fully
   transparent has the opposite failure: a sub-modal over a parent modal has
   nothing between them, so two cards float at similar elevations and the eye
   can't tell which is on top or that the one behind is inert.

   So: the FIRST backdrop is invisible, and any SUBSEQUENT one gets a faint
   scrim. `.modal-backdrop ~ .modal-backdrop` does this in CSS alone — no JS
   counting, and it self-corrects as modals open and close. The result is that
   a lone modal floats over an undimmed page, while a sub-modal visibly mutes
   its parent. Stacking stays legible without ever double-darkening, since only
   the topmost scrim is perceptible against what's below it.

   The general sibling combinator is deliberate over `:not(:first-of-type)`:
   Bootstrap appends backdrops to <body> (Backdrop's `rootElement: 'body'`),
   where they are NOT the only div siblings — #layout-wrapper is the first, so
   :first-of-type would match the layout instead and every real backdrop would
   take the stacked treatment. `~` counts only backdrops, whatever else shares
   the parent.

   The three JS popups that hand-manage stacking z-index (patient-info-popup,
   practitioner-info-popup, calendar-index) set `zIndex` inline on the last
   backdrop. They don't touch background-color, so they compose with this
   untouched — they reorder the same elements this recolors. */
body[data-modal-style="floating"] .modal-backdrop {
    --bs-backdrop-opacity: 1;   /* opacity would fade the whole element; tint via rgba below */
    background-color: transparent;
}

/* A stacked modal's backdrop mutes the modal beneath it. Warm ink rather than
   black — a neutral scrim over the warm paper ramp reads as a dirty gray.

   The `~` pair sits INSIDE the body selector, not after it: the backdrops are
   siblings of each other and children of <body>, so the descendant combinator
   does the scoping and the sibling combinator does the counting. */
body[data-modal-style="floating"] .modal-backdrop ~ .modal-backdrop {
    background-color: rgba(23, 19, 14, 0.32);
}

/* Elevation replaces the scrim as the "this floats above the page" signal, so
   the shadow does the work the dimming used to. Warm ink to match the palette,
   and layered — a tight contact shadow for the edge plus a wide soft one for
   the lift. A single large blur reads as haze; two reads as an object.

   On .modal-content, not .modal-dialog: the dialog is the element the drag
   translates, and its box is wider than the visible card on centered/sized
   modals, which would float the shadow away from the card's edge. */
body[data-modal-style="floating"] .modal-content {
    box-shadow: 0 2px 6px rgba(23, 19, 14, 0.16),
                0 18px 48px -8px rgba(23, 19, 14, 0.34);
}

/* Lifted while dragging — the shadow deepens and spreads as if the card were
   picked up further off the page. Not transitioned: the drag itself sets
   `transition: none` on the dialog to stay responsive, and a shadow easing in
   over a moving card lags visibly behind it. */
body[data-modal-style="floating"] .modal-dialog.modal-dragging .modal-content {
    box-shadow: 0 4px 10px rgba(23, 19, 14, 0.18),
                0 28px 64px -8px rgba(23, 19, 14, 0.40);
}

/* Dark mode: the paper-on-dark contrast that carries the light-mode shadow
   isn't available, so the lift comes from a hairline catching the light at the
   card's edge, with the shadow deepened underneath it. Pure black rather than
   warm ink — against an already-dark surface, warm ink is too close to the
   background to register as shadow at all. */
body[data-modal-style="floating"][data-layout-mode="dark"] .modal-content {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06),
                0 2px 6px rgba(0, 0, 0, 0.40),
                0 18px 48px -8px rgba(0, 0, 0, 0.62);
}

body[data-modal-style="floating"][data-layout-mode="dark"] .modal-dialog.modal-dragging .modal-content {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.09),
                0 4px 10px rgba(0, 0, 0, 0.44),
                0 28px 64px -8px rgba(0, 0, 0, 0.70);
}

body[data-modal-style="floating"][data-layout-mode="dark"] .modal-backdrop ~ .modal-backdrop {
    background-color: rgba(0, 0, 0, 0.42);
}

/* Below the drag breakpoint the dialog nearly fills the viewport, so there's
   little page left to see and the shadow has nothing to cast against. Restore
   the scrim there — it's doing its original job on small screens, where the
   experiment's premise (see the page around the modal) doesn't hold.

   The stacked selector is repeated here rather than relying on the plain one:
   the sibling pair outweighs the single-class rule, so without a matching
   override the stacked backdrop would keep its 0.32 tint on mobile instead of
   returning to the full scrim. Each selector below carries the same
   body[data-modal-style] qualifier as the rule it overrides — dropping it
   would lose the specificity race and the override would silently no-op. */
@media (max-width: 991.98px) {
    body[data-modal-style="floating"] .modal-backdrop,
    body[data-modal-style="floating"] .modal-backdrop ~ .modal-backdrop {
        background-color: rgba(23, 19, 14, 0.5);
    }

    body[data-modal-style="floating"][data-layout-mode="dark"] .modal-backdrop,
    body[data-modal-style="floating"][data-layout-mode="dark"] .modal-backdrop ~ .modal-backdrop {
        background-color: rgba(0, 0, 0, 0.6);
    }
}

/* Read-only mode (Suspended/Cancelled subscription) — hide write-action triggers as a
   convenience layer. The server-side ReadOnlyEnforcementFilter is the source of truth; this
   just declutters the UI. `body.read-only` is set by _Layout.cshtml. Add `js-write-action`
   to any bespoke create/edit/delete trigger to have it hidden here too. */
body.read-only #btn-new-schedule,
body.read-only .js-write-action {
    display: none !important;
}

/* ============================================================
   Brand mark — inline SVG + wordmark in the header and login.
   Brand, not theme: renders identically under both Serenity and
   Incandescence (theme-specific auth flips live in
   brand-incandescence.css).
   ============================================================ */

.brand-inc {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
}

.brand-inc svg {
    width: 30px;
    height: 30px;
    flex-shrink: 0;
}

/* Topbar brand: center the mark against the 70px bar.
   app.min.css centers .logo with `line-height: 70px`, which only works for
   inline text. .brand-inc is inline-flex, so the line box aligns it on its
   BASELINE, not its centre — and an inline-flex box's baseline comes from its
   first flex item (an SVG, which has none), landing the mark above centre.

   The fix belongs on the BOX, not the anchor. .navbar-brand-box has no height
   of its own and sits in a flex row under align-items:center, so it collapses
   to its content and there is nothing for the anchor to centre against —
   height on .logo alone just makes a 70px child inside a 70px box, which
   re-centres nothing and drifts further up. Stretching the box to the full bar
   height first, then centring the anchor inside it, gives the mark the same
   70px reference the hamburger gets from .header-item. */
.navbar-brand-box {
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    height: 70px;
}

.navbar-brand-box .logo {
    display: flex;
    align-items: center;
    line-height: 1;
}

.brand-inc .brand-inc-word {
    font-family: "Fraunces", Georgia, serif;
    font-weight: 600;
    font-size: 1.2rem;
    letter-spacing: -0.01em;
    line-height: 1;
}

.logo-light .brand-inc .brand-inc-word { color: #f7f3eb; }
.logo-dark .brand-inc .brand-inc-word { color: #17130e; }

.auth-logo .brand-inc .brand-inc-word {
    color: #17130e;
    font-size: 1.45rem;
}

.auth-logo .brand-inc svg {
    width: 32px;
    height: 32px;
}

/* ============================================================
   Theme-neutral micro-refinements promoted from the incandescence
   sweep — geometry, not color; both themes get them.
   ============================================================ */

/* Small icon-only buttons — optical centering. The global svg.lucide
   vertical-align (-0.18em) is tuned for text-adjacent baselines; bare
   icons in btn-sm sit visually low without this shallower value. */
.btn-sm svg.lucide.centered-bx-control,
.btn-sm[data-repeater-delete] svg.lucide,
.btn-sm[data-nested-repeater-delete] svg.lucide,
.btn-sm.remove-repeater-item svg.lucide {
    vertical-align: -0.08em;
}

/* Sidebar count badges — line-height:1 reserves descender space digits
   never use, so glyphs sit high of geometric center; ~0.04em more top
   than bottom padding pushes them to the optical center. Horizontal
   values restore symmetry against Bootstrap's 0.4em default. */
#sidebar-menu ul li .badge {
    padding: 0.38em 0.48em 0.34em;
}

/* Activity charts (dashboard + practitioner profile) drill through to the matching list
   view on click, so the bars need to read as actionable. ApexCharts exposes no cursor
   option, and the bars are SVG paths inside the series group — target them directly.
   Scoped to the two clickable charts so no other Apex chart picks up a false affordance. */
#stacked-column-chart .apexcharts-bar-area,
#practitioner-activity-chart .apexcharts-bar-area {
    cursor: pointer;
}

/* Footer version marks — serenity styling; brand-incandescence.css
   restyles these (ink/amber) with the same selectors, winning by
   later load. */
.footer .footer-brand-word {
    color: #495057;
    font-weight: 600;
}

.footer .footer-v5 {
    color: #556ee6;
    font-weight: 600;
}

[data-layout-mode=dark] .footer .footer-brand-word {
    color: #a6b0cf;
}

[data-layout-mode=dark] .footer .footer-v5 {
    color: #f1b44c;
}

/* ============================================================
   PORTAL — dashboard welcome callout
   Orientation card for newly-granted portal logins (see
   Views/Portal/_PortalWelcomePartial.cshtml). The portal layout does NOT
   load brand-incandescence.css, so every brand value here reads through a
   var(--r5-*, <classic>) fallback: warm ember when the brand layer is
   present, Serenity indigo when it is not.
   ============================================================ */

.portal-welcome-card {
    background-color: var(--r5-accent-softer, rgba(85, 110, 230, 0.05));
    border-left: 3px solid var(--r5-accent, #556ee6) !important;
}

.portal-welcome-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background-color: var(--r5-accent-soft, rgba(85, 110, 230, 0.1));
    color: var(--r5-accent, #556ee6);
}

.portal-welcome-icon svg {
    width: 1.35rem;
    height: 1.35rem;
}

.portal-welcome-title {
    color: var(--bs-heading-color, #343a40);
    font-weight: 600;
}

/* The dismiss control is a quiet affordance, not a call to action */
.portal-welcome-dismiss {
    opacity: 0.4;
    font-size: 0.7rem;
}

.portal-welcome-dismiss:hover,
.portal-welcome-dismiss:focus {
    opacity: 0.9;
}

[data-layout-mode=dark] .portal-welcome-card {
    background-color: rgba(255, 255, 255, 0.04);
}

[data-layout-mode=dark] .portal-welcome-title {
    color: #e8e1d3;
}

[data-layout-mode=dark] .portal-welcome-dismiss {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* ============================================
   Collapsible Cards
   Card-level sibling of the form-section collapse in form-density.css. The
   chevron rotates purely off Bootstrap's `.collapsed` class on the trigger —
   no icon swapping, so nothing to re-render and no lucide re-seal needed.
   ============================================ */
.card-collapse-chevron {
    transition: transform 0.2s ease;
    display: inline-block;
    width: 0.85em;
    height: 0.85em;
    opacity: 0.55;
    vertical-align: -0.1em;
}

.card-collapse-toggle.collapsed .card-collapse-chevron {
    transform: rotate(-90deg);
}

/* The title becomes a button; strip every button affordance so it still reads
   as a heading. Inheriting font/color is what keeps <h4 class="card-title">
   and <h5 class="card-title"> looking identical to their non-collapsible peers. */
.card-collapse-toggle {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    box-shadow: none;
    cursor: pointer;
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
}

.card-collapse-toggle:hover .card-collapse-chevron,
.card-collapse-toggle:focus-visible .card-collapse-chevron {
    opacity: 1;
}

.card-collapse-toggle:focus-visible {
    outline: 2px solid rgba(150, 24, 47, 0.35);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Collapsed, every card should settle to the SAME height regardless of which
   idiom it uses. The title-to-content gap is authored inconsistently across the
   app — mb-0/mb-3/mb-4 on the .card-title in some cards, on a flex wrapper around
   it in others — and collapsed, that margin is a gap to nothing. JS tags the
   element that actually sits above the collapsed region as .card-collapse-heading,
   so this zeroes the right one either way rather than guessing at the nesting.

   Zeroed at ALL times, not just when collapsed, and the gap is re-created on the
   collapsible wrapper below instead. Toggling this margin with the class moved the
   content 24px in a single frame at the end of an expand (mb-4 = 1.5rem restored
   the instant .card-collapsed came off) — the content-settling shift. Owning the
   gap on the wrapper means it animates WITH the height instead of snapping. */
.card[data-collapsible-card] .card-collapse-heading,
.card[data-collapsible-card] .card-collapse-heading .card-title {
    margin-top: 0 !important;
    margin-bottom: 0 !important;
}

/* The title-to-content gap, now on the animating element so it grows and shrinks
   with the collapse rather than appearing all at once. Bootstrap animates
   height only, so padding here is inside the measured box and rides along. */
.card[data-collapsible-card] > .card-body > [id^="cardcollapse-"],
[data-collapsible-panel] > [id^="cardcollapse-"] {
    padding-top: 1.5rem;
}

/* Bootstrap's .card-header padding is tighter than .card-body's, which left the
   two tabbed cards (Activities & Alerts, Billing) 16px shorter than their
   body-idiom neighbours collapsed. Match .card-body's 1.25rem/1rem.

   Applied at ALL times, not only when collapsed. Anything that changes with the
   .card-collapsed class necessarily changes in ONE FRAME — JS adds the class on
   `hidden.bs.collapse` and removes it on `shown.bs.collapse`, i.e. at the
   animation's boundaries — so a state-scoped padding jump reads as the title
   visibly hopping after the height has finished moving. Holding the padding
   constant costs 6px of expanded header padding and removes the hop entirely. */
.card[data-collapsible-card] > .card-header.card-collapse-heading {
    padding-top: 1.25rem;
    padding-bottom: 1rem;
}

/* The border, unlike the padding, is safe to toggle: it occupies no layout box of
   its own here (the header keeps its padding either way), so removing it collapsed
   changes only the paint, never the geometry. */
.card-collapsed > .card-header.card-collapse-heading {
    border-bottom: 0;
}

/* HIDE the sibling controls when collapsed — tab strips, period pills, kebab
   menus, "View All" buttons — rather than squashing them into the title's line box.

   This replaces an earlier max-height clamp, and is better on both counts. The
   clamp had to squeeze a 37px control into 19px, so releasing it sprang the heading
   back open in one frame and dropped the title (Personal Information, Latest
   Invoices) or the pills themselves (Patient Activity). It also could not be made
   permanent, because at full height it clipped the expanded .nav-tabs underline.

   Hiding sidesteps all of it: display:none takes the control out of flow entirely,
   so the collapsed heading is exactly the title's own line box with nothing to
   spring back, and the expanded control is never touched. It reads better too — a
   collapsed card's tabs and date filters have nothing to act on.

   The nested FaceSheet panels are deliberately exempt: their heading is an
   avatar + title + kebab that already collapses without any shift, and the
   coloured avatar is identity, not a control — hiding it would make the three
   collapsed panels look alike.

   Target the control TYPES rather than "every sibling of the title". Structure
   varies too much for a positional rule: Latest Invoices puts its button directly
   beside the title, while the tabbed cards wrap title AND tabs together in one
   d-flex, so the heading has a single child and a sibling rule either hides
   nothing or hides the title with it (which rendered a blank bar). Descendant
   selectors on the actual controls work regardless of nesting depth. */
.card-collapsed:not([data-collapsible-panel]) .card-collapse-heading :is(
    .nav,              /* tab strips and pill groups (Activities & Alerts, Billing, Patient Activity) */
    .dropdown,         /* kebab menus (Personal Information) */
    .btn,              /* header actions ("View All" on Latest Invoices) */
    .ms-auto,          /* the wrapper Patient Activity pushes its pills into */
    .flex-shrink-0     /* the wrapper Latest Invoices puts its button in */
) {
    display: none !important;
}

/* With the controls out of flow the heading holds only the title button, so a
   plain line box is all that is needed for every collapsed card to settle to the
   same height — no flex-wrap pinning, and nothing applied while expanded. */
.card-collapsed:not([data-collapsible-panel]) .card-collapse-heading {
    min-height: 1.1875rem; /* 19px — the natural height of the title button */
    justify-content: flex-start;
}

/* Same reasoning as the header padding: constant, not state-scoped. This sits
   BELOW the animating wrapper, so shrinking it only when collapsed made the whole
   card body jump 4px at the instant the class flipped — visible as the content
   settling downward at the end of an expand, and upward at the start of a
   collapse. The nested FaceSheet panels never showed the effect precisely because
   they have no .card-body for this rule to catch. */
.card[data-collapsible-card] > .card-body {
    padding-bottom: 1rem;
}

/* Panel idiom (FaceSheet's nested Contact Information / Addresses / Emergency
   Contacts): the panel IS its own body, so there is no .card-body to re-pad —
   its own p-3 already sets the collapsed height. The heading here is the avatar
   row, whose 32px avatar is the tall item, so it gets its own line box rather
   than the 19px one used for card titles. */
.card-collapsed[data-collapsible-panel] .card-collapse-heading {
    min-height: 2rem;
}

.card-collapsed[data-collapsible-panel] .card-collapse-heading > *:not(.card-collapse-toggle) {
    max-height: 2rem;
}

/* ══════════════════════════════════════════════════════════════
   PHONE: LABEL/VALUE TABLES THAT MUST STACK
   ══════════════════════════════════════════════════════════════
   Two-column "label : value" tables read fine on a desktop and not at all on a
   phone. The label column sizes to its longest label ("Disciplines requested",
   "Contact information") and the value column gets whatever is left — which at
   393px is not enough, so `.table-responsive` does its job and scrolls the value
   column off-screen. The user sees labels and no data.

   Below sm these become stacked blocks: label on its own line, value beneath it
   at full width. Nothing is hidden and nothing scrolls sideways. Above sm the
   markup is untouched and the two-column table is exactly as authored. */
@media (max-width: 575.98px) {

    /* ── Referrals > Respond modal, "Patient information" ──
       The labels used to carry an inline `white-space: nowrap` (ten of them).
       That inline style is what forced the table's min-content width past the
       viewport, so it was removed from the markup rather than fought here with
       an !important — the nowrap is a desktop nicety, and it now lives on the
       class below where a media query can switch it off.

       Only the value cells become full-width blocks; the label <th> keeps its
       own line above. The `+ td` sibling form means an unmatched cell is left
       alone, so any future single-cell row still renders normally. */
    .respond-detail-table > tbody > tr,
    .respond-detail-table > tbody > tr > th,
    .respond-detail-table > tbody > tr > td {
        display: block;
        width: auto;
    }

    .respond-detail-table > tbody > tr {
        padding-bottom: 0.5rem;
    }

    /* padding-left is zeroed along with the rest: the theme gives table cells a
       horizontal padding that reads as an indent once the cell is a block, and
       the <th> and <td> pick up different amounts — leaving the label visibly
       inset from its own value. */
    .respond-detail-table > tbody > tr > th {
        padding: 0 0 0.125rem 0;
        font-size: 0.8125rem;
        color: var(--bs-secondary-color);
        font-weight: 500;
    }

    .respond-detail-table > tbody > tr > td {
        padding-top: 0;
        padding-left: 0;
        overflow-wrap: anywhere;
    }

    /* The wrapper is only there to scroll a too-wide table. Once the table
       stacks there is nothing to scroll, and leaving `overflow-x: auto` on
       would let a stray wide child (a long email address) reintroduce the
       sideways scroll this rule exists to remove. */
    .table-responsive:has(> .respond-detail-table) {
        overflow-x: visible;
    }
}

/* Above the stacking width the labels keep the single-line treatment they had
   as an inline style. Stated once, in one place, instead of ten times in the
   markup. */
@media (min-width: 576px) {
    .respond-detail-table > tbody > tr > th {
        white-space: nowrap;
    }
}

/* ══════════════════════════════════════════════════════════════
   PHONE: TREATMENT RECORD MODAL
   ══════════════════════════════════════════════════════════════
   The View/Edit Treatment Record modal is `modal-xl` and holds four tabs of
   desktop-shaped content. Two shapes inside it overflow a phone, and neither
   was reachable by the existing repeater or table-restack rules:

     * the Treatment Frequencies row, which is a SENTENCE assembled from inline
       pixel widths and so matches no `col-*` selector; and
     * the Visits / Form Instances tables, which are plain server-rendered
       tables with no class of their own.

   Both are given a hook in the markup rather than being reshaped there, so the
   desktop layout stays byte-for-byte what it was. */
@media (max-width: 575.98px) {

    /* ── Treatment Frequencies ──
       The row reads "[4] times per [Week] for [7] [Days]  [Service] [x]" with
       widths of 90 + 60 + 130 + 28 + 90px hardcoded inline. That is 398px of
       fixed columns before the two flexible selects and the 36px delete button
       — wider than the phone itself, so the tail of the sentence hung off the
       right edge of the modal.

       Release the flex containers to wrap and drop every inline width to full
       width. `!important` is required: these are inline `style` attributes,
       which no plain declaration can outrank. The `> div` reach is two levels
       because the row nests a flex:3 group (the sentence) inside a flex:2 group
       (service + delete). */
    .frequency-grid,
    .frequency-grid > div > .d-flex {
        flex-wrap: wrap;
        row-gap: 0.5rem;
    }

    .frequency-grid > div,
    .frequency-grid > div > .d-flex > div {
        flex: 0 0 100% !important;
        width: 100% !important;
        max-width: 100% !important;
    }

    /* "times per" and "for" are grammatical glue between two fields. Once the
       fields stack they are joining nothing, and a centred orphan word between
       two inputs reads as a stray label. The sentence is reconstructed by the
       field labels below, so the glue is dropped. */
    .frequency-grid .frequency-conjunction {
        display: none;
    }

    /* Each field takes the heading it lost when the aligned header row above it
       was hidden. Rendered from data-field-label so the text lives in the markup
       next to the field it names, not in this stylesheet. */
    .frequency-grid [data-field-label]::before {
        content: attr(data-field-label);
        display: block;
        margin-bottom: 0.125rem;
        font-size: 0.8125rem;
        font-weight: 500;
        color: var(--bs-secondary-color);
    }

    /* The header row's whole purpose is horizontal alignment with the fields
       below it. With the fields stacked it aligns with nothing.

       `!important` because the element also carries Bootstrap's `.d-flex`, and
       every Bootstrap display utility ships as `display: flex !important` — a
       plain declaration here loses no matter how specific the selector is. */
    .frequency-grid-head {
        display: none !important;
    }

    /* The delete button is the one item that should NOT become full width — a
       full-bleed trash button at the end of every frequency block invites the
       exact mistap it should discourage. */
    .frequency-grid > div > .d-flex > div:has(> [data-repeater-delete]) {
        flex: 0 0 auto !important;
        width: auto !important;
    }

    /* ── Visits / Form Instances / Medications tables ──
       Six columns each. Restacked into one labelled block per row, matching the
       .line-items-table and .claim-lines-table treatments already in this file.

       `display: block` is applied to tbody/tr/td but deliberately NOT to the
       table element itself. patients-facesheet.js calls .hide()/.show() on the
       Medications table, and jQuery's .show() writes an inline `display: table`
       that no stylesheet declaration can outrank without !important — which would
       in turn break the hiding. Leaving the table as a table and restacking only
       its innards gives the same result and stays out of that fight. */
    .tr-stack-table > tbody,
    .tr-stack-table > tbody > tr,
    .tr-stack-table > tbody > tr > td {
        display: block;
        width: auto;
    }

    .tr-stack-table > thead {
        display: none;
    }

    .tr-stack-table > tbody > tr {
        padding: 0.5rem 0;
        border-bottom: 1px solid var(--bs-border-color);
    }

    .tr-stack-table > tbody > tr:last-child {
        border-bottom: 0;
    }

    .tr-stack-table > tbody > tr > td {
        display: flex;
        align-items: baseline;
        gap: 0.75rem;
        border: 0;
        padding: 0.1875rem 0;
    }

    .tr-stack-table > tbody > tr > td::before {
        content: attr(data-label);
        flex: 0 0 7rem;
        font-size: 0.8125rem;
        color: var(--bs-secondary-color);
    }

    /* The value half takes the remaining width and may break a long form title
       or practitioner name; without min-width:0 a flex item refuses to shrink
       below its content and the row would overflow again. */
    .tr-stack-table > tbody > tr > td > * {
        min-width: 0;
        overflow-wrap: anywhere;
    }

    /* Nothing left to scroll once the table is a stack. */
    .table-responsive:has(> .tr-stack-table) {
        overflow-x: visible;
    }
}

/* ══════════════════════════════════════════════════════════════
   PHONE: STOP THE CONTROL COLUMN'S PADDING STARVING ITS TEXT
   ══════════════════════════════════════════════════════════════
   Existing rules further up make the surviving Responsive column break long
   tokens (`overflow-wrap: anywhere` on td.dtr-control) so a 43-character 837
   filename cannot push the table off screen. That is right, and it stays.

   What it does not account for is how little room that cell may actually have.
   The control column also carries --dtr-control-clearance (27px) of left padding
   to clear the (+) circle, plus 12px of right padding. When DataTables gives the
   column a narrow natural width, almost all of it is padding:

       measured at 393px
         Reference > HCPCS    column  80px  →  41px of usable text
         Intake > Documents   column  88px  →  49px of usable text

   At 41px `anywhere` has no word boundary to prefer and simply shreds the value:
   the 5-character code "A0021" came apart over three lines, and "HIPAA
   Information & Consent Form" ran to eight. Meanwhile the neighbouring column
   sat half empty, which is what makes it read as a bug rather than tight spacing.

   The obvious fix — a `min-width` floor on the cell — was tried and REJECTED, and
   the reason is worth keeping. Responsive decides column-by-column whether a
   column fits or gets folded into the (+) child row, and it makes that decision
   from the widths this stylesheet hands it. ANY floor, from 1.5rem up, pushed
   HCPCS past the threshold and folded Short Description away entirely: measured
   1.5 / 2.5 / 3.5 / 4.5 / 5.5 / 6.5rem all produced the identical single-column
   result. Trading a visible data column for one line of wrapping is a bad deal,
   and it is invisible in a diff — the rule looks like it only sets a width.

   So instead of demanding more column, take back the space already inside it.
   The 27px clearance exists to keep text clear of the (+) circle, which is a
   FIRST-LINE problem: the circle is one glyph tall at the top of the cell. Lines
   two onward have nothing to clear, so they can start at the cell edge, and the
   text gets its width back without the column growing by a pixel — Responsive's
   arithmetic is untouched and every column it was keeping is still kept.

   The right-hand padding is where the slack is. 12px of trailing padding on the
   NARROWEST column in the table is pure waste — the column is already the one
   short of room, and the gap to its neighbour is drawn by the table's own border
   spacing. Reclaiming it, and trimming the left clearance to what the circle
   actually needs, gives the text back a third of its width without the column
   growing by a pixel. Responsive's arithmetic is untouched, so every column it
   was keeping is still kept — which is the whole point.

     measured at 393px, usable text width in the control cell
       Reference > HCPCS    41px → 61px   ("A0021" needs 40px: 2 lines → 1)
       Intake > Documents   49px → 69px   ("HIPAA Information…": 8 lines → 4)

   Both tables keep every column they had. */
@media (max-width: 575.98px) {
    .table.dataTable.dtr-inline.collapsed > tbody > tr:not(.child) > td.dtr-control,
    table.dataTable.dtr-inline.collapsed > tbody > tr:not(.child) > td.dtr-control {
        /* 5px offset + 14px circle + 2px border + 4px gap. The band-2 rule above
           adds a 6px reading gap on top of that, which is comfortable on a roomy
           column and unaffordable on a starved one; 4px still separates the text
           from the circle without costing a line. Same specificity as that rule,
           later in the file, so it wins on order alone. */
        padding-left: calc(5px + 14px + 2px + 4px) !important;
        padding-right: 4px;
    }
}

/* ══════════════════════════════════════════════════════════════
   New Practitioner: the NPI lookup row
   ══════════════════════════════════════════════════════════════
   Input + "Look up" button + a parenthetical hint, authored as one nowrap row
   with the input pinned at 220px. On a phone that forced the button to shrink
   until its label broke over two lines ("Look" / "up") and pushed the hint past
   the modal's right edge.

   The row now wraps. The input keeps its intended size where there is room and
   is allowed to shrink before anything else, so the button — whose label is
   text-nowrap and therefore incompressible — never gets squeezed. */
.npi-lookup-row > .npi-lookup-input {
    flex: 1 1 220px;
    min-width: 8rem;
    max-width: 220px;
}

/* Below sm the hint takes a line of its own rather than fighting the button for
   the tail of the first line. `flex-basis: 100%` in a wrapping flex row is the
   whole mechanism — no separate breakpoint markup, and the desktop row is
   untouched because the rule simply does not apply there. */
@media (max-width: 575.98px) {
    .npi-lookup-row > .npi-lookup-hint {
        flex: 1 0 100%;
        margin-top: -0.25rem;
    }

    /* With the hint on its own line the input and button share line one, and the
       input should take whatever the button does not. */
    .npi-lookup-row > .npi-lookup-input {
        flex: 1 1 auto;
        max-width: none;
    }
}

/* ══════════════════════════════════════════════════════════════
   PHONE: File Manager browser table
   ══════════════════════════════════════════════════════════════
   Not a DataTable — filemanager-index.js builds this one by hand, with four of
   the five columns pinned by inline width (90 + 140 + 140 + 130 = 500px). Name
   gets what is left, which on a 393px phone is negative, so a filename with no
   space to break on came apart mid-token across four lines.

   The fixed widths are right on a desktop and are left alone. Below sm the table
   becomes one labelled block per file, the same treatment as the invoice, claim
   and treatment-record tables. Widths on a `display: block` cell are inert, so
   the inline attributes need no override. */
@media (max-width: 575.98px) {
    .fm-files-table,
    .fm-files-table > tbody,
    .fm-files-table > tbody > tr,
    .fm-files-table > tbody > tr > td {
        display: block;
        width: auto;
    }

    .fm-files-table > thead {
        display: none;
    }

    /* table-bordered draws a box round every cell, which on a stack reads as a
       grid of empty rectangles. One rule per FILE is the structure that matters
       here, so the per-cell borders go and the row keeps a single divider. */
    .fm-files-table > tbody > tr {
        border: 0;
        border-bottom: 1px solid var(--bs-border-color);
        padding: 0.5rem 0;
    }

    .fm-files-table > tbody > tr:last-child {
        border-bottom: 0;
    }

    .fm-files-table > tbody > tr > td {
        display: flex;
        align-items: baseline;
        gap: 0.75rem;
        border: 0;
        padding: 0.1875rem 0;
    }

    .fm-files-table > tbody > tr > td::before {
        content: attr(data-label);
        flex: 0 0 4.5rem;
        font-size: 0.8125rem;
        color: var(--bs-secondary-color);
    }

    /* The filename is the one value that still needs to break a long token — but
       now it does so across the FULL row width rather than a starved column, so
       it takes two lines instead of four fragments. */
    .fm-files-table > tbody > tr > td[data-label="Name"] {
        font-weight: 500;
        overflow-wrap: anywhere;
    }

    .table-responsive:has(> .fm-files-table) {
        overflow-x: visible;
    }
}


/* ══════════════════════════════════════════════════════════════
   PHONE: LET THE SURVIVING COLUMNS RE-NEGOTIATE THEIR WIDTHS
   ══════════════════════════════════════════════════════════════
   DataTables measures its columns once, against the FULL desktop column set,
   and writes the result as inline `width` on every <th>. Responsive then hides
   most of those columns — but the inline widths stay, so the two or three
   columns left on screen keep dividing the row by proportions computed for a
   table that is no longer being displayed. The result is a column starved next
   to a column with room to spare:

       Intake > Documents at 393px, before this rule
         Name      granted  88px (usable  59px) — wanted 226px   → 5 lines
         Key       granted 126px (usable 102px) — wanted  96px   → surplus
         Version   granted  89px (usable  65px) — wanted  12px   → 53px on "v1"

   So on a phone the inline widths are cleared and every cell may wrap, letting
   the auto table layout redistribute on what the cells ACTUALLY need at this
   width. No percentages, no per-table tuning — a fixed share is wrong for any
   table whose column count it was not chosen for (60% on column 1 helped the
   three-column case and took HCPCS's two-column Short Description from one
   line to three). `max-width: 45vw` on the non-control cells is the one bound:
   without it a single long value in a trailing column can claim more than half
   the row before wrapping, which is what starves the first column in the first
   place. The control column is exempt — it is the one that needs the room.

   ── Why this exact rule was reverted once (02ecf24e), and what is different ──

   The first attempt applied these declarations to `table.dataTable` at large,
   and every DataTable in the app degenerated to one-character-per-line cells
   because Responsive started keeping 5 and 6 columns of an 11-column table on
   a 393px screen. The autopsy blamed `width: auto` for "removing the evidence
   Responsive measures from" — but the width was never the poison. Responsive
   sizes columns from an off-screen CLONE of the table (same id and classes;
   header cells re-inlined to `width: auto` by Responsive itself), reading each
   column's minimum single-line content width from it. What poisoned that
   measurement was `white-space: normal` + `overflow-wrap: anywhere` matching
   the clone's cells: a cell that may break anywhere measures one character
   wide, every column "fits", and nothing is hidden.

   The clone is mounted in a classless throwaway <div> inserted BESIDE the real
   table, while every real DataTable in the app is a direct child of its
   wrapper (each of the ~90 dom strings places `t` at top level — verified).
   DataTables core's own width-measurement clone hangs from a classless holder
   div the same way. So `.dataTables_wrapper > table.dataTable` matches every
   real table and can never match either measuring clone: the clones keep
   measuring single-line desktop-truthful widths, Responsive keeps hiding
   exactly what does not fit, and only the table the user is looking at gets
   to renegotiate. That anchor is the entire fix — remove it and the 02ecf24e
   shredding comes straight back at first paint.

   Byte-identical at 768 / 1366 / 1920px: the block is phone-only, and above
   the breakpoint DataTables' own widths are left exactly as it computed
   them. */
@media (max-width: 575.98px) {
    .dataTables_wrapper > table.dataTable > thead > tr > th {
        width: auto !important;
        white-space: normal !important;
        overflow-wrap: anywhere;
    }

    .dataTables_wrapper > table.dataTable > tbody > tr:not(.child) > td {
        white-space: normal !important;
        overflow-wrap: anywhere;
    }

    .dataTables_wrapper > table.dataTable > tbody > tr:not(.child) > td:not(.dtr-control) {
        max-width: 45vw;
    }

    /* Invoices pins its customer column on screen (className: "all" in
       invoices-index.js) because a list of bare invoice numbers identifies
       nothing — so this is the one table where two columns share the row by
       decree rather than by fit. Between an identifier and a company name, the
       name is the one that reads fine wrapped; re-freezing the number makes
       its full width a floor the auto layout must honour, so "INV-202608-0002"
       stays whole (~150px against a ~300px row) and the name takes the rest.

       !important because it is overriding the generic renegotiation rule
       above, which is itself !important; the `*` leg re-freezes the cell's
       inner span, which would otherwise take `normal` from the dtr-control
       rule and wrap inside the nowrap cell. */
    .dataTables_wrapper > #invoices-table > tbody > tr:not(.child) > td.dtr-control,
    .dataTables_wrapper > #invoices-table > tbody > tr:not(.child) > td.dtr-control * {
        white-space: nowrap !important;
    }
}

/* ══════════════════════════════════════════════════════════════
   PHONE: SHARED RESTACK HOOK FOR PLAIN SERVER-RENDERED TABLES
   ══════════════════════════════════════════════════════════════
   The per-table classes above (.line-items-table, .claim-lines-table,
   .tr-stack-table, .fm-files-table) each grew out of one surface's quirks. The
   tables below share no quirks at all — they are ordinary server-rendered
   tables, four to seven columns wide, that simply cannot fit a phone — so they
   share one class instead of collecting four more:

     Passkeys                 Name / Added / Type / Actions
     Referral Current Offers  Practitioner / Discipline / Status / Expires On
     Admin > Payments         Date / Invoice / Agency / Method / Amount / Status
     Subscription history     Invoice / Period / Seats / Total / Status / Due / Paid
     Invoice Detail lines     # / Description / Qty / Rate / Total

   The last one is worth a note. `.line-items-table` already had a restack, but
   it was deliberately scoped to `.line-items-scroll > .line-items-table` because
   the Detail view shares the class while being read-only and, at the time,
   carrying no data-label attributes. It has them now, so it opts in explicitly
   via this second class rather than by widening that scope — the Edit view's
   editable table keeps its own rule, and neither can start matching the other by
   accident.

   `text-align: left` on the cells is not cosmetic: several of these columns are
   `.text-end` for a right-aligned money column, which reads as a stray
   right-floating value once the cell is a full-width block with its label on the
   left. */
@media (max-width: 575.98px) {
    .stack-table > tbody,
    .stack-table > tbody > tr,
    .stack-table > tbody > tr > td {
        display: block;
        width: auto;
    }

    .stack-table > thead {
        display: none;
    }

    /* All three of these tables carry Bootstrap's `table-bordered`, which draws a
       box round the table AND round every cell. On a stack that reads as each
       record being a framed card, and the outer box adds a second frame just
       inside the panel that already contains it. Only the rule BETWEEN records
       carries meaning here, so the frame is dropped and the separator kept.

       `table-bordered` applies its border via `> :not(caption) > * > *` with a
       CSS variable, so it is neutralised on the cells rather than by unsetting
       the variable — that keeps the desktop table's own borders untouched, since
       none of this exists above the breakpoint. */
    .stack-table,
    .stack-table > tbody,
    .stack-table > tbody > tr > td {
        border: 0 !important;
    }

    .stack-table > tbody > tr {
        border: 0;
        border-bottom: 1px solid var(--bs-border-color);
        padding: 0.5rem 0;
    }

    .stack-table > tbody > tr:last-child {
        border-bottom: 0;
    }

    .stack-table > tbody > tr > td {
        display: flex;
        align-items: baseline;
        gap: 0.75rem;
        border: 0;
        padding: 0.1875rem 0;
        text-align: left !important;
    }

    .stack-table > tbody > tr > td::before {
        content: attr(data-label);
        flex: 0 0 5.5rem;
        font-size: 0.8125rem;
        color: var(--bs-secondary-color);
    }

    /* A cell whose data-label is empty (an actions column) has no heading to
       show, so it keeps the full width instead of being indented past a blank
       label box. */
    .stack-table > tbody > tr > td[data-label=""]::before {
        content: none;
    }

    .stack-table > tbody > tr > td > * {
        min-width: 0;
        overflow-wrap: anywhere;
    }

    /* Nothing left to scroll once the table is a stack. */
    .table-responsive:has(> .stack-table) {
        overflow-x: visible;
    }

    /* Passkeys: the actions cell holds a rename input plus two labelled buttons.
       Even stacked, that is three controls on one line, and "Rename" was breaking
       across two lines to fit. The rename form gets the full width and its input
       takes whatever the button does not, so both buttons keep their labels on
       one line. The inline 11rem on the input has to be overridden — it is wider
       than the room left beside the button. */
    .stack-table input[type="text"] {
        max-width: 100% !important;
    }

    /* The actions cell becomes ONE flex line holding all three controls:
       [ New name ][ Rename ][ trash ]. It used to be `width: 100%` plus a
       margin-bottom, which pushed the trash button onto its own row and left
       the spacing reading as two separate groups.

       The gaps are what this is really about. The desktop pairing (see
       Passkeys.cshtml) is deliberate: the space INSIDE the rename form -- input
       to button -- is tighter than the space separating that form from the
       delete button beside it, so "Rename" reads as belonging to the field it
       submits while "delete" reads as its own action. Below sm the form's own
       `me-1` is zeroed by the stacked treatment, which lost that rhythm and left
       a wider gap after [Rename] than before it -- backwards from every other
       width. Restoring it as a real gap keeps the three evenly and correctly
       spaced on the phone, matching desktop and the tablet band. */
    .stack-table td[data-label=""] {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        flex-wrap: nowrap;
    }

    .stack-table td > form.d-inline-flex {
        display: flex !important;
        flex: 1 1 auto;
        min-width: 0;
        margin-right: 0 !important;
        margin-bottom: 0 !important;
        gap: 0.375rem;
    }

    .stack-table td > form.d-inline-flex > input[type="text"] {
        flex: 1 1 auto;
        width: auto !important;
        min-width: 0;
    }

    .stack-table td > form.d-inline-flex > button {
        flex: 0 0 auto;
    }

    /* The delete form is a plain `d-inline` wrapper round one icon button; it
       must not absorb slack, or the trash drifts away from the pair. */
    .stack-table td > form.d-inline {
        flex: 0 0 auto;
    }

    /* The Incandera admin tables opt OUT of the nowrap above.
       ─────────────────────────────────────────────────────────────
       That rule was written for the Passkeys actions cell, where the three
       controls are an input plus two buttons and the input absorbs the slack —
       so a single unbreakable line always fits. These tables carry a different
       shape: two fully LABELLED buttons ("Edit" + "Generate" on Subscriptions,
       "Decommission" on Tenants), with no elastic control between them to give
       way. Held on one line at 393px those labels squeeze and break mid-word.

       Scoped to this one class rather than relaxing the shared rule, so the
       Passkeys cell keeps the single-line treatment it was tuned for. */
    .admin-stack-table td[data-label=""] {
        flex-wrap: wrap;
        row-gap: 0.375rem;
    }

    /* `text-nowrap` on the Subscriptions actions cell keeps its two buttons from
       breaking apart on DESKTOP, where they share a narrow column. Stacked, the
       cell is full-width and that same rule is what stops the pair from wrapping
       onto a second line when they cannot both fit. */
    .admin-stack-table td[data-label=""].text-nowrap {
        white-space: normal;
    }

    /* A cell holding TEXT PLUS A BLOCK CHILD — a value with a muted subtitle under
       it, or a list of items beneath a count badge.
       ─────────────────────────────────────────────────────────────
       The stacked cell is `display: flex` so the label and the value sit on one
       line. That makes every child of the cell a flex ITEM, so a <div> after the
       text does not fall beneath it — it becomes a third column competing for the
       leftovers, and with `min-width: 0` above it can be squeezed to a few pixels
       and render one character per line.

       Letting the cell wrap fixes it: the label keeps its fixed 5.5rem basis, the
       text takes the rest of line one, and any block child is pushed onto its own
       full-width line under both. `flex-basis: 100%` is what forces that break —
       without it a narrow child would still try to sit in the remaining space.

       `:not(:first-child)` is the discriminator and it is load-bearing. A cell
       whose ONLY child is a div is the toggle/radio case (the .form-check wrapper
       round a switch), where the control is the cell's value and belongs BESIDE
       its label, not on a line of its own. Only a div that FOLLOWS something is a
       subtitle needing its own line. */
    .admin-stack-table > tbody > tr > td {
        flex-wrap: wrap;
    }

    .admin-stack-table > tbody > tr > td > div:not(:first-child),
    .admin-stack-table > tbody > tr > td > ul:not(:first-child) {
        flex: 0 0 100%;
    }

    /* A COLSPAN CELL MUST OPT OUT OF THE STACK.
       ─────────────────────────────────────────────────────────────
       The rule above makes every <td> `display: flex`, and a flex container
       cannot stay spanned — the cell collapses to the first column's width and
       its text wraps a character or two per line. (The same finding is recorded
       against the Forms group rows earlier in this file.)

       Every table using this hook has full-width status rows — "Loading…",
       "No invoices in the last 30 days." — which are a MESSAGE, not a record, so
       they want neither a stacked layout nor a data-label. They stay a plain
       full-width block and simply centre.

       The class is opt-in rather than an attribute selector on [colspan] because
       a colspan cell is not always a message; this says "this row is chrome".
       ⚠ `display: table-cell` is the WRONG escape and was measured as such: the row
       above it is already `display: block` from this same rule, so a table-cell
       child gets an anonymous table wrapper that SHRINK-WRAPS — the cell came out
       200px inside a 303px row, floating left of centre, and `width: 100%` was
       ignored because a table cell's width is negotiated by the table algorithm.
       `display: block` keeps the full row width and needs no colspan at all: once
       the row is a block, there are no columns left to span. */
    .stack-table > tbody > tr > td.stack-table-full {
        display: block;
        width: auto;
        text-align: center !important;
    }

    .stack-table > tbody > tr > td.stack-table-full::before {
        content: none;
    }

    /* Dashboard "Latest Invoices": the first stack-table user that also carries
       Bootstrap's `table-nowrap`.
       ─────────────────────────────────────────────────────────────
       `table-nowrap` sets `white-space: nowrap` on every th/td, and it is WANTED
       on desktop — eight columns of invoice number, company, money and status
       read as a scannable grid only while nothing wraps. But it is also what
       forces the horizontal scroll on a phone: a stacked cell whose content
       cannot wrap simply pushes the row wider than the viewport, so the restack
       would leave the overflow exactly where it was.

       Relaxed rather than removed, and only inside this media query, so the
       desktop table is untouched above 575.98px. Company names and payment
       method labels are the two that actually need it. */
    .dashboard-invoices-table.table-nowrap > tbody > tr > td,
    .dashboard-invoices-table.table-nowrap > tbody > tr > td * {
        white-space: normal;
    }
}

/* ══════════════════════════════════════════════════════════════
   FORM DISPLAY — the status/submit bar
   ══════════════════════════════════════════════════════════════
   "Update Status:" + a select2 + the Submit button. The intended reading at
   every width is one row of

       [Update Status:] [-- status options -- ▾ ..........] [Submit]

   with the select absorbing the slack and Submit therefore sitting at the
   row's right edge. What broke that on desktop: select2 copies the <select>'s
   computed width onto its container as an inline PIXEL width, and it measures
   while the original select is flex-grown — so the container arrives
   pre-widened to (almost) the full row and the button wraps beneath it.
   `flex-basis: 0%` makes the flex algorithm ignore that inline width outright
   (basis outranks width for the main size), which is cleaner than fighting
   the pixel value select2 wrote. The bare <select> is named alongside the
   container so the pre-select2 frame (and any select2-less fallback) lays out
   the same. */
.form-status-bar > select,
.form-status-bar > .select2-container {
    flex: 1 1 0% !important;
    min-width: 0;
    width: auto !important;
}

.form-status-bar > button[type="submit"] {
    flex: 0 0 auto;
}

/* Below sm the same row cannot hold all three: the label and button are fixed
   text, so the select would be squeezed to "-- Keep ...". The label is the
   part that gives — it takes a full line of its own, and the select + Submit
   share the second line, where ~260px is comfortably more than the widest
   option needs. (The markup's flex-wrap is what lets the label's 100% basis
   force the break.) */
@media (max-width: 575.98px) {
    .form-status-bar > label {
        flex: 0 0 100%;
    }
}

/* ── Environment strip ────────────────────────────────────────────────────────
   A thin full-width band flush beneath the topbar, carrying DEV / DEV (Docker) /
   UAT. It replaces the inline badge that used to sit in the topbar's left group.

   The motivation is horizontal room, not decoration. The topbar is short of width
   and nothing else, and the badge consumed a whole slot of it on every viewport —
   enough that Maia's toggle had to stay hidden below 992px. Out here the label
   costs no width at all, and it is MORE visible than before: it spans the viewport
   instead of competing with the hamburger and the brand mark on a phone.

   The strip renders INSIDE #page-topbar, after .navbar-header (see
   _EnvironmentBadgePartial.cshtml). The topbar is already `position: fixed;
   top: 0`, so a child of it is flush against the bar by construction — there is no
   second fixed element to keep in sync and no seam to chase.

   Production renders NO strip, so every offset below is gated on
   `body:has(.env-bar)`. With no strip in the DOM none of these rules match and the
   stock 70px geometry is bit-for-bit what it was — which is the property that
   matters, because PROD is the one environment that can never be checked by eye. */
/* The strip starts where the sidebar ends, so it reads as belonging to the CONTENT
   area rather than slicing the menu in half. The offsets are the sidebar's own
   widths, and they are the same three values #page-topbar::after already uses a few
   hundred lines up for exactly this reason — expanded 250px, collapsed 70px, and 0
   below 992px where the sidebar is an off-canvas overlay that occupies no space.

   Below 992px the full-width strip is correct and stays: there is no menu beside it
   to bisect, and spanning the viewport is what makes it legible on a phone. */
.env-bar {
    margin-left: 250px;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Tight but legible. 15px is enough for 9px text plus a hairline of padding;
       any shorter and the descender-free capitals start to look clipped. */
    height: 15px;
    line-height: 1;
    /* The label is uppercase already; the tracking is what keeps three or four
       characters from reading as a smudge at this size. */
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    user-select: none;
    /* Optical centring. `align-items: center` balances the LINE BOX, and DM Sans
       carries a descent even for all-caps, so the visible capitals settle a touch
       low. Expressed in em rather than px on purpose: it scales with the font, so
       it holds at every zoom level and form factor instead of drifting the way a
       fixed 1px nudge does. Verified on-device across resolutions.

       An earlier attempt to correct this with `position: relative; top: -1px` on
       the label was reverted -- it over-corrected and read as riding the top edge,
       because the strip's optical field includes the topbar space above it, not
       just its own 15px box. This lands where the eye actually wants it. */
    padding-top: 0.14em;
}

/* The strip paints its OWN background rather than taking Bootstrap's .bg-* value.

   EnvironmentBadge.Resolve hands out a bg-* class, which is right for a badge but
   not for 9px text: the two brands ship different palettes under the same class
   names, and Serenity's are pastel. Measured with white ink at this size:

                    Serenity              Incandescence
     bg-secondary   #74788d  4.36:1       #7d7365  4.65:1
     bg-info        #50a5f1  2.63:1       #3d74b8  4.78:1
     bg-danger      #f46a6a  2.95:1       #c22945  5.68:1

   9px is far below WCAG's large-text allowance, so 4.5:1 is the threshold, and
   three of Serenity's four miss it -- bg-info at 2.63:1 badly. Rather than fork the
   ink per brand AND per class (eight combinations that drift the moment either
   palette moves), the strip pins one deep tone per environment and pairs it with a
   fixed ink. Same hue family as the badge it replaces, dark enough that white
   clears AA by a wide margin in both brands.

   Deliberately NOT brand-tokenised: this is a build-environment warning, not part
   of either brand's visual language, and it should look identical wherever it
   appears so it is never mistaken for chrome. */
.env-bar.bg-secondary {
    background-color: #4a4d5c !important;
    color: #fff;
}

.env-bar.bg-info {
    background-color: #1c5a8f !important;
    color: #fff;
}

.env-bar.bg-warning {
    /* Amber stays light with dark ink -- inverting it would lose the "this is the
       one that matters" read that UAT needs against the DEV greys.

       The ink is a warm brown rather than near-black: pure black on amber reads as
       harsh at this size, and the brown sits in the amber's own hue family. It is
       NOT free to pick, though -- 9px is well below WCAG's large-text allowance, so
       4.5:1 is the threshold, and the warm browns cross it quickly. Measured on
       #efa02b: #654411 is 4.08:1 (short), #5a3a0e 4.76:1, #503208 5.40:1. This is
       the lightest shade that still clears AA. Re-measure before warming it further. */
    background-color: #efa02b !important;
    color: #5a3a0e;
}

.env-bar.bg-danger {
    background-color: #a32036 !important;
    color: #fff;
}

/* Collapsed rail — the strip follows it in to 70px. */
body.vertical-collpsed .env-bar {
    margin-left: 70px;
}

/* The gutter the strip's margin leaves behind sits in the SIDEBAR's column, but it
   belongs to the topbar's box — so without this the topbar's own cream shows
   through as a bright 15px band between the dark brand box (which ends at 70px) and
   the dark menu (which now starts at 85px). It reads as a crack in the sidebar.

   Filling it from the topbar rather than by stretching .navbar-brand-box keeps the
   brand box's 70px line-height centring untouched — that is what vertically centres
   the logo, and growing the box would drag the mark off centre.

   The colour is the SIDEBAR's, and it differs per brand: Serenity keeps the base
   theme's navy (#2a3042, app.css), Incandescence warms it to ink (#17130e,
   brand-incandescence.css). This file loads for BOTH brands, so a single hardcoded
   value paints one of them wrong — ink across Serenity's navy read as a black band
   sliced through the sidebar. The default below is Serenity's, because Serenity is
   the base theme and brand-incandescence.css is the conditional override.

   The same pairing is spelled out in _Layout.cshtml's anti-FOUC script
   (`isSerenity ? '#2a3042' : '#17130e'`); keep the two in step. */
body[data-sidebar="dark"]:has(.env-bar) #page-topbar::before {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    width: 250px;
    height: 15px;
    background-color: #2a3042;
}

body[data-sidebar="dark"][data-brand="incandescence"]:has(.env-bar) #page-topbar::before {
    background-color: #17130e;
}

body[data-sidebar="dark"]:has(.env-bar).vertical-collpsed #page-topbar::before {
    width: 70px;
}

/* Below 992px the strip is full width and the sidebar is an overlay, so there is no
   gutter to fill and the fill would show as a stray dark block beside the strip. */
@media (max-width: 991.98px) {
    body[data-sidebar="dark"]:has(.env-bar) #page-topbar::before,
    body[data-sidebar="dark"]:has(.env-bar).vertical-collpsed #page-topbar::before {
        display: none;
    }
}

/* Below 992px the sidebar leaves the flow entirely (it becomes an overlay), so the
   strip spans the viewport. This also has to beat the collapsed rule above: the
   `vertical-collpsed` class stays on the body at these widths even though the rail
   itself is gone, so without this the strip would keep a 70px indent against no
   sidebar at all. */
@media (max-width: 991.98px) {
    .env-bar,
    body.vertical-collpsed .env-bar {
        margin-left: 0;
    }
}

/* The strip is chrome, not content: it must never intercept a click meant for
   whatever sits beneath it, and it has nothing to click.

   No nudge on the label itself -- the optical centring lives on .env-bar's
   padding-top above, which scales with the font instead of drifting at other zoom
   levels the way a px offset here did. */
.env-bar-label {
    pointer-events: none;
}

/* ── Everything below the topbar shifts down by the strip's height ───────────
   The theme hardcodes the topbar's 70px in several places rather than deriving it.
   Rather than re-plumb all of them, each offset that actually governs a surface
   the user sees is restated here with the strip added.

   `.page-content` is padding, the other two are `top`/`margin-top`; they are all
   the same 15px shift, just expressed in whatever unit the original rule used. */
body:has(.env-bar) .page-content {
    padding-top: calc(70px + 15px + 24px);
}

body:has(.env-bar) .vertical-menu {
    top: calc(70px + 15px);
}

body:has(.env-bar) .topnav {
    margin-top: calc(70px + 15px);
}

/* The collapsed rail is pinned to `top: 70px` by the rail-scrolling block earlier
   in this file, and its scrollport is sized against that same figure. Both move
   together or the rail's height stops matching its box. */
@media (min-width: 992px) {
    body:has(.env-bar).vertical-collpsed .vertical-menu {
        top: calc(70px + 15px);
    }

    body:has(.env-bar).vertical-collpsed .vertical-menu .simplebar-content-wrapper {
        max-height: calc(100vh - 70px - 15px);
    }

    /* The flyout's fallback ceiling and its clamp both reference the rail's top.
       app.js supplies the live `top` value and reads the item's real box, so it
       needs no change — this only keeps the first-frame fallback honest. */
    body:has(.env-bar).vertical-collpsed .vertical-menu #sidebar-menu > ul > li:hover > ul {
        max-height: calc(100vh - 70px - 15px);
    }
}

/* No rule is needed for #page-topbar::after. That hairline is drawn at `bottom: 0`,
   which with the strip in place would land under the coloured band rather than under
   the bar — but an earlier override in this file already disables it outright, and
   measurement confirms it computes to `display: none` with and without the strip.
   A `body:has(.env-bar)` rule here would look load-bearing while doing nothing. */

/* Search hint popover
   ============================================================================
   PR #69 put the typed search hints (name:, npi:, phone: ...) in the search
   input's placeholder, but `.dataTables_filter input` is 200px wide, so the
   longest list — Practitioners' — was cut off mid-hint. The hints now live in
   a popover behind a small icon, which costs the header bar no width at all.

   The trigger sits INSIDE the DataTables filter label, after the input, so it
   follows the search box wherever the responsive toolbar puts it. Styled as a
   bare icon rather than a .btn: the label is a compact 28px row and a button
   with Bootstrap padding would grow it. */
/* The trigger is an <a tabindex>, not a <button>: Bootstrap requires that for a
   focus-triggered popover (iOS Safari does not focus buttons on tap). These rules
   strip the anchor back to a bare icon so it looks the same as it always did. */
.dataTables_filter label .search-hint-toggle {
    border: 0;
    background: none;
    padding: 0;
    margin-left: .35rem;
    line-height: 1;
    vertical-align: middle;
    cursor: pointer;
    text-decoration: none;
    color: var(--bs-secondary-color, #74788d);
}

.dataTables_filter label .search-hint-toggle:hover,
.dataTables_filter label .search-hint-toggle:focus {
    color: var(--bs-primary, #556ee6);
    text-decoration: none;
}

/* Keyboard users still need to see where focus is; the popover opens on focus,
   so the ring and the panel appear together. */
.dataTables_filter label .search-hint-toggle:focus-visible {
    outline: 2px solid var(--bs-primary, #556ee6);
    outline-offset: 2px;
    border-radius: 2px;
}

.dataTables_filter label .search-hint-toggle svg {
    width: 15px;
    height: 15px;
}

/* Match the profile-image popover's frame so the two read as one component. */
.popover.search-hint-popover {
    --bs-popover-border-color: rgba(0, 0, 0, 0.25);
    --bs-popover-arrow-border: rgba(0, 0, 0, 0.25);
    border: 1px solid rgba(0, 0, 0, 0.25) !important;
    border-radius: 0.5rem;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important;

    /* 18rem, not the 20rem this shipped at, so the footnote breaks a word
       earlier and the popover reads taller than wide. MEASURED per grid by
       walking the note's text node and recording where the line box top
       changes: 18rem and 17rem both end line one at "digits" on
       Practitioners, Patients and Companies; 16rem breaks a word too early
       and 13rem starts wrapping the hint rows themselves. 18rem is the wide
       edge of that band, leaving a word of margin before the break moves
       back. Referrals breaks one word earlier again ("patient name" is
       longer) and still reads as a balanced two lines. */
    max-width: 18rem;
}

.popover.search-hint-popover .popover-header {
    border-radius: 0.5rem 0.5rem 0 0;
    border-bottom: none;
    font-size: 0.75rem;
    padding: 0.35rem 0.5rem;
}

.popover.search-hint-popover .popover-body {
    padding: 0.5rem 0.6rem;
    font-size: 0.75rem;
}

.popover.search-hint-popover .search-hint-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.popover.search-hint-popover .search-hint-list li + li {
    margin-top: 0.2rem;
}

.popover.search-hint-popover .search-hint-list code {
    display: inline-block;
    min-width: 5.2rem;
    color: var(--bs-primary, #556ee6);
}

.popover.search-hint-popover .search-hint-note {
    margin: 0.45rem 0 0;
    padding-top: 0.4rem;
    border-top: 1px solid rgba(0, 0, 0, 0.1);
    color: var(--bs-secondary-color, #74788d);
}

[data-layout-mode="dark"] .popover.search-hint-popover .search-hint-note {
    border-top-color: rgba(255, 255, 255, 0.1);
}
