*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Root only, not body - see the matching note near the end of this file: on
   body, overflow-x:hidden forces overflow-y to `auto`, making body a scroll
   container that position:sticky would anchor to and never stick against. */
html {
    overflow-x: hidden;
}

/* Hide custom translated labels during page initialization to prevent FOUT */
body.loading-terminology [data-translate] {
    visibility: hidden !important;
}


/* The document itself is the app's one intentional scroll region (#sidebar
   is `position: fixed` and #app-header is `position: sticky`, so scrolling
   html/body is what makes long pages - a big filtered grid, a tall form -
   reachable while the sidebar/header stay in place; see #app-main's
   min-height: 100vh further down). That's correct architecture, not a bug -
   but the browser's default scrollbar is a chunky, unstyled OS widget that
   clashes with the rest of this theme. Give it the same thin/subtle
   treatment already used for .sidebar-menu's own internal scrollbar,
   instead of trying to suppress scrolling altogether (which would just
   make anything below one screen's worth of content unreachable). */
html {
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 0, 0, 0.18) transparent;
    /* #app-header is fixed over the top of the page, so anything the browser
       scrolls to on its own - scrollIntoView, focusing an invalid field, an
       #anchor jump - would otherwise land underneath it. */
    scroll-padding-top: calc(var(--shell-gap) + var(--header-height) + var(--content-gap));
}

html::-webkit-scrollbar {
    width: 8px;
}

html::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.18);
    border-radius: 999px;
}

html::-webkit-scrollbar-thumb:hover {
    background: rgba(0, 0, 0, 0.28);
}

html::-webkit-scrollbar-track {
    background: transparent;
}

:root {
    /* --- Typography ---
       The ONE font stack for the whole application. Every screen, every
       module, every layout resolves its type through these two tokens and
       nothing else - see playbook/design_system/tokens.md §3.

       Written here rather than repeated per page because it kept drifting:
       three dashboards had settled on 'Outfit' (a font this app does not ship
       and never loaded, so those pages silently rendered in the browser's
       default sans while everything beside them rendered in Inter), and five
       different monospace stacks had accumulated across the templates. A
       single token is the only version of this that stays true.

       --font-mono is for machine-shaped text only - error reference ids, JSON
       diffs, SQL, source-column names, hex codes. It is not a style choice
       available to ordinary copy.

       Inter is vendored at app/static/vendor/inter-font/ and MUST be linked by
       every layout (layout.html, layout_bare.html, layout_embed.html);
       otherwise these tokens fall through to Arial and the page quietly looks
       like a different application. */
    --font-sans: "Inter", "Gilroy", Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;

    /* Bootstrap builds its own components off these three, and they are NOT
       inherited from `body` - .tooltip, .popover, .toast, .modal, .dropdown-menu
       and .offcanvas each re-declare `font-family: var(--bs-body-font-family)`
       against Bootstrap's default system-UI stack. Without this remapping a
       tooltip or a dropdown rendered in Segoe UI on top of an Inter page, which
       is the subtle half of "the fonts don't match". */
    --bs-font-sans-serif: var(--font-sans);
    --bs-font-monospace: var(--font-mono);
    --bs-body-font-family: var(--font-sans);

    --sidebar-width: 230px;
    --sidebar-collapsed-width: 64px;
    /* Gap between the sidebar/header "floating panel" surfaces and the
       viewport edge / each other on desktop - not used on the mobile
       off-canvas drawer, which stays edge-to-edge by its own separate
       @media (max-width: 767.98px) rules. */
    --shell-gap: 12px;
    /* Gap between the content column and the two shell surfaces it sits
       against - the header card above it and the sidebar card to its left.
       Separate from --shell-gap so the outer frame inset (surfaces to
       viewport edge) can stay wider than this inner seam.
       8px, not the original 5px: the seam is where --shell-shadow below falls,
       and at 5px the shadow had no room to be seen - the two surfaces read as
       one flat sheet with a hairline in it. */
    --content-gap: 8px;
    /* Elevation for the shell's floating surfaces (the sidebar and header
       cards) and, one step lighter, for the cards inside the content column.
       These seams are just the canvas showing through, and #f4f5f7 canvas
       against a #ffffff card is a ~2% step - far too little to read as a
       boundary on its own, which is the entire job of these shadows. Two
       levels so the hierarchy stays legible: the shell floats above the
       canvas, content cards sit on it. */
    --shell-shadow: 0 6px 18px rgba(16, 24, 40, 0.10), 0 1px 3px rgba(16, 24, 40, 0.06);
    --panel-shadow: 0 2px 10px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04);
    /* Height of the floating header card. The header spans the full width
       ACROSS THE TOP (above the sidebar, not beside it), so both #sidebar's
       top inset and #app-main's top padding are derived from this - change
       it here and the whole shell follows. */
    --header-height: 62px;
    /* Usable height for a full-page workspace shell - one that fills the screen
       and scrolls inside its own panels rather than scrolling the page (the
       Report Designer and the Form Builder).
       Derived so the shell's bottom edge lands exactly on #sidebar's. Both
       start at the same seam (#sidebar's `top` and #app-main's `padding-top`
       are the same expression), so matching the bottom means subtracting that
       shared top inset and then #sidebar's own `bottom: var(--shell-gap)`.
       The workspace is the one place the misalignment shows: #sidebar is a
       full-height floating card right beside it, so any shortfall reads as the
       panel stopping short of the menu rather than as ordinary page padding.
       #main_body's 20px padding-bottom is deliberately NOT subtracted - it is
       zeroed for these pages instead (see the #main_body:has() rule below),
       because subtracting it is what left the shell 20px shy of the sidebar.
       Both shells previously hardcoded `calc(100vh - 160px)`, 66px short.
       Expressed in the same tokens the shell is positioned by, so retuning the
       header or the gaps carries the workspaces with it.
       Assumes the page renders no {% block secondary_nav %} tab bar - true of
       both current users, and a page that adds one must subtract its height. */
    --workspace-shell-height: calc(100vh - var(--shell-gap) - var(--header-height) - var(--content-gap) - var(--shell-gap));
    --header-border: #eff2f5;
    --brand-accent: #e94f7c;
    --dm-tab-border: #d9dfe7;

    /* --- Design Config (Administration -> Design Config) ---
       CORRECTION 14: every variable below is admin-configurable from that
       page and applied globally by app/static/js/theme.js on every
       authenticated page load - see app/schemas/design_config.py for the
       field <-> CSS var mapping (1:1, underscores -> dashes) and
       app/services/design_config_service.py for per-tenant storage. Values
       here are exactly what this app already shipped with before this
       feature existed (this renamed/split the old --dm-teal, --sidebar-*,
       --dm-panel-bg and --header-icon-color variables into these semantic
       names, matched 1:1 to real usage sites below - nothing here changes what
       the app looks like today; see individual rules for where --dm-panel-bg's
       two different real uses - nav hover vs. card/panel background - were
       split into --nav-hover-bg and --card-bg).
       NB: never write a wildcard variable name (asterisk) directly followed by
       a slash inside a CSS comment - those two characters ARE the comment
       terminator. This block previously read "--sidebar-" + asterisk + slash,
       which closed the comment four lines early; the remaining prose then
       parsed as CSS, and the intended terminator below became a stray token
       whose error recovery ran to the next semicolon - silently eating the
       --btn-primary-bg declaration on the far side of it. That is the token
       .sk-tabs, every button and the whole primary palette read. It went
       unnoticed for so long because each usage site writes
       var(--btn-primary-bg, #1b838b) and theme.js re-declares the value at
       runtime, so only an unauthenticated or static render showed it. */
    /* Buttons - Primary = Save/Update/Apply/Create actions, Secondary =
       Cancel/Close/Reset outline actions, Danger = destructive/Delete
       actions (no delete button exists in this port yet, but this is
       wired now so one just works when built). */
    --btn-primary-bg: #1b838b;
    --btn-primary-text: #ffffff;
    --btn-primary-hover-bg: #146267;
    --btn-secondary-bg: #ffffff;
    --btn-secondary-text: #1b838b;
    --btn-secondary-border: #1b838b;
    --btn-secondary-hover-bg: #f2f4f7;
    --btn-danger-bg: #e04f5f;
    --btn-danger-text: #ffffff;
    --btn-danger-hover-bg: #c53c4b;

    /* Navigation - Top menu is covered by --header-bg/--header-text below
       (Headers section), not a separate pair - see design_config.py. */
    --nav-side-bg: #ffffff;
    --nav-side-text: #344054;
    /* Must track design_config.py's nav_active_bg default - this is what
       paints on first load, before theme.js has applied the tenant's saved
       config, so a mismatch shows as a colour flash on every page. */
    --nav-active-bg: #1b838b;
    --nav-active-text: #ffffff;
    --nav-hover-bg: #f2f4f7;
    --nav-icon-color: #1b838b;
    --child-page-icon-color: #1b838b;

    /* DataTable / AG Grid - icons (original scope) plus header text, zebra
       striping, row hover/selected, and footer/pagination bar background
       (DataTable UI enhancement pass - see design_config.py's comment on
       this team for why grid_header_text_color isn't just a reuse of
       grid_sort_icon_color). */
    --grid-header-bg: #fafbfc;
    --grid-header-text-color: #98a2b3;
    --grid-sort-icon-color: #344054;
    --grid-search-icon-color: #98a2b3;
    --grid-pagination-icon-color: #344054;
    --grid-action-icon-color: #1b838b;
    /* No zebra striping - rows are plain white, separated by the hairline
       row border alone. */
    --grid-zebra-bg: #ffffff;
    --grid-row-hover-bg: #f2f4f7;
    --grid-row-selected-bg: #dcefef;
    --grid-footer-bg: #fafbfc;

    /* Form controls */
    --form-label-color: #344054;
    --form-focus-color: #1b838b;
    --form-required-color: #e04f5f;

    /* Links / Icons (general, non-nav) */
    --link-color: #4dabf7;
    --icon-color: #78829d;

    /* Cards / Panels */
    --card-bg: #ffffff;
    --card-border: #eef1f4;

    /* Form container radius - mandatory app-wide standard (Engineering
       Playbook, playbook/design_system/component_catalog.md §19; see also
       tokens.md §4). Applies to the OUTER wrapping surface of every
       create/edit/view/config form: modal dialogs (.modal-content,
       .nd-modal), a form section/card (.safekivi-card.sk-form-card - see the
       modifier's own comment for why the bare .safekivi-card base rule is
       untouched), and the handful of bespoke wizard/builder shells
       (.hc-editor-card, .fb-shell, .rd-shell, .stg-shell, .section-card,
       Super Admin's .sa-card/.mm-*-card). Never write a literal 26px in a
       new rule - reference this token so a future change to the standard is
       a one-line edit, not a repeat of this audit. */
    --form-container-radius: 26px;

    /* Headers / Page titles (also covers Navigation -> Top menu) */
    --header-bg: #ffffff;
    --header-text: #1e2129;
    --page-title-color: #1e2129;

    /* Alerts / Notifications - real, already-rendered Bootstrap .alert-*
       classes (e.g. #userFormError/#userListError), not a stub. */
    --alert-success-bg: #d1e7dd;
    --alert-success-text: #0f5132;
    --alert-warning-bg: #fff3cd;
    --alert-warning-text: #664d03;
    --alert-danger-bg: #f8d7da;
    --alert-danger-text: #842029;
    --alert-info-bg: #cff4fc;
    --alert-info-text: #055160;

    /* Tabs - simplified from the secondary-tab-bar's original two-tone
       gradient to flat configurable colors (a color picker can't drive a
       gradient stop pair) - deliberate, disclosed simplification. */
    --tab-bg: #f1f1f1;
    --tab-hover-bg: #d7eaea;
    --tab-active-color: #1b838b;
    --tab-text-color: #344054;
    --tab-icon-color: #1b838b;

    /* Breadcrumbs - not yet rendered anywhere in this port (no page uses
       breadcrumb navigation today) - wired end-to-end regardless so it
       just works the moment one exists; see .breadcrumb rules below. */
    --breadcrumb-text-color: #667085;
    --breadcrumb-active-color: #1e2129;
    --breadcrumb-separator-color: #98a2b3;
}

/* Generic links - real nav/menu/tab links all carry their own more-specific
   selector (#sidebar-brand a, .secondary-tab, .menu-* etc.) so they keep
   winning over this bare `a` rule regardless of source order; this only
   affects plain links that don't opt into one of those components. */
a {
    color: var(--link-color, #4dabf7);
}

a:hover {
    color: var(--link-color, #4dabf7);
    filter: brightness(0.85);
}

/* Page title utility - no page in this port currently renders a distinct
   "page title" element (admin pages use the secondary tab bar instead of
   an <h1>-style heading) - available now so future pages can opt in. */
.page-title {
    color: var(--page-title-color, #1e2129);
    font-weight: 700;
}

/* Breadcrumbs - not yet rendered anywhere in this port (see
   design_config.py's module docstring) - Bootstrap's own .breadcrumb/
   .breadcrumb-item classes, themed now so it just works the moment a page
   adds one. */
.breadcrumb .breadcrumb-item {
    color: var(--breadcrumb-text-color, #667085);
}

.breadcrumb .breadcrumb-item.active {
    color: var(--breadcrumb-active-color, #1e2129);
}

.breadcrumb .breadcrumb-item+.breadcrumb-item::before {
    color: var(--breadcrumb-separator-color, #98a2b3);
}

/* Alerts / Notifications - real, already-rendered Bootstrap alert classes
   (#userFormError/#userListError use `alert alert-danger` today) - themed
   here instead of relying on Bootstrap's own stock alert colors, and
   extended to the other 3 severities so all 4 are ready wherever used. */
.alert-success {
    background-color: var(--alert-success-bg, #d1e7dd);
    color: var(--alert-success-text, #0f5132);
    border-color: var(--alert-success-text, #0f5132);
}

.alert-warning {
    background-color: var(--alert-warning-bg, #fff3cd);
    color: var(--alert-warning-text, #664d03);
    border-color: var(--alert-warning-text, #664d03);
}

.alert-danger {
    background-color: var(--alert-danger-bg, #f8d7da);
    color: var(--alert-danger-text, #842029);
    border-color: var(--alert-danger-text, #842029);
}

.alert-info {
    background-color: var(--alert-info-bg, #cff4fc);
    color: var(--alert-info-text, #055160);
    border-color: var(--alert-info-text, #055160);
}

body {
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 500;
    color: #304050;
    margin: 0;
    /* The page/canvas colour. #app-main paints the same grey, but it only
       covers the content column - the shell gaps around the floating header
       and sidebar (and the strip above the sidebar, beside the full-width
       header) are body, and without this they fell through to the browser's
       default white. The login page and the bare/error layouts each paint
       their own full-viewport background over this, so they're unaffected. */
    background: var(--body-bg, #f4f5f7);
}

/* --- Sidebar ---
   A floating rounded panel below the full-width header, holding only the
   scrollable menu list (its brand/logo block was removed - the logo lives
   in the header alone now; see _menu.html). */
#sidebar {
    position: fixed;
    /* Starts below the full-width header card, on the same seam as #app-main's
       content so the two tops stay flush: gap + header + content-gap. */
    top: calc(var(--shell-gap) + var(--header-height) + var(--content-gap));
    left: var(--shell-gap);
    bottom: var(--shell-gap);
    width: var(--sidebar-width);
    background: var(--nav-side-bg, #ffffff);
    color: var(--nav-side-text, #344054);
    border: 1px solid var(--card-border, #eef1f4);
    border-radius: 16px;
    box-shadow: var(--shell-shadow);
    overflow: hidden;
    z-index: 1030;
    transition: width 0.2s ease, transform 0.2s ease;
    display: flex;
    flex-direction: column;
}

body.sidebar-collapsed #sidebar {
    width: var(--sidebar-collapsed-width, 64px) !important;
    min-width: var(--sidebar-collapsed-width, 64px) !important;
    max-width: var(--sidebar-collapsed-width, 64px) !important;
    transform: none !important;
}

/* The sidebar's own brand/logo block was removed (see _menu.html) - the
   SafeKivi mark + wordmark now live only in the full-width top header, so
   every #sidebar-brand / .sidebar-brand-logo-* / .brand-text rule that used
   to be here went with it. These two are NOT sidebar-only and stay: the
   header (_header.html), the login page and the Super Admin layout all still
   render the two-tone "Safe"+"Kivi" wordmark. */
.brand-text-safe {
    color: var(--btn-secondary-text, #344054);
}

.brand-text-kivi {
    color: #69be19;
}

/* Sidebar toggle, docked as the first control in the header card.
   Still exactly ONE control, and still outside #sidebar (which slides fully
   off-screen when collapsed, so a toggle anchored to it would leave no way to
   reopen the drawer) - but no longer `position: fixed` on the sidebar/content
   seam. That seam is 8px wide and this button is 32px, so it necessarily
   covered something: near the top, both .sidebar-menu's first row and every
   page's title block. In the header it is in normal flow inside .header-inner,
   so it cannot overlap page content at all.
   Inherits its shape, hover tint and sizing from .header-icon-btn, matching the
   notification/logout/assistant controls at the other end of the same card. */
/* Sidebar collapse toggle, docked in the sidebar's top-right corner.
   `position: fixed` against the viewport, NOT a child of #sidebar: the sidebar
   narrows to a 64px icon rail when collapsed and slides fully off-screen as a
   drawer on mobile, so a child would be clipped by the rail or carried away
   with the drawer, leaving no way to reopen it.

   The `left` value keeps the whole button INSIDE the sidebar's own box - 32px
   wide, 8px clear of the panel's right edge. The earlier version centred it on
   the sidebar/content seam, which meant it always overhung the content column
   by 8px; at this top offset that overhang ran straight through every page's
   title block. Sitting fully inside the panel is what removes the overlap while
   keeping the control attached to the thing it collapses. */
.sidebar-collapse-btn {
    position: fixed;
    top: calc(var(--shell-gap) + var(--header-height) + var(--content-gap) + 6px);
    left: calc(var(--sidebar-width) + var(--shell-gap) - 40px);
    background: var(--nav-side-bg, #ffffff);
    border: none;
    color: #3e97ff;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    cursor: pointer;
    z-index: 1031;
    transition: left 0.2s ease;
}

.sidebar-collapse-btn:hover {
    /* Sits on the sidebar's own surface, so it takes the sidebar's hover shade
       rather than the light-gray card tint used by header controls. */
    background: var(--nav-hover-bg, #f4f5f7);
}

/* Collapsed: the rail is only 64px wide, so there is no "inside, clear of the
   right edge" to sit in without landing on the icons. Centre it in the rail
   instead and let the menu below start further down (see the padding-top on
   .sidebar-menu just after this), which gives the button its own row at the top
   of the rail rather than putting it on top of the first icon. */
body.sidebar-collapsed .sidebar-collapse-btn {
    left: calc(var(--shell-gap) + (var(--sidebar-collapsed-width, 64px) - 32px) / 2) !important;
}

/* Sidebar collapsed icon rail styling.
   padding-top clears the collapse button docked above the first icon: the
   button's bottom edge sits 6px + 32px = 38px below the rail's top, so 44px
   leaves an 6px gap before the first icon starts. */
body.sidebar-collapsed .sidebar-menu {
    padding: 44px 4px 24px !important;
}

body.sidebar-collapsed .menu-team-toggle {
    justify-content: center !important;
    padding: 10px 0 !important;
    color: var(--nav-side-text, #9899ac) !important;
    border-radius: 999px !important;
    width: 44px !important;
    height: 44px !important;
    margin: 4px auto !important;
}

body.sidebar-collapsed .menu-team-toggle.active {
    background: var(--nav-active-bg) !important;
    color: var(--nav-active-text) !important;
}

body.sidebar-collapsed .menu-team-icon {
    width: 24px !important;
    height: 24px !important;
    font-size: 1.15rem !important;
    margin: 0 auto !important;
}

body.sidebar-collapsed .menu-team-label,
body.sidebar-collapsed .menu-caret,
body.sidebar-collapsed .menu-subitems,
body.sidebar-collapsed .menu-link-label {
    display: none !important;
}

body.sidebar-collapsed .sidebar-menu a {
    display: flex !important;
    justify-content: center !important;
    align-items: center !important;
    padding: 10px 0 !important;
    text-align: center !important;
    color: var(--nav-side-text, #9899ac) !important;
    border-radius: 999px !important;
    width: 44px !important;
    height: 44px !important;
    margin: 4px auto !important;
}

body.sidebar-collapsed .sidebar-menu a.active {
    background: var(--nav-active-bg) !important;
    color: var(--nav-active-text) !important;
}

body.sidebar-collapsed .sidebar-menu a .menu-link-icon {
    margin-right: 0 !important;
    font-size: 1.15rem !important;
}

.brand-mark {
    display: inline-block;
    width: 14px;
    height: 14px;
    background: var(--brand-accent);
    border-radius: 3px;
    transform: rotate(45deg);
}

.sidebar-menu {
    list-style: none;
    margin: 0;
    padding: 12px;
    /* min-height: 0 is required for a flex child to actually be allowed to
       shrink/scroll instead of forcing its parent to grow. (The old
       `position: relative; top: -30px` nudge here existed only to claw back
       the oversized #sidebar-brand block's height - that block is gone now,
       so the offset would just crop the first menu item.) */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.sidebar-menu a,
.menu-team-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    margin-bottom: 2px;
    color: var(--nav-side-text);
    text-decoration: none;
    background: none;
    border: none;
    border-radius: 12px;
    text-align: left;
    font-weight: 600;
    font-size: 0.87rem;
    cursor: pointer;
}

/* Hover deliberately does NOT use --nav-active-text: that variable is the
   text colour for the *selected* item, which sits on the solid dark
   --nav-active-bg pill, so it's near-white - unreadable against this light
   --nav-hover-bg. Hover only deepens the background. */
.sidebar-menu a:hover,
.menu-team-toggle:hover {
    background: var(--nav-hover-bg);
    color: var(--nav-side-text);
}

/* Decorative icon before each team label - generic/neutral (not asserting
   what any specific real menu category "means"), since menu_items/
   menu_categories has no icon column to draw a real per-item icon from.
   Flagging rather than inventing per-category icon assignments. */
.menu-team-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    background: transparent;
    color: inherit;
    font-size: 0.95rem;
    flex-shrink: 0;
}

.menu-team-label {
    flex: 1;
}

.menu-caret {
    font-size: 0.7rem;
    color: inherit;
    opacity: 0.8;
    transition: transform 0.15s ease;
}

.menu-team-toggle[aria-expanded="true"] .menu-caret {
    transform: rotate(180deg);
}

.menu-subitems {
    list-style: none;
    margin: 4px 0 8px;
    padding: 0;
    background: none;
}

.menu-subitems a {
    padding-left: 40px;
    padding-top: 8px;
    padding-bottom: 8px;
    font-weight: 500;
    font-size: 0.9rem;
    position: relative;
}

/* Bullet marker before each sub-item label, matching the reference's small
   dot markers. If a sub-item has a custom icon, we hide the dot. */
.menu-subitems a::before {
    content: "";
    position: absolute;
    left: 22px;
    top: 50%;
    transform: translateY(-50%);
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.6;
}

/* Sub-items carry their own icon (renderMenuLink always adds .has-icon), so
   the bullet dot is hidden - but this rule used to also force padding-left
   back to 22px, i.e. LESS than a top-level item's 16px+icon, which flattened
   the hierarchy: a child like "Data Library" sat at the same indent as its
   own parent "Control Center". Indent per depth instead, so nesting reads. */
.menu-subitems a.has-icon {
    padding-left: 38px !important;
}

.menu-subitems .menu-subitems a.has-icon {
    padding-left: 54px !important;
}

.menu-subitems .menu-subitems .menu-subitems a.has-icon {
    padding-left: 70px !important;
}

/* A team nested inside another team: the extra <li class="menu-link-li">
   wrapper nav.js emits means the `>li>` child selectors further down never
   match these, so indent them by descendant selector here. */
.menu-subitems .menu-team-toggle {
    padding-left: 38px;
}

.menu-subitems .menu-subitems .menu-team-toggle {
    padding-left: 54px;
}

.menu-subitems a.has-icon::before {
    display: none !important;
    /* Hide dot */
}

/* Vertical connector rail down the left of each nested group - the visual
   cue that these items belong to the team above them. */
.menu-subitems {
    position: relative;
}

.menu-subitems::before {
    content: "";
    position: absolute;
    left: 25px;
    top: 2px;
    bottom: 2px;
    width: 1px;
    background: var(--card-border, #e6e9ef);
    opacity: 0.9;
}

.menu-subitems .menu-subitems::before {
    left: 41px;
}

.menu-subitems .menu-subitems .menu-subitems::before {
    left: 57px;
}

.menu-subitems a.has-icon .menu-link-icon {
    width: 22px;
    text-align: center;
    margin-right: 8px !important;
    font-size: 1rem !important;
    display: inline-block !important;
    vertical-align: middle !important;
    opacity: 0.8 !important;
}

/* See the .sidebar-menu a:hover note above for why hover doesn't reuse
   --nav-active-text. */
.menu-subitems a:hover {
    background: var(--nav-hover-bg);
    color: var(--nav-side-text);
}

.menu-subitems a.active {
    background: var(--nav-active-bg);
    color: var(--nav-active-text);
    font-weight: 700;
    border-radius: 12px;
}

/* Top-level flat links (category id==1 items, or a single-item category
   rendered flat) get the same active highlight as sub-items. */
.menu-link-item>a.active {
    background: var(--nav-active-bg);
    color: var(--nav-active-text);
    border-radius: 12px;
}

/* A parent toggle carries .active only to mean "the current page lives
   somewhere inside me" - nav.js sets it so the team auto-expands (see
   renderMenuNode's hasActiveItem). It must NOT wear the same filled pill as
   the page itself, otherwise two items look selected at once (e.g. both
   "Control Center" and "Data Library"). The expanded caret plus the child's
   own pill already show where you are, so this just reads as emphasis.
   Exception: the collapsed icon rail hides children entirely, so there the
   parent IS the only possible indicator - it keeps the filled pill via
   body.sidebar-collapsed .menu-team-toggle.active further up. */
.menu-team-toggle.active {
    background: transparent;
    color: var(--nav-side-text);
    font-weight: 700;
}

.menu-show-more {
    display: block;
    padding: 8px 12px 8px 40px;
    background: none;
    border: none;
    color: var(--link-color, #4dabf7);
    font-weight: 600;
    font-size: 0.9rem;
    cursor: pointer;
    text-align: left;
}

.menu-show-more:hover {
    text-decoration: underline;
}

/* Menu Builder unlimited-depth support: a team can now nest inside another
   team's .menu-subitems (previously teams only ever appeared at the top
   level, since the menu_categories table had no self-nesting). Indent
   progressively by [data-depth] so deeper levels read as a clear hierarchy;
   depth 1 keeps the original fixed 40px look, deeper levels add 16px each. */
.menu-subitems[data-depth] a,
.menu-subitems[data-depth]>li>.menu-team-toggle {
    padding-left: calc(24px + (attr(data-depth type(<number>), 1) * 16px));
}

/* attr() with a type() argument isn't supported everywhere yet - the
   fallback below (plain nth-level selectors) guarantees correct indentation
   on every browser regardless of attr() support for the rule above. */
.menu-subitems a,
.menu-subitems>li>.menu-team-toggle {
    padding-left: 40px;
}

.menu-subitems .menu-subitems a,
.menu-subitems .menu-subitems>li>.menu-team-toggle {
    padding-left: 56px;
}

.menu-subitems .menu-subitems .menu-subitems a,
.menu-subitems .menu-subitems .menu-subitems>li>.menu-team-toggle {
    padding-left: 72px;
}

.menu-subitems .menu-subitems .menu-subitems .menu-subitems a,
.menu-subitems .menu-subitems .menu-subitems .menu-subitems>li>.menu-team-toggle {
    padding-left: 88px;
}

.menu-subitems .menu-subitems a::before {
    left: 38px;
}

.menu-subitems .menu-subitems .menu-subitems a::before {
    left: 54px;
}

.menu-subitems .menu-subitems .menu-subitems .menu-subitems a::before {
    left: 70px;
}

.menu-subitems .menu-team-icon {
    width: 18px;
    height: 18px;
    font-size: 0.7rem;
}

.menu-link-icon {
    margin-right: 2px;
}

/* Thin custom scrollbar matching the sidebar (WebKit browsers; Firefox falls
   back to `scrollbar-color` below). Scoped to .sidebar-menu, which is the
   actual scrolling element, rather than its #sidebar shell. */
.sidebar-menu {
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}

.sidebar-menu::-webkit-scrollbar {
    width: 6px;
}

.sidebar-menu::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.2);
    border-radius: 999px;
}

.sidebar-menu::-webkit-scrollbar-track {
    background: transparent;
}

/* --- App shell / header & Sticky Layout --- */
/* No height cap and no overflow lock: the document is the app's scroll region
   (see the scrollbar-styling note at the top of this file). #sidebar and
   #app-header are position:fixed, so they stay put while long pages scroll
   the page itself. Anything that pins html/body to 100vh + overflow:hidden
   turns #main_body back into a nested scrollport - which is what this
   deliberately does not do. */
html,
body {
    margin: 0;
    padding: 0;
}

#app-main {
    /* --shell-gap clears #sidebar's own left inset; --content-gap is then the
       seam between the sidebar's right edge and this column. */
    margin-left: calc(var(--sidebar-width) + var(--shell-gap) + var(--content-gap));
    /* Top padding clears the fixed, full-width #app-header above (its own gap
       + its height) and then leaves the same seam below it. Right/bottom keep
       the wider --shell-gap: those edges face the viewport, not another
       surface. */
    padding: calc(var(--shell-gap) + var(--header-height) + var(--content-gap)) var(--shell-gap) var(--shell-gap) 0;
    transition: margin-left 0.2s ease;
    /* min-height, not height: the column fills the viewport when a page is
       short and grows past it when a page is tall, letting the document scroll.
       A fixed height + overflow:hidden here would trap tall content instead. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--body-bg, #f4f5f7);
}

body.sidebar-collapsed #app-main {
    margin-left: calc(var(--sidebar-collapsed-width, 64px) + var(--shell-gap) + var(--content-gap)) !important;
    width: calc(100% - var(--sidebar-collapsed-width, 64px) - var(--shell-gap) - var(--content-gap)) !important;
}

/* Spans the FULL viewport width across the top, above #sidebar - not beside
   it. Fixed rather than moved in the DOM (it still lives inside #app-main in
   layout.html) so no template restructuring is needed; #sidebar's top inset
   and #app-main's top padding below both offset by --header-height to leave
   room for it. z-index sits above #sidebar (1030) since it now crosses over
   the sidebar's column. */
#app-header {
    position: fixed;
    /* Spans the viewport edges and carries the inset as PADDING rather than as
       top/left/right offsets. The card inside still sits --shell-gap in on every
       side, so nothing moves visually - but this element's own opaque background
       now covers the whole band above the content instead of leaving the inset
       as a transparent window for the scrolling page to show through.
       The band has to reach --content-gap PAST the card's bottom edge: content
       begins at #app-main's padding-top (gap + header + content-gap) and would
       otherwise be visible in that seam on its way up behind the header. Height
       here therefore equals that padding-top exactly. */
    top: 0;
    left: 0;
    right: 0;
    padding: var(--shell-gap) var(--shell-gap) var(--content-gap);
    background: var(--body-bg, #f4f5f7);
    z-index: 1035;
    transition: none;
}

.header-inner {
    display: flex;
    align-items: center;
    gap: 18px;
    background: var(--header-bg, #ffffff) !important;
    color: var(--header-text, #1e2129) !important;
    /* min-height (not height) so a long tenant site name can wrap without
       being clipped. */
    min-height: var(--header-height);
    padding: 0 20px;
    border-radius: 16px;
    box-shadow: var(--shell-shadow);
    width: 100%;
}

/* The old "joined header + secondary nav bar" rules lived here: they squared
   off the header's bottom corners so a .cc-nav/.secondary-tab-bar/.dl-nav
   directly beneath it read as one continuous block. That no longer applies -
   #app-header is now a fixed, full-width card spanning above the sidebar,
   while those tab bars stay in the content column below it, so the header
   keeps all four corners rounded and each tab bar is its own rounded card
   (see .secondary-tab-bar / .cc-area-bar / .dl-cat-bar below). */

.cc-nav,
.secondary-tab-bar,
.dl-nav {
    flex: 0 0 auto;
    position: relative;
    z-index: 1010;
}

/* Page action toolbars & headings - sticky directly below tab bar */
.page-action-bar,
.mb-toolbar,
.sticky-page-bar {
    position: sticky;
    /* Sticky anchors to the viewport (the document is the scroll container), so
       a bare -12px would park this behind the fixed header and it would never
       be seen. Offset to the shell seam, less the 12px padding below, so the
       bar's own content lands on the seam and that padding covers the strip
       between the header card and the bar. margin-top cancels the padding so
       none of this adds layout space. */
    top: calc(var(--shell-gap) + var(--header-height) + var(--content-gap) - 12px);
    margin-top: -12px;
    padding-top: 12px;
    padding-bottom: 12px;
    background: #f4f5f7;
    z-index: 1000;
}

/* There used to be a `body.sidebar-collapsed #app-header { padding-left: 0 }`
   rule here, from when the header started at the sidebar's right edge and had
   to clear the floating toggle that docked near the viewport edge on collapse.
   It was annotated as a no-op once #app-header became full-width and fixed,
   but it never was one: the base rule above sets padding-left to --shell-gap,
   so zeroing it slid the whole header card 12px left the moment the sidebar
   collapsed, out of line with #sidebar's own `left: var(--shell-gap)`.
   Deleted rather than re-annotated - the collapsed state wants the same shell
   inset as every other state, which the base rule already gives it. */

/* Plain outline icons on the header's own background - not solid filled
   brand-colour discs. The filled treatment put three heavy circles next to
   the tenant name and fought the brand mark for attention; a subtle tinted
   disc appears on hover instead. */
.header-icon-btn {
    background: transparent !important;
    border: none;
    color: var(--icon-color, #667085) !important;
    font-size: 1.15rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    text-decoration: none;
    position: relative;
    transition: background-color 0.2s ease, color 0.2s ease;
}

.header-icon-btn:hover {
    background: var(--card-bg, #f2f4f7) !important;
    color: var(--header-text, #1e2129) !important;
}

.header-icon-btn i {
    color: inherit !important;
}

.header-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--header-text);
    text-decoration: none;
    font-weight: 700;
}

/* Line the header's divider up with the sidebar's right edge, so the
   SafeKivi mark occupies the sidebar's column and the tenant block starts
   where the content column starts.

   The divider's x = card padding-left + this width + .header-inner's flex
   gap, and it needs to equal --sidebar-width (the card and the sidebar
   share the same --shell-gap left inset, so that offset cancels out) -
   hence subtracting those two spacings here. Only applied from 768px up:
   below that the sidebar is an off-canvas drawer with no fixed rail to
   align to, so the brand just takes its natural width. */
@media (min-width: 768px) {
    .header-brand {
        flex: 0 0 calc(var(--sidebar-width) - 20px - 18px);
    }
}

.header-brand-logo {
    width: 28px;
    height: 28px;
    object-fit: contain;
    display: inline-block;
    vertical-align: middle;
}

.sidebar-brand-logo {
    width: 28px;
    height: 28px;
    object-fit: contain;
    display: inline-block;
    flex-shrink: 0;
    vertical-align: middle;
}

.header-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: #1e3a5c;
    color: #ffffff;
    font-weight: 800;
}

/* Tenant slot - sits next to (not instead of) .header-brand; see the
   comment in _header.html for why this exists as a separate element
   rather than reusing the SafeKivi logo/text spots. */
.header-divider {
    width: 1px;
    align-self: stretch;
    margin: 4px 0;
    background: var(--header-border, #eff2f5);
}

.header-tenant {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

/* Height-constrained but free-width: tenant logos are commonly wide
   wordmarks, and forcing them into a 28x28 square made them render tiny
   (object-fit: contain letterboxes to the shorter side). max-width keeps a
   very wide logo from crowding out the site name beside it. */
.header-tenant-logo {
    height: 30px;
    width: auto;
    max-width: 130px;
    border-radius: 6px;
    object-fit: contain;
    flex: none;
}

.header-tenant-fallback {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: var(--card-bg, #f2f4f7);
    color: var(--header-text, #1e2129);
    font-weight: 800;
    font-size: 0.7rem;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
}

.header-tenant-text {
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 0;
}

.header-tenant-name {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--header-text, #1e2129);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 200px;
}

.header-tenant-label {
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #98a2b3;
}

@media (max-width: 575.98px) {

    .header-divider,
    .header-tenant {
        display: none !important;
    }
}

.header-spacer {
    flex: 1;
}

.header-icons {
    display: flex;
    align-items: center;
    /* Metronic's header row spaces its icons out generously rather than
       packing them edge-to-edge. */
    gap: 14px;
}

/* Decorative placeholder icons (search/messages/activity/apps/appearance -
   see the CORRECTION 2 note in partials/_header.html) look identical to
   real icons but aren't clickable - cursor signals that without visually
   graying them out and breaking the requested Metronic look. */
.header-icon-btn[aria-disabled="true"] {
    cursor: default;
}

/* Small dot indicator (Metronic style) instead of an always-visible "0"
   count badge - only shown when there's an actual unread notification. */
.notification-dot {
    position: absolute;
    top: 7px;
    right: 9px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    /* Red, not green - this marks "you have unread notifications", i.e.
       something needing attention, and now has to read as such against a
       plain (no longer solid-filled) bell icon. */
    background: var(--btn-danger-bg, #e04f5f);
    border: 1.5px solid var(--header-bg, #ffffff);
}

.header-avatar-wrap {
    margin-left: 4px;
}

/* Outer button (Metronic-style: circular avatar + a small dropdown chevron
   beside it, rather than the avatar alone being the whole clickable toggle
   with no visible affordance that it opens a menu). */
.header-avatar-toggle {
    display: inline-flex;
    align-items: center;
    background: none;
    border: none;
    padding: 0;
    border-radius: 50%;
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.header-avatar-toggle:hover {
    background: none;
    transform: scale(1.05);
}

.header-avatar-toggle:hover .header-avatar {
    box-shadow: 0 2px 8px rgba(16, 24, 40, 0.14);
}

/* Soft tinted disc with brand-coloured initials, rather than a solid brand
   fill - keeps the avatar readable as a person/account affordance without
   competing with the primary buttons elsewhere on the page. */
.header-avatar,
.header-avatar.role-admin,
.header-avatar.role-inspector,
.header-avatar.role-user {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    color: var(--tab-active-color, #1b838b) !important;
    font-weight: 700;
    font-size: 0.8rem;
    background: var(--tab-hover-bg, #e6f4f4) !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.header-avatar #userAvatarInitials,
.header-avatar span {
    color: var(--tab-active-color, #1b838b) !important;
}

.header-avatar-menu {
    border-radius: 10px;
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.12);
    border: none;
    padding: 8px;
    min-width: 200px;
}

.header-avatar-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    border-radius: 6px;
    padding: 8px 10px;
}

.header-avatar-menu .dropdown-item:hover {
    background: var(--card-bg, #f4f5f7);
}

.header-avatar.role-user {
    background: #6c757d;
}

/* --- Reusable secondary admin tab bar (see layout.html's secondary_nav block) ---
   Ported directly from the real Data Manager tab strip's CSS
   (styles/safekivi_theme.css `.nav-tabs-ss` + the `.dm-nav-tabs-ss` override):
   thin rounded tabs on a light gradient, 2px margin between them, bold
   13px/700-weight text, and an inner bottom-bordered <span> that carries
   the resting/active/hover accent line rather than a background swap. */
.secondary-tab-bar {
    display: flex;
    flex-wrap: wrap;
    background: #ffffff;
    padding: 0 0.5rem;
    min-height: 0;
}

/* Section divider label inside the Edit Template modal (Settings block). */
.settings-section-label {
    display: block;
    font-weight: 700;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #55637a;
    margin: 0.25rem 0 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid #e9edf3;
}

/* --- Control Center two-level nav (parent "area" tabs + nested "sub" tabs) ---
   Redesigned for a clear, modern parent/child hierarchy:
   - .cc-area-bar: the top-level areas (Audit, plus future areas). Bold,
     spaced-out pills with a solid underline indicator on the active item -
     reads as the primary navigation level.
   - .cc-sub-bar: the active area's children (Templates, Questions,
     Categories, ...). Visually "hangs" off the area bar via a tinted
     background + a small "Audit" connector label on the left, so it's
     unmistakably a nested row rather than a second row of identical tabs.
   Both rows keep the same responsive horizontal-scroll behavior as the
   original .secondary-tab-bar so nothing breaks on narrow viewports. */
/* ── Content column rhythm ────────────────────────────────────────────────────
   Every seam in the shell is --content-gap, so the spacing reads the same on
   every module:

     header card  -> sidebar card      (#sidebar top)
     header card  -> nav strip         (#app-main padding-top)
     nav strip    -> page content      (.cc-nav / .dl-nav padding-bottom)
     header card  -> page content      (pages with no nav strip)

   The corollary is that a top-level content wrapper must NOT add a top inset of
   its own - that is what produced six different gaps (0/4/12/16/20/24px) across
   the modules. Bottom padding is each page's own business; only the top and the
   sides belong to the shell.
   ──────────────────────────────────────────────────────────────────────────── */

/* No side padding: this block renders as a direct child of #app-main (outside
   #main_body), which already owns both side insets, so its card edges line up
   with the header card above and the page content below. Mobile re-adds the
   gutter, matching what #main_body does there. */
.cc-nav {
    background: #f4f5f7;
    padding: 0 0 var(--content-gap) 0;
    border-bottom: none;
}

/* Bootstrap's .container-fluid carries a 12px gutter (--bs-gutter-x * .5), and
   the near-universal `py-3` adds 16px on top. As the top-level content wrapper
   both fight the shell, so neutralise them here rather than editing the ~20
   templates that use it. Nested .container-fluid keeps its own gutter - only
   the outer one is redundant. padding-bottom is left alone. */
#main_body>.container-fluid {
    /* !important only on the top: `py-3` is a Bootstrap *utility*, so it ships
       with !important and a plain declaration loses to it however specific.
       The side paddings come from the .container-fluid component rule, which
       carries no !important, so they need none here. padding-bottom is left to
       `py-3` deliberately - the page keeps its own bottom breathing room. */
    padding-top: 0 !important;
    padding-left: 0;
    padding-right: 0;
}

.cc-area-bar {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none !important;
    -ms-overflow-style: none !important;
    gap: 0.25rem;
    padding: 4px 12px;
    background: #ffffff;
    /* Standalone rounded card - no longer joined to the header above. */
    border-radius: 10px;
    border: 1px solid var(--header-border, #eff2f5);
    box-shadow: var(--panel-shadow);
    width: 100%;
}

/* Still joins DOWNWARD to its own nested sub-tab row. */
.cc-area-bar:has(+ .cc-sub-bar) {
    border-radius: 10px 10px 0 0 !important;
    border-bottom: 1px solid #eff2f5 !important;
}

.cc-area-bar::-webkit-scrollbar {
    display: none !important;
    width: 0 !important;
    height: 0 !important;
}

.cc-area-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.55rem 1rem;
    border-radius: 999px;
    text-decoration: none;
    color: var(--tab-text-color, #344054);
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: 0.1px;
    white-space: nowrap;
    border-bottom: none;
    background: var(--tab-bg, transparent);
    transition: background-color 0.18s ease, color 0.18s ease;
}

.cc-area-tab i {
    font-size: 0.95em;
    color: var(--tab-icon-color, #1b838b);
    transition: color 0.18s ease;
}

.cc-area-tab:hover {
    background: var(--tab-hover-bg, #f1f5f9);
    color: var(--tab-text-color, #1f2937);
    text-decoration: none;
    border-radius: 999px;
}

.cc-area-tab:hover i {
    color: var(--tab-icon-color, var(--tab-active-color, #1b838b));
}

.cc-area-tab.active {
    color: var(--tab-active-color, #1b838b);
    background: var(--tab-hover-bg, #e6f4f4);
    border-radius: 999px;
}

.cc-area-tab.active i {
    color: var(--tab-active-color, #1b838b);
}

.cc-area-tab.is-disabled {
    color: #b7bfc9;
    cursor: not-allowed;
}

.cc-area-tab.is-disabled i {
    color: #d3d8de;
}

.cc-sub-bar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: #ffffff;
    border-radius: 0 0 10px 10px;
    border: 1px solid var(--header-border, #eff2f5);
    border-top: none;
    padding: 6px 16px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
    width: 100%;
    overflow-x: auto;
    scrollbar-width: none !important;
    -ms-overflow-style: none !important;
}

.cc-sub-bar::-webkit-scrollbar {
    display: none !important;
    width: 0 !important;
    height: 0 !important;
}

.cc-sub-label {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #94a0b3;
    padding-right: 0.5rem;
    border-right: 1px solid #dbe2ea;
}

.cc-sub-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}

.cc-sub-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    text-decoration: none;
    color: var(--tab-text-color, #344054);
    font-weight: 700;
    font-size: 0.85rem;
    white-space: nowrap;
    background: var(--tab-bg, transparent);
    border: none;
    transition: background-color 0.18s ease, color 0.18s ease, transform 0.12s ease;
}

.cc-sub-tab i {
    font-size: 0.85rem;
    color: var(--tab-icon-color, #1b838b);
    transition: color 0.18s ease;
}

.cc-sub-tab:hover {
    background: var(--tab-hover-bg, #f1f5f9);
    color: var(--tab-text-color, #1f2937);
    text-decoration: none;
    border-radius: 999px;
}

.cc-sub-tab:hover i {
    color: var(--tab-icon-color, var(--tab-active-color, #1b838b));
}

.cc-sub-tab.active {
    color: var(--tab-active-color, #1b838b);
    background: var(--tab-hover-bg, #e6f4f4);
    border-radius: 999px;
    box-shadow: none;
}

.cc-sub-tab.active i {
    color: var(--tab-active-color, #1b838b);
}

.cc-sub-tab.active:hover {
    background: var(--tab-hover-bg, #f1f5f9);
    color: var(--tab-active-color, #1b838b);
}

/* ── .sk-tabs — in-page underline tabs ─────────────────────────
   For switching views WITHIN one page (Training -> Reports' report
   categories), as opposed to .cc-sub-tab/.dl-sub-tab, which navigate between
   pages. Bootstrap's own .nav-tabs is deliberately not used for these: it
   paints Bootstrap blue (#0d6efd) with a boxy bordered active tab, and neither
   appears anywhere else in this app.

   The .nav / .nav-link markup is kept even though none of Bootstrap's tab
   *styling* is: its tab plugin (data-bs-toggle="tab") drives the panes, toggles
   .active for us, and looks for a parent .nav to wire arrow-key navigation.
   Every rule below is scoped under .sk-tabs so no other .nav on the site is
   touched, and .sk-tabs .nav-link.active carries three classes so it outranks
   Bootstrap's own two-class .nav-link.active regardless of load order.

   The indicator colour is --btn-primary-bg: the site primary, "Primary button
   background" in Control Center -> Design Config, so the underline follows a
   tenant's configured brand colour. (Design Config also exposes a separate
   --tab-active-color, which the module nav pills above use; this row follows
   the primary by request - swapping which variable these three rules read is
   the whole change if that should ever be revisited.) */
.sk-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    list-style: none;
    padding-left: 0;
    margin: 0 0 1rem;
    border-bottom: 1px solid var(--header-border, #eff2f5);
}

.sk-tabs .nav-item {
    margin-bottom: 0;
}

.sk-tabs .nav-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.6rem 0.9rem;
    font-size: 0.875rem;
    font-weight: 700;
    color: #64748b;
    background: none;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    /* Pulls the indicator down onto the container's own 1px rule so the two
       read as a single line rather than as two stacked borders. */
    margin-bottom: -1px;
    white-space: nowrap;
    cursor: pointer;
    transition: color 0.18s ease, border-color 0.18s ease;
}

.sk-tabs .nav-link i {
    font-size: 0.9rem;
    color: #94a3b8;
    transition: color 0.18s ease;
}

.sk-tabs .nav-link:hover {
    color: #1e293b;
    background: none;
}

.sk-tabs .nav-link:hover i {
    color: var(--btn-primary-bg, #1b838b);
}

/* Keyboard focus has to be visible without the boxy Bootstrap tab returning:
   the ring is drawn on the top corners only, since the bottom edge is the
   indicator. */
.sk-tabs .nav-link:focus-visible {
    outline: none;
    color: #1e293b;
    border-radius: 6px 6px 0 0;
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.18);
}

.sk-tabs .nav-link.active {
    color: var(--btn-primary-bg, #1b838b);
    border-bottom-color: var(--btn-primary-bg, #1b838b);
    background: none;
}

.sk-tabs .nav-link.active i {
    color: var(--btn-primary-bg, #1b838b);
}

/* Compact variant for a tab strip nested INSIDE a card - e.g. the HTML Body /
   Plain Text / Preview switch inside Email Setup's template editor, which sits
   flush on the editor below it and shouldn't carry the full-size row's weight. */
.sk-tabs.sk-tabs--sm .nav-link {
    padding: 0.4rem 0.7rem;
    font-size: 0.8rem;
}

.sk-tabs.sk-tabs--sm .nav-link i {
    font-size: 0.82rem;
}

/* Long category labels wrap to a second row rather than scrolling sideways;
   the rule stays under whichever row ends up last. */
@media (max-width: 575.98px) {
    .sk-tabs .nav-link {
        padding: 0.55rem 0.7rem;
        font-size: 0.82rem;
    }
}

@media (max-width: 768px) {
    .cc-area-bar {
        padding: 0.4rem 0.6rem 0;
    }

    .cc-area-tab {
        padding: 0.5rem 0.7rem;
        font-size: 0.88rem;
    }

    .cc-area-tab span {
        display: none;
    }

    .cc-area-tab i {
        font-size: 1.05rem;
    }

    .cc-sub-bar {
        padding: 0.4rem 0.6rem;
    }

    .cc-sub-label span {
        display: none;
    }
}

/* ============================================================
   Data Library – categorized two-row EHS navigation (dl-nav)
   Mirrors the cc-nav pattern but with four top-level category
   pills and a tinted sub-tab row per category.
   ============================================================ */
/* Side padding matches .cc-nav's - i.e. none. This renders as a direct child of
   #app-main (outside #main_body), which already owns both side insets, so its
   card edges line up with the header card above and the page content below.
   Mobile re-adds the gutter. */
.dl-nav {
    background: #f4f5f7;
    padding: 0 0 var(--content-gap) 0;
    border-bottom: none;
}

/* Row 1: Category pill tabs */
/* Row 1: Category pill tabs */
.dl-cat-bar {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none !important;
    -ms-overflow-style: none !important;
    gap: 0.25rem;
    padding: 4px 12px;
    background: #ffffff;
    border-radius: 10px;
    border: 1px solid var(--header-border, #eff2f5);
    box-shadow: var(--panel-shadow);
    width: 100%;
}

/* Still joins DOWNWARD to its own nested sub-tab row (only its BOTTOM
   corners square off - the top stays rounded now that nothing sits
   flush above it). */
.dl-cat-bar:has(+ .dl-sub-bar) {
    border-radius: 10px 10px 0 0 !important;
    border-bottom: 1px solid #eff2f5 !important;
}

.dl-cat-bar::-webkit-scrollbar {
    display: none !important;
    width: 0 !important;
    height: 0 !important;
}

.dl-cat-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.55rem 1rem;
    border-radius: 999px;
    text-decoration: none;
    color: var(--tab-text-color, #344054);
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: 0.1px;
    white-space: nowrap;
    border-bottom: none;
    background: var(--tab-bg, transparent);
    transition: background-color 0.18s ease, color 0.18s ease;
}

.dl-cat-tab i {
    font-size: 1rem;
    color: var(--tab-icon-color, #1b838b);
    transition: color 0.18s ease;
}

.dl-cat-tab:hover {
    background: var(--tab-hover-bg, #f1f5f9);
    color: var(--tab-text-color, #1f2937);
    text-decoration: none;
    border-radius: 999px;
}

.dl-cat-tab:hover i {
    color: var(--tab-icon-color, var(--tab-active-color, #1b838b));
}

.dl-cat-tab.active {
    color: var(--tab-active-color, #1b838b);
    background: var(--tab-hover-bg, #e6f4f4);
    border-radius: 999px;
}

.dl-cat-tab.active i {
    color: var(--tab-active-color, #1b838b);
}

/* Category badge counter (optional – hidden by default) */
.dl-cat-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background: #e8f4f5;
    color: var(--tab-active-color, #1b838b);
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
}

.dl-cat-tab.active .dl-cat-count {
    background: var(--tab-active-color, #1b838b);
    color: #ffffff;
}

/* Row 2: Sub-tab strip (tinted, reads as nested under the active category) */
.dl-sub-bar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: #ffffff;
    border-radius: 0 0 10px 10px;
    border: 1px solid var(--header-border, #eff2f5);
    border-top: none;
    padding: 6px 16px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
    width: 100%;
    overflow-x: auto;
    scrollbar-width: none !important;
    -ms-overflow-style: none !important;
}

.dl-sub-bar::-webkit-scrollbar {
    display: none !important;
    width: 0 !important;
    height: 0 !important;
}

/* Connector label — small "→ Category" text on the left */
.dl-sub-label {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--tab-active-color, #1b838b);
    padding-right: 0.6rem;
    border-right: 1px solid #c3e0e2;
    user-select: none;
}

.dl-sub-label i {
    font-size: 0.75rem;
    color: var(--tab-active-color, #1b838b);
}

/* Sub-tab pills container */
.dl-sub-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
}

/* Individual sub-tab pill */
.dl-sub-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    text-decoration: none;
    color: var(--tab-text-color, #344054);
    font-weight: 700;
    font-size: 0.85rem;
    white-space: nowrap;
    background: var(--tab-bg, transparent);
    border: none;
    transition: background-color 0.18s ease, color 0.18s ease, transform 0.12s ease;
}

.dl-sub-tab i {
    font-size: 0.85rem;
    color: var(--tab-icon-color, #1b838b);
    transition: color 0.18s ease;
}

.dl-sub-tab:hover {
    background: var(--tab-hover-bg, #f1f5f9);
    color: var(--tab-text-color, #1f2937);
    text-decoration: none;
    border-radius: 999px;
}

.dl-sub-tab:hover i {
    color: var(--tab-icon-color, var(--tab-active-color, #1b838b));
}

.dl-sub-tab.active {
    color: var(--tab-active-color, #1b838b);
    background: var(--tab-hover-bg, #e6f4f4);
    border-radius: 999px;
    box-shadow: none;
}

.dl-sub-tab.active i {
    color: var(--tab-active-color, #1b838b);
}

.dl-sub-tab.active:hover {
    background: var(--tab-hover-bg, #f1f5f9);
    color: var(--tab-active-color, #1b838b);
}

/* Responsive: collapse category tab text on small screens, show icons only */
@media (max-width: 768px) {
    .dl-cat-bar {
        padding: 0.5rem 0.75rem 0;
    }

    .dl-cat-tab {
        padding: 0.5rem 0.75rem;
        font-size: 0.85rem;
    }

    .dl-cat-tab span {
        display: none;
    }

    .dl-cat-tab i {
        font-size: 1.1rem;
    }

    .dl-sub-bar {
        padding: 0.38rem 0.75rem;
    }

    .dl-sub-label span {
        display: none;
    }
}

/* Premium, modern secondary-tab-bar styling: card grid layout matching cc-area-bar */
.secondary-tab-bar {
    display: flex !important;
    align-items: center !important;
    flex-wrap: nowrap !important;
    overflow-x: auto !important;
    scrollbar-width: none !important;
    -ms-overflow-style: none !important;
    gap: 0.25rem !important;
    padding: 4px 12px !important;
    background: #ffffff !important;
    /* Standalone rounded card - no longer visually joined to the header
       above (the header is a separate full-width fixed card now). */
    border-radius: 10px !important;
    border: 1px solid var(--header-border, #eff2f5) !important;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02) !important;
    width: 100% !important;
    margin: 0 !important;
}

.secondary-tab-bar::-webkit-scrollbar {
    display: none !important;
    width: 0 !important;
    height: 0 !important;
}

.secondary-tab {
    display: inline-flex !important;
    align-items: center !important;
    gap: 0.4rem !important;
    padding: 0.55rem 1rem !important;
    border-radius: 999px !important;
    text-decoration: none !important;
    color: var(--tab-text-color, #344054) !important;
    font-weight: 700 !important;
    font-size: 0.95rem !important;
    letter-spacing: 0.1px !important;
    white-space: nowrap !important;
    border: none !important;
    background: transparent !important;
    box-shadow: none !important;
    height: auto !important;
    transition: background-color 0.18s ease, color 0.18s ease !important;
}

.secondary-tab span {
    display: inline !important;
    padding: 0 !important;
    border: none !important;
    white-space: nowrap !important;
    color: inherit !important;
}

/* Hover and active both read --tab-hover-bg, which is what Design Config's
   "Tab hover/active background" writes. They used to carry the token's own
   default values as literal hex (#f1f5f9 / #e6f4f4), which looked identical
   out of the box and is exactly why the bug hid for so long: the Control
   Center tab bar was the one tab strip in the app that ignored the setting,
   and changing the colour appeared to do nothing.

   Same token, two different fallbacks, matching `.cc-area-tab` above: left
   unconfigured, hover stays a neutral slate and only the active pill is
   tinted; configured, the one setting drives both, which is what its label
   promises. The `!important` is inherited from this block, not added — every
   declaration around it carries one, so dropping it here would hand the rule
   back to the earlier `.secondary-tab` block. */
.secondary-tab:hover {
    background: var(--tab-hover-bg, #f1f5f9) !important;
    color: var(--tab-text-color, #1f2937) !important;
    text-decoration: none !important;
    border-radius: 999px !important;
}

.secondary-tab.active {
    color: var(--tab-active-color, #1b838b) !important;
    background: var(--tab-hover-bg, #e6f4f4) !important;
    border-radius: 999px !important;
    box-shadow: none !important;
}

.secondary-tab.active span {
    color: var(--tab-active-color, #1b838b) !important;
}

.secondary-tab.disabled,
.secondary-tab[disabled],
.secondary-tab[title="Not migrated yet"] {
    color: #a1a5b7 !important;
    cursor: not-allowed !important;
    border-color: #e4e6ef !important;
    background-color: #ffffff !important;
    opacity: 0.8 !important;
}

.secondary-tab.disabled::before,
.secondary-tab[disabled]::before,
.secondary-tab[title="Not migrated yet"]::before {
    opacity: 0.65 !important;
}

.secondary-tab.disabled:hover,
.secondary-tab[title="Not migrated yet"]:hover {
    background-color: #ffffff !important;
    border-color: #e4e6ef !important;
    color: #a1a5b7 !important;
}

/* Navigation Icons (Top Header Menu & Left Sidebar Menu) */
#headerMenu i,
#headerMenu .header-menu-toggle i,
#headerMenu .header-submenu i,
#headerMenu .megamenu-dropdown i,
#sidebarMenu i,
#sidebarMenu .menu-team-icon i,
#sidebarMenu .menu-link-icon {
    color: var(--nav-icon-color, #1b838b) !important;
    transition: color 0.2s ease !important;
}

/* Only the actual current page's icon flips to the on-pill colour. An
   expanded parent toggle no longer has a filled pill behind it (see
   .menu-team-toggle.active), so tinting its icon --nav-active-text (white)
   would make it invisible - it keeps the normal nav icon colour. */
#sidebarMenu a.active .menu-link-icon {
    color: var(--nav-active-text, #ffffff) !important;
}

/* ...except in the collapsed rail, where the parent DOES keep the filled
   pill because its children aren't rendered at all. */
body.sidebar-collapsed #sidebarMenu .menu-team-toggle.active .menu-team-icon i {
    color: var(--nav-active-text, #ffffff) !important;
}

/* Child Page Icons (Secondary Tab Bar / Child Page Tabs) */
.secondary-tab i,
.secondary-tab i.bi,
.secondary-tab-bar i {
    font-size: 1.1rem !important;
    color: var(--tab-icon-color, var(--child-page-icon-color, #1b838b)) !important;
    display: inline-block !important;
    line-height: 1 !important;
    margin-right: 4px !important;
    transition: color 0.2s ease !important;
}

.secondary-tab:hover i,
.secondary-tab:hover i.bi {
    color: var(--tab-icon-color, var(--child-page-icon-color, #1b838b)) !important;
}

.secondary-tab.active i,
.secondary-tab.active i.bi {
    color: var(--tab-active-color, var(--tab-icon-color, #1b838b)) !important;
}

.users-filter-offcanvas {
    width: 420px !important;
}

.users-filter-offcanvas .offcanvas-body {
    overflow-x: hidden;
}

/* RAS's field-team card (safekivi_theme.css: .chem-ss-filter-block -
   background:#f2f4f7; border-radius:7px; padding:20px) - color/radius
   ported directly; padding/margin tightened slightly since this drawer's
   cards hold plain label+input rows, not RAS's denser date-range layout. */
/* Explicitly white rather than var(--card-bg): --card-bg is the Design
   Config "Card/panel background" token, and any tenant still holding its
   old grey value (#f2f4f7) rendered these cards grey. The border is what
   keeps them readable now that they're white - inside a filter offcanvas
   the drawer body is white too, so without it the card edges disappear. */
.filter-card {
    background: #ffffff;
    border: 1px solid var(--card-border, #eef1f4);
    border-radius: 8px;
    padding: 14px 16px;
    margin: 0 0 12px;
}

.filter-card .filter-row+.filter-row {
    margin-top: 10px;
}

/* RAS's offcanvas-footer (safekivi_theme.css: background-color:#ddebff) - kept as
   a sticky bar so Close/Clear Filter/Apply Filters stay reachable however
   long the filter-card list scrolls above it. Button colors follow this
   app's teal accent rather than RAS's own blue, per CORRECTION 8/9's
   existing brand palette (--btn-primary-bg, renamed from --dm-teal in
   CORRECTION 14). */
.offcanvas-footer {
    background: #eef6f6;
    padding: 14px 16px;
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    border-top: 1px solid #d7e6e6;
}

.btn-outline-filter {
    padding: 8px 18px;
    /* Teal-outline pill (not the gray --btn-secondary-* look) - this is an
       outline variant of the PRIMARY accent, not the same "Secondary
       button" concept as e.g. the modal's gray Cancel button, so it's
       deliberately bound to --btn-primary-bg rather than a new/duplicate
       button variable. */
    border: 1.5px solid var(--btn-primary-bg);
    border-radius: 999px;
    background: #fff;
    color: var(--btn-primary-bg);
    font-weight: 700;
    font-size: 0.875rem;
    cursor: pointer;
}

.btn-outline-filter:hover {
    background: rgba(27, 131, 139, 0.08);
}

/* Footer's Apply Filters button reuses .btn-apply-filters but shouldn't
   stretch full-width like it did as the old inline panel's own submit
   button - override just the sizing, keep the teal pill styling. */
.offcanvas-footer .btn-apply-filters {
    display: inline-block;
    width: auto;
    margin-top: 0;
}

/* Toolbar trigger button that opens the drawer (RAS: .btn-ss-showAdv-filters,
   a circular icon-only button at background #f2f4f7). Adapted to an
   icon+label pill to match this toolbar's other buttons (.btn-export)
   rather than RAS's bare circular icon, since every other action here
   already carries a text label. */
.users-grid-toolbar,
.safekivi-datatable-toolbar {
    display: flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    flex-wrap: wrap !important;
    gap: 12px !important;
    margin-bottom: 16px !important;
    width: 100% !important;
}

.datatable-toolbar-primary {
    display: flex !important;
    align-items: center !important;
    gap: 10px !important;
}

/* nowrap, not wrap: the actions cluster is one row of controls that belong
   together (search / filters / exports), and wrapping it splits the search onto
   its own line with the export pills orphaned underneath — the layout this
   replaced. Nothing here needs to wrap, because the only elastic member
   (`.datatable-toolbar-search`) is allowed to shrink instead. Below 768px the
   toolbar stacks properly and wrapping is re-enabled in that media query. */
.datatable-toolbar-actions {
    display: flex !important;
    align-items: center !important;
    gap: 10px !important;
    flex-wrap: nowrap !important;
    min-width: 0 !important;
}

/* The export pills are fixed content — they must never be the thing that gives. */
.datatable-toolbar-actions .users-export-btns {
    flex: 0 0 auto !important;
}

.users-export-btns {
    display: flex !important;
    align-items: center !important;
    gap: 8px !important;
}

/* The toolbar's own search field.
   `data_table_standards.md` §1a allows "a debounced toolbar input" as the
   alternative to a search field inside the filter drawer — but there was no
   toolbar-sized control for it, so pages reached for `.filter-input`. That is a
   *drawer* control: it carries `flex: 1 1 auto` so it stretches to fill a
   `.filter-row`, and it renders 34px tall against this toolbar's 38px, leaving
   the search box visibly short of the buttons beside it. Both pages that did it
   then pinned the width back with an inline `style="max-width: 260px"`.
   These are those controls, sized to the toolbar instead: a fixed width that
   does not grow, and the same 38px height every other toolbar control uses.
   `-select` is the same control for a toolbar dropdown (Audit Types' class
   filter), which had the identical problem sitting beside it. */
.datatable-toolbar-search,
.datatable-toolbar-select {
    /* `0 1 <basis>`: never grows (growing is what stretched the old
       `.filter-input` across the whole actions row and pushed the export pills
       onto a second line), but may shrink, so a narrow viewport takes width
       from the search rather than breaking the row. */
    flex: 0 1 220px !important;
    width: 220px !important;
    min-width: 120px !important;
    max-width: 100% !important;
    height: 38px !important;
    min-height: 38px !important;
    padding: 8px 14px !important;
    border: 1px solid var(--form-input-border, #d0d5dd) !important;
    border-radius: 8px !important;
    background: #ffffff !important;
    color: #344054 !important;
    font-size: 0.875rem !important;
    font-weight: 500 !important;
}

/* A dropdown is sized to its longest option, not to a search phrase. */
.datatable-toolbar-select {
    flex: 0 1 180px !important;
    width: 180px !important;
    cursor: pointer !important;
}

.datatable-toolbar-search:focus,
.datatable-toolbar-select:focus {
    outline: none !important;
    border-color: var(--form-input-focus, #1b838b) !important;
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.15) !important;
}

.users-grid-toolbar .btn-create-user,
.users-grid-toolbar .btn-filter-toggle,
.users-grid-toolbar .btn-clear-filters,
.users-grid-toolbar .btn-export,
.safekivi-datatable-toolbar .btn-create-user,
.safekivi-datatable-toolbar .btn-filter-toggle,
.safekivi-datatable-toolbar .btn-clear-filters,
.safekivi-datatable-toolbar .btn-export,
/* Security tab: its toolbar is .sec-toolbar rather than the shared
   .users-grid-toolbar (it carries a section title and its own border/padding),
   so it never picked up this normalization and its buttons rendered at the base
   0.9rem/40px instead of the toolbar standard 0.875rem/38px. Listed here rather
   than restated on the page, so there is still one definition of what a toolbar
   button looks like. */
.sec-toolbar .btn-create-user,
.sec-toolbar .btn-filter-toggle,
.sec-toolbar .btn-clear-filters,
.sec-toolbar .btn-export {
    height: 38px !important;
    min-height: 38px !important;
    max-height: 38px !important;
    padding: 0 16px !important;
    font-size: 0.875rem !important;
    font-weight: 600 !important;
    border-radius: 999px !important;
    display: inline-flex;
    align-items: center !important;
    justify-content: center !important;
    gap: 6px !important;
    box-sizing: border-box !important;
    line-height: 1 !important;
}

.users-grid-toolbar .btn-create-user i,
.users-grid-toolbar .btn-filter-toggle i,
.users-grid-toolbar .btn-clear-filters i,
.users-grid-toolbar .btn-export i,
.sec-toolbar .btn-create-user i,
.safekivi-datatable-toolbar i {
    font-size: 1rem !important;
    line-height: 1 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
}

@media (max-width: 768px) {

    .users-grid-toolbar,
    .safekivi-datatable-toolbar {
        flex-direction: column !important;
        align-items: stretch !important;
        gap: 10px !important;
    }

    .datatable-toolbar-primary,
    .datatable-toolbar-actions,
    .users-export-btns {
        width: 100% !important;
        justify-content: space-between !important;
    }

    /* Stacked toolbar: here wrapping IS wanted — the controls are full-width
       rows rather than one horizontal cluster, so restore it from the nowrap
       set on the desktop rule above. */
    .datatable-toolbar-actions {
        flex-wrap: wrap !important;
    }

    /* Stacked toolbar: these take the full row like every other control, rather
       than sitting at their desktop widths against full-width siblings.
       `flex` must be reset too, not just `width` — a flex item's main size comes
       from `flex-basis` (220px/180px above), which outranks `width` entirely. */
    .datatable-toolbar-search,
    .datatable-toolbar-select {
        flex: 1 1 100% !important;
        width: 100% !important;
    }

    .users-grid-toolbar .btn-create-user,
    .safekivi-datatable-toolbar .btn-create-user {
        width: 100% !important;
    }
}

.btn-filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 16px;
    border: 1px solid #d0d5dd;
    border-radius: 999px;
    background: #f2f4f7;
    color: #344054;
    font-weight: 600;
    font-size: 0.875rem;
    cursor: pointer;
}

.btn-filter-toggle:hover {
    background: #e4e7ec;
}

.btn-clear-filters {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 16px;
    border: 1px solid #fda29b;
    border-radius: 999px;
    background: #fef3f2;
    color: #b42318;
    font-weight: 600;
    font-size: 0.875rem;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.btn-clear-filters:hover {
    background: #fee4e2;
    border-color: #f87171;
}

.filter-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.filter-label {
    flex: 0 0 130px;
    font-weight: 600;
    color: var(--form-label-color, #344054);
    font-size: 0.9rem;
    margin: 0;
}

.filter-input {
    flex: 1 1 auto;
    min-width: 0;
    border: 1px solid #d0d5dd;
    border-radius: 8px;
    padding: 5px 12px;
    font-size: 0.875rem;
    background: #fff;
}

.filter-input:focus {
    outline: none;
    border-color: var(--form-focus-color);
    box-shadow: 0 0 0 3px rgba(29, 122, 122, 0.15);
}

select.filter-input[multiple] {
    display: none !important;
}

/* --- Select2 & Multiselect (User Keywords / Access Roles filters) ---
   Scoped overrides so Select2 container/pill badges fit our .filter-row
   layout and match plain <select> inputs identically.
   .add-member-field (Roster Management's Add Member card) gets the identical
   treatment: same controls, stacked label-above-field instead of in a
   .filter-row, so both selectors carry every rule below. */
.add-member-field .multiselect-native-select,
.filter-row .multiselect-native-select,
.add-member-field span.multiselect-native-select,
.filter-row span.multiselect-native-select,
.add-member-field .select2,
.filter-row .select2,
.add-member-field .select2-container,
.filter-row .select2-container,
.add-member-field .select2-container--bootstrap-5,
.filter-row .select2-container--bootstrap-5 {
    display: block !important;
    flex: 1 1 auto !important;
    min-width: 0 !important;
    width: 100% !important;
}

.add-member-field .select2-container .selection,
.filter-row .select2-container .selection {
    display: block !important;
    width: 100% !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple {
    min-height: 38px !important;
    border: 1px solid #d0d5dd !important;
    border-radius: 8px !important;
    padding: 3px 8px !important;
    background-color: #fff !important;
    display: flex !important;
    flex-wrap: wrap !important;
    align-items: center !important;
    gap: 4px !important;
    width: 100% !important;
    box-sizing: border-box !important;
}

.add-member-field .select2-container--bootstrap-5.select2-container--focus .select2-selection--multiple,
.filter-row .select2-container--bootstrap-5.select2-container--focus .select2-selection--multiple {
    border-color: var(--form-focus-color, #1B838B) !important;
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.15) !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__rendered,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__rendered {
    display: flex !important;
    flex-wrap: wrap !important;
    align-items: center !important;
    gap: 4px !important;
    padding: 0 !important;
    margin: 0 !important;
    width: 100% !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__choice,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__choice {
    background-color: #f2f4f7 !important;
    border: 1px solid #d0d5dd !important;
    border-radius: 6px !important;
    color: #344054 !important;
    font-size: 0.8rem !important;
    font-weight: 500 !important;
    padding: 2px 8px !important;
    margin: 1px 0 !important;
    display: inline-flex !important;
    align-items: center !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__choice__remove,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__choice__remove {
    color: #667085 !important;
    margin-right: 4px !important;
    border: none !important;
    background: transparent !important;
    cursor: pointer !important;
    font-size: 0.95rem !important;
    line-height: 1 !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__choice__remove:hover,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__choice__remove:hover {
    color: #f04438 !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple .select2-search.select2-search--inline,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple .select2-search.select2-search--inline {
    flex: 1 1 auto !important;
    display: inline-flex !important;
    margin: 0 !important;
    padding: 0 !important;
    min-width: 80px !important;
}

.add-member-field .select2-container--bootstrap-5 .select2-selection--multiple .select2-search__field,
.filter-row .select2-container--bootstrap-5 .select2-selection--multiple .select2-search__field {
    margin: 0 !important;
    padding: 2px 4px !important;
    font-size: 0.875rem !important;
    color: #344054 !important;
    height: 28px !important;
    line-height: 28px !important;
    width: 100% !important;
    min-width: 80px !important;
    border: none !important;
    outline: none !important;
    background: transparent !important;
    box-shadow: none !important;
}

.select2-dropdown {
    z-index: 1060 !important;
    border-radius: 8px !important;
    border: 1px solid #d0d5dd !important;
    box-shadow: 0 4px 16px rgba(16, 24, 40, 0.1) !important;
    overflow: hidden !important;
}

.select2-container--bootstrap-5 .select2-results__option--highlighted[aria-selected] {
    background-color: var(--btn-primary-bg, #1B838B) !important;
    color: #fff !important;
}

/* The rule above only claims the row the pointer/keyboard is ON. The theme
   paints the row that is the CURRENT VALUE separately, and paints it
   `--bs-primary` blue (#0d6efd) — so any open dropdown whose value is not the
   row under the pointer showed a blue row and a teal row at once. Reported
   from the audit trail's Module filter; it affects every Select2 in the app.

   Not simply the same solid teal: "this is the current value" and "this is the
   row you are on" are two different things and a list needs to show both. The
   current value takes the grid's own selected-row tint against the solid fill
   of the highlighted row — the same light-teal/solid-teal pairing
   `.sk-dp-action-primary` uses, so the distinction is carried by weight rather
   than by a second hue. Text is the darker primary, not #1b838b, for the same
   reason it is there: 5.93:1 on this tint where the lighter teal gives 3.78:1.

   Both selectors carry `:not(--highlighted)` so that a row which is selected
   AND highlighted keeps the solid fill from the rule above — without it this
   block's higher specificity would win and the focused row would go pale. */
.select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option--selected:not(.select2-results__option--highlighted),
.select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option[aria-selected=true]:not(.select2-results__option--highlighted) {
    background-color: var(--grid-row-selected-bg, #dcefef) !important;
    color: var(--btn-primary-hover-bg, #146267) !important;
}

.filter-row .multiselect-filter-wrap {
    display: block !important;
    flex: 1 1 auto !important;
    min-width: 0 !important;
    width: 100% !important;
    position: relative !important;
}

.multiselect-filter-wrap select {
    display: none !important;
}

.filter-multiselect-btn {
    width: 100% !important;
    min-width: 0 !important;
    height: 38px !important;
    display: flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    border: 1px solid #d0d5dd !important;
    border-radius: 8px !important;
    padding: 6px 12px !important;
    font-size: 0.875rem !important;
    background: #fff !important;
    color: #344054 !important;
    box-sizing: border-box !important;
}

.multiselect-container .multiselect-filter input.multiselect-search {
    border: 1px solid #d0d5dd !important;
    border-radius: 8px !important;
    background: #fff;
}

.multiselect-container .multiselect-filter input.multiselect-search:focus {
    border-color: var(--form-focus-color) !important;
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.15);
}

.multiselect-container .multiselect-filter .fa-search {
    /* This IS the DataTable filter's own search icon. */
    color: var(--grid-search-icon-color, #98a2b3);
}

.btn-apply-filters {
    display: block;
    width: 100%;
    margin-top: 12px;
    padding: 8px 20px;
    border: none;
    border-radius: 999px;
    background: var(--btn-primary-bg);
    color: var(--btn-primary-text, #fff);
    font-weight: 700;
    font-size: 0.95rem;
    cursor: pointer;
}

.btn-apply-filters:hover {
    background: var(--btn-primary-hover-bg);
}

.btn-create-user {
    display: inline-block;
    padding: 7px 20px;
    border: none;
    border-radius: 999px;
    background: var(--btn-primary-bg);
    color: var(--btn-primary-text, #fff);
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    /* `margin-bottom: 10px` removed. It was doing "space below the toolbar"
       work that the toolbar container already does for itself
       (`.users-grid-toolbar` sets its own margin-bottom: 10px), while the
       side effect was real: inside a centred flex row it lifted the button
       5px off the baseline its siblings share, so on EVERY data-table page
       the Create button sat visibly higher than Filters/CSV/Excel/PDF, and
       in the modal footers that use this class it sat higher than Cancel.
       rosters/list.html had already hit this and pinned `margin-bottom: 0`
       on its own button with a comment explaining why - a local workaround
       for something that belonged here. Measured before/after: centre
       offset against the Filters button went from -5px to 0. */
}

.btn-create-user:hover {
    background: var(--btn-primary-hover-bg);
}

/* CORRECTION 8 (AG Grid Community migration): the hand-rolled table, its
   pagination footer, and the chevron expand-row (.col-expand/.row-expand-btn/
   .user-detail-row) are gone - replaced by a real AG Grid Community grid
   (#usersGrid, styling below) with the grid's own built-in sorting and
   pagination panel. .actions-cell/.action-icon-btn (CORRECTION 7, further
   below) are also reused as-is, now rendered inside an AG Grid cell renderer
   instead of a plain <td>.
   Superseded later (platform-wide responsive expand/collapse rollout): this
   correction's own follow-up note used to say User Keywords/Access Roles
   moved into the view/edit modal's read-only section "since row expand is
   AG Grid Enterprise-only" - that modal section was never actually built
   (confirmed directly: openUserForm() populates no such fields, and the
   modal's markup has no such section), so those two fields had no
   interactive home at all except the grid's own now-removed floating panel
   (users.js's row-expand-btn/user-expand-panel, a second, separate expand
   mechanism from the platform's). Also, row-expand/detail panels are not
   Enterprise-only - the platform's own chevron/push-down mechanism
   (safekivi-grid.js, every grid app-wide) is a from-scratch DOM
   implementation, not AG Grid's Master/Detail API. User Keywords and Access
   Roles are now ordinary grid columns (usersGridColumnDefs(), forced to the
   lowest responsive priority alongside Division) - reached through that
   same platform chevron like any other hidden column, with no
   Users-specific code. The .user-detail-* classes below remain in active
   use by Divisions/Enterprises/Rosters/Assignment Roles' own detail
   sections - unrelated to this correction, do not remove them. */
.users-grid-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;
}

.users-kpi-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px;
    margin-bottom: 12px;
}

/* Compact: these are a glanceable summary above the grid, not the page's
   main content, so they stay visually quieter than the table below. */
.users-kpi-card {
    background: #ffffff;
    border: 1px solid var(--card-border, #eef1f4);
    border-radius: 10px;
    padding: 9px 12px;
    display: flex;
    align-items: center;
    gap: 10px;
}

.users-kpi-icon {
    width: 26px;
    height: 26px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    font-size: 0.8rem;
}

.users-kpi-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.users-kpi-icon-total {
    background: var(--tab-hover-bg, #e6f4f4);
    color: var(--tab-active-color, #1b838b);
}

.users-kpi-icon-active {
    background: var(--alert-success-bg, #d1e7dd);
    color: var(--alert-success-text, #0f5132);
}

.users-kpi-icon-inactive {
    background: var(--alert-warning-bg, #fff3cd);
    color: var(--alert-warning-text, #664d03);
}

.users-kpi-icon-archived {
    background: #f3f0ff;
    color: #5b21b6;
}

.users-kpi-num {
    font-size: 1.15rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--page-title-color, #1e2129);
}

.users-kpi-label {
    font-size: 0.72rem;
    color: #667085;
    font-weight: 600;
    line-height: 1.2;
}

.users-export-btns {
    display: flex;
    gap: 8px;
}

.btn-export {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 16px;
    border: 1px solid #d0d5dd;
    border-radius: 999px;
    background: #fff;
    color: #344054;
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
}

.btn-export:hover {
    border-color: var(--btn-primary-bg);
    color: var(--btn-primary-bg);
}

/* Shared shell for the grid + its custom footer (see list.html) - one
   border-radius/box-shadow/border for the whole panel, so the pagination
   footer reads as part of the same card rather than a second, separately-
   boxed element bolted on underneath. */
/* Softened, two-layer shadow (subtle "lifted card" look) and a slightly
   translucent border instead of the flat single-shadow/solid-border combo
   this had before - visual refinement only, no radius/size/spacing change. */
.users-ag-grid-panel {
    border-radius: 14px;
    overflow: hidden;
    box-shadow: var(--panel-shadow);
    border: 1px solid color-mix(in srgb, var(--card-border) 85%, transparent);
}

/* Responsive column priority: a chevron toggle wrapped inline into the
   first column's own cell (safekivi-grid.js's init(), not a dedicated AG
   Grid column) opens a detail panel (.ehs-row-detail) listing whatever
   columns didn't fit the container for that row, in priority order. A
   genuine push-down — the row grows to fit it and every row below shifts
   down to match, both done by hand in JS (repositionRows()/
   syncWrapperHeight() in safekivi-grid.js): AG Grid's Infinite Row Model
   (the server-paginated default nearly every grid uses) doesn't reposition
   sibling rows on its own when one's height changes after initial render,
   confirmed directly against this app's exact vendored build. `top` is set
   inline per-row by bindRowExpand() (the panel's actual anchor point, the
   row's own pre-expand height, varies per row/grid), not fixed here. */
.ehs-row-expand-cell {
    display: flex;
    /* center is correct for the overwhelming majority of first columns - a
       single line of text (or text + a side-by-side icon/avatar) beside a
       20px button, where centring both on the same cross-axis is exactly
       what "aligned with the text" means. Confirmed against a plain
       `field: 'name'` column (e.g. divisions.js) where flex-start had left
       the chevron sitting visibly above the text baseline instead of level
       with it. */
    align-items: center;
    gap: 6px;
    width: 100%;
    min-width: 0;
}

/* Opt-in for the handful of first columns whose cellRenderer stacks TWO
   lines in the same cell (a title with a code/duration/status sub-line
   under it, e.g. training-catalog.js's course title + code). Centring the
   20px toggle against that combined ~38px block drops it below the title
   it belongs to; aligning to the top instead puts the button against the
   first line, and since that line is itself ~20px tall the button ends up
   centred on it. Applied by safekivi-grid.js only when a page passes
   `firstColumnMultiline: true` to SafeKiviGrid.init() - see the JSDoc
   there. Do not use this for single-line columns; align-items: center
   above is correct for those. */
.ehs-row-expand-cell--multiline {
    align-items: flex-start;
}

.ehs-row-expand-content {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ehs-row-expand-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--btn-primary-bg, #1b838b);
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.ehs-row-expand-btn:hover,
.ehs-row-expand-btn:focus-visible {
    background: var(--grid-row-selected-bg, #dcefef);
    outline: none;
}

/* The glyph itself never changes — bi-chevron-right rotates in place
   (closed -> pointing right, open -> pointing down), a smoother, more
   deliberate motion than swapping between two unrelated icons. */
.ehs-row-expand-btn i {
    display: inline-block;
    transition: transform 0.18s ease;
}

.ehs-row-expand-btn.ehs-row-expand-btn-open i {
    transform: rotate(90deg);
}

/* Starts hidden (opacity:0, nudged up 4px) and fades/slides in once
   bindRowExpand() adds .ehs-row-detail-visible (a forced-reflow-then-class-
   toggle, not requestAnimationFrame -- see bindRowExpand()'s own comment);
   reversed on close before the element is actually removed. The 0.18s here
   must match bindRowExpand()'s CLOSE_MS constant in safekivi-grid.js, which
   times the DOM removal (and the row's own height collapsing back down) to
   happen after this transition finishes. */
.ehs-row-detail {
    position: absolute;
    left: 0;
    right: 0;
    /* Above AG Grid's own `.ag-grid-pinned-right-cells` (z-index: 2) --
       repositionRows()' translateY makes `.ag-row` a stacking context, so
       without this the pinned Actions section (present on 7 grids) paints
       over the panel's right edge and swallows its pointer events there. */
    z-index: 3;
    padding: 10px 16px 14px;
    /* Explicitly white rather than var(--card-bg): same reason as
       .filter-card/.ra-card/.audit-card elsewhere in this file --
       --card-bg is Design Config's "Card/panel background" token, and a
       tenant still holding its old grey value (#f2f4f7) renders this panel
       grey instead of white. .users-ag-grid-panel (the shell every grid
       sits in) deliberately paints no background of its own -- AG Grid's
       own theme covers the normal rows via --grid-zebra-bg, a different,
       unaffected token -- so this panel was the one piece of a grid's
       surface still wired to the stale token. Reported directly, with a
       screenshot, against the Scope Access grid; this rule is shared by
       every grid's expand panel in the app, so the fix is automatically
       platform-wide. */
    background: #fff;
    border: 1px solid var(--card-border, #e5e7eb);
    border-top: none;
    box-shadow: 0 8px 16px rgba(16, 24, 40, 0.12);
    font-size: 0.82rem;
    color: var(--grid-cell-text-color, #344054);
    box-sizing: border-box;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.18s ease, transform 0.18s ease;
    pointer-events: none;
}

.ehs-row-detail.ehs-row-detail-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.ehs-row-detail-item {
    padding: 3px 0;
    line-height: 1.4;
}

.ehs-row-detail-item strong {
    color: var(--grid-header-text-color, #667085);
    font-weight: 600;
    margin-right: 6px;
}

/* ROOT CAUSE of "Design Config grid colour changes have zero effect" (and,
   retroactively, why earlier header-background edits never visibly changed
   anything): ag-theme-quartz.css is loaded from list.html's extra_css block,
   which layout.html renders AFTER site.css (layout.html: site.css link, then
   {% block extra_css %}). Its packaged `.ag-theme-quartz { --ag-header-
   background-color: <built-in default>; ... }` rule has the exact same
   specificity (one class, 0-1-0) as the `.users-ag-grid { --ag-header-
   background-color: var(--grid-header-bg); }` rule below - and in a
   specificity tie, whichever rule is later in the cascade wins, which was
   AG Grid's own packaged CSS, not ours. Every --ag-* override in this block
   was silently losing to the theme's built-in values regardless of what
   --grid-* variables Design Config supplied.
   Fix: target `.ag-theme-quartz.users-ag-grid` (both classes already sit on
   the real #usersGrid div - see list.html) for specificity 0-2-0, which
   always outranks the packaged theme's single-class rule no matter which
   stylesheet loads last. No selector/load-order change needed elsewhere. */
.ag-theme-quartz.users-ag-grid {
    /* No fixed height here anymore - domLayout: 'autoHeight' (users.js)
       sizes the grid to exactly however many rows the current page actually
       has (via an inline style AG Grid sets itself). The old fixed 480px
       left a large blank gap under the rows whenever a page held fewer
       rows than 480px worth (e.g. the last page, or a small filtered
       result set) - reported as unwanted empty space above the pagination
       footer. min-height keeps the "No users found" overlay (0-row state)
       from collapsing to an awkward sliver. */
    width: 100%;
    min-height: 160px;
    --ag-font-family: var(--font-sans);
    --ag-font-size: 13px;
    --ag-accent-color: var(--grid-action-icon-color);
    /* Light gray header + zebra-striped rows, per the chemical-inventory-
       style reference screenshot - CORRECTION 12 - now fully Design Config
       driven (DataTable UI enhancement pass): header background/text,
       zebra tone, row hover, and row selection all pull from dedicated
       --grid-* variables instead of the earlier mix of hardcoded values
       and reused --card-bg. */
    --ag-header-background-color: var(--grid-header-bg);
    --ag-header-foreground-color: var(--grid-header-text-color);
    /* AG Grid's general text/icon color, including its own pagination
       panel (page numbers, prev/next arrows, page-size dropdown) - the
       real hook for the "Pagination icons" requirement category. The
       native panel itself is hidden (suppressPaginationPanel, users.js) in
       favor of the custom #usersGridFooter below, but this still covers
       any other AG Grid chrome (column menu icons, filter icons) that
       share this same foreground concept. */
    --ag-foreground-color: var(--grid-pagination-icon-color);
    --ag-border-color: var(--card-border);
    --ag-row-hover-color: var(--grid-row-hover-bg);
    --ag-selected-row-background-color: var(--grid-row-selected-bg);
    --ag-odd-row-background-color: var(--grid-zebra-bg);
    --ag-border-radius: 10px;
    /* Belt-and-braces with the .ag-header-cell-resize::after rule below:
       kills any vertical cell border the packaged theme might draw, so the
       grid is separated by horizontal row lines only. */
    --ag-cell-horizontal-border: none;
    --ag-column-border: none;
    /* Slightly roomier cell padding for a cleaner, less cramped look
       (spacing/alignment requirement) - row/header height are left alone
       (34px, set in users.js) since those were deliberately tuned earlier
       to avoid reintroducing the grid's own vertical scrollbar. */
    --ag-cell-horizontal-padding: 14px;
}

/* Explicit header bottom border - Quartz's own border-color variable
   already tints internal grid lines, but a dedicated rule here guarantees
   a visible, deliberate separation between header and body rows (as
   opposed to relying on whatever the packaged theme does by default). */
/* NOTE: an earlier pass added `.users-ag-grid .ag-body-viewport { overflow-y:
   hidden !important; }` here as a defensive guard. Pulled it - fetching AG
   Grid 36's real packaged CSS showed `.ag-body-viewport` isn't even the
   element that renders the visible scrollbar (that's `.ag-body-vertical-
   scroll-viewport`, a separate synced custom-scrollbar element AG Grid
   shows/hides itself via its own `ag-invisible`/`ag-hidden` classes once
   content stops overflowing) - so the override never touched the actual
   scrollbar and would only have risked silently clipping real rows if it
   had matched something. The real fix is rowBuffer: 0 in users.js's
   gridOptions, which stops AG Grid from rendering extra buffered rows past
   the current page's real content in the first place - once there's no
   genuine overflow, AG Grid's own show/hide logic hides the scrollbar
   without any CSS override needed. suppressHorizontalScroll (JS) remains
   the belt-and-suspenders guard against the horizontal scrollbar, since
   that one is an officially documented gridOption rather than a guess at
   internal class names. */

/* ROOT CAUSE (AG Grid 36): the `--ag-header-background-color` /
   `--ag-header-foreground-color` overrides set on `.ag-theme-quartz.users-ag-grid`
   above still resolve correctly (confirmed: the grid element's computed
   `--ag-header-background-color` == the saved Design Config value), but AG
   Grid 36's Quartz CSS no longer paints `.ag-header` FROM that variable -
   the header element computes to `rgba(0,0,0,0)` (transparent) regardless of
   the variable's value, so every Design Config header colour change had no
   visible effect. AG Grid's legacy `--ag-header-*` plumbing is effectively
   dead in v36. Fix: paint the header element directly from our own
   `--grid-header-*` variables here, instead of relying on AG Grid to consume
   its `--ag-*` indirection. site.css is unlayered, so this reliably wins over
   AG Grid's own @layer-wrapped theme rules regardless of load order. */
.ag-theme-quartz.users-ag-grid .ag-header {
    border-bottom: 1px solid var(--card-border);
    background-color: var(--grid-header-bg);
}

.ag-theme-quartz.users-ag-grid .ag-header-row {
    background-color: var(--grid-header-bg);
}

/* AG Grid 36 inserts a new `.ag-grid-scrolling-cells` wrapper INSIDE the
   header row (between `.ag-header-row` and the transparent `.ag-header-cell`s)
   that carries its own opaque near-white background. It stacks ABOVE the red
   `.ag-header`/`.ag-header-row` we paint above, so without this rule it
   covers the header colour and only the row's thin edges show through - the
   "colour not applying fully" symptom. Scoped under `.ag-header` so the
   identically-named body scrolling container is left untouched. */
.ag-theme-quartz.users-ag-grid .ag-header .ag-grid-scrolling-cells {
    background-color: var(--grid-header-bg);
}

.ag-theme-quartz.users-ag-grid .ag-header-cell,
.ag-theme-quartz.users-ag-grid .ag-header-team-cell {
    color: var(--grid-header-text-color);
}

.ag-theme-quartz.users-ag-grid .ag-header-cell-label {
    font-weight: 600;
    color: var(--grid-header-text-color);
    /* Fix: on columns with a longer header label (e.g. "Username"), the
       label text and the sort-indicator icon are flex siblings with no
       shrink priority set, so the text was taking all the space and
       squeezing the icon out of view entirely - the icon only "happened"
       to show on columns with short labels like "Role"/"Status". Force the
       text to truncate (ellipsis) first and reserve fixed space for the
       icon, so every sortable column keeps a visible sort icon regardless
       of label length or column width. No column width/padding changed. */
    overflow: hidden;
}

/* Small uppercase, letter-spaced header labels. Deliberately scoped to
   .ag-header-cell-text (the label text node) rather than the enclosing
   .ag-header-cell-label, because the sort-indicator container is also a
   child of the label - typography set there would reach the sort icons,
   which are explicitly out of scope for this restyle. */
.ag-theme-quartz.users-ag-grid .ag-header-cell-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1 1 auto;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Body cell typography/padding. Set directly rather than via --ag-font-size /
   --ag-cell-horizontal-padding: same AG Grid 36 problem documented for the
   header above - the packaged Quartz theme no longer consumes those legacy
   --ag-* variables, so cells were rendering at the theme's own 14px/15px
   defaults regardless of what we set. Colour still flows from a Design
   Config token so the grid stays themeable. */
.ag-theme-quartz.users-ag-grid .ag-cell {
    font-size: 0.84rem;
    color: var(--grid-pagination-icon-color, #344054);
    padding-left: 14px;
    padding-right: 14px;
}

/* No column dividers. An earlier pass deliberately stretched AG Grid's
   resize-handle tick into a full-height hairline so columns read as "fully
   separated"; the approved design goes the other way - rows are separated by
   horizontal lines only, with nothing vertical. The handle itself stays
   present and draggable, it just isn't painted. */
.ag-theme-quartz.users-ag-grid .ag-header-cell-resize::after {
    display: none;
}

/* Modern, minimal row separators + a smooth hover/selection fade (color
   transitions only - AG Grid's own --ag-row-hover-color/--ag-selected-
   row-background-color vars, set above, already drive the actual colors;
   this only softens the border and makes the state change feel deliberate
   rather than an instant flip). No row height/padding/column width
   changes - purely border color/opacity and a background-color
   transition. */
.ag-theme-quartz.users-ag-grid .ag-row {
    border-bottom: 1px solid color-mix(in srgb, var(--card-border) 70%, transparent);
    transition: background-color 0.12s ease;
}

.ag-theme-quartz.users-ag-grid .ag-row:last-child {
    border-bottom: none;
}

/* Sort icons (bi-sort-up/bi-sort-down/bi-arrow-down-up, supplied via
   gridOptions.icons in users.js) - matching the requested reference style:
   the neutral "not sorted yet" icon (always visible via
   defaultColDef.unSortIcon, not just on hover) is a light, minimal
   double-arrow in the app's muted grey icon tone; the active (asc/desc)
   icon on the currently-sorted column is bold and dark, using the same
   dark tone as the header text itself, so the active column is
   unmistakably highlighted rather than just a recolored variant of the
   neutral glyph. */
.ag-theme-quartz.users-ag-grid .ag-sort-icon-asc,
.ag-theme-quartz.users-ag-grid .ag-sort-icon-desc,
.ag-theme-quartz.users-ag-grid .ag-sort-icon-none {
    display: inline-block;
    transition: color 0.15s ease, opacity 0.15s ease;
    /* AG Grid swaps the icons.sort* HTML string wholesale when the sort
       state changes (unsorted -> asc -> desc -> unsorted) rather than
       toggling a class on one persistent element, so a plain `transition`
       on color/opacity never has anything to interpolate from - the new
       icon just appears already in its final state. This short entrance
       animation (fade + tiny scale-in) is what actually makes that swap
       read as a smooth state change instead of an instant snap, per
       "use smooth visual transitions when the sort order changes". */
    animation: ehs-sort-icon-in 0.18s ease;
}

@keyframes ehs-sort-icon-in {
    from {
        opacity: 0;
        transform: scale(0.6);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

/* Uses --grid-sort-icon-color (the dedicated Design Config token for exactly
   this) rather than --grid-header-text-color, which it borrowed before. Both
   default to #344054 so the icon looks identical - but they're now
   independent, which is what lets the header LABEL go light grey without
   dragging the sort icons along with it. */
.ag-theme-quartz.users-ag-grid .ag-sort-icon-asc,
.ag-theme-quartz.users-ag-grid .ag-sort-icon-desc {
    color: var(--grid-sort-icon-color, #344054);
    font-size: 0.85rem;
    font-weight: 700;
    opacity: 1;
}

.ag-theme-quartz.users-ag-grid .ag-sort-icon-none {
    color: var(--grid-search-icon-color, #98a2b3);
    font-size: 0.8rem;
    font-weight: 400;
    opacity: 0.85;
}

/* Small, consistent gap between the column label and its sort icon -
   AG Grid's own sort indicator wrapper, kept regardless of the custom
   icon content supplied above. */
.ag-theme-quartz.users-ag-grid .ag-sort-indicator-container {
    margin-left: 6px;
    /* Never let the icon shrink to make space for the label text above -
       the label truncates with an ellipsis instead. This is the other half
       of the header-icon-clipping fix. */
    flex: 0 0 auto;
}

/* Custom pagination footer (list.html's #usersGridFooter) - replaces AG
   Grid's native pagination panel entirely (suppressPaginationPanel: true,
   users.js), rendered/kept in sync by renderGridPaginationFooter(). Sits
   flush under .users-ag-grid inside the shared .users-ag-grid-panel shell
   (its own border-radius/shadow/border live on that wrapper, not here) -
   only the top divider border below is this element's own. */
.ehs-grid-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    padding: 10px 16px;
    background: var(--grid-footer-bg);
    border-top: 1px solid var(--card-border);
    font-size: 0.85rem;
    color: var(--grid-header-text-color);
}

.ehs-grid-footer-left {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.ehs-grid-page-size-label {
    margin: 0;
    color: #667085;
    font-weight: 500;
}

.ehs-grid-page-size-select {
    padding: 4px 28px 4px 10px;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: #fff;
    font-size: 0.85rem;
    color: var(--grid-header-text-color);
    cursor: pointer;
}

.ehs-grid-page-size-select:focus {
    outline: none;
    border-color: var(--form-focus-color);
    /* Ring geometry unified with .form-control:focus's canonical
       0 0 0 4px rgba(27, 131, 139, 0.15) (v1.22.1+20260811.04, changelog.md). */
    box-shadow: 0 0 0 4px rgba(27, 131, 139, 0.15);
}

.ehs-grid-row-summary {
    color: #667085;
}

/* Numbered pagination - rounded pill buttons with visible spacing between
   them (requirement: "Add proper spacing between page numbers"), the
   active page picked out in the app's primary theme color, and distinct
   hover/focus states - the part AG Grid's own native panel can't do at all
   since it has no per-page-number buttons. */
.ehs-grid-pagination {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.ehs-page-btn {
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--card-border);
    border-radius: 999px;
    background: #fff;
    color: var(--grid-header-text-color);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.ehs-page-btn:hover:not(:disabled):not(.active) {
    background: var(--grid-row-hover-bg);
    border-color: var(--btn-primary-bg);
    color: var(--btn-primary-bg);
}

.ehs-page-btn:focus-visible {
    outline: none;
    border-color: var(--form-focus-color);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--form-focus-color) 25%, transparent);
}

.ehs-page-btn.active {
    background: var(--btn-primary-bg);
    border-color: var(--btn-primary-bg);
    color: var(--btn-primary-text, #fff);
    cursor: default;
}

.ehs-page-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.ehs-page-btn.ehs-page-nav {
    font-size: 0.75rem;
}

.ehs-page-ellipsis {
    min-width: 20px;
    text-align: center;
    color: #98a2b3;
    font-size: 0.85rem;
}

@media (max-width: 576px) {
    .ehs-grid-footer {
        justify-content: center;
        text-align: center;
    }

    .ehs-grid-pagination {
        justify-content: center;
        width: 100%;
    }
}

.status-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 12px;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 700;
    /* Without an explicit line-height the pill inherits the grid's 34px row
       line-height, which (plus 3px vertical padding) made the pill 40px tall -
       taller than the 34px row - so it bled into the rows above and below.
       A compact line-height keeps the pill ~22px and vertical-align:middle
       centres it within the cell. */
    line-height: 1.2;
    vertical-align: middle;
}

/* Leading status dot. currentColor means each state's dot automatically
   matches that state's own text colour, so the three rules below stay the
   single source of truth for status colours. */
.status-pill::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}

.status-pill.status-active {
    background: #e6f4ea;
    color: #1e7e34;
}

.status-pill.status-inactive {
    background: #fdf1d6;
    color: #94640a;
}

.status-pill.status-archive {
    background: #f3f0ff;
    color: #5b21b6;
}

/* Extension-request decisions. `extStatusCellRenderer` in remediation.js has
   built `status-pill status-<decision_status>` since the Extensions page
   shipped, and the corrective-action dialog now shows `status-pending` for a
   request awaiting a decision — but none of the three variants existed, so
   every one of them fell through to the bare pill: no background, and a dot
   inheriting whatever colour it landed on. Pending borrows the amber already
   used for `status-inactive`, the palette's existing "needs attention, not
   wrong" tone. */
.status-pill.status-pending {
    background: #fdf1d6;
    color: #94640a;
}

.status-pill.status-approved {
    background: #e6f4ea;
    color: #1e7e34;
}

.status-pill.status-denied {
    background: #fdeaea;
    color: #b02a37;
}

/* Shared loading overlay for every Data Manager AG Grid. Rendered via each
   grid's `overlayLoadingTemplate` and shown/hidden through the grid's overlay
   API while a data-manager fetch is in flight. Uses the app's Bootstrap
   spinner and the primary theme colour so it matches the rest of the UI. */
.ehs-grid-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    color: var(--grid-header-text-color, #344054);
    font-size: 0.85rem;
    font-weight: 600;
}

.ehs-grid-loading .spinner-border {
    width: 2.25rem;
    height: 2.25rem;
    border-width: 3px;
    color: var(--btn-primary-bg, #1b838b);
}

/* Printable table for exportPdf() across all datatables - hidden on screen, shown only
   inside the browser print / Save as PDF dialog. */
[id$="PrintArea"],
.safekivi-print-area {
    display: none;
}

@media print {
    @page {
        size: landscape;
        margin: 8mm 10mm;
    }

    body * {
        visibility: hidden !important;
    }

    [id$="PrintArea"],
    [id$="PrintArea"] *,
    .safekivi-print-area,
    .safekivi-print-area * {
        visibility: visible !important;
    }

    [id$="PrintArea"],
    .safekivi-print-area {
        display: block !important;
        position: absolute !important;
        top: 0 !important;
        left: 0 !important;
        width: 100% !important;
        margin: 0 !important;
        padding: 0 !important;
        background: #ffffff !important;
    }

    .users-print-table,
    [id$="PrintArea"] table,
    .safekivi-print-area table {
        width: 100% !important;
        max-width: 100% !important;
        table-layout: auto !important;
        border-collapse: collapse !important;
        font-size: 10px !important;
        color: #000000 !important;
        background: #ffffff !important;
        word-break: break-word !important;
    }

    .users-print-table th,
    .users-print-table td,
    [id$="PrintArea"] table th,
    [id$="PrintArea"] table td,
    .safekivi-print-area table th,
    .safekivi-print-area table td {
        border: 1px solid #cbd5e1 !important;
        padding: 5px 6px !important;
        text-align: left !important;
        color: #000000 !important;
        word-wrap: break-word !important;
        overflow-wrap: break-word !important;
        vertical-align: middle !important;
    }

    .users-print-table th,
    [id$="PrintArea"] table th,
    .safekivi-print-area table th {
        background-color: #f1f5f9 !important;
        font-weight: 700 !important;
        font-size: 10.5px !important;
        -webkit-print-color-adjust: exact !important;
        print-color-adjust: exact !important;
    }
}

/* CORRECTION 6 (originally the expand-row detail panel, matching the
   Metronic user-management demo's stacked full-width labeled sections; now
   reused verbatim inside the view/edit modal's "User Details" section - see
   CORRECTION 8 above). Bold label row + pill-keyword values, thin divider
   between sections. */
.user-detail-sections {
    display: flex;
    flex-direction: column;
}

.user-detail-section {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 10px;
    padding: 10px 0;
    border-bottom: 1px solid #eef1f4;
}

.user-detail-section:last-child {
    border-bottom: none;
}

.user-detail-label {
    flex: 0 0 130px;
    font-weight: 700;
    font-size: 0.9rem;
    color: #1e2129;
}

.user-detail-values {
    flex: 1 1 auto;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.user-detail-values .keyword-pill {
    display: inline-block;
    padding: 3px 12px;
    border-radius: 999px;
    background: var(--card-bg);
    color: #344054;
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
}

.user-detail-values .user-detail-empty {
    color: #98a2b3;
    font-size: 0.85rem;
}

/* Modifier for reusing the sections above inside the add/edit user modal
   (see CORRECTION 8) - the modal is narrower than the old table-width
   detail row, so the label column shrinks a bit and the panel background
   goes back to plain white (the panel-gray background made sense as a
   visual break from a white table row; inside the modal it's redundant). */
.user-detail-sections-modal .user-detail-label {
    flex-basis: 110px;
}

.actions-cell {
    display: flex;
    align-items: center;
    /* Every Actions column is a fixed width sized to the icon buttons plus
       deliberate slack (see users.js's actionsCellRenderer comment), not a
       width that hugs its content - without this, that slack all sat on the
       right and the icon(s) rendered flush against the cell's left edge
       instead of sitting in the middle of the column, most visible on the
       single-icon columns (asset_types.js, remediation.js, and a dozen more)
       where it's one 28px button in an 96px+ cell. */
    justify-content: center;
    gap: 2px;
    white-space: nowrap;
    /* height: 100% is necessary but, on its own, not sufficient - see the
       .ag-cell-wrapper/.ag-cell-value rule right below, which is the other
       half of this same fix. Without either half the icon(s) render flush
       against the TOP of the row instead of centered in it. */
    height: 100%;
}

/* AG Grid wraps every cell's rendered content in .ag-cell-wrapper >
   .ag-cell-value (vendor/ag-grid/styles/ag-grid.css) before it ever reaches
   this cellRenderer's own .actions-cell div. .ag-cell-wrapper is flex with
   align-items: center, but neither it nor .ag-cell-value is given a height
   of its own - both size to their content (this cellRenderer's 28px button)
   and sit flush at the top of the real, taller .ag-cell box, rather than
   filling it. .actions-cell's height: 100% above has nothing taller than
   28px to actually inherit from until these two also stretch.
   Scoped to cells that wrap an .actions-cell specifically, not applied
   globally: every other grid cell is a single line of text with no reason
   to grow, and .ag-cell-value clips its own overflow, so forcing every
   cell's wrapper to stretch for no reason would risk clipping content that
   is taller than one line elsewhere. */
.ag-cell-wrapper:has(> .ag-cell-value > .actions-cell),
.ag-cell-value:has(> .actions-cell) {
    height: 100%;
}

.action-icon-btn {
    background: none;
    border: none;
    color: var(--grid-action-icon-color);
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-size: 0.95rem;
    cursor: pointer;
}

.action-icon-btn:hover {
    background: var(--card-bg);
    color: var(--btn-primary-hover-bg);
}

.action-icon-btn[aria-disabled="true"] {
    color: #c4cad3;
    cursor: default;
}

.action-icon-btn[aria-disabled="true"]:hover {
    background: none;
    color: #c4cad3;
}

@media (max-width: 767.98px) {
    .users-filter-offcanvas {
        width: 100% !important;
    }

    .filter-label {
        flex-basis: 110px;
    }
}

/* The full-page workspaces (Report Designer, Form Builder) are sized to end on
   #sidebar's bottom edge - see --workspace-shell-height. This column's own
   20px bottom padding sits below that line, so leaving it on would push the
   document 20px past the viewport and raise a page scrollbar on a layout whose
   whole point is that only the panels scroll. Zeroed for these two pages only;
   #app-main's --shell-gap padding-bottom still supplies the inset, and it is
   the same value #sidebar's own `bottom` uses, which is why the edges line up.
   Matched on the page's root wrapper rather than on the shell class because
   both shells sit one level down inside it (#main_body > #rdRoot > .rd-shell).
   Same scoping pattern as #main_body:has(.error-page-content) further down. */
#main_body:has(> #rdRoot),
#main_body:has(> #fbRoot) {
    padding-bottom: 0;
}

#main_body {
    /* Grows to fill a short page, past the viewport on a tall one. No overflow
       of its own: any value other than visible would make this a nested
       scrollport again, and `overflow-x: hidden` is not exempt - per spec a
       non-visible value on one axis forces the other to `auto`, which is
       exactly the inner scrollbar this layout avoids. */
    flex: 1 1 auto;
    /* No horizontal padding: #app-main already owns both side insets - its
       margin-left puts this column --content-gap from the sidebar, and its
       padding-right holds the same --shell-gap the header card uses, so the
       content's right edge lines up with the header's. Padding here would add
       to both and break that alignment. Mobile restores it (see the
       max-width:767.98px block), where #app-main's own margin/padding are
       zeroed and this is the only inset. */
    padding: 0 0 20px 0;
    position: relative;
    z-index: auto;
}

/* Sidebar Backdrop for Mobile Off-Canvas Drawer */
.sidebar-backdrop {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(2px);
    z-index: 1040;
    opacity: 0;
    transition: opacity 0.25s ease;
}

.sidebar-backdrop.show {
    display: block;
    opacity: 1;
}

@media (max-width: 767.98px) {
    #sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        width: 260px !important;
        min-width: 260px !important;
        max-width: 260px !important;
        z-index: 1045 !important;
        transform: translateX(-100%) !important;
        transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
        box-shadow: none;
        border-radius: 0 !important;
        border: none;
        border-right: 1px solid var(--card-border, #eef1f4);
    }

    body.sidebar-mobile-open #sidebar {
        transform: translateX(0) !important;
        box-shadow: 6px 0 24px rgba(0, 0, 0, 0.15) !important;
    }

    /* Force all text labels, brand logo, and sub-items to be visible in mobile drawer */
    body.sidebar-collapsed #sidebar,
    body.sidebar-mobile-open #sidebar {
        width: 260px !important;
        min-width: 260px !important;
        max-width: 260px !important;
    }

    .sidebar-menu {
        padding: 12px !important;
        position: static !important;
        top: 0 !important;
    }

    .sidebar-menu a,
    .menu-team-toggle {
        display: flex !important;
        justify-content: flex-start !important;
        align-items: center !important;
        gap: 10px !important;
        width: 100% !important;
        height: auto !important;
        padding: 9px 12px !important;
        text-align: left !important;
        margin: 0 0 2px !important;
        border-radius: 12px !important;
        color: var(--nav-side-text, #344054) !important;
    }

    body.sidebar-collapsed .menu-team-label,
    body.sidebar-collapsed .menu-link-label,
    body.sidebar-collapsed .menu-caret,
    .menu-team-label,
    .menu-link-label,
    .menu-caret {
        display: inline-block !important;
        font-size: 0.95rem !important;
        font-weight: 600 !important;
        color: var(--nav-side-text, #344054) !important;
    }

    .menu-team-icon {
        width: 22px !important;
        height: 22px !important;
        margin: 0 8px 0 0 !important;
        font-size: 0.95rem !important;
    }

    .menu-subitems {
        display: none;
    }

    .menu-subitems.show {
        display: block !important;
    }

    body.sidebar-collapsed .menu-subitems,
    body.sidebar-collapsed .menu-subitems.collapse {
        display: none;
    }

    body.sidebar-collapsed .menu-subitems.show {
        display: block !important;
    }

    /* There is no docked-in-the-sidebar position to hold here: #sidebar is an
       off-canvas drawer at this width, parked entirely off-screen until opened,
       so a button pinned to its top-right corner would sit off-screen too. It
       becomes a standalone floating menu button at the top-left of the viewport
       instead - the conventional place for one - and nav.js swaps it to a
       hamburger/close pair to match. The desktop rule's `left` is calculated
       from --sidebar-width, which is meaningless here, hence the full reset. */
    .sidebar-collapse-btn {
        position: fixed !important;
        top: 14px !important;
        left: 12px !important;
        z-index: 1035 !important;
        width: 36px !important;
        height: 36px !important;
        border-radius: 8px !important;
        border: 1px solid #d0d5dd !important;
        background: #ffffff !important;
        color: #1b838b !important;
        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08) !important;
        transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
    }

    /* Open drawer: #sidebar above is `top: 0; left: 0; width: 260px` at
       z-index 1045, so it would cover this button at left: 12px along with the
       close (X) state nav.js switches it to. Slide it past the drawer's right
       edge and raise it above, so the control stays reachable. #sidebarBackdrop
       also closes the drawer, so this is a second way out rather than the only
       one, but a visible X that cannot be tapped is worse than no X at all. */
    body.sidebar-mobile-open .sidebar-collapse-btn {
        left: 244px !important;
        z-index: 1050 !important;
    }

    /* The desktop rule reserves 44px at the top of the collapsed rail for the
       docked button. There is no rail at this width - collapsed or not, the
       drawer shows full-width labels - so the menu goes back to its normal
       inset and that space is not left empty above the first item. */
    body.sidebar-collapsed .sidebar-menu {
        padding: 12px !important;
    }

    #app-main {
        margin-left: 0 !important;
        width: 100% !important;
        padding: 0 !important;
    }

    /* #app-main contributes no inset here, so #main_body carries both side
       gutters again (the desktop rule drops them to 0 - see the note there).
       .cc-nav sits outside #main_body, so it needs the same gutter itself to
       stay aligned with the content. */
    #main_body {
        padding-left: 20px;
        padding-right: 20px;
    }

    .cc-nav,
    .dl-nav {
        padding-left: 20px;
        padding-right: 20px;
    }

    body.sidebar-collapsed #app-main {
        margin-left: 0 !important;
        width: 100% !important;
    }

    /* Back to normal document flow on mobile - the desktop rule pins the
       header full-width/fixed above the sidebar, which the edge-to-edge
       off-canvas drawer layout here neither needs nor has room for.
       z-index is reset with it: #app-header is a flex item of #app-main, and
       per the flexbox spec a flex item with a z-index other than `auto`
       creates a stacking context even while `position: static` - which used to
       let the header out-paint the floating drawer toggle and swallow its
       clicks. The toggle is a child of the header now, so it can no longer
       lose to its own parent, but `auto` is still correct here: the open
       drawer (z-index 1045) must paint over the header, not under it.

       The 56px left gutter that used to be reserved here is gone with the
       floating button it was reserving space FOR - the toggle is the header's
       own first flex item now, so it takes its space from normal flow. */
    #app-header {
        position: static !important;
        z-index: auto !important;
        padding: 8px 20px !important;
        margin-top: 0 !important;
    }
}

/* --- CORRECTION 10: Add/Edit User modal, Metronic "solid" form style ---
   Reference: https://preview.keenthemes.com/metronic8/demo1/?page=utilities/
   modals/forms/new-address - Metronic8's form-control-solid/form-select-solid
   input treatment (no visible border, light gray fill, generous radius/
   padding, background lightens on focus instead of a heavy blue outline)
   plus a `required` label modifier that appends a red asterisk. That page is
   a client-rendered SPA fragment; a direct fetch only returned its field
   labels (First name/Last name/Country) since Claude in Chrome wasn't
   connected this session to screenshot/audit the live DOM/CSS directly.
   The values below follow form-control-solid's well-documented, consistent
   convention used across every other form page in that same demo (it isn't
   a one-off style unique to the new-address modal), rather than an assumed
   guess. Scoped to #userForm/#userModal only - does not touch the plain
   Bootstrap .form-control/.form-select/.btn classes used elsewhere (e.g.
   login.html), consistent with this file's existing scoping discipline. */
/* Global Modal & Form Styling - Light, clean, consistent across all forms & popups */
.modal-content,
.modal-header,
.modal-body,
.modal-footer,
.nd-modal {
    background-color: #ffffff !important;
    color: #1e2129 !important;
}

.modal-content {
    border-radius: var(--form-container-radius, 26px);
    overflow: hidden;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
}

.modal-header {
    border-bottom: 1px solid #eff2f5 !important;
    padding: 1.25rem 1.75rem;
}

.modal-body {
    padding: 1.5rem 1.75rem;
}

.modal-footer {
    border-top: 1px solid #eff2f5 !important;
    padding: 1rem 1.75rem;
}

/* Ensure form labels inside all modals and form panels have crisp, high-contrast text */
/* --- Labels: one label treatment app-wide (Form Design System, see the
   banner comment further down this file at ".form-control, .form-select").
   The size/weight/spacing values here are the Add/Edit User modal's, which
   used to live in a `#userForm`-scoped rule that no other form could reach;
   they are the standard for every form now. --- */
.modal .form-label,
.modal label,
.nd-modal .form-label,
.nd-modal label,
.form-label {
    color: #344054 !important;
    font-size: 0.9rem;
    font-weight: 600;
    margin-bottom: 6px;
}

/* Modal Title typography - default for a plain (non .sk-modal-header) modal.
   #userModal was previously listed here explicitly, from before Users
   adopted .sk-modal-header's solid teal header; left in, its (1,1,0)
   ID-scoped specificity beat .sk-modal-header .modal-title's (0,2,0) even
   though both carry !important, forcing the title back to near-black on
   the new teal background - reported directly. Removed; .sk-modal-header
   .modal-title (below) is the only rule that should touch this modal's
   title color now. */
.modal-title,
.nd-title {
    color: #1e2129 !important;
    font-weight: 700;
}

/* Form input text color inside modals */
.modal .form-control,
.modal .form-select,
.modal .form-control-solid,
.modal .form-select-solid {
    color: #1e2129 !important;
}

/* Modal header close button */
.modal-header .btn-close {
    filter: none !important;
    opacity: 0.65;
}

.modal-header .btn-close:hover {
    opacity: 1;
}

/* `#userForm .form-label`, `#userForm .form-label.required::after` and
   `#userForm .form-control-solid/.form-select-solid` all lived here. Every
   one of them is now an app-wide rule instead (the label block above, the
   `.required-label::after, .required::after` block further down, and the
   `.form-control, .form-select, .form-control-solid, ...` block below) -
   the Add/Edit User modal's own look is the application standard now, so
   scoping it to that one form is exactly backwards. Nothing about how
   `#userForm` renders changed; every other form caught up to it. */

/* #userForm .form-control-solid:focus / .form-select-solid:focus removed
   (v1.22.1+20260811.04, changelog.md) - confirmed dead code: every element
   it targeted also carries the bare .form-control/.form-select class, and
   the app-wide .form-control:focus rule (site.css, ~line 3549) styles the
   same background-color/border-color/box-shadow/outline properties with
   !important, so it always won the cascade and this rule never rendered. */

/* The small-control variant keeps its tighter sizing against the standard
   control rule above (both carry !important, so this needs the extra class
   to win on specificity). App-wide, not `#userForm`-scoped as it once was -
   `.form-control-sm` means the same thing on every form. */
.form-control-sm.form-control-solid,
.form-select-sm.form-select-solid {
    padding: 0.35rem 0.7rem !important;
    font-size: 0.825rem !important;
}

/* --- CORRECTION 13: modern inline validation (replaces the native
   reportValidity() browser tooltip - see submitUserForm()/validateUserForm()
   in users.js) - a red-bordered field plus small helper text under it,
   consistent with the rest of this form's own styling instead of the
   browser's plain default popup. */
/* The invalid-field treatment that used to live here (`#userForm`-scoped) is
   merged into the single app-wide `.form-control.is-invalid` block further
   down this file, which already carried the warning-icon and focus-ring half
   of the same state - two half-definitions of one state in one stylesheet is
   how they drift apart. */

.invalid-feedback-solid {
    color: var(--form-required-color, #e04f5f);
    font-size: 0.78rem;
    font-weight: 500;
    margin-top: 5px;
}

/* --- CORRECTION 15/16: SafeKivi modal-form standard (`.sk-modal-header`) ---
   Piloted on the Edit User modal, then requested app-wide for every Add/Edit
   form dialog. A solid primary-color modal header (matching the Filter
   drawer's `.offcanvas-header` treatment below in this file exactly - same
   background token, same inverted close button) plus a leading icon avatar
   and subtitle, and pure-white (not grey-filled) inputs with a visible
   border and a primary-color focus ring.

   Opt-in classes, not a silent global override of bare `.modal-header` -
   `.nd-modal`'s own dialogs (SafeKiviDialog) and any confirm/alert modal
   that isn't an Add/Edit record form must NOT pick this up unasked, the
   same reasoning `.sk-input-icon`/`.sk-form-header` are opt-in rather than
   applied to every `.form-control` on sight.

   Markup:
     <div class="modal-header sk-modal-header">
       <div class="d-flex align-items-center gap-2">
         <div class="sk-modal-header-icon"><i class="bi bi-people-fill"></i></div>
         <div>
           <h5 class="modal-title mb-0">Add Team</h5>
           <p class="sk-modal-header-subtitle mb-0">Create a new team and assign its type</p>
         </div>
       </div>
       <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
     </div>

   The white-input half of this standard needs no per-page markup at all:
   every field this app has already wrapped in `.sk-input-icon` (component
   catalog #16) picks it up automatically via the descendant rule below,
   since that wrapper is already the marker for "this is a real form field",
   the exact same reasoning that made the app-wide icon rollout free of
   per-field CSS. This supersedes CORRECTION 10's grey fill wherever
   `.sk-input-icon` is present - the grey fill remains the default only for
   fields that were deliberately never wrapped (textareas, and anything
   else `.sk-input-icon` itself is scoped away from). */
.sk-modal-header {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    color: #ffffff !important;
    padding: 1.1rem 1.5rem !important;
    border-bottom: none !important;
}

.sk-modal-header .modal-title {
    color: #ffffff !important;
}

.sk-modal-header-icon {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.18);
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    font-size: 1.1rem;
    flex: 0 0 auto;
}

.sk-modal-header-subtitle {
    color: rgba(255, 255, 255, 0.78);
    font-size: 0.78rem;
    margin: 2px 0 0;
}

/* A de-emphasized identifier sitting inline in the title itself (e.g.
   "Corrective Action #2"), same treatment as .sk-modal-header-subtitle above
   for the same reason: Bootstrap's .text-muted is a mid-gray with its own
   !important, so it was rendering barely visible against this header's solid
   teal fill instead of inheriting .modal-title's white - a direct rule on the
   element always wins over an inherited one, .text-muted's !important or
   not. */
.sk-modal-header-id {
    color: rgba(255, 255, 255, 0.78);
    font-weight: 500;
}

/* Same treatment as .offcanvas-header .btn-close above - turns the dark X
   white so it reads against the new solid background. */
.sk-modal-header .btn-close {
    filter: invert(1) grayscale(1) brightness(2) !important;
    opacity: 0.85 !important;
}

.sk-modal-header .btn-close:hover {
    opacity: 1 !important;
}

.sk-input-icon .form-control-solid,
.sk-input-icon .form-select-solid {
    background-color: #ffffff !important;
    /* border: 1px solid var(--card-border, #e1e3ea) !important; */
}

.sk-input-icon .form-control-solid:focus,
.sk-input-icon .form-select-solid:focus {
    background-color: #ffffff !important;
    border-color: var(--btn-primary-bg, #1b838b) !important;
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.15) !important;
}

/* A handful of dense Add/Edit modals (Corrective Action, Request Extension,
   New Course, Schedule Session, Add Team, and others) were sitting close
   enough to the modal's own `max-height: calc(100% - 3.5rem)` (further down
   this file) that ordinary rendering differences (font metrics, zoom, the
   .sk-input-icon wrapper's own markup) tipped a few of them into showing
   `.modal-body`'s `overflow-y: auto` scrollbar even though there was really
   nothing worth scrolling - reported directly. Tightening every modal's row
   spacing (not just #userForm's own, already-tightened rows above) removes
   enough height across a form's full field list to clear that margin
   reliably, rather than patching each affected modal individually. */
.modal-body .mb-3 {
    margin-bottom: 0.6rem !important;
}

/* Required-fields notice - already a documented, established component,
   see `.sk-required-note` further down this file (piloted on the Audit
   Plans/Start Audit modal footers). Reused as-is, not redefined here. */

/* Roster Assignments card - reuses .safekivi-card/.sk-card-header verbatim
   (component_catalog.md §13/§9) rather than a bespoke section style, just
   with padding/margins tightened for a nested-in-modal context instead of a
   full page. */
#userRosterSectionWrap .safekivi-card {
    padding: 14px !important;
    margin-bottom: 0 !important;
}

#userRosterSectionWrap .sk-card-header {
    padding-bottom: 10px !important;
    margin-bottom: 10px !important;
}

#userRosterSectionWrap .sk-card-header-title {
    font-size: 0.95rem;
}

.user-roster-container {
    max-height: 220px;
    overflow-y: auto;
}

/* Global Bootstrap .btn-primary/.btn-secondary/.btn-outline-secondary/
   .btn-danger/.btn-outline-danger theming (CORRECTION 14) - NOT scoped to
   #userModal. This is deliberately global, not modal-only: login.html's
   "Continue"/"Sign in" buttons and dashboard.html's "Log out" button use
   these exact same Bootstrap classes and were still rendering Bootstrap's
   stock blue/gray (never themed before this feature), which is precisely
   the cross-page color inconsistency Design Config exists to eliminate.
   Applying this globally means those pages now pick up the same brand
   color as everywhere else - a real, intended visual change, not a
   regression. Save/Update-style actions = Primary, Cancel/Close-style =
   Secondary, Delete-style = Danger (no delete button exists yet in this
   port, but it's wired now so one just works when built). */
.btn-primary {
    background: var(--btn-primary-bg);
    border-color: var(--btn-primary-bg);
    color: var(--btn-primary-text, #fff);
}

.btn-primary:hover,
.btn-primary:focus {
    background: var(--btn-primary-hover-bg);
    border-color: var(--btn-primary-hover-bg);
}

.btn-secondary,
.btn-outline-secondary {
    background: var(--btn-secondary-bg);
    border-color: var(--btn-secondary-border);
    color: var(--btn-secondary-text);
}

.btn-secondary:hover,
.btn-outline-secondary:hover {
    background: var(--btn-secondary-hover-bg);
    color: var(--btn-secondary-text);
}

.btn-danger,
.btn-outline-danger {
    background: var(--btn-danger-bg);
    border-color: var(--btn-danger-bg);
    color: var(--btn-danger-text, #fff);
}

.btn-outline-danger {
    background: transparent;
    color: var(--btn-danger-bg);
}

.btn-danger:hover,
.btn-outline-danger:hover {
    background: var(--btn-danger-hover-bg);
    border-color: var(--btn-danger-hover-bg);
    color: var(--btn-danger-text, #fff);
}

/* --- Form action buttons (Form Design System) --- App-wide, not
   `#userModal`-scoped as this once was: Save/Cancel in a form dialog are the
   same control wherever they appear, and every other modal's footer buttons
   were rendering at Bootstrap's default padding/weight against Users'
   deliberately-tuned ones. Radius stays 999px (the app's established pill
   convention, already global for .btn). */
.modal-footer .btn,
.sk-form-actions .btn {
    border-radius: 999px;
    padding: 7px 22px;
    font-size: 0.875rem;
    font-weight: 600;
}

.modal-footer .btn-primary,
.sk-form-actions .btn-primary {
    padding: 7px 24px;
    font-weight: 700;
}

/* Row of actions closing a non-modal (in-page) form - the page equivalent of
   a modal footer, so the same button treatment and the same right-aligned
   layout, including room for a `.sk-required-note` pushed left with me-auto. */
.sk-form-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    justify-content: flex-end;
    flex-wrap: wrap;
    padding-top: 14px;
    margin-top: 4px;
    border-top: 1px solid var(--card-border, #eef1f4);
}

/* --- Users grid expand-row: floating detail panel (User Keywords + Access
   Roles) - see the large comment above the expand/collapse section in
   users.js for why this replaced the earlier colSpan + variable-row-height
   "child row" approach (that mechanism relied on a getRowHeight callback,
   which AG Grid's own docs say the Infinite Row Model doesn't support -
   it's what made the old toggle unreliable). Row height/spacing/column
   widths are completely unaffected now - .name-cell is always the same
   single-line layout regardless of expand state; only the toggle button's
   own icon rotates. */
.name-cell {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 100%;
}

.name-cell-text {
    font-weight: 600;
    color: #1d2939;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.name-cell-avatar {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    font-size: 0.68rem;
    font-weight: 700;
    flex: none;
}

/* Disable static backdrop bounce/scale animation to prevent visual flickering on click outside */
.modal.modal-static .modal-dialog {
    transform: none !important;
    transition: none !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SAFEKIVI FORM DESIGN SYSTEM - the single source of truth for every form
   control in the application. See playbook/design_system/component_catalog.md
   §19 ("Form Design System") for the written standard and the rules that go
   with it.

   The Add/Edit User modal is the visual reference this was derived from; the
   rules below are the generalization of what used to be `#userForm`-scoped
   CSS, so every form in the app - and every form built in future - inherits
   the same look with no per-page CSS of its own.

   DO NOT restate any of these properties in a page's own <style> block or in
   a module stylesheet. Four pages used to (home_config, super_admin/tenants,
   login's #sitePicker, control_center/security) and every one of them had
   drifted to slightly different values, which is exactly the inconsistency
   this block exists to prevent. If a form genuinely needs something this
   system doesn't provide, extend it HERE so every page benefits.

   `.form-control-solid` / `.form-select-solid` are listed alongside the base
   classes deliberately: they are supposed to be paired with a base class
   (`form-control form-control-solid`), but runtime-generated markup has
   repeatedly shipped the modifier alone (form_builder.js, and audit_plans'
   29 controls fixed at v1.34.3). Matching the bare modifier too means such a
   field still renders correctly instead of falling back to an unstyled
   native input - a safety net, NOT a licence to omit the base class.
   ═══════════════════════════════════════════════════════════════════════════ */

/* --- Controls: text inputs, selects, textareas --- */
.form-control,
.form-select,
.form-control-solid,
.form-select-solid,
.filter-input,
.filter-multiselect-btn,
/* The lookup widget's control (component_catalog.md §12). Listed here rather
   than restating these values in form-fields.css for the same reason
   `.filter-multiselect-btn` and select2's selection box are: they are not
   native form controls, but they stand where one stands and must therefore
   carry the identical border, radius, padding and font. Leaving it out is what
   made the audit forms' entity picker the one field on the page that did not
   look like a field. */
.sk-lookup-control,
.select2-container--bootstrap-5 .select2-selection {
    background-color: #ffffff !important;
    border: 1px solid #e1e3ea !important;
    color: #3f4254 !important;
    /* The one standard control font-size, app-wide. Previously only
       `#userForm` set this (0.9rem) and every other form in the app fell
       through to Bootstrap's 1rem default - the single biggest source of
       "these forms look different" across the product. */
    font-size: 0.9rem !important;
    font-weight: 500 !important;
    border-radius: 8px !important;
    padding: 8px 14px !important;
    transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease !important;
}

/* The lookup widget's inner search box is the control's text field, not a
   control in its own right — `.sk-lookup-control` above already draws the
   border, radius and padding, so a second set here would nest one control
   inside another. `!important` because the canonical rule it would otherwise
   inherit from `.form-control` is itself `!important`. The left padding is the
   gutter for its own search icon.

   The `:focus` variant carries the same suppression at higher specificity than
   `.form-control:focus`, which is declared later in this file and would
   otherwise draw a focus ring around the inner field — a ring inside a box. The
   ring belongs on `.sk-lookup-control`, which takes it via `:focus-within` in
   the focus rule below.

   The border stays off even when the field is invalid: giving it one back adds
   ~1.4px of height and the control visibly jumps as validation fires. The
   invalid cue is carried by the box's own red border (rule below) plus the pink
   fill and warning icon that `.form-control.is-invalid` still paints on this
   field — neither of which affects layout. */
.sk-lookup-control .sk-lookup-input,
.sk-lookup-control .sk-lookup-input:focus {
    border: 0 !important;
    border-radius: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    padding: 0 0 0 20px !important;
    height: auto !important;
}

/* The red border goes on the box, because the box is what has one. Callers keep
   marking `.sk-lookup-input` — it is the only descendant `.form-control.is-invalid`
   can paint, and every existing caller already targets it — so this lifts the
   state up rather than requiring each of them to change. */
.sk-lookup-control:has(.sk-lookup-input.is-invalid) {
    border-color: var(--form-required-color) !important;
}

/* Adjust size for smaller form elements */
.form-control-sm,
.form-select-sm,
.filter-input-sm {
    padding: 6px 10px !important;
    font-size: 0.825rem !important;
}

/* Textareas: same control treatment, but a readable line-height and a sane
   minimum height (a 1-row textarea reads as a broken text input). Never
   given the leading-icon padding `.sk-input-icon` applies - that component
   is single-line only, see component_catalog.md §16. */
textarea.form-control,
textarea.form-control-solid {
    min-height: 84px;
    line-height: 1.5;
}

/* Help text under a field - one size app-wide, deliberately smaller than the
   label above it and the control it explains, and matching
   `.invalid-feedback-solid`'s size so help text and validation text on the
   same form don't render at two different scales. */
.form-text,
.sk-form-help {
    font-size: 0.72rem !important;
    color: var(--text-muted, #667085) !important;
    margin-top: 5px !important;
}

/* Placeholder styling - one placeholder treatment app-wide. Sized off the
   control's own font-size (inherit) rather than a second hardcoded value, so
   a placeholder can never drift out of step with the text the user types
   over it. */
.form-control::placeholder,
.form-control-solid::placeholder,
.filter-input::placeholder {
    color: #a1a5b7 !important;
    font-size: inherit !important;
    font-weight: 400 !important;
}

/* Focus states */
.form-control:focus,
.form-select:focus,
.form-control-solid:focus,
.form-select-solid:focus,
.filter-input:focus,
.filter-multiselect-btn:focus,
.filter-multiselect-btn.show,
/* :focus-within, not :focus — what the user actually focuses is the search box
   inside this control, and the ring belongs on the control around it. */
.sk-lookup-control:focus-within,
.select2-container--bootstrap-5.select2-container--focus .select2-selection,
.select2-container--bootstrap-5.select2-container--open .select2-selection {
    background-color: #ffffff !important;
    border-color: var(--btn-primary-bg, #1B838B) !important;
    box-shadow: 0 0 0 4px rgba(27, 131, 139, 0.15) !important;
    color: #181c32 !important;
    outline: 0 !important;
}

/* ── Selection controls: checkbox / radio / switch ────────────────────────────
   Every one of these takes the brand primary, never Bootstrap's blue.

   This rule used to rely on `accent-color` alone, with a comment claiming that
   avoided "reimplementing the control via appearance:none + a custom SVG". That
   reasoning only holds for NATIVE controls. Bootstrap's own `.form-check-input`
   already sets `appearance: none` and paints its own box, which makes
   `accent-color` a complete no-op there - so every Bootstrap checkbox, radio and
   switch in the app carried on rendering in stock Bootstrap blue. Measured on a
   live switch: `accent-color` resolved to the teal correctly, and
   `background-color` was still `rgb(13, 110, 253)`. That blue is what was
   reported.

   `accent-color` is KEPT below: it is what colours the ~100 plain native
   checkboxes in the app (which have no Bootstrap class and do honour it). The
   rules after it cover the Bootstrap-styled ones. Both are needed. */
.form-check-input,
input[type="checkbox"],
input[type="radio"] {
    accent-color: var(--btn-primary-bg, #1b838b) !important;
}

/* The box itself, checked or indeterminate. Beats Bootstrap's own
   `.form-check-input:checked` (equal specificity, so `!important` decides).

   `:not(.is-valid):not(.is-invalid)` is not defensive padding: because this rule
   carries `!important`, it would otherwise also beat Bootstrap's
   `.form-check-input.is-invalid:checked` (which has HIGHER specificity but no
   `!important`), so a checkbox flagged invalid would have rendered brand teal
   instead of red. Nothing in the app marks a checkbox invalid today - grepped
   markup and JS, and `was-validated` is unused - so this is a latent trap being
   closed rather than a live bug being fixed. Validation state must outrank brand
   colour. */
.form-check-input:checked:not(.is-valid):not(.is-invalid),
.form-check-input:indeterminate:not(.is-valid):not(.is-invalid) {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    border-color: var(--btn-primary-bg, #1b838b) !important;
}

/* Focus ring. Geometry matches `.form-control:focus` above, so a checkbox next
   to a text field on the same form reads as one design language. Same
   validation-state exclusion as the checked rule, for the same reason. */
.form-check-input:focus:not(.is-valid):not(.is-invalid),
input[type="checkbox"]:focus,
input[type="radio"]:focus {
    border-color: var(--btn-primary-bg, #1b838b) !important;
    box-shadow: 0 0 0 4px rgba(27, 131, 139, 0.15) !important;
    outline: 0 !important;
}

/* Switch knob. Bootstrap 5.3 exposes it as the `--bs-form-switch-bg` custom
   property and swaps it for a light-BLUE circle while focused, which is the one
   remaining place its blue leaks through on a toggle. Overriding the variable
   keeps Bootstrap's own geometry and changes only the colour.
   Scoped to :not(:checked) deliberately - a checked switch already gets
   Bootstrap's white knob on our teal track, which is correct. The neutral knob
   below is Bootstrap's own unchecked value; a brand hex baked into a data URI
   could not follow a tenant's Design Config retheme anyway. */
.form-switch .form-check-input:focus:not(:checked) {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e") !important;
}

/* ── Native <select> option list ──────────────────────────────────────────────
   The last blue left on a form, and the one nobody could place: the popup a
   plain `<select>` opens is painted by the browser, not by us, and it
   highlights the current value in the OS accent — reported from the New Course
   dialog's Delivery Method, where the open list showed a blue "Online" row
   beside teal everything else.

   `option:checked` is that row. Chromium honours a background-color on it (and
   on the selected rows of an inline `<select multiple>`, which is where the
   Prerequisites and Competencies lists show it), so it takes the brand primary
   like every other selection control above. `accent-color` is set alongside it
   for the browsers that map a listbox highlight onto it rather than onto the
   option's own background.

   WHAT THIS CANNOT DO, stated plainly so the next person does not go hunting: a
   native popup is an OS menu, and no stylesheet controls all of it. The
   hover-highlight as the pointer moves down the list, the popup's own border and
   shadow, and the whole control on macOS are the browser's to draw. The app
   already has the answer where a dropdown must be fully themed —
   `SafeKiviFormWidgets`' lookup widget and Select2, whose highlighted row is
   brand-coloured a few hundred lines above — and that is what
   component_catalog.md §12 asks for on any picker of consequence. This rule is
   for the short fixed lists that legitimately stay native. */
select {
    accent-color: var(--btn-primary-bg, #1b838b);
}

select option:checked {
    background-color: var(--btn-primary-bg, #1b838b);
    color: var(--btn-primary-text, #ffffff);
}

/* Range slider and Bootstrap toggle buttons - the remaining controls
   Bootstrap paints with `--bs-primary`, wired here so that color never
   reappears the first time a page uses one. First (and, at time of writing,
   only) consumer: Home Config's banner positioning controls
   (home_config.html's Zoom/Offset sliders and Position/Fit button groups). */
.form-range::-webkit-slider-thumb,
.form-range::-moz-range-thumb {
    background-color: var(--btn-primary-bg, #1b838b) !important;
}

.btn-check:checked+.btn,
.btn-check:active+.btn {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    border-color: var(--btn-primary-bg, #1b838b) !important;
    color: var(--btn-primary-text, #fff) !important;
}

/* Adjust form select dropdown arrow appearance.

   `padding-right` is not decoration: the standard control rule above sets the
   `padding` SHORTHAND to `8px 14px !important`, which overrides Bootstrap's own
   `padding-right: 2.25rem` reservation for the chevron. The chevron is drawn as
   a background image 16px wide positioned 1rem from the right edge, so it
   occupies roughly 16px-32px of the right gutter while the text was only being
   held back 14px - long option text ran underneath the arrow.

   Only noticeable once a `<select>` actually carries `.form-select`; several
   runtime-generated selects (form_builder.js) previously carried only the
   `-solid` modifier and so rendered the browser's native arrow, which reserves
   its own space. Giving them their base class - correct, and required by §19 -
   is what exposed this. Fixed here, centrally, rather than per page.

   A longhand beats the shorthand above at equal specificity because this rule
   comes later in the file; `!important` is still needed to beat the shorthand's
   own `!important`. */
.form-select,
.form-select-solid,
.filter-input select {
    padding-right: 2.5rem !important;
    background-position: right 1rem center !important;
    background-size: 16px 12px !important;
}

/* Session Expiration Warning & Backdrop Overlay.
 *
 * The dialog itself is now built from the shared .nd-* dialog classes (see
 * .nd-modal / .nd-header / .nd-icon / .nd-title / .nd-body / .nd-footer /
 * .nd-btn-primary further down this file) so it looks like the same dialog
 * system as SafeKiviDialog.warning()/.confirm() rather than a one-off popup.
 * Only what .nd-* has no token for — the countdown pill, and this dialog's
 * own backdrop — are styled here.
 *
 * z-index is intentionally ABOVE .nd-overlay/.nd-modal (10500/10501): this
 * warning must stay reachable even if a SafeKiviDialog (a confirm, a loading
 * spinner) happens to be open when the inactivity timer fires. At the old
 * z-index (9999/10000) it rendered fully hidden behind any open SafeKiviDialog,
 * so a user watching a stalled "Loading…" spinner could be logged out with no
 * visible warning at all.
 */
#diagModalOverlay {
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    width: 100vw !important;
    height: 100vh !important;
    background-color: rgba(10, 14, 26, 0.55) !important;
    /* Same backdrop treatment as .nd-overlay, for visual consistency. */
    z-index: 10600 !important;
    backdrop-filter: blur(3px) !important;
}

#sk_session_timeout_warning {
    z-index: 10601 !important;
    /* Positioning, size, background, radius and shadow all come from
       .nd-modal + .nd-modal--sm (added in the markup) — no overrides needed. */
}

/* The one thing the shared dialog system has no token for: a ticking
   MM:SS countdown rendered as a single pill inside the message text. */
#sk_session_timeout_warning .sk-session-countdown {
    display: inline-block;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--header-text, #1e2129);
    background-color: var(--card-header-bg, #f5f8fa);
    padding: 2px 8px;
    border-radius: 999px;
    margin: 0 2px;
}

/* ==========================================
   Centralized UI/UX Design System Guidelines
   ========================================== */

/* --- Centralized Global Button System --- */
.btn-primary,
.btn-safekivi-primary,
.nd-btn-primary,
.nd-btn-ok,
.btn-apply-filters,
.btn-create-user {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    color: var(--btn-primary-text, #ffffff) !important;
    border: 1px solid var(--btn-primary-bg, #1b838b) !important;
    font-weight: 600 !important;
    border-radius: 999px !important;
    padding: 8px 20px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 6px !important;
    transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease !important;
    cursor: pointer !important;
    text-decoration: none !important;
    outline: none !important;
}

.btn-primary:hover,
.btn-safekivi-primary:hover,
.nd-btn-primary:hover,
.nd-btn-ok:hover,
.btn-apply-filters:hover,
.btn-create-user:hover {
    background-color: var(--btn-primary-hover-bg, #146267) !important;
    border-color: var(--btn-primary-hover-bg, #146267) !important;
    color: var(--btn-primary-text, #ffffff) !important;
}

.btn-primary:focus,
.btn-safekivi-primary:focus,
.nd-btn-primary:focus,
.nd-btn-ok:focus,
.btn-apply-filters:focus,
.btn-create-user:focus {
    box-shadow: 0 0 0 4px rgba(27, 131, 139, 0.15) !important;
    outline: 0 !important;
}

.btn-primary:disabled,
.btn-safekivi-primary:disabled,
.nd-btn-primary:disabled,
.nd-btn-ok:disabled,
.btn-apply-filters:disabled,
.btn-create-user:disabled {
    background-color: #e1e3ea !important;
    border-color: #e1e3ea !important;
    color: #a1a5b7 !important;
    cursor: not-allowed !important;
    opacity: 0.65 !important;
}

/* Secondary & Outline Buttons */
.btn-secondary,
.btn-safekivi-secondary,
.nd-btn-secondary,
.btn-light,
.btn-outline-secondary,
.btn-outline-primary,
.btn-filter-toggle,
.btn-export,
.btn-clear-filters {
    background-color: var(--btn-secondary-bg, #ffffff) !important;
    color: var(--btn-secondary-text, #344054) !important;
    border: 1px solid var(--btn-secondary-border, #d0d5dd) !important;
    font-weight: 600 !important;
    border-radius: 999px !important;
    padding: 8px 20px !important;
    display: inline-flex;
    align-items: center !important;
    justify-content: center !important;
    gap: 6px !important;
    transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease !important;
    cursor: pointer !important;
    text-decoration: none !important;
    outline: none !important;
}

.btn-secondary:hover,
.btn-safekivi-secondary:hover,
.nd-btn-secondary:hover,
.btn-light:hover,
.btn-outline-secondary:hover,
.btn-outline-primary:hover,
.btn-filter-toggle:hover,
.btn-export:hover,
.btn-clear-filters:hover {
    background-color: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    border-color: var(--btn-secondary-border, #d0d5dd) !important;
    color: var(--btn-secondary-text, #344054) !important;
}

.btn-secondary:focus,
.btn-safekivi-secondary:focus,
.nd-btn-secondary:focus,
.btn-light:focus,
.btn-outline-secondary:focus,
.btn-outline-primary:focus,
.btn-filter-toggle:focus,
.btn-export:focus,
.btn-clear-filters:focus {
    box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.05) !important;
    outline: 0 !important;
}

.btn-secondary:disabled,
.btn-safekivi-secondary:disabled,
.nd-btn-secondary:disabled,
.btn-light:disabled,
.btn-outline-secondary:disabled,
.btn-outline-primary:disabled,
.btn-filter-toggle:disabled,
.btn-export:disabled,
.btn-clear-filters:disabled {
    background-color: #ffffff !important;
    border-color: #eff2f5 !important;
    color: #d8d8d8 !important;
    cursor: not-allowed !important;
    opacity: 0.65 !important;
}

/* Danger / Destructive Buttons */
.btn-danger,
.btn-safekivi-danger,
.nd-btn-danger,
.btn-outline-danger {
    background-color: var(--btn-danger-bg, #e04f5f) !important;
    color: var(--btn-danger-text, #ffffff) !important;
    border: 1px solid var(--btn-danger-bg, #e04f5f) !important;
    font-weight: 600 !important;
    border-radius: 999px !important;
    padding: 8px 20px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 6px !important;
    transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease !important;
    cursor: pointer !important;
    text-decoration: none !important;
    outline: none !important;
}

.btn-danger:hover,
.btn-safekivi-danger:hover,
.nd-btn-danger:hover,
.btn-outline-danger:hover {
    background-color: var(--btn-danger-hover-bg, #c53c4b) !important;
    border-color: var(--btn-danger-hover-bg, #c53c4b) !important;
    color: var(--btn-danger-text, #ffffff) !important;
}

.btn-danger:focus,
.btn-safekivi-danger:focus,
.nd-btn-danger:focus,
.btn-outline-danger:focus {
    box-shadow: 0 0 0 4px rgba(224, 79, 95, 0.15) !important;
    outline: 0 !important;
}

.btn-danger:disabled,
.btn-safekivi-danger:disabled,
.nd-btn-danger:disabled,
.btn-outline-danger:disabled {
    background-color: #e1e3ea !important;
    border-color: #e1e3ea !important;
    color: #a1a5b7 !important;
    cursor: not-allowed !important;
    opacity: 0.65 !important;
}

/* Modal Footer Button Consistency */
.modal-footer .btn,
.modal-footer button {
    border-radius: 999px !important;
    padding: 8px 24px !important;
    font-weight: 600 !important;
}

/* --- Required Label Indicator --- */
.required-label::after,
.required::after {
    content: " *" !important;
    color: var(--form-required-color, #e04f5f) !important;
    font-weight: bold !important;
    margin-left: 2px !important;
}

/* --- Validation feedback & warnings --- */
.invalid-feedback-safekivi,
.invalid-feedback-solid {
    margin-top: 6px !important;
    /* Was 0.85rem - reduced per direct feedback ("decrease the font size for
       the better view"). This one rule drives every form's inline field
       validation text app-wide, so the change needs no per-template work. */
    font-size: 0.72rem !important;
    color: var(--form-required-color, #e04f5f) !important;
    font-weight: 500 !important;
}

/* The one invalid-field treatment, app-wide (Form Design System). The
   `-solid` modifiers are matched here too, for the same reason the standard
   control rule matches them: runtime-generated markup has repeatedly shipped
   the modifier without its base class. */
.form-control.is-invalid,
.form-select.is-invalid,
.form-control-solid.is-invalid,
.form-select-solid.is-invalid {
    border-color: var(--form-required-color, #e04f5f) !important;
    background-color: #fdf1f2 !important;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23e04f5f'%3e%3ccircle cx='6' cy='6' r='4.5' stroke-width='1'/%3e%3cpath d='M6 3.5v3M6 8.5h.01' stroke-width='1.5' stroke-linecap='round'/%3e%3c/svg%3e") !important;
    background-repeat: no-repeat !important;
    background-position: right calc(0.375em + 0.1875rem) center !important;
    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important;
}

.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
.form-control-solid.is-invalid:focus,
.form-select-solid.is-invalid:focus {
    border-color: var(--form-required-color, #e04f5f) !important;
    box-shadow: 0 0 0 4px rgba(224, 79, 95, 0.15) !important;
}

/* A Select2-enhanced control hides the real <select> and renders a replacement
   container as its NEXT SIBLING, so `.is-invalid` on the select paints nothing
   the user can see - the invalid state would be set correctly and be invisible.
   Carry it across to the replacement. `+` (adjacent sibling) is exactly how
   Select2 inserts the container; the rendered box is what draws the border. */
select.is-invalid+.select2-container .select2-selection {
    border-color: var(--form-required-color, #e04f5f) !important;
    background-color: #fdf1f2 !important;
}

select.is-invalid+.select2-container--focus .select2-selection,
select.is-invalid+.select2-container--open .select2-selection {
    border-color: var(--form-required-color, #e04f5f) !important;
    box-shadow: 0 0 0 4px rgba(224, 79, 95, 0.15) !important;
}

/* --- SafeKivi Card/Panel ---
   Explicitly white for the same reason as .filter-card above: --card-bg is
   Design Config's "Card/panel background" token, and a tenant still holding
   its old grey value had every panel on pages like My Learning (KPI tiles,
   Continue Learning, My Qualifications) render grey. The token still drives
   hover tints elsewhere, where a light grey against white is what's wanted. */
.safekivi-card {
    background-color: #ffffff !important;
    border: 1px solid var(--card-border, #eef1f4) !important;
    border-radius: 10px !important;
    padding: 20px !important;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
    margin-bottom: 20px !important;
}

/* .safekivi-card is shared between genuine form sections (component_catalog.md
   §19's "Section/card inside a form") AND plain content panels that hold no
   form fields at all - KPI tiles, dashboard charts, read-only summary lists.
   The mandatory 26px form-container-radius standard applies only to the
   former, so it is this OPT-IN modifier, not the bare .safekivi-card rule
   above - changing the base rule would round every KPI tile and dashboard
   widget in the app, which is not a form and is explicitly out of scope. Add
   `sk-form-card` alongside `safekivi-card` only on a card that actually wraps
   or sits directly beside real form fields (inputs/selects/textareas/a <form>). */
.safekivi-card.sk-form-card {
    border-radius: var(--form-container-radius, 26px) !important;
}

/* --- SafeKivi Section Header --- */
.safekivi-section-header {
    font-size: 1.1rem !important;
    font-weight: 700 !important;
    color: var(--page-title-color, #1e2129) !important;
    border-bottom: 2px solid var(--card-border, #eef1f4) !important;
    padding-bottom: 8px !important;
    margin-bottom: 16px !important;
}

/* --- SafeKivi Table (.safekivi-table) ---
   Typography for the handful of tables that are legitimately NOT SafeKiviGrid
   - the bounded dashboard summaries and small curated lists listed as
   deliberate exemptions in playbook/standards/data_table_standards.md §7.
   A record list is still a grid; this is not an escape hatch from that.

   Why this exists: `.safekivi-table` was already being asked for by
   training/my_learning.html - the Training module's landing page - and was
   defined NOWHERE, in that file or any other. So the two tables on the first
   Training screen a user sees rendered as raw Bootstrap: 16px sentence-case
   headers and 16px cells, sitting in an application whose every other record
   list is an AG Grid at 11.5px uppercase headers and 13.4px cells. Same
   family, conspicuously different size, weight and case - which is exactly
   what "the font looks different in Training" turns out to mean. Measured, not
   assumed: 16px/700/none against the grid's 0.72rem/700/uppercase/0.04em.

   The values below are the grid's own, read from the .ag-header-cell-text and
   .ag-cell rules further up this file rather than copied from the docs, and
   the colours are the same Design Config --grid-* tokens, so a table and a
   grid re-theme together and read as one product.

   Deliberately drives Bootstrap's --bs-table-* variables instead of setting
   `color`/`background` directly: that is what keeps `.table-danger` (Training
   uses it for overdue rows) and `.table-hover` working, since each sets those
   same variables on its own <tr> and would otherwise be overridden. No
   !important anywhere - site.css loads after bootstrap.min.css in all four
   layouts, so equal specificity resolves in this file's favour. */
.safekivi-table {
    --bs-table-color: var(--grid-pagination-icon-color, #344054);
    --bs-table-border-color: var(--card-border, #eef1f4);
    --bs-table-hover-bg: var(--grid-row-hover-bg, #f7f9fa);
    --bs-table-striped-bg: var(--grid-zebra-bg, #f7f9fa);
    margin-bottom: 0;
}

/* Matches Bootstrap's own `.table > :not(caption) > * > *` shape so padding and
   type land on every cell without reaching a nested table. */
.safekivi-table> :not(caption)>*>* {
    padding: 12px 14px;
    font-size: 0.84rem;
}

.safekivi-table>thead>tr>th {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--grid-header-text-color, #98a2b3);
    background-color: var(--grid-header-bg, #dfe3e5);
    border-bottom: 1px solid var(--card-border, #eef1f4);
    white-space: nowrap;
}

/* A cell must not opt itself out of the row's type. Bootstrap's `.small`
   (0.875rem) had been put on one cell of Training's Assigned-To-Me row, which
   made the Category column a different size from the Course column beside it -
   a mismatch inside a single row. Neutralised here so the class is harmless
   wherever it is inherited from older markup. */
.safekivi-table>tbody>tr>td.small {
    font-size: 0.84rem;
}

/* --- SafeKivi Card Header (.sk-card-header) ---
   An audit of card/section headers across the app found 11 structurally
   distinct patterns in production (dashboards, config pages, wizard review
   steps), most of them copy-pasted per page rather than shared. This is the
   one reusable component for the "here's what you're about to confirm"
   summary-card header: an icon in a circle, a title + muted subtitle stack,
   and an optional right-aligned action button. It is NOT the same thing as
   .sk-page-header (page-shell.css) — that is a full-width brand-primary banner for a
   whole PAGE; this is a header row for one CARD.
   Sits inside a padded container's existing padding (e.g. .safekivi-card's
   20px) rather than bleeding to the card's edges via negative margins, so it
   drops into any already-padded card without also needing a padding override.
   Markup contract (see playbook/design_system/component_catalog.md):
     <div class="safekivi-card">
       <div class="sk-card-header">
         <div class="sk-card-header-icon is-complete"><i class="bi bi-check-lg"></i></div>
         <div class="sk-card-header-text">
           <h6 class="sk-card-header-title">Patient Identification</h6>
           <p class="sk-card-header-subtitle">Identify the patient involved in the incident</p>
         </div>
         <div class="sk-card-header-action">
           <button type="button" class="btn-outline-filter btn-sm"><i class="bi bi-pencil"></i> Edit</button>
         </div>
       </div>
       <div class="sk-card-body">...</div>
     </div>
   `.sk-card-header-icon`/`-action` are optional — omit either when a step has
   no status icon or nothing to edit. */
.sk-card-header {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding-bottom: 16px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--card-border, #eef1f4);
}

.sk-card-header-icon {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(27, 131, 139, 0.1);
    color: var(--btn-primary-bg, #1b838b);
    font-size: 1rem;
}

/* Completed-step variant — the reference this component was built from uses a
   soft green check circle for a wizard step that's already been filled in. */
.sk-card-header-icon.is-complete {
    background: rgba(18, 183, 106, 0.12);
    color: #12b76a;
}

.sk-card-header-text {
    flex: 1 1 auto;
    min-width: 0;
}

.sk-card-header-title {
    font-size: 1rem;
    font-weight: 700;
    color: var(--page-title-color, #1e2129);
    margin: 0;
}

.sk-card-header-subtitle {
    font-size: 0.8125rem;
    color: #667085;
    margin: 2px 0 0;
}

.sk-card-header-action {
    flex: 0 0 auto;
}

@media (max-width: 576px) {
    .sk-card-header {
        flex-wrap: wrap;
    }

    /* Once the action button wraps to its own line, align it under the
       title rather than back under the icon. */
    .sk-card-header-action {
        margin-left: 44px;
    }
}

/* ── Form header (`.sk-form-header`) ──────────────────────────────────────────
   The opening block of a substantial form: what this form is, one line on why
   you would fill it in, and optional decorative artwork.

   Distinct from its two neighbours, which it must not be confused with:
     .sk-page-header  a brand-primary full-width gradient banner for a whole PAGE;
     .sk-card-header  an icon + title + action row inside ONE card.
   This is the top of a FORM — lighter than the page banner, and carrying a
   subtitle and art that the card header has no room for. */
.sk-form-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 14px;
    margin-bottom: 18px;
    border-bottom: 1px solid var(--card-border, #eef1f4);
}

.sk-form-header-text {
    min-width: 0;
}

.sk-form-header-title {
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--text-strong, #101828);
    margin: 0;
}

.sk-form-header-subtitle {
    font-size: 0.8125rem;
    color: var(--text-muted, #667085);
    margin: 3px 0 0;
}

/* Decorative only — never the sole carrier of meaning, and hidden from
   assistive tech by the author (aria-hidden) rather than by this rule. It is
   the first thing to go when space is tight. */
.sk-form-header-art {
    flex: 0 0 auto;
    width: 84px;
    max-width: 26%;
    opacity: 0.9;
}

.sk-form-header-art img,
.sk-form-header-art svg {
    width: 100%;
    height: auto;
    display: block;
}

@media (max-width: 576px) {
    .sk-form-header-art {
        display: none;
    }
}

/* ── Icon field (`.sk-input-icon`) ────────────────────────────────────────────
   A form control with a leading icon inside it. The icon is a recognition aid
   for a form long enough that field labels alone are slow to scan — it never
   replaces the label, which stays above the control as normal.

   Wraps exactly one `.form-control` / `.form-select`; the padding is applied to
   the control rather than the wrapper so focus rings and validation states
   still land on the input itself. */
.sk-input-icon {
    position: relative;
    display: block;
}

/* Descendant, not child: voice-dictation.js wraps every text input and textarea
   in a `.voice-input-wrapper` at runtime, so a `>` selector silently stops
   matching the moment that script touches the field. */
.sk-input-icon i {
    position: absolute;
    top: 50%;
    left: 12px;
    transform: translateY(-50%);
    font-size: 0.9rem;
    line-height: 1;
    color: var(--grid-header-text-color, #98a2b3);
    pointer-events: none;
    z-index: 2;
}

/* `!important` because the app-wide control rule near the top of this file sets
   the `padding` *shorthand* with `!important` — no amount of specificity beats
   that, so matching it is the only way to reserve room for the icon. */
.sk-input-icon .form-control,
.sk-input-icon .form-select {
    padding-left: 36px !important;
}

/* Select2 replaces the <select> with its own markup, and that replacement
   carries NEITHER `.form-control` nor `.form-select` - so the rule above never
   reached it and the leading icon sat directly on top of the placeholder /
   first chip. Every `.sk-input-icon` wrapping a `<select multiple>` hit this,
   because shared.js's `$.fn.multiselect` shim routes those through Select2.
   The padding goes on `.select2-selection` (the visible box, the element that
   actually renders the border), matching the 36px reserved above. */
.sk-input-icon .select2-container .select2-selection {
    padding-left: 36px !important;
}

/* The multi-select variant lays its chips out in a `<ul>` inside that box; the
   theme gives that list its own horizontal padding, which would add to the
   36px above and push the chips out of alignment with every other field on the
   form. Zero it and let the box own the inset. */
.sk-input-icon .select2-container .select2-selection--multiple .select2-selection__rendered {
    padding-left: 0 !important;
}

/* select2-bootstrap-5-theme hardcodes the inline search row at `height:1.5rem`
   (24px against the ROOT font size) rather than an `em` value against the
   control's own font - so it never shrank when the app-wide control rule
   above dropped every field to 0.9rem. The empty multi-select was the one
   `.sk-input-icon` field that grew past the standard 35.6px control height
   (Course, Assign To, Priority, ... - every other field on the New Assignment
   form) because that oversized row, not the shared padding, was the tallest
   thing inside it. 16px is the field's own font metrics (Inter at 0.9rem
   measures a 16px ascent+descent box, matching the placeholder text every
   `--single` variant already renders at) - tall enough that the placeholder
   and typed characters aren't clipped, short enough that the box still
   settles on the shared `min-height` floor instead of growing past it. Chips
   still wrap the box onto extra lines once picked - only the empty/placeholder
   row shrinks. */
.sk-input-icon .select2-container .select2-selection--multiple .select2-search,
.sk-input-icon .select2-container .select2-selection--multiple .select2-search__field {
    height: 16px !important;
}

/* The wrapper that voice-dictation.js injects must not shrink-wrap the field. */
.sk-input-icon .voice-input-wrapper {
    display: block;
    width: 100%;
}

/* The icon takes the accent colour while the field has focus, so the eye is
   drawn to the row being edited on a form of this length. */
.sk-input-icon:focus-within i {
    color: var(--form-input-focus, #1b838b);
}

/* A cleared-value affordance on the right of an icon field (dates, mainly,
   where the browser's own control cannot be emptied from the keyboard). */
.sk-input-clear {
    position: absolute;
    top: 50%;
    /* Left of the browser's own calendar button on a date input, and left of
       voice-dictation's microphone on a text one — both sit at the right edge. */
    right: 34px;
    transform: translateY(-50%);
    border: 0;
    background: transparent;
    padding: 2px 4px;
    line-height: 1;
    cursor: pointer;
    color: var(--grid-header-text-color, #98a2b3);
    z-index: 2;
}

.sk-input-clear:hover {
    color: var(--audit-nonconform, #842029);
}

/* ── Mandatory-fields note (`.sk-required-note`) ──────────────────────────────
   The one-line reminder that sits with a long form's actions. Paired with
   `required-label`'s asterisk, which is what it refers to. */
.sk-required-note {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border-radius: 8px;
    font-size: 0.78rem;
    color: var(--text-muted, #667085);
    background: var(--card-header-bg, #f8fafc);
}

.sk-required-note i {
    color: var(--btn-primary-bg, #1b838b);
}

/* --- User-Selectable Navigation Layouts --- */

/* Left Navigation Mode (default) */
body.layout-left-nav #sidebar {
    display: flex;
}

body.layout-left-nav .sidebar-collapse-btn {
    display: inline-flex;
}

body.layout-left-nav #app-main {
    /* Must track the base #app-main rule above - this selector outranks it, so
       leaving the old value here would silently undo the seam. */
    margin-left: calc(var(--sidebar-width) + var(--shell-gap) + var(--content-gap));
}

body.layout-left-nav #headerMenu {
    display: none !important;
}

/* Top Navigation Mode */
body.layout-top-nav #sidebar {
    display: none !important;
}

body.layout-top-nav .sidebar-collapse-btn {
    display: none !important;
}

body.layout-top-nav #app-main {
    margin-left: 0 !important;
    /* No sidebar in this mode, so the content column supplies its own left
       inset (the base rule leaves it 0 because the sidebar gap covers it). */
    padding-left: var(--shell-gap) !important;
}

/* #app-header is full-width/fixed and its inner card owns the padding, so
   this mode needs no extra left inset (there's no sidebar to clear). */
body.layout-top-nav #app-header {
    padding-left: 0 !important;
}

body.layout-top-nav #headerMenu {
    display: flex !important;
}

/* Horizontal Top Navigation Styling */
.header-menu {
    display: none;
    align-items: center;
    gap: 0.5rem;
    list-style: none;
    margin: 0 0 0 2rem;
    padding: 0;
}

.header-menu>li {
    position: relative;
}

.header-menu a,
.header-menu-toggle {
    color: var(--nav-side-text, #9899ac);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.95rem;
    padding: 0.5rem 0.85rem;
    border-radius: 6px;
    display: flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    cursor: pointer;
    transition: all 0.15s ease;
}

.header-menu a:hover,
.header-menu-toggle:hover {
    background: var(--nav-hover-bg, #25253a);
    color: var(--nav-active-text, #ffffff);
}

.header-menu a.active,
.header-menu-toggle.active {
    background: var(--nav-active-bg, #2b2b40);
    color: var(--nav-active-text, #ffffff);
}

/* Submenu dropdowns */
.header-submenu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    background: var(--nav-side-bg, #ffffff);
    border: 1px solid var(--nav-hover-bg, #eef1f4);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    list-style: none;
    margin: 4px 0 0 0;
    padding: 6px 0;
    min-width: 220px;
    z-index: 1050;
}

.header-menu>li:hover>.header-submenu,
.header-menu>li:focus-within>.header-submenu {
    display: block;
}

.header-submenu li {
    position: relative;
}

.header-submenu a {
    padding: 8px 16px;
    font-weight: 500;
    font-size: 0.9rem;
    border-radius: 0;
    color: var(--nav-side-text, #9899ac);
}

.header-submenu a::before {
    content: "";
    position: absolute;
    left: 22px;
    top: 50%;
    transform: translateY(-50%);
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.6;
    display: none;
}

.header-submenu a:hover {
    background: var(--nav-hover-bg, #25253a);
    color: var(--nav-active-text, #ffffff);
}

.header-submenu a.active {
    background: var(--nav-active-bg, #2b2b40);
    color: var(--nav-active-text, #ffffff);
}

/* For nested submenus */
.header-submenu .header-submenu {
    top: 0;
    left: 100%;
    margin-top: -6px;
}

.header-submenu li:hover>.header-submenu,
.header-submenu li:focus-within>.header-submenu {
    display: block;
}

.caret-icon {
    font-size: 0.7rem;
    opacity: 0.7;
}

/* --- Centering & Light Mode Top Navigation overrides --- */
body.layout-top-nav .header-nav {
    display: block !important;
    flex-grow: 1;
}

body.layout-top-nav #headerMenu {
    display: flex !important;
    justify-content: center;
}

body.layout-top-nav .header-spacer {
    display: none !important;
}

/* Accessibility focus indicators */
.header-menu a:focus-visible,
.header-menu-toggle:focus-visible {
    outline: 2px solid var(--btn-primary-bg, #1b838b) !important;
    outline-offset: 2px !important;
    box-shadow: 0 0 0 4px rgba(27, 131, 139, 0.25) !important;
}

body.layout-top-nav .header-menu a,
body.layout-top-nav .header-menu-toggle {
    color: var(--form-label-color, #344054);
    background: transparent;
    transition: all 0.2s ease;
    font-weight: 500;
}

body.layout-top-nav .header-menu a:hover,
body.layout-top-nav .header-menu-toggle:hover {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--header-text, #1e2129) !important;
}

body.layout-top-nav .header-menu a.active,
body.layout-top-nav .header-menu-toggle.active {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--header-text, #1e2129) !important;
    font-weight: 600;
}

/* Light dropdown styling for standard dropdowns in top nav */
body.layout-top-nav .header-submenu {
    background: #ffffff !important;
    border: 1px solid rgba(0, 0, 0, 0.08) !important;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08) !important;
}

body.layout-top-nav .header-submenu a {
    color: var(--form-label-color, #344054) !important;
    background: transparent !important;
    border-radius: 4px !important;
    margin: 0 6px !important;
    padding: 8px 12px !important;
}

body.layout-top-nav .header-submenu a:hover {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--btn-primary-bg, #1b838b) !important;
}

body.layout-top-nav .header-submenu a.active {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--btn-primary-bg, #1b838b) !important;
    font-weight: 600 !important;
}

/* --- Megamenu layout & styling --- */
.megamenu-parent {
    position: relative;
}

.megamenu-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    background: #ffffff;
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 12px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
    margin-top: 12px;
    padding: 1.5rem;
    min-width: 620px;
    z-index: 1060;
}

.megamenu-arrow {
    position: absolute;
    top: -6px;
    left: 50%;
    transform: translateX(-50%) rotate(45deg);
    width: 12px;
    height: 12px;
    background: #ffffff;
    border-left: 1px solid rgba(0, 0, 0, 0.08);
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.megamenu-parent:hover .megamenu-dropdown,
.megamenu-parent:focus-within .megamenu-dropdown {
    display: block;
}

.megamenu-columns {
    display: flex;
    gap: 2rem;
    justify-content: flex-start;
    align-items: flex-start;
}

.megamenu-column {
    flex: 1;
    min-width: 130px;
}

.megamenu-column-header {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--header-text, #1e2129);
    margin-bottom: 0.75rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}

.megamenu-column-header i {
    font-size: 1rem;
    color: var(--icon-color, #78829d);
}

.megamenu-column-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.megamenu-column-list li {
    display: block;
}

.megamenu-column-list a {
    display: block;
    padding: 6px 10px !important;
    font-size: 0.85rem !important;
    font-weight: 500 !important;
    color: var(--nav-side-text, #5e6278) !important;
    text-decoration: none;
    border-radius: 6px !important;
    background: transparent !important;
    transition: all 0.15s ease;
    white-space: nowrap;
}

.megamenu-column-list a:hover {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--btn-primary-bg, #1b838b) !important;
    padding-left: 14px !important;
}

.megamenu-column-list a.active {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--btn-primary-bg, #1b838b) !important;
    font-weight: 600 !important;
}

/* Force-hide dropdown on click */
.header-menu .force-hide {
    display: none !important;
}

/* Ensure modal overlays block parent body scrolling and modal dialog content scrolls internally */
html.modal-open,
body.modal-open {
    overflow: hidden !important;
    height: 100% !important;
}

.modal-backdrop {
    z-index: 1050 !important;
}

.modal {
    z-index: 1060 !important;
    overflow-y: hidden !important;
    overscroll-behavior: contain !important;
}

.modal-dialog {
    display: flex !important;
    margin: 1.75rem auto !important;
    max-height: calc(100% - 3.5rem) !important;
    overscroll-behavior: contain !important;
}

@media (max-width: 575.98px) {
    .modal-dialog {
        margin: 0.5rem auto !important;
        max-height: calc(100% - 1rem) !important;
    }
}

.modal-content {
    max-height: 100% !important;
    display: flex !important;
    flex-direction: column !important;
    overflow: hidden !important;
}

.modal-body {
    overflow-y: auto !important;
    flex: 1 1 auto !important;
    overscroll-behavior: contain !important;
}

/* --- Global responsive layout overrides --- */

/* Horizontal scrolling for top nav tabs on mobile */
@media (max-width: 768px) {
    body.layout-top-nav #headerMenu {
        justify-content: flex-start !important;
        overflow-x: auto !important;
        white-space: nowrap !important;
        flex-wrap: nowrap !important;
        -webkit-overflow-scrolling: touch;
        padding: 0 16px 8px 16px !important;
    }

    body.layout-top-nav .header-menu-item {
        display: inline-block !important;
        flex: 0 0 auto !important;
    }
}

/* Responsive Megamenu on mobile */
@media (max-width: 768px) {
    .megamenu-dropdown {
        min-width: 100% !important;
        width: calc(100vw - 32px) !important;
        left: 0 !important;
        transform: none !important;
        position: fixed !important;
        top: 60px !important;
        margin: 0 16px !important;
        box-sizing: border-box !important;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1) !important;
    }

    .megamenu-arrow {
        display: none !important;
        /* Hide floating arrow on mobile */
    }

    .megamenu-columns {
        flex-direction: column !important;
        gap: 1.25rem !important;
    }

    .megamenu-column {
        width: 100% !important;
    }
}

/* Ensure data tables/panels never leak width on small screen resolutions */
.users-ag-grid-panel,
.ag-theme-quartz,
.table-responsive {
    max-width: 100% !important;
    overflow-x: auto !important;
}

/* ============================================================
   SafeKiviDialog — Centralized Modal Dialog System
   Replaces native browser alert(), confirm(), prompt() calls.
   Uses CSS custom properties from the SafeKivi design token set.
   ============================================================ */

/* ── Keyframes ───────────────────────────────────────────── */
@keyframes ndOverlayIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

@keyframes ndModalIn {
    from {
        opacity: 0;
        transform: translate(-50%, calc(-50% - 18px)) scale(0.97);
    }

    to {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1);
    }
}

@keyframes ndSpin {
    to {
        stroke-dashoffset: -280;
    }
}

@keyframes ndToastIn {
    from {
        opacity: 0;
        transform: translateX(120%);
    }

    to {
        opacity: 1;
        transform: translateX(0);
    }
}

@keyframes ndToastOut {
    from {
        opacity: 1;
        transform: translateX(0);
    }

    to {
        opacity: 0;
        transform: translateX(120%);
    }
}

/* ── Overlay backdrop ────────────────────────────────────── */
.nd-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(10, 14, 26, 0.55);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    z-index: 10500;
}

.nd-overlay.nd-visible {
    display: block;
    animation: ndOverlayIn 0.18s ease forwards;
}

/* ── Modal shell ─────────────────────────────────────────── */
.nd-modal {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.97);
    z-index: 10501;
    background: #ffffff;
    border-radius: var(--form-container-radius, 26px);
    box-shadow: 0 24px 64px rgba(10, 14, 26, 0.22), 0 4px 16px rgba(10, 14, 26, 0.10);
    width: 90%;
    max-height: 90vh;
    overflow-y: auto;
    outline: none;
}

.nd-modal.nd-visible {
    display: block;
    animation: ndModalIn 0.22s cubic-bezier(0.34, 1.12, 0.64, 1) forwards;
}

/* Size variants */
.nd-modal--sm {
    max-width: 420px;
}

.nd-modal--md {
    max-width: 540px;
}

.nd-modal--lg {
    max-width: 720px;
}

.nd-modal--xl {
    max-width: 900px;
}

.nd-modal--full {
    max-width: 98vw;
    width: 98vw;
}

/* ── Content layout ──────────────────────────────────────── */
.nd-content {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.nd-content--loading {
    align-items: center;
    padding: 2.25rem 1.5rem 2rem;
    text-align: center;
    gap: 1rem;
}

/* ── Header ──────────────────────────────────────────────── */
.nd-header {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 1.5rem 1.5rem 0.75rem;
}

/* ── Title ───────────────────────────────────────────────── */
.nd-title {
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--header-text, #1e2129);
    margin: 0;
    line-height: 1.3;
}

.nd-title--center {
    text-align: center;
}

/* ── Body ────────────────────────────────────────────────── */
.nd-body {
    padding: 0 1.5rem 1.25rem;
}

.nd-message {
    font-size: 0.9375rem;
    color: #475467;
    line-height: 1.6;
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ── Footer / button row ─────────────────────────────────── */
.nd-footer {
    padding: 0.75rem 1.5rem 1.5rem;
    display: flex;
    justify-content: flex-end;
}

.nd-footer--row {
    gap: 0.625rem;
}

/* ── Buttons ─────────────────────────────────────────────── */
/* ── Universal Dialog & Modal Buttons ───────────────────── */
.nd-modal .nd-btn,
.nd-modal .btn,
.modal-dialog .btn,
.modal-content .btn,
.modal-footer .btn,
.nd-btn {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    padding: 0.5rem 1.25rem !important;
    border-radius: 999px !important;
    font-size: 0.875rem !important;
    font-weight: 600 !important;
    border: 1px solid transparent !important;
    cursor: pointer !important;
    transition: background 0.15s, box-shadow 0.15s, filter 0.12s !important;
    min-width: 88px !important;
    outline: none !important;
    white-space: nowrap !important;
}

/* ...but `.d-none` still has to win.
   Bootstrap's utility is `.d-none { display: none !important }` — specificity
   (0,1,0). The block above is (0,2,0) and also `!important`, and between two
   `!important` declarations the more specific selector wins. So every attempt
   to hide a button inside a modal did nothing at all: markup that shipped with
   `class="btn ... d-none"` rendered visible, and `toggleClass('d-none')` in JS
   was a no-op. Found via a closed corrective action still offering Reassign,
   Close Action and Request Extension while the non-button controls beside them
   hid correctly.

   The list below is (0,3,0), so it outranks the block above. Keep the two
   selector lists identical: the moment a modal selector is added up there and
   not down here, the bug comes back silently. */
.nd-modal .nd-btn.d-none,
.nd-modal .btn.d-none,
.modal-dialog .btn.d-none,
.modal-content .btn.d-none,
.modal-footer .btn.d-none,
.nd-btn.d-none {
    display: none !important;
}

.nd-btn:focus-visible {
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.35) !important;
}

/* Primary Dialog Buttons */
.nd-modal .nd-btn-primary,
.modal-dialog .btn-primary,
.modal-content .btn-primary,
.modal-footer .btn-primary,
.nd-btn-primary {
    background: var(--btn-primary-bg, #1b838b) !important;
    color: var(--btn-primary-text, #ffffff) !important;
    border-color: var(--btn-primary-bg, #1b838b) !important;
}

.nd-modal .nd-btn-primary:hover,
.modal-dialog .btn-primary:hover,
.modal-content .btn-primary:hover,
.modal-footer .btn-primary:hover,
.nd-btn-primary:hover {
    background: var(--btn-primary-hover-bg, #146267) !important;
    border-color: var(--btn-primary-hover-bg, #146267) !important;
}

/* Secondary Dialog Buttons */
.nd-modal .nd-btn-secondary,
.modal-dialog .btn-secondary,
.modal-dialog .btn-outline-secondary,
.modal-content .btn-secondary,
.modal-content .btn-outline-secondary,
.modal-footer .btn-secondary,
.modal-footer .btn-outline-secondary,
.nd-btn-secondary {
    background: var(--btn-secondary-bg, #ffffff) !important;
    color: var(--btn-secondary-text, #344054) !important;
    border-color: var(--btn-secondary-border, #d0d5dd) !important;
}

.nd-modal .nd-btn-secondary:hover,
.modal-dialog .btn-secondary:hover,
.modal-dialog .btn-outline-secondary:hover,
.modal-content .btn-secondary:hover,
.modal-content .btn-outline-secondary:hover,
.modal-footer .btn-secondary:hover,
.modal-footer .btn-outline-secondary:hover,
.nd-btn-secondary:hover {
    background: var(--btn-secondary-hover-bg, #f2f4f7) !important;
    color: var(--btn-secondary-text, #344054) !important;
    border-color: var(--btn-secondary-border, #d0d5dd) !important;
}

/* Danger / Destructive Dialog Buttons */
.nd-modal .nd-btn-danger,
.modal-dialog .btn-danger,
.modal-content .btn-danger,
.modal-footer .btn-danger,
.nd-btn-danger {
    background: var(--btn-danger-bg, #e04f5f) !important;
    color: var(--btn-danger-text, #ffffff) !important;
    border-color: var(--btn-danger-bg, #e04f5f) !important;
}

.nd-modal .nd-btn-danger:hover,
.modal-dialog .btn-danger:hover,
.modal-content .btn-danger:hover,
.modal-footer .btn-danger:hover,
.nd-btn-danger:hover {
    background: var(--btn-danger-hover-bg, #c53c4b) !important;
    border-color: var(--btn-danger-hover-bg, #c53c4b) !important;
}

.nd-btn-danger:disabled {
    opacity: 0.45 !important;
    cursor: not-allowed !important;
}

/* Warning & Action Dialog Buttons */
.nd-modal .nd-btn-warning,
.modal-dialog .btn-warning,
.modal-content .btn-warning,
.modal-footer .btn-warning,
.nd-btn-warning {
    background: var(--btn-primary-bg, #1b838b) !important;
    color: var(--btn-primary-text, #ffffff) !important;
    border-color: var(--btn-primary-bg, #1b838b) !important;
}

.nd-modal .nd-btn-warning:hover,
.modal-dialog .btn-warning:hover,
.modal-content .btn-warning:hover,
.modal-footer .btn-warning:hover,
.nd-btn-warning:hover {
    background: var(--btn-primary-hover-bg, #146267) !important;
    border-color: var(--btn-primary-hover-bg, #146267) !important;
}

.nd-btn-ok {
    min-width: 100px;
}

/* ── Icon circles ────────────────────────────────────────── */
.nd-icon {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: 50%;
}

.nd-icon--success {
    color: #16a34a;
}

.nd-icon--error {
    color: var(--btn-danger-bg, #e04f5f);
}

.nd-icon--warning {
    color: #d97706;
}

.nd-icon--info {
    color: var(--btn-primary-bg, #1b838b);
}

/* ── Input fields ────────────────────────────────────────── */
.nd-label {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--form-label-color, #344054);
    margin-bottom: 0.375rem;
}

.nd-input-field {
    display: block;
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-size: 0.9375rem;
    border: 1px solid #d0d5dd;
    border-radius: 7px;
    color: var(--header-text, #1e2129);
    background: #ffffff;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.nd-input-field:focus {
    border-color: var(--form-focus-color, #1b838b);
    /* Ring geometry unified with .form-control:focus's canonical
       0 0 0 4px rgba(27, 131, 139, 0.15) (v1.22.1+20260811.04, changelog.md).
       border-radius/background stay on .nd-input-field's own base rule above. */
    box-shadow: 0 0 0 4px rgba(27, 131, 139, 0.15);
    outline: none;
}

.nd-textarea {
    resize: vertical;
    min-height: 100px;
}

.nd-input-error {
    font-size: 0.8125rem;
    color: var(--btn-danger-bg, #e04f5f);
    margin-top: 0.375rem;
}

/* ── Delete checkbox ─────────────────────────────────────── */
.nd-checkbox-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    color: #475467;
    margin-top: 0.875rem;
    cursor: pointer;
}

.nd-checkbox {
    width: 1rem;
    height: 1rem;
    accent-color: var(--btn-danger-bg, #e04f5f);
    cursor: pointer;
    flex-shrink: 0;
}

/* ── Loading spinner ─────────────────────────────────────── */
.nd-spinner {
    width: 52px;
    height: 52px;
}

.nd-spinner-svg {
    width: 52px;
    height: 52px;
    animation: ndSpin 0s linear infinite;
}

.nd-spinner-track {
    stroke: #e5e7eb;
}

.nd-spinner-fill {
    stroke: var(--btn-primary-bg, #1b838b);
    stroke-dasharray: 125;
    stroke-dashoffset: 0;
    stroke-linecap: round;
    transform-origin: center;
    animation: ndSpin 1.1s linear infinite;
}

.nd-loading-msg {
    font-size: 0.9375rem;
    color: #475467;
    margin: 0;
}

/* ── Progress bar ────────────────────────────────────────── */
.nd-progress-track {
    width: 100%;
    height: 8px;
    background: #e5e7eb;
    border-radius: 999px;
    overflow: hidden;
    margin-top: 0.75rem;
}

.nd-progress-bar {
    height: 100%;
    background: var(--btn-primary-bg, #1b838b);
    border-radius: 999px;
    transition: width 0.3s ease;
}

.nd-progress-pct {
    font-size: 0.8125rem;
    color: #98a2b3;
    margin: 0.375rem 0 0;
    text-align: right;
}

/* ── Toast notifications (Modernized 2026 Style) ─────────── */
.nd-toast-container {
    position: fixed;
    top: 1.5rem;
    right: 1.5rem;
    z-index: 99999;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    pointer-events: none;
    max-width: 380px;
    width: calc(100vw - 3rem);
}

.nd-toast {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 0.875rem 1.125rem;
    border-radius: 12px;
    pointer-events: all;
    opacity: 0;
    transform: translateY(-12px) scale(0.95);
    transition: opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1),
        transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
    word-break: break-word;
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
}

.nd-toast.nd-toast--visible {
    opacity: 1;
    transform: translateY(0) scale(1);
}

/* Success Toast */
.nd-toast--success {
    background: rgba(240, 253, 244, 0.94);
    border: 1px solid rgba(187, 247, 208, 0.8);
    color: #14532d;
    box-shadow: 0 10px 30px -5px rgba(22, 163, 74, 0.08),
        0 4px 12px -2px rgba(22, 163, 74, 0.04);
}

.nd-toast--success .nd-icon {
    color: #16a34a;
}

/* Error Toast */
.nd-toast--error {
    background: rgba(254, 242, 242, 0.94);
    border: 1px solid rgba(254, 226, 226, 0.8);
    color: #7f1d1d;
    box-shadow: 0 10px 30px -5px rgba(220, 38, 38, 0.08),
        0 4px 12px -2px rgba(220, 38, 38, 0.04);
}

.nd-toast--error .nd-icon {
    color: #dc2626;
}

/* Warning Toast */
.nd-toast--warning {
    background: rgba(255, 251, 235, 0.94);
    border: 1px solid rgba(254, 243, 199, 0.8);
    color: #78350f;
    box-shadow: 0 10px 30px -5px rgba(217, 119, 6, 0.08),
        0 4px 12px -2px rgba(217, 119, 6, 0.04);
}

.nd-toast--warning .nd-icon {
    color: #d97706;
}

/* Info Toast */
.nd-toast--info {
    background: rgba(240, 253, 250, 0.94);
    border: 1px solid rgba(204, 251, 241, 0.8);
    color: #134e4a;
    box-shadow: 0 10px 30px -5px rgba(13, 148, 136, 0.08),
        0 4px 12px -2px rgba(13, 148, 136, 0.04);
}

.nd-toast--info .nd-icon {
    color: #0d9488;
}

.nd-toast-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
}

.nd-toast-icon .nd-icon {
    width: 22px;
    height: 22px;
}

.nd-toast-msg {
    flex: 1;
    font-size: 0.875rem;
    font-weight: 550;
    line-height: 1.4;
}

.nd-toast-close {
    background: none;
    border: none;
    color: currentColor;
    opacity: 0.45;
    font-size: 1.15rem;
    cursor: pointer;
    padding: 0;
    line-height: 1;
    flex-shrink: 0;
    transition: opacity 0.15s ease, background-color 0.15s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 4px;
}

.nd-toast-close:hover {
    opacity: 0.9;
    background: rgba(0, 0, 0, 0.05);
}

/* Dark Mode Overrides */
@media (prefers-color-scheme: dark) {
    .nd-toast--success {
        background: rgba(20, 83, 45, 0.96);
        border: 1px solid rgba(34, 197, 94, 0.25);
        color: #dcfce7;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
    }

    .nd-toast--success .nd-icon {
        color: #4ade80;
    }

    .nd-toast--success .nd-toast-close:hover {
        background: rgba(255, 255, 255, 0.1);
    }

    .nd-toast--error {
        background: rgba(127, 29, 29, 0.96);
        border: 1px solid rgba(239, 68, 68, 0.25);
        color: #fee2e2;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
    }

    .nd-toast--error .nd-icon {
        color: #f87171;
    }

    .nd-toast--error .nd-toast-close:hover {
        background: rgba(255, 255, 255, 0.1);
    }

    .nd-toast--warning {
        background: rgba(120, 53, 4, 0.96);
        border: 1px solid rgba(249, 115, 22, 0.25);
        color: #fef3c7;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
    }

    .nd-toast--warning .nd-icon {
        color: #fbbf24;
    }

    .nd-toast--warning .nd-toast-close:hover {
        background: rgba(255, 255, 255, 0.1);
    }

    .nd-toast--info {
        background: rgba(19, 78, 74, 0.96);
        border: 1px solid rgba(20, 184, 166, 0.25);
        color: #ccfbf1;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
    }

    .nd-toast--info .nd-icon {
        color: #2dd4bf;
    }

    .nd-toast--info .nd-toast-close:hover {
        background: rgba(255, 255, 255, 0.1);
    }
}


/* ── Dark mode overrides ─────────────────────────────────── */
@media (prefers-color-scheme: dark) {
    .nd-modal {
        background: #1e2129;
        box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55), 0 4px 16px rgba(0, 0, 0, 0.3);
    }

    .nd-title {
        color: #f9fafb;
    }

    .nd-message {
        color: #9ca3af;
    }

    .nd-btn-secondary {
        background: #2d3748;
        color: #e5e7eb;
        border-color: #4b5563;
    }

    .nd-btn-secondary:hover {
        background: #374151;
    }

    .nd-input-field {
        background: #2d3748;
        border-color: #4b5563;
        color: #f9fafb;
    }

    .nd-spinner-track {
        stroke: #374151;
    }

    .nd-progress-track {
        background: #374151;
    }
}

/* ── Mobile ──────────────────────────────────────────────── */
@media (max-width: 480px) {
    .nd-modal {
        border-radius: 10px 10px 0 0;
        top: auto;
        bottom: 0;
        left: 0;
        right: 0;
        transform: none;
        width: 100%;
        max-width: 100%;
    }

    .nd-modal.nd-visible {
        animation: none;
    }

    .nd-footer--row {
        flex-direction: column-reverse;
    }

    .nd-btn {
        width: 100%;
    }

    .nd-toast-container {
        top: 0.75rem;
        bottom: auto;
        right: 0.75rem;
        left: 0.75rem;
        max-width: none;
    }
}

#quickLogoutBtn {
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

#quickLogoutBtn:hover {
    color: #ef4444 !important;
    transform: scale(1.1);
}

/* ── Voice Dictation Component ───────────────────────────── */
.voice-input-wrapper {
    position: relative;
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    width: 100%;
    max-width: 100%;
}

.input-group>.voice-input-wrapper {
    flex: 1 1 auto;
    width: 1%;
    min-width: 0;
}

.voice-input-wrapper>textarea.form-control,
.voice-input-wrapper>input.form-control,
.voice-input-wrapper>input,
.voice-input-wrapper>textarea,
.voice-input-wrapper>.form-control-solid {
    width: 100% !important;
    padding-right: 2.5rem !important;
    box-sizing: border-box !important;
}

.voice-dictation-btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    right: 8px;
    z-index: 5;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--text-muted, #64748b);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    cursor: pointer;
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    outline: none;
}

.voice-input-wrapper>textarea~.voice-dictation-btn,
.voice-input-wrapper>textarea.form-control~.voice-dictation-btn {
    top: 8px;
    transform: none;
}

/* Select2's inline search box (the one that sits among the pills of a
   multi-select) is an input[type="search"], so voice-dictation.js wraps it and
   drops a 28px mic button in it - absolutely positioned, which lands the mic on
   top of the last selected pill. There is nothing to dictate into a field that
   only filters an option list a few characters at a time, and the dropdown's
   own search box (single-selects) keeps its mic, so this one is hidden rather
   than given room it doesn't have. */
.select2-selection--multiple .select2-search--inline .voice-dictation-btn {
    display: none !important;
}

/* Same wrapper, same reason as .sk-input-icon above: it must not shrink-wrap
   the search field Select2 sizes itself. `!important` is required, not
   defensive: _initUI() copies the field's own inline width onto the wrapper,
   and Select2's inline search box carries `width: 0px` at the moment it is
   wrapped - a stylesheet `width: 100%` loses to that inline 0, collapsing the
   wrapper and clamping the field to its 80px min-width, which cut the
   placeholder off mid-word. */
.select2-search .voice-input-wrapper {
    display: block;
    width: 100% !important;
    min-width: 0;
}

.voice-dictation-btn:hover {
    background: rgba(27, 131, 139, 0.1);
    color: var(--form-focus-color, #1b838b);
}

.voice-dictation-btn:focus-visible {
    box-shadow: 0 0 0 3px rgba(27, 131, 139, 0.25);
    color: var(--form-focus-color, #1b838b);
}

.voice-dictation-btn.is-disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.voice-dictation-btn.is-recording {
    background: #ef4444;
    color: #ffffff !important;
    animation: voiceRecordingPulse 1.5s infinite;
}

@keyframes voiceRecordingPulse {
    0% {
        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6);
    }

    70% {
        box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
    }
}

.voice-status-badge {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    right: 46px;
    z-index: 5;
    font-size: 0.75rem;
    font-weight: 600;
    color: #ef4444;
    background: rgba(239, 68, 68, 0.1);
    border: 1px solid rgba(239, 68, 68, 0.25);
    padding: 2px 8px;
    border-radius: 999px;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.voice-input-wrapper>textarea~.voice-status-badge,
.voice-input-wrapper>textarea.form-control~.voice-status-badge {
    top: 10px;
    transform: none;
}

@media (prefers-color-scheme: dark) {
    .voice-dictation-btn {
        color: #94a3b8;
    }

    .voice-dictation-btn:hover {
        background: rgba(45, 212, 191, 0.15);
        color: #2dd4bf;
    }

    .voice-status-badge {
        color: #f87171;
        background: rgba(239, 68, 68, 0.2);
        border-color: rgba(239, 68, 68, 0.4);
    }
}

/* ── Design Config page: modern visual refresh ───────────────
   Scoped entirely under .design-config-page (the wrapper added around
   design_config/edit.html's content block) so none of this touches the
   40+ other pages that also use .filter-card/.filter-row/.filter-label
   for their own filter-drawer panels - only this page's field-editor
   cards and Live Preview card opt in. */
.design-config-page .page-action-bar {
    border-bottom: 1px solid var(--header-border, #eff2f5);
}

.design-config-page .page-subtitle {
    color: #667085;
    font-size: 0.85rem;
    font-weight: 500;
    margin-top: 2px;
}

/* Border now comes from the base .filter-card rule; this only adds the
   lift/hover treatment specific to this page's field-editor cards. */
.design-config-page .filter-card {
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
    transition: box-shadow 0.2s ease;
}

.design-config-page .dc-field-card:hover {
    box-shadow: 0 6px 18px rgba(16, 24, 40, 0.06);
}

/* Each field-editor card sits in the col-lg-7 pane, which is far wider
   than one label+swatch+hex-input row needs - left as a single column
   the extra width just reads as dead space (and 10-field groups like
   Buttons need a lot of scrolling). Let rows re-flow into as many
   340px+ columns as the card has room for; .dc-card-title/.dc-note
   opt out via grid-column so they still span the full card width. */
.design-config-page .dc-field-card {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
    column-gap: 16px;
    /* .filter-card (site.css's shared 8px radius, ~40 pages) is reused here
       for layout only - Design Config is the one place it holds a genuine
       config form (a color-field editor group), so it alone gets the
       mandatory form-container radius. Every other .filter-card usage is a
       plain search/filter panel and stays at the shared 8px. */
    border-radius: var(--form-container-radius, 26px);
}

.design-config-page .dc-card-title,
.design-config-page .dc-note {
    grid-column: 1 / -1;
}

/* This margin assumes rows stack in a single column (site.css's own
   .filter-card usage elsewhere) - in the grid above, DOM-adjacent rows
   can land side-by-side rather than stacked, where the extra top margin
   would just misalign that row against its row-mate in the next column. */
.design-config-page .dc-field-card .filter-row+.filter-row {
    margin-top: 0;
}

.design-config-page .dc-card-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.8rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #667085;
    padding-bottom: 12px;
    margin-bottom: 14px !important;
    border-bottom: 1px solid var(--card-border, #eef1f4);
}

.design-config-page .dc-card-title i {
    font-size: 1rem;
    color: var(--form-focus-color, #1b838b);
}

.design-config-page .dc-note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

.design-config-page .dc-note i {
    color: #98a2b3;
    margin-top: 2px;
}

.design-config-page .dc-field-row {
    padding: 6px 8px;
    border-radius: 8px;
    transition: background-color 0.15s ease;
}

.design-config-page .dc-field-row:hover {
    background-color: rgba(16, 24, 40, 0.035);
}

.design-config-page .dc-swatch {
    width: 38px;
    height: 38px;
    padding: 3px;
    border-radius: 10px;
    border: 1px solid #e1e3ea !important;
    background-color: #fff !important;
    flex: 0 0 auto;
    cursor: pointer;
}

.design-config-page .dc-swatch::-webkit-color-swatch {
    border-radius: 6px;
    border: none;
}

.design-config-page .dc-swatch::-moz-color-swatch {
    border-radius: 6px;
    border: none;
}

.design-config-page .dc-hex-input {
    font-family: var(--font-mono) !important;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    max-width: 140px;
}

/* voice-dictation.js wraps the hex input in its own .voice-input-wrapper
   element (see voice-dictation.js's _initUI()), and the mic button is
   absolutely positioned against THAT wrapper, not the input - the
   wrapper's base rule (site.css ~4632) stretches it to width:100% of its
   flex container regardless of the input's own max-width, which is what
   left a large gap between the (now narrow) input and an orphaned mic
   button. Pin the wrapper itself to the same width as the input so they
   move together. */
.design-config-page .dc-field-row .voice-input-wrapper {
    flex: 0 0 140px;
    width: 140px;
    max-width: 140px;
}

.design-config-page .dc-select {
    max-width: 220px;
}

.design-config-page .live-preview-card .dc-live-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--form-focus-color, #1b838b);
    background: rgba(27, 131, 139, 0.1);
    padding: 3px 9px;
    border-radius: 999px;
}

.design-config-page .dc-live-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--form-focus-color, #1b838b);
    animation: dcLivePulse 1.6s infinite;
}

@keyframes dcLivePulse {
    0% {
        box-shadow: 0 0 0 0 rgba(27, 131, 139, 0.5);
    }

    70% {
        box-shadow: 0 0 0 6px rgba(27, 131, 139, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(27, 131, 139, 0);
    }
}

.design-config-page .dc-preview-divider {
    border: none;
    border-top: 1px solid var(--card-border, #eef1f4);
    margin: 14px 0;
}

@media (max-width: 991.98px) {
    .design-config-page .live-preview-card {
        position: static !important;
    }
}

/* ── Home Page & Home Config Preview Components ──────────── */
.home-hero {
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: 20px;
    position: relative;
    background-color: var(--nav-active-bg, #12233d);
    color: #ffffff;
}

/* The banner renders as `.home-hero-banner-img` (a real <img>), not this
   div's background-image - see home.js::renderHero(). object-fit/
   object-position/transform are set per-config in JS; the declarations here
   are just the structural default so the element behaves sanely before JS
   has run once. */
.home-hero-banner-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    object-fit: cover;
    object-position: center;
}

.home-hero-inner {
    padding: 24px 20px;
    position: relative;
    z-index: 1;
}

.home-hero-logo {
    max-height: 48px;
    margin-bottom: 10px;
    object-fit: contain;
}

.home-hero-title {
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0;
    line-height: 1.25;
    color: #ffffff !important;
}

.home-hero-welcome {
    font-size: 0.95rem;
    opacity: 0.9;
    margin-top: 4px;
    color: #e2e8f0 !important;
}

.home-quick-links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 20px;
}

.home-content {
    margin-bottom: 20px;
}

.home-columns {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}

.home-column {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* A 2- or 3-column Home Config layout has to become one column on a phone:
   two columns of a 375px screen is ~160px each, which is narrower than a single
   widget's own stat tiles. Stacking preserves the configured order (column 0
   first, then column 1), so nothing is lost - it just reads top to bottom. */
@media (max-width: 767.98px) {
    .home-columns {
        flex-direction: column;
    }

    .home-column {
        width: 100%;
    }
}

.home-widget {
    border: 1px solid var(--card-border, #eef1f4);
    border-radius: 10px;
    padding: 14px;
    background: #ffffff;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
}

.home-widget h6 {
    font-weight: 700;
    margin-bottom: 10px;
    color: #1e293b;
    font-size: 0.95rem;
}

.home-widget-stat-row {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.home-widget-stat {
    flex: 1 1 70px;
    text-align: center;
    padding: 8px;
    border-radius: 8px;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
}

.home-widget-stat .num {
    font-size: 1.25rem;
    font-weight: 700;
    display: block;
    line-height: 1.2;
}

.home-widget-stat .lbl {
    font-size: 0.72rem;
    color: #64748b;
    font-weight: 500;
}

.home-widget-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.home-widget-list li {
    padding: 5px 0;
    border-bottom: 1px solid #f1f5f9;
    font-size: 0.85rem;
    color: #334155;
}

.home-widget-list li:last-child {
    border-bottom: none;
}

.home-widget-empty {
    color: #94a3b8;
    font-size: 0.82rem;
    font-style: italic;
}

/* Module launcher (the `modules` widget - what this workspace actually does,
   which is what an EHS landing page leads with). auto-fit rather than a fixed
   column count: the card count is however many modules the tenant has switched
   on, and the row has to read the same at two cards as at seven. */
.home-module-grid {
    display: grid;
    /* min(230px, 100%), not a bare 230px: this grid sits inside a widget inside
       a Home Config column, which can be narrower than one card's ideal width -
       a fixed track floor would push the cards out of their own column. */
    grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
    gap: 12px;
}

.home-module-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px;
    border: 1px solid var(--card-border, #eef1f4);
    border-radius: 10px;
    background: #ffffff;
    text-decoration: none;
    color: inherit;
    box-shadow: var(--panel-shadow);
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

.home-module-card:hover {
    transform: translateY(-2px);
    border-color: var(--btn-primary-bg, #1b838b);
    box-shadow: var(--shell-shadow);
    color: inherit;
}

.home-module-icon {
    flex: 0 0 42px;
    width: 42px;
    height: 42px;
    border-radius: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    background: color-mix(in srgb, var(--btn-primary-bg, #1b838b) 10%, #ffffff);
    color: var(--btn-primary-bg, #1b838b);
}

.home-module-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}

.home-module-label {
    font-weight: 700;
    font-size: 0.92rem;
    color: #1e293b;
}

.home-module-desc {
    font-size: 0.78rem;
    color: #64748b;
}

.home-module-go {
    color: #cbd5e1;
    flex: 0 0 auto;
}

.home-module-card:hover .home-module-go {
    color: var(--btn-primary-bg, #1b838b);
}

.home-announcement {
    border-left: 3px solid var(--btn-primary-bg, #1b838b);
    padding-left: 10px;
    margin-bottom: 10px;
}

.home-announcement:last-child {
    margin-bottom: 0;
}

.home-announcement-title {
    font-weight: 600;
    color: #1e293b;
    font-size: 0.88rem;
}

.home-announcement-date {
    font-size: 0.72rem;
    color: #64748b;
}

@media (prefers-color-scheme: dark) {
    .home-widget {
        background: #1e2129;
        border-color: #374151;
    }

    .home-widget h6,
    .home-announcement-title {
        color: #f9fafb;
    }

    .home-widget-stat {
        background: #2d3748;
        border-color: #4b5563;
    }

    .home-widget-list li {
        border-color: #374151;
        color: #cbd5e1;
    }

    /* Module cards are their own surface inside .home-widget, so they need the
       same treatment its background/border already gets here. */
    .home-module-card {
        background: #2d3748;
        border-color: #4b5563;
    }

    .home-module-label {
        color: #f9fafb;
    }

    .home-module-desc {
        color: #cbd5e1;
    }
}

/* Standardized Active Filter Indicator Badge */
.active-filter-indicator {
    display: inline-flex;
    align-items: center !important;
}

.active-filter-indicator .badge {
    background-color: rgba(27, 131, 139, 0.08) !important;
    color: #1B838B !important;
    font-weight: 700 !important;
    font-size: 0.85rem !important;
    padding: 8px 16px !important;
    border-radius: 999px !important;
    border: 1px solid rgba(27, 131, 139, 0.15) !important;
    letter-spacing: 0.02em !important;
    transition: all 0.2s ease !important;
}

.active-filter-indicator .badge:hover {
    background-color: rgba(27, 131, 139, 0.12) !important;
    border-color: rgba(27, 131, 139, 0.25) !important;
}

/* Playbook Export Buttons Custom Branding */
.btn-export-excel {
    color: #107c41 !important;
    border-color: rgba(16, 124, 65, 0.25) !important;
    background-color: rgba(16, 124, 65, 0.04) !important;
}

.btn-export-excel:hover {
    background-color: rgba(16, 124, 65, 0.08) !important;
    border-color: rgba(16, 124, 65, 0.4) !important;
    color: #107c41 !important;
}

.btn-export-pdf {
    color: #b42318 !important;
    border-color: rgba(180, 35, 24, 0.25) !important;
    background-color: rgba(180, 35, 24, 0.04) !important;
}

.btn-export-pdf:hover {
    background-color: rgba(180, 35, 24, 0.08) !important;
    border-color: rgba(180, 35, 24, 0.4) !important;
    color: #b42318 !important;
}

/* Playbook Offcanvas Filter Header & Stacking Layout */
.offcanvas-header {
    background-color: var(--btn-primary-bg, #1B838B) !important;
    color: #ffffff !important;
    padding: 16px 24px !important;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
}

.offcanvas-header .offcanvas-title {
    color: #ffffff !important;
    font-weight: 700 !important;
    font-size: 1.25rem !important;
    letter-spacing: 0.02em !important;
    margin: 0 !important;
}

.offcanvas-header .btn-close {
    filter: invert(1) grayscale(1) brightness(2) !important;
    opacity: 0.8 !important;
    outline: none !important;
    box-shadow: none !important;
}

.offcanvas-header .btn-close:hover {
    opacity: 1 !important;
}

/* Offcanvas Body Filter Rows - Stacked layout */
.offcanvas .filter-row {
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 6px !important;
}

.offcanvas .filter-label {
    flex: 0 0 auto !important;
    width: 100% !important;
    margin-bottom: 2px !important;
    text-align: left !important;
}

.offcanvas .filter-input {
    width: 100% !important;
    min-height: 38px !important;
}

/* Bootstrap Multiselect full width inside filter rows */
.filter-row .btn-group,
.filter-row .multiselect-native-select .btn-group {
    width: 100% !important;
    display: block !important;
}

.filter-row button.multiselect {
    width: 100% !important;
    text-align: left !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    background-color: #fff !important;
    border: 1px solid #d0d5dd !important;
    border-radius: 8px !important;
    padding: 8px 12px !important;
    font-size: 0.875rem !important;
    font-weight: 500 !important;
    color: #344054 !important;
    min-height: 38px !important;
}

.filter-row button.multiselect::after {
    margin-left: auto !important;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Premium Tooltip Design System Styling (SafeKivi Playbook)
   ───────────────────────────────────────────────────────────────────────────── */
.tooltip {
    --bs-tooltip-bg: var(--btn-primary-bg, #1b838b) !important;
    --bs-tooltip-color: var(--btn-primary-text, #ffffff) !important;
    --bs-tooltip-border-radius: 50rem !important;
    /* Pill-shaped borders matching standard button/pill styling */
    --bs-tooltip-padding-x: 14px !important;
    --bs-tooltip-padding-y: 6px !important;
    font-size: 0.85rem !important;
    font-weight: 500 !important;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
}

/* Fallback support: explicitly style the tooltip inner body container */
.tooltip-inner {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    color: var(--btn-primary-text, #ffffff) !important;
    border-radius: 50rem !important;
}

/* Ensure the arrow pointer is a clean sharp triangle of the primary color, instead of a solid block */
.tooltip .tooltip-arrow::before {
    border-color: transparent !important;
    border-style: solid !important;
}

.bs-tooltip-top .tooltip-arrow::before,
.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before {
    border-top-color: var(--bs-tooltip-bg, #1b838b) !important;
}

.bs-tooltip-bottom .tooltip-arrow::before,
.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before {
    border-bottom-color: var(--bs-tooltip-bg, #1b838b) !important;
}

.bs-tooltip-start .tooltip-arrow::before,
.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before {
    border-left-color: var(--bs-tooltip-bg, #1b838b) !important;
}

.bs-tooltip-end .tooltip-arrow::before,
.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before {
    border-right-color: var(--bs-tooltip-bg, #1b838b) !important;
}

/* ─────────────────────────────────────────────────────────────────────────────
   SafeKivi Playbook Standardized Button System
   Full rounded pill geometry, consistent typography, brand colors & micro-interactions
   ───────────────────────────────────────────────────────────────────────────── */
.btn,
.btn-ob-primary,
.btn-ob-ghost,
.btn-ob-success,
.btn-ob-sm,
.btn-apply-filters,
.btn-outline-filter,
.btn-export {
    border-radius: 9999px !important;
    font-weight: 700 !important;
    letter-spacing: 0.01em !important;
    transition: all 0.18s cubic-bezier(0.4, 0, 0.2, 1) !important;
}

/* Primary buttons across modal footers and wizards */
.btn-primary,
.btn-ob-primary,
.btn-ob-success {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    color: var(--btn-primary-text, #ffffff) !important;
    border: 1px solid transparent !important;
}

.btn-primary:hover,
.btn-ob-primary:hover,
.btn-ob-success:hover {
    background-color: #146267 !important;
    color: #ffffff !important;
    box-shadow: 0 4px 12px rgba(27, 131, 139, 0.25) !important;
}

/* Ghost / Cancel / Secondary outline buttons across modal footers and wizards */
.btn-secondary,
.btn-outline-secondary,
.btn-ob-ghost {
    background-color: #ffffff !important;
    color: #475569 !important;
    border: 1px solid #cbd5e1 !important;
}

.btn-secondary:hover,
.btn-outline-secondary:hover,
.btn-ob-ghost:hover {
    background-color: #f8fafc !important;
    color: #0f172a !important;
    border-color: #94a3b8 !important;
}

/* ─────────────────────────────────────────────────────────────────────────────
   SafeKivi Playbook Datatable Action Buttons & Layout Overflow Guards
   ───────────────────────────────────────────────────────────────────────────── */
.actions-cell .btn,
.actions-cell .btn-outline-primary,
.actions-cell .btn-outline-danger {
    width: 32px !important;
    height: 32px !important;
    min-width: 32px !important;
    max-width: 32px !important;
    padding: 0 !important;
    border-radius: 50% !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    font-size: 0.88rem !important;
    transition: all 0.18s ease !important;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04) !important;
}

.actions-cell .btn-outline-primary {
    background-color: #ffffff !important;
    color: var(--btn-primary-bg, #1b838b) !important;
    border: 1px solid rgba(27, 131, 139, 0.35) !important;
}

.actions-cell .btn-outline-primary:hover {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    color: #ffffff !important;
    border-color: var(--btn-primary-bg, #1b838b) !important;
}

.actions-cell .btn-outline-danger {
    background-color: #ffffff !important;
    color: #ef4444 !important;
    border: 1px solid rgba(239, 68, 68, 0.35) !important;
}

.actions-cell .btn-outline-danger:hover {
    background-color: #ef4444 !important;
    color: #ffffff !important;
    border-color: #ef4444 !important;
}

/* Prevent a horizontal scrollbar (e.g. the roster management view's wide grid).
   Root only, deliberately NOT body: on the root, overflow propagates to the
   viewport and vertical page scrolling is preserved. On body it would instead
   make body a scroll container - `overflow-x: hidden` forces the y axis to
   `auto` - and every position:sticky inside would anchor to that never-
   scrolling box and silently stop sticking. */
html {
    overflow-x: hidden !important;
}

/* ── Error Page Styles ── */
/* Flex, so .error-page-content can stretch to the full column height below.
   #main_body has no definite height any more (the document scrolls, not an
   inner box), so the percentage min-height this used to rely on would collapse
   to the content's own height and stop centring. */
#main_body:has(.error-page-content) {
    padding: 0;
    display: flex;
}

.error-page-content {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    padding: 2rem;
    background-color: #f5f8fa;
}

.error-card {
    background: #f7f8fb;
    border-radius: 12px;
    border: 1px solid #eff2f5;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03) !important;
    width: 100%;
    max-width: 800px;
    margin: auto;
}

.error-title {
    color: #446383;
    font-size: 2.2rem;
    font-weight: 800;
    margin-bottom: 12px;
}

.error-desc {
    color: #5e6278;
    font-size: 1.05rem;
    line-height: 1.6;
}

.ref-container {
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.ref-label {
    color: rgba(0, 0, 0, 0.4);
}

.ref-value {
    color: #1b838b;
}

.btn-action-primary {
    background-color: var(--btn-primary-bg, #1b838b) !important;
    color: #ffffff !important;
    border: none;
    font-weight: 600;
    border-radius: 50px;
    padding: 0.5rem 1.5rem;
    transition: all 0.2s ease;
}

.btn-action-primary:hover {
    background-color: var(--btn-primary-hover-bg, #146267) !important;
    transform: translateY(-1px);
    color: #ffffff !important;
}

.btn-action-secondary {
    background-color: transparent !important;
    border: 1px solid #1b838b !important;
    color: #1b838b !important;
    font-weight: 600;
    border-radius: 50px;
    padding: 0.5rem 1.5rem;
    transition: all 0.2s ease;
}

.btn-action-secondary:hover {
    background-color: rgba(27, 131, 139, 0.05) !important;
    transform: translateY(-1px);
    color: #1b838b !important;
}

body.logged-out-wrapper {
    background-color: #f5f8fa;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

body.logged-out-wrapper .error-wrapper {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
    margin-top: 60px;
}

.lock-icon-container,
.clock-icon-container {
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background: rgba(27, 131, 139, 0.08);
    border: 2px solid rgba(27, 131, 139, 0.2);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 24px auto;
    color: #1b838b;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
}

.lock-icon-container svg,
.clock-icon-container svg {
    width: 56px;
    height: 56px;
}

/* ============================================================
   RISK ASSESSMENT MODULE
   Restored after an uncommitted-change discard while resolving merge
   conflicts against the concurrent v1.21.0 Audit Module rebuild — see
   playbook/governance/changelog.md v1.21.1/v1.21.2 for the fixes this
   documents. The module's other CSS hooks (.rc-band-*, .rc-level-*,
   .rc-scope-*, .rc-suggested-*, .ra-control-*) are deliberately style-free:
   they're jQuery selector hooks layered on Bootstrap .row/.form-control/
   .form-select, which already do all the visual work, so nothing was lost
   there.
   ============================================================ */

/* Card surface for the risk and audit modules' CONTENT panels.
   `.users-ag-grid-panel` deliberately sets no background: behind an AG Grid the
   theme paints the surface, so the shell only needs a border, radius and
   shadow. The risk module reuses that shell for plain forms and report panels
   with no grid inside, which left them transparent and showing the page
   background. `.ra-card` supplies the missing surface.
   Scoped to this module rather than added to `.users-ag-grid-panel` itself,
   which sits under ~45 grids across the app and is not ours to repaint.
   Explicitly white for the same reason as .filter-card / .safekivi-card above:
   --card-bg is Design Config's "Card/panel background" token, and a tenant
   still holding its old grey value had this render grey instead of white. */
.ra-card,
.audit-card {
    background-color: #ffffff !important;
}

/* `.ra-sticky-actions` below needs `#main_body` (the real scroll container, see
   layout.html) as its nearest non-visible-overflow ancestor for position:sticky
   to measure against — but `.ra-card` also carries `.users-ag-grid-panel`,
   which sets `overflow: hidden` (for AG Grid's rounded corners) and, via the
   "small screen resolutions" rule further up this file, `overflow-x: auto
   !important` at EVERY viewport width, not just small ones. Either alone makes
   `.ra-card` itself the sticky-containing block instead of `#main_body`; since
   `.ra-card` never scrolls (it grows to fit its content), the button row can
   never detach from normal flow and visibly stick to the viewport edge. Both
   classes are required so this beats the `!important` overflow-x rule outright
   rather than depending on source order. Safe: no `.ra-card` panel anywhere in
   the codebase ever wraps a real AG Grid (grep confirms `.ra-card` and AG Grid
   markup never coexist), so this cannot affect the ~45 other panels that rely
   on `.users-ag-grid-panel`'s clipping alone. */
.ra-card.users-ag-grid-panel,
.audit-card.users-ag-grid-panel {
    overflow: visible !important;
}

/* `.btn-outline-filter` sets no `display`, so inside a `d-flex` row it becomes
   a stretched flex item — taller than the button beside it, label pinned to the
   top. Anchors styled as buttons need to lay themselves out like one. */
.ra-btn-link,
.audit-btn-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    line-height: 1.5;
}

.ra-btn-link:hover,
.audit-btn-link:hover {
    text-decoration: none;
}

/* Sticky action bar on the New Assessment form and the Perform Audit screen. Mirrors
   `.sticky-page-bar` above (negative margin + compensating padding escapes
   the ancestor's own padding so the bar sits flush against the scroll edge)
   but for the BOTTOM edge of `#main_body` instead of the top: `.ra-card` uses
   Bootstrap's `.p-3` (16px), so bottom:-16px + margin-bottom:-16px cancels
   that padding and padding-bottom:16px puts it back on the bar itself. A
   hardcoded white background (same reasoning as `.ra-card` above) and
   border-top keep fields scrolling underneath from showing through or
   visually running into the buttons. */
.ra-sticky-actions,
.audit-sticky-actions {
    position: sticky;
    bottom: -16px;
    margin-bottom: -16px;
    padding: 12px 0 16px;
    background-color: #ffffff;
    border-top: 1px solid var(--card-border, #eef1f4);
    z-index: 1000;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Audit module
   ═══════════════════════════════════════════════════════════════════════════
   The compliance palette, derived from the tenant's own alert tokens so an
   audit page re-themes with the rest of the app through Design Config rather
   than carrying its own hardcoded greens and reds.

   Severity and finding-class colours are NOT here: those are per-tenant
   configuration read from audit_severity.color / audit_finding_class.color and
   applied inline, because they are data an administrator chose, not design
   tokens. */
:root {
    --audit-conform: var(--alert-success-text, #0f5132);
    --audit-conform-bg: var(--alert-success-bg, #d1e7dd);
    --audit-nonconform: var(--alert-danger-text, #842029);
    --audit-nonconform-bg: var(--alert-danger-bg, #f8d7da);
    --audit-observation: var(--alert-warning-text, #664d03);
    --audit-observation-bg: var(--alert-warning-bg, #fff3cd);
    --audit-na: var(--grid-header-text-color, #98a2b3);
    --audit-na-bg: var(--card-header-bg, #f8fafc);

    /* Fill weights. The --audit-* pair above is a text/background pair — the
       `--alert-*-text` end is near-black by design and reads far too heavy as a
       large filled area. Anything that fills (a bar, a track) uses these
       mid-weight semantic values instead. */
    --audit-fill-conform: var(--bs-success, #198754);
    --audit-fill-nonconform: var(--bs-danger, #dc3545);
    --audit-fill-observation: var(--bs-warning, #ffc107);
    --audit-fill-na: var(--card-border, #eef1f4);
}

/* --- Training palette ---
   Same idea as the --audit-* block above: a module's semantic fills named once
   here rather than written as literals at the point of use.

   The "completed" series on the Training dashboard's Assigned-vs-Completed
   chart was the same green literal in two different files — the bar in
   training-dashboard.js and its legend swatch in training/dashboard.html — so
   retinting one silently desynced it from the other. The "assigned" series
   beside it already read --btn-primary-bg, which is why only this one needed a
   token. Kept at its shipped value so the chart looks the same as it did. */
:root {
    --training-fill-complete: #12B76A;
}

/* Compliance verdict, as a leading dot on an answer option and as the left
   edge of an answered question card. Always paired with the option's own text
   label — colour alone is not an accessible signal (WCAG 2.1 AA). */
.audit-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
    display: inline-block;
    background: var(--audit-na);
}

.audit-dot-conform {
    background: var(--audit-conform);
}

.audit-dot-nonconform {
    background: var(--audit-nonconform);
}

.audit-dot-observation {
    background: var(--audit-observation);
}

.audit-dot-na {
    background: var(--audit-na);
}

/* Conformance bar on the audit report's per-section breakdown. The percentage
   is always printed beside the bar, for the same accessibility reason. */
.audit-bar-excellent {
    background: var(--audit-fill-conform);
}

.audit-bar-good {
    background: var(--link-color, #4dabf7);
}

.audit-bar-fair {
    background: var(--audit-fill-observation);
}

.audit-bar-poor {
    background: var(--audit-fill-nonconform);
}

.audit-bar-none {
    background: var(--audit-fill-na);
}

/* An overdue date, wherever one is rendered. */
.audit-overdue {
    color: var(--audit-nonconform);
    font-weight: 600;
}

/* Chips for the audit module's fixed vocabulary — review decisions and the
   repeat-finding marker. A finding class, severity or rating band carries the
   tenant's own configured colour instead and is styled inline. */
.ar-chip-approve {
    color: var(--audit-conform);
    background: var(--audit-conform-bg);
}

.ar-chip-comment {
    color: var(--alert-info-text, #055160);
    background: var(--alert-info-bg, #cff4fc);
}

.ar-chip-reject,
.ar-chip-repeat {
    color: var(--audit-nonconform);
    background: var(--audit-nonconform-bg);
}

/* Inline completion bar in the audit register's Progress column. */
.audit-progress {
    display: inline-block;
    width: 52px;
    height: 6px;
    border-radius: 999px;
    background: var(--card-border, #eef1f4);
    overflow: hidden;
}

.audit-progress>span {
    display: block;
    height: 100%;
    background: var(--tab-active-color, #1b838b);
}

/* ════════════════════════════════════════════════════════════════════════════
   PDF REPORT DESIGNER (Control Center -> Report Templates / Report Designer)

   The `rd-*` family styles the three-panel visual designer
   (control_center/report_designer.html + js/report_designer.js); the two
   `rt-*` rules serve its register page. They live here rather than in a page
   <style> block or a new stylesheet for the reason component_catalog.md
   section 19 rule 1 gives: a designer with its own private CSS is exactly how
   a screen drifts away from the rest of the product, and the pre-commit
   compliance check flags both of the alternatives.

   EVERY colour below resolves through a token the :root block at the top of
   this file actually defines. Deliberately no hex fallbacks: a fallback is a
   second, invisible source of truth, and the ones in older rules here predate
   Design Config rather than justifying it.
   ════════════════════════════════════════════════════════════════════════ */

/* -- Version-state pills --------------------------------------------------
   Beside the existing .status-active/.status-inactive/.status-archive states.
   A report template has a lifecycle the others do not - a published version
   in use plus, often, an unpublished draft alongside it - and the register
   shows both facts at once, so both need a pill. */
.status-pill.status-draft {
    background: var(--alert-warning-bg);
    color: var(--alert-warning-text);
}

.status-pill.status-published {
    background: var(--alert-success-bg);
    color: var(--alert-success-text);
}

.status-pill.status-archived {
    background: var(--nav-hover-bg);
    color: var(--grid-header-text-color);
}

/* Published version + open draft sit side by side in one grid cell. */
.rt-version-cell {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

/* -- Shell ----------------------------------------------------------------
   The designer is a fixed-height workspace, not a scrolling page: the canvas
   has to stay beside the palette and the inspector while it is scrolled, so
   each panel owns its own scrollbar and the shell claims the viewport. */
.rd-shell {
    display: flex;
    flex-direction: column;
    height: var(--workspace-shell-height);
    min-height: 520px;
}

/* Embedded in an iframe the frame IS the viewport, so take all of it. */
.rd-shell-embed {
    height: 100vh;
    min-height: 480px;
}

.rd-topbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 16px;
    background: var(--grid-header-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--form-container-radius, 26px) var(--form-container-radius, 26px) 0 0;
}

.rd-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--form-label-color);
}

.rd-spacer {
    flex: 1 1 auto;
}

.rd-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.3;
}

.rd-badge-draft {
    background: var(--alert-warning-bg);
    color: var(--alert-warning-text);
}

.rd-badge-published {
    background: var(--alert-success-bg);
    color: var(--alert-success-text);
}

.rd-badge-archived,
.rd-badge-version,
.rd-badge-entity {
    background: var(--nav-hover-bg);
    color: var(--grid-header-text-color);
}

/* -- Why every designer surface below hardcodes #ffffff instead of --card-bg --
   `card_bg`'s shipped default is #ffffff, but a default only applies to a
   tenant who never saved a Design Config. A tenant who saved one while the
   default was still the old hover-grey #f2f4f7 has that grey *stored*, so
   theme.js writes it onto :root on every page load and `var(--card-bg)` -
   fallback and all - resolves grey. The designer is where that reads worst:
   three full-height panels side by side turn the whole workspace into one
   flat grey slab. Same reasoning, and same fix, as .ra-card / .safekivi-card
   / .filter-card above. */

/* -- Notice strip (validation results + the read-only banner) ------------- */
.rd-notices {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px 16px;
    border: 1px solid var(--card-border);
    border-top: none;
    background: #ffffff;
    max-height: 132px;
    overflow-y: auto;
}

.rd-notice {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 0.8rem;
    line-height: 1.4;
}

.rd-notice-error {
    background: var(--alert-danger-bg);
    color: var(--alert-danger-text);
}

.rd-notice-warning {
    background: var(--alert-warning-bg);
    color: var(--alert-warning-text);
}

.rd-notice-info {
    background: var(--alert-info-bg);
    color: var(--alert-info-text);
}

/* -- Panels -------------------------------------------------------------- */
.rd-body {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    border: 1px solid var(--card-border);
    border-top: none;
    border-radius: 0 0 var(--form-container-radius, 26px) var(--form-container-radius, 26px);
    overflow: hidden;
}

.rd-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: #ffffff;
}

.rd-left {
    width: 260px;
    flex: none;
    border-right: 1px solid var(--card-border);
}

/* No background of its own - the element also carries .rd-panel, which sets it. */
.rd-center {
    flex: 1 1 auto;
    min-width: 0;
}

.rd-right {
    width: 360px;
    flex: none;
    border-left: 1px solid var(--card-border);
}

.rd-panel-head {
    padding: 10px 14px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--grid-header-text-color);
    border-bottom: 1px solid var(--card-border);
}

.rd-panel-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 12px;
}

/* A published version is immutable, so its controls are disabled in JS; this
   only makes that visible at a glance rather than one control at a time. */
.rd-locked {
    opacity: 0.65;
}

/* -- Left panel accordions ----------------------------------------------- */
.rd-acc {
    border-bottom: 1px solid var(--card-border);
}

.rd-acc:last-child {
    border-bottom: none;
}

.rd-acc-head {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin: 0;
    padding: 11px 6px;
    background: none;
    border: 0;
    border-radius: 6px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--form-label-color);
    text-align: left;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}

.rd-acc-head:hover {
    background: var(--nav-hover-bg);
    color: var(--btn-primary-bg);
}

.rd-acc-chevron {
    margin-left: auto;
    color: var(--icon-color);
}

.rd-acc-body {
    padding: 2px 6px 14px;
}

/* -- Palette ------------------------------------------------------------- */
.rd-palette-group {
    margin: 10px 0 6px;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--grid-header-text-color);
}

.rd-palette-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    margin-bottom: 6px;
    padding: 9px 11px;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: #ffffff;
    font-size: 0.84rem;
    color: var(--form-label-color);
    cursor: grab;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.rd-palette-item:hover {
    border-color: var(--btn-primary-bg);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}

.rd-palette-item i {
    font-size: 1rem;
    color: var(--btn-primary-bg);
}

/* -- Shared field scaffolding (layout only - every control keeps the app-wide
      SafeKivi Form Design System look; nothing here restates it) ---------- */
.rd-field {
    margin-bottom: 12px;
}

.rd-field .form-label {
    margin-bottom: 4px;
}

.rd-field-check .form-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    cursor: pointer;
}

.rd-grid-2 {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0 10px;
}

.rd-sub-title {
    margin: 14px 0 8px;
    padding-top: 10px;
    border-top: 1px dashed var(--card-border);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--grid-header-text-color);
}

.rd-help {
    margin-top: 4px;
    font-size: 0.72rem;
    line-height: 1.45;
    color: var(--grid-header-text-color);
}

.rd-add-btn {
    margin-bottom: 6px;
}

/* -- Canvas: header/footer bands ----------------------------------------- */
.rd-band {
    margin-bottom: 12px;
    padding: 8px 10px;
    border: 1px dashed var(--card-border);
    border-radius: 8px;
    background: #ffffff;
}

.rd-band-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--grid-header-text-color);
}

.rd-band-note {
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
}

/* A zero-height drop target cannot be dropped into, and an empty band is
   exactly when a user most needs to drop something in. */
.rd-band-drop,
.rd-sec-body,
.rd-cmp-children {
    min-height: 34px;
}

/* -- Canvas: sections ---------------------------------------------------- */
.rd-sections {
    min-height: 20px;
}

.rd-sec {
    margin-bottom: 10px;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: #ffffff;
}

.rd-sec.selected,
.rd-cmp.selected {
    border-color: var(--btn-primary-bg);
    box-shadow: 0 0 0 2px rgba(27, 131, 139, 0.15);
}

.rd-sec-head {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--card-border);
    cursor: pointer;
}

.rd-sec-main,
.rd-cmp-main {
    flex: 1 1 auto;
    min-width: 0;
}

.rd-sec-title {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--form-label-color);
    word-break: break-word;
}

.rd-sec-meta,
.rd-cmp-meta {
    margin-top: 3px;
    font-size: 0.72rem;
    color: var(--grid-header-text-color);
    word-break: break-word;
}

.rd-sec-body {
    padding: 10px 12px;
}

.rd-sec-grip,
.rd-cmp-grip,
.rd-col-grip {
    display: inline-flex;
    align-items: center;
    padding-top: 2px;
    color: var(--icon-color);
    cursor: grab;
}

.rd-add-section {
    margin: 4px 0 12px;
}

/* -- Canvas: components -------------------------------------------------- */
.rd-cmp {
    margin-bottom: 8px;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    background: #ffffff;
}

.rd-cmp-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 10px;
    cursor: pointer;
}

.rd-cmp-title {
    font-size: 0.84rem;
    font-weight: 600;
    color: var(--form-label-color);
    word-break: break-word;
}

.rd-cmp-children {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 0 10px 10px 30px;
}

.rd-cmp-children>.rd-cmp {
    flex: 1 1 160px;
    min-width: 150px;
    margin-bottom: 0;
}

.rd-type-badge,
.rd-chip {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--nav-hover-bg);
    color: var(--grid-header-text-color);
    font-size: 0.68rem;
    font-weight: 600;
}

/* A disabled node is still in the template and prints nothing. Not hidden
   here - the whole point of the flag is that an author can see what they
   switched off. */
.rd-off>.rd-cmp-row,
.rd-off>.rd-sec-head,
.rd-off>.rd-band-head {
    opacity: 0.55;
}

.rd-drop-hint {
    margin: 0 12px 10px;
    padding: 10px;
    border: 1px dashed var(--card-border);
    border-radius: 6px;
    text-align: center;
    font-size: 0.75rem;
    color: var(--grid-header-text-color);
}

.rd-canvas-empty {
    padding: 40px 16px;
    text-align: center;
    font-size: 0.85rem;
    color: var(--grid-header-text-color);
}

.rd-canvas-empty i {
    display: block;
    margin-bottom: 8px;
    font-size: 1.75rem;
}

.rd-ghost {
    opacity: 0.4;
}

/* -- Inspector sub-editors (table columns, key/value rows, conditions) ---- */
.rd-inspector-empty {
    padding: 34px 12px;
    text-align: center;
    font-size: 0.82rem;
    color: var(--grid-header-text-color);
}

.rd-inspector-empty i {
    display: block;
    margin-bottom: 8px;
    font-size: 1.5rem;
}

.rd-col-row,
.rd-rule-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 8px;
    padding: 8px;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: var(--card-bg);
}

.rd-col-top {
    display: flex;
    align-items: center;
    gap: 6px;
}

.rd-col-top .form-control,
.rd-col-top .form-select {
    flex: 1 1 auto;
    min-width: 0;
}

/* Alignment, format and width weight on one line: three controls that are
   only meaningful together, and stacking them triples the height of a table
   with eight columns. min-width:0 is what stops a select in a grid track
   overflowing it instead of shrinking. */
.rd-col-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) 72px;
    gap: 6px;
}

.rd-col-grid .form-select,
.rd-col-grid .form-control {
    width: 100%;
    min-width: 0;
}

/* -- PDF preview frame (the register modal and the designer modal share it) */
.rd-preview-frame {
    display: block;
    width: 100%;
    height: 72vh;
    min-height: 420px;
    border: 0;
    background: var(--nav-hover-bg);
}

.rd-preview-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.rd-preview-record {
    width: 120px;
    flex: none;
}

/* Below a laptop width the three panels cannot all keep a usable width, so
   the workspace becomes a single scrolling column rather than three unusable
   slivers. The canvas keeps its drag targets; nothing is hidden. */
@media (max-width: 1200px) {
    .rd-shell {
        height: auto;
        min-height: 0;
    }

    .rd-body {
        flex-direction: column;
    }

    .rd-left,
    .rd-right {
        width: 100%;
        border-right: none;
        border-left: none;
        border-bottom: 1px solid var(--card-border);
    }

    .rd-panel-scroll {
        max-height: 460px;
    }
}

/* ── Training / My Learning: path + qualification records ─────────────────────
   Both lists on this page rendered as tall blocks of undifferentiated 1rem/600
   text, and every path row ended in a full-width saturated primary button.
   Two mechanical causes, named here so they are not reintroduced:

   1. `btn-sm` is a NO-OP on `.btn-safekivi-*`. Bootstrap 5.3 defines it purely
      as `--bs-btn-*` custom properties, and those are read only by the `.btn`
      base rule — which these classes do not carry. So the button the author
      wrote as small rendered at inherited 1rem/600 with the base rule's
      `padding: 8px 20px`, about 42px tall. `.btn-safekivi-sm` below is the
      size modifier that was missing.
   2. The course title was concatenated into the button label, so the control's
      width was a function of a data field — roughly 580px of solid brand teal,
      repeated once per path row. That repetition, not the hue, is what read as
      heavy. The title now has its own line and the label is the verb.

   Deliberately no hex fallbacks below: a fallback is a second, invisible
   source of truth, and every token used here is declared in :root.
   ────────────────────────────────────────────────────────────────────────── */

/* The compact button metric this app has already agreed on in three separate
   places — component_catalog.md §8 rule 4's toolbar standard, `.btn-outline-filter`
   and `.nd-btn` — all of which land on 0.875rem / 600 / pill / ~36-38px.

   Scoped to the three sanctioned button classes rather than teaching `btn-sm`
   to work, because `btn-sm` sits on ~47 other buttons across the app that
   would all resize at once. Needs both the two-class specificity and
   `!important` to clear the base rule's own `padding: 8px 20px !important`. */
.btn-safekivi-primary.btn-safekivi-sm,
.btn-safekivi-secondary.btn-safekivi-sm,
.btn-safekivi-danger.btn-safekivi-sm {
    min-height: 36px !important;
    padding: 6px 16px !important;
    font-size: 0.875rem !important;
    line-height: 1.2 !important;
}

/* ── Learning path record ──────────────────────────────────────────────────── */
.tr-path-list {
    display: flex;
    flex-direction: column;
}

/* Separator between records rather than under every one, so the card does not
   end on a rule the way `mb-3 pb-3 border-bottom` made it. */
.tr-path+.tr-path {
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--card-border);
}

.tr-path-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}

/* `min-width: 0` here and `flex: none` on the counter is the entire fix for
   the long generated identifiers. Without it the name could grow past the
   track, and the browser broke it at its own hyphens — "Path Regression
   PATH-TEST-2BF5B6F2" wrapped mid-identifier onto a second line. One line,
   ellipsis, full text on the title attribute.

   0.875rem/600 against the card header's 1rem/700 is a real size step. The old
   1rem/600 differed from its own section heading by a single weight, so a row
   competed with the heading containing it. */
.tr-path-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--header-text);
}

.tr-path-count {
    flex: none;
    white-space: nowrap;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--breadcrumb-text-color);
}

/* The step rail keeps every course it always showed; only the palette changed.
   It was `bg-success` green and `bg-primary` — Bootstrap blue, the one blue
   left on a teal page — so state now reads through the module's own tokens. */
.tr-path-rail {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 10px;
}

.tr-step {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1;
}

/* Done steps take the subtle success pair (7.21:1) rather than a solid fill of
   --training-fill-complete, which at white-on-#12B76A measures 2.62:1 and fails
   even the 3:1 a non-text indicator needs. It also reads better: the completed
   steps recede and the single solid chip below is the one the eye lands on,
   which is the whole point of a progress rail. */
.tr-step.is-done {
    background: var(--alert-success-bg);
    color: var(--alert-success-text);
}

.tr-step.is-current {
    background: var(--btn-primary-bg);
    color: var(--btn-primary-text);
}

.tr-step.is-todo {
    background: var(--grid-row-hover-bg);
    color: var(--breadcrumb-text-color);
}

/* The course name that used to live inside the button label. Because the
   button is now the verb alone, its width is a function of the action and
   every row's action lands on the same right edge — the old ragged edge moved
   with the length of whatever course happened to be next. */
.tr-path-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 12px;
}

.tr-path-next {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

/* The muted tier here is --breadcrumb-text-color (4.97:1 on white), NOT the
   lighter --grid-header-text-color, which measures 2.58:1 and fails AA — the
   same trap the datepicker's outside-days and footer actions hit. */
.tr-path-next-label {
    flex: none;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--breadcrumb-text-color);
}

.tr-path-next-course {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.875rem;
    color: var(--grid-pagination-icon-color);
}

.tr-path-action {
    flex: none;
}

/* A real focus ring. The shared `.btn-safekivi-secondary:focus` rule above
   gives this control `box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.05)` and
   `outline: 0` — a 5%-alpha black ring that is effectively invisible, and a
   `:focus` rule so it also fires on mouse click. ui_ux_guidelines.md §2
   requires a high-contrast ring on every interactive control, so this states
   one at the brand focus token, keyboard-only. Scoped to this control rather
   than corrected on `.btn-safekivi-secondary` itself, which would change the
   focus treatment of every secondary button in the product at once. */
.tr-path-action:focus-visible {
    outline: 2px solid var(--form-focus-color) !important;
    outline-offset: 2px !important;
}

/* ── Qualification record ──────────────────────────────────────────────────── */
.tr-qual-list {
    display: flex;
    flex-direction: column;
}

.tr-qual {
    padding: 10px 0;
}

/* The app hairline, not Bootstrap's `--bs-border-color` (#dee2e6), which the
   `list-group-flush` this replaces was drawing — visibly heavier than every
   other rule on the same page. */
.tr-qual+.tr-qual {
    border-top: 1px solid var(--card-border);
}

.tr-qual-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* Same truncation contract as `.tr-path-name`, and the reason the record went
   from five lines to two: the name no longer wraps under the status badge. */
.tr-qual-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--header-text);
}

.tr-qual-head .badge {
    flex: none;
}

.tr-qual-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 2px 10px;
    margin-top: 4px;
    font-size: 0.75rem;
}

.tr-qual-muted {
    color: var(--breadcrumb-text-color);
}

/* The certificate link was Bootstrap's default blue, which measures about
   3.6:1 on white at this size. The darker brand teal clears AA and is the
   colour every other affordance on the page already uses. */
.tr-qual-cert {
    color: var(--btn-primary-hover-bg);
    font-weight: 600;
    text-decoration: none;
}

.tr-qual-cert:hover {
    text-decoration: underline;
}

.tr-qual-cert:focus-visible {
    outline: 2px solid var(--form-focus-color);
    outline-offset: 2px;
    border-radius: 4px;
}