/*
 * Ureh Admin UI — the small set of custom rules beyond what Bootstrap /
 * the vendored Jampack bundle already provide. Every rule here uses
 * Bootstrap CSS variables (var(--bs-*)), never a literal hex, so it
 * inherits both the light and dark theme for free — see
 * docs_2/ui-design-system.md.
 */

/* Enterprise Typography System — the single source of truth for every
 * font-family in the Admin UI. Native system-font stacks only: no
 * Google/Adobe/web fonts, no network font request. This file loads
 * after vendors/main.css (see layouts/app.php / auth.php), so — same
 * specificity, later in the cascade — these declarations override that
 * bundle's own --bs-body-font-family/--bs-font-sans-serif/
 * --bs-font-monospace (including its "DM Sans" values) everywhere,
 * without editing the vendor file itself, per guide.md's CSS
 * Architecture section ("never edit main.css directly; override
 * variables here instead"). The one exception is main.css's own Google
 * Fonts @import, which a cascade override can't cancel — see that
 * file's matching comment at the @import site. Icon fonts (Line
 * Awesome, Remixicon, Bootstrap Icons, Font Awesome, Material Icons,
 * etc.) are untouched; they set their own font-family per glyph class
 * and never read these variables. */
:root {
  --font-ui-sans:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI Variable",
    "Segoe UI",
    Roboto,
    "Helvetica Neue",
    Helvetica,
    Arial,
    sans-serif;

  --font-ui-mono:
    ui-monospace,
    SFMono-Regular,
    "SF Mono",
    Cascadia Code,
    Consolas,
    "Liberation Mono",
    Menlo,
    monospace;

  --bs-font-sans-serif: var(--font-ui-sans);
  --bs-font-monospace: var(--font-ui-mono);
  --bs-body-font-family: var(--font-ui-sans);
}

/* Enterprise surface-border refinement (2026-07-23) — light theme only,
 * background/palette untouched. vendors/main.css's --hk-border-primary
 * (rgb(233.66, 233.66, 233.66), ~#e9e9e9) is the one variable behind
 * every major surface edge in the app — .card, .table cell borders,
 * .hk-navbar's bottom border, .hk-menu's (sidebar) right border,
 * .hk-footer's top border, and assorted dividers (75 references) — but
 * at ~92% lightness on a white page it reads as barely-there, which is
 * why white-on-white surfaces (a card sitting on the page, a table
 * sitting in a card) can look soft/washed out rather than deliberately
 * separated. Deepening just this one token to a fully neutral #e0e0e0
 * (no hue shift, so it stays "crisp gray," never "blue-gray") sharpens
 * every one of those edges at once — cards read as intentional surfaces,
 * table rows/columns scan more easily, the sidebar/header/footer read as
 * bounded regions — without adding a single new color anywhere or
 * touching --hk-border-secondary (form-control borders, already a
 * deliberately darker #d8d8d8, left alone) or --hk-border-tertiary
 * (near-invisible dividers, unaffected). Dark theme's own
 * --hk-border-primary (main.css's [data-bs-theme=dark] block, #35393F)
 * is untouched — this selector only matches in light mode. */
[data-bs-theme="light"] {
  --hk-border-primary: #e0e0e0;
}

/* Dark-theme neutral palette (2026-07-27, simplified 2026-07-27) —
 * ChatGPT-desktop-inspired soft charcoal, applied app-wide (sidebar,
 * navbar, footer, cards, tables, forms, dropdowns, modals, pagination —
 * anything that reads one of these tokens) by overriding the same custom
 * properties vendors/main.css's own [data-bs-theme=dark] block already
 * defines, not by adding new component-specific selectors. admin.css
 * loads after vendors/main.css, so this wins at equal specificity/
 * selector with no `!important`, the same mechanism as the light-theme
 * border override directly above.
 *
 * Consolidated to exactly two levels, per direct feedback that four
 * closely-spaced steps still read as "several different grays" rather
 * than one cohesive surface: every application-chrome surface — body,
 * sidebar (--hk-menu-bg), navbar, footer, and the dashboard's own boxed
 * workspace container — is now the *same* `--bs-body-bg` value (Level 1),
 * and every raised surface — cards, tables, modals, dropdowns, widgets —
 * is `--hk-bg-primary` (Level 2, the card color, kept exactly as-is per
 * explicit "I like the current card color, use it as the reference"
 * direction). `.hk-navbar`/`.hk-footer` needed their own small override
 * below (see that rule's comment) since vendors/main.css hardwires both
 * of them to `--hk-bg-primary` directly — that token is now reserved
 * purely for Level 2, so pulling chrome bars back to Level 1 without also
 * dragging cards down with them requires overriding those two selectors
 * directly, not just the shared variable. A third, small "utility" shade
 * (`--bs-tertiary-bg`) remains for hover/active feedback *within* a
 * Level-2 surface (dropdown-item hover, table row hover/active, toggle
 * tracks) — not a competing background level, just enough contrast for
 * those states to stay visible against the card they sit on; see that
 * variable's own comment below for why it can't simply equal Level 2.
 * Borders are translucent white overlays (rgba(255,255,255,X)) rather
 * than a flat hex, so the same border reads correctly on either level.
 * Status/brand colors (--bs-primary/success/warning/danger and every
 * --hk-menu-item-active-* token) are untouched — this only refines
 * neutral surface/border/muted-text tokens. Nothing here touches light
 * theme. */
[data-bs-theme="dark"] {
  /* Level 1 — application background. Every piece of chrome (sidebar,
   * navbar, footer, the dashboard's boxed workspace container) is pinned
   * to this exact same value below rather than getting a shade of its
   * own, so there is only ever one "page" tone in the whole app. */
  --bs-body-bg: #19191a;
  --bs-body-bg-rgb: 25, 25, 26;
  --hk-menu-bg: var(--bs-body-bg);

  /* Level 2 — the one raised-surface tone (cards, tables, modals,
   * dropdowns, widgets). This is the color already in use and explicitly
   * kept as-is — every other token in this file is defined relative to
   * it, not the other way around. */
  --hk-bg-primary: #242426;
  --hk-bg-primary-rgb: 36, 36, 38;

  /* Utility shade for hover/active *states on* a Level-2 surface — a
   * dropdown-item's hover highlight, a table row's hover/active
   * background, a form-switch track. This can't simply equal Level 2
   * (--hk-bg-primary): a hover state that's the exact same color as the
   * card it's inside of would be invisible, and pagination/list-group
   * hover (vendors/main.css) already reads `--bs-tertiary-bg` for
   * exactly this purpose. One small shade, reused everywhere a
   * near-Level-2 highlight is needed, rather than several unrelated
   * grays for the same job — --hk-bg-hover, --hk-bg-secondary and
   * --hk-bg-tertiary below all resolve to it instead of carrying their
   * own separate values. */
  --bs-tertiary-bg: #2a2a2d;
  --bs-tertiary-bg-rgb: 42, 42, 45;
  --hk-bg-hover: var(--bs-tertiary-bg);
  --bs-secondary-bg: var(--bs-tertiary-bg);
  --bs-secondary-bg-rgb: 42, 42, 45;
  --hk-bg-secondary: var(--bs-tertiary-bg);
  --hk-bg-tertiary: var(--bs-tertiary-bg);

  /* Every table (components/table.php's `.table.table-hover`) paints its
   * own background from --bs-table-bg, which vendors/main.css's `.table`
   * sets to var(--bs-body-bg) — inert while page and card were the same
   * shade, but now that cards (Level 2) are visibly lighter than the
   * page (Level 1), that default would paint a visibly *darker*
   * rectangle inside every card — exactly the "patchy" seam this pass is
   * meant to avoid. Transparent lets a table always match whichever
   * surface it's actually placed on instead. */
  --bs-table-bg: transparent;

  /* Borders — translucent white overlays instead of one flat hex, so one
   * border color reads correctly against either level above rather than
   * needing a different shade per surface. */
  --hk-border-primary: rgba(255, 255, 255, .08);
  --bs-border-color: rgba(255, 255, 255, .08);
  --hk-menu-border: rgba(255, 255, 255, .08);
  --hk-border-secondary: rgba(255, 255, 255, .16);
  --hk-border-tertiary: rgba(255, 255, 255, .05);
  --bs-border-color-translucent: rgba(255, 255, 255, .1);

  /* Shadow color (2026-07-27, glow audit): vendors/main.css's own
   * [data-bs-theme=dark] block sets --bs-shadow-color and --hk-shadow-rgb
   * to a *white* RGB triplet (255, 255, 255) — inconsistent with every
   * other shadow token in that same block (--bs-box-shadow/-sm/-lg all
   * stay black-based rgba(0,0,0,...) in dark theme). Every rule that reads
   * either variable renders as a pale halo instead of depth once the
   * surface behind it is dark — and this pair backs far more than the
   * dashboard cards already fixed above: the base `.card` shadow used
   * app-wide (every list/detail page's cards), `.card.card-shadow`,
   * `.dropdown-menu`/`.popover` (account menu, notifications, table row
   * actions, tooltips), the ApexCharts tooltip, `.btn-floating`, and the
   * resizable-panel shadows (several declared `!important`). Overriding
   * the variable here fixes all of them at the shared source in one place
   * — including the `!important` cases, since supplying a different value
   * for a var() doesn't require outranking `!important` on the property,
   * only changing what the variable resolves to. Matches the black already
   * used by --bs-box-shadow-sm etc. so every shadow in dark theme is now
   * the same low-opacity black, never a light color. Light theme's copy of
   * both variables (near-black already) is untouched. */
  --bs-shadow-color: 0, 0, 0;
  --hk-shadow-rgb: 0, 0, 0;

  /* Muted text softened so secondary/tertiary copy (captions, labels,
   * placeholder-weight text) doesn't compete with actual content —
   * primary body/heading text (--bs-body-color, --bs-emphasis-color) is
   * left exactly as-is; it was already a comfortable off-white, never a
   * stark #fff, so it needed no change. */
  --bs-secondary-color: rgba(255, 255, 255, .55);
  --bs-tertiary-color: rgba(255, 255, 255, .35);
}

/* Audit follow-up (2026-07-27): modals and dropdown menus were the one
 * remaining background layer still sitting at the *old*, undifferentiated
 * darkness rather than the new palette above. `.modal-content` reads
 * `--bs-modal-bg`, and `.dropdown-menu` reads `--bs-dropdown-bg` — both
 * are Bootstrap component tokens declared *inside* their own selector
 * (`.modal{--bs-modal-bg:var(--bs-body-bg)}`,
 * `.dropdown-menu{--bs-dropdown-bg:var(--bs-body-bg)}`, vendors/main.css),
 * not at `:root`/`[data-bs-theme]`, so the root-level `--bs-body-bg`
 * override above can't reach them directly — they need their own small
 * override, same as any other component-scoped Bootstrap variable. Before
 * this pass, page and card were the same shade, so a modal or dropdown
 * matching the page looked correct by accident; now that cards are
 * visibly lighter (Level 2), every "Create/Edit" modal and every dropdown
 * in the app (account menu, notifications, table row actions) would still
 * render at page-level (Level 1) darkness — the one place left where the
 * old flat look survives, and, being how most create/edit flows and menus
 * in this app work, arguably the most *visible* one. A floating overlay
 * is conceptually a raised surface like a card, so both are lifted to the
 * same Level-2 tone (`--hk-bg-primary`) rather than getting a level of
 * their own. Scoped to dark theme only — light theme's cards are
 * deliberately flat white and were never part of this complaint. */
[data-bs-theme="dark"] .modal-content {
  --bs-modal-bg: var(--hk-bg-primary);
}

[data-bs-theme="dark"] .dropdown-menu {
  --bs-dropdown-bg: var(--hk-bg-primary);
}

/* Brand rollout (#28343D) dark-mode fix: vendors/main.css's `.btn-primary`
 * sets --bs-btn-hover-bg/--bs-btn-active-bg to shades *darker* than
 * --bs-primary — correct in light mode (a darker navy on white still
 * reads clearly), but #28343D is already close to this theme's own
 * near-black surfaces (Level 2 card ≈ #242426), so darkening it further
 * on hover/active would make the button all but disappear into the card
 * it sits on. `.btn-primary` itself has no theme scoping (one rule, both
 * themes), so this is a small, theme-scoped override rather than a
 * change to the base rule — same mechanism as every other dark-theme
 * override in this file. Hover/active now *lighten* instead (the only
 * direction that stays visible against a near-black resting state), and
 * the resting state gets a subtle light border it doesn't have in light
 * mode, so the button reads as a defined surface against the card even
 * before it's interacted with — "adjust the surrounding tonal hierarchy,
 * don't change the brand color," applied to the one component where the
 * brand color's own darkness is the thing closest to the surface behind
 * it. */
[data-bs-theme="dark"] .btn-primary {
  --bs-btn-border-color: rgba(255, 255, 255, .14);
  --bs-btn-hover-bg: rgb(105.28, 113.64, 119.72);
  --bs-btn-hover-border-color: rgb(105.28, 113.64, 119.72);
  --bs-btn-active-bg: rgb(140.74, 147.12, 151.76);
  --bs-btn-active-border-color: rgb(140.74, 147.12, 151.76);
}

/* Chrome consistency (2026-07-27): vendors/main.css hardwires both the
 * top navbar (`.hk-wrapper .hk-navbar{background:var(--hk-bg-primary)}`)
 * and the simple footer (`[data-footer=simple] .hk-footer{background:
 * var(--hk-bg-primary)}`) directly to the *card* token — reasonable while
 * page and card were one shade, but `--hk-bg-primary` is now reserved
 * purely for Level 2 (see the root block's own comment), so left alone
 * these two chrome bars would keep popping to card-level lightness while
 * the sidebar right next to them (`--hk-menu-bg`, now Level 1) stays
 * page-dark — a visible seam exactly where navbar meets sidebar. Both are
 * chrome, not raised content surfaces, so both get pulled back to
 * Level 1 to match the sidebar. Equal specificity to the vendor rules
 * being overridden (one attribute selector + one class each), so this
 * wins on source order alone — see the light-theme border override at
 * the top of this file for the same mechanism. */
[data-bs-theme="dark"] .hk-navbar {
  background: var(--bs-body-bg);
}

[data-bs-theme="dark"] .hk-footer {
  background: var(--bs-body-bg);
}

/* .hk-pg-wrapper and .hk-pg-body (layouts/app.php — .hk-pg-wrapper is the
 * outer wrapper around .hk-pg-body, which in turn wraps .container-fluid/
 * #uh-page-body; together they're the whole main-content region behind
 * every page's content) have no background rule of their own anywhere in
 * vendors/main.css, so both were relying entirely on inheriting `body`'s
 * own `background-color: var(--bs-body-bg)` — correct in practice, but
 * implicit: nothing here documented that either element was actually part
 * of the theme system, so a real bug elsewhere could hide behind "it's
 * probably just inheriting fine." This makes that explicit for both — same
 * token, same Level-1 page tone, just stated directly on each element
 * rather than left to inheritance. No `!important`, no hardcoded literal
 * color: both now participate in the theme the same way every other
 * surface in this file does. */
.hk-pg-wrapper,
.hk-pg-body {
  background-color: var(--bs-body-bg);
}

/* Financial values (wallet balances, transaction amounts, KPI figures,
 * table numerics) get fixed-width digits so stacked numbers align —
 * harmless no-op on non-numeric text, so applied app-wide rather than
 * requiring every amount call site to opt in. `button` is listed
 * alongside `body` because form controls don't inherit font-variant/
 * font-feature-settings from an ancestor the way normal elements do —
 * Bootstrap's Reboot only forces font-family/font-size/line-height back
 * to `inherit` on button/input/select/textarea, so a bare <button>
 * silently keeps the UA-default `normal` instead of picking up
 * tabular-nums from here. That one font-feature difference is also what
 * was shifting header-icon <button>s (e.g. layouts/header.php's
 * #theme-toggle, the mobile .navbar-toggle) a few px off the baseline
 * that vertical-align:middle computes for the sibling <a>-based icon
 * buttons — confirmed by a live tag-swap test (swapping an <a> icon to
 * <button> reproduced the offset; swapping a <button> icon to <a> fixed
 * it, with no other CSS involved). Listing `button` here fixes both the
 * original numeric-alignment gap and that baseline mismatch in one
 * place, rather than patching #theme-toggle in isolation. */
body,
button {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

/* Icon-button hover blob covering the icon — every `.btn.btn-icon` that
 * also carries a `btn-flush-*` variant (theme toggle, notifications
 * bell, audit log, mobile menu toggle, sidebar collapse toggle —
 * layouts/header.php, layouts/sidebar.php) gets its hover/focus/active
 * feedback from vendors/main.css's `.btn.btn-icon[class*=btn-flush-]`
 * `::before`: a 0×0 circle at the button's center that grows to
 * `top:0;left:0;height:100%;width:100%` on hover (main.css ~116085-
 * 116100), colored per variant — `--bs-bg-gray-light-5` for the
 * `flush-soft-hover` pairing every one of these buttons already uses
 * (~#f7f7f7 in light mode, ~#38403f-ish dark gray in dark mode; plain
 * `--bs-dark` — solid black — for `btn-flush-dark` on its own). Root
 * cause: that `::before` is `position:absolute`, while the icon
 * (`uh_icon()`'s bare `<svg>`, or a feather-icon `<i>` that
 * `feather.replace()` turns into an `<svg>` at runtime) is plain
 * in-flow, non-positioned inline content. Per the CSS painting-order
 * algorithm (CSS 2.1 Appendix E), positioned descendants at the same
 * z-index paint *after* — on top of — non-positioned in-flow content of
 * the same stacking context, regardless of source order, so the
 * fully-grown circle always ends up over the icon: a near-white disc in
 * light mode, a near-black one in dark mode, both fully hiding the
 * glyph rather than sitting behind it as the soft-hover treatment
 * intends. Lifting the icon into its own stacking position above the
 * `::before` (rather than touching main.css's colors/sizing/transition,
 * which stay exactly as designed) fixes every instance at once, in both
 * themes, without a per-page override.
 *
 * UI audit follow-up: components/table.js's actionsMenu() has the exact
 * same problem for its "⋯" overflow trigger and any primary row-action
 * button whose label has no LABEL_ICON mapping (falls back to raw
 * text) — both are plain non-positioned text, not an `<svg>`, so this
 * rule never covered them. Confirmed live: on hover/focus the "⋯"
 * button visibly disappeared into a flat, out-of-place light-gray/near-
 * white disc in *both* themes (worse, more obviously wrong in dark
 * mode, since a light disc sitting on an otherwise near-black page reads
 * as a real bug rather than a hover state) — the Team page's "⋯" action
 * menu is where this was first reported, but the same markup backs
 * every actionsMenu() call site app-wide (Team Members, Webhook
 * Deliveries, all three Notifications tabs). table.js now wraps both in
 * a `.uh-btn-icon-content` span so they can be targeted the same way;
 * added to this selector rather than duplicating the rule. */
.btn.btn-icon[class*="btn-flush-"] svg,
.btn.btn-icon[class*="btn-flush-"] .uh-btn-icon-content {
  position: relative;
  z-index: 1;
}

/* Disable the vendor's collapsed-sidebar hover flyout (Phase: sidebar
 * persistence follow-up). vendors/main.css's `[data-layout=vertical]
 * .navbar-toggle` click handler (main-DXmtY0ym.js) sets
 * data-layout-style="collapsed" on .hk-wrapper, then — 250ms later —
 * data-hover="active"; every rule below is copied verbatim from
 * main.css's own `@media(min-width:1200px)` block, selector for
 * selector, so cascade order (this file loads after vendors/main.css —
 * see this file's header comment) wins at *equal* specificity, no
 * `!important` needed. Deliberately NOT touching data-hover itself
 * (e.g. via a MutationObserver stripping it): that attribute is also
 * the click handler's own precondition for the *un*-collapse branch
 * ("if collapsed && data-hover==='active', go back to default") — an
 * approach that suppressed data-hover would silently break "click the
 * toggle again to expand." Overriding only the *visual* properties the
 * flyout's :hover rule sets, while leaving that attribute's lifecycle
 * exactly as the vendor drives it, keeps expand-again, active-item
 * highlighting, submenu `.collapse` toggling and every transition
 * untouched — this only cancels the one animation this app doesn't
 * want. `.callout` (one of the vendor's own flyout targets) is skipped:
 * layouts/sidebar.php never renders it, so that vendor rule is still a
 * no-op here. `.brand-img` *used* to be in that same "no-op" category
 * (sidebar.php had no logo markup at all, just plain text) — now that the
 * sidebar has a real logo placeholder (two `.brand-img` children, see that
 * file), its matching flyout rule needs the same cancellation as
 * everything else in this block, added below. */
@media (min-width: 1200px) {
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover {
    width: 72px;
  }
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover .menu-content-wrap .menu-group .nav-header {
    display: none;
  }
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover .menu-content-wrap .menu-group .navbar-nav > .nav-item > .nav-link > *:not(.nav-icon-wrap) {
    visibility: hidden;
  }
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover .menu-content-wrap .menu-group .collapse.show {
    display: none;
  }
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover .menu-content-wrap .menu-gap {
    display: none;
  }
  /* The wordmark stays hidden even while the (now width-pinned-to-72px)
   * flyout is hovered — without this, main.css's own hover rule
   * (`display:inline`) would try to reveal a ~112px-wide wordmark inside a
   * column that never actually widens past 72px, since the rule above
   * already cancels the width expansion that vendor rule assumes. */
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover .menu-header > span .navbar-brand .brand-img:last-child {
    display: none;
  }

  /* Collapsed rail: no horizontal scrolling.
   *
   * The nav labels stay in flow when the rail collapses — main.css hides
   * them with `visibility:hidden`, which removes them visually but keeps
   * their box, so a ~110px label still contributes width inside a 72px
   * column. `.nicescroll-bar` carries `overflow-x:auto`, so that surplus
   * became a real horizontal scrollbar: measured on a 1440px viewport,
   * the collapsed rail scrolled 108px sideways (scrollWidth 179 against
   * clientWidth 71), dragging the icons out of alignment. Expanded, the
   * same container measured 269/269 and scrolled 0, which is why this
   * only ever showed up collapsed.
   *
   * Clipping is safe here specifically BECAUSE of the block above: the
   * hover flyout is deliberately disabled, so the collapsed rail never
   * widens and no label is ever meant to be readable in this state.
   * Nothing visible is cut off — only the invisible overhang that made
   * the rail scrollable.
   *
   * overflow-y is left untouched so long menus still scroll vertically,
   * and this is scoped to the collapsed state so the expanded sidebar,
   * the mobile off-canvas menu (which uses the same attribute value
   * below 1200px with a different meaning) and every tooltip behave
   * exactly as before. */
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"] .hk-menu .nicescroll-bar {
    overflow-x: hidden;
  }

  /* Collapse-toggle visibility & placement (same follow-up).
   * vendors/main.css blurs .navbar-toggle to invisibility whenever
   * collapsed (`filter:blur(100px)`, unconditional — not gated by
   * :hover) and only ever reverses it inside the hover-flyout rule
   * this file already disables above, so with the flyout off the
   * button was left permanently blurred.
   *
   * .menu-header is a single fixed-height flex *row*
   * (display:flex;align-items:center;height:65px;overflow:hidden —
   * main.css ~127465), and the one <span> wrapping brand+toggle inside
   * it is *also* a single flex row (display:flex;justify-content:
   * space-between — ~127480): the two were always meant to sit
   * side-by-side on one line, never stacked, so there's no second row
   * for the toggle to occupy without either widening the (now 72px)
   * header or growing it vertically. Widening isn't an option (that's
   * the whole point of collapsed), so this switches .menu-header and
   * its <span> from a row to a *column* — same elements, same
   * `align-items:center` the vendor rule already sets (now centering
   * the column's cross-axis instead of its row cross-axis, for free),
   * just a different flex-direction — and relaxes the fixed 65px
   * height to `auto` so both rows fit without clipping. Brand stays
   * exactly where the expanded layout puts it (first child, untouched,
   * no display:none); the toggle becomes the second row directly below
   * it, centered in the 72px column the same way every nav icon below
   * it is — reading as the top of the icon rail rather than a
   * replacement for the logo. .hk-menu is itself `display:flex;
   * flex-flow:column` (main.css ~127324) with .menu-header as just its
   * first `flex:0 0 auto` item, so a taller header reflows the
   * scrollable nav list below it automatically — no fixed pixel
   * offsets anywhere in that relationship to break.
   *
   * Un-gated by :hover/data-hover, unlike the flyout block above — this
   * must hold constantly while collapsed, not just flash in on hover. */
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"] .hk-menu .menu-header {
    height: auto;
    padding-left: 0;
    padding-right: 0;
    padding-top: .75rem;
    padding-bottom: .75rem;
  }
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"] .hk-menu .menu-header > span {
    flex-direction: column;
    justify-content: center;
    gap: .5rem;
  }
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"] .hk-menu .menu-header .navbar-toggle {
    filter: none;
  }
}

/* Sidebar logo placeholder (2026-07-27) — sizes the *slot*, not the
 * artwork, so dropping in the real Ureh logo later (a similarly-sized
 * `<img>` or inline SVG in place of the two `.brand-img` spans in
 * layouts/sidebar.php) needs no further changes here. Icon chip ~2rem/32px
 * square (visible in both expanded and collapsed sidebar states — the
 * vendor's own `.brand-img:last-child` rule, and this file's matching
 * hover-flyout override above, are what hide the wordmark on collapse, not
 * this block); wordmark SVG's own `viewBox` fixes its ~112x32 footprint,
 * so this only needs to constrain its rendered height to match the icon. */
.uh-sidebar-brand {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
}

.uh-sidebar-brand-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: .5625rem;
  background: rgba(var(--bs-primary-rgb), .12);
  /* Brand rollout (#28343D) dark-mode fix: --bs-primary-text-emphasis, not
   * --bs-primary itself — see .uh-dashboard-kpi-icon's own comment. Same
   * collapse risk here (icon fill vs. this chip's own translucent wash)
   * plus a second one this class doesn't share with that one: the
   * wordmark right next to it (.uh-sidebar-brand-mark below) sits
   * directly on the dark sidebar background with no wash at all, so it
   * needed the identical fix for the same underlying reason (the brand
   * color is dark enough to vanish against a dark surface either way). */
  color: var(--bs-primary-text-emphasis);
  flex-shrink: 0;
}

.uh-sidebar-brand-mark {
  display: inline-flex;
  align-items: center;
  height: 2rem;
  color: var(--bs-primary-text-emphasis);
}

.uh-sidebar-brand-mark svg {
  display: block;
  height: 100%;
  width: auto;
}

/* Official Ureh brand marks inlined by uh_brand_svg() (sidebar icon +
   wordmark). CSS sizes by height with width:auto so the source aspect
   ratio is never stretched. The collapsed rail shows the icon; the
   expanded rail shows the wordmark (the last .brand-img is hidden on
   collapse by the vendor rule). */
.uh-sidebar-brand-icon svg {
  display: block;
  width: 1.375rem;
  height: 1.375rem;
}

.uh-sidebar-brand-mark .uh-sidebar-brand-svg {
  display: block;
  height: 100%;
  width: auto;
}



/* Lightweight, reusable hover tooltip — assets/js/components/
 * sidebar-tooltip.js is the only thing that toggles `.uh-tooltip--visible`;
 * generic, not sidebar-specific (currently only wired to the collapsed
 * sidebar's nav-links, per the flyout-disable block above, but reusable
 * anywhere else a collapsed/icon-only control needs an on-hover label).
 * Single shared DOM node (see that file), repositioned per-target rather
 * than one node per icon, so this stays cheap regardless of sidebar
 * length.
 *
 * Built on vendors/main.css's own Bootstrap tooltip classes/tokens
 * (`.tooltip`/`.tooltip-inner`, ~main.css:5168) rather than new colors —
 * shipped in every page already, fully theme-aware, but unused anywhere
 * in this app until now (no `data-bs-toggle="tooltip"` exists). A
 * second, more specific rule further down that same file (~main.css:
 * 120983 — this app's own "Enterprise" template layer, so it wins the
 * cascade over Bootstrap's base version) is what actually supplies the
 * look: `background:var(--bs-foreground)` / `color:var(--hk-bg-primary)`
 * — a dark-slate-on-light chip in light mode, a white-on-near-black chip
 * in dark mode (both tokens flip in the `[data-bs-theme=dark]` block, so
 * this needs zero dark-mode-specific rules of its own) — plus
 * `border-radius:.5rem`. Its own `.tooltip-arrow{display:none}` (this
 * app's template doesn't use the classic CSS-triangle arrow anywhere)
 * is left alone — this component doesn't use that element at all,
 * building its own callout-style diamond pointer instead (below) via a
 * plain `::before` on `.tooltip-inner`, so there's no conflict with
 * that vendor rule either way. Class names only, no Bootstrap Tooltip
 * JS/Popper dependency: this component positions itself with a plain
 * getBoundingClientRect() call, since it only ever needs to sit beside
 * a fixed 72px-wide rail — Popper's dynamic collision/placement logic
 * has nothing to solve here.
 *
 * What this file adds on top of vendor's bare .tooltip/.tooltip-inner:
 * `position:fixed` (vendor's version assumes Popper sets
 * `position:absolute` itself; this component has no Popper instance, so
 * it needs its own fixed-position placement instead), slightly more
 * generous padding (`.375rem`/`.75rem` — this file's own already-
 * established spacing increments, e.g. `.uh-dashboard-kpi .card-body`
 * above), a soft shadow (`--bs-box-shadow-sm`, the same subtle-
 * elevation token `.dropdown-menu` builds its own shadow from), the
 * fade+slide transition (`prefers-reduced-motion` respected, the same
 * convention main.css's own `.fade` utility already follows elsewhere
 * in this app), and the diamond pointer itself: an 8px square,
 * `background:inherit` (copies `.tooltip-inner`'s own computed
 * background — the same theme-aware `--bs-foreground` token, so it's
 * always in sync with the box in both themes with no color of its own
 * to keep in sync by hand), rotated 45deg and pulled half-outside the
 * box's left edge — a callout pointer (Linear/Notion/Raycast-style)
 * rather than the vendor's classic CSS-triangle arrow. */
.uh-tooltip.tooltip {
  position: fixed;
  z-index: 1071;
  opacity: 0;
  transform: translateY(-50%) translateX(-6px);
  transition: opacity .15s ease, transform .15s ease;
  pointer-events: none;
}
.uh-tooltip.tooltip.uh-tooltip--visible {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}
.uh-tooltip .tooltip-inner {
  position: relative;
  padding: .375rem .75rem;
  box-shadow: var(--bs-box-shadow-sm);
}
.uh-tooltip .tooltip-inner::before {
  content: "";
  position: absolute;
  top: 50%;
  left: -4px;
  width: 8px;
  height: 8px;
  background: inherit;
  border-radius: 1px;
  transform: translateY(-50%) rotate(45deg);
}
@media (prefers-reduced-motion: reduce) {
  .uh-tooltip.tooltip {
    transition: none;
  }
}

/* Table Responsiveness & Header UI Refinement pass — docs/ui-table-audit.md.
 * Reusable column-width hooks so Status/Amount/Date/Reference/Actions read
 * as the same width across every table in the app rather than each table's
 * own content organically sizing them. Applied to <th> (components/table.php)
 * and mirrored onto the matching <td> (assets/js/components/table.js) since
 * table-layout stays the Bootstrap default (auto) — a plain width hint, not
 * a hard constraint, so long content still isn't clipped without warning.
 * Amount also right-aligns, which reads naturally alongside the app-wide
 * tabular-nums rule above. */
.uh-col-status { width: 110px; }
.uh-col-amount { width: 130px; text-align: right; }
.uh-col-date { width: 130px; }
.uh-col-reference { width: 170px; }
.uh-col-actions { width: 90px; }

/* Truncation for the small set of genuinely-unbounded free-text columns
 * (Description, Message, Reason, User Agent, URL, Value, ...) — paired
 * with Bootstrap's own .text-truncate (already compiled into main.css:
 * overflow:hidden;text-overflow:ellipsis;white-space:nowrap), which needs
 * a max-width to actually trigger. Each call site also sets title="" on
 * the same element for a native hover tooltip, so the full value is never
 * actually lost, just not shown inline. */
.uh-table-truncate {
  max-width: 260px;
  display: inline-block;
  vertical-align: bottom;
}

/* Consistent gap between a row's primary action and its "⋯" overflow
 * menu (assets/js/components/table.js's actionsMenu()) — previously two
 * inline elements with no explicit spacing, relying on incidental
 * whitespace in the template string. */
.uh-row-actions {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  white-space: nowrap;
}

/* Whole-row navigation (usability pass) — cursor affordance only; hover
 * highlight already comes from Bootstrap's own .table-hover, already on
 * every table via components/table.php, so no new hover style is added
 * here. */
.uh-row-clickable {
  cursor: pointer;
}

/* Keyboard-focus ring for a clickable row (tabindex="0", assets/js/
 * components/table.js) — same treatment .dropdown-item:focus-visible
 * already uses, inset so it never gets clipped by .table-responsive's own
 * overflow-x: auto on an edge row. */
.uh-row-clickable:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

/* Filter-bar control widths — every list page's search/filter row
 * (e.g. transactions/index.php, clients/index.php) previously hardcoded
 * its own style="width:Npx" per control with no shared scale (220px,
 * 260px, 180px, 140px, 120px, 150px, 160px all appeared for
 * differently-sized versions of the same kind of control). One scale,
 * reused everywhere; still plain widths (not min-width) so controls keep
 * wrapping naturally on narrow/mobile viewports exactly as before. */
.uh-filter-xs { width: 120px; }
.uh-filter-sm { width: 150px; }
.uh-filter-md { width: 180px; }
.uh-filter-lg { width: 220px; }

/* components/form.php's uh_form_select() own width rule, moved from an
 * inline style to a class for the same reason as the filter widths above
 * — see that function's docblock for why min-width (not a fixed width)
 * is deliberate here. */
.uh-filter-select {
  width: auto;
  min-width: 170px;
}

/* Account dropdown (layouts/header.php) — scoped so these refinements
 * never leak into the header's other dropdown (navbar-search), which has
 * its own, unrelated markup. Brings the dropdown's text down to the
 * app-wide 14px scale (it was the last surviving 16px text in the
 * authenticated shell — --bs-dropdown-font-size defaults to 1rem) and
 * tightens item/divider spacing to match the rest of the compact shell,
 * all via the same --bs-dropdown-* custom properties main.css's own
 * .dropdown-menu rule already reads, not by fighting its specificity. */
.uh-account-dropdown {
  --bs-dropdown-font-size: .875rem;
  --bs-dropdown-item-padding-x: .75rem;
  --bs-dropdown-item-padding-y: .4rem;
  --bs-dropdown-divider-margin-y: .375rem;
  min-width: 230px;
}

/* display:flex + align-items:center replaces the per-icon position:relative;
 * top:2px/-2px offset hacks main.css's own .dropdown-icon rules use to
 * approximate vertical centering — flex centers the icon and label
 * against each other exactly, at any font-size, so the top overrides
 * directly below zero them out rather than compounding with flex. */
.uh-account-dropdown .dropdown-item {
  display: flex;
  align-items: center;
  border-radius: var(--bs-border-radius-sm);
  transition: background-color .15s ease-in-out, color .15s ease-in-out;
}

.uh-account-dropdown .dropdown-icon,
.uh-account-dropdown .dropdown-icon.feather-icon {
  top: 0;
  width: 18px;
  margin-right: .5rem;
}

.uh-account-dropdown .dropdown-header {
  padding-top: .5rem;
  padding-bottom: .25rem;
}

/* Phase F4-02: the vendored bundle's own :focus-visible rules
   (vendors/main.css) only cover .btn/.nav-link/.btn-link/.icon-link-hover —
   not .dropdown-item (F4-01's rebuilt account dropdown). A real,
   self-introduced gap, closed here rather than in the vendor bundle. */
.dropdown-item:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

/* Phase F4-02: the skeleton->content swap (assets/js/components/loader.js's
   swapSkeleton()) fades content in rather than popping it, guarded the same
   way as the scroll-behavior rule above. */
.uh-fade-in {
  animation: uh-fade-in .2s ease-in-out;
}

@media (prefers-reduced-motion: reduce) {
  .uh-fade-in {
    animation: none;
  }
}

@keyframes uh-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.uh-file-drop {
  cursor: pointer;
  border-color: var(--bs-border-color) !important;
  transition: border-color .15s ease-in-out, background-color .15s ease-in-out;
}

.uh-file-drop:hover,
.uh-file-drop:focus-visible {
  border-color: var(--bs-primary) !important;
  background-color: var(--bs-primary-bg-subtle);
}

.uh-chart-summary {
  border-bottom: 1px solid var(--bs-border-color);
  padding-bottom: .5rem;
      font-size: 0.875rem !important; /* Adjust pixel/rem size as needed */
}

.uh-empty-state {
  color: var(--bs-secondary-color);
}

/* Compact, consistent form controls + denser typography across the
 * authenticated app shell — scoped off .hk-pg-auth (layouts/auth.php's
 * own wrapper class) so login/OTP is never touched, per that page being
 * out of scope. Values below are the vendor bundle's own .form-control-sm/
 * .form-select-sm numbers (vendors/main.css) applied as the app-wide
 * default, rather than requiring every call site to add -sm — this is
 * also what fixes filter bars mixing a plain .form-control search box
 * (previously 16px/38px tall) with a .form-select-sm dropdown (14px/31px)
 * side by side.
 *
 * Enterprise input polish pass — font-size stepped down one more notch
 * (14px -> 13px, this app's own established "compact data" tier; see
 * settings/business.php's/settings/kyc.php's own .uh-*-detail-list rows,
 * already 13px) with padding widened slightly (4px/8px -> 6px/10px) so
 * the smaller text still sits in a comfortably roomy box rather than
 * reading as cramped — smaller text plus more breathing room, not just
 * "smaller." padding-right is deliberately left alone here (top/bottom/
 * left only, exactly like .form-select already had to do below) —
 * .uh-password-field .form-control's own padding-right:2.75rem
 * (space for the eye-icon toggle button) has LOWER selector specificity
 * than this rule, so a padding *shorthand* here would win over that
 * longhand override and squeeze the toggle button into the typed text;
 * every other .form-control keeps the vendor bundle's own default
 * padding-right unless something more specific overrides it, same as
 * before this pass. Border/focus/hover treatment mirrors .hk-pg-auth's
 * own already-established "enterprise-SaaS-style focus ring" (border
 * tightens to the brand primary color + a soft rgba ring on focus, a
 * lighter border on hover) instead of the vendor bundle's stock,
 * non-brand, non-theme-aware #86b7fe/#0d6efd40 default — the login page
 * already made this exact call for its own inputs; this extends the
 * same call to every other authenticated page's inputs, so both areas
 * share one visual language instead of two.
 */
/* Typography re-balance pass: this tier (and every rule this file's own
 * comments explicitly pair with it — .form-select/.form-label below,
 * .hk-pg-auth's own form-control/.uh-auth-label, .uh-detail-list's value
 * column, .uh-entity-header-meta) had been stepped down to .8125rem/13px
 * by an earlier compact-input pass. Live review found the result read as
 * a touch too small across form inputs and detail views specifically —
 * this brings that one shared tier up a single notch to .875rem/14px,
 * not back to the pre-compaction 1rem/16px ambient default: still
 * distinctly "compact enterprise," just no longer cramped. Scoped to
 * exactly the rules already tied to this tier by their own prior
 * comments, not a blanket app-wide size bump. */
.hk-wrapper:not(.hk-pg-auth) .form-control {
  min-height: calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));
  padding-top: .375rem;
  padding-bottom: .375rem;
  padding-left: .625rem;
  font-size: .875rem;
  border-color: var(--hk-border-primary);
  transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out, background-color .15s ease-in-out;
}

.hk-wrapper:not(.hk-pg-auth) .form-select {
  padding-top: .375rem;
  padding-bottom: .375rem;
  padding-left: .625rem;
  font-size: .875rem;
  border-color: var(--hk-border-primary);
  transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out, background-color .15s ease-in-out;
}

.hk-wrapper:not(.hk-pg-auth) .form-control:hover:not(:disabled):not(:focus),
.hk-wrapper:not(.hk-pg-auth) .form-select:hover:not(:disabled):not(:focus) {
  border-color: var(--hk-border-secondary);
}

.hk-wrapper:not(.hk-pg-auth) .form-control:focus,
.hk-wrapper:not(.hk-pg-auth) .form-select:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 .2rem rgba(var(--bs-primary-rgb), .15);
}

.hk-wrapper:not(.hk-pg-auth) .form-control::placeholder {
  color: var(--bs-tertiary-color);
}

.hk-wrapper:not(.hk-pg-auth) .form-control:disabled,
.hk-wrapper:not(.hk-pg-auth) .form-select:disabled {
  background-color: var(--bs-tertiary-bg);
  opacity: .65;
}

/* Labels a notch smaller than before (matching the new input text size
 * instead of sitting a size above it) plus a touch of weight and the
 * app's own standard muted tone — the same "small, medium-weight,
 * muted caption above a clearer value" hierarchy already established
 * for .uh-entity-header-label/.uh-dashboard-section-title elsewhere in
 * this app, applied to every ordinary form label too. */
.hk-wrapper:not(.hk-pg-auth) .form-label {
  font-size: .875rem;
  font-weight: 500;
  color: var(--bs-secondary-color);
}

/* Sidebar nav text was rendering at the body's default 16px — every other
 * "small" text in the app (breadcrumb items, table headers, filter
 * selects) already sits at 14px, so this brings the sidebar in line with
 * that established scale rather than introducing a new size. */
.hk-menu .nav-link-text {
  font-size: .875rem;
}

/* Table body content (16px) was noticeably larger than its own header
 * row (14px, components/table.php's fs-8 header cells) — matching it
 * keeps rows visually calmer without touching header/badge sizing. */
.hk-wrapper:not(.hk-pg-auth) .table td {
  font-size: .875rem;
}

/* Portal-wide typography pass (2026-08) — the one outlier this app's own
 * already-compact scale still had: every page's own title
 * (layouts/app.php's shared `<h4 class="mb-1">`) rendered at the vendor
 * bundle's raw h4 size (24px at desktop, ~21-22px on narrow viewports via
 * its own `calc(1.275rem + .3vw)`), against a page built almost entirely
 * out of the 14px tier established above (breadcrumbs, card headers,
 * sidebar, table, form controls) — a page title nearly double its own
 * breadcrumb's size read as oversized/unbalanced rather than as
 * deliberate hierarchy. A fixed 1.25rem is at or below the previous size
 * at every breakpoint (never a viewport where this reads larger than
 * before), so this is a pure reduction, not a new responsive behavior.
 * `#uh-page-body` scoping (rather than a bare `h4.mb-1`) keeps this from
 * ever matching an unrelated h4 a page's own $content might render.
 * Breadcrumbs (14px) move down one tier to the same 12px "metadata" size
 * already used for page subtitles/muted captions elsewhere on this same
 * header — wayfinding text, not primary content, so it reads more
 * calmly a size below the page title without losing legibility. Card
 * headers, dashboard section titles, table/form text, and sidebar nav
 * are untouched — already at or below this same 14px/12px scale, not
 * outliers. */
#uh-page-body > div.mb-4 > h4.mb-1 {
  font-size: 1.25rem;
}

.hk-pg-header .breadcrumb {
  font-size: .75rem;
}

/* Dashboard redesign (docs/dashboard-redesign-plan.md) — scoped to
 * dashboard/index.php's own markup only (uh-dashboard-* prefix), so none
 * of this can leak onto or conflict with any other page's cards. */

/* Section headings (Executive Overview, User Overview, ...) — a
 * distinct tier from a card's own <h6> title, so "this is a page
 * section" reads differently from "this is one card's title". */
.uh-dashboard-section-title {
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--bs-secondary-color);
  margin-bottom: .75rem;
 
}

/* Visual-polish pass (2026-07-27): every card on this page previously
 * carried whatever the vendor bundle happened to assign it — Quick
 * Actions (plain `.card`, no `card-border`) kept Bootstrap's own default
 * `box-shadow:0 2px 2px rgba(...,.1)`, while every KPI/data card
 * (`.card-border`, vendors/main.css) explicitly zeroes that shadow to
 * `none` — two different elevation treatments on one page — and both sat
 * at the vendor's flat `.5rem` corner radius, one step smaller than this
 * page's own `.75rem` workspace frame around them. One rule, scoped to
 * `.uh-dashboard-section .card` (every card on this page lives inside a
 * `.uh-dashboard-section` wrapper — Quick Actions included), gives all of
 * them the same quiet elevation and a radius that actually matches the
 * frame they sit in. `rgba(var(--bs-shadow-color),...)` reuses the exact
 * token the vendor's own `.card` shadow already reads (main.css) — that
 * variable flips from a dark to a light RGB triplet in
 * `[data-bs-theme=dark]`, which is exactly why dark theme needs its own
 * override below (a light-RGB shadow reads as a glow, not depth, on a
 * dark card). admin.css loads after vendors/main.css, so this wins over
 * both vendor rules at equal specificity without `!important`.
 *
 * White-surface revision (2026-07-27, follow-up): the workspace frame
 * behind these cards no longer carries any background tint in light
 * theme (see `.uh-dashboard-workspace` below), so this shadow is the only
 * thing left doing the work of separating a card from its surroundings.
 * A third, wider/softer layer was added — still each individually
 * low-opacity — so depth still reads clearly on a flat white canvas
 * rather than depending on a tinted background to make the edge visible. */
.uh-dashboard-section .card {
  border-radius: .75rem;
  box-shadow:
    0 1px 2px rgba(var(--bs-shadow-color), .04),
    0 2px 4px rgba(var(--bs-shadow-color), .05),
    0 10px 20px rgba(var(--bs-shadow-color), .05);
  transition: box-shadow .2s ease-in-out, border-color .2s ease-in-out;
}

/* Dashboard UI refinement pass (dev-ui only, Supabase-style enterprise
 * polish — see dashboard/index.php's own top comment): a light,
 * ambient hover acknowledgment on every dashboard card, not a lift/
 * translate — none of these cards are themselves a click target (their
 * own links/rows are), so a transform here would misread as "this whole
 * card is draggable/clickable" rather than the intended "your cursor is
 * here" polish. Light theme deepens the same shadow recipe above; dark
 * theme instead brightens the border a step (--hk-border-secondary,
 * already used for exactly this "one step up from the resting border"
 * purpose elsewhere in this file) rather than adding any shadow, per the
 * dark-theme comment directly below this rule ("integrated, not
 * floating" — a hover state shouldn't contradict that). */
.uh-dashboard-section .card:hover {
  box-shadow:
    0 2px 4px rgba(var(--bs-shadow-color), .06),
    0 6px 12px rgba(var(--bs-shadow-color), .07),
    0 16px 28px rgba(var(--bs-shadow-color), .08);
}

@media (prefers-reduced-motion: reduce) {
  .uh-dashboard-section .card {
    transition: none;
  }
}

/* Dark-theme refinement (2026-07-27, second follow-up): the shadow above
 * reads `--bs-shadow-color`, which flips to a *white* RGB triplet in dark
 * theme (vendors/main.css) — so on a dark card that same "subtle shadow"
 * recipe renders as a pale glow/halo around every edge instead of depth,
 * the opposite of the calm, integrated look enterprise dark dashboards
 * (Linear, Vercel, GitHub Enterprise, Datadog) go for. Dark mode already
 * gets its separation from the workspace's own tertiary-tint background
 * (`.uh-dashboard-workspace` below, left as-is for dark theme) plus each
 * card's existing 1px border (--hk-border-primary) — a shadow on top of
 * that is redundant and works against "integrated, not floating". Higher
 * specificity than the base rule above (attribute selector + two classes
 * beats two classes), so this wins regardless of source order with no
 * `!important` needed; the light theme is untouched by this selector. */
[data-bs-theme="dark"] .uh-dashboard-section .card {
  box-shadow: none;
}

[data-bs-theme="dark"] .uh-dashboard-section .card:hover {
  box-shadow: none;
  border-color: var(--hk-border-secondary);
}

.uh-dashboard-section .card .card-header:first-child {
  border-radius: calc(.75rem - 1px) calc(.75rem - 1px) 0 0;
}

/* Alignment/typography fix for the Active/Pending/Suspended and
 * Today/Successful/Pending/Failed/Needs Reconciliation pills (User &
 * Financial Overview) — `.badge-status` has no layout rule of its own
 * anywhere in vendors/main.css (only its `.badge-label` descendant does),
 * so the dot + label + value previously aligned on plain inline baseline,
 * sitting the small round dot noticeably low against the text next to it.
 * `badge-status` is used exclusively on this page (no other admin-ui page
 * references it), so this is scoped here rather than risking a global
 * badge behavior change. Label muted to secondary-color to match the rest
 * of the page's label/value hierarchy (label quiet, value — already
 * `fw-semibold` in the markup — the thing that's supposed to draw the
 * eye); dot/value colors are untouched. */
.badge-status {
  display: inline-flex;
  align-items: center;
}

.badge-status .badge-label {
  color: var(--bs-secondary-color);
}

/* The one KPI-tile template every tile in every section uses (icon +
 * label header row, then a full-width value, then an optional
 * caption/note line) — replaces the old page's two different ad hoc
 * tile treatments (fs-6 fw-bold vs fs-4 fw-medium) with a single
 * consistent one.
 *
 * UX pass (2026-07-23): the previous version sat the icon *beside* the
 * label+value stack in one flex row, so a large icon permanently ate
 * into the value's own width — on a real Naira balance
 * ("₦1,505,650.98") that meant the number wrapped mid-digit onto a
 * second line, which read as broken rather than dense. The fix is
 * structural, not a smaller font: the icon now sits in its own small
 * header row next to the label only (`.uh-dashboard-kpi-head`), and the
 * value is a block-level element with the *entire* card's content width
 * to itself, no icon competing for the space. Icon shrunk 40px -> 28px
 * to match its new, smaller role (a label-row accent, not something
 * sized to match the metric itself). */
.uh-dashboard-kpi-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-primary-bg-subtle);
  /* Brand rollout (#28343D) fix: this chip's own background is
   * --bs-primary-bg-subtle (a translucent wash of the brand color) —
   * --bs-primary-text-emphasis is the token Bootstrap's subtle-color
   * system provides specifically for text/icons that need to read
   * against that background in either theme (light: a shade darker than
   * --bs-primary; dark: a tint lighter), not --bs-primary itself. Using
   * --bs-primary directly here put the icon at almost the same
   * darkness as its own (dark-mode) chip background — effectively
   * invisible — since #28343D is close enough to black that its
   * "subtle" wash and its own solid value stop being distinguishable
   * once both are dark. */
  color: var(--bs-primary-text-emphasis);
  flex-shrink: 0;
}

/* Aspect-ratio pass (2026-07-23): Bootstrap's global `.card .card-body`
 * default (1.25rem/20px on every side) is tuned for content-heavy cards
 * (tables, charts) — on a KPI tile that's just an icon+label+value, that
 * much *vertical* padding on top of the content stack made the card read
 * as nearly square instead of the wide, flat "summary tile" shape
 * enterprise dashboards use (Stripe/Vercel/Linear). Horizontal padding
 * is left at a full 1rem so the card doesn't feel pinched side-to-side —
 * width was explicitly not the problem, only height. Scoped to
 * `.uh-dashboard-kpi`'s own card-body only, so every other card on the
 * page (Clients, Financial Summary, Transaction Volume, ...) keeps the
 * app-wide default padding untouched. */
.uh-dashboard-kpi .card-body {
  padding: .75rem 1rem;
}

/* Bug fix (Enterprise Detail Page pass): the icon-beside-value tile
 * layout transactions/detail.php's headline band uses (icon + a plain
 * wrapping <div> holding label/value, side by side in one flex row) is
 * older than the icon-above-value `.uh-dashboard-kpi-head`/-body restructure
 * dashboard/index.php's own tiles moved to (see that pass's comment above)
 * — transaction-detail.php's tiles were never migrated to it, so they
 * still have this gap: a flex item with no min-width sizes to its
 * content's *unwrapped* intrinsic width first, so `.uh-dashboard-kpi-value`'s
 * own `overflow-wrap: break-word` never gets a chance to apply — a long
 * reference/identifier pushes the tile (and the card around it) wider
 * than its column instead of wrapping, exactly the reported overflow bug.
 * `.uh-dashboard-kpi-content` on that wrapping <div> gives the fix a real
 * class instead of a per-tile inline style, reusable anywhere else this
 * exact tile shape appears (wallets/overview.php's balance tiles use it
 * too). flex: 1 1 auto so it still claims the row's remaining width
 * (matching its previous unstyled-div behavior) once it's allowed to
 * shrink. */
.uh-dashboard-kpi-content {
  min-width: 0;
  flex: 1 1 auto;
}

/* The tile's own flex-column stack: head row, value, optional
 * caption/note — one `gap` controls the rhythm between all of them so
 * no individual line needs its own top/bottom margin. Tightened from
 * .375rem — the label/value/caption relationship reads more like one
 * cohesive metric when the lines sit closer together, rather than each
 * floating with its own breathing room. */
.uh-dashboard-kpi-body {
  display: flex;
  flex-direction: column;
  gap: .25rem;
}

.uh-dashboard-kpi-head {
  display: flex;
  align-items: center;
  gap: .5rem;
}

/* The label's own margin-bottom (below) exists for the one remaining
 * place it's used standalone, stacked directly above a value with no
 * icon (User Overview's "Total Clients" mini-stat) — zeroed here since
 * inside the head row it sits beside the icon instead, and the row's
 * own `gap` already provides the spacing down to the value line. */
.uh-dashboard-kpi-head .uh-dashboard-kpi-label {
  margin-bottom: 0;
}

.uh-dashboard-kpi-label {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--bs-secondary-color);
  margin-bottom: .125rem;
  overflow-wrap: anywhere;
text-transform: capitalize;
}

/* Restored to the full 1.5rem size (a previous pass had dropped this to
 * 1.25rem as a stopgap overflow fix) — now that the value owns the
 * tile's entire width instead of sharing a row with a 40px icon, it no
 * longer needs to give up size to fit; it goes back to being the clear
 * focal point of the tile. `overflow-wrap: break-word` is kept only as
 * a last-resort safety net for a pathological case (a huge number on a
 * very narrow phone viewport) — normal layouts never reach it, since
 * the head-row restructure is what actually prevents wrapping now, not
 * this property. `word-break: break-word` (more aggressive, breaks
 * anywhere) was removed for the same reason it caused the reported
 * mid-digit wrap in the first place. line-height tightened 1.25 -> 1.1
 * (aspect-ratio pass, 2026-07-23): at a bold 1.5rem this is still
 * comfortable — no ascender/descender clipping — but without the extra
 * quarter-line of empty space a body-text line-height convention adds
 * on top of a large display number, which was a real contributor to the
 * card's too-tall, too-square footprint. */
.uh-dashboard-kpi-value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--bs-body-color);
  line-height: 1.1;
  overflow-wrap: break-word;
}

/* Consistency fix (2026-07-24): Wallet Balance is a full Naira amount
 * (typically 12-14 characters, e.g. "₦1,505,650.98") — the longest
 * value anywhere in Executive Overview by a clear margin, and the only
 * one that could still wrap onto a second line at the tightest card
 * width, which made this one card render taller than its 3 siblings
 * even with `h-100` restored (h-100 matches the *tallest* card in the
 * row — if Wallet Balance's own content grows to two lines, it becomes
 * the tallest, and pulls the others down with it). A 10% reduction
 * (1.5rem -> 1.35rem) is barely perceptible sitting next to the other
 * three values at full size, but removes just enough width pressure to
 * keep a full amount on one line in the vast majority of cases.
 * Deliberately scoped to this one id — every other KPI value on the
 * page (Total Clients, Transactions Today, Success Rate, and everything
 * in User/Platform Overview) is untouched and stays at the full
 * 1.5rem. */
#uh-kpi-wallet-total {
  font-size: 1.35rem;
}

/* A third microcopy line under a *real* (non-placeholder) KPI value —
 * Platform Overview's Active Services/Total Providers use this to
 * explain what the number means, which also happens to give them the
 * same 3-line height as their placeholder siblings in the same row
 * (uh-dashboard-kpi--placeholder below), so a real tile and a
 * not-yet-available tile never sit at visibly different heights next to
 * each other. Not italic, unlike the placeholder note — italics are
 * reserved for signaling "this isn't real data yet". Matches
 * .uh-dashboard-placeholder-note (both .75rem, Bootstrap's fs-8 step) —
 * one shared "fine print" size for both. line-height set explicitly
 * (aspect-ratio pass, 2026-07-23) — this line previously inherited the
 * body's own 1.5 line-height, noticeably loose for a single small
 * caption line and, compounded with the tile's other spacing, part of
 * why 3-line tiles read as too tall. */
.uh-dashboard-kpi-caption {
  font-size: .75rem;
  line-height: 1.3;
  color: var(--bs-secondary-color);
  overflow-wrap: anywhere;
}

/* Placeholder tiles (Staff, Revenue, API Environment, Redis Status,
 * Queue Status — no backend field exists for any of these yet) get a
 * visibly muted icon/value plus a small italic note, so they read as
 * *intentionally* empty rather than broken. */
.uh-dashboard-kpi--placeholder .uh-dashboard-kpi-icon {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
}

/* System Status page redesign: the overall-status hero banner's icon
 * chip recolors to match the real platform_status value (SystemHealthService::
 * check()'s 'healthy'/'degraded' vocabulary) — same override shape as
 * `--placeholder` above (background + icon color only), just semantic
 * success/warning instead of muted, and both already theme-aware via
 * Bootstrap's own --bs-success/--bs-warning subtle-bg custom properties. */
.uh-dashboard-kpi-icon--success {
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success);
}

.uh-dashboard-kpi-icon--warning {
  background-color: var(--bs-warning-bg-subtle);
  color: var(--bs-warning);
}

/* Dashboard enterprise redesign pass: Primary Metrics' Failed This Month
 * tile — completes the --success/--warning family above with the one
 * semantic tint this page's own KPI tiles didn't have a use for until
 * now (Success Rate picked up --success the same pass). Same
 * theme-aware Bootstrap subtle-bg pairing as its siblings. */
.uh-dashboard-kpi-icon--danger {
  background-color: var(--bs-danger-bg-subtle);
  color: var(--bs-danger);
}

.uh-dashboard-kpi--placeholder .uh-dashboard-kpi-value {
  color: var(--bs-secondary-color);
}

.uh-dashboard-placeholder-note {
  font-size: .75rem;
  line-height: 1.3;
  color: var(--bs-secondary-color);
  font-style: italic;
  overflow-wrap: anywhere;
}

/* A plain 1px rule separating a placeholder tile from the stat strip
 * below it in Financial Overview's combined card — var(--bs-border-color)
 * so it inherits both themes for free, same as every other rule here. */
.uh-dashboard-divider {
  border-top: 1px solid var(--bs-border-color);
}

/* Dashboard UI refinement pass — Account Overview identity strip
 * (dashboard/index.php's own top comment). Replaces uh_detail_list()'s
 * bordered label/value rows: separation between chips now comes purely
 * from gap/whitespace, not divider lines, per the "use whitespace, not
 * heavy borders" principle this pass follows. Reuses
 * .uh-dashboard-kpi-icon verbatim for each chip's icon (already
 * theme-aware, already the established "small icon chip" language on
 * this page) rather than a second icon treatment invented for this one
 * card — the only new classes are the flex layout and the value's own
 * (smaller, since these are identifiers/badges, not headline numbers)
 * typography. */
.uh-dashboard-identity {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 1.5rem;
}

.uh-dashboard-identity-item {
  display: flex;
  align-items: center;
  gap: .625rem;
  min-width: 0;
}

/* Premium one-line stat-strip pass: on a genuinely large PC screen, all
 * five Account Overview items sit on one row, evenly spaced with a
 * subtle vertical divider between them (the same "stat bar" treatment
 * enterprise dashboards commonly use for a short row of at-a-glance
 * facts) instead of wrapping. `flex: 1 1 0` + `min-width: 0` means a
 * narrow item's label/value text wraps onto a second line rather than
 * forcing horizontal overflow — this can never scroll, only get more
 * compact.
 *
 * Deliberately no justify-content override here: centering the row
 * read fine with the sidebar expanded, but collapsing it (dev-ui's own
 * sidebar-collapse toggle, core/config.js) widens the workspace without
 * changing item count, so a centered row visibly drifted off the left
 * edge instead of tracking it. Left-aligned (the flex default) tracks
 * the available width naturally at every sidebar state instead.
 *
 * 1536px, not xl (1200px): the workspace's left panel (7fr of
 * .uh-dashboard-workspace's 7fr/3fr split) is real-browser-measured, not
 * guessed — at 1200-1440px (common laptop widths) five items' own
 * min-content (icon + a non-wrapping Bootstrap .badge) genuinely doesn't
 * fit even at zero spacing; forcing nowrap there overflowed the card by
 * 10-45px in testing. 1536px+ (a 16" MacBook Pro's own scaled
 * resolution, and every wider desktop monitor above it) is the first
 * width that measured zero overflow. Below this breakpoint the plain
 * flex-wrap above still applies unchanged (full-width single-column
 * workspace at <lg, a wrapped multi-row strip at lg/xl/1536), so
 * smaller/tablet layouts and mid-size laptops are untouched. */
@media (min-width: 1536px) {
  .uh-dashboard-identity {
    flex-wrap: nowrap;
    /* Spacing between items now comes entirely from each item's own
     * padding below, not this gap too — the two stacking was what
     * pushed 5 items past the available width in earlier testing. */
    gap: 0;
  }

  .uh-dashboard-identity-item {
    flex: 1 1 0;
    min-width: 0;
    gap: .5rem;
    padding: 0 .5rem;
    border-right: 1px solid var(--bs-border-color);
  }

  .uh-dashboard-identity-item:first-child {
    padding-left: 0;
  }

  .uh-dashboard-identity-item:last-child {
    padding-right: 0;
    border-right: none;
  }
}

.uh-dashboard-identity-text {
  display: flex;
  flex-direction: column;
  gap: .125rem;
  min-width: 0;
}

.uh-dashboard-identity-text .uh-dashboard-kpi-label {
  margin-bottom: 0;
}

.uh-dashboard-identity-value {
  font-size: .875rem;
  font-weight: 600;
  color: var(--bs-body-color);
  overflow-wrap: anywhere;
}

/* Quick Actions row (enterprise redesign pass — dashboard/index.php's
 * own comment on that section): replaces the previous centered
 * icon-above-label box grid (`.uh-dashboard-quickaction-tile`, removed)
 * with a horizontal list of full-width action rows — icon chip
 * (`.uh-dashboard-kpi-icon`, reused verbatim, not a second chip shape),
 * label + one-line description, trailing chevron. `flex: 1 1 240px` on
 * each item is deliberate over a fixed Bootstrap grid column: items
 * fill the row evenly and reflow to 2-, then 1-per-row as the panel
 * narrows, entirely from content width rather than a breakpoint table —
 * one fewer thing to keep in sync with this page's own panel widths,
 * which already differ from a plain full-page column at lg+ (see
 * `.uh-dashboard-workspace` below). */
.uh-dashboard-quickaction-list {
  display: flex;
  flex-wrap: wrap;
  gap: .625rem;
}

.uh-dashboard-quickaction-item {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex: 1 1 240px;
  padding: .75rem .875rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  text-decoration: none;
  color: var(--bs-body-color);
  transition: background-color .15s ease-in-out, border-color .15s ease-in-out, transform .15s ease-in-out;
}

/* No box-shadow on hover here, unlike `.uh-dashboard-section .card:hover`
 * below — that rule needs its own `[data-bs-theme="dark"]` override
 * because `--bs-shadow-color` flips to a light RGB triplet and reads as
 * a halo on a dark surface (see that rule's own comment). A plain
 * background tint + border + 1px lift already reads as "interactive" in
 * both themes with no shadow at all, so this sidesteps that whole
 * problem rather than needing a second dark-theme override for it. */
.uh-dashboard-quickaction-item:hover,
.uh-dashboard-quickaction-item:focus-visible {
  background-color: var(--bs-primary-bg-subtle);
  border-color: var(--bs-primary);
  color: var(--bs-body-color);
  transform: translateY(-1px);
}

.uh-dashboard-quickaction-item:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}

.uh-dashboard-quickaction-icon {
  flex-shrink: 0;
}

.uh-dashboard-quickaction-text {
  display: flex;
  flex-direction: column;
  gap: .0625rem;
  min-width: 0;
  flex: 1 1 auto;
}

.uh-dashboard-quickaction-label {
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1.2;
}

.uh-dashboard-quickaction-desc {
  font-size: .75rem;
  color: var(--bs-secondary-color);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Hidden until hover/focus, then slides in from the left — the same
 * "this row reveals its own affordance on interaction" convention
 * Recent Activity's `.table-hover` and Notifications' link-hover tint
 * already use elsewhere on this page, applied here with a directional
 * cue since this row's whole purpose is navigation. */
.uh-dashboard-quickaction-arrow {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--bs-secondary-color);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity .15s ease-in-out, transform .15s ease-in-out, color .15s ease-in-out;
}

.uh-dashboard-quickaction-item:hover .uh-dashboard-quickaction-arrow,
.uh-dashboard-quickaction-item:focus-visible .uh-dashboard-quickaction-arrow {
  opacity: 1;
  transform: translateX(0);
  color: var(--bs-primary-text-emphasis);
}

@media (prefers-reduced-motion: reduce) {
  .uh-dashboard-quickaction-item,
  .uh-dashboard-quickaction-arrow {
    transition: none;
  }
}

/* API Health (final-polish pass): a status-page-style dot + label +
 * status text row, replacing a badge-pill-per-service treatment — see
 * assets/js/pages/dashboard.js's populateApiHealth() own comment. Rows
 * separate with a hairline + whitespace rather than each sitting in its
 * own bordered box, per this pass's "whitespace over heavy borders"
 * direction; `:not(:first-child)` rather than a sibling-combinator +
 * first-child pair since JS rebuilds this list wholesale on every load
 * (no risk of a stale border sticking to a removed first row).
 *
 * Enterprise polish pass: shared verbatim with dev-ui/support/status.php
 * (support-status.js's own comment on this same class family), so every
 * change below refines both pages, not just the Dashboard. Horizontal
 * padding + a hover tint added — these rows aren't a click target (no
 * per-service page to navigate to), so this stops at a plain background
 * acknowledgment, not the bordered-lift treatment Notifications/Quick
 * Actions use for their own *actually* clickable rows (same restraint
 * `.uh-dashboard-section .card:hover`'s own comment already argues for
 * a non-actionable surface). */
.uh-dashboard-health-row {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .625rem .5rem;
  border-radius: var(--bs-border-radius);
  transition: background-color .15s ease-in-out;
}

.uh-dashboard-health-row:hover {
  background-color: var(--bs-secondary-bg);
}

@media (prefers-reduced-motion: reduce) {
  .uh-dashboard-health-row {
    transition: none;
  }
}

.uh-dashboard-health-row:not(:first-child) {
  border-top: 1px solid var(--bs-border-color);
}

/* A soft halo ring around the status dot — the same "premium depth" cue
 * the Transactions KPI row's own icon rings use (above,
 * .uh-dashboard-txn-kpi .uh-dashboard-kpi-icon), scaled down for an 8px
 * dot instead of a 21px icon chip. */
.uh-dashboard-health-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.uh-dashboard-health-indicator--up {
  background-color: var(--bs-success);
  box-shadow: 0 0 0 3px rgba(var(--bs-success-rgb), .15);
}

.uh-dashboard-health-indicator--down {
  background-color: var(--bs-danger);
  box-shadow: 0 0 0 3px rgba(var(--bs-danger-rgb), .15);
}

.uh-dashboard-health-label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: .8125rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

/* Loading skeleton — dashboard/index.php's own initial markup inside
 * #uh-dashboard-health-list, each shaped to match a real row's own
 * dot + label + badge so the swap to real content doesn't jump. */
.uh-dashboard-health-skeleton-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.uh-dashboard-health-skeleton-label {
  flex: 1 1 auto;
  height: .8125rem;
  border-radius: .25rem;
}

.uh-dashboard-health-skeleton-badge {
  width: 4.5rem;
  height: 1.125rem;
  border-radius: 999px;
  flex-shrink: 0;
}

/* Dashboard Refresh control, enterprise polish pass — dashboard/index.php's
 * own comment on this markup covers the reasoning. The "live" dot reuses
 * API Health's own halo-dot recipe verbatim (same success color + soft
 * ring) — one consistent "status indicator" language across the page,
 * not a second one invented for this control. */
.uh-dashboard-refresh-status {
  display: inline-flex;
  align-items: center;
  gap: .4375rem;
}

.uh-dashboard-refresh-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: var(--bs-success);
  box-shadow: 0 0 0 3px rgba(var(--bs-success-rgb), .15);
  flex-shrink: 0;
}

/* A slight press-down on click (Linear/Vercel's own tactile button
 * convention) on top of whatever hover/focus treatment
 * .btn-outline-light already provides everywhere else in this app —
 * refines, doesn't replace, the existing button styling. */
.uh-dashboard-refresh-btn {
  display: inline-flex;
  align-items: center;
  transition: transform .1s ease-in-out;
}

.uh-dashboard-refresh-btn:active {
  transform: scale(.96);
}

/* Loading state: the existing refresh icon spins in place (rather than
 * swapping the button's content for a separate spinner element) while
 * assets/js/pages/dashboard.js's loadDashboard() has a request in
 * flight — the same recognizable "refresh in progress" cue GitHub/
 * Vercel/Linear all use. `disabled` (set by the same JS) already gets
 * Bootstrap's own dimmed/no-pointer-events treatment for free. */
.uh-dashboard-refresh-btn svg {
  transition: transform .15s ease-in-out;
}

.uh-dashboard-refresh-btn.uh-btn-loading {
  cursor: progress;
}

.uh-dashboard-refresh-btn.uh-btn-loading svg {
  animation: uh-icon-spin .7s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .uh-dashboard-refresh-btn,
  .uh-dashboard-refresh-btn svg {
    transition: none;
  }

  .uh-dashboard-refresh-btn.uh-btn-loading svg {
    animation: none;
  }
}

.uh-dashboard-health-status {
  font-size: .75rem;
  font-weight: 600;
}

.uh-dashboard-health-status--up {
  color: var(--bs-success);
}

.uh-dashboard-health-status--down {
  color: var(--bs-danger);
}

/* Notifications, enterprise polish pass — dashboard.js's own comment on
 * populateNotifications() covers the full reasoning. Rows are now an
 * icon + text-stack flex row (the same shape Quick Actions/Account
 * Overview already use for a "clickable row with a leading icon chip"),
 * a transparent-by-default border that only turns visible on hover (so
 * rows read as a plain list at rest, not a stack of boxes, but still
 * get real depth when interactive), and the exact hover recipe Quick
 * Actions already established below (border-color + bg tint + a 1px
 * lift) — one consistent "clickable row" feel across this whole page,
 * not two different hover languages. */
.uh-dashboard-notification-list {
  display: flex;
  flex-direction: column;
  gap: .375rem;
}

.uh-dashboard-notification-item {
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  padding: .5rem .625rem;
  border: 1px solid transparent;
  border-radius: var(--bs-border-radius);
  color: inherit;
  text-decoration: none;
  transition: background-color .15s ease-in-out, border-color .15s ease-in-out, transform .15s ease-in-out;
}

.uh-dashboard-notification-item:hover,
.uh-dashboard-notification-item:focus-visible {
  background-color: var(--bs-primary-bg-subtle);
  border-color: var(--bs-primary);
  color: inherit;
  transform: translateY(-1px);
}

.uh-dashboard-notification-item:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .uh-dashboard-notification-item {
    transition: none;
  }
}

.uh-dashboard-notification-item-icon {
  flex-shrink: 0;
}

.uh-dashboard-notification-item-body {
  flex: 1 1 auto;
  min-width: 0;
}

.uh-dashboard-notification-item-title {
  display: inline-flex;
  /* flex-start, not center: a long title wraps onto several lines inside
   * this flex container, and `align-items: center` was centering the dot
   * against that *entire* multi-line block — visually stranding it next
   * to a middle line instead of the title's own first word. flex-start
   * keeps it pinned to the top, beside line one, regardless of how many
   * lines the title wraps to. */
  align-items: flex-start;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--bs-body-color);
  overflow-wrap: anywhere;
}

/* Unread indicator, refined: a small dot + bold title instead of the
 * previous always-on tinted background + left border — "elegant, not
 * heavy," the same convention Linear/Superhuman use. The background
 * tint isn't gone, just much fainter than before (a hand-picked low
 * opacity, not --bs-primary-bg-subtle's own stronger value, which is
 * still reserved for the hover state above so hover still reads as a
 * clearly stronger step up from an unread row's own resting state). */
.uh-dashboard-notification-item--unread {
  background-color: rgba(var(--bs-primary-rgb), .05);
}

.uh-dashboard-notification-item--unread .uh-dashboard-notification-item-title {
  font-weight: 700;
}

.uh-dashboard-notification-item-dot {
  width: 6px;
  height: 6px;
  /* Nudges the dot down from the pure flex-start (text top) to sit level
   * with the first line's own x-height instead of its ascender line. */
  margin-top: .375rem;
  margin-right: .375rem;
  border-radius: 50%;
  background-color: var(--bs-primary);
  flex-shrink: 0;
}

.uh-dashboard-notification-item-date {
  flex-shrink: 0;
  font-size: .75rem;
  color: var(--bs-secondary-color);
}

.uh-dashboard-notification-item-message {
  margin: .125rem 0 0;
  font-size: .75rem;
  color: var(--bs-secondary-color);
  overflow-wrap: anywhere;
}

/* Loading skeleton — dashboard/index.php's own initial markup inside
 * #uh-dashboard-notifications-list, sized to roughly match a real row's
 * height so the swap to real content (or the empty state) doesn't jump.
 * populateNotifications() always replaces this container's entire
 * innerHTML on its first call (success or empty), the same mechanism
 * that already clears every other skeleton in this app — no JS change
 * needed to wire this up. */
.uh-dashboard-notification-skeleton-row {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .5rem .625rem;
}

.uh-dashboard-notification-skeleton-icon {
  width: 28px;
  height: 28px;
  border-radius: var(--bs-border-radius);
  flex-shrink: 0;
}

.uh-dashboard-notification-skeleton-lines {
  flex: 1 1 auto;
  min-width: 0;
}

/* Wallet Balance hero (final-polish pass) — see dashboard/index.php's own
 * comment on this card. A larger, solid-filled icon chip (vs. the
 * app-wide subtle-tint .uh-dashboard-kpi-icon) and a larger value size,
 * both scoped to this one card only. */
.uh-dashboard-kpi-icon--lg {
  width: 36px;
  height: 36px;
  background-color: var(--bs-primary);
  color: #fff;
}

/* Wallet Balance hero, enterprise redesign pass (Stripe/Mercury/Brex-
 * style balance card) — dashboard/index.php's own comment on this card
 * covers the full reasoning. `.uh-dashboard-section .card`'s own
 * hover-elevation/shadow rule (above) already applies here for free
 * (this card is a plain `.card` inside a `.uh-dashboard-section`,
 * nothing new needed for that layer); everything below is the card's
 * *internal* composition only. */
.uh-dashboard-wallet-hero {
  position: relative;
  overflow: hidden;
}

/* Same faint radial-gradient-wash idiom `.uh-auth-hero-glow` already
 * established for the login page (rgba(var(--bs-primary-rgb), .0x),
 * absolutely positioned, non-interactive) — reused at a lower opacity
 * suited to a small card rather than a full page panel, anchored to the
 * top-right corner (behind the balance figure) instead of centered. */
.uh-dashboard-wallet-hero-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: radial-gradient(circle at 100% 0%, rgba(var(--bs-primary-rgb), .08), transparent 55%);
  pointer-events: none;
}

.uh-dashboard-wallet-hero .card-body {
  position: relative;
  z-index: 1;
}

/* Tabular figures + a touch of negative tracking is a deliberate, small
 * "considered numeric typography" detail real fintech balance displays
 * use (Stripe/Mercury) — digits align to a fixed width (no jitter as the
 * value updates) and read slightly denser at this large a size. */
.uh-dashboard-wallet-hero .uh-dashboard-kpi-value {
  font-size: 1.25rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Skeleton sizing for the balance figure — Bootstrap's own .placeholder
 * (vendors/main.css) has no intrinsic width, so this just gives it one
 * roughly matching a real Naira amount at the value's own 2.25rem size.
 * Lives *inside* #uh-kpi-wallet-balance as its initial content; the
 * existing, unmodified setText() call replaces it via .textContent the
 * instant real data arrives (dashboard/index.php's own comment). */
.uh-dashboard-wallet-skeleton {
  display: block;
  width: 9rem;
  height: 1.9rem;
  border-radius: .375rem;
}

.uh-dashboard-wallet-skeleton--sm {
  width: 4.5rem;
  height: 1rem;
  border-radius: .25rem;
}

/* Total Credits/Debits KPI chips — replaces the old plain badge-status
 * row. Neutral chip background (the icon alone carries the success/
 * danger semantic, same recipe Success Rate/Failed This Month already
 * use below) so the two chips read as one consistent family rather than
 * introducing a second color language on top of the icon tint. */
.uh-dashboard-wallet-stat {
  display: flex;
  align-items: center;
  gap: .625rem;
  height: 100%;
  padding: .625rem .75rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

.uh-dashboard-wallet-stat-text {
  display: flex;
  flex-direction: column;
  gap: .125rem;
  min-width: 0;
}

.uh-dashboard-wallet-stat-value {
  display: block;
  font-size: .9375rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--bs-body-color);
  overflow-wrap: break-word;
}

/* Micro-interaction: a brief reveal pulse on a numeric KPI value whenever
 * its populate*() function runs (assets/js/pages/dashboard.js —
 * populateWallet() and populateTransactions() both call the shared
 * pulseValue() helper) — first load doubles as the skeleton-to-real-value
 * reveal, a manual Refresh doubles as a quiet "this just updated" cue.
 * Same prefers-reduced-motion guard .uh-fade-in already established
 * above, same restrained duration/easing family. Domain-neutral name
 * (not wallet-specific) since both widgets share it. */
@keyframes uh-value-pulse {
  from { opacity: .35; }
  to { opacity: 1; }
}

.uh-value-pulse {
  animation: uh-value-pulse .35s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .uh-value-pulse {
    animation: none;
  }
}

/* Generic KPI-value skeleton — same recipe as the Wallet hero's own
 * .uh-dashboard-wallet-skeleton above, generalized under a domain-neutral
 * name (not left wallet-only) since the Transactions KPI row below reuses
 * it too, and any future KPI tile can adopt it instead of a third
 * near-identical class. Sized for the shared 1.5rem .uh-dashboard-kpi-value
 * scale every plain KPI tile app-wide renders at (the Wallet hero's own
 * value is deliberately larger — 2.25rem — hence its own, separately
 * sized skeleton). */
.uh-dashboard-kpi-value-skeleton {
  display: block;
  width: 3.5rem;
  height: 1.6rem;
  border-radius: .375rem;
}

/* Balance/financial-summary consistency audit: a standalone, reusable
 * version of the "considered numeric typography" treatment first
 * established on the Wallet hero's balance figure and then re-scoped a
 * second time for the Transactions KPI row's own values
 * (.uh-dashboard-wallet-hero .uh-dashboard-kpi-value / .uh-dashboard-txn-kpi
 * .uh-dashboard-kpi-value, both above — neither touched here, so neither
 * page's already-shipped rendering changes). Extracted as its own class,
 * applied directly on a value element, so any other monetary/numeric KPI
 * app-wide (wallet/statements.php's Opening/Closing Balance and Period
 * Credits/Debits below) can opt into the same treatment without a third
 * near-identical parent-scoped rule. */
.uh-kpi-value-tabular {
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Transactions KPI row, enterprise redesign pass — matches the Wallet
 * hero's level of polish. Scoped to .uh-dashboard-txn-kpi (dashboard/
 * index.php's own comment on that class explains why: NOT the bare
 * .uh-dashboard-kpi/-icon/-value classes, which are shared app-wide and
 * must stay untouched) so none of this can ripple onto any *other* KPI
 * tile shape in the app. `.uh-dashboard-section .card`'s own hover-
 * elevation (above) already applies here for free, same as the Wallet
 * hero — nothing new needed for that layer either.
 *
 * Statistic-card polish pass: analytics/index.php's own "API Usage" row
 * now opts into this exact same class/markup shape (its own comment
 * covers why) — this recipe is the shared "premium KPI tile" language
 * for this whole reporting domain (transaction counts/rates), not a
 * dashboard-only one-off; any future page rendering the same kind of
 * tile should reuse it rather than re-deriving a near-identical rule. */

/* Low-profile revision (follow-up pass): direct feedback on the first
 * cut said these still read as tall, square Bootstrap cards, and that
 * the top accent bar/gradient made the row look busy. This replaces that
 * approach entirely rather than layering on top of it:
 *  - The accent-bar pseudo-element (::before) is gone — no colored
 *    gradient decoration anywhere on these cards anymore.
 *  - The icon moves out of its own header row and becomes a direct flex
 *    child of .uh-dashboard-kpi-body, beside a single text-stack column
 *    (label/value/caption) instead of above it — one flex ROW (icon +
 *    text) rather than three stacked rows (head-row, value, caption).
 *    This is the actual mechanism that shortens the card: one row of
 *    height max(icon, text-stack) instead of icon-row + value-row +
 *    caption-row each adding their own height. dashboard/index.php's own
 *    comment on this markup covers the structural change; every id and
 *    dashboard.js binding is untouched — only which element wraps which
 *    changed. The asymmetric corner radius stays (still the "tapered,
 *    not a square" cue the design direction asks for), tuned slightly
 *    crisper now that it's carrying that job alone. */
.uh-dashboard-txn-kpi {
  border-radius: .625rem .15rem .625rem .625rem;
}

/* The structural flip: was flex-column (icon+label header row, then
 * value, then caption, each a separate stacked row) — now flex-row, icon
 * as the first child, .uh-dashboard-txn-kpi-text (label+value+caption)
 * as the second. `.uh-dashboard-kpi-body`'s own column direction (base
 * rule, shared app-wide) is what every OTHER KPI tile in this app still
 * uses; this override only ever applies within `.uh-dashboard-txn-kpi`. */
.uh-dashboard-txn-kpi .uh-dashboard-kpi-body {
  flex-direction: row;
  align-items: center;
  gap: .5rem;
}

/* New text-stack wrapper (label/value/caption) — replaces the old
 * .uh-dashboard-kpi-head (icon+label only; no longer used by this
 * markup, its CSS rule is untouched for the pages that still do) and
 * absorbs the caption into the same tight column instead of leaving it
 * as a third sibling of .uh-dashboard-kpi-body. min-width: 0 lets a long
 * label/caption wrap inside this column rather than pushing the card
 * wider than its grid column. */
.uh-dashboard-txn-kpi-text {
  display: flex;
  flex-direction: column;
  gap: .0625rem;
  min-width: 0;
}

/* The label no longer sits inside .uh-dashboard-kpi-head (whose own
 * margin-bottom: 0 override doesn't reach it anymore) — re-zeroed here
 * so .uh-dashboard-txn-kpi-text's own gap is the only spacing at work,
 * not gap plus the base .uh-dashboard-kpi-label's own margin-bottom
 * stacking on top of it. */
.uh-dashboard-txn-kpi-text .uh-dashboard-kpi-label {
  margin-bottom: 0;
}

/* Compactness pass: tighter padding than the shared `.uh-dashboard-kpi
 * .card-body` default above (wins by source order — same 0,2,0
 * specificity, this rule is declared later) — "fitted," not padded out
 * like a generic Bootstrap card. */
.uh-dashboard-txn-kpi .card-body {
  padding: .55rem .75rem;
}

/* Icon shrunk again (25px -> 21px) to match the now-compact text stack
 * beside it, keeping the same asymmetric-corner echo of the card's own
 * shape and the same semantic halo ring (scaled down to match), just
 * smaller across the board. */
.uh-dashboard-txn-kpi .uh-dashboard-kpi-icon {
  width: 21px;
  height: 21px;
  border-radius: .4rem .15rem .4rem .4rem;
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), .07);
}

.uh-dashboard-txn-kpi .uh-dashboard-kpi-icon--success {
  box-shadow: 0 0 0 3px rgba(var(--bs-success-rgb), .08);
}

.uh-dashboard-txn-kpi .uh-dashboard-kpi-icon--danger {
  box-shadow: 0 0 0 3px rgba(var(--bs-danger-rgb), .08);
}

/* Value scaled down from the shared 1.5rem KPI scale — on a low, wide
 * card beside a small icon rather than a full-width hero figure, 1.5rem
 * read oversized relative to everything else in the tile. Still clearly
 * the largest, boldest text in the tile (vs. the label's .75rem/danger
 * .75rem caption), so the focal-point hierarchy holds. Tabular-nums +
 * tight tracking carried over from the previous pass. */
.uh-dashboard-txn-kpi .uh-dashboard-kpi-value {
  font-size: 1.1875rem;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Charts are supporting detail in the redesigned layout, not the
 * card's main subject (their JS chart:{height:...} config was already
 * reduced to match — see dashboard.js's populateClientChart()/
 * populateMonthlyChart() comments) — this just stops either chart's
 * container from claiming more width than its card intends on very wide
 * viewports. */
.uh-dashboard-chart-support {
  max-width: 100%;
}

/* Layout refactor (2026-07-23) — boxed two-panel workspace. Purely
 * structural: no widget markup, color, typography or spacing scale
 * changed, only the container the existing sections now sit inside (see
 * dashboard/index.php's matching comment). One bordered, rounded frame
 * (the "workspace") holds two independently-scrolling panels instead of
 * the page itself being one long scroll — the Stripe/Linear/Vercel
 * "boxed application view" feel asked for. Mobile-first: a single
 * stacked column (both panels full width, workspace height auto, page
 * scrolls normally) is the base rule; the side-by-side split with fixed,
 * independently-scrolling panels only turns on at lg+, matching every
 * other lg breakpoint already used across this page's own grid (row
 * g-3 col-lg-*). */
/* Canvas-contrast pass (2026-07-27): the comment above already describes
 * this as a "bordered, rounded frame", but border was actually `none` — so
 * the frame had no visible presence at all beyond the panel-divider line
 * between its two columns. A real hairline border is what actually
 * delivers the "boxed application view" the surrounding comments describe.
 * `overflow: hidden` clips the two panels' background to the frame's own
 * rounded corners; it doesn't affect `.uh-dashboard-panel`'s independent
 * `overflow-y: auto` scrolling below, since that's the child's own
 * overflow context, not this element's.
 *
 * White-surface revision (2026-07-27, follow-up): an earlier version of
 * this pass filled the frame with a visible tint so the white cards
 * inside would read as elevated above it — reverted in light theme (see
 * the `[data-bs-theme="light"]` override directly below) per explicit
 * direction that every card must sit on a true white surface with zero
 * background tint, separation coming only from the border + each card's
 * own shadow (`.uh-dashboard-section .card` below, strengthened slightly
 * to carry that depth cue on its own).
 *
 * Two-level consolidation (2026-07-27, second follow-up): this is a
 * layout container, not a raised content surface, so in dark theme it
 * now reads `--bs-body-bg` directly (Level 1 — the same app-background
 * token as `body`, the sidebar, the navbar and the footer) instead of
 * `--bs-tertiary-bg`, which was repurposed to a Level-2 hover/utility
 * shade in the root dark-theme block and is no longer close to Level 1.
 * The border above still gives the frame its own visible edge; the cards
 * inside (Level 2, `--hk-bg-primary`) are what actually needs to read as
 * elevated, not this container itself. */
.uh-dashboard-workspace {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border: 1px solid var(--bs-border-color);
  border-radius: .75rem;
  background-color: var(--bs-body-bg);
  overflow: hidden;
}

/* Light theme only: pure white, matching every card's own background
 * (--hk-bg-primary) exactly, so the frame itself carries no tint at
 * all — separation comes entirely from the border above and the cards'
 * own shadows, not from a background color. */
[data-bs-theme="light"] .uh-dashboard-workspace {
  background-color: var(--hk-bg-primary);
}

.uh-dashboard-panel {
  min-width: 0;
  padding: 1.5rem;

}

/* dashboard.js's no-permission fallback (contentEl.innerHTML = ...)
 * replaces the two panels below this container with one plain card,
 * bypassing both .uh-dashboard-panel elements entirely — without this,
 * that card would only fill the grid's first column at lg+, leaving a
 * blank gap where the right panel used to be. Spans the full workspace
 * and picks up the same padding a panel would have had. */
.uh-dashboard-workspace > :not(.uh-dashboard-panel) {
  grid-column: 1 / -1;
  padding: 1.5rem;
}

.uh-dashboard-panel-left {
  border-bottom: 1px solid var(--bs-border-color);
}

@media (min-width: 992px) {
  /* 7fr/3fr reads as the requested ~65-70% / ~30-35% split while still
   * letting each panel's own width be a simple fraction of the
   * workspace rather than a hardcoded percentage. */
  .uh-dashboard-workspace {
    grid-template-columns: 7fr 3fr;
    /* Re-measured for the enterprise redesign pass (dashboard/index.php's
     * own top comment, item 4): Quick Actions' new horizontal-row layout
     * has a different height than the old tile grid it replaced, so the
     * previous 17.5rem (itself a hand-itemized estimate, not a
     * measurement) was stale the moment that markup changed. Rather than
     * re-itemize by hand again — the exact failure mode that made the
     * previous value drift — this was measured directly:
     * `.uh-dashboard-workspace`'s own `getBoundingClientRect().top` in a
     * real authenticated render (Firefox, 1440px viewport) reads
     * 344.6px, i.e. 21.5rem at the app's 16px root. If this ever looks
     * short/tall in a real browser again, re-measure the same way
     * (`document.querySelector('.uh-dashboard-workspace')
     * .getBoundingClientRect().top` in devtools) rather than guessing —
     * this value is exactly that number, not a padding/margin sum that
     * can silently drift out of sync with a future markup change. */
    height: calc(100vh - 21.5rem);
  }

  .uh-dashboard-panel-left {
    border-bottom: none;
    border-right: 1px solid var(--bs-border-color);
  }

  .uh-dashboard-panel {
    height: 100%;
    overflow-y: auto;
    
  }
}

/* Sidebar footer nav (Enterprise UX pass) — layouts/sidebar.php's
 * .hk-menu now has a third flex child after .nicescroll-bar (System API
 * Management / Audit Logs / Profile), pinned to the bottom by the same
 * flex-column layout vendors/main.css already gives .hk-menu itself; this
 * rule only adds the visual separator the vendor bundle has no opinion
 * on. Written against the same attribute-qualified selector vendors/
 * main.css uses for .menu-content-wrap so specificity is equal-or-higher
 * regardless of cascade order (this file loads after the vendor bundle,
 * but that shouldn't be the only reason it wins). */
.hk-wrapper[data-layout=vertical] .hk-menu .uh-sidebar-footer {
  padding-top: .75rem;
  padding-bottom: .75rem;
  border-top: 1px solid var(--hk-border-primary);
  flex: 0 0 auto;
}

/* Profile row: same .nav-link the two links above it use (so collapsed
 * mode's existing `>*:not(.nav-icon-wrap){visibility:hidden}` rule hides
 * this row's text/chevron for free, exactly like every other nav item),
 * plus a two-line name/role stack in place of a single label. No `gap`
 * here (a previous pass added one) — the two link rows above get their
 * icon-to-text spacing entirely from .nav-icon-wrap's own vendor
 * `margin-right: .875rem`; adding a second spacing source only on this
 * row pushed its text ~8px further right than the other two, one of the
 * two causes of the reported misalignment (see .uh-sidebar-footer
 * .nav-icon-wrap below for the other). The chevron needs no gap either —
 * `margin-left: auto` on .uh-sidebar-profile-chevron already pushes it
 * to the row's end on its own. */

/* Root cause of the misalignment: vendors/main.css's .nav-icon-wrap has
 * no fixed size — `display: flex` with no width/height, so its rendered
 * box is exactly as wide as whatever it wraps. System API Management and
 * Audit Logs wrap a 20x20 uh_icon() SVG, so their wrap is 20px wide; the
 * Profile row instead wraps a 32x32 .avatar-xs (vendors/main.css's own
 * `.avatar.avatar-xs{width:2rem;height:2rem}`), so *its* wrap was 32px
 * wide — 12px more, shifting the label after it and making the avatar
 * itself look off-axis. Fixing the wrapper to the same fixed 20x20 slot
 * every icon in this footer already renders at (rather than special-
 * casing the profile row with a margin/offset) is the actual fix; the
 * avatar is then explicitly sized to fill that same slot below, instead
 * of overflowing or shrinking unpredictably as a flex child would. */
.uh-sidebar-footer .nav-icon-wrap {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
}

.uh-sidebar-footer .nav-icon-wrap .avatar {
  width: 1.25rem;
  height: 1.25rem;
}

.uh-sidebar-footer .nav-icon-wrap .avatar .initial-wrap {
  font-size: .5rem;
}

.uh-sidebar-profile-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  white-space: normal;
  line-height: 1.2;
}

.uh-sidebar-profile-name {
  font-size: .875rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.uh-sidebar-profile-role {
  font-size: .75rem;
  color: var(--bs-secondary-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.uh-sidebar-profile-role:empty {
  display: none;
}

.uh-sidebar-profile-chevron {
  margin-left: auto;
  flex-shrink: 0;
  color: var(--bs-secondary-color);
  display: flex;
  transition: transform .15s ease-in-out;
}

/* Dropup + Bootstrap's own aria-expanded toggle (no new JS): the chevron
 * points up while closed (hinting the menu opens upward, this row being
 * the last thing in the sidebar) and flips to point at the row it
 * belongs to once open. */
.uh-sidebar-profile-item .nav-link[aria-expanded="true"] .uh-sidebar-profile-chevron {
  transform: rotate(180deg);
}

.uh-sidebar-account-dropdown {
  min-width: 240px;
}

/* Enterprise Detail Page pass — shared across every profile/detail page
 * (clients/profile.php, transactions/detail.php, staff/detail.php,
 * providers/detail.php, products/detail.php, wallets/overview.php,
 * staff/role-detail.php), unlike the system/index.php redesign's
 * deliberately page-scoped uh-system-hub-* rules: this pass explicitly
 * asked for one reusable design language across many pages, so these
 * classes are named generically and meant to be reused by any future
 * detail page too. */

/* Root-cause fix for "long identifier overflows its card": uh_detail_list()
 * (components/detail-list.php) is the one shared component every detail
 * page above renders its label/value rows through, and its value column
 * had no overflow protection at all — a long, space-less token (a
 * transaction reference, a UUID, an idempotency key, a callback URL) set
 * as plain text or <code> doesn't wrap by default, so it pushes past the
 * column/card edge instead. Fixing it once here fixes every current and
 * future caller, rather than patching each page's markup individually.
 * overflow-wrap: anywhere (not the more conservative break-word) because
 * these are frequently *fully* unbroken tokens (no hyphens/underscores
 * for break-word to hook into) — anywhere is the one value guaranteed to
 * never overflow regardless of token shape. min-width: 0 is required for
 * this to take effect at all inside a Bootstrap .row/.col flex/grid
 * context, which otherwise sizes the column to its content's intrinsic
 * (unwrapped) width first.
 *
 * Content-formatting audit: this rule used to also carry
 * `text-transform: capitalize !important`, blanket-applied to every
 * value this shared component ever renders. A full audit of every
 * uh_detail_list()/detailsModal() call site found that hurt far more
 * than it helped: only two fields app-wide (the wallet Statement/Ledger
 * entry modals' own "Reason") are short, enum-like values that actually
 * want title-casing — those two now opt in explicitly via Bootstrap's
 * own `.text-capitalize` utility at their own render call
 * (assets/js/pages/wallet-statements.js, wallet-ledger.js), the same
 * "opt in on the one value that needs it" convention `.uh-text-url`
 * below already established for opting URLs *out*. Every other
 * plain-text value in every one of these modals is genuine free-text
 * prose from the backend (a ledger/statement "Description", a security/
 * team activity "Detail"/"Event", a webhook delivery's "Last Error"/
 * "Response Body"/"Payload"/"URL", an API key status note) — the
 * blanket rule was title-casing every word of those on display (e.g.
 * "Wallet funded via paystack virtual account deposit" rendering as
 * "Wallet Funded Via Paystack Virtual Account Deposit"), which this
 * removal fixes at every one of those call sites at once, without
 * touching the underlying data (already correctly cased at the source
 * — confirmed against services/WalletFundingService.php,
 * VirtualAccountService.php, ClientActivityPresenter.php). */
.uh-detail-list .row > [class*="col-"] {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Data/value typography pass: the shared component's value column
 * (col-sm-8) had no font-size override at all, inheriting the ambient
 * 1rem/16px body default — confirmed live (transaction-detail.php):
 * 16px/24px line-height computed, visibly larger than both its own
 * 12px label directly above it and the app's own established "compact
 * data" tier (.8125rem/13px) already used everywhere else a label/value
 * pair appears — .form-control inputs, .uh-entity-header-meta, and (until
 * now) two page-scoped variants of this exact component
 * (.uh-business-detail-list, .uh-kyc-review-list, settings/business.php
 * and settings/kyc.php) that had already independently reinvented this
 * same fix for their own pages only. Moved here, onto the shared class
 * every current and future detail view renders through
 * (components/detail-list.php's uh_detail_list() and modal.js's
 * detailsModal() both emit this exact markup) — those two page-scoped
 * rules are now fully redundant and removed below this block rather
 * than left duplicating it. Row padding tightens to the same .4375rem
 * those two variants already used ("compact but comfortable," not the
 * shared component's original py-2/.5rem nor a cramped size below
 * either) — line-height is left to inherit Bootstrap's own relative 1.5
 * rather than a fixed override, which already computes to a comfortable
 * ~19.5px at this size. `!important` is required here — the row markup
 * carries Bootstrap's own `.py-2` utility class (components/detail-list.php),
 * which vendors/main.css compiles with `!important` on both properties;
 * a plain override loses to that regardless of selector specificity. */
.uh-detail-list .row {
  padding-top: .4375rem !important;
  padding-bottom: .4375rem !important;
}

.uh-detail-list .col-sm-8 {
  font-size: .875rem;
}

/* URL/domain values must never be visually capitalized — the capitalize
 * rule above (added for enum-like values, mirrored from admin-ui's own
 * copy of this same rule) otherwise turns a stored URL like
 * "https://example.com" into "https://Example.Com" purely at render
 * time (the value itself is never altered — display-layer bug, not a
 * data one). text-transform is inherited, so setting a *different* value
 * directly on the element itself (no !important needed) always wins over
 * whatever the ancestor .uh-detail-list col computed, regardless of that
 * rule's own specificity/!important. Apply directly to whichever span/
 * code actually renders a URL (webhooks/index.php's endpoint URL row,
 * the KYC wizard's Website review-summary row, and any future one)
 * rather than removing the shared rule, which real enum-value callers
 * still rely on. */
.uh-text-url {
  text-transform: none;
}

/* Content-formatting audit follow-up: a handful of pages render a full
 * sentence or multi-word phrase inside a `.badge` pill instead of plain
 * text — security-activity.js/security-login-history.js's own
 * eventBadge(), team-activity.js's activityBadge() (event/activity
 * labels like "All sessions signed out."), and api-keys.js's
 * secretStatusHtml() ("Active — legacy format, rotation recommended").
 * These were never affected by .uh-detail-list's own (now-removed)
 * blanket capitalize rule — the culprit here is vendors/main.css's own
 * base `.badge{text-transform:capitalize}` (~line 123259), which applies
 * to every badge in the app regardless of this app's own classes, and
 * which this project's own conventions don't allow hand-editing. Every
 * *other* badge in the app (statusBadge(), the ones above) genuinely
 * wants that — they hold a single short enum word. Same opt-out shape as
 * .uh-text-url above, generalized under its own name since "this badge
 * holds a sentence" is a distinct reason from "this value is a URL." No
 * !important needed — dev.css loads after vendors/main.css, so a plain
 * override already wins at equal specificity via source order alone
 * (confirmed live, the same mechanism this file's other overrides of
 * vendor rules already rely on). */
.uh-badge-sentence {
  text-transform: none;
}

/* .uh-business-detail-list (settings/business.php) and .uh-kyc-review-list
 * (settings/kyc.php) used to each independently set this same row
 * padding/value font-size for their own page only — both markers are
 * still present in their markup (harmless) but now fully redundant:
 * the data/value typography pass above moved the identical values onto
 * the shared .uh-detail-list class itself, which both pages' markup
 * already carries alongside these marker classes. Removed here rather
 * than left duplicating that rule.
 *
 * Entity header band — icon + primary name/value + status badge(s) +
 * a muted secondary-facts line, sitting above the existing two-column
 * detail/actions row on every page listed above. Deliberately built from
 * each page's *existing* element ids relocated into this markup, not new
 * ids — see the per-page comments at each call site for exactly which
 * ids moved. */
.uh-entity-header {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding: 1.25rem;
  margin-bottom: 1.5rem;
  background-color: var(--bs-body-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
}

.uh-entity-header-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-primary-bg-subtle);
  /* Brand rollout (#28343D) dark-mode fix — see .uh-dashboard-kpi-icon's own comment. */
  color: var(--bs-primary-text-emphasis);
  flex-shrink: 0;
}

/* Notification View redesign — category-tinted icon-chip variants.
 * Reuses Bootstrap's own subtle-bg/text-emphasis token pairs, the exact
 * same ones the category badge (JS-set badge-soft-{variant}) already
 * colors itself with, so the icon and its badge always visually agree
 * and both stay theme-aware automatically — no new colors introduced.
 * Modifier classes (BEM-ish, matching .uh-ticket-message--staff's own
 * convention elsewhere in this file) added alongside the base class
 * above rather than a variant prop on it, so every other page's plain,
 * uncategorized entity-header icon (Transaction, Support Ticket, ...)
 * is unaffected. */
.uh-entity-header-icon--danger {
  background-color: var(--bs-danger-bg-subtle);
  color: var(--bs-danger-text-emphasis);
}

.uh-entity-header-icon--success {
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
}

.uh-entity-header-icon--info {
  background-color: var(--bs-info-bg-subtle);
  color: var(--bs-info-text-emphasis);
}

.uh-entity-header-icon--secondary {
  background-color: var(--bs-secondary-bg-subtle);
  color: var(--bs-secondary-text-emphasis);
}

.uh-entity-header-body {
  min-width: 0;
  flex: 1 1 auto;
}

.uh-entity-header-title {
  font-size: 1.1rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

/* A status badge (settings/kyc.php's KYC Status, settings/business.php's
 * Verification Status) sitting in this *title* slot reads visually thin
 * next to the size/weight this slot implies for plain text — this gives
 * a badge specifically here more presence (bigger type, roomier padding)
 * so it actually carries the "primary fact of this header" weight the
 * slot is designed for. Scoped to badges inside .uh-entity-header-title
 * only — every other badge elsewhere (table rows, the Submission Status
 * field below) is completely unaffected. */
.uh-entity-header-title .badge {
  font-size: .8125rem;
  padding: .4375rem .875rem;
}

.uh-entity-header-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .5rem;
}

/* Information-hierarchy pass — every value in the entity header (the
 * title, each status badge, each meta fact) previously had no label
 * identifying what it represented, relying solely on position/badge
 * color. This wraps a badge (or the title) with a small label above it,
 * reusing the exact same small/muted/uppercase recipe
 * .uh-dashboard-kpi-label already established for "label above value"
 * elsewhere in the app (transaction/wallet summary tiles) — not a new
 * visual language, just this component's own version of it. */
.uh-entity-header-field {
  display: flex;
  flex-direction: column;
  gap: .125rem;
}

.uh-entity-header-label {
  font-size: .6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--bs-secondary-color);
}

.uh-entity-header-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .375rem;
  margin-top: .5rem;
  font-size: .875rem;
  color: var(--bs-secondary-color);
  overflow-wrap: anywhere;
}

.uh-entity-header-meta .uh-entity-header-label {
  color: inherit;
}

/* KYC Status header polish — settings/kyc.php's review note is the one
 * piece of admin-authored, actionable feedback in this header (why a
 * submission was sent back), not a passive fact like Client ID/Email on
 * Profile's own meta row — a warning tone plus an icon gives it the
 * visual weight it needs to actually get read, without a boxed/tinted
 * background competing with the header's own KYC Status/Submission
 * Status facts above it. Scoped to this one element (an extra class
 * alongside .uh-entity-header-meta, not a change to that shared rule) so
 * every other page's plain meta row is untouched. --bs-warning-text-emphasis
 * is Bootstrap's own theme-aware token — already flips correctly for dark
 * mode with zero extra rules, same as every other warning-toned element
 * in this app. */
.uh-kyc-header-note {
  align-items: center;
  gap: .5rem;
  margin-top: .75rem;
  color: var(--bs-warning-text-emphasis);
  font-size: .8125rem;
  line-height: 1.4;
}

/* The icon inherits color: currentColor (uh_icon()'s own stroke
 * attribute) from .uh-kyc-header-note above automatically — flex-shrink
 * only, so a longer note wrapping to two lines can never squeeze it. */
.uh-kyc-header-note svg {
  flex-shrink: 0;
}

.uh-entity-header-meta .uh-entity-header-meta-sep {
  color: var(--bs-border-color);
}

/* Support Ticket conversation redesign (UI audit pass, support/
 * ticket.php + assets/js/pages/support-ticket-detail.js) — a chat/
 * comment-style stacked thread (avatar + name/timestamp header + body)
 * instead of the old solid full-width color blocks. Reuses this app's
 * existing avatar-chip language (.avatar/.avatar-xs/.avatar-rounded —
 * already the header/account-dropdown's own avatar treatment, same
 * initials() util too — see utils/formatter.js's own comment) rather
 * than inventing a second avatar system for this one page, and existing
 * Bootstrap subtle-bg/border tokens for the staff-reply accent instead
 * of a new color. */
.uh-ticket-message {
  display: flex;
  gap: .75rem;
  align-items: flex-start;
  margin-top: 1rem;
}

.uh-ticket-message:first-child {
  margin-top: 0;
}

/* Consecutive messages from the same person (support-ticket-detail.js's
 * own authorKey() grouping) sit closer together and skip their own
 * avatar/name header — a smaller positive margin instead of the
 * default, rather than a negative one, keeps this simple to reason
 * about alongside every other spacing rule in this file. */
.uh-ticket-message--grouped {
  margin-top: .375rem;
}

.uh-ticket-message-avatar {
  flex-shrink: 0;
  margin-top: .125rem;
}

/* Keeps a grouped message's body aligned under the leader message's
 * body (not its avatar) without a second layout system for this one
 * variant — same width as the real avatar it stands in for. */
.uh-ticket-message-avatar-spacer {
  display: inline-block;
  width: 2rem;
  flex-shrink: 0;
}

.uh-ticket-message-body {
  flex: 1 1 auto;
  min-width: 0;
  padding: .625rem .875rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

/* The one accent color on this page (mirrors the dashboard's own
 * "one deliberate accent, not app-wide" restraint) — an official Ureh
 * Support reply gets a subtle primary tint + border so it stands out
 * from the client/team side of the conversation at a glance, the same
 * --bs-primary-bg-subtle/-border-subtle pairing already used elsewhere
 * in this app for exactly this "highlighted, not just colored" look. */
.uh-ticket-message--staff .uh-ticket-message-body {
  background-color: var(--bs-primary-bg-subtle);
  border: 1px solid var(--bs-primary-border-subtle);
}

.uh-ticket-message-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .75rem;
  margin-bottom: .25rem;
}

.uh-ticket-message-author {
  font-weight: 600;
  font-size: .8125rem;
}

.uh-ticket-message-time {
  font-size: .75rem;
  color: var(--bs-secondary-color);
  flex-shrink: 0;
  white-space: nowrap;
}

/* Typography re-balance pass: .9375rem/15px, one notch up from .875rem —
 * paired with .uh-notification-message-body below (same prose-content
 * tier, same reasoning: see .form-control's own comment further up this
 * file for the full rationale). */
.uh-ticket-message-text {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: .9375rem;
  line-height: 1.5;
}

/* Notification View redesign — the message now reads as an actual
 * delivered message rather than one more labeled card field: its own
 * gently distinct surface (a tertiary-bg fill, the same recipe
 * .uh-ticket-message-body above already uses for a conversation
 * message — no border added on top of it, which would double up
 * against the card's own already-bordered edge and look like two nested
 * boxes) with roomier padding and a readable measure (max-width) rather
 * than stretching full card width on a wide desktop viewport, which
 * hurts long-message readability the same way an over-wide printed page
 * would. Font-size/line-height match .uh-ticket-message-text's own
 * .875rem/1.5 (data/value typography pass, prior session) — both are
 * prose/message-body content, a different category from a label/value
 * data row. white-space/word-break replace the inline style the old
 * markup carried — same values, now a class instead of a per-element
 * style attribute. */
.uh-notification-message-body {
  padding: 1.25rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  max-width: 640px;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: .9375rem;
  line-height: 1.6;
}

/* The "Mark as Read" action sits below the message as its own row, not
 * appended to the message text itself — a top border/spacing break
 * (the same "quiet divider" idea a modal-footer or card-footer would
 * give it, neither of which this app currently uses anywhere) keeps it
 * reading as "an action about this message," not part of the message's
 * own content. Hidden via the same d-none toggle the button itself used
 * to carry directly (assets/js/pages/notifications-view.js) — moved to
 * this wrapper so the divider disappears along with the button once a
 * notification is already read, instead of leaving an empty bordered
 * gap. */
.uh-notification-message-actions {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

/* Action-card icon badge — the same 36px colored-square recipe as
 * system/index.php's own hub cards (uh-system-hub-icon), generalized
 * into a shared, non-page-scoped class since this pass reuses it across
 * every detail page's action panel, not just one page. */
.uh-action-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-primary-bg-subtle);
  /* Brand rollout (#28343D) dark-mode fix — see .uh-dashboard-kpi-icon's own comment. */
  color: var(--bs-primary-text-emphasis);
  flex-shrink: 0;
}

/* A <pre> JSON/config dump's own overflow-safety — transactions/detail.php
 * already referenced this class name on its Provider Response block but
 * relied entirely on a duplicated inline style for the actual behavior;
 * this gives the class real content so any future <pre> dump (this pass
 * also applies it to system/security.php's Rate Limit Defaults block,
 * which had no wrap protection at all) gets it by adding one class,
 * rather than copying the same four properties inline again. */
.uh-json-block {
  white-space: pre-wrap;
  word-break: break-word;
  overflow-wrap: anywhere;
  max-height: 420px;
  overflow: auto;
}

/* System Administration hub redesign (system/index.php) — scoped to this
 * page's own markup only (uh-system-hub-* prefix), same isolation
 * approach as the dashboard's uh-dashboard-* rules above (see that
 * block's own comment). Deliberately not reusing .uh-dashboard-section-
 * title/.uh-dashboard-kpi-icon directly even though the recipe is
 * identical — those are explicitly scoped to dashboard/index.php's own
 * markup, and duplicating the ~10 lines here is cheaper than either
 * loosening that scoping comment or coupling two unrelated pages to one
 * shared class. */
.uh-system-hub-section-title {
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--bs-secondary-color);
  margin-bottom: .75rem;
}

.uh-system-hub-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-primary-bg-subtle);
  /* Brand rollout (#28343D) dark-mode fix — see .uh-dashboard-kpi-icon's own comment. */
  color: var(--bs-primary-text-emphasis);
  flex-shrink: 0;
}

/* Breadcrumb separator fix — the vendor template's own
 * .breadcrumb-item+.breadcrumb-item:before rule (vendors/main.css)
 * renders its divider through an icon font, `font-family:
 * dripicons-v2!important; content:"V"` — that font's "V" character maps
 * to a chevron glyph in the vendor's own theme, but this app never loads
 * dripicons-v2 (every icon here is an inline SVG via uh_icon(), no icon
 * font anywhere else in the bundle), so the browser falls back to
 * literally rendering the capital letter "V", which reads as a
 * dropdown-style arrow next to every breadcrumb crumb. This app's own
 * admin.css loads after vendors/main.css (layouts/app.php, layouts/
 * auth.php), so the same selector here wins by cascade order and resets
 * the divider to a plain "/" — Bootstrap's own original default the
 * vendor rule was overriding — with no icon-font dependency. */
.breadcrumb-item + .breadcrumb-item::before {
  font-family: inherit;
  content: "/";
}

/* Global navigation preloader — visual redesign pass. Architecture
 * unchanged (assets/js/utils/preloader.js still owns click interception,
 * the sessionStorage continuation handoff between pages, and the
 * is-active/is-continuing/is-done class sequence; only what those classes
 * *look like* changed here, from a top progress bar to a centered,
 * three-dot "flowing pulse" — the same restrained motif Stripe/Linear/
 * Raycast use for an inline "working on it" cue rather than a determinate
 * progress metaphor, which suits this MPA better: the browser's own
 * navigation timing was never something we could measure precisely
 * enough to justify a bar that claims to show real progress.
 *
 * Markup: #uh-preloader (layouts/app.php / layouts/auth.php) now wraps
 * three small .uh-preloader-dot spans instead of standing alone with a
 * ::before bar — still just 4 total nodes (1 wrapper + 3 dots), no
 * heavier than the previous single-pseudo-element bar in any way that
 * matters (no images, no SVG, nothing that blocks or delays render).
 *
 * `background-color: var(--bs-primary)` on the dots is the exact same
 * accent every other themed element in this file already reads
 * (`.uh-dashboard-kpi-icon`, `.uh-entity-header-icon`, ...) — change that
 * one Bootstrap variable anywhere upstream and this repaints with it, in
 * both themes, automatically.
 *
 * Only `transform` (scale) and `opacity` are ever animated — both
 * GPU-compositable, neither triggers layout or repaints surrounding
 * content. `animation-play-state` (paused by default, running only while
 * a state class is present) means the three dots never tick a single
 * animation frame while the loader is hidden — the idle-page cost is
 * exactly zero, same as the bar it replaces. `pointer-events: none` plus
 * a z-index above the navbar/sidebar's own 1030-1083 range (see
 * vendors/main.css) means it can never intercept a click or shift any
 * real layout — a fixed-position 33x7px cluster is all that ever paints. */
#uh-preloader {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 6px;
  opacity: 0;
  z-index: 1200;
  pointer-events: none;
  transition: opacity .25s ease-out;
  will-change: opacity;
}

.uh-preloader-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--bs-primary);
  opacity: .35;
  transform: scale(.6);
  animation: uh-preloader-pulse 1.3s ease-in-out infinite;
  animation-play-state: paused;
  will-change: transform, opacity;
}

.uh-preloader-dot:nth-child(2) {
  animation-delay: .15s;
}

.uh-preloader-dot:nth-child(3) {
  animation-delay: .3s;
}

/* Calm, deliberate "breathe" — a slow scale+fade per dot, staggered so
 * the sequence reads as one smooth left-to-right wave rather than three
 * dots bouncing in place (bounce/translate motion is what reads as
 * playful; a shared scale+opacity curve at this speed reads as
 * sophisticated, closer to a heartbeat than a spinner). */
@keyframes uh-preloader-pulse {
  0%, 80%, 100% { transform: scale(.6); opacity: .35; }
  40% { transform: scale(1); opacity: 1; }
}

/* Triggered by a qualifying <a> click, on the page being left, and by the
 * destination page's own completion — both just fade the whole cluster
 * in/out; the dots keep pulsing the entire time they're visible (paused
 * again only once fully hidden), so the motion never abruptly starts or
 * stops mid-cycle. */
#uh-preloader.is-active,
#uh-preloader.is-continuing {
  opacity: 1;
}

#uh-preloader.is-active .uh-preloader-dot,
#uh-preloader.is-continuing .uh-preloader-dot,
#uh-preloader.is-done .uh-preloader-dot {
  animation-play-state: running;
}

/* The destination page's own first paint, when it's the continuation of
 * a cluster already started on the previous page — appears at full
 * opacity instantly, transition disabled, so there is no fade-in restart
 * and no gap between "left the old page" and "arrived at the new one." */
#uh-preloader.is-continuing {
  transition: none;
}

/* Destination page has finished loading — a slightly slower fade-out
 * than the fade-in, so the loader visibly settles rather than just
 * vanishing mid-pulse. */
#uh-preloader.is-done {
  opacity: 0;
  transition: opacity .3s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .uh-preloader-dot {
    animation: none;
    opacity: .85;
    transform: scale(.85);
  }
}

/* ==========================================================================
 * Enterprise auth redesign (2026-07-27, revised same day) — login.php only.
 * Pure CSS/JS-hook addition: no PHP auth logic, form action, id, name, or
 * validation touched — see login.php/login.js for the (also purely
 * additive) markup/JS this pairs with. Everything below is scoped under
 * `.hk-pg-auth`/`.uh-auth-*`, so none of it can leak onto the authenticated
 * app shell. This page was previously untouched by admin.css entirely (see
 * the compact-form-control pass above, which explicitly carves
 * `.hk-pg-auth` out) — a blank canvas, not a component being reworked out
 * from under existing overrides.
 *
 * Revision note: the first pass wrapped the form in an elevated
 * `.uh-auth-card` (background/border/shadow/padding box). Direct feedback
 * asked for that removed — an open, integrated layout instead, in the spirit
 * of an API-platform login (Stripe/Postman/Kong) rather than a boxed
 * consumer-app form. The card rule and its dark/mobile overrides are gone;
 * input/button styling below now targets `.hk-pg-auth` directly (the same
 * scoping convention the compact-form-control pass above already
 * establishes) instead of a wrapper div that no longer exists. Typography,
 * the password toggle, and the OTP field treatment from that first pass are
 * unchanged — none of that was card-dependent.
 * ========================================================================== */

/* Background: a faint two-point brand-tinted radial wash behind the whole
 * split layout, rather than a flat page color — reads as "considered" the
 * way Linear/Vercel auth pages do, without competing with the form itself
 * (each glow tops out at 5% opacity and fades to nothing by mid-viewport). */
.hk-pg-auth {
  background:
    radial-gradient(circle at 12% 18%, rgba(var(--bs-primary-rgb), .05), transparent 55%),
    radial-gradient(circle at 88% 82%, rgba(var(--bs-primary-rgb), .04), transparent 50%),
    var(--bs-body-bg);
}

/* A single quiet rule beneath the brand mark stands in for the elevation
 * the card used to provide — enough separation to feel intentional without
 * boxing the form in. Faded at both ends rather than a hard-edged line, so
 * it reads as a subtle break rather than a form-builder-style divider. */
.uh-auth-divider {
  height: 1px;
  width: 100%;
  max-width: 18rem;
  margin-left: auto;
  margin-right: auto;
  background: linear-gradient(to right, transparent, var(--hk-border-primary), transparent);
}

/* Brand lockup: small icon mark + the existing text logotype, tightened
 * and center-balanced rather than a single plain line of bold text. */
.uh-auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
}

.uh-auth-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: .5rem;
  background: rgba(var(--bs-primary-rgb), .12);
  /* Brand rollout (#28343D) dark-mode fix — see .uh-dashboard-kpi-icon's own comment. */
  color: var(--bs-primary-text-emphasis);
  flex-shrink: 0;
}

.uh-auth-brand .navbar-brand {
  letter-spacing: -.01em;
}

/* Official Ureh icon inlined into the auth badge + hero badge. Sized to
   the slot, square (1:1) so it can never distort; dark-mode recolour is
   handled by the shared .uh-brand-adaptive rule. */
.uh-auth-brand-mark .uh-auth-brand-svg {
  display: block;
  width: 1.4rem;
  height: 1.4rem;
}

.uh-auth-hero-badge .uh-auth-hero-svg {
  display: block;
  width: 1.75rem;
  height: 1.75rem;
}

/* Compact Ureh icon on a clean white chip (production branding pass).
   The dashboard header brand icon sits on white for clear contrast, and
   the auth badges get the same treatment so the unchanged slate + green
   mark reads on any surface in either theme (the neutral ink is no
   longer force-whitened in dark mode). Only the container changes — the
   icon artwork is untouched. */
.uh-sidebar-brand-icon,
.uh-auth-brand-mark,
.uh-auth-hero-badge {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, .08);
}

:root[data-bs-theme="dark"] .uh-auth-hero-badge {
  border-color: rgba(0, 0, 0, .08);
}

/* Sidebar brand is icon-only now (the wordmark was removed). The vendor
   collapse rule hides `.navbar-brand .brand-img:last-child` to drop the
   wordmark on the 72px rail — but with a single child the icon IS the
   last child, so it was hidden along with it. Keep the icon visible in
   every collapsed state (the base rail and the width-pinned hover
   flyout) so the brand still shows when the sidebar is collapsed.
   Specificity matches the vendor rules; this file loads after
   vendors/main.css, so it wins the tie. */
@media (min-width: 1200px) {
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"] .hk-menu .menu-header > span .navbar-brand .brand-img.uh-sidebar-brand-icon,
  .hk-wrapper[data-layout="vertical"][data-layout-style="collapsed"][data-hover="active"] .hk-menu:hover .menu-header > span .navbar-brand .brand-img.uh-sidebar-brand-icon {
    display: inline-flex;
  }
}


/* Typography: stronger title, calmer subtitle line-height than the vendor
 * default (which is body copy's 1.5 — fine for paragraphs, slightly loose
 * for a single centered sentence under a heading). */
.uh-auth-title {
  font-size: 1.375rem;
  font-weight: 700;
  letter-spacing: -.01em;
  margin-bottom: .375rem;
}

.uh-auth-subtitle {
  font-size: .875rem;
  line-height: 1.45;
}

.uh-auth-label {
  font-size: .875rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--bs-secondary-color);
}

/* Dev-mode OTP note (the code is echoed directly since no email/SMS
 * transport exists yet — see login.js's own comment): given a quiet pill
 * treatment so it reads as "diagnostic aside," not body copy. */
.uh-auth-devnote:not(:empty) {
  display: inline-block;
  margin-top: .25rem;
  padding: .25rem .625rem;
  border-radius: .5rem;
  background: var(--bs-warning-bg-subtle);
  border: 1px solid var(--bs-warning-border-subtle);
}

/* Inputs. `.hk-pg-auth` was already explicitly excluded from the app-wide
 * compact `.form-control` sizing near the top of this file, so this was a
 * clean surface — a larger click target and an enterprise-SaaS-style focus
 * ring, not a size fight with that other rule.
 *
 * Typography refinement pass: font-size brought down to this app's own
 * established compact-input tier (.8125rem/13px — the exact value the
 * app-shell block above already uses, and .uh-auth-label next to this
 * input was already set to) — at the vendor bundle's un-overridden base
 * (1rem/16px, see vendors/main.css's own .form-control), auth-page input
 * text read noticeably larger than every other input in the app,
 * including its own label. min-height/padding trimmed slightly to match
 * (2.75rem/.625rem-.875rem -> 2.5rem/.5rem-.875rem) so the now-smaller
 * text still sits in a comfortably proportioned box instead of floating
 * in space sized for the old, larger text — still a noticeably roomier
 * click target than the dense app-shell tier's own box (this remains a
 * public, first-impression page), just no longer oversized. padding-right
 * is set explicitly (not omitted) so every ordinary field keeps the same
 * .875rem it already had; .uh-password-field .form-control's own
 * padding-right:2.75rem override further below still wins for password
 * fields specifically — same equal-specificity, later-source-order
 * mechanic the app-shell block's own comment already documents;
 * confirmed here by source order (that rule is further down this file,
 * so wins the tie), not assumed. */
.hk-pg-auth .form-control {
  min-height: 2.5rem;
  padding-top: .5rem;
  padding-bottom: .5rem;
  padding-left: .875rem;
  padding-right: .875rem;
  font-size: .875rem;
  border-radius: .625rem;
  border-color: var(--hk-border-primary);
  transition: border-color .18s ease, box-shadow .18s ease, background-color .18s ease;
}

.hk-pg-auth .form-control:hover:not(:disabled):not(:focus) {
  border-color: var(--hk-border-secondary);
}

.hk-pg-auth .form-control:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 .2rem rgba(var(--bs-primary-rgb), .15);
}

.hk-pg-auth .form-control::placeholder {
  color: var(--bs-tertiary-color);
}

.hk-pg-auth .form-control:disabled {
  background: var(--bs-tertiary-bg);
  opacity: .65;
  cursor: not-allowed;
}

/* Verification-code input: wider letter-spacing and a centered, slightly
 * larger monospace-adjacent size — the common "this is a code, not a
 * sentence" treatment (Stripe/Auth0 OTP fields). */
.uh-auth-otp-input {
  text-align: center;
  font-size: 1.125rem;
  letter-spacing: .35em;
  text-indent: .35em;
}

/* Password field + its inline visibility toggle. The toggle is an
 * absolutely-positioned button *inside* the same wrapper as the input
 * (not a sibling row), so it can never affect layout/height — exactly the
 * "does not affect layout" requirement. */
.uh-password-field {
  position: relative;
}

.uh-password-field .form-control {
  padding-right: 2.75rem;
}

.uh-password-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--bs-tertiary-color);
  cursor: pointer;
  border-radius: 0 .625rem .625rem 0;
  transition: color .18s ease, background-color .18s ease;
}

.uh-password-toggle:hover {
  color: var(--bs-secondary-color);
  background-color: var(--bs-tertiary-bg);
}

.uh-password-toggle:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

/* Two stacked icons, cross-faded by `aria-pressed` (set in login.js) rather
 * than swapped via innerHTML — keeps the toggle a pure CSS state change. */
.uh-password-toggle-icon {
  display: inline-flex;
  transition: opacity .18s ease;
}

.uh-password-toggle-icon--hide {
  display: none;
}

.uh-password-toggle[aria-pressed="true"] .uh-password-toggle-icon--show {
  display: none;
}

.uh-password-toggle[aria-pressed="true"] .uh-password-toggle-icon--hide {
  display: inline-flex;
}

/* Password Strength — registration page's live requirement checklist
 * (components/password-strength.js). Collapsed to just the meter's
 * resting 2px track until the field has content
 * (.uh-password-strength--visible, set by JS on first keystroke), so an
 * empty form doesn't open with a wall of gray checklist text. */
.uh-password-strength {
  margin-top: .625rem;
}

.uh-password-strength-meter {
  height: 3px;
  border-radius: 2px;
  background-color: var(--bs-tertiary-bg);
  overflow: hidden;
}

.uh-password-strength-meter-bar {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: inherit;
  background-color: var(--bs-danger);
  transition: width .2s ease, background-color .2s ease;
}

.uh-password-strength-meter-bar[data-strength="3"] {
  background-color: var(--bs-warning);
}

.uh-password-strength-meter-bar[data-strength="4"],
.uh-password-strength-meter-bar[data-strength="5"] {
  background-color: var(--bs-success);
}

.uh-password-strength-list {
  list-style: none;
  margin: .625rem 0 0;
  padding: 0;
  display: none;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .375rem .75rem;
}

.uh-password-strength--visible .uh-password-strength-list {
  display: grid;
}

.uh-password-strength-item {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .75rem;
  color: var(--bs-tertiary-color);
  transition: color .18s ease;
}

.uh-password-strength-icon-dot {
  flex: none;
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background-color: var(--bs-tertiary-color);
  transition: background-color .18s ease;
}

.uh-password-strength-icon-check {
  display: none;
  flex: none;
  color: var(--bs-success);
}

.uh-password-strength-item.is-met {
  color: var(--bs-success);
}

.uh-password-strength-item.is-met .uh-password-strength-icon-dot {
  display: none;
}

.uh-password-strength-item.is-met .uh-password-strength-icon-check {
  display: inline-flex;
}

.uh-password-strength-status {
  margin: .5rem 0 0;
  font-size: .75rem;
  font-weight: 600;
  color: var(--bs-tertiary-color);
  min-height: 1em;
}

.uh-password-strength-status--strong {
  color: var(--bs-success);
}

.uh-password-strength-status--strong::before {
  content: '✓ ';
}

/* Primary button: same vendor .btn-primary recipe underneath, refined with
 * enterprise radius, a touch more weight/tracking on the (already
 * uppercase) label, and calm hover/active motion instead of the vendor
 * bundle's flat instant-swap. */
.hk-pg-auth .btn-primary {
  position: relative;
  border-radius: .625rem;
  padding-top: .75rem;
  padding-bottom: .75rem;
  font-weight: 600;
  letter-spacing: .03em;
  transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
}

.hk-pg-auth .btn-primary:hover:not(:disabled) {
  filter: brightness(1.06);
  box-shadow: 0 6px 16px rgba(var(--bs-primary-rgb), .28);
}

.hk-pg-auth .btn-primary:active:not(:disabled) {
  transform: translateY(1px);
  filter: brightness(.98);
}

.hk-pg-auth .btn-primary:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* Loading state: guardSubmit() (components/form.js) already toggles this
 * exact `.disabled` class on the submit button for the duration of the
 * request — this only styles that existing state, no JS change needed.
 * Label is hidden (not removed) so button width never shifts. */
.hk-pg-auth .btn-primary.disabled {
  color: transparent;
  filter: none;
}

.hk-pg-auth .btn-primary.disabled::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1rem;
  height: 1rem;
  margin: -.5rem 0 0 -.5rem;
  border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: uh-auth-spin .6s linear infinite;
}

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

.btn-flush-primary {
  transition: color .18s ease, background-color .18s ease;
}

/* Hero (right-hand informational panel). A faint radial highlight behind
 * the copy, plus a small icon badge above the heading, echoing the brand
 * mark near the form so both halves of the page read as one system.
 *
 * z-index:0 here is the actual fix for the background photo/glow being
 * invisible, not opacity tuning (a prior pass wrongly assumed it was).
 * `position: relative` (the `position-relative` utility class already on
 * this element in the markup) alone does NOT create a CSS stacking
 * context — that requires z-index to be a real number, not `auto`.
 * Without it, .uh-auth-hero-photo/.uh-auth-hero-glow's `z-index:-1`
 * (below) has no local stacking context to nest inside, so it bubbles up
 * to the nearest ancestor that IS one — where it paints BELOW this
 * element's own `bg-primary-light-5` background-color instead of above
 * it (this element, being position:relative with z-index:auto, paints
 * its own background at "stack level 0" in that ancestor context, which
 * is above a bubbled-up negative z-index). Confirmed via a real headless-
 * browser render (Playwright/Chromium, not just CSS/source inspection):
 * every computed style on .uh-auth-hero-photo was already exactly as
 * intended (correct opacity, correct resolved background-image URL,
 * confirmed loaded, correct size/position) — this was a pure paint-order
 * bug, invisible to per-property inspection. z-index:0 makes this
 * element its own stacking context, so its negative-z-index children
 * finally nest where the "negative-z-index trick" comment below always
 * assumed they already did. */
.uh-auth-hero {
  z-index: 0;
  overflow: hidden;
}

/* .auth-content is flex-column with align-items:center (vendors/main.css
 * + the .flex-column utility in includes/auth-hero.php's own markup) —
 * on a column-direction flex container, align-items:center means cross-
 * axis (horizontal) children shrink-to-fit their own content instead of
 * stretching to the container's full width. Harmless when the text
 * inside was centered (a narrower shrink-fit box, itself centered, still
 * centers its own centered text identically either way) — but now that
 * .uh-auth-hero-title/-text are left-aligned, that shrink-fit box's own
 * width (and so its left edge, where the text actually starts) would
 * vary with content length: confirmed via real layout measurement that
 * this page's own longer default title produced a ~100px-wider column
 * than team-login.php's short "Built for teams" override, visibly
 * shifting the whole block sideways between pages. width:100% here
 * makes the row fill .auth-content's actual full width regardless of
 * its content, so col-xxl-8/etc.'s own percentages (and mx-auto's own
 * centering of that now-stable box) compute identically on every page. */
.uh-auth-hero .auth-content > .row {
  width: 100%;
}

/* Infrastructure-mesh background, four stacked layers behind .auth-content
 * — DOM order (mesh, grid, network, glow, all z-index:-1) settles their
 * relative stacking, same "negative-z-index trick" this rule already
 * relied on for the two image treatments (and, before either, the inline
 * SVG illustration) this replaced (all absolutely-positioned siblings of
 * the static `.auth-content`, which would otherwise paint *under* them).
 *
 * Elevation pass over the original single-layer gradient mesh (kept, not
 * discarded — same blob recipe below) — adds a depth vignette to this
 * layer plus two new ones (.uh-auth-hero-grid, .uh-auth-hero-network,
 * both further down this file) for the "premium infrastructure" texture
 * a flat blurred wash alone couldn't carry. Built entirely from
 * rgba(var(--bs-primary-rgb)/--bs-info-rgb) plus this rule's own explicit
 * [data-bs-theme=dark] overrides below — the *tokens* already track
 * theme automatically (core/Config.php's brand rollout), but a live
 * side-by-side check found the token values themselves barely differ
 * between the two themes (the brand primary is the same dark charcoal in
 * both; only the page background swaps), so light and dark were
 * rendering as the same recipe composited over a different backdrop
 * rather than two deliberately art-directed moods — the explicit
 * overrides below are what actually fixes that, not the tokens alone.
 * blur(60px) on fairly large (45-60% of the panel) blobs is what turns a
 * hard-edged radial-gradient into the soft "aurora" look this style is
 * named for. */
.uh-auth-hero-mesh {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background:
    /* Depth vignette — a faint darkening toward the panel's own corners
       (independent of theme colors, pure black/white at very low alpha)
       so the blobs below read as sitting *in* a space with depth rather
       than floating on a flat tint. Kept extremely subtle — this is
       felt more than seen. Nudged a touch stronger in this refinement
       pass (was transparent 55%/.05) — the original was closer to
       imperceptible than felt. */
    radial-gradient(ellipse 90% 80% at 50% 50%, transparent 50%, rgba(0, 0, 0, .06) 100%),
    radial-gradient(circle at 50% 65%, rgba(var(--bs-primary-light-rgb), .16), transparent 55%);
}

/* Refinement pass: blur(60px) -> blur(84px). At the original radius the
   blobs' own circular edge was still faintly legible on a large panel —
   more diffusion is what actually reads as a soft aurora wash rather
   than "two blurred circles," without changing their color, position,
   or the drift animation below at all. */
.uh-auth-hero-mesh::before,
.uh-auth-hero-mesh::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(84px);
}

/* Positioned toward the panel's top-left and bottom-right corners,
 * deliberately away from the badge/title/text block's own center-30%
 * vertical position (.uh-auth-hero-glow below) so the two never wash
 * over the copy itself. Refinement pass: rebalanced from a 2:1 alpha
 * ratio (.20/.10 — one dominant blob, one faint afterthought) to a
 * ~1.2:1 one (.16/.13), and grown/pushed further off-canvas (52-58% ->
 * 56-62% width, -8/-12% -> -10/-14% offset) so more of each blob's soft
 * edge is what's actually visible rather than a harder near-complete
 * circle — two deliberately paired accents reads as more considered
 * than one loud blob and one quiet one. */
.uh-auth-hero-mesh::before {
  top: -14%;
  left: -10%;
  width: 56%;
  height: 46%;
  background: rgba(var(--bs-primary-rgb), .16);
}

.uh-auth-hero-mesh::after {
  right: -14%;
  bottom: -17%;
  width: 62%;
  height: 54%;
  background: rgba(var(--bs-info-rgb), .13);
}

/* Dark mode: deliberately deeper, not just "the same blobs on a dark
 * page." A touch more presence on the color blobs (dark backgrounds
 * absorb low-alpha color much more than light ones do) plus a stronger
 * corner vignette for real depth instead of a uniformly flat dark panel.
 * Refinement pass: rebalanced alongside the light-mode ratio above —
 * .38/.16 (a 2.4:1 ratio) is now .32/.19 (1.7:1), keeping dark mode
 * visibly richer than light mode while the two blobs still read as one
 * coordinated pair rather than one blob carrying the whole panel. */
:root[data-bs-theme="dark"] .uh-auth-hero-mesh {
  background:
    radial-gradient(ellipse 90% 80% at 50% 50%, transparent 42%, rgba(0, 0, 0, .24) 100%),
    radial-gradient(circle at 50% 65%, rgba(var(--bs-primary-light-rgb), .10), transparent 55%);
}

:root[data-bs-theme="dark"] .uh-auth-hero-mesh::before {
  background: rgba(var(--bs-primary-rgb), .32);
}

:root[data-bs-theme="dark"] .uh-auth-hero-mesh::after {
  background: rgba(var(--bs-info-rgb), .19);
}

/* A slow, gentle drift — "the mesh is quietly alive," same reasoning
 * and same transform-only/GPU-cheap approach dev.css's own
 * uh-hero-drift (this panel's original SVG illustration) already
 * established, just applied to two independent blobs instead of one
 * SVG group; each blob's own translate keyframe intentionally differs
 * (extent and duration) so they never move in visible unison. */
@media (prefers-reduced-motion: no-preference) {
  .uh-auth-hero-mesh::before {
    animation: uh-hero-mesh-drift-a 20s ease-in-out infinite;
  }

  .uh-auth-hero-mesh::after {
    animation: uh-hero-mesh-drift-b 26s ease-in-out infinite;
  }
}

@keyframes uh-hero-mesh-drift-a {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(4%, 6%); }
}

@keyframes uh-hero-mesh-drift-b {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(-5%, -4%); }
}

/* Engineering/blueprint grid — "premium texture, not decoration": a
 * faint 1px line grid at a scale deliberately too fine to read as
 * individual squares from normal viewing distance, just a texture that
 * says "precision" the way a spec sheet or blueprint does. mask-image
 * (not opacity — opacity would dim everything equally) fades it out
 * entirely near the panel's own center, where the badge/title/text
 * block actually sits regardless of exact panel width (that block is
 * vertically centered via .auth-content's own py-8, not pinned to one
 * corner, so a center-anchored fade — rather than a left/right split —
 * is the one approach that stays correct across every breakpoint this
 * panel renders at, confirmed by live screenshot at three widths). The
 * grid is strongest toward the edges, exactly where this panel already
 * has empty margin. rgba(0,0,0,alpha) in the mask (not `transparent`/
 * `black` keywords) is deliberate — CSS mask gradients are interpreted
 * by alpha by default in every evergreen browser, and using explicit
 * alpha values here removes any ambiguity with the luminance-mode
 * reading of "black," which would invert this same gradient. */
/* Refinement pass: background-size 40px -> 48px. A slightly wider grid
   reads as calmer and more deliberate than a denser one at the same
   line weight/alpha — the same "precision without clutter" intent the
   comment above already describes, just tuned further toward it; the
   mask geometry (verified by live screenshot at three widths, per that
   same comment) is untouched. */
.uh-auth-hero-grid {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(var(--bs-primary-rgb), .05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--bs-primary-rgb), .05) 1px, transparent 1px);
  background-size: 48px 48px;
  mask-image: radial-gradient(ellipse 68% 62% at 42% 46%, rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 1) 88%);
  -webkit-mask-image: radial-gradient(ellipse 68% 62% at 42% 46%, rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 1) 88%);
}

:root[data-bs-theme="dark"] .uh-auth-hero-grid {
  background-image:
    linear-gradient(rgba(255, 255, 255, .06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .06) 1px, transparent 1px);
}

/* Network/connectivity graphic — a small, hand-placed inline SVG
 * (includes/auth-hero.php's own markup: two loose node clusters joined
 * by one long connection), not a literal circuit board or any real
 * topology. Anchored to the panel's lower-right — the one area that
 * stays clear of the text block across every breakpoint this panel
 * renders at (the text is vertically centered and left-aligned within a
 * column that's wide at `lg` and narrower at `xl`/`xxl`, so "lower-right"
 * is the one corner reliably far from it at every width, unlike a fixed
 * left/right split). stroke/fill="currentColor" on the SVG itself (the
 * same convention uh_icon()/svgIcon() already use app-wide) means the
 * `color` set here is this graphic's *entire* theming surface — no
 * separate SVG asset or fill attribute to keep in sync per theme.
 * Deliberately static (no drift, no animated "data flow" dot along a
 * line) — the slowly-drifting color blobs behind it already carry this
 * panel's one motion cue; a second, independent animation here would
 * read as busy rather than "quietly alive," and a moving light doesn't
 * suit a graphic meant to read as precise, engineered structure. */
/* Refinement pass: base color .4 -> .32 (dark-mode .55 -> .48) — a
   touch further back into "background texture" so it recedes behind
   the grid/mesh rather than competing with them for attention; every
   individual line/circle's own relative opacity (set per-element in
   includes/auth-hero.php's own SVG markup) is untouched, so the
   graphic's internal hierarchy (hubs brighter than edge nodes, the
   backbone faintest of all) is unaffected. */
.uh-auth-hero-network {
  position: absolute;
  z-index: -1;
  right: -8%;
  bottom: -12%;
  width: 58%;
  aspect-ratio: 1;
  color: rgba(var(--bs-primary-rgb), .32);
  pointer-events: none;
}

.uh-auth-hero-network svg {
  display: block;
  width: 100%;
  height: 100%;
}

:root[data-bs-theme="dark"] .uh-auth-hero-network {
  color: rgba(var(--bs-primary-light-rgb), .48);
}

/* Refinement pass: a wider, softer halo (transparent 60% -> 68%) at a
   slightly lower peak (.08 -> .07, dark .16 -> .14) — bigger and
   fainter reads as a more considered "quiet glow behind the copy" than
   a smaller, more concentrated one, the same size-for-intensity trade
   the mesh blobs above just made too. Center position (50% 30%, aligned
   with the badge/title block) is unchanged. */
.uh-auth-hero-glow {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(circle at 50% 30%, rgba(var(--bs-primary-rgb), .07), transparent 68%);
  pointer-events: none;
}

:root[data-bs-theme="dark"] .uh-auth-hero-glow {
  background: radial-gradient(circle at 50% 30%, rgba(var(--bs-primary-rgb), .14), transparent 68%);
}

/* Refinement pass: added border + a hairline shadow — a flat tint alone
   read as a color swatch; a defined edge plus a whisper of lift is what
   turns it into a deliberate "icon chip," the same enterprise-UI
   pattern this panel's own premium framing is otherwise already going
   for. Border uses a fixed light-mode alpha, not the token directly,
   for the same reason the dark-mode override below exists at all — the
   brand primary is the same dark charcoal in both themes, so a border
   this faint would all but vanish over the dark panel without one. */
.uh-auth-hero-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: .75rem;
  background: rgba(var(--bs-primary-rgb), .12);
  border: 1px solid rgba(var(--bs-primary-rgb), .14);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
  /* Brand rollout (#28343D) dark-mode fix — see .uh-dashboard-kpi-icon's own comment. */
  color: var(--bs-primary-text-emphasis);
  margin-bottom: 1rem;
}

:root[data-bs-theme="dark"] .uh-auth-hero-badge {
  border-color: rgba(255, 255, 255, .12);
}

/* Typography/hierarchy refinement pass. Two deliberate rhythm decisions,
 * not just size tuning:
 *  - Badge-to-title gap (1rem, above) is noticeably tighter than
 *    title-to-text (2rem, below) — groups the icon+headline as one
 *    visual unit and sets the body text apart as a distinct, supporting
 *    line, rather than three evenly-spaced elements with no implied
 *    grouping.
 *  - text-wrap:balance replaces relying on font-size alone to avoid an
 *    awkward wrap — at the old 1.75rem this app's own actual title
 *    ("Enterprise Infrastructure for Bill Payments") wrapped 1/2/1 words
 *    per line, splitting "Bill" from "Payments" mid-phrase; balance is
 *    the modern, standards-track fix for exactly this (widely supported:
 *    Chrome/Edge 114+, Safari 17.4+, Firefox 121+), and unlike a manual
 *    max-width tune or a hardcoded <br>, it keeps working correctly for
 *    every OTHER page's own override text (team-login's "Built for
 *    teams" and the rest never wrapped in the first place, so this is a
 *    no-op for them) rather than tuning for one specific string. Size
 *    bumped back up toward Bootstrap's own bare <h2> default (2rem) now
 *    that balance — not a smaller font — is doing the wrapping work. */
.uh-auth-hero-title {
  font-size: 2rem;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -.02em;
  margin-bottom: 2rem;
  text-wrap: balance;
}

/* max-width (no auto margins — this block is left-aligned now, not
 * centered, so centering its own box would contradict that) keeps the
 * longer sentence at a comfortable reading line-length instead of
 * stretching to the full width of the column wrapping it. */
.uh-auth-hero-text {
  font-size: .9375rem;
  line-height: 1.65;
  max-width: 28rem;
}

/* Sub-navigation step rail (dev-ui only — see includes/helpers.php's
 * uh_subnav() docblock for the reasoning). Every module's tab strip
 * (Wallet, API, Security, ...) reads as a step progression — numbered
 * marker, "complete" pill for anything before the active page in the
 * list — while staying honest that these are ordinary sibling pages a
 * caller can visit in any order, not a tracked wizard: "complete" is
 * derived purely from list position relative to $active, nothing is
 * persisted. One accent hue (primary, at different intensities) runs
 * through every state rather than a traffic-light complete/active/
 * upcoming scheme — a green "done" pill would misstate what's true here
 * (visited earlier in the list), so intensity of the same color carries
 * the distinction instead. */

.uh-steps {
  margin-bottom: 1.5rem;
}

.uh-steps-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.uh-step {
  flex: 0 0 auto;
}

.uh-step-link {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .4375rem .875rem .4375rem .4375rem;
  border-radius: 999px;
  border: 1px solid var(--bs-border-color);
  background-color: var(--bs-body-bg);
  color: var(--bs-secondary-color);
  font-size: .8125rem;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  text-decoration: none;
  transition: background-color .15s ease-in-out, border-color .15s ease-in-out,
    color .15s ease-in-out, box-shadow .15s ease-in-out;
}

.uh-step-link:hover {
  color: var(--bs-body-color);
  border-color: var(--hk-border-secondary);
  background-color: var(--bs-tertiary-bg);
  text-decoration: none;
}

.uh-step-link:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

.uh-step-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.375rem;
  height: 1.375rem;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--bs-border-color);
  background-color: var(--bs-body-bg);
  color: var(--bs-secondary-color);
  font-size: .6875rem;
  font-weight: 600;
  transition: background-color .15s ease-in-out, border-color .15s ease-in-out,
    color .15s ease-in-out;
}

.uh-step-marker-icon {
  display: block;
}

/* Active: the current page. Solid fill + a soft ring, same visual weight
 * as a primary button, so exactly one step ever reads as "you are here". */
.uh-step--active .uh-step-link {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), .16);
}

.uh-step--active .uh-step-marker {
  border-color: rgba(255, 255, 255, .55);
  background-color: transparent;
  color: #fff;
}

/* Complete: earlier than the active page in this list. A soft tinted
 * pill (not the active page's solid fill) + a checkmark marker — reads as
 * "already visited", never as the active page's own emphasis. */
.uh-step--complete .uh-step-link {
  border-color: transparent;
  background-color: var(--bs-primary-bg-subtle);
  /* Brand rollout (#28343D) dark-mode fix — see .uh-dashboard-kpi-icon's own comment. */
  color: var(--bs-primary-text-emphasis);
}

.uh-step--complete .uh-step-marker {
  border-color: transparent;
  background-color: rgba(var(--bs-primary-rgb), .18);
  color: var(--bs-primary-text-emphasis);
}

@media (prefers-reduced-motion: reduce) {
  .uh-step-link,
  .uh-step-marker {
    transition: none;
  }
}

/* Mobile: one horizontally-scrollable row instead of wrapping onto 3-4
 * narrow lines — Security's own uh_security_nav_items() (the largest
 * module today, 7 items) is the worst case this guards against, the same
 * "swipeable strip over a wrapped grid" call a mobile app's category strip
 * or a settings tab bar makes at this width. Scrollbar hidden (the strip
 * stays scrollable via touch/trackpad — this only removes the visible
 * track, which reads as clutter on a short inline control) rather than
 * removed as a feature. */
@media (max-width: 575.98px) {
  .uh-steps-list {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: .25rem;
    margin-bottom: -.25rem;
  }

  .uh-steps-list::-webkit-scrollbar {
    display: none;
  }

  .uh-step {
    scroll-snap-align: start;
  }
}

[data-bs-theme="dark"] .uh-step--active .uh-step-link {
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), .28);
}

/* KYC redesign (settings/kyc.php) — multi-step wizard built on top of
 * the .uh-steps rail above (rendered here as <button>s by
 * assets/js/pages/settings-kyc.js, not <a href>s — in-page step
 * switching, not navigation), plus the entity-type selector cards,
 * step footer, review-summary "Edit" links, document list, and success
 * state that rail doesn't already provide. Scoped to .uh-kyc-* only —
 * none of this can leak onto or conflict with any other page. */

/* An upcoming (not-yet-reached) step's marker is a real <button
 * disabled>, so the browser's own default cursor already applies —
 * this only overrides the *link-style* pointer .uh-step-link normally
 * sets so a disabled step doesn't still look clickable. */
.uh-kyc-steps .uh-step--upcoming .uh-step-link {
  cursor: default;
}

.uh-kyc-step-title {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .9375rem;
  font-weight: 700;
  margin-bottom: .25rem;
}

.uh-kyc-step-title svg {
  color: var(--bs-primary);
  flex-shrink: 0;
}

.uh-kyc-step-actions {
  display: flex;
  align-items: center;
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

/* Create Account (register.php) multi-step wizard — same .uh-steps rail
 * + step-actions footer language as .uh-kyc-step-actions above, scoped
 * to .uh-register-* only so it can't leak onto or conflict with any
 * other page, same isolation the KYC block above documents for itself. */
.uh-register-step-actions {
  display: flex;
  align-items: center;
  margin-top: 1.5rem;
}

/* Consent copy, directly above the submit button (register.php's own
 * comment) — fs-8/text-muted already de-emphasizes it same as this
 * page's "Already have an account?" line below the form; this only adds
 * a touch of breathing room above and keeps the Terms/Privacy Policy
 * links legible without competing with the primary submit action. */
.uh-register-consent-note {
  margin-top: 1rem;
  line-height: 1.5;
}

.uh-register-consent-note a {
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: var(--bs-border-color);
  text-underline-offset: 2px;
}

.uh-register-consent-note a:hover {
  text-decoration-color: currentColor;
}

/* A subtle, directional step transition — fade + a gentle upward settle,
 * one step further than dev.css's own generic .uh-fade-in (opacity
 * only), so advancing through the wizard reads as "moving forward," not
 * just "appearing." Re-triggered on every showStep() call (register.js
 * removes then re-adds this class, forcing a reflow in between — a class
 * already present does not replay its animation on its own). */
.uh-register-step.uh-step-transition-in {
  animation: uh-register-step-in .25s ease-out;
}

@keyframes uh-register-step-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .uh-register-step.uh-step-transition-in {
    animation: none;
  }
}

/* Refinement pass: keep the 3-pill stepper on one row on a desktop-class
 * viewport ("14-inch MacBook Pro/desktop where space permits") instead
 * of the base .uh-steps-list rule's flex-wrap: wrap dropping the longest
 * label ("Password & Security") onto its own second row. register.php's
 * own outer column (col-xl-5, shared app-wide by every auth page — see
 * that file's own comment) only has real room to spare from the xl
 * breakpoint up, so nowrap is scoped to >=1200px rather than forced
 * everywhere; below that this rail keeps the same safe wrap-to-two-rows
 * fallback every other viewport already had. Sizing here is intentionally
 * more compact than .uh-kyc-steps' own (a wider app-shell context that
 * never had this problem) — own scope, .uh-kyc-steps is untouched. */
.uh-register-steps .uh-step-link {
  padding: .375rem .625rem .375rem .375rem;
  font-size: .75rem;
}

.uh-register-steps .uh-step-marker {
  width: 1.25rem;
  height: 1.25rem;
  font-size: .625rem;
}

.uh-register-steps .uh-steps-list {
  gap: .375rem;
}

@media (min-width: 1200px) {
  .uh-register-steps .uh-steps-list {
    flex-wrap: nowrap;
  }
}

/* Entity Type step — big selectable cards instead of bare radio dots,
 * the first impression of the wizard. :has() drives the checked-state
 * styling directly off the native input (broadly supported in every
 * browser this app already requires ES modules/optional-chaining for),
 * so no extra JS is needed just to keep a "selected card" visual in
 * sync with the radio's own state. */
.uh-kyc-entity-option {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: 1rem;
  height: 100%;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  cursor: pointer;
  transition: border-color .15s ease-in-out, background-color .15s ease-in-out;
}

.uh-kyc-entity-option:hover {
  border-color: var(--hk-border-secondary);
  background-color: var(--bs-tertiary-bg);
}

.uh-kyc-entity-option:has(input:checked) {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
}

.uh-kyc-entity-option-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
  transition: background-color .15s ease-in-out, color .15s ease-in-out;
}

.uh-kyc-entity-option:has(input:checked) .uh-kyc-entity-option-icon {
  background-color: var(--bs-primary);
  color: #fff;
}

.uh-kyc-entity-option-title {
  display: block;
  font-weight: 600;
  font-size: .875rem;
}

.uh-kyc-entity-option-desc {
  display: block;
  font-size: .75rem;
  color: var(--bs-secondary-color);
  margin-top: .125rem;
}

/* Review & Submit step's per-section "Edit" link — a plain small text
 * link, not a full button, so it reads as a secondary affordance next
 * to each summary section's own uppercase label. */
.uh-kyc-summary-edit {
  font-size: .75rem;
  text-decoration: none;
}

/* Submission confirmation state (shown in place of the wizard card
 * after a successful submit) — mirrors .uh-entity-header-icon's own
 * "tinted circle" recipe at a larger size for a centered success state. */
.uh-kyc-success-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin: 0 auto 1rem;
  border-radius: 50%;
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
}

/* Required Documents — a bordered list of compact rows instead of one
 * full .card per document (previously every requirement was its own
 * card-border card, which reads as oversized once there are more than
 * one or two). */
.uh-kyc-doc-list {
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
}

.uh-kyc-doc-row {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .875rem 1rem;
}

.uh-kyc-doc-row + .uh-kyc-doc-row {
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

.uh-kyc-doc-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
}

.uh-kyc-doc-body {
  min-width: 0;
  flex: 1 1 auto;
}

.uh-kyc-doc-title {
  font-weight: 600;
  font-size: .875rem;
}

.uh-kyc-doc-upload {
  max-width: 26rem;
}

@media (max-width: 575.98px) {
  .uh-kyc-doc-upload {
    max-width: none;
  }
}

/* Notifications UI polish pass: the Inbox's "Mark Read" row action
 * (components/table.js's actionsMenu(), rendered with
 * `btn-flush-dark flush-soft-hover` — the same icon-button treatment
 * every other row action/copy button in this app already uses) picks up
 * the vendor bundle's own hover fill on top of an already near-white
 * `--hk-bg-secondary`/`--bs-bg-gray-light-5` (both a `:before` pseudo-
 * element fill AND the button's own background-color, vendors/main.css)
 * — on a light card background this reads as a flat white/washed-out
 * block behind the check icon rather than a deliberate hover state.
 * Scoped to `[data-action="mark-read"]` — the one actionsMenu key this
 * feature ever emits, confirmed unique across the app — rather than
 * touching `.flush-soft-hover` itself, which every other icon button
 * (Copy, Replay, view actions elsewhere) still relies on unchanged.
 * `!important` matches the specificity vendors/main.css's own `:before`
 * rule already needs to beat (that rule outweighs a plain override at
 * equal source order otherwise). */
[data-action="mark-read"].flush-soft-hover:hover,
[data-action="mark-read"].flush-soft-hover:focus {
  background-color: transparent !important;
  border-color: transparent !important;
}

[data-action="mark-read"].flush-soft-hover:hover:before,
[data-action="mark-read"].flush-soft-hover:focus:before {
  background: transparent !important;
}

/* Webhook Deliveries compact-layout pass: same fix as "Mark Read" above,
 * same reason — the Replay action (webhooks-deliveries.js) is now a
 * single, always-visible icon button rather than the raw overflowing
 * "Replay" text it rendered as before (table.js's LABEL_ICON had no
 * entry for it), and at that new compact size the vendor's
 * `flush-soft-hover` fill (`:before` pseudo-element + button
 * background, vendors/main.css) reads as a small filled block sitting
 * in the table row rather than a deliberate hover state. Scoped to
 * `[data-action="replay"]` — the one actionsMenu() key this page emits
 * — for the same reason the Mark Read override above is scoped rather
 * than touching `.flush-soft-hover` itself, which every other icon
 * button in the app still relies on unchanged. The icon itself still
 * gets an opacity dip on hover — not a `color` swap: a live check in
 * both themes found `var(--bs-primary)` on the icon reads with almost
 * no contrast against dark mode's own near-black row background, which
 * a relative opacity change can't get wrong in either theme — so hover
 * remains a real, visible, accessible state just without the filled
 * circle. `:focus-visible` (keyboard) still gets the app's own
 * standard ring, same as `.uh-row-clickable:focus-visible` elsewhere in
 * this file, rather than the opacity dip alone. */
[data-action="replay"].flush-soft-hover:hover,
[data-action="replay"].flush-soft-hover:focus {
  background-color: transparent !important;
  border-color: transparent !important;
}

[data-action="replay"].flush-soft-hover:hover:before,
[data-action="replay"].flush-soft-hover:focus:before {
  background: transparent !important;
}

[data-action="replay"].flush-soft-hover:hover .uh-btn-icon-content {
  opacity: .65;
}

[data-action="replay"].flush-soft-hover:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}

/* Header polish pass: header.php's own `.uh-notif-badge` class has been
 * reserved on the bell icon's badge since Phase 1 but never actually had
 * CSS behind it — the `badge-xs` class sitting alongside it in the
 * markup doesn't exist anywhere in the vendored bundle (grepped, confirmed
 * unstyled), so the badge was rendering at Bootstrap's full default
 * `.badge` size (`.65em .35em` padding, `.75em` font) on top of an 18px
 * icon — oversized enough to visibly unbalance the header. Sized here
 * instead: a small, fixed-height pill just large enough for one or two
 * digits, with a border matching the page background (`--bs-body-bg`,
 * already theme-aware) so it reads as a distinct badge sitting ON the
 * icon rather than a shape merging into it — the same "bordered dot"
 * convention GitHub/Slack-style notification badges use.
 *
 * Position: vendors/main.css's `position-top-end-overflow-1` (still on
 * the element, still driving the `translate(50%,-50%)` corner pull) sets
 * `top`/`right` as a percentage of the *button's* 38px box, not the 18px
 * bell glyph centered inside it — that lands the badge nearer the
 * button's own corner than the bell's, short of the "cutting into the
 * glyph" look modern apps use. Overridden below in px, measured against
 * the glyph: an 18px icon centered in a 38px box sits (38-18)/2 = 10px
 * in from each edge, so anchoring the (pre-translate) badge position at
 * that same 10px and letting the vendor's translate(50%,-50%) pull it
 * outward from there lands the badge dead on the glyph's top-right
 * corner. `#uh-notif-badge` (id) beats the vendor's single-class
 * selector on specificity, so this wins regardless of stylesheet order —
 * more robust than relying on `!important` + source order alone. */
.uh-notif-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  font-size: .5625rem;
  line-height: 1;
  border: 1.5px solid var(--bs-body-bg);
}

#uh-notif-badge.uh-notif-badge {
  top: 10px !important;
  right: 10px !important;
}

/* Header polish pass: the account avatar toggle now carries
 * `btn btn-icon btn-flush-dark flush-soft-hover` (header.php) so its
 * clickable footprint matches the theme/notifications buttons' 38px box
 * exactly, instead of the bare 32px avatar circle it used to expose —
 * previously the one control in this row with a smaller hit target and
 * no hover feedback. That borrowed `.btn-icon` styling brings an
 * unwanted side effect along with it though: vendors/main.css's
 * `.btn > span:not(.badge):not(.sr-only):not([class^=spinner])
 * {width:100%;height:100%}` — written for a `.icon` wrapper span that's
 * meant to fill its button — also matches `.avatar`, since it's the
 * toggle's only direct-child span, and outranks `.avatar.avatar-xs`'s
 * own `width:2rem` on specificity (four :not()-argument classes beat
 * two plain ones). Left alone that stretches the 32px avatar circle out
 * to fill the full 38px button, losing the circular crop and the ring
 * of padding every other header icon has around it. `!important` here
 * restores avatar-xs's intended 32px, matching the fix
 * `.uh-sidebar-footer .nav-icon-wrap .avatar` above already applies for
 * the same avatar-in-a-fixed-box scenario in the sidebar footer. */
.uh-account-toggle > .avatar.avatar-xs {
  width: 2rem !important;
  height: 2rem !important;
}
