/* ==========================================================================================
   spike-app.css — project components Spike has no equivalent for (spec "Kept as-is, re-skinned
   only"). Every color is a Spike token; every direction-sensitive property is logical (RTL
   groundwork). Loaded after styles.css. Nothing here restyles a stock Spike component.
   Comment prose never contains a star followed by a slash (CLAUDE.md: it truncates the sheet).
   ========================================================================================== */

/* ---- Client feedback: every card-header (dashboard widgets, every DataTable list page's card,
   Employee/Leave/Sick Leave tab section cards, Roles/Profile, ...) carries styles.css's own faint
   `--bs-card-cap-bg` tint + a border-bottom — the reference dashboard (package/starerkit
   .../index2.html) never uses `.card-header` at all; every one of its widgets is a plain
   `<h4 class="card-title mb-0">` sitting directly in `.card-body`, no separate capped region. Rather
   than rewrite the ~50 views that already structure a title inside `.card-header` (a real but far
   riskier change for a same-day pass), this neutralizes the ONE thing that made it read as a
   distinct "header" — its background and border — and removes its own bottom padding so it flows
   straight into the body below instead of stacking two paddings into an oversized gap. Every
   existing card-header's title/actions markup is untouched; only its chrome changes. ---- */
.card-header {
  background-color: transparent;
  border-bottom: 0;
  padding-block-end: 0;
}

/* ---- Known sharp edge: styles.css's LIGHT-mode --bs-*-text-emphasis tokens are literal,
   uncompiled Sass (e.g. `shade-color(#fb977d, 60%)` as a text string, not a color) — confirmed via
   getComputedStyle, every one of primary/secondary/success/info/warning/danger. The dark-mode block
   compiles these correctly (real hex); only light doesn't. `color: var(--bs-danger-text-emphasis)`
   is therefore invalid at computed-value time in light mode and silently falls back to the
   INHERITED color instead — confirmed live on a Select2 role chip: expected a shaded danger red,
   got plain inherited gray, no console error, nothing to suggest the variable was ever consulted.
   Recomputed with Bootstrap's own shade-color(60%) formula (mix 60% black into the real base hex
   from this file's :root block) so every current and future use of these six tokens is correct
   without needing its own per-call workaround. ---- */
:root, [data-bs-theme='light'] {
  --bs-primary-text-emphasis: #003558;
  --bs-secondary-text-emphasis: #1c515e;
  --bs-success-text-emphasis: #1e5338;
  --bs-info-text-emphasis: #093248;
  --bs-warning-text-emphasis: #634d2f;
  --bs-danger-text-emphasis: #643c32;
}

/* ---- Client feedback (two rounds): --bs-secondary-color/--bs-tertiary-color (.text-muted,
   .form-text, breadcrumb dividers/inactive items, stat-card captions, dropdown header text,
   disabled controls — anywhere "muted" text appears) rendered too faint to read comfortably.
   styles.css defines both as the body-text color at reduced alpha (secondary .75, tertiary .5),
   which reads washed out regardless of what's behind it. Round 1 made both fully opaque (same
   shade as body text); round 2 asked to go further — as dark/light as --bs-heading-color itself,
   not just matching body text — so these are now solid shades a couple of steps off heading-color
   rather than the original body-color hue, secondary closer to it than tertiary. ---- */
:root, [data-bs-theme='light'] {
  /* --bs-heading-color: #111c2d */
  --bs-body-color: #233042;
  --bs-secondary-color: #233042;
  --bs-tertiary-color: #3c4858;
}
[data-bs-theme='dark'] {
  /* --bs-heading-color: #EAEFF4 */
  --bs-body-color: #d6dee6;
  --bs-secondary-color: #d6dee6;
  --bs-tertiary-color: #bec8d4;
}

/* ---- Footer pinned to the viewport bottom on a short page, in normal flow after content on a
   long one. .body-wrapper already carries min-height:100vh (styles.css) but no flex — footer.php
   just sat wherever content ended, floating mid-page on any screen shorter than the viewport. ---- */
.body-wrapper { display: flex; flex-direction: column; }
.body-wrapper > .container-fluid { flex: 1 0 auto; }

/* ---- Every badge/pill gets a border in its own text color (global, client request) — none had
   one; a bg-{color}-subtle + text-{color}-emphasis badge (this app's own convention) always has a
   real color in `color`, so `currentColor` never needs a per-variant rule. ---- */
.badge { border: 1px solid currentColor; }

/* ---- Sidebar pending-count badge (partials/sidebar.php's `bg-danger` pill) silently loses its own
   number on hover/active — confirmed live. It carries `.hide-menu` purely so the mini-sidebar
   collapse can hide it like every other label span, but styles.css's own per-item accent mechanism
   (`sidebar.php`'s rotating `$tint` -> `<tint>-hover-bg`) ALSO recolors every `.hide-menu` under a
   hovered/active row to that tint via `.danger-hover-bg:hover .hide-menu{color:var(--bs-danger)}`
   (one such rule per tint). Whenever a row's own rotating tint happens to be "danger" — the same hue
   as this badge's own solid background — the override paints the counter's text the EXACT color of
   its own background, not merely low-contrast: the number disappears outright. Pinned to a fixed,
   state-independent color/border here (not `currentColor`, precisely because currentColor is what
   the vendor rule was hijacking) so the badge reads the same regardless of hover, active, or which
   tint its own row landed on. #sidebarnav's id beats the vendor rule's `.tint-hover-bg:hover`/
   `.active` selectors on specificity, not source order, so this can't be re-shadowed by an
   unrelated future stylesheet edit. ---- */
#sidebarnav .sidebar-link .badge.hide-menu {
    color: #fff !important;
    border-color: transparent;
}

/* ---- Toast container: styles.css bundles a second, unrelated toast-plugin's CSS under the exact
   same .toast-container class name, whose `width:100% !important` beats Bootstrap's own
   `.toast-container{width:max-content}` — the container silently spanned the full viewport width,
   so a toast (a plain block-level child, no inner alignment of its own) rendered flush against the
   LEFT edge instead of hugging bottom-right per its own bottom-0/end-0 utility classes (confirmed
   live). #id beats that vendor rule's plain class even against its !important. ---- */
#toast-container { width: max-content !important; }

/* ---- Topbar (partials/navbar.php) must never split into two stacked rows/boxes on a narrow phone.
   styles.css's base `.navbar{display:flex;flex-wrap:wrap}` only gets `flex-wrap:nowrap` back from
   `.navbar-expand-lg` at >=992px (`@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap}}`) —
   below that, the two direct `<ul class="navbar-nav">` children (the sidebar-toggle button and the
   icon row) are free to wrap onto separate lines the moment their combined width (plus the hidden-
   but-still-flex-tracked desktop search box) doesn't fit one row, which reads as two disconnected
   floating cards rather than one cohesive topbar (live feedback, screenshot in hand). ---- */
.navbar { flex-wrap: nowrap; }

/* `nowrap` above trades that split-row bug for the opposite failure below ~400px: 6 icon buttons at
   40x40 (styles.css's `.nav-icon-hover-bg{width:40px;height:40px}`) plus `margin:0 6px` on every
   `.navbar-nav .nav-item .nav-link` (12px per pair, confirmed live via getComputedStyle) plus the
   profile link's own `ms-6` (12px, `!important` like every Bootstrap spacing utility) add up to
   ~309px of icon row alone — more than fits in a real 360px device viewport once the sidebar-toggle
   button and the topbar's own side padding are subtracted, so instead of wrapping the excess simply
   overflowed the viewport (live feedback, DevTools device toolbar at 360x851 DPR2: scrollWidth 385
   vs clientWidth 360 — a real 25px horizontal overflow, not merely a "should still fit" guess).
   Trimmed every fixed measurement that goes into that total specifically below 576px — an earlier
   claim here that plain window resizing "confirmed" no overflow down to 320px was wrong: a desktop
   resize doesn't reproduce this the same way real mobile viewport+UA emulation does, which is what
   actually caught it. */
@media (max-width: 575.98px) {
  /* `.topbar` prefix is load-bearing, not decorative — styles.css's own rule is
     `.topbar .navbar .navbar-nav .nav-item .nav-link{margin:0 6px}` (5 classes); dropping the
     `.topbar` here left this at 4 classes, so the vendor rule's higher specificity won regardless of
     source order and this had NO effect at all (confirmed live: still 6px, unchanged) until matched
     back to 5. */
  .topbar .navbar .navbar-nav .nav-item .nav-link { margin-inline: 2px; }
  /* .nav-icon-hover-bg ALSO carries its own `margin:0 3px` (separate from the .nav-link margin
     just above — confirmed live, still a 6px LI-to-LI gap after only trimming the .nav-link one)
     — zeroed here so the .nav-link rule above is the only thing setting the gap, not both stacked. */
  .navbar .nav-icon-hover-bg { width: 34px; height: 34px; margin-inline: 0; }
  .navbar .navbar-nav .nav-item.dropdown:last-child .nav-link.ms-6 { margin-inline-start: 4px !important; }
  .navbar .user-profile img { width: 32px; height: 32px; }
}

/* ---- Avatar (Spike ships none; Sneat's .avatar/.avatar-initial family maps onto this) ---- */
.hr-avatar {
  --hr-avatar-size: 40px;
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  inline-size: var(--hr-avatar-size); block-size: var(--hr-avatar-size);
  font-weight: 600; font-size: calc(var(--hr-avatar-size) * 0.4); line-height: 1; overflow: hidden;
}
.hr-avatar img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.hr-avatar-xs { --hr-avatar-size: 24px; }
.hr-avatar-sm { --hr-avatar-size: 32px; }
.hr-avatar-md { --hr-avatar-size: 48px; }
.hr-avatar-lg { --hr-avatar-size: 64px; }
.hr-avatar-xl { --hr-avatar-size: 80px; }
.hr-avatar iconify-icon { font-size: calc(var(--hr-avatar-size) * 0.5); }

/* ---- Hub / hidden-sidebar pages (home/index.php sets $hideSidebar) ---- */
.hr-no-sidebar .page-wrapper { margin-inline-start: 0 !important; }

/* ---- Company logo in the sidebar brand row ---- */

.hr-brand-logo { max-block-size: 50px; max-inline-size: 180px; object-fit: contain; }

.login-page .hr-brand-logo { max-block-size: 65px }

[data-sidebartype="mini-sidebar"]  .hr-brand-logo{max-block-size: 35px; height: auto;}

.text-dark.hr-logo-text{ color: var(--bs-heading-color) !important; }

/* ---- Sidebar profile card's mb-3 has no visible effect on its own (confirmed live) — styles.css
   sizes the scrollable nav with a hardcoded `.left-sidebar .scroll-sidebar{height:calc(100% - 200px)}`,
   a magic-number budget for "everything else" (brand row + profile card) that already consumes
   ~199px of that 200px with the vendor's OWN original spacing (no bottom margin). Adding mb-3 to the
   profile card pushes the total past that fixed 200px, so the extra margin overflows past the
   sidebar's own fixed-height box instead of creating a visible gap — getBoundingClientRect()
   confirmed the margin computes correctly (16px) while the card's rendered bottom edge still landed
   flush with the viewport. Making the sidebar a real flex column — nav flex:1 (min-block-size:0 so
   its own overflow-y:auto still works correctly as a flex child), everything else auto-sized — lets
   nav claim exactly "100% minus its siblings' real rendered height, margins included" instead of a
   hardcoded guess, so the profile card's mb-3 (or any future change to its own height) always shows
   correctly with no magic number to keep in sync by hand. ---- */
.left-sidebar { display: flex; flex-direction: column; }
/* `block-size: 0` (not `auto`) is load-bearing, and the reason is SimpleBar, not layout: this nav is
   a SimpleBar host (`data-simplebar`, partials/sidebar.php). SimpleBar sizes its generated
   `.simplebar-content-wrapper` from the HOST's own declared height, and an `auto` height puts it in
   "height auto" mode — it then writes `height: auto` onto that wrapper, which therefore never clips,
   so the host's own `overflow: auto` (styles.css) starts scrolling the same content the wrapper is
   already scrolling. Two scroll containers over one list renders a second scrollbar, leaves a large
   blank area under the last item once scrolled down, and strands an expanded submenu (Lookups, the
   last item) outside the reachable scroll range — read live as "the dropdown doesn't open" even
   though the toggle fires correctly and its `<ul>` measures 510px. `block-size: 0` + `flex-grow` is
   the standard flexbox idiom for "definite basis, grow to fill": the box still resolves to exactly
   the same height, but it is now a DEFINITE one, so SimpleBar sizes and clips its wrapper properly.
   Latent until flat navigation mode (client module editions, 2026-09-16) first made the sidebar
   taller than its own box — a single module's slice never overflowed.
   Do NOT "fix" this by putting `overflow: hidden` on the host: SimpleBar mirrors the host's overflow
   onto its wrapper, which silently removes user scrolling (programmatic scrollTop keeps working, so
   it measures as fixed while being broken). ---- */
.left-sidebar .scroll-sidebar { block-size: 0; flex: 1 1 auto; min-block-size: 0; }

/* ---- Clickable counter cards above list DataTables (ported from ui-tweaks.css) ---- */
.stat-card { border: 1px solid transparent; transition: border-color .15s ease; }
.stat-card[style*="cursor:pointer"]:hover { border-color: var(--bs-primary-border-subtle); }
.stat-card.active { border-color: var(--bs-primary); }
.stat-card .card-body > .hr-avatar { flex-shrink: 0; }
.row > div:has(> .stat-card) { min-inline-size: 0; }
.hr-stat-label { display: block; margin-block-end: .25rem; color: var(--bs-secondary-color); }
/* 2-line reserve, only on rows app.js's initStatCardRowAlignment() flags (CLAUDE.md
   "stat-card row's icon box and number can desync"). */
.hr-stat-row-2line .hr-stat-label {
  display: -webkit-box !important; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-block-size: 2.75em;
}
/* Stacked list-page variant (2026-09-13 live feedback: icon -> number -> title, card background
   left plain, only the icon tile stays colorful). Scoped to .hr-stat-stack, NOT .stat-card itself,
   because partials/dashboard/widget-kpi.php's Dashboard widget card also carries `.stat-card` and
   `.hr-stat-label` but keeps its own side-by-side, label-above-value layout — a bare `.stat-card
   .card-body` rule here would silently ripple into that unrelated component. */
/* Left-aligned, compact ("medium") sizing — 2026-09-13 second pass, live feedback: the first pass's
   centered layout with the vendor's full 30px card-body padding-block read as oversized/unbalanced
   next to a plain-card reference; padding-block (not the `padding` shorthand) trims only top/bottom
   so the vendor's own left/right padding is untouched. */
/* Third pass, same day: `.h-100` + the row's default `align-items:stretch` (Bootstrap) means a card
   is routinely taller than its own content whenever a sibling card in the row needs more height (a
   2-line label, a longer number) — with plain block flow, ALL of that extra space collects below
   the last child, reading as "way more padding on the bottom than the top" even though
   padding-block above is genuinely equal on both sides (confirmed live via getComputedStyle: 16px
   top AND bottom, ~40px of dead space below the label was the real culprit, not uneven padding).
   Fixed by centering the content vertically instead: flex-column + justify-content:center splits any
   such leftover space evenly top/bottom. `align-items` is intentionally left at its `stretch`
   default (not `center`) — `.hr-avatar` has an explicit inline-size so stretch never touches its
   width, but the number/label are `auto`-width and MUST stay stretched to the card's full width, or
   initStatCardRowAlignment()'s `.hr-stat-label` scrollHeight measurement (needs the label at its
   real rendered width) breaks. */
.hr-stat-stack { display: flex; flex-direction: column; justify-content: center; text-align: left; padding-block: 1rem; }
.hr-stat-stack > .hr-avatar { margin-block-end: .5rem; }
.hr-stat-value { font-size: 1.5rem; font-weight: 700; line-height: 1.2; margin-block-end: .15rem; }
.hr-stat-stack .hr-stat-label { margin-block-end: 0; }
@media (max-width: 575.98px) {
  .stat-card .card-body { padding: 1rem; }
  .stat-card .hr-avatar { --hr-avatar-size: 36px; }
  .hr-stat-value { font-size: 1.25rem; }
}

/* ---- Every button: icon + text baseline-align inconsistently otherwise (an <iconify-icon>/<i>
   glyph sized via fs-* sits on its own line-box, not centered against the button's text) ---- */
.btn { display: inline-flex; align-items: center; justify-content: center; }

/* ---- Topbar icons (search, home, theme, shortcuts, bell, mobile search, sidebar toggle) have no
   explicit color of their own in styles.css — only a :hover rule exists, so the resting state just
   inherits body-color, which read as too faint/incidental for a row of primary navigation actions
   (client feedback). Heading-color makes every one of them read as prominent as the page's own
   headings; the existing :hover rules (higher specificity) still take over to var(--bs-primary). ---- */
.topbar .nav-link { color: var(--bs-heading-color); }

/* ---- Name links inside list rows (app.js hrNameCell) ---- */
.hr-name-link { color: inherit; text-decoration: none; }
.hr-name-link:hover { color: var(--bs-primary); }

/* ---- Tab strip on every layout => 'tabs' form. A form with enough tabs (Employee: 9) never fits
   one line at any real card width — a forced single line + horizontal scrollbar (an earlier
   approach here) just hid tabs off-screen with no visual cue. Bootstrap's own .nav-tabs already
   wraps by default; flex-wrap only needs to exist to stop that default from being fought
   elsewhere — it stays as the fallback for a genuinely narrow viewport.
   Icons before each label were tried and dropped again same session (live feedback: "too busy") —
   plain text only, from here on.
   Live feedback on the plain text itself: the INACTIVE tab looked "awkward and too small" next to
   the active one's bold white-on-blue pill. Root cause, confirmed in styles.css: `.nav-tabs .nav-
   link{color:#7c8fac}` is a literal hardcoded gray — not `var(--bs-nav-link-color)` (which resolves
   to a real, dark, theme-aware `#111c2d`), and not adjusted for dark mode either, so it reads
   washed-out in both themes. Swapped for `var(--bs-heading-color)` (this app's own high-contrast
   text token) at a medium weight, so an inactive tab is clearly legible text — a real button, not a
   ghost — while the active tab's own bold/white/pill treatment still reads as the obvious "current"
   one. */
.hr-form-tabs { flex-wrap: wrap; row-gap: .5rem; }
.hr-form-tabs .nav-link {
  padding-block: .55rem;
  padding-inline: .75rem;
  font-size: .875rem;
  font-weight: 600;
  color: var(--bs-heading-color);
}
/* :not(.active) is load-bearing — without it, hovering the ALREADY-active tab (easy to do; it's
   sitting right under the pointer after the click that activated it) let this rule's own class-level
   specificity beat .nav-tabs .nav-link.active's white text color, painting primary-blue text on that
   same tab's primary-blue background: confirmed live, the label went fully invisible. */
.hr-form-tabs .nav-link:hover:not(.active) { color: var(--bs-primary); }

/* Below phone width, flex-wrap alone (the fallback above) packs tabs into an uneven grid — one per
   row here, two there, however each label's own width happens to divide the remaining space — which
   reads as broken, not "responsive" (live feedback, screenshot in hand). Stacked into one clean
   full-width column instead: every tab becomes its own row, active one still the same blue pill —
   effectively a vertical menu, the standard mobile substitute for a horizontal tab strip, with zero
   JS changes since Bootstrap's tab behavior (data-bs-toggle="tab") only cares about the click, never
   the strip's layout direction. */
@media (max-width: 575.98px) {
  .hr-form-tabs {
    flex-direction: column;
    flex-wrap: nowrap;
    row-gap: .35rem;
  }
  .hr-form-tabs .nav-item { width: 100%; }
  .hr-form-tabs .nav-link {
    width: 100%;
    text-align: start;
    border: 1px solid transparent;
    border-radius: var(--bs-border-radius);
  }
}

/* ---- Mobile tab-strip trigger (partials/form/tabs.php + form-tabs.js) — even the tidy vertical
   stack directly above still occupies its own full-height block permanently ("lingers too much" —
   live feedback), pushing every form field down by however many tabs the schema happens to declare
   before a phone user ever reaches one. Collapsed behind this one-line button by default; opens the
   same stacked list from the rule above over the fields (not push them further down) while open, so
   the fields' own position on screen never jumps around underneath an already-open list. ---- */
.hr-tabs-trigger {
  display: none;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  padding: .65rem .9rem;
  margin-block-end: .75rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-body-bg);
  color: var(--bs-heading-color);
  font-size: .9375rem;
  font-weight: 600;
}
.hr-tabs-trigger iconify-icon { transition: transform .15s ease-in-out; color: var(--bs-body-color); }
.hr-tabs-trigger[aria-expanded="true"] iconify-icon { transform: rotate(180deg); }

@media (max-width: 575.98px) {
  .hr-tabs-trigger { display: flex; }
  /* Positioning context for the open panel below — scoped to just this wrapper (not .card-body
     itself) so it can't change how any OTHER absolutely-positioned element on the page resolves. */
  .hr-tabs-wrap { position: relative; }
  .hr-form-tabs {
    display: none;
    position: absolute;
    inset-inline: 0;
    inset-block-start: 100%;
    z-index: 5;
    margin-block: 0;
    padding: .5rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background-color: var(--bs-body-bg);
    box-shadow: 0 8px 20px rgba(0, 0, 0, .12);
  }
  .hr-form-tabs.hr-tabs-open { display: flex; }
}

/* ---- Money tag after a total (app.js hrCurrencyTag) ---- */
.hr-currency { font-size: .7em; font-weight: 500; margin-inline-start: .25rem; color: var(--bs-secondary-color); }

/* ---- Reports module ledger/tfoot chrome (ported from ui-tweaks.css) ---- */
/* Grand-totals tfoot row (Employee Settlements / LMRA WPS / Over Paid) — a touch larger/bolder
   than the table body so the totals read as headline figures, not just another row. */
.hr-dt-tfoot-emphasis th { font-size: 1rem; font-weight: 700; }
/* Ticket-policy badge on the Employee Settlements ledger's Ticket Amount column ("yearly x 3") —
   trims the badge to sit comfortably under a money figure instead of a normal button-adjacent size. */
.hr-ticket-badge { font-size: .7rem; font-weight: 500; inline-size: fit-content; margin-inline-start: auto; }
/* Employee Leave Ledger — thin percent-of-entitled meter under the Annual Remaining number
   (reports-leave-ledger.js's remainingCell()). margin-inline-start:auto right-aligns the
   fixed-width bar inside its .text-end cell. */
.hr-ledger-remaining { block-size: 4px; inline-size: 72px; margin-block-start: 4px; margin-inline-start: auto; background-color: var(--bs-border-color); }

/* ---- Alert INSIDE a modal body (lookup-crud.js .hr-modal-alert) ---- */
.hr-modal-alert { margin-block-end: 1rem; }
.hr-modal-alert .btn-close { position: absolute; inset-block-start: 50%; inset-inline-end: 1rem; transform: translateY(-50%); }

/* ---- Stacked password-confirm modal above another modal (app.js hrConfirmPassword) ---- */
.hr-modal-backdrop-stacked { z-index: 1056; }
#hrPasswordConfirmModal { z-index: 1057; }

/* ---- Year-first flatpickr overlay (date_of_birth) ---- */
.hr-fp-year-view { display: grid; grid-template-columns: repeat(4, 1fr); gap: .25rem; max-block-size: 240px; overflow-y: auto; padding: .5rem; }
.hr-fp-year-btn { border: 1px solid var(--bs-border-color); background: var(--bs-body-bg); color: var(--bs-body-color); border-radius: var(--bs-border-radius); padding: .35rem 0; font-size: .875rem; }
.hr-fp-year-btn:hover { background: var(--bs-primary-bg-subtle); color: var(--bs-primary); }

/* ---- Arabic text fields (CLAUDE.md Arabic-field convention) ---- */
.form-control[dir='rtl'] { text-align: end; }

/* ---- Form placeholders ---- */
/* styles.css sets `.form-control::placeholder{color:#111c2d;opacity:1}` (and the -moz- variant, as
   its OWN separate rule) in light mode — near-black, so a placeholder reads as already-typed text
   (confirmed live on the Approve modal's remarks box). No !important needed: same specificity, and
   this file loads after styles.css, so plain source order wins in light mode; dark mode is untouched
   because styles.css's OWN `[data-bs-theme=dark] ::placeholder{color:#7c8fac!important}` is
   `!important` and this rule isn't, so the important rule always wins there regardless of order —
   giving this rule `!important` too would make IT win in dark mode instead by source order (equal
   specificity, both important) and silently break the already-correct dark placeholder; confirmed
   live, don't reintroduce it.
   NOT `var(--bs-secondary-color)`: this file's own "Client feedback" block two sections up
   deliberately redefines `--bs-secondary-color` to equal `--bs-body-color` (full-opacity body-text
   color, by explicit two-round client request — muted text was "too faint to read") — so it is NOT a
   lighter/muted token in this app, unlike stock Bootstrap. Using it here silently reproduced the
   exact same "looks like real typed text" bug with a different hex, confirmed live via
   getComputedStyle. `--bs-gray-600` is the genuinely lighter, theme-invariant token already vouched
   for elsewhere in this file (the scrollbar-thumb rule below) — use that for anything that must read
   as visibly lighter than body text, never `--bs-secondary-color`/`--bs-tertiary-color` in this app.
   These must stay TWO separate rules, never comma-joined: Chrome doesn't recognize
   `::-moz-placeholder` and drops an ENTIRE comma-separated selector list the moment any one compound
   selector in it is invalid/unsupported — confirmed live (a joined version silently zeroed out this
   whole rule, verified via document.styleSheets showing it simply absent from the parsed sheet
   despite being present, byte for byte, in the served CSS text). The vendor's own two adjacent rules
   for the same pair use this exact same split for the same reason — never "simplify" it back into
   one selector list. */
.form-control::placeholder { color: var(--bs-gray-600); opacity: 1; }
.form-control::-moz-placeholder { color: var(--bs-gray-600); opacity: 1; }

/* ---- Global DataTables safety (CLAUDE.md: vendored Responsive never collapses columns) ---- */
.dataTable th, .dataTable td { white-space: nowrap; }
.dataTable .dt-wrap { white-space: normal; }
/* dataTables.bootstrap5.min.css zeroes the FIRST grid column's padding inside .table-responsive
   > .dt-container > .row; below md the two columns stack and it reads as misaligned. */
@media (max-width: 767.98px) {
  div.table-responsive > div.dt-container > div.row > div[class^="col-"]:first-child { padding-inline-start: calc(var(--bs-gutter-x) * .5); }
  .dt-container .dt-length, .dt-container .dt-paging { inline-size: 100%; display: flex; justify-content: center; }
  .dt-container .dt-paging .pagination { flex-wrap: wrap; justify-content: center; }
  /* The shared dom string's inner l+p wrapper (d-flex align-items-center gap-4) has no flex-wrap,
     so the length select and the whole pagination sit on one row and the pagination's own <li>s
     wrap instead, orphaning the last page button (CLAUDE.md DataTables footer sharp edge). Make
     each control own a full row; the two rules above then center them. */
  div.dt-container div.d-flex.align-items-center.gap-4 { flex-wrap: wrap; flex: 1 1 100%; row-gap: .75rem; }
  div.dt-container div.dt-length, div.dt-container div.dt-paging { flex: 0 0 100%; }
}
/* Spike's styles.css themes DataTables 1.x's .dataTables_* wrapper only — these give the 3.x
   wrapper the same tokens in both themes. */
.dt-container .dt-info, .dt-container .dt-length label { color: var(--bs-secondary-color); }
.dt-container .dt-length select { display: inline-block; inline-size: auto; }
.dt-container .page-link { background: var(--bs-body-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }
.dt-container .page-item.active .page-link { background: var(--bs-primary); border-color: var(--bs-primary); color: #fff; }

/* ---- flatpickr: retarget its hardcoded blue to Spike's primary, both themes ---- */
.flatpickr-day.selected, .flatpickr-day.startRange, .flatpickr-day.endRange,
.flatpickr-day.selected:hover, .flatpickr-day.startRange:hover, .flatpickr-day.endRange:hover,
.flatpickr-day.selected:focus, .flatpickr-day.startRange:focus, .flatpickr-day.endRange:focus {
  background: var(--bs-primary); border-color: var(--bs-primary); color: #fff;
}
.flatpickr-day.inRange { background: var(--bs-primary-bg-subtle); border-color: var(--bs-primary-bg-subtle); box-shadow: -5px 0 0 var(--bs-primary-bg-subtle), 5px 0 0 var(--bs-primary-bg-subtle); }
.flatpickr-day.today { border-color: var(--bs-primary); }
[data-bs-theme='dark'] .flatpickr-calendar { background: #111c2d; /* styles.css dark --bs-body-bg */ border-color: var(--bs-border-color); box-shadow: 0 2px 6px rgba(0, 0, 0, .4); color: var(--bs-body-color); }
[data-bs-theme='dark'] .flatpickr-day, [data-bs-theme='dark'] .flatpickr-weekday, [data-bs-theme='dark'] .flatpickr-current-month, [data-bs-theme='dark'] .flatpickr-months .flatpickr-month { color: var(--bs-body-color); }
[data-bs-theme='dark'] .flatpickr-day.flatpickr-disabled, [data-bs-theme='dark'] .flatpickr-day.prevMonthDay, [data-bs-theme='dark'] .flatpickr-day.nextMonthDay { color: var(--bs-tertiary-color); }
[data-bs-theme='dark'] .flatpickr-day:hover { background: #333f55; /* styles.css dark input surface */ border-color: #333f55; }

/* ---- Select2 (default theme, restyled by styles.css) — the two gaps found live on Sneat too.
   styles.css hardcodes .form-control/.form-select's own border to a literal #aebcc3 / .5rem, not
   the --bs-border-color/--bs-border-radius root tokens (those resolve to a much lighter #e6ecf1 /
   4px) — match the literal values so a Select2 field is indistinguishable from a plain input. ---- */
/* styles.css's `body .select2-container{display:inline}` out-specifies Select2's own
   `.select2-container{display:inline-block}` — an inline element's containing block for an
   absolutely-positioned descendant is the union of its (possibly fragmented) inline boxes, not a
   single box, so the dropdown arrow (position:absolute, top:-2px) anchored to a completely wrong
   box and rendered ~28px too high, up near the field's <label> (confirmed live). Restoring
   inline-block — the only thing an absolutely-positioned child actually needs — fixes it. */
body .select2-container { display: inline-block; }
/* styles.css's own rule is `body .select2-container--default .select2-selection--single` (element +
   2 classes) — out-specifies a bare 2-class override with no !important needed, so this one matches
   that same shape rather than fighting it with !important. */
body .select2-container--default .select2-selection--single, body .select2-container--default .select2-selection--multiple { min-block-size: calc(1.5em + .75rem + 2px); border-color: #aebcc3; border-radius: .5rem; }
[data-bs-theme='dark'] body .select2-container--default .select2-selection--single, [data-bs-theme='dark'] body .select2-container--default .select2-selection--multiple { border-color: rgba(189, 200, 240, .2); }
.is-invalid + .select2-container--default .select2-selection { border-color: var(--bs-form-invalid-border-color); }

/* ---- SweetAlert2 must sit above Spike's sidebar (z 99) and sticky topbar (1020) ---- */
.swal2-container { z-index: 1090 !important; }

/* ---- Modern thin scrollbars (ported). `scrollbar-width`/`scrollbar-color` are Firefox-only — they
   silently do nothing in Chrome/Edge/Safari (WebKit/Blink), so this rule alone left every scrollable
   area in those browsers on the OS/browser's own default scrollbar behavior, which on some systems is
   an overlay style that only appears transiently on hover/scroll instead of staying visible (live
   feedback on the Schedule Board's horizontally-scrolling lanes: "it must be showing so user can know
   there is a scrollbar"). The `::-webkit-scrollbar` family is the WebKit/Blink equivalent — styling it
   at all switches the browser from that overlay behavior to a classic, always-reserved, always-drawn
   scrollbar, so this needs its own explicit rule rather than expecting the Firefox properties above to
   somehow cover it.
   `--bs-border-color` (#e6ecf1 in light mode) was the first color tried here — technically renders
   (confirmed live: the scrollable element's offsetHeight/clientWidth gap the thumb reserves is really
   there), but is too pale to actually SEE against a light card, which is indistinguishable from "still
   not showing" to a user looking at it (live feedback, screenshot in hand, after the first pass).
   `--bs-gray-600` is a real medium-dark gray with enough contrast on both a light card and a dark one,
   not tied to the theme so both engines paint the same visible thumb light or dark. ---- */
* { scrollbar-width: thin; scrollbar-color: var(--bs-gray-600) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background-color: transparent; }
*::-webkit-scrollbar-thumb { background-color: var(--bs-gray-600); border-radius: 999px; border: 2px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background-color: var(--bs-heading-color); }

/* ---- Shared DataTable filter toolbar (partials/datatable/filter-toolbar.php, wired by
   hr-datatable-toolbar.js's hrDtToolbar()) — ported from ui-tweaks.css: search box, Filters
   toggle, active-filter chips, Clear button and the collapsible filter grid beneath. The Filters
   button fills solid whenever the panel is open OR a filter is active (hrDtToolbar's refresh()). ---- */
.hr-dt-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.hr-dt-toolbar .hr-dt-search { inline-size: 260px; flex: 0 1 auto; }
.hr-dt-chips { display: inline-flex; flex-wrap: wrap; gap: .35rem; align-items: center; }
.hr-dt-chip {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  padding-block: .28rem;
  padding-inline: .8rem .5rem;
  border-radius: 50rem;
  font-size: .78rem;
  font-weight: 500;
  background-color: rgba(var(--bs-primary-rgb), .1);
  border: 1px solid rgba(var(--bs-primary-rgb), .3);
  color: var(--bs-primary);
}
.hr-dt-chip .btn-close { font-size: .55rem; }

/* Filters toggle pill: Bootstrap's own .btn-outline-primary supplies the rest state; this only
   adds the persistent filled/active state and the count badge's two looks. */
.hr-dt-filter-btn.active {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
  box-shadow: 0 2px 6px rgba(var(--bs-primary-rgb), .45);
}
.hr-dt-filter-btn .hr-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 1.25rem;
  block-size: 1.25rem;
  padding: 0 .3rem;
  border-radius: 50rem;
  font-size: .6875rem;
  font-weight: 600;
  background-color: var(--bs-primary);
  color: #fff;
}
.hr-dt-filter-btn.active .hr-count { background-color: #fff; color: var(--bs-primary); }

/* Collapsible filter grid, animated via the grid-template-rows 0fr-to-1fr trick (display:none
   can't be transitioned, so "open" is a real class). The row height animates on .hr-dt-filters;
   .hr-dt-filters-inner carries overflow:hidden + min-height:0 on a non-animated child. The field
   grid (4-up lg, 2-up md, 1-up below) lives on .hr-dt-filters-grid with the panel's separator. */
.hr-dt-filters {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows .28s ease, opacity .22s ease;
}
.hr-dt-filters.open { grid-template-rows: 1fr; opacity: 1; }

.hr-dt-filters-inner {
  min-block-size: 0;
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear .28s;
}
.hr-dt-filters.open .hr-dt-filters-inner { visibility: visible; transition-delay: 0s; }

.hr-dt-filters-grid {
  margin-block-start: 1.1rem;
  padding-block-start: 1.1rem;
  border-block-start: 1px solid var(--bs-border-color);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .75rem 1rem;
}
@media (max-width: 991.98px) { .hr-dt-filters-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 575.98px) { .hr-dt-filters-grid { grid-template-columns: 1fr; } }

/* Filter-field unification (live feedback: date/text/month inputs look thinner and flatter than the
   Select2-rendered <select> fields sitting right next to them in the same row). Two real mismatches,
   confirmed live via getBoundingClientRect()/getComputedStyle(): (1) height — Select2's own vendor
   override (`body .select2-container--default .select2-selection--single{min-block-size:calc(1.5em +
   .75rem + 2px)}`) computes against the SELECTION element's own font-size (not this grid's `.form-
   control-sm` .7875rem), landing at a measured 40px — copying that same calc() onto a `.form-control-
   sm` field resolves its `1.5em` against the SMALLER font-size instead and lands short (measured
   32.9px, not 40), so this pins the plain fixed pixel value actually measured rather than a formula
   that only coincidentally matches in Select2's own context; (2) fill — styles.css's base
   `.form-control` rule is `background-color:transparent`, so a plain field shows the PANEL's own
   backdrop through it instead of a solid surface, while Select2's box is opaque. Matched TO Select2
   (not the other way — shrinking Select2 would also need re-deriving its internal line-height/arrow
   centering, `body .select2-container--default .select2-selection--single{line-height:37px}` above,
   a second moving part with no upside); `min-block-size` (not `block-size`) so it only ever grows a
   field, never fights `.form-control-sm`'s own explicit smaller `height`. */
.hr-dt-filters-grid .form-control {
  min-block-size: 40px;
  background-color: var(--bs-body-bg);
}

@media (prefers-reduced-motion: reduce) {
  .hr-dt-filters,
  .hr-dt-filters-inner {
    transition-duration: .01ms !important;
  }
}

/* Always-on separator between the toolbar/filter zone and the table itself, whether or not the
   filter panel is open — applied on each page's .table-responsive wrapper via its DataTable dom.
   Framed as a plain bordered/rounded box (native Spike's own "Basic Table" demo,
   table-basic.html: `.table-responsive.border.rounded-1`) rather than a bare top hairline, so the
   table reads as its own contained element inside the card instead of bleeding into the card body.
   border-radius only (no overflow:hidden) — clipping isn't needed since the table itself carries no
   striping/background of its own, and hiding overflow here would fight .table-responsive's own
   required overflow-x:auto (mobile horizontal scroll, see CLAUDE.md's DataTables sharp edge).
   --bs-border-radius-lg (live feedback: wanted rounder, "like cards, maybe more than current") —
   .card itself computes to 18px (--bs-card-border-radius); the -lg token is 24px, deliberately a
   touch rounder than the card it sits inside, not just matching it. */
.hr-dt-table-sep {
  margin-block-start: 1.1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
}

/* ---- Employee profile header cover band (Task 9) ---- */
.hr-profile-cover { max-block-size: 120px; inline-size: 100%; object-fit: cover; }

/* ---- Employee profile Overview tab label/value rows (ported from ui-tweaks.css .hr-info-list) ----
   Spike cards ARE body-bg + shadow, so var(--bs-paper-bg) becomes var(--bs-body-bg); every physical
   direction is logical. */
.hr-info-list { margin: 0; }
.hr-info-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: .55rem;
  border-block-end: 1px solid var(--bs-border-color);
}
.hr-info-list li:last-child { border-block-end: none; padding-block-end: 0; }
.hr-info-list li:first-child { padding-block-start: 0; }

/* ---- Employee Salary tab allowance-row column spacer (Task 9, ported from ui-tweaks.css) ----
   Reserves the real remove-button column's WIDTH without copying its height — see CLAUDE.md's
   ".invisible"-height sharp edge: a contentless placeholder sized only on inline-size stays 0px
   tall regardless of width, so it never stretches its row siblings (.row's default align-items:
   stretch). Matches .btn-outline-danger.rounded-circle.round-40's own inline-size exactly. */
.hr-allowance-col-spacer { display: inline-block; inline-size: 40px; }

/* Toolbar on phones: search takes the full first line, buttons wrap beneath. */
@media (max-width: 767.98px) {
  .hr-dt-toolbar .hr-dt-search { inline-size: 100%; flex: 1 1 100%; }
}

/* ---- Collapse-trigger chevron (Task 10, moved unchanged from ui-tweaks.css) ----
   Built from a plain .btn-link + <i>/iconify-icon (Bootstrap's own collapse.js already toggles
   .collapsed on every trigger pointing at a target when it opens/closes, this just gives that state
   a visual). Used by leave/show.php and leave/sick-leave-show.php's collapsed-by-default Documents
   section — scoped generically enough to reuse. */
.hr-collapse-chevron { transition: transform 0.15s ease; }
[data-bs-toggle='collapse']:not(.collapsed) .hr-collapse-chevron { transform: rotate(90deg); }

/* ---- Employee-preview card (rebuilt 2026-09-13, fifth pass — mockup-approved "Direction 4":
   docs/superpowers/mockups/2026-09-13-employee-preview-eligibility-directions.html) ----
   Styles for the shared employee-preview card built by employee-preview.js (Leave, Sick Leave and
   Absence create forms + Leave and Sick Leave review pages). Close to the very first version of this
   card — plain rows, no boxes/tiles/coloured strip — with every row's value promoted to a bold
   weight against a small grey label, and the Entitled/Used/Remaining trio + eligibility pills as the
   two things a viewer actually needs to notice, everything else deliberately quiet. Every colour
   below is a Spike CSS variable that already flips for [data-bs-theme=dark] — no separate dark-mode
   block needed. */
.hr-employee-preview-content--loading { opacity: 0.4; transition: opacity 0.15s ease; }
.hr-employee-preview-spinner-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }

/* Bootstrap 5's own utility set (as vendored here) has no min-width:0 class — needed on a flex
   child that must be allowed to shrink/wrap/ellipsize below its content's own intrinsic width (the
   header's name block) rather than forcing the row wider. */
.min-w-0 { min-inline-size: 0; }

/* header: avatar/name/code/subtitle, plus the balance trio pushed to the far right */
.hr-emp-name { color: var(--bs-heading-color); font-size: 1rem; }
.hr-emp-sub  { font-size: .875rem; color: var(--bs-secondary-color); margin-block-start: .1rem; }

.hr-emp-bal { display: flex; align-items: center; margin-inline-start: auto; flex: 0 0 auto; padding-inline-start: .5rem; }
.hr-emp-bal > div { padding-inline: 1rem; text-align: center; border-inline-start: 1px solid var(--bs-border-color); }
.hr-emp-bal > div:first-child { border-inline-start: 0; padding-inline-start: 0; }
.hr-emp-bal-k { font-size: .6875rem; color: var(--bs-secondary-color); text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
.hr-emp-bal-v { font-weight: 700; color: var(--bs-heading-color); font-size: 1.3125rem; line-height: 1.15; }
.hr-emp-bal-v--rem { color: var(--bs-primary); font-size: 1.5625rem; }

/* "Emergency Leave Request" — plain, bold, red text (no icon/pill chrome by request), with the
   employee's Emergency leave HISTORY stacked beneath it as one thin red-ruled box (2026-09-17 live
   feedback: "under the label, in a red-border box, more readable" — a first side-by-side flex-row
   pass put the history under the balance trio on a wide card and floating mid-card on a narrow one).
   Stacked, never a flex row, so its position never depends on card width. Border-only on purpose —
   Spike's --bs-danger-bg-subtle turns muddy maroon in dark mode, and this card is deliberately quiet
   elsewhere (round-5 history above rejected a boxed/coloured-strip verdict for the SAME card's Annual
   eligibility fact) — a hairline box with one red rule reads as flagged without fighting that.
   Toggled via Bootstrap's own .d-none (see leave-form.js). */
.hr-emp-emergency { margin-block-start: .75rem; }
.hr-emp-type-emergency { font-size: 1.0625rem; font-weight: 700; color: var(--bs-danger); line-height: 1.3; }
.hr-emp-emergency-history {
  margin-block-start: .5rem;
  padding: .625rem .875rem;
  border: 1px solid var(--bs-danger-border-subtle);
  border-inline-start: 3px solid var(--bs-danger);
  border-radius: var(--bs-border-radius);
}
/* "No prior record" is good news — same shape, neutral border, no red rule. */
.hr-emp-emergency-history--clear {
  border-color: var(--bs-border-color);
  border-inline-start-color: var(--bs-border-color);
}
/* Label matches .hr-emp-bal-k (the balance trio's ENTITLED/USED captions) so the box shares the
   card's existing type scale; --bs-secondary-color is already theme-flipped at the top of this file.
   NOT --bs-danger-text-emphasis: it computes to the same dark brown under [data-bs-theme=dark] too
   (confirmed live) and all but vanishes on the dark card. */
.hr-emp-emergency-history-k {
  font-size: .6875rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--bs-secondary-color); line-height: 1.3; margin-block-end: .25rem;
}
.hr-emp-emergency-history-v { font-size: .9375rem; font-weight: 600; color: var(--bs-heading-color); line-height: 1.4; overflow-wrap: anywhere; }
.hr-emp-emergency-history-q { font-size: .8125rem; color: var(--bs-body-color); line-height: 1.4; margin-block-start: .125rem; overflow-wrap: anywhere; }

/* joined date + tenure, with the eligibility pills on the same row */
.hr-emp-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .625rem 1rem;
  margin-block-start: 1rem;
}
.hr-emp-joined { display: flex; align-items: center; gap: .5rem; font-size: .875rem; }
.hr-emp-joined iconify-icon { color: var(--bs-primary); }
.hr-emp-joined .val { font-weight: 600; color: var(--bs-heading-color); }
.hr-emp-joined .ten { color: var(--bs-secondary-color); }
.hr-emp-pills { display: flex; gap: .5rem; flex-wrap: wrap; }

.hr-emp-reason { font-size: .8125rem; color: var(--bs-body-color); margin-block-start: .625rem; }
.hr-emp-reason-next { color: var(--bs-danger); font-weight: 500; }

/* shared label + quiet-value tokens, used by the facts row */
.hr-emp-label { font-size: .875rem; color: var(--bs-secondary-color); line-height: 1.3; margin-bottom: 10px; }
.hr-emp-quiet { color: var(--bs-body-color); font-weight: 400; }

/* review-only: one divider (same treatment as the mockup's reference card), then the facts row */
.hr-emp-context {
  margin-block-start: 1.125rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--bs-border-color);
}
.hr-emp-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .875rem 1.25rem;
}
.hr-emp-fact { display: flex; align-items: flex-start; gap: .75rem; min-inline-size: 0;  }
.hr-emp-fact iconify-icon, .hr-emp-joined iconify-icon { color: var(--bs-primary); font-size: 1.375rem; margin-block-start: .0625rem; flex: 0 0 auto; }
.hr-emp-fact-v { font-size: 0.95rem; font-weight: 600; color: var(--bs-heading-color); overflow-wrap: anywhere; }
.hr-emp-fact-q { font-size: .875rem; color: var(--bs-body-color); margin-block-start: .15rem; }

@media (max-width: 575.98px) {
  .hr-emp-bal { margin-inline-start: 0; padding-inline-start: 0; padding-block-start: .625rem; }
  .hr-emp-bal > div:first-child { border-inline-start: 0; padding-inline-start: 0; }
  .hr-emp-top { flex-direction: column; align-items: flex-start; }
}

/* ---- Avatar group (Task 16, roles/index.php's Role Cards + roles.js's table column) ----
   Replaces Sneat's .pull-up + the ui-tweaks.css .avatar-group flex fix: .hr-avatar is already a
   flex box (spike-app.css's own Avatar section, top of file), so the group itself only needs to
   provide the flex context for its <li> children plus the overlap margin and hover lift. */
.hr-avatar-group { display: flex; align-items: center; }
.hr-avatar-group > .hr-avatar + .hr-avatar { margin-inline-start: -.6rem; }
.hr-avatar-group > .hr-avatar { transition: transform .15s ease; }
.hr-avatar-group > .hr-avatar:hover { transform: translateY(-4px); z-index: 1; }
/* Table row avatar-group (roles.js's Users column): same overlapping-circle treatment as the Role
   Cards grid, at avatar-sm scale so it fits a table row's line height without inflating it. */
.hr-table-avatar-group { cursor: default; }
.hr-table-avatar-group .hr-avatar { cursor: pointer; }

/* ---- Role Cards grid (Roles & Permissions page) and the "Add New Role" card (ported from
   ui-tweaks.css) ---- */
.hr-role-card { transition: box-shadow 0.2s ease, transform 0.2s ease; }
.hr-role-card:hover { box-shadow: var(--bs-box-shadow); }
.hr-add-role-card {
  border: 1px dashed var(--bs-border-color);
  background: transparent;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.hr-add-role-card:hover { border-color: var(--bs-primary); background-color: var(--bs-primary-bg-subtle); }

/* The "System" badge reads as too big/heavy at Bootstrap's default .badge size (ported from
   ui-tweaks.css) — a deliberately small, sleek variant (own class, so no other badge is affected). */
.hr-badge-sm { font-size: 0.6875rem; padding: 0.2em 0.55em; font-weight: 500; }

/* ---- Selected-chip styling for every Select2 multi-select (Roles on the User form, Weekend Days
   on Company Settings, and any future one) — ported from ui-tweaks.css, rescoped to Spike's default
   (non-bootstrap-5) Select2 theme container so this wins over styles.css's own chip rules without
   needing a higher-specificity !important war. Roles then layers its own PER-ROLE color on top
   (users-form.js's paintRoleChips(), the hr-chip-* rules below) — same specificity, but declared
   later in this file, so it wins over this neutral default. ---- */
.select2-container--default .select2-selection--multiple .select2-selection__choice {
  display: inline-flex !important;
  align-items: center !important;
  border-radius: 50rem !important;
  padding-block: 0.15rem !important;
  padding-inline: 0.35rem 0.75rem !important;
  margin-block: 2px !important;
  margin-inline: 0 4px !important;
  border: 1px solid var(--bs-primary-border-subtle) !important;
  background-color: var(--bs-primary-bg-subtle) !important;
  color: var(--bs-primary-text-emphasis) !important;
  font-size: 0.8rem !important;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice .select2-selection__choice__display {
  padding: 0 !important;
}
/* Vendor CSS absolutely-positions this button (position:absolute;left:0;top:0) over the choice's
   own reserved left padding — our tighter pill padding above no longer leaves it enough room,
   so the "x" silently overlapped the label's first letter(s). Static + flex (from the rule above)
   places it as a normal sibling instead, in its real DOM order (before the label). */
.select2-container--default .select2-selection--multiple .select2-selection__choice .select2-selection__choice__remove {
  position: static !important;
  border: none !important;
  background: transparent !important;
  /* Vendor CSS's own combined selector (shared with .select2-selection__choice) sets color:#fff
     unconditionally — invisible against this pill's own light subtle fill in light mode (confirmed
     live: a near-blank circle where the "x" should read). `inherit` matches whatever the parent
     chip's own current color is — the neutral primary above, or a per-role hr-chip-* color when
     users-form.js's paintRoleChips() applies one — rather than hardcoding one variant. Also center
     the glyph inside the button's own box (it rendered flush top-left otherwise). */
  color: inherit !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  line-height: 1 !important;
  border-radius: 50%;
  margin-inline-end: 0.25rem;
}
/* prettier-ignore */
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-primary   { background-color: var(--bs-primary-bg-subtle)   !important; border-color: var(--bs-primary-border-subtle)   !important; color: var(--bs-primary-text-emphasis)   !important; }
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-success   { background-color: var(--bs-success-bg-subtle)   !important; border-color: var(--bs-success-border-subtle)   !important; color: var(--bs-success-text-emphasis)   !important; }
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-info      { background-color: var(--bs-info-bg-subtle)      !important; border-color: var(--bs-info-border-subtle)      !important; color: var(--bs-info-text-emphasis)      !important; }
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-warning   { background-color: var(--bs-warning-bg-subtle)   !important; border-color: var(--bs-warning-border-subtle)   !important; color: var(--bs-warning-text-emphasis)   !important; }
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-danger    { background-color: var(--bs-danger-bg-subtle)    !important; border-color: var(--bs-danger-border-subtle)    !important; color: var(--bs-danger-text-emphasis)    !important; }
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-secondary { background-color: var(--bs-secondary-bg-subtle) !important; border-color: var(--bs-secondary-border-subtle) !important; color: var(--bs-secondary-text-emphasis) !important; }
.select2-container--default .select2-selection--multiple .select2-selection__choice.hr-chip-dark      { background-color: var(--bs-dark-bg-subtle)      !important; border-color: var(--bs-dark-border-subtle)      !important; color: var(--bs-dark-text-emphasis)      !important; }

body .select2-container--classic .select2-selection--single, body .select2-container--classic .select2-selection--single .select2-selection__arrow, body .select2-container--classic .select2-selection--single .select2-selection__rendered, body .select2-container--default .select2-selection--multiple, body .select2-container--default .select2-selection--single, body .select2-container--default .select2-selection--single .select2-selection__arrow, body .select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: 37px;
}

.select2-container--default .select2-selection--single .select2-selection__placeholder {
  color: var(--bs-body-color) !important;
}
