/* Pulse app shell — persistent nav rail (hover-flyout icon rail on desktop, swipeable
   bottom bar + sheet on phone), theme-variable driven.
   Consumes --pulse-theme-* custom properties defined in pulse-theme-variables.css
   (ported from PulseAI's theme system — see REDESIGN-TRACKING.md).
   Load AFTER pulse-theme-variables.css and BEFORE any page-specific CSS. */

/* bootstrap5-toggle.min.css (loaded globally, see _Layout.cshtml) reads --bs-primary,
   --bs-success and --bs-danger for its checked-state .toggle-handle colors
   (.btn-outline-primary/-success/-danger). Those are never redefined elsewhere in the
   app, so without this override the plugin's toggles (TagList.cshtml Active switch,
   CheckpointReaderList.cshtml AutoImportTags/In-Out switches) render stock Bootstrap
   colors instead of following the active Aurora Glass theme preset. */
:root {
    --bs-primary: var(--pulse-theme-primary, #2563eb);
    --bs-success: var(--pulse-theme-teal, #0d9488);
    --bs-danger: var(--pulse-theme-danger, #dc2626);
}

html, body {
    height: 100%;
}

body.pulse-shell {
    margin: 0;
    background: var(--pulse-theme-page-bg-a, #eef2f9);
    color: var(--pulse-theme-text, #1e293b);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, Arial, sans-serif;
}

/* height (not just min-height) gives the shell a true ceiling — content
   taller than one viewport now scrolls inside the shell itself (it already
   computes overflow-y:auto via the CSS overflow interop rule, since
   overflow-x is hidden) instead of growing the shell past the viewport and
   relying on the page-level scrollbar. Previously min-height-only meant a
   flex-item descendant that filled 100% of its real available space (no
   slack left over) would make .pulse-main-col's hypothetical content size
   (which includes the fixed-size copyright footer below it) exceed the
   viewport by a small, easy-to-miss amount on every page — invisible
   everywhere else because ordinary page content leaves enough slack to
   absorb it, but exposed as a stray scrollbar on any page whose content is
   sized to precisely fill its box. Verified this doesn't clip long-content
   pages (Home, ~1575px of KPI/report content at a 900px viewport): they now
   scroll inside .pulse-app-shell instead of scrolling the whole html/body,
   and the sticky nav-rail still tracks correctly against that container. */
.pulse-app-shell {
    position: relative;
    display: flex;
    height: 100vh;
    overflow-x: hidden;
}

/* Aurora background wash — global, all themes get it (per PulseAI research: aurora/orb
   animations are not theme-specific, only their colors vary). Respects reduced motion. */
.pulse-aurora-bg {
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    background: linear-gradient(135deg,
        var(--pulse-theme-page-bg-a, #eef2fb) 0%,
        var(--pulse-theme-page-bg-b, #eaf3f2) 35%,
        var(--pulse-theme-page-bg-c, #f1eefb) 70%,
        var(--pulse-theme-page-bg-d, #eef2fb) 100%);
}
.pulse-aurora-blob {
    position: absolute;
    border-radius: 50%;
    filter: blur(60px);
    /* Scaled by the user's Appearance > Animation Intensity setting (Configuration page). */
    opacity: calc(0.5 * var(--pulse-theme-anim-intensity, 1));
}
@media (prefers-reduced-motion: no-preference) {
    .pulse-aurora-blob { animation: pulse-aurora-drift 26s ease-in-out infinite; }
}

.icon {
    width: 16px;
    height: 16px;
    display: inline-block;
    vertical-align: middle;
    flex-shrink: 0;
    /* The _IconSprite.cshtml shapes carry no fill/stroke attributes of their
       own (by design, so a single shared sprite can be recolored per usage
       via CSS), so without these two lines SVG's own initial values apply
       instead: fill defaults to black (rendering closed shapes as solid black
       blobs) and stroke defaults to none (rendering open line/polyline shapes
       as invisible) — the .icon { color: var(--pulse-theme-text-muted) }
       rules elsewhere in this file had no visible effect at all. */
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* .glass-panel is a global utility class (loaded app-wide via
   _Layout.cshtml) applied directly to real content wrappers that
   frequently contain modals as descendants (e.g. `<div class="pulse-at-
   orders glass-panel">`). backdrop-filter -- like transform/filter/
   perspective -- creates a new CSS stacking context independent of
   z-index, which traps any modal nested inside behind Bootstrap's
   .modal-backdrop (appended directly to <body>, outside the trap) no
   matter how high the modal's own z-index is set. This is the exact
   same bug class root-caused and fixed for #Content.tab-content on
   2026-07-19 ("Add Checkpoint modal backdrop blocks everything") --
   .glass-panel is a DIFFERENT, more widely-used class that audit didn't
   cover. Fix: move the glass background/blur off the real element onto
   a ::before pseudo-element (a pseudo-element's own stacking context
   doesn't propagate to its host), with an explicit z-index so it paints
   behind real content instead of over it -- omitting that z-index is
   what caused the separate "Checkpoint Groups paragraph invisible" bug
   documented elsewhere in REDESIGN-TRACKING.md; do not repeat it. */
.glass-panel {
    position: relative;
    border: 1px solid var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
    box-shadow: var(--pulse-theme-glass-shadow, 0 8px 32px rgba(31,38,135,0.10));
    border-radius: var(--pulse-theme-radius, 16px);
}
.glass-panel::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--pulse-theme-glass-bg, rgba(255,255,255,0.68));
    backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    -webkit-backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    border-radius: inherit;
    pointer-events: none;
}

/* ---------- Nav rail v2: hover-flyout icon rail (desktop/tablet) / swipeable bottom bar
   + sheet (phone). Replaced the old drawer rail (272px expanded / 60px collapsed / off-canvas
   phone drawer) -- founder-reported 2026-08-09 that the drawer's subnav-on-top placement and
   Configuration's indentation both read as dated; approved redesign after a click-through
   prototype review. One set of "flyout" panels (.rail-v2-flyout) is shared between the desktop
   hover-drawer and the phone bottom-sheet presentation purely via these media queries -- same
   markup, same _NavRailLocalItems.cshtml content, just repositioned. See REDESIGN-TRACKING.md. */
.reset-btn { appearance: none; background: none; border: none; padding: 0; margin: 0; cursor: pointer; color: inherit; font: inherit; text-align: left; }

.nav-rail-v2 {
    position: sticky;
    top: 14px;
    z-index: 20;
    width: 56px;
    height: calc(100vh - 28px);
    flex-shrink: 0;
    margin: 14px 0 14px 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 14px 0;
    gap: 4px;
}

.rail-v2-brand { flex-shrink: 0; margin-bottom: 6px; }
.rail-v2-brand .brand-mark {
    width: 32px; height: 32px; border-radius: 9px;
    background: rgba(255,255,255,0.9);
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 2px 8px rgba(15,23,42,0.12), inset 0 0 0 1px rgba(15,23,42,0.05);
    overflow: hidden;
}
.rail-v2-brand .brand-mark img { width: 22px; height: 22px; object-fit: contain; }

.rail-v2-desktop-icons { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1; min-height: 0; overflow-y: auto; width: 100%; }
.rail-v2-spacer { flex: 1; min-height: 8px; }

.rail-v2-item, .rail-v2-avatar {
    display: flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: 10px; flex-shrink: 0;
    color: var(--pulse-theme-text-muted); text-decoration: none; cursor: pointer;
    transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}
.rail-v2-item:hover { background: color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 9%, transparent); color: var(--pulse-theme-text); }
.rail-v2-item.is-active { background: color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 12%, transparent); color: var(--pulse-theme-primary, #2563eb); box-shadow: inset 0 0 0 1.5px var(--pulse-theme-primary, #2563eb); }
.rail-v2-avatar {
    background: linear-gradient(135deg, var(--pulse-theme-teal, #0d9488), var(--pulse-theme-primary, #2563eb));
    color: #fff; font-size: 11px; font-weight: 700; margin-top: 4px;
}
.rail-v2-avatar.is-active { box-shadow: 0 0 0 2px var(--pulse-theme-primary, #2563eb); }
.rail-v2-item .icon { width: 18px; height: 18px; }

.icon-wrap { position: relative; display: inline-flex; }
.sub-dot { position: absolute; top: -1px; right: -2px; width: 5px; height: 5px; border-radius: 50%; background: var(--pulse-theme-primary, #2563eb); }

/* Flyout / sheet panel -- desktop (default): absolute drawer sliding out to the right of the
   rail, click-triggered (see pulse-shell.js). Repositioned to a bottom sheet at the phone
   breakpoint below. Header/body split (overflow:hidden + flex column) so .fly-title stays a
   fixed band at the top -- styled like this app's own .modal-header ("Layered Glass Depth",
   see REDESIGN-TRACKING.md) rather than inventing a new header treatment -- while .fly-body
   scrolls independently underneath it for long lists (AssetTag's 11 items). */
.rail-v2-flyout {
    position: absolute;
    top: 0; left: 100%;
    width: 224px;
    max-height: calc(100vh - 28px);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--pulse-theme-radius, 16px);
    background: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
    -webkit-backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    border: 1px solid var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
    box-shadow: 10px 8px 32px -12px rgba(15,23,42,0.28);
    transform: translateX(-8px);
    opacity: 0;
    pointer-events: none;
    transition: transform .2s cubic-bezier(.32,.72,0,1), opacity .15s ease;
}
.rail-v2-flyout.is-open { transform: translateX(0); opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .rail-v2-flyout { transition: none; } }

/* Configuration and Profile sit at the bottom of the rail (below the spacer), so their
   flyout opening downward from a fixed top:0 read as disconnected from the icon that
   triggered it -- these two grow upward from the bottom instead, staying anchored to
   roughly where their icon actually is. The 5 apps above the spacer keep the default
   top-anchor since they're already near the top of the rail. Desktop-only; the mobile
   sheet below is always viewport-bottom-anchored regardless of which app opened it. */
.rail-v2-flyout[data-flyout-panel="configuration"],
.rail-v2-flyout[data-flyout-panel="profile"] {
    top: auto;
    bottom: 0;
}

.fly-title {
    position: relative;
    display: flex; align-items: center; gap: 8px;
    flex-shrink: 0;
    font-size: 13.5px; font-weight: 700;
    padding: 12px 14px;
    background:
        radial-gradient(130% 180% at 12% 0%, color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 16%, transparent) 0%, transparent 60%),
        var(--pulse-theme-glass-bg, rgba(255,255,255,0.68));
    border-bottom: 1px solid var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
    color: var(--pulse-theme-text, #1e293b);
    text-shadow: 0 0 20px color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 35%, transparent);
    overflow: hidden;
}
.fly-title::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -1px;
    height: 1px;
    background: var(--pulse-theme-glow-color, rgba(37, 99, 235, 0.40));
    filter: blur(2px);
    opacity: 0.65;
    pointer-events: none;
}
.fly-title .icon { width: 15px; height: 15px; color: var(--pulse-theme-primary, #2563eb); flex-shrink: 0; }

.fly-body { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 12px 14px; }

.rail-flyout-section { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--pulse-theme-text-muted); padding: 10px 8px 4px; }
.rail-flyout-section:first-child { padding-top: 0; }
.rail-flyout-item {
    display: flex; align-items: center; gap: 9px; width: 100%;
    padding: 7px 8px; border-radius: 8px; font-size: 13px; font-weight: 500;
    color: var(--pulse-theme-text); text-decoration: none; cursor: pointer;
}
.rail-flyout-item:hover { background: color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 7%, transparent); }
.rail-flyout-item.is-current { background: color-mix(in srgb, var(--pulse-theme-teal, #0d9488) 14%, transparent); color: color-mix(in srgb, var(--pulse-theme-teal, #0d9488) 70%, var(--pulse-theme-text, #1e293b)); font-weight: 700; }
.rail-flyout-item[aria-disabled="true"], .rail-flyout-item.is-disabled { opacity: .45; pointer-events: none; }
.rail-flyout-item .icon { width: 15px; height: 15px; color: var(--pulse-theme-text-muted); flex-shrink: 0; }
.rail-flyout-item.is-current .icon { color: color-mix(in srgb, var(--pulse-theme-teal, #0d9488) 70%, var(--pulse-theme-text, #1e293b)); }
.rail-flyout-logout-form { display: block; margin-top: 4px; border-top: 1px solid rgba(148,163,184,.25); padding-top: 4px; }

.rail-v2-scrim { display: none; }

/* ---------- Mobile bottom bar (hidden above the phone breakpoint) ---------- */
.rail-v2-mobile-wrap { display: none; }
.rail-v2-mobile-bar { display: none; }

/* ---------- Main content column ---------- */
.pulse-main-col {
    position: relative;
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 14px 14px 14px 10px;
}
/* overflow-y:auto (not the previous default `visible`) so a tab whose real content is
   taller than the flex-computed box (e.g. Configuration > Appearance's Theme/Panel
   Style/Animation grids) scrolls INSIDE <main> instead of painting past its own
   bottom edge. Without this, <footer> -- a sibling positioned right after <main>'s
   flex-computed (not real) height -- visually collided mid-content ("copyright shows
   up inside the Theme section, Panel Style/Animation render below it"). Scoped to
   <main> only, so the .pulse-app-shell single-shell-scroll model above (2026-07-19)
   still governs every page whose content actually fits; this scrollbar only ever
   activates when a tab's real content genuinely overflows its own box. */
.pulse-content-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* ---------- Responsive: phone -- swipeable bottom bar + sheet ---------- */
@media (max-width: 700px) {
    /* The rail's own box collapses to nothing; its fixed-position children
       (bottom bar, flyout-turned-sheet, scrim) render regardless.
       align-self:flex-start stops it stretching to the full column height it would
       otherwise get from .pulse-app-shell's default align-items:stretch (a flex item's
       own height:auto does NOT opt out of stretch on its own) -- confirmed live this
       was actually happening (rect reported 2px x 812px, not near-zero as intended).
       The ::before display:none is the real fix, not just hygiene: .nav-rail-v2 carries
       .glass-panel, whose ::before is position:absolute;inset:0 with its own
       backdrop-filter blur -- even confined to a 2px-wide sliver, that pseudo-element
       still spans the FULL viewport height immediately beside <main>'s own
       overflow-y:auto scroll container, and reproducibly corrupts Chrome's
       backdrop-filter compositing for the entire nearby scrolling content under
       mobile device emulation's 2x devicePixelRatio (confirmed both ways: hiding every
       OTHER new mobile nav element left the corruption in place; disabling backdrop-
       filter on ONLY this one pseudo-element, leaving the rail's stretched size and
       every other backdrop-filter on the page untouched, fully fixed it). The rail has
       no visible content of its own at this width anyway (only its position:fixed
       children, styled independently, are ever shown), so it never needed glass
       styling here -- see REDESIGN-TRACKING.md. */
    .nav-rail-v2 { position: static; width: auto; height: auto; margin: 0; padding: 0; align-self: flex-start; }
    .nav-rail-v2::before { display: none; }
    .rail-v2-brand, .rail-v2-desktop-icons { display: none; }
    .pulse-main-col { padding-left: 10px; padding-bottom: 74px; }

    .rail-v2-mobile-wrap {
        display: block;
        position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
        border-top: 1px solid var(--pulse-theme-header-border, rgba(203,213,225,0.4));
        background: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
        -webkit-backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
        backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    }
    .rail-v2-mobile-wrap::before, .rail-v2-mobile-wrap::after {
        content: ''; position: absolute; top: 0; bottom: 0; width: 22px; pointer-events: none; z-index: 2;
        opacity: 0; transition: opacity .15s ease;
    }
    .rail-v2-mobile-wrap::before { left: 0; background: linear-gradient(to right, var(--pulse-theme-page-bg-b, #fff), transparent); }
    .rail-v2-mobile-wrap::after { right: 0; background: linear-gradient(to left, var(--pulse-theme-page-bg-b, #fff), transparent); }
    .rail-v2-mobile-wrap.show-left::before { opacity: .9; }
    .rail-v2-mobile-wrap.show-right::after { opacity: .9; }

    .rail-v2-mobile-bar {
        display: flex; gap: 2px; overflow-x: auto; scroll-snap-type: x proximity;
        padding: 7px 8px; scrollbar-width: none;
    }
    .rail-v2-mobile-bar::-webkit-scrollbar { display: none; }
    .rail-v2-mobile-item {
        flex: 0 0 auto; scroll-snap-align: center;
        display: flex; flex-direction: column; align-items: center; gap: 3px;
        min-width: 58px; padding: 6px 10px; border-radius: 10px;
        color: var(--pulse-theme-text-muted); text-decoration: none;
    }
    .rail-v2-mobile-item .icon { width: 19px; height: 19px; }
    .rail-v2-mobile-item span { font-size: 9px; font-weight: 600; white-space: nowrap; }
    .rail-v2-mobile-item.is-active { color: var(--pulse-theme-primary, #2563eb); }

    .rail-v2-scrim {
        display: block; position: fixed; inset: 0; z-index: 25;
        background: rgba(15,23,42,.4); opacity: 0; pointer-events: none;
        transition: opacity .25s ease;
    }
    .rail-v2-scrim.is-open { opacity: 1; pointer-events: auto; }

    /* Same panel, repositioned: bottom sheet instead of a side drawer. */
    .rail-v2-flyout {
        position: fixed; left: 0; right: 0; bottom: 0; top: auto;
        width: auto; max-height: 62vh;
        border-radius: 18px 18px 0 0;
        transform: translateY(100%);
        transition: transform .28s cubic-bezier(.32,.72,0,1);
        z-index: 30;
    }
    .rail-v2-flyout.is-open { transform: translateY(0); }
    @media (prefers-reduced-motion: reduce) { .rail-v2-flyout { transition: none; } }
    .rail-v2-flyout::before {
        content: ''; display: block; flex-shrink: 0; width: 36px; height: 4px; border-radius: 3px;
        background: var(--pulse-theme-divider-color, rgba(203,213,225,.7)); margin: 10px auto 6px;
    }
    .fly-title { font-size: 14.5px; padding: 4px 18px 14px; }
    .fly-body { padding: 10px 18px 20px; }
    .rail-flyout-item { padding: 10px 8px; font-size: 14px; }
}

@keyframes pulse-aurora-drift {
    0%, 100% { transform: translate(0,0) scale(1); }
    50% { transform: translate(30px, 20px) scale(1.06); }
}

/* ------------------------------------------------------------
   Global fix: themed popup for any bootstrap-select using
   data-container="body" (2026-07-19)

   bootstrap-select's data-container="body" option doesn't just
   reposition the open dropdown -- it clones the popup into a fresh
   ".bs-container" div appended directly to <body>, escaping whatever
   modal/view ancestor it started under. A repo-wide audit found 46
   CSS rules across 13 files that all tried to theme these popups via
   "#someModalId .bootstrap-select .dropdown-menu" (or an equivalent
   view-class scope) -- several of those (confirmed: pulse-at-checkpoint.css's
   #mdlEvent/#mdlCheckpointFlowItemEvent/#mdlEditCheckpointGroup rules,
   likely pulse-reports.css's chart-field rules) were silently dead for
   exactly this reason, since the id/class they depend on is never an
   ancestor of the relocated node. This is a shared library-mechanism bug,
   not a per-screen design choice, so it's fixed once here (keyed off
   bootstrap-select's own .bs-container wrapper class, not a bare
   .dropdown-menu, so unrelated Bootstrap dropdowns -- nav menus, DataTables
   export buttons -- aren't affected) instead of duplicated per view. Every
   view-specific ".dropdown-menu"/".dropdown-item" rule scoped through a
   modal id or view class remains dead weight and should be treated as
   superseded by this rule, not a real override (it can never win a
   specificity fight it never enters). */
.bs-container .dropdown-menu {
    background: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
    -webkit-backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    border: 1px solid var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
    border-radius: var(--pulse-theme-radius-sm, 10px);
}
.bs-container .dropdown-menu .dropdown-item {
    color: var(--pulse-theme-text, #1e293b);
}
.bs-container .dropdown-item:hover,
.bs-container .dropdown-item.active {
    background: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
}

/* ------------------------------------------------------------
   Global fix: Accent-header modal chrome, rolled out app-wide
   (2026-07-19)

   Originated as a proposal for Asset Tag's Add Checkpoint modal,
   refined over several founder-reviewed rounds (5 design options ->
   accent gradient header chosen -> PulseAI-matched soft input border
   -> opaque solid background instead of translucent glass -- full
   history in REDESIGN-TRACKING.md). Founder then asked for this
   carried system-wide: "the styles you did for Add Checkpoint need
   to be carried across system wide... I opened the Events modal and
   noticed there is no styling." Clarified that header/background/
   border chrome should be UNIFORM on every modal -- only BUTTON
   colors differ by purpose (Save gets the gradient below; Delete
   stays semantic red via .btn-danger, untouched here, so destructive
   actions still read distinctly at a glance).

   A repo-wide audit found ~80 modals across every cluster, with ~77
   distinct #modalId/.viewClass-scoped .modal-header/.modal-content/
   .form-control rules across 27 CSS files, all following the SAME
   old plain-glass pattern (background: var(--pulse-theme-glass-bg),
   no gradient, --pulse-theme-input-border) with zero use of
   !important anywhere in that set. Rather than hand-editing 77 scope
   contexts across 27 files, this rule uses !important on only the
   properties that actually changed (background/border-color) --
   verified safe against that whole set, so it supersedes every one
   of them, current and future, without per-file duplication. Those
   77 old declarations are now dead weight (harmless, but see
   REDESIGN-TRACKING.md for the cleanup note) rather than a real
   override, the same relationship the .bs-container fix above has
   with its own now-dead per-view duplicates.

   Explicitly OUT of scope, not touched by this rule:
   - PulseAI's own #confirmation-modal (pulseai.css/pulseai-themes.css)
     -- the original reference design this whole Aurora Glass system
     was ported FROM, not a "Pulse" app screen; changing it is its
     own decision, not an automatic consequence of this rollout.
   - Login/Register's modal-shaped cards (.login-layout .modal-content
     in pulse-login-shared.css) -- structurally unaffected anyway,
     since those public unauthenticated pages use a different layout
     that never loads this file (no nav shell to speak of). */
/* ------------------------------------------------------------
   Modal header/button chrome, replaced 2026-08-02: "Layered Glass Depth"
   (superseded the 2026-07-19 accent-gradient rollout above -- see
   REDESIGN-TRACKING.md for the full mockup/critique history).

   The gradient wasn't wrong to want energy -- it painted a hue transition
   onto flat surfaces instead of using the material this app is actually
   built from: glass, translucency, and light. This extends that same
   vocabulary (already used by the shell's floating orbs and every panel)
   into the header and buttons instead of a second, unrelated hue.

   Header: frosted glass with a faint single-hue radial wash (not a
   two-hue band) and a blurred glow seam along the bottom edge in place
   of a hard border/gradient. Buttons: frosted-glass pills (999px radius,
   distinct from the app's 16px/10px panel radii so they read as a
   distinct tappable class of object) with a colored border carrying the
   semantic hue at full strength, a diagonal white sheen for the "glass
   catching light" cue, and a glow-on-hover via --pulse-theme-glow-color
   (defined per-theme, previously unused anywhere in this file). Danger
   still stays unambiguously distinct via its own hue, same as before. */
.modal-header {
    position: relative;
    background:
        radial-gradient(130% 180% at 12% 0%, color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 16%, transparent) 0%, transparent 60%),
        var(--pulse-theme-glass-bg, rgba(255,255,255,0.68)) !important;
    border-bottom: 1px solid var(--pulse-theme-glass-border, rgba(255,255,255,0.85)) !important;
    border-top-left-radius: var(--pulse-theme-radius, 16px) !important;
    border-top-right-radius: var(--pulse-theme-radius, 16px) !important;
    backdrop-filter: blur(var(--pulse-theme-glass-blur, 18px));
    -webkit-backdrop-filter: blur(var(--pulse-theme-glass-blur, 18px));
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.06),
        0 8px 24px -14px rgba(0, 0, 0, 0.55);
    overflow: hidden;
}
.modal-header::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -1px;
    height: 1px;
    background: var(--pulse-theme-glow-color, rgba(37, 99, 235, 0.40));
    filter: blur(2px);
    opacity: 0.65;
    pointer-events: none;
}
.modal-header .modal-title {
    color: var(--pulse-theme-text, #1e293b) !important;
    text-shadow: 0 0 20px color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 35%, transparent);
}
/* Bootstrap's .btn-close is a black SVG background-image with no
   currentColor support, brightened to white by the old rule via
   filter:invert() -- safe only because the old header was ALWAYS a
   saturated gradient. This header's fill now follows --pulse-theme-text
   (dark on light themes, light on dark ones), so a fixed invert would go
   invisible on half the theme catalog. Swapped for a text glyph that
   inherits color instead, so it tracks the same token the title already
   uses and needs no per-theme override. */
.modal-header .btn-close {
    background-image: none;
    opacity: 0.65;
    color: var(--pulse-theme-text, #1e293b);
    font-size: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity .15s ease, transform .2s ease;
}
.modal-header .btn-close::before {
    content: "\2715";
    font-size: 1rem;
    line-height: 1;
}
.modal-header .btn-close:hover {
    opacity: 1;
    transform: rotate(90deg);
}
.modal-content {
    background: var(--pulse-theme-solid-bg, rgba(235,242,255,0.97)) !important;
    /* This rule themes the modal's background but never set a matching
       text color, so any plain element with no more-specific color rule
       (bare <p>, <li>, etc. -- most modal body copy app-wide, e.g. every
       "Are you sure?" confirm dialog) falls back to Bootstrap's own
       unthemed --bs-body-color (#212529, near-black). Confirmed live on
       Keys' "Create Keys" modal in a dark theme: computed color came back
       rgb(33,37,41) against a rgba(3,10,28,.97) background -- functionally
       invisible. Setting color here is a low-specificity base every
       modal already using this file inherits; anything more specific
       (.modal-title's own #fff, .text-danger, .text-muted, etc.) is
       untouched since those selectors already win on specificity.
       !important is required here (not just additive): Bootstrap's own
       ".modal-content" rule ALSO sets "color: var(--bs-modal-color)" at
       the exact same specificity (one bare class), and since bootstrap.css
       loads after pulse-shell.css in _Layout.cshtml, Bootstrap's same-
       specificity rule would otherwise win the tie -- confirmed live, the
       color fix silently did nothing until !important was added here,
       matching the background property right above it. */
    color: var(--pulse-theme-text, #1e293b) !important;
}
.modal-body .form-control {
    border-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85)) !important;
}
.modal-body .form-control:focus {
    outline: none;
    border-color: var(--pulse-theme-primary, #2563eb) !important;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 20%, transparent);
}
/* Read-only plaintext fields (.form-control-plaintext) get NO box at all --
   founder feedback, 2026-07-19, on the RFID Checkpoint modal's Model/Serial
   No./Temperature fields: a rounded-corner box whose fill nearly matches the
   (now near-opaque) modal background reads as a rendering glitch rather than
   an intentional shape, not a "textbox that doesn't blend." Root cause: this
   file's own earlier rule gave .form-control-plaintext the same glass-border
   box as real editable .form-control fields, for visual consistency at the
   time -- exactly what now looks wrong. This is a general read-only-vs-
   editable distinction, not something specific to RFID's content, so it's
   fixed globally here rather than scoped to one modal: strip the box back to
   plain bold text, leaving only genuinely editable fields boxed, which also
   makes "you can type here" vs. "this is just shown" clearer at a glance. */
.modal-body .form-control-plaintext {
    background: transparent !important;
    border: none !important;
    border-radius: 0 !important;
    padding-left: 0 !important;
}
/* Every solid semantic button inside a modal is a frosted-glass pill,
   replaced 2026-08-02 (see the Layered Glass Depth header comment above
   for the full rationale). Each still pairs the button's own theme hue
   with a colored border at full strength and a hue-matched glow-on-hover,
   so Delete/Warning/etc. stay just as visually distinct from Save as they
   were under the gradient -- only the fill technique changed, not which
   hue means what. 999px radius is deliberate (see above); overrides the
   app-wide ".modal .btn" 10px rule below via !important since both
   selectors share the same specificity and this one must win.
   Deliberately NOT applied to .btn-outline-* -- same reasoning as the
   gradient version had (transparent fill, border+text only; the RFID
   checkpoint on/off toggles depend on that distinct look). */
.modal .btn-success,
.modal .btn-primary,
.modal .btn-danger,
.modal .btn-warning,
.modal .btn-info,
.modal .btn-secondary {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    border-radius: 999px !important;
    padding: 0.55rem 1.6rem;
    min-height: 40px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--pulse-theme-text, #1e293b) !important;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    transition: background .2s ease, border-color .2s ease, box-shadow .25s ease, transform .15s ease;
}
.modal .btn-success::before,
.modal .btn-primary::before,
.modal .btn-danger::before,
.modal .btn-warning::before,
.modal .btn-info::before,
.modal .btn-secondary::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(115deg, rgba(255, 255, 255, 0.35) 0%, rgba(255, 255, 255, 0.08) 35%, rgba(255, 255, 255, 0) 55%);
    opacity: 0.6;
    transition: opacity .2s ease;
    pointer-events: none;
}
.modal .btn-success:hover::before,
.modal .btn-primary:hover::before,
.modal .btn-danger:hover::before,
.modal .btn-warning:hover::before,
.modal .btn-info:hover::before,
.modal .btn-secondary:hover::before {
    opacity: 0.9;
}
.modal .btn-success:active,
.modal .btn-primary:active,
.modal .btn-danger:active,
.modal .btn-warning:active,
.modal .btn-info:active,
.modal .btn-secondary:active {
    transform: translateY(0) !important;
}
.modal .btn-success:focus-visible,
.modal .btn-primary:focus-visible,
.modal .btn-danger:focus-visible,
.modal .btn-warning:focus-visible,
.modal .btn-info:focus-visible,
.modal .btn-secondary:focus-visible {
    outline: 2px solid var(--pulse-theme-primary-light, var(--pulse-theme-primary, #2563eb));
    outline-offset: 2px;
}
.modal .btn-success,
.modal .btn-primary {
    background: color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 18%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border: 1.5px solid var(--pulse-theme-primary, #2563eb) !important;
}
.modal .btn-success:hover:not(:disabled),
.modal .btn-primary:hover:not(:disabled) {
    background: color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 30%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border-color: var(--pulse-theme-primary-light, var(--pulse-theme-primary, #2563eb)) !important;
    box-shadow: 0 0 22px 2px var(--pulse-theme-glow-color, rgba(37, 99, 235, 0.40)), 0 6px 20px -6px color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 60%, transparent);
    transform: translateY(-1px);
}
.modal .btn-danger {
    background: color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 18%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border: 1.5px solid var(--pulse-theme-danger, #dc2626) !important;
}
.modal .btn-danger:hover:not(:disabled) {
    background: color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 30%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border-color: color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 80%, white 20%) !important;
    box-shadow: 0 0 22px 2px color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 45%, transparent), 0 6px 20px -6px color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 60%, transparent);
    transform: translateY(-1px);
}
.modal .btn-warning {
    background: color-mix(in srgb, var(--bs-warning, #f59e0b) 18%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border: 1.5px solid var(--bs-warning, #f59e0b) !important;
}
.modal .btn-warning:hover:not(:disabled) {
    background: color-mix(in srgb, var(--bs-warning, #f59e0b) 30%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border-color: color-mix(in srgb, var(--bs-warning, #f59e0b) 80%, white 20%) !important;
    box-shadow: 0 0 22px 2px color-mix(in srgb, var(--bs-warning, #f59e0b) 45%, transparent), 0 6px 20px -6px color-mix(in srgb, var(--bs-warning, #f59e0b) 60%, transparent);
    transform: translateY(-1px);
}
.modal .btn-info {
    background: color-mix(in srgb, var(--bs-info, #0dcaf0) 18%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border: 1.5px solid var(--bs-info, #0dcaf0) !important;
}
.modal .btn-info:hover:not(:disabled) {
    background: color-mix(in srgb, var(--bs-info, #0dcaf0) 30%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border-color: color-mix(in srgb, var(--bs-info, #0dcaf0) 80%, white 20%) !important;
    box-shadow: 0 0 22px 2px color-mix(in srgb, var(--bs-info, #0dcaf0) 45%, transparent), 0 6px 20px -6px color-mix(in srgb, var(--bs-info, #0dcaf0) 60%, transparent);
    transform: translateY(-1px);
}
.modal .btn-secondary {
    background: color-mix(in srgb, var(--pulse-theme-text-muted, #64748b) 18%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border: 1.5px solid var(--pulse-theme-text-muted, #64748b) !important;
}
.modal .btn-secondary:hover:not(:disabled) {
    background: color-mix(in srgb, var(--pulse-theme-text-muted, #64748b) 30%, var(--pulse-theme-glass-bg, rgba(255,255,255,0.68))) !important;
    border-color: color-mix(in srgb, var(--pulse-theme-text-muted, #64748b) 80%, white 20%) !important;
    box-shadow: 0 0 22px 2px color-mix(in srgb, var(--pulse-theme-text-muted, #64748b) 45%, transparent), 0 6px 20px -6px color-mix(in srgb, var(--pulse-theme-text-muted, #64748b) 60%, transparent);
    transform: translateY(-1px);
}
/* Every modal button gets the app's signature soft corner radius -- an audit
   (2026-07-29) checking every screen already marked "redesigned" for modals
   left behind found this same gap repeated across several destructive-delete
   modals (Employees, EmployeeTypes, Notifications) with no scoped radius rule
   at all, falling back to Bootstrap's default ~6px. Fixed once, globally,
   rather than re-adding the same ".screen .btn { border-radius: ... }" rule
   per file -- the same "shared mechanism, fix globally" call already made for
   the button-gradient rules directly above. */
.modal .btn {
    border-radius: var(--pulse-theme-radius-sm, 10px);
}
/* Consequence-aware delete-confirmation pattern, established on OCREventList's
   #mdlDeleteEvent (2026-07-29): danger icon + text naming the specific item
   being deleted + a real Cancel/Delete footer, instead of a bare "Are you
   sure?" with only the header X to back out. The same OCREventList review
   found this exact gap repeated on 6 other already-"redesigned" screens
   (Employees, EmployeeTypes, Keys, Security, Users, TemplateList, Notifications)
   -- pulled out here as a shared utility so each of those doesn't duplicate
   its own copy of the same three rules. */
.pulse-delete-confirm-icon {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--pulse-theme-danger, #dc2626) 30%, transparent);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 16px;
}
.pulse-delete-confirm-icon i {
    color: var(--pulse-theme-danger, #dc2626);
    font-size: 22px;
}
.pulse-delete-confirm-title {
    color: var(--pulse-theme-text, #1e293b);
    font-size: 15px;
    font-weight: 600;
    margin: 0 0 6px;
}
/* Full --pulse-theme-text, not -text-muted -- an exhaustive WCAG contrast
   audit across all 21 theme presets (2026-07-29 OCREventList sign-off pass)
   found text-muted-on-solid-bg fails 4.5:1 in 6 themes (kanso-ink 3.47:1,
   confetti-pop 3.98:1, arctic-glass 4.23:1, cyber-grid 4.26:1, violet-storm
   4.39:1) with no accent-mix to lean on for a cushion, unlike this app's
   other muted-text usages that sit on a tinted/glass background instead of
   a flat solid one. */
.pulse-delete-confirm-text {
    color: var(--pulse-theme-text, #1e293b);
    font-size: 13px;
    margin: 0;
    line-height: 1.5;
}
.pulse-delete-confirm-text strong {
    color: var(--pulse-theme-text, #1e293b);
}
.modal .bootstrap-select > .dropdown-toggle {
    border-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85)) !important;
    color: var(--pulse-theme-text, #1e293b) !important;
}
/* bootstrap-select's own CSS gives the closed toggle two different
   unthemed text colors depending on state, neither ever fixed by the
   border-color rule above: bs-placeholder ("Nothing selected") is a
   hardcoded #999 (bootstrap-select.css:66), and the default/populated
   state inherits Bootstrap's ".btn-light" text color, pure black
   (bootstrap.css) -- confirmed live on Checkpoint Groups' Checkpoints
   picker: "Nothing selected" read as a washed-out gray, and selecting
   a real item turned the text literally rgb(0, 0, 0) against the
   dark themed input background. !important matches the border-color
   rule directly above (needed for the same reason: bootstrap-select's
   own ".bs-placeholder" rule and Bootstrap's ".btn-light" rule are
   both plain, unguarded declarations at comparable specificity, so
   matching their existing precedent here rather than relying on a
   load-order tiebreak). */
.modal .bootstrap-select > .dropdown-toggle.bs-placeholder {
    color: var(--pulse-theme-text-muted, #64748b) !important;
}

/* ------------------------------------------------------------
   Global fix: bootstrap-select wrapper chrome + actionsBox theming
   (2026-07-20)

   Originated as founder-reported bugs on Inventory Products' Checkpoint
   Flows picker ("double rings around the select picker", "widen the
   select picker to fill the remainder of the space", "style the Select
   All/Deselect All... make sure this is a system wide fix") --
   confirmed via a repo-wide grep for "actionsBox" that this exact
   multi-select + actionsBox pattern is used in at least two places:
   AssetTag/productType.js (#FlowIDs, styled per-screen in
   pulse-at-tags-products.css) and AssetTag/editOrder.js (#TagID on the
   Edit Order modal) -- which loads a completely different, unrelated
   stylesheet (pulse-at-orders.css) that never touched any of this, so
   Orders' Tag picker has had the exact same unstyled-actionsbox/
   double-ring/cramped-width bugs the whole time, just never reported.
   Consolidated here (promoted from pulse-at-tags-products.css, which
   only ever reaches the two AssetTag screens that link it) instead of
   copy-pasting into pulse-at-orders.css too, so any FUTURE actionsBox
   picker gets this for free as well.

   1) Wrapper double-ring + cramped width: bootstrap-select copies
      classes (here, "form-select"/"form-control") from the ORIGINAL
      <select> onto the wrapper <div> it generates, so Bootstrap's own
      stock .form-select/.form-control rules paint a SECOND border/
      background around the already-styled .dropdown-toggle button
      inside it ("double ring"), and separately compute width:100%
      against the wrapper's container size AT INIT TIME -- which is 0,
      since .selectpicker() runs in $(document).ready() while the
      enclosing modal is still hidden, so "100% of 0" falls back to a
      content-measured default (~220px) instead of actually filling the
      column like every sibling field does. Neutralizing the wrapper's
      chrome entirely (it's a layout container, never meant to render
      its own panel) and forcing width via CSS -- which resolves live at
      paint time, unlike the JS-computed value -- fixes both at once.
   2) actionsBox "Select All"/"Deselect All": bootstrap-select renders
      these as plain Bootstrap .btn-light buttons (bright white bg,
      black text) with zero theme awareness, clashing hard against every
      dark preset. Re-themed to match the same subtle glass-button
      recipe used for DataTables toolbar buttons elsewhere in this file.
   Neither needs !important: .bootstrap-select.form-select/.form-control
   (2 classes) already outguns Bootstrap's bare .form-select/.form-control
   (1 class), and .bs-actionsbox .btn (2 classes) already outguns .btn-light
   (1 class), regardless of stylesheet load order.

   :not(.bs-container) added 2026-07-22 -- founder-reported "select pickers
   filling up the entire screen" on Report Filters' Edit page. Root cause:
   bootstrap-select copies "form-select"/"bootstrap-select" onto the
   .bs-container clone too (same class-copying behavior described in the
   .bs-container fix above), so this rule's width:100% !important was ALSO
   matching that clone. For the normal inline toggle, width:100% correctly
   fills its parent; but .bs-container is position:absolute with no
   positioned ancestor (relocated straight under <body>), so its "100%"
   resolved against the viewport instead, clobbering the correct per-select
   pixel width bootstrap-select's own JS had just set inline. Confirmed live
   via getComputedStyle: inline style had "width: 207px" but computed width
   was "1337px" until this exclusion was added. */
.bootstrap-select.form-select:not(.bs-container),
.bootstrap-select.form-control:not(.bs-container) {
    border: none !important;
    background: transparent !important;
    padding: 0 !important;
    width: 100% !important;
}
.bs-actionsbox .btn {
    background: var(--pulse-theme-btn-bg, rgba(255,255,255,0.82));
    border: 1px solid var(--pulse-theme-btn-border, rgba(37,99,235,0.22));
    color: var(--pulse-theme-text, #1e293b);
    border-radius: var(--pulse-theme-radius-sm, 10px);
}
.bs-actionsbox .btn:hover {
    background: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
    box-shadow: var(--pulse-theme-btn-hover-shadow, 0 2px 8px rgba(37,99,235,0.15));
}

/* ------------------------------------------------------------
   Global fix: unthemed DataTable/form chrome across every module
   (2026-07-19)

   Three related gaps confirmed live on Checkpoint Groups
   (pulse-at-hub.css) and traced app-wide via a 21-file audit of every
   module's own DataTable/form CSS -- all share the same root cause:
   each module theme its search input, buttons and active pagination,
   but never touched these three, so they fall back to raw Bootstrap
   defaults that clash with the dark presets. Same shared-mechanism
   pattern as the .bs-container fix above, so it's fixed once here
   instead of duplicated per module. Scoped additively (only sets
   currently-unset properties, no !important needed) so the module
   files that already theme one of these on their own
   (pulse-at-orders.css, pulse-tc-satellites.css, pulse-project.css,
   pulse-doc-admin.css, pulse-profile-core.css, pulse-dashboard.css,
   pulseai.css) keep winning by normal cascade order. */

/* 1. DataTables' length-picker <select> inherits Bootstrap's
   .form-select color (var(--bs-body-color), a hardcoded near-black
   the app never rebinds -- see the .text-muted comment in site.css
   for the same root cause) instead of the active theme's text color.
   ".dt-length select" (a class + a type) is more specific than
   ".form-select" (a class alone), so this wins regardless of load
   order without needing !important.

   Also sets background/border/radius/padding, not just color --
   confirmed via live cross-cluster testing (Configuration/Keys, whose
   own pulse-admin-crud.css never themes .dt-length select at all) that
   a color-only fix creates the SAME bug in reverse on the 4 module
   files with zero existing .dt-length select rule: the select keeps
   Bootstrap's plain white background, and the new light theme-text
   color becomes invisible against it instead of the original dark
   text being invisible against a themed dark background. The full
   treatment here is copy-identical to the token pattern every module
   that already themes this selector already uses (e.g.
   pulse-at-orders.css, pulse-tc-satellites.css), so on those modules
   this is a harmless same-value redundant match, not a conflict. */
.dt-length select {
    color: var(--pulse-theme-text, #1e293b);
    background-color: var(--pulse-theme-input-bg, rgba(255,255,255,0.85));
    border: 1px solid var(--pulse-theme-input-border, rgba(203,213,225,0.6));
    border-radius: var(--pulse-theme-radius-sm, 10px);
    /* Right side needs to clear Bootstrap's native .form-select caret
       (its background-image chevron sits at "calc(100% - 12px)", ~20px
       wide including its own margin) -- a uniform 10px here left the
       caret painted directly over the "10"/"25"/etc. value, confirmed
       live via getComputedStyle (rendered box was only 38px wide, all
       of it claimed by text+icon with no clearance). Left/top/bottom
       stay tight since this is a compact "sm" control. */
    padding: 5px 28px 5px 10px;
}
/* background-color above is translucent (blends against the page behind
   the closed control), but the OS-drawn open option list has no page
   content behind it to blend against -- alpha there collapses to flat
   system grey (founder-reported on OCREmail's Template picker, confirmed
   live: closed box correctly dark, open list grey). Options need an
   explicitly opaque background; this is the single most-global .dt-length
   select rule in the app so it's fixed once here rather than per-module --
   same reasoning as the rule above it. */
.dt-length select option {
    background: var(--pulse-theme-page-bg-a, #fff);
    color: var(--pulse-theme-text, #1e293b);
}

/* 3. DataTables' top toolbar row (length picker / export buttons /
   search box) is generated with a fixed col-sm-4/col-sm-3/col-sm-5
   split -- identical across every DataTable in the app (Configuration,
   Reports, ReportFilters, Notifications, AssetTag, ...). The 3-button
   export group (Copy/CSV/Excel) is the tightest of the three and wraps
   internally (2-over-1, its own .flex-wrap class) well before the row
   actually runs out of room to just stack the three zones instead --
   confirmed live at 700px: the buttons column was 162px wide while the
   unwrapped button group needs ~285px, so "Excel" fell to an awkward
   centered second line under "Copy"/"CSV". Scoped via :has() to
   DataTables' own generated wrapper classes (.dt-length/.dt-buttons/
   .dt-search), not the bare "col-sm-*" classes those columns also
   carry, since col-sm-3/4/5 are generic Bootstrap grid utilities reused
   all over the app for unrelated layouts. Breakpoint matches Bootstrap's
   own "lg" (992px) -- comfortably wider than the export group's natural
   width in every module measured, and roughly where the nav rail itself
   also auto-collapses, so there's headroom on both sides of the cutoff. */
@media (max-width: 991.98px) {
    .dt-container .row > div:has(> .dt-length),
    .dt-container .row > div:has(> .dt-buttons),
    .dt-container .row > div:has(> .dt-search) {
        flex: 0 0 100%;
        max-width: 100%;
        text-align: left;
    }
}

/* 3b. 2026-08-09, system-wide mobile audit: the row wrapping the actual
   <table> (DataTables' Responsive extension is supposed to hide/collapse
   columns to fit, but its own JS width calc proved unreliable across
   several real screens here -- Checkpoint Groups, Products, Report
   Filters, Reports, Notifications, OCR Events all independently showed
   either squeezed/overlapping columns or content pushed past the visible
   width with zero way to reach it) had no bounded overflow of its own.
   With nothing local to scroll, the ONLY thing that could absorb the
   table's real width was main.pulse-content-scroll several ancestors up
   -- dragging the WHOLE page (KPI cards, header, everything) sideways
   just to reach a truncated column, on the rare screens where that
   ancestor even allows it; everywhere else (most screens -- confirmed via
   .pulse-app-shell's own overflow-x:hidden) the content is just gone,
   unreachable. Giving the table's own row a local horizontal scrollport
   fixes both: reachable via a contained swipe, page stays put. Scoped to
   mobile only -- desktop's wider viewport rarely needs this and the
   existing Responsive behavior there is unaffected. */
@media (max-width: 767.98px) {
    .dt-container .row > div:has(> table.dataTable) {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
}

/* 2. Bare <label> elements (no class) are the single most common
   form-label pattern app-wide (modals, edit pages) and have zero
   color set anywhere in the redesign -- confirmed live on
   CheckpointGroupList's "Add Checkpoint Group" modal, and by a sweep
   of every existing label rule across wwwroot/css. Kept to the
   lowest possible specificity (a bare element selector) on purpose:
   every module's own label override (.form-label, .col-form-label,
   "#id label", ".some-page label", etc. -- 20+ of them, all at least
   one class) is strictly more specific and keeps winning untouched,
   including the ones that intentionally look different (e.g.
   Notifications/Edit's day-of-week toggle labels styled as buttons
   via a .btn class, or PulseAI's own #pai-cost-dashboard panel,
   which is deliberately always-light and colors its labels via a
   higher-specificity .pai-cost-control-label class). */
label {
    color: var(--pulse-theme-text, #1e293b);
}
/* bootstrap-multiselect's popup (#cbxAPIKeys on UserActivity, see
   pulse-activity.css) clones its option <label>s into a
   ".multiselect-container" appended with its own background that is
   never theme-colored (stays Bootstrap white) -- letting the rule
   above reach it would paint theme text (pale-on-white for many dark
   presets) onto that still-white popup. This restores plain
   inheritance instead, leaving the popup exactly as it renders today
   until it gets themed as its own follow-up (its background isn't
   theme-driven either). */
.multiselect-container label {
    color: inherit;
}

/* 3. DataTables pagination buttons: every module themes the ACTIVE
   page and the :hover background, but none theme the IDLE state --
   confirmed live as a stock Bootstrap gray pill against the dark
   panel behind it. ".dt-paging-button.page-item .page-link" (three
   classes) is used rather than bare ".page-link" so this: (a) beats
   Bootstrap's own base ".page-link" rule (one class) regardless of
   load order without needing !important, and (b) does not match
   PulseAI's separate "#canvas-datatable_wrapper" pagination widget,
   which already has its own complete, correctly-themed idle/hover/
   active/disabled states built on plain ".page-item .page-link"
   (no "dt-paging-button" class -- see pulseai.css). Every module's
   own ".active"/":hover" rule (all more specific and/or !important)
   keeps overriding this on those states; this only fills the gap
   they leave at rest. */
.dt-paging-button.page-item .page-link {
    background: var(--pulse-theme-btn-bg, rgba(255,255,255,0.82));
    border-color: var(--pulse-theme-btn-border, rgba(37,99,235,0.22));
    color: var(--pulse-theme-text, #1e293b);
    border-radius: var(--pulse-theme-radius-sm, 10px);
}

/* 4. Every DataTable in the app is missing its horizontal row-divider
   lines (2026-07-19) -- confirmed via getComputedStyle this is a real
   rendering gap, not a color/contrast issue: Bootstrap 5's
   .table-bordered splits its border between the <tr> (gets the
   top/bottom border via "border-width: var(--bs-border-width) 0") and
   the <td>/<th> (gets only left/right via
   "border-width: 0 var(--bs-border-width)"), relying on the <tr>'s
   own border to supply the horizontal line. That split only actually
   paints under border-collapse: collapse -- per the CSS table
   rendering model, borders declared directly on <tr> are never drawn
   under border-collapse: separate. DataTables' own vendor CSS
   (datatables.net.min.css) unconditionally sets
   "table.dataTable { border-collapse: separate }" on every DataTable
   in the app (needed for its own internal layout), which silently
   drops every row's horizontal divider app-wide while leaving the
   vertical column dividers (drawn on the <td>, which does paint under
   "separate") untouched -- exactly the asymmetric "columns have lines,
   rows don't" look seen live. Fixing by putting the horizontal line
   back on the cell itself (which does paint under "separate"),
   instead of fighting DataTables' own required collapse mode. */
table.dataTable > :not(caption) > * > * {
    border-bottom-width: var(--bs-border-width, 1px);
}

/* 5. DataTables' own .table-bordered CSS (datatables.net.min.css)
   deliberately never draws an outer box around the whole table --
   every cell gets "border-top-width: 0" (interior lines rely on the
   row ABOVE's own bottom border instead, avoiding doubled-up
   thickness at each boundary) and the LAST row additionally gets its
   own "border-bottom-width: 0" via
   "table.dataTable.table-bordered tr:last-child th, ... td" -- both
   by DataTables' explicit design, not a bug. Before fix #4 above
   (which restored the missing interior row-divider lines) this
   absence was invisible, since nothing had any border at all; now
   that interior lines exist, the missing top-of-table and
   bottom-of-table edges read as visibly incomplete -- confirmed live
   per the founder's report ("The top-most and bottom-most border are
   gone too"). Rather than fighting DataTables' specific
   tr:last-child suppression (which exists for a reason -- avoiding
   doubled border thickness between interior rows), this closes the
   box at the OUTER table level instead: a border on the <table>
   element itself paints independently of any cell/row border logic,
   so it supplies just the top/bottom bookend lines without touching
   the interior row-divider treatment. Uses the same
   --pulse-theme-header-border token already governing every row/
   column divider's color (via site.css's ".table" block, which feeds
   Bootstrap's own --bs-table-border-color), so it matches exactly. */
table.dataTable.table-bordered {
    border-top: var(--bs-border-width, 1px) solid var(--pulse-theme-header-border, rgba(203,213,225,0.4));
    border-bottom: var(--bs-border-width, 1px) solid var(--pulse-theme-header-border, rgba(203,213,225,0.4));
}

/* 6. Table header background wash (2026-07-19) -- founder-approved
   after an interactive opacity mockup, see REDESIGN-TRACKING.md.
   Adds a soft primary-color wash behind every DataTable header row,
   giving headers a bit more visual weight than the existing bare 2px
   accent underline, without going as far as the bold gradient
   treatment used on modal headers. Deliberately NOT a gradient here:
   a modal is a rare, singular, elevated surface that benefits from a
   strong visual anchor, but a DataTable header repeats on every
   table, every module, and needs to stay legible for scanning rather
   than compete with the badges/chips/colored buttons already inside
   the rows below it.
   color-mix() (not a hand-picked rgba) so this derives correctly from
   each theme's own solid --pulse-theme-primary hex across all 15
   presets, matching the same idiom already used for
   --bs-table-striped-bg/-hover-bg/-active-bg in site.css. Every
   per-module thead th rule (18+ files) already sets its own
   border-bottom/color but never a background -- confirmed via a
   repo-wide grep -- so this is purely additive, no conflict. The one
   real exception, pulseai.css's "#canvas-datatable thead th", sets
   its own background with "!important" and PulseAI's own separate
   --pai-* token namespace; that always wins regardless of this rule,
   so it's left untouched rather than needing an explicit exclusion.
   Selector is "table.dataTable.table-bordered thead th" (2 classes),
   not the simpler bare "table.dataTable thead th" (1 class) --
   confirmed live via CSSOM that DataTables' own vendor rule
   "table.table.dataTable > :not(caption) > * > *" (2 classes) was
   out-specificity-ing a 1-class version of this rule and winning with
   "background-color: var(--bs-table-bg)" (transparent), so the wash
   never painted. Matches the same "table.dataTable.table-bordered"
   selector already used and verified safe for fix #5 above. */
table.dataTable.table-bordered thead th {
    background: color-mix(in srgb, var(--pulse-theme-primary, #2563eb) 8%, transparent);
}

/* 7. Long unbroken strings (serial numbers, GUIDs, order numbers) overflow
   their cell and visually spill into the next column instead of wrapping
   (2026-07-21, founder-reported on Tags, but confirmed via grep to be
   app-wide -- every one of the 14 DataTable instances in the app uses
   "table-layout:fixed" inline on the <table> element itself, none of
   them have any overflow-wrap/word-break handling on cells). With
   table-layout:fixed, column widths are locked once computed, but CSS's
   default "white-space: normal" can still only wrap at whitespace --
   a string with no spaces (e.g. a 24-char RFID serial number) has
   nowhere to break, so it renders past the fixed column boundary and
   visually overlaps whatever is next to it, rather than being contained
   or clipped. Fixing with overflow-wrap (lets a break happen mid-string
   as a last resort, only when no natural word-break point exists --
   normal text with spaces is completely unaffected) rather than
   text-overflow:ellipsis, since these are identifying values (serial
   numbers, order numbers) a user needs to actually read, not truncate. */
table.dataTable > :not(caption) > * > * {
    overflow-wrap: break-word;
}

/* 8. DataTables expand/collapse control-cell indicator, unified to ONE
   icon app-wide (2026-07-21). First pass (founder-reported "the arrow
   ... is not matching the custom css style") themed the vendor's raw
   CSS border-triangle via --pulse-theme-text. Second pass, same day,
   founder-reported the Tags table showing BOTH that triangle AND a
   second, pre-existing icon -- a manually-injected
   "<i class='fa-circle-plus'>" (tag.js/editOrder.js's "dt-control"
   columns render this real icon themselves, colored teal already on
   Tags via pulse-at-tags-products.css's "#tblTags td.dt-control
   i.fa-circle-plus" rule) -- and asked for ONE consistent identifier
   app-wide, matching the ALREADY-established design concept, not the
   newly-themed triangle. The circle-plus is that established concept:
   it predates this session (the founder referred to EditOrder's as
   "the green + icon" months before Scans/Tags existed) and I reused it
   verbatim when building Tags -- the triangle only entered the picture
   as an unrelated bugfix for tables that never had a custom icon.

   Two DataTables mechanisms exist and get different treatment now:
   - "td.dt-control" (Tags, EditOrder) already renders the real
     Font-Awesome icon via JS -- the vendor's own ::before triangle is
     suppressed entirely here (content:none) so only that real icon
     shows, fixing the reported doubling.
   - "td.dtr-control" (the Responsive extension's auto-inserted control
     cell -- the majority pattern app-wide: ocrResultList.js,
     checkpoint_reader.js, employeeEdit.js, Reports/generate.js,
     editResultsGridAssetTracking.js, editResultsGridMonitoring.js,
     ocr_eventList.js, UserActivity.cshtml, pulseai_canvas.js, and
     scanList.js) had no icon of its own at all -- its ::before is
     redrawn from a border-triangle into a small solid circle + "+"
     glyph, matching Tags/EditOrder's icon in shape and rolling the
     SAME visual out to every other DataTable, without touching any of
     those screens' JS. Pure CSS content ("+"), not a Font-Awesome
     codepoint injection -- avoids depending on the exact icon-font
     unicode value and font-loading timing, and reuses this app's own
     established "solid-color circle badge, white glyph" language
     already used for every KPI card badge, rather than trying to
     pixel-match a vendor icon. --pulse-theme-teal fill matches the
     token Tags' real icon already uses (EditOrder's matching fix is
     alongside pulse-at-orders.css's own dt-control rule). No swap to a
     "-" glyph on expand -- dt-control's real icon doesn't do that
     either (confirmed in tag.js/editOrder.js, no icon-swap logic
     exists there), so this stays static to match, not to introduce a
     new state Tags/EditOrder don't have. */
#Content table.dataTable td.dt-control:before,
#Content table.dataTable th.dt-control:before {
    content: none;
}
#Content table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
#Content table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control {
    position: relative;
}
#Content table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
#Content table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before {
    content: "+";
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 16px;
    height: 16px;
    margin-right: .5em;
    border: none;
    border-radius: 50%;
    background: var(--pulse-theme-teal, #0d9488);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
}
/* Tags/EditOrder's manual "dt-control" column (see above) can ALSO pick up
   Responsive's own "dtr-control" class once it has a responsivePriority
   (2026-07-21, found live during the priority rollout: Responsive started
   choosing that already-visible column as its own auto-control cell, so it
   carries both classes and the badge above stacked a second circle-plus
   next to tag.js's/editOrder.js's real rendered icon). Suppress the badge
   in that combined case -- the manual icon already covers it. */
#Content table.dataTable.dtr-inline.collapsed > tbody > tr > td.dt-control.dtr-control:before,
#Content table.dataTable.dtr-inline.collapsed > tbody > tr > th.dt-control.dtr-control:before {
    content: none;
}

/* 9. Horizontal scrollbar on every DataTable screen (2026-07-28,
   founder-reported on "most screens in the Configuration collection"
   and AssetTag's Checkpoints List, confirmed live to be app-wide:
   every DataTable wrapper measured had its scrollWidth exactly 12px
   wider than its clientWidth). Root cause: DataTables' Bootstrap5
   integration (dt-bootstrap5) renders its own toolbar/info/pagination
   rows as raw Bootstrap grid markup (".row" > ".col-sm-*"), which
   assumes it always sits inside a real Bootstrap ".container"/
   ".container-fluid" -- that ancestor is what supplies the matching
   positive padding that cancels ".row"'s own negative
   "calc(var(--bs-gutter-x) * -0.5)" margin (-12px each side by
   default). None of this app's own panel/tab-content wrappers are
   actual Bootstrap containers, so nothing cancels the negative
   margin -- confirmed live via getBoundingClientRect that
   ".dt-container > .row" measures exactly 24px wider than its
   parent, bleeding 12px past the right edge (the visible scrollbar)
   and 12px past the left (silently absorbed, no left-side symptom).
   Zeroing the row's own margin is the standard fix for "Bootstrap
   grid row used without a Bootstrap container" -- the columns' own
   padding (already 12px each side) still supplies the same visual
   gutter between columns, just without the extra bleed past the
   row's edge. Scoped to "> .row" (immediate children only) so this
   never touches an unrelated, intentionally-Bootstrap-nested ".row"
   elsewhere in the app. */
.dt-container > .row {
    margin-left: 0;
    margin-right: 0;
}

/* 10. Paginated DataTable panels don't visually "close" at the
   bottom (2026-07-28, founder-reported twice on the Templates list --
   "the bottom border ... is still missing"). The border IS present:
   every ".glass-panel" already carries a border-bottom sourced from
   "--pulse-theme-glass-border" (confirmed live via getComputedStyle).
   But that token is calibrated as a barely-there hairline BY DESIGN
   in dark presets (e.g. Neon Obsidian's is 20% opacity cyan over a
   near-black page background -- blending the two shows the effective
   rendered contrast is negligible). That subtlety reads fine for a
   card edge sitting next to other chrome, but a long paginated list
   ending mid-scroll has nothing else nearby to signal "this is the
   end", so the missing visual weight reads as a genuinely missing
   border, exactly as reported -- twice.
   Fixing by giving panels that actually contain a paginated
   DataTable a bolder closing edge, reusing the exact same "2px solid
   var(--pulse-theme-primary)" idiom already established for the
   table header's own accent underline (see e.g. pulse-doc-admin.css
   "table.dataTable thead th") instead of the glass-border token --
   full opacity guarantees real visibility against every preset's
   page background, light or dark, and bookends the header's own
   underline symmetrically. Scoped via :has() to only DataTable-
   containing panels, so plain card/form glass-panels elsewhere (no
   pagination, no "did this end?" ambiguity) keep their original,
   deliberately subtle edge untouched. */
.glass-panel:has(.dt-container) {
    border-bottom: 2px solid var(--pulse-theme-primary, #2563eb);
}

/* 11. DataTable content sitting flush against the panel edges, no
   breathing room top or bottom (2026-07-28, founder-reported: "can
   you put some padding around the top and bottom of the html
   tables"). Root cause: this app's ".glass-panel" utility class
   itself never sets padding -- every screen's own scoped wrapper
   class (e.g. Templates' ".pulse-doc-admin", "padding: 22px 24px")
   is individually responsible for that. Several screens (Security,
   Users, Employees, Employee Types, Checkpoints, Checkpoint Groups,
   Checkpoint Flow, Products, Tags, Report Filters/Reports/
   Notifications lists) never had their own padding rule at all --
   they were relying entirely on the shared ".tab-content:not(:has(
   .glass-panel))" fallback padding (20px 22px, see pulse-admin-
   crud.css/pulse-at-hub.css/etc.), which stops applying the moment
   a nested ".glass-panel" exists -- exactly what the earlier same-day
   fix (#10 above, plus the glass-panel-consistency pass documented in
   REDESIGN-TRACKING.md) added to all of them. Confirmed live via
   getComputedStyle on Security: "padding: 0px", heading sitting
   literally 1px from the panel's own edge.
   Fixing at the same ":has(.dt-container)" scope as #10, using the
   same "20px 22px" value already standard everywhere else in the app,
   so every DataTable-containing panel gets consistent breathing room
   regardless of whether its own scoped class happens to duplicate
   this (a handful, e.g. Templates, already declare their own slightly
   different padding -- this is more specific due to :has() and wins,
   which is a harmless 1-2px difference, not a visual regression). */
.glass-panel:has(.dt-container) {
    padding: 20px 22px;
}

/* ------------------------------------------------------------
   Global fix: icon-only row-action buttons (.pulse-icon-btn) sizing
   (2026-07-19)

   The founder asked to standardize every DataTable action column
   app-wide on the icon+tooltip pattern piloted on AssetTag's
   Checkpoints screen (tblRFIDReaders). That pilot's own sizing rule
   was written as "#tblRFIDReaders .pulse-icon-btn" in
   pulse-at-checkpoint.css -- fine for that one screen, but
   pulse-at-checkpoint.css is intentionally never loaded through
   _Layout.cshtml (that view is a standalone kiosk page with its own
   <link>, see that file's own header comment), so every OTHER module
   adopting the ".pulse-icon-btn" class as part of this rollout (e.g.
   Profile's tblInvoices) would get the class with none of its sizing
   -- a plain wide Bootstrap button with visible padding, not the
   square bare-icon control the reference design shows. Same
   shared-mechanism situation as the other global fixes in this file
   (a per-view rule that every future consumer needs), so it's
   promoted here off the bare class, unscoped by table id, instead of
   being re-declared with a new id selector on every module's own CSS
   file as each one is converted. The original #tblRFIDReaders-scoped
   rule is left in place (harmless same-value redundant match there,
   and that view never loads this file anyway). */
/* Founder-reported 2026-08-06 (Timesheet Actions buttons still wrapping
   despite a column width sized for all 4 to fit): confirmed live via
   getComputedStyle that each button was rendering at ~41px instead of the
   34px this rule declares -- this rule sets flex-shrink:0 but never
   flex-grow:0, so inside a .btn-group (Bootstrap's own
   ".btn-group > .btn { flex: 1 1 auto }") these buttons grow to fill
   whatever space is available rather than staying fixed-size, regardless of
   this rule's higher ID-scoped specificity elsewhere (specificity only
   matters when two rules target the same property, and this one never
   contested flex-grow). Only surfaced now because every prior multi-button
   .btn-group in the app happened to have a column width that left ~zero
   slack to grow into -- a fragile coincidence, not evidence the bug wasn't
   there. Confirmed live: setting flex-grow:0 on a button dropped its
   rendered width from 41.3px back to the intended 34px. */
.pulse-icon-btn {
    width: 34px;
    height: 34px;
    padding: 0;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-grow: 0;
    flex-shrink: 0;
}

/* Mobile-only touch-target bump for icon-only row-action buttons (2026-08-09).
   Mobile audit confirmed .pulse-icon-btn's 34x34 desktop size (above) sits below
   the ~44px minimum recommended touch target on phones -- reproduced on the
   Activity/Monitoring hub's Report Filters/Reports/Notifications row actions,
   and likely present on every other screen using this shared class. 44px matches
   the min-height already established for icon buttons inside mobile-card action
   bars app-wide (e.g. #pulseUsersMobileCards .pulse-users-card-actions
   .pulse-icon-btn, #pulseEmployeesMobileCards .pulse-employees-card-actions
   .pulse-icon-btn, and others in site.css from that earlier rollout), so this
   keeps the touch target consistent between DataTable rows and mobile cards
   instead of introducing a second size. Scoped to this app's standard
   767.98px mobile breakpoint (see the .dt-container rule above) so desktop
   stays at the original compact 34x34.
   !important is required here: site.css has ~20 ID/class-scoped
   ".pulse-icon-btn" re-declarations (one per table/module, e.g.
   "#tblReportFilters .pulse-icon-btn", "#tblReports .pulse-icon-btn",
   "#tblNotifications .pulse-icon-btn") that each hardcode width/height:34px
   at higher specificity than this bare-class rule -- confirmed live via
   getComputedStyle that without !important those rules kept every one of
   those tables at 34px on mobile regardless of this media query matching.
   None of those 34px re-declarations use !important themselves, so this
   safely wins everywhere without needing to touch each one individually.
   This does NOT affect the mobile-card action-bar rules from the earlier
   mobile-card rollout (e.g. #pulseUsersMobileCards .pulse-users-card-actions
   .pulse-icon-btn) -- those already set "width: auto !important" at higher
   specificity (ID+class+class vs. bare class), so they continue to win and
   keep stretching to fill their row as designed. Verified live via
   getComputedStyle at both 375px and 1280px widths, and checked
   multi-button .btn-group rows (AssetTag Report Filters, Configuration
   Users mobile cards) at 375px for crowding/overflow after the bump. */
@media (max-width: 767.98px) {
    .pulse-icon-btn {
        width: 44px !important;
        height: 44px !important;
    }
}

/* ------------------------------------------------------------
   Flatpickr theming, app-wide (2026-07-20)

   Founder directive: replace every bootstrap-timepicker instance in the
   app with Flatpickr, "make sure to apply our custom css style to its
   UI". Flatpickr's popup (.flatpickr-calendar) is a floating panel
   appended near its input, the same shape of problem the .bs-container
   .dropdown-menu fix above solves for bootstrap-select -- so it gets the
   same glass-popup treatment (translucent background, backdrop blur,
   glass border/radius) for visual consistency between the two floating
   controls. Placed here (global, not any one screen's CSS) since
   Flatpickr is loaded app-wide via _Layout.cshtml and used across many
   unrelated clusters (AssetTag, TimeClock, Notifications, ReportFilters).

   Every usage site in this app initializes Flatpickr as a time-only
   picker (enableTime:true, noCalendar:true) -- see the shared init
   helper in site.js -- so .flatpickr-time and its children are the
   primary target. The full calendar selectors (.flatpickr-months,
   .flatpickr-day, etc.) are themed too even though nothing currently
   uses date mode, so a future date-picker adoption doesn't land
   unstyled against the default light-grey Flatpickr skin. */
.flatpickr-calendar {
    background: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
    -webkit-backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    backdrop-filter: blur(var(--pulse-theme-glass-blur, 20px));
    border: 1px solid var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
    border-radius: var(--pulse-theme-radius-sm, 10px);
    box-shadow: var(--pulse-theme-glass-shadow, 0 8px 32px rgba(31, 38, 135, 0.10));
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
    border-bottom-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
}
.flatpickr-calendar.arrowBottom:before,
.flatpickr-calendar.arrowBottom:after {
    border-top-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
}
.flatpickr-calendar.arrowTop:after {
    border-bottom-color: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
}
.flatpickr-calendar.arrowBottom:after {
    border-top-color: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
}

/* Founder-reported 2026-08-04, live (Timesheets date fields): bootstrap-
   datepicker.css hardcodes its connecting-arrow triangle to #999/#fff
   (bootstrap-datepicker.css lines 30-48, 67-76), which read fine against
   the plain white box it ships with by default, but showed as a stray
   solid-white wedge once the widening of .dropdown-menu above themed the
   rest of the popup (.datepicker-dropdown reuses that literal class for
   its own box chrome - confirmed live via getComputedStyle before this
   fix). Same shape of problem as Flatpickr's arrow above, same fix:
   override just the border colors the vendor CSS sets, covering both
   arrow orientations (default = arrow at top, popup opens below the
   input; .datepicker-orient-top = arrow at bottom, popup opens above). */
.datepicker-dropdown:before {
    border-bottom-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
}
.datepicker-dropdown:after {
    border-bottom-color: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
}
.datepicker-dropdown.datepicker-orient-top:before {
    border-top-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
}
.datepicker-dropdown.datepicker-orient-top:after {
    border-top-color: var(--pulse-theme-glass-bg, rgba(255,255,255,0.92));
}

/* Month header / navigation (unused today -- noCalendar:true everywhere --
   themed for forward-compatibility, see header comment above) */
.flatpickr-months .flatpickr-month,
.flatpickr-current-month {
    color: var(--pulse-theme-text, #1e293b);
    fill: var(--pulse-theme-text, #1e293b);
}
.flatpickr-current-month input.cur-year {
    color: var(--pulse-theme-text, #1e293b);
}
/* Founder-reported 2026-08-07 (Activity Dashboard's date picker, the first
   real calendar-mode Flatpickr with the month dropdown actually exercised --
   see "first real full-calendar usage" comments below): the month <select>
   itself already inherits themed color (.flatpickr-current-month above), but
   its open OS-drawn <option> list has no page content behind it to blend
   against -- flatpickr's own CSS sets background-color:transparent on
   .flatpickr-monthDropdown-month, which collapses to flat system white/black
   text with no styling applied at all. Same root cause and same fix as
   "#Content .dt-length select option" and ".modal .form-select option"
   above/elsewhere in this file -- needs an explicit opaque themed background,
   not just a transparent one. */
.flatpickr-current-month .flatpickr-monthDropdown-months option,
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
    background: var(--pulse-theme-page-bg-a, #fff);
    color: var(--pulse-theme-text, #1e293b);
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
    fill: var(--pulse-theme-text-muted, #64748b);
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
    fill: var(--pulse-theme-primary, #2563eb);
}
/* Flatpickr's own rule is "span.flatpickr-weekday" (1 type + 1 class =
   specificity 0,1,1) -- a bare ".flatpickr-weekday" (0,1,0) loses that
   fight even though this file loads after flatpickr.min.css, same bug
   class already fixed for .flatpickr-am-pm just below (see that rule's
   comment). Founder-reported 2026-07-21: weekday headers stayed black
   (flatpickr's hardcoded rgba(0,0,0,.54)) on every theme until this. */
span.flatpickr-weekday {
    color: var(--pulse-theme-text-muted, #64748b);
}
.flatpickr-day {
    color: var(--pulse-theme-text, #1e293b);
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
    color: var(--pulse-theme-text-muted, #64748b);
    opacity: 0.4;
}
/* Founder-reported 2026-08-05 (Timesheet Flatpickr -- the first real
   full-calendar usage in the app; every prior Flatpickr site was time-only,
   see this section's header comment): prevMonthDay/nextMonthDay never got
   themed here because nothing exercised date mode when this section was
   written. Flatpickr's own default is a hardcoded rgba(57,57,57,0.3), tuned
   to fade against a white calendar body -- composited over this app's dark
   glass calendar background it reads as almost invisible (confirmed via
   getComputedStyle live: near-black text on a near-black background).
   bootstrap-datepicker's equivalent (td.old/td.new) uses a plain solid
   mid-grey #777777 for the same "de-emphasized but still clickable" idea;
   same principle here via the theme-aware muted token instead of a hardcoded
   hex. Kept less dim than .flatpickr-disabled above since these days are
   still selectable, not actually disabled. */
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
    color: var(--pulse-theme-text-muted, #64748b);
    opacity: 0.55;
}
.flatpickr-day:hover {
    background: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
    border-color: transparent;
}
.flatpickr-day.today {
    border-color: var(--pulse-theme-primary, #2563eb);
}
.flatpickr-day.selected,
.flatpickr-day.selected:hover {
    background: var(--pulse-theme-primary, #2563eb);
    border-color: var(--pulse-theme-primary, #2563eb);
    color: #fff;
}
.flatpickr-innerContainer,
.flatpickr-rContainer,
.flatpickr-days {
    border-color: var(--pulse-theme-header-border, rgba(203, 213, 225, 0.40));
}

/* Time picker -- the part every current usage site actually renders */
.flatpickr-time {
    border-top: 1px solid var(--pulse-theme-divider-color, rgba(203, 213, 225, 0.70));
}
.flatpickr-time .numInputWrapper,
.flatpickr-time input {
    color: var(--pulse-theme-text, #1e293b);
}
.flatpickr-time input.flatpickr-hour,
.flatpickr-time input.flatpickr-minute {
    background: var(--pulse-theme-input-bg, rgba(255,255,255,0.85));
    border-radius: var(--pulse-theme-radius-sm, 10px);
}
.flatpickr-time input.flatpickr-hour:hover,
.flatpickr-time input.flatpickr-minute:hover,
.flatpickr-time .numInputWrapper:hover {
    background: var(--pulse-theme-input-bg-focus, rgba(255,255,255,0.95));
}
.flatpickr-time input.flatpickr-hour:focus,
.flatpickr-time input.flatpickr-minute:focus {
    background: var(--pulse-theme-input-bg-focus, rgba(255,255,255,0.95));
    box-shadow: 0 0 0 2px var(--pulse-theme-input-glow-2, rgba(37,99,235,0.18));
    outline: none;
}
.flatpickr-time .numInputWrapper span.arrowUp:after {
    border-bottom-color: var(--pulse-theme-primary, #2563eb);
}
.flatpickr-time .numInputWrapper span.arrowDown:after {
    border-top-color: var(--pulse-theme-primary, #2563eb);
}
.flatpickr-time .numInputWrapper span:hover {
    background: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
}
.flatpickr-time .flatpickr-time-separator {
    color: var(--pulse-theme-text-muted, #64748b);
}
/* Matches flatpickr's own ".flatpickr-time .flatpickr-am-pm" (2 classes) --
   a bare ".flatpickr-am-pm" (1 class) loses that specificity fight even
   though this file loads after flatpickr.min.css (see _Layout.cshtml). */
.flatpickr-time .flatpickr-am-pm {
    color: var(--pulse-theme-primary, #2563eb);
    border-radius: var(--pulse-theme-radius-sm, 10px);
}
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time .flatpickr-am-pm:focus {
    background: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
    color: var(--pulse-theme-primary, #2563eb);
}

/* Flatpickr time-field width fix -- founder feedback 2026-07-20: "The time
   field needs to be wider to hold the full time." Root cause is shared by
   every Flatpickr time input in the app (CheckpointFlow/CheckpointReaderList
   #SendTime, Notifications/Edit #ScheduleTime, TimeClock/Timesheet
   #StartTime/#EndTime, and the JS-built #Value_{id}/#ValueTime_{id} and punch
   -clock #StartTime/#EndTime fields): Bootstrap's .input-group gives its
   .form-control child "flex: 1 1 auto; width: 1%; min-width: 0" so the field
   can shrink to fill whatever column it happens to sit in, with zero floor.
   Several of those columns (col-sm-3 in Notifications, and CheckpointFlow/
   CheckpointReaderList's col-sm-5 which isn't even wrapped in a .row, so its
   41.667% never resolves against a definite width and collapses further via
   shrink-to-fit) end up narrower than an "10:00 AM"/"2:45 PM" value needs,
   clipping the text. Rather than hand-tune N column widths across 6 call
   sites (4 views + 2 JS builders), give the shared Flatpickr time input
   itself a floor wide enough for an 8-9 character "h:mm AM/PM" value plus
   its own padding -- it now wins the width fight against an undersized
   column instead of losing it.

   Selector specificity note: a bare ".flatpickr-input" (0,1,0) LOSES to
   Bootstrap's own ".input-group > .form-control" rule (0,2,0), which is
   the very rule that sets "min-width: 0" in the first place -- confirmed
   live (computed min-width stayed 0px until the selector below was used).
   ".input-group > .form-control.flatpickr-input" is (0,3,0), so it always
   wins regardless of stylesheet load order. */
.input-group > .form-control.flatpickr-input {
    min-width: 6.5rem;
}

/* ------------------------------------------------------------
   Global fix: .btn-outline-dark theme-awareness (2026-07-20)

   Founder feedback: "The 'Close' button in the modal is too dark... this
   might be a system-wide issue... based on what Custom theme is selected."
   Confirmed live -- Available Checkpoints' "Close" button is near-black
   text/border (rgb(33,37,41)) on the dark glass modal background.

   Root cause: unlike .btn-primary/.btn-success/.btn-danger, Bootstrap's
   .btn-outline-dark is never re-themed anywhere in this app. And unlike
   the --bs-primary/--bs-success/--bs-danger remap at the top of this file
   (which bootstrap5-toggle's CSS reads via var()), remapping a --bs-dark
   custom property would do nothing here: the vendored bootstrap.css
   (wwwroot/lib/bootstrap/dist/css/bootstrap.css:3289) hardcodes
   .btn-outline-dark's --bs-btn-color/--bs-btn-border-color/etc. directly
   to the literal "#212529" at build time -- it never references a
   --bs-dark variable at all. So the fix has to target .btn-outline-dark's
   own per-component custom properties (Bootstrap 5.2+'s intended
   per-button theming API) directly, not a root variable.

   Several files use .btn-outline-dark app-wide (CheckpointFlow's "Close",
   AssetTag/Downloads.cshtml's disabled "download" buttons -- a distinct
   screen from the Activity hub's Downloads tab, which dropped its own
   .btn-outline-dark usage in its 2026-08-06 redesign -- Notifications'
   TriggerTypeID/IntervalBetweenOccuranceUOM bootstrap-select toggles,
   Security's disabled-state "edit"/"delete" fallback) -- all sit on the
   same dark glass surfaces as everything else in this system, so this is
   the same "component styled for a fixed light background, never
   re-themed" bug class as the other global fixes in this file. Fixed
   once here rather than per-view. Only the color-bearing custom
   properties are overridden -- hover/active/disabled STATE LOGIC stays
   exactly as Bootstrap's own rule defines it, just pointed at theme-aware
   values instead of the hardcoded hex.

   Selector specificity note: this file loads BEFORE bootstrap.css in
   _Layout.cshtml (it has to, to establish theme variables first), so a
   bare ".btn-outline-dark" (0,1,0) ties Bootstrap's own same-specificity
   rule and LOSES on load order -- confirmed live (computed color stayed
   rgb(33,37,41) until the selector below was used). ".btn.btn-outline-dark"
   (0,2,0) always wins, matching every real usage (always "btn
   btn-outline-dark" together) and the same fix pattern already used for
   the Flatpickr min-width rule above. */
.btn.btn-outline-dark {
    --bs-btn-color: var(--pulse-theme-text, #1e293b);
    --bs-btn-border-color: var(--pulse-theme-text, #1e293b);
    --bs-btn-hover-color: var(--pulse-theme-text, #1e293b);
    --bs-btn-hover-bg: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
    --bs-btn-hover-border-color: var(--pulse-theme-text, #1e293b);
    --bs-btn-active-color: var(--pulse-theme-text, #1e293b);
    --bs-btn-active-bg: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
    --bs-btn-active-border-color: var(--pulse-theme-text, #1e293b);
    --bs-btn-disabled-color: var(--pulse-theme-text-muted, #64748b);
    --bs-btn-disabled-border-color: var(--pulse-theme-text-muted, #64748b);
}

/* ------------------------------------------------------------
   Global fix: #documentTabs tab-bar styling drift, consolidated (2026-07-30)

   #documentTabs/#tabOCRResults is the exact same shared markup (identical
   ids/classes, copy-pasted) across OCRResult.cshtml and all 3 Template
   editor views (TemplateImage/TemplateAI/TemplateHTML.cshtml) -- but it was
   never a truly shared component, since each view's own CSS file styled it
   independently and the two diverged: pulse-doc-results.css's
   "#tabOCRResults.nav-tabs" gave OCRResult.cshtml a gradient-fill active
   tab (linear-gradient primary->purple, white text), while
   pulse-doc-editors.css's "#documentTabs .nav-tabs" gave the 3 Template
   editors a glass-card active tab (translucent background, bordered,
   colored text) -- same component, two different looks depending on which
   view happened to load it. Founder: "Fix the tab-bar styling drift too."

   Standardized on the glass-card treatment, not the gradient one: it's
   already the majority pattern independently converged on by 3 separate
   modules (Template editors here, plus Import's #myTab.nav-tabs and
   Reports' own wizard tabs use the identical --pulse-theme-glass-bg /
   --pulse-theme-header-border / --pulse-theme-primary recipe) -- the
   gradient-fill version was the one true outlier, used only by
   OCRResult.cshtml. Consolidated into ONE rule here (global, loaded before
   any page-specific CSS per this file's own load-order note above) instead
   of leaving two copies that can silently diverge again -- the diverged
   page-specific versions in pulse-doc-results.css/pulse-doc-editors.css
   were deleted outright rather than left as dead weight, since at matching
   specificity a page-specific rule loading AFTER this file would silently
   win and re-introduce the exact same drift this fix is closing.

   #documentTabs.d-flex mobile fix, same global rule (caught during a
   pre-next-screen validation pass, 375px viewport): Bootstrap's own
   .nav-tabs sets flex-wrap:wrap, so at narrow widths the 4 tab buttons
   wrap onto 2 lines -- but #documentTabs itself (a plain .d-flex, no wrap
   of its own) forced the sibling .btn-group to stretch to match that
   taller height instead of staying compact, since flex children default
   to align-items:stretch. Confirmed live on OCRResult.cshtml: #documentTabs
   measured 123px tall with the button group stretched the same 123px.
   Since #documentTabs is this same shared component, this also silently
   affects the 3 Template editor views' identical toolbar -- fixed once
   here rather than per-view, same reasoning as the drift fix above. */
#documentTabs.d-flex {
    flex-wrap: wrap;
    align-items: center;
    row-gap: 8px;
}
/* Founder-reported 2026-08-10: "screens with tabs are missing the line just
   under the tab" -- the 2026-08-03 change above switched this divider from
   --pulse-theme-header-border to --pulse-theme-glass-border to close a seam
   against the panel's own edge (reasoning still valid, see above), but
   glass-border is designed for a glass CARD'S OWN EDGE against whatever's
   behind the card -- deliberately subtle by design -- not for a divider
   line separating two regions *inside* the same card. Used for the latter
   job it's nearly invisible: confirmed live via getComputedStyle across
   every theme in pulse-theme-variables.css that glass-border's alpha
   (~0.14-0.22 in most presets, e.g. 0.20 on Neon Obsidian) sits well under
   a perceptible contrast threshold against the glass panel's own
   near-equally-subtle background (glass-bg). --pulse-theme-divider-color is
   the token actually built for this: every preset in that file gives it a
   distinctly higher alpha than glass-border (Neon Obsidian: 0.25 vs 0.20;
   the default light theme: 0.70 vs a near-white glass-border) specifically
   because it's meant to separate content within a surface, not edge a
   surface against its background -- already the established choice for
   this exact job elsewhere (e.g. the Timesheets card actions divider). The
   active tab's own border (below, unchanged) still uses glass-border
   correctly -- that's the tab visually fusing with the panel's edge, a
   different, still-valid use of that token. */
#documentTabs .nav-tabs,
#tabOCRResults.nav-tabs {
    border-bottom: 1px solid var(--pulse-theme-divider-color, rgba(203,213,225,0.7));
    gap: 4px;
}
#documentTabs .nav-tabs .nav-link,
#tabOCRResults.nav-tabs .nav-link {
    border: 1px solid transparent;
    border-radius: var(--pulse-theme-radius-sm, 10px) var(--pulse-theme-radius-sm, 10px) 0 0;
    color: var(--pulse-theme-text-muted, #64748b);
    font-weight: 500;
    padding: 8px 16px;
    transition: background .15s ease, color .15s ease;
}
#documentTabs .nav-tabs .nav-link:hover:not(.active),
#tabOCRResults.nav-tabs .nav-link:hover:not(.active) {
    background: var(--pulse-theme-btn-hover-bg, rgba(37,99,235,0.09));
    color: var(--pulse-theme-text, #1e293b);
}
#documentTabs .nav-tabs .nav-link.active,
#tabOCRResults.nav-tabs .nav-link.active {
    background: var(--pulse-theme-glass-bg, rgba(255,255,255,0.68));
    border-color: var(--pulse-theme-glass-border, rgba(255,255,255,0.85));
    border-bottom-color: transparent;
    color: var(--pulse-theme-primary, #2563eb);
    font-weight: 700;
}
