/*
 * Bootstrap 5 Specific Overrides for LB-APP-BASE
 * This file contains CSS that adapts the application styles for Bootstrap 5
 *
 * Console/bricklet module fully converted to native BS5 classes (April 2026).
 * Remaining compat rules below are for classes still in use outside console/bricklets,
 * or utility classes used by JS (e.g. .hidden).
 */

/* ================================
 * WCAG 2.4.7 Focus Visibility
 * ================================
 */
:focus-visible {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: 2px;
}

.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: 2px;
    box-shadow: none;
}

/* ================================
 * Modernization (2026): Default Typography
 * ================================
 * Source-of-truth for app fonts. Themes can override --app-font-sans /
 * --app-font-display to brand-shift, but a non-themed campus inherits
 * the Performance baseline by default (Manrope body, Outfit display).
 *
 * Loaded via @import so non-TLN campuses get the fonts without the theme.
 */
@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Manrope:wght@400;500;600&display=swap");

:root {
    --app-font-sans:    "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --app-font-display: "Outfit", "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

    /* Map Bootstrap's body font var to ours so .form-control, .btn, etc. follow */
    --bs-font-sans-serif: var(--app-font-sans);
    --bs-body-font-family: var(--app-font-sans);
}

body {
    font-family: var(--app-font-sans);
    line-height: 1.55;   /* Slightly more breathing room than BS5 default 1.5 */
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-family: var(--app-font-display);
    font-weight: 600;
}

/* Modernized type scale — smaller, denser headings than Bootstrap defaults.
   Aligns with the Performance style guide and modern SaaS dashboards
   (Linear, Stripe, GitHub use similar steeper scales).

   Defaults below for reference vs new:
       BS5 default      Modernized
   h1: 2.5rem  (40px)   1.875rem (30px)   page title
   h2: 2rem    (32px)   1.5rem   (24px)   section title
   h3: 1.75rem (28px)   1.25rem  (20px)
   h4: 1.5rem  (24px)   1.125rem (18px)
   h5: 1.25rem (20px)   1rem     (16px)
   h6: 1rem    (16px)   0.875rem (14px)   small heading / caption */
h1, .h1 { font-size: 1.875rem; }
h2, .h2 { font-size: 1.5rem; }
h3, .h3 { font-size: 1.25rem; }
h4, .h4 { font-size: 1.125rem; }
h5, .h5 { font-size: 1rem; }
h6, .h6 { font-size: 0.875rem; }

/* Card-header sizing — applies regardless of inner markup. Widget JSPs use
   <span class="card-title"> for the title (sometimes alongside indicator
   icons); the .card-header font-size/weight governs the whole row. */
.card-header {
    font-size: 0.875rem;     /* 14px */
    font-weight: 600;
}

/* ============================================================
   Left-hand menu (campus config setting MenuLayout = "left")
   ============================================================
   Vertical rail rendered by leftMenu.xsl. Same data + permissions
   as the top menu, different shape. Theme-driven colors via
   var(--bs-primary). Collapsible to icon-only via .collapsed class
   (toggle button) or below 768px viewport (responsive). */

:root {
    --lb-left-menu-width: 240px;
    --lb-left-menu-collapsed-width: 50px;
}

body.has-left-menu {
    /* Shift main content right to make room for the fixed rail. */
    padding-left: var(--lb-left-menu-width);
    transition: padding-left 200ms ease;
}
body.has-left-menu.left-menu-collapsed {
    padding-left: var(--lb-left-menu-collapsed-width);
}

/* ── Brand override: menu surfaces ───────────────────────────────────
   The menu surface (top nav strip .banner-navbar + left rail .lb-left-menu)
   is painted from --tln-menu-bg / --tln-menu-fg, which the active theme
   sets at :root (only `default` and `bumblebee` do; other themes leave
   them unset so the consumers below fall through to --bs-primary).

   A campus brand override (BrandMenuBg / BrandMenuFg) is layered on by
   brand_overrides.jsp, which RE-EMITS --tln-menu-bg / --tln-menu-fg at
   :root. Because brand_overrides.jsp is the last stylesheet loaded (after
   the theme CSS and this file — see ui.jsp), its :root value wins over
   the theme's via normal cascade order. No per-element re-scoping rule is
   used here: an earlier attempt redefined --tln-menu-bg ON these elements
   as `var(--lb-menu-bg, var(--tln-menu-bg))`, but that self-references
   --tln-menu-bg in its own fallback, which the CSS Custom Properties spec
   treats as a cycle (invalid-at-computed-value-time) — so the consumers
   fell through to --bs-primary and bumblebee's yellow menu rendered black.

   --tln-menu-bg is read ONLY by .banner-navbar / .lb-left-menu rules in
   this file, so re-emitting it at :root affects only the menu surfaces.
   .lb-banner (the logo strip above the menu) reads --tln-header-bg, never
   --tln-menu-bg, so it stays on the BrandHeaderBg / theme header cascade
   independently. */

.lb-left-menu {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--lb-left-menu-width);
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    /* Thin custom scrollbar so the rail doesn't shift width when content
       overflows. Firefox uses scrollbar-* properties, WebKit uses pseudos. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
    z-index: 1000;
    transition: width 200ms ease;
    background-color: var(--tln-menu-bg, var(--bs-primary));
    color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
    font-size: 0.875rem;
    /* Reuse BS5 dropdown classes (SmartMenus expects them) but override the
       defaults so nested submenus paint primary-rail-on-light, not white.
       Link colors honor --tln-menu-fg for themes with a light banner bg. */
    --bs-dropdown-bg: transparent;
    --bs-dropdown-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
    --bs-dropdown-border-width: 0;
    --bs-dropdown-border-color: transparent;
    --bs-dropdown-padding-y: 0;
    --bs-dropdown-padding-x: 0;
    --bs-dropdown-font-size: 0.875rem;
    --bs-dropdown-link-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.78));
    --bs-dropdown-link-hover-color: var(--tln-menu-fg, #fff);
    --bs-dropdown-link-hover-bg: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 80%, #000);
    --bs-dropdown-link-active-color: var(--tln-menu-fg, #fff);
    --bs-dropdown-link-active-bg: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 70%, #000);
    --bs-dropdown-divider-bg: rgba(0, 0, 0, 0.15);
}
.lb-left-menu::-webkit-scrollbar {
    width: 6px;
}
.lb-left-menu::-webkit-scrollbar-track {
    background: transparent;
}
.lb-left-menu::-webkit-scrollbar-thumb {
    background-color: rgba(255, 255, 255, 0.25);
    border-radius: 3px;
}
.lb-left-menu::-webkit-scrollbar-thumb:hover {
    background-color: rgba(255, 255, 255, 0.4);
}
.lb-left-menu.collapsed,
body.left-menu-collapsed .lb-left-menu {
    width: var(--lb-left-menu-collapsed-width);
}

/* Header strip: home icon + collapse toggle */
/* Header strip aligns with the top banner row's height so the top-left
   square reads cleanly against the banner. Same token both places, so a
   change to the rail's collapsed width keeps them aligned. */
.lb-left-menu-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--lb-left-menu-collapsed-width);
    padding: 0 1rem;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.lb-left-menu-home,
.lb-left-menu-toggle {
    color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
    background: transparent;
    border: 0;
    font-size: 1.25rem;
    cursor: pointer;
}
.lb-left-menu-home:hover,
.lb-left-menu-toggle:hover {
    color: var(--tln-menu-fg, #fff);
}

/* Top-level nav-link rows */
.lb-left-menu .nav-link {
    display: flex;
    align-items: center;
    color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
    padding: 0.5rem 1rem;
    background: transparent;
    text-decoration: none;
    white-space: nowrap;
}
.lb-left-menu .nav-link:hover,
.lb-left-menu .nav-link:focus {
    color: var(--tln-menu-fg, #fff);
    background-color: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 80%, #000);
}
.lb-left-menu .nav-link i.bi {
    flex-shrink: 0;
    width: 1.25rem;
    font-size: 1rem;
}

/* Collapsed-state: hide labels visually but keep them in the a11y tree
   (BS5 .visually-hidden pattern, inlined). Screen readers still announce them. */
.lb-left-menu.collapsed .menu-label,
body.left-menu-collapsed .lb-left-menu .menu-label {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* Rail tooltips are useful only in icon-only (collapsed) state. When
   expanded, the menu labels are visible so a popup of the same text is
   noise. Marker class lb-rail-tooltip is set on the popup template at
   init (frameset_include.jsp), so this rule scopes precisely to those
   popups — never affects tooltips on other parts of the page. */
body:not(.left-menu-collapsed) .lb-rail-tooltip {
    display: none !important;
}

/* Collapsed: only the hamburger fits comfortably at 50px — hide the
   header-strip home icon (a list-row Home shows in the menu instead). */
body.left-menu-collapsed .lb-left-menu-home {
    display: none;
}
body.left-menu-collapsed .lb-left-menu-header {
    justify-content: center;
}
/* Expanded: hide the list-row Home (the header-strip home is showing). */
body:not(.left-menu-collapsed) .lb-left-menu-home-row {
    display: none;
}
.lb-left-menu.collapsed .nav-link,
body.left-menu-collapsed .lb-left-menu .nav-link {
    justify-content: center;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}
.lb-left-menu.collapsed .nav-link i.bi,
body.left-menu-collapsed .lb-left-menu .nav-link i.bi {
    margin: 0 !important;
}
/* SmartMenus5 BS5 addon draws a rounded-square border around the +/- glyph
   on collapsible menus (.navbar-nav.sm-collapsible .sub-arrow, see
   jquery.smartmenus.bootstrap-5.css). Drop the chrome so it's the glyph
   alone — applies to both the left rail (sm-vertical) and the top menu's
   collapsed (hamburger-expanded) mode. Specificity must beat the addon's
   bare `.navbar-nav.sm-collapsible .sub-arrow` rule. */
.lb-left-menu .navbar-nav.sm-collapsible .sub-arrow,
.banner-navbar .navbar-nav.sm-collapsible .sub-arrow {
    border: 0;
    background: transparent;
}

/* Collapsed rail has no room for the +/- caret on parent items, and clicking
   the icon already expands the rail — hide the caret in collapsed state. */
.lb-left-menu.collapsed .nav-link .sub-arrow,
body.left-menu-collapsed .lb-left-menu .nav-link .sub-arrow {
    display: none !important;
}
/* Without the caret, drop SmartMenus' reserved right-padding too. */
.lb-left-menu.collapsed .has-submenu,
body.left-menu-collapsed .lb-left-menu .has-submenu {
    padding-right: 0.5rem !important;
}

/* Nested submenu — flat vertical (SmartMenus collapsible), not a floating popover.
   BS5 sets --bs-dropdown-* on .dropdown-menu itself, beating the inherited values
   from .lb-left-menu. Override the vars at this scope so they propagate to items.
   SmartMenus toggles display:block/none. Wrap content in a grid row that animates
   from 0fr → 1fr for a smooth slide-down without measuring heights in JS. */
.lb-left-menu .dropdown-menu {
    position: static !important;
    transform: none !important;
    float: none;
    border: 0;
    margin: 0;
    box-shadow: none;
    --bs-dropdown-bg: transparent;
    --bs-dropdown-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
    --bs-dropdown-padding-y: 0;
    --bs-dropdown-padding-x: 0;
    --bs-dropdown-font-size: 0.875rem;
    --bs-dropdown-link-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.78));
    --bs-dropdown-link-hover-color: var(--tln-menu-fg, #fff);
    --bs-dropdown-link-hover-bg: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 80%, #000);
    --bs-dropdown-link-active-color: var(--tln-menu-fg, #fff);
    --bs-dropdown-link-active-bg: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 70%, #000);
    --bs-dropdown-divider-bg: rgba(0, 0, 0, 0.15);
}
.lb-left-menu .dropdown-item {
    padding: 0.4rem 1rem 0.4rem 3rem;
    white-space: normal;
}
/* SmartMenus tags the "current" item with .highlighted plus BS utilities
   (bg-light/text-dark). Override so the current item stays in our palette. */
.lb-left-menu .dropdown-item.highlighted,
.lb-left-menu .dropdown-item.bg-light,
.lb-left-menu .nav-link.highlighted,
.lb-left-menu .nav-link.bg-light {
    background-color: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 70%, #000) !important;
    color: var(--tln-menu-fg, #fff) !important;
}
.lb-left-menu .dropdown-item.text-dark,
.lb-left-menu .nav-link.text-dark {
    color: var(--tln-menu-fg, #fff) !important;
}
.lb-left-menu.collapsed .dropdown-menu,
body.left-menu-collapsed .lb-left-menu .dropdown-menu {
    /* Hide nested submenus when rail is collapsed — only icons show. */
    display: none !important;
}

/* Responsive: below 768px the JS adds .left-menu-collapsed to <body>
   (frameset_include.jsp uses matchMedia). All collapsed-state styling
   lives on body.left-menu-collapsed selectors above. */

/* Below sm (<576px): on phones, hide the left rail entirely and let the
   top-menu hamburger nav take over. Even the 50px collapsed rail steals
   ~13% of a 390px viewport, and a phone has no use for a permanently
   visible side rail. Tablets (>=576px) keep the rail (collapsed to 50px
   at <768px via the existing JS matchMedia handler). menu_include.jsp
   renders the top navbar AND the left rail when MenuLayout=left; CSS
   swaps which one is visible at this breakpoint. The .lb-mobile-only-nav
   class is added to the top navbar wrapper when in left-menu mode so it's
   hidden above this breakpoint. */
@media (max-width: 575.98px) {
    body.has-left-menu,
    body.has-left-menu.left-menu-collapsed {
        padding-left: 0;
    }
    body.has-left-menu .lb-left-menu {
        display: none;
    }
    .lb-mobile-only-nav {
        display: block;
    }
}
@media (min-width: 576px) {
    /* Above sm: top navbar in left-menu mode is hidden; the rail takes over. */
    .lb-mobile-only-nav {
        display: none;
    }
}

/* Headless widget — admin opted out of the panel header per-instance via
   pc_consolewidget.headless. The console renderer adds .widget-headless on
   the slot wrapper (and widget.jsp adds it to the standalone container).
   Hides the header (title, chevron, header-embedded controls). The card
   body becomes the full visual surface; top accent stripe from
   .border-{style} still shows. */
.widget-headless .card-header {
    display: none;
}

/* App menu strip (frameset top navigation).
   Markup uses .navbar.navbar-dark for white text; this rule fills in the
   background color from the theme's secondary token. Themes that want a
   different menu color should override --bs-secondary (preferred) or
   .banner-navbar's background-color directly.

   Themes that want a LIGHT menu strip need to swap navbar-dark -> navbar-light
   in markup AND override these rules — see plan for the override pattern. */
.banner-navbar {
    background-color: var(--tln-menu-bg, var(--bs-primary));
    /* Feed BS5's own navbar CSS vars from --tln-menu-fg so the built-in
       .navbar-nav .nav-link { color: var(--bs-navbar-color) } rule resolves
       to the theme's banner foreground. Fallback values match navbar-dark
       defaults so unchanged themes look the same. */
    --bs-navbar-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.55));
    --bs-navbar-hover-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.75));
    --bs-navbar-active-color: var(--tln-menu-fg, #fff);
    --bs-navbar-brand-color: var(--tln-menu-fg, #fff);
    --bs-navbar-brand-hover-color: var(--tln-menu-fg, #fff);
    --bs-navbar-disabled-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.25));
}

/* navbar-dark defaults are intentionally dim (white@55%/75% for hierarchy on
   dedicated dark navbars). For Performance's app menu strip, links are the
   primary navigation surface — full white reads better.
   --tln-menu-fg lets themes override the link color when --tln-menu-bg is
   a light color (e.g. bumblebee yellow needs dark text). */
.banner-navbar .nav-link {
    color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
    background-color: transparent;
    font-size: 0.875rem;   /* 14px — was 1rem (16px) */
}
.banner-navbar .nav-link:hover,
.banner-navbar .nav-link:focus {
    color: var(--tln-menu-fg, #fff);
    background-color: transparent;
}
/* Active / dropdown-open parent items: keep transparent bg (SmartMenus/BS5
   default would paint a light bg on .show or aria-expanded=true, which flashes
   white against the dark strip). */
.banner-navbar .nav-link.active,
.banner-navbar .nav-link.show,
.banner-navbar .nav-item.show > .nav-link,
.banner-navbar .nav-link[aria-expanded="true"] {
    color: var(--tln-menu-fg, #fff);
    background-color: transparent;
}

/* SmartMenus5 adds .bg-light + .highlighted to active parents on hover/open.
   .bg-light is a BS utility with !important — must override with !important.
   TOP-STRIP parent items only (direct children of the navbar's nav list) —
   darken from --tln-menu-bg so the highlight matches the strip color.
   Parent items INSIDE the popout panel are handled below by the dropdown's
   own --tln-menu-dropdown-bg, so they darken from the panel color (e.g.
   white -> grey on bumblebee) instead of the banner color (yellow). */
.banner-navbar > .container-fluid > .navbar-collapse > .navbar-nav > li > .nav-link.bg-light,
.banner-navbar > .container-fluid > .navbar-collapse > .navbar-nav > li > .nav-link.highlighted {
    background-color: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 80%, #000) !important;
    color: var(--tln-menu-fg, #fff) !important;
}

/* Parent items inside the popout panel: darken from the dropdown panel
   color so a yellow-banner / white-panel theme like bumblebee gets a
   grey highlight on nested parents (matching leaf-item hover), not
   a yellow highlight that bleeds the banner color into the panel. */
.banner-navbar .dropdown-menu .nav-link.bg-light,
.banner-navbar .dropdown-menu .nav-link.highlighted,
.banner-navbar .dropdown-menu .dropdown-item.bg-light,
.banner-navbar .dropdown-menu .dropdown-item.highlighted {
    background-color: color-mix(in srgb, var(--tln-menu-dropdown-bg, var(--tln-menu-bg, var(--bs-primary))) 80%, #000) !important;
    color: var(--tln-menu-dropdown-fg, var(--tln-menu-fg, #fff)) !important;
}

/* Top-banner dropdown (children) bg/fg.
   Independent variables so a theme can make the popout panel a different
   palette than the banner strip itself — e.g. bumblebee keeps a yellow
   banner but pops white-bg dark-text children for high contrast, the way
   the BS3 theme always did. Defaults chain: dropdown-bg falls through to
   --tln-menu-bg, which falls through to --bs-primary; dropdown-fg falls
   through to --tln-menu-fg, which falls through to white. */
.banner-navbar .dropdown-menu {
    --bs-dropdown-bg: var(--tln-menu-dropdown-bg, var(--tln-menu-bg, var(--bs-primary)));
    --bs-dropdown-color: var(--tln-menu-dropdown-fg, var(--tln-menu-fg, rgba(255, 255, 255, 0.92)));
    --bs-dropdown-link-color: var(--tln-menu-dropdown-fg, var(--tln-menu-fg, rgba(255, 255, 255, 0.92)));
    /* Hover/active darken the ACTUAL surface (--bs-dropdown-bg), not a fixed
       reference. White panels → gray hover; yellow banner panels → dark-yellow
       hover. Single formula, follows whatever surface the current menu inherits. */
    --bs-dropdown-link-hover-color: var(--tln-menu-dropdown-fg, var(--tln-menu-fg, #fff));
    --bs-dropdown-link-hover-bg: color-mix(in srgb, var(--bs-dropdown-bg) 80%, #000);
    --bs-dropdown-link-active-color: var(--tln-menu-dropdown-fg, var(--tln-menu-fg, #fff));
    --bs-dropdown-link-active-bg: color-mix(in srgb, var(--bs-dropdown-bg) 70%, #000);
    --bs-dropdown-border-color: color-mix(in srgb, var(--tln-menu-dropdown-bg, var(--tln-menu-bg, var(--bs-primary))) 80%, #000);
    --bs-dropdown-divider-bg: color-mix(in srgb, var(--tln-menu-dropdown-bg, var(--tln-menu-bg, var(--bs-primary))) 70%, #000);
    --bs-dropdown-header-color: var(--tln-menu-dropdown-fg, var(--tln-menu-fg, rgba(255, 255, 255, 0.7)));
    --bs-dropdown-font-size: 0.875rem;   /* 14px to match top-nav links */

    /* Direction of the slide animation. Set by JS at runtime on top-level
       dropdowns only. Nested submenus stay at 0 → just fade. */
    --menu-slide-distance: 0px;

    /* Fade-in/out + directional slide. CSS transitions don't fire on
       display:none→block, so @starting-style defines the "just appeared"
       state for the open. SmartMenus writes inline opacity:0 during its
       fade-out — we catch that to trigger a parallel slide via transform.
       Matches SmartMenus's default hideDuration (200ms) for symmetry. */
    transition: opacity 200ms ease-out, transform 200ms ease-out;
    opacity: 1;
    transform: translateX(0);
}
/* Slide direction during fade-out. Top-level dropdowns get a non-zero
   distance from JS; nested submenus inherit the default 0 → fade only. */
.banner-navbar .dropdown-menu[style*="opacity: 0"] {
    transform: translateX(var(--menu-slide-distance));
}
/* Slide direction during fade-in. Same variable, so nested submenus fade
   without horizontal movement. */
@starting-style {
    .banner-navbar .dropdown-menu {
        opacity: 0;
        transform: translateX(var(--menu-slide-distance));
    }
}

@media (prefers-reduced-motion: reduce) {
    .banner-navbar .dropdown-menu {
        transition: none;
        transform: none;
    }
}

/* Below the navbar-expand-md breakpoint the top menu collapses to a vertical
   stack inside the hamburger panel. In that mode, nested dropdowns should
   inherit the banner color all the way down (matches BS3 behavior) — not
   the light popover surface (--tln-menu-dropdown-bg) used in expanded mode. */
@media (max-width: 767.98px) {
    .banner-navbar .dropdown-menu {
        --bs-dropdown-bg: var(--tln-menu-bg, var(--bs-primary));
        --bs-dropdown-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
        --bs-dropdown-link-color: var(--tln-menu-fg, rgba(255, 255, 255, 0.92));
        --bs-dropdown-link-hover-color: var(--tln-menu-fg, #fff);
        --bs-dropdown-link-hover-bg: color-mix(in srgb, var(--bs-dropdown-bg) 80%, #000);
        --bs-dropdown-link-active-color: var(--tln-menu-fg, #fff);
        --bs-dropdown-link-active-bg: color-mix(in srgb, var(--bs-dropdown-bg) 70%, #000);
        --bs-dropdown-border-color: transparent;
        --bs-dropdown-divider-bg: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 70%, #000);
    }
    /* SmartMenus current-item highlight: in collapsed hamburger mode, the
       nested dropdown panel inherits banner bg (rules above), so the existing
       .dropdown-menu .nav-link.highlighted override (which uses
       --tln-menu-dropdown-bg) lands on the wrong color anchor. Override here
       so highlight darkens from banner-bg, matching the panel itself. */
    .banner-navbar .dropdown-menu .nav-link.bg-light,
    .banner-navbar .dropdown-menu .nav-link.highlighted,
    .banner-navbar .dropdown-menu .dropdown-item.bg-light,
    .banner-navbar .dropdown-menu .dropdown-item.highlighted {
        background-color: color-mix(in srgb, var(--tln-menu-bg, var(--bs-primary)) 70%, #000) !important;
        color: var(--tln-menu-fg, #fff) !important;
    }
}

/* User-dropdown info header (username + version), shown above the menu items
   in the user popover. Two short lines, muted, non-interactive. */
.lb-banner-user-info {
    line-height: 1.3;
    white-space: normal;
    font-size: 0.75rem;
}

/* Guest/external users have no user dropdown, so banner_include.jsp emits a bare
   standalone build-version string instead (.lb-banner-user-info-external, in the
   iuserIsExternal branch = the guest-login flag), gated by the ShowServerInfoInBanner
   campus config. A logged-out guest has no reason to see the build version, so
   hide it. display:none KEEPS it in the DOM, so the build is still readable by
   inspecting. The logged-in version (inside the dropdown popover,
   .lb-banner-user-info above) is unaffected. (PERF-14937) */
.lb-banner-user-info-external {
    display: none;
}

/* Banner button cluster. The overall band height still tracks the collapsed
   left-rail width (via the pill height + margins below), so the top-left corner
   stays aligned with the rail. */
.lb-banner-row {
    /* Inset the pill vertically: reduce its height by the top+bottom margins so
       the band still totals the collapsed-rail width (38px pill + 0.375rem x2),
       leaving a sliver of header above and below so all four corners round
       instead of the pill sitting flush against the window top. */
    min-height: calc(var(--lb-left-menu-collapsed-width) - 0.75rem);
    margin-top: 0.375rem;
    margin-bottom: 0.375rem;
    padding-top: 0;
    padding-bottom: 0;
    /* Opt-in rounded backing behind the banner action buttons, for campuses
       whose custom header image makes the buttons hard to read. Hugs its
       buttons (fit-content, pushed to the right edge) instead of filling the
       column as a square. Background is the SAME menu colour as the nav strip
       (.banner-navbar) at --lb-banner-bg-opacity, which defaults to 0 = fully
       transparent, so unbranded campuses see nothing. Text colour is unchanged
       (the .lb-banner rule sets --tln-header-fg); a campus that turns the pill
       dark pairs it with Header Text in the branding editor. */
    width: fit-content;
    max-width: 100%;
    margin-left: auto;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    border-radius: var(--bs-border-radius, 0.375rem);
    background-color: color-mix(in srgb,
        var(--tln-menu-bg, var(--bs-primary)) calc(var(--lb-banner-bg-opacity, 0) * 100%),
        transparent);
}

/* Banner background color is opt-in per theme via --tln-menu-bg. Themes
   that set it win; themes that don't fall back to --bs-primary, which is
   what existing branded themes (TLN, etc.) want. Default theme picks a
   calm slate so the unbranded look isn't the BS5 default-blue. */
.banner-navbar {
    background-color: var(--tln-menu-bg, var(--bs-primary));
}
/* Banner row child wrappers — legacy padding from the old multi-row layout
   pushed elements vertically. Reset so flex centering aligns them cleanly.
   Also re-assert the header text colour directly on these wrappers (not just
   inherited from .lb-banner) so a campus's old per-element custom CSS — e.g.
   Daimler's server-side `.lb-banner-user { color:#444 }` — can't override the
   branded Header Text. A direct child rule beats an inherited value even when
   the parent is !important, so the only way to win without editing their
   branding files is to match these elements directly at higher specificity
   (0,0,2,0 here vs the custom rule's 0,0,1,0). No !important needed. */
.lb-banner-row .lb-banner-links,
.lb-banner-row .lb-banner-links-user,
.lb-banner-row .lb-banner-user {
    padding: 0;
    color: var(--tln-header-fg, inherit);
}
/* Banner action affordances (Messages / Search / Help / Log Off). Plain
   icon+label links, no BS5 button machinery. Theme-agnostic - inherits
   the banner row's text color and stays transparent against whatever
   background the theme provides for the banner. */
.lb-banner-action {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.25rem 0.5rem;
    border-radius: 0.375rem;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    background-color: transparent;
    border: 0;
    transition: background-color 120ms ease;
}
.lb-banner-action:hover,
.lb-banner-action:focus-visible {
    background-color: rgba(0, 0, 0, 0.05);
    color: inherit;
    text-decoration: none;
    outline: none;
}
/* Banner-row icons: uniform sizing + softening across all icon fonts. Applies
   to both icons-only mode (banner-btn-image-div) and the user-name person
   icon. Theme-agnostic — was previously in tln-light theme as component
   chrome, moved here so every theme renders the banner identically. */
.lb-banner .banner-btn-icon,
.lb-banner .banner-btn-icon-lg,
.lb-banner-user > .bi {
    font-weight: 300;
    line-height: 1;
    opacity: 0.62;
    transition: opacity 120ms ease;
}
/* Person icon is in a different container (no banner-btn-image-div wrapper)
   and renders ~5px higher than the action icons. Nudge to align. */
.lb-banner-user > .bi-person {
    position: relative;
    top: 1px;
}
/* Center the icon glyph in its box. Measured: the bi glyph's rendered box is
   taller than its container and, in a display:block div, rides the TOP (glyph
   center ~2.4px above the label center) instead of centering. Making the icon
   div a flex box distributes that overflow evenly so the glyph centers and
   lines up with the label. Covers both the labelled buttons and icons-only mode. */
.lb-banner .banner-btn-image-div {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.lb-banner .banner-btn-image-div:hover .banner-btn-icon,
.lb-banner .banner-btn-image-div:hover .banner-btn-icon-lg,
.lb-banner .banner-button:hover .banner-btn-icon,
.lb-banner .banner-button:focus .banner-btn-icon,
.lb-banner-user:hover > .bi {
    opacity: 0.85;
}

/* Link underline policy.
   WCAG 1.4.1 requires non-color cues for links *within blocks of text* (so
   colorblind users don't miss them). It does NOT require underlines on
   lists of explicit links — sidebars, widget link rows, nav items, etc.
   are already understood as links by position and context.

   Default for widget/card links: no underline at rest, underline on hover.
   Body text links inside paragraphs keep the always-on underline. */
.card a,
.card-body a,
.list-group-item a {
    text-decoration: none;
}
.card a:hover,
.card a:focus-visible,
.card-body a:hover,
.card-body a:focus-visible,
.list-group-item a:hover,
.list-group-item a:focus-visible {
    text-decoration: underline;
}

/* Re-enable always-underline for links inside body-text contexts (paragraphs,
   help text, descriptions). These ARE blocks of text so WCAG applies. */
.card-body p a,
.card-body .text-body a,
.card-body .lb-text a,
.text-body a {
    text-decoration: underline;
}

/* Restore readable rest-state text on .list-group-item-action.
   Bootstrap 5.3.4+ regressed --bs-list-group-action-color to a very muted
   secondary-color, so enabled actionable rows look indistinguishable from
   .disabled ones until you hover. Pin the rest color to the plain
   list-group-item color (body text) - hover/active/focus rules in BS5
   stay intact and still flip background/contrast on interaction.
   Upstream tracking: https://github.com/twbs/bootstrap/issues/41725 */
.list-group-item-action {
    --bs-list-group-action-color: var(--bs-list-group-color);
}

/* ================================
 * Remaining Compatibility Rules
 * These are still referenced in converted files, by JS, or from DB-stored content
 * ================================
 */

/* Badge default background — BS3 .badge had gray bg (#777) by default, BS5 has none.
   Restores visibility for bare .badge and custom badge classes (badge-circle-sm, scorecard-badge, etc.).
   Pulls from theme secondary so each theme's badges match its own palette. */
.badge:not([class*="bg-"]) {
    background-color: var(--bs-secondary, #6c757d);
}

/* Filter panel spacing */
.panel-filter {
    margin-bottom: 10px;
}
.panel-filter.filter-child,
.panel-filter.filter-child-last {
    margin-bottom: 0 !important;
    border-top: none;
}

/* btn-block: removed in BS5, but used in DB-stored HTML content (w2_html_grid_1, etc) */
/* Need !important to override BS5's .btn { display: inline-block } */
.btn-block {
    display: block !important;
    width: 100% !important;
}

/* btn-default: renamed to btn-secondary in BS5, but used in DB-stored HTML content.
   Pulls from theme --bs-secondary so the button matches each theme's palette.
   Hover/active states use color-mix shades of the same base. */
.btn-default {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--bs-secondary, #6c757d);
    --bs-btn-border-color: var(--bs-secondary, #6c757d);
    --bs-btn-hover-color: #fff;
    /* Hover shade — bigger delta so it's visibly different on dark theme secondaries.
       Was 85%/80% (~7% darker, barely perceptible on #555756). */
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-secondary, #6c757d) 70%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-secondary, #6c757d) 65%, #000);
    --bs-btn-focus-shadow-rgb: var(--bs-secondary-rgb, 130,138,145);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-secondary, #6c757d) 60%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-secondary, #6c757d) 55%, #000);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--bs-secondary, #6c757d);
    --bs-btn-disabled-border-color: var(--bs-secondary, #6c757d);
}

/* .hidden is used by JS for dynamic show/hide — not a BS3 responsive class */
.hidden {
    display: none !important;
}

/* Visible utilities — still referenced in 1 bricklet */
.visible-xs,
.visible-sm,
.visible-md,
.visible-lg {
    display: none !important;
}

@media (max-width: 575.98px) {
    .visible-xs {
        display: block !important;
    }
}

@media (min-width: 576px) and (max-width: 767.98px) {
    .visible-sm {
        display: block !important;
    }
}

@media (min-width: 768px) and (max-width: 991.98px) {
    .visible-md {
        display: block !important;
    }
}

@media (min-width: 992px) {
    .visible-lg {
        display: block !important;
    }
}

/* Well component — still referenced in 7 bricklets */
.well {
    min-height: 20px;
    padding: 19px;
    margin-bottom: 20px;
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, 0.375rem);
}

.well-sm {
    padding: 9px;
    border-radius: var(--bs-border-radius-sm, 0.25rem);
}

.well-lg {
    padding: 24px;
    border-radius: var(--bs-border-radius-lg, 0.5rem);
}

/* ================================
 * Dashboard card border colors
 * Preserves BS3 panel look where the entire widget had colored borders
 * ================================
 */
/* PERF-14831: the old BS3 "default" panel style is normalized to "none" at
   the JSP read site for every bricklet, so .text-bg-default / .border-default
   should never be emitted anymore. The aliases that used to live here (mapping
   default to secondary) caused confusion because the .card-dshbd block below
   then overrode the alias on dashboard widgets, leaving a neutral hairline
   instead of the secondary color. Cleaner to make "default" a no-op upstream
   and have nothing claim those classes here. */


/* App-level card shadow token. Light-mode default; dark-mode themes should
   set this to `none` (or a subtle inner border) since shadows don't work
   on dark backgrounds. */
:root {
    --app-card-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 4px 10px rgba(0,0,0,0.06);
}

/* Header strip above the menu (logo, user dropdown). Defaults to
   transparent so the underlying theme / page surface (--bs-body-bg)
   shows through instead of forcing an opaque white block over themes
   that colour the header via another surface. Set --tln-header-bg
   (theme :root or brand_overrides.jsp from BrandHeaderBg) to paint it.
   The companion --tln-header-fg drives the text/icon colour on top of
   that surface (defaults to black so an unbranded light header reads
   correctly; brand_overrides.jsp emits a contrast-derived value when
   BrandHeaderBg is set so a dark header gets light text automatically). */
.lb-banner,
#topFrame {
    background-color: var(--tln-header-bg, transparent);
}
.lb-banner {
    color: var(--tln-header-fg, var(--bs-body-color, #000));
}

/* Brand block in the .lb-banner cell (left side of the banner row).
   topFrame_include.jsp builds this composite when BrandingAdminEnabled
   is on so the brand can show:
     • logo image only (BrandLogoUrl set), or
     • app-name text only (no logo, BrandAppName set), or
     • either of the above with BrandTagline below.
   When neither logo nor name is set, the original LTBOOrganizationBanner
   HTML renders here instead and these classes are absent. */
.lb-brand-block {
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* Pin children to their natural cross-axis (width) size so the
       logo image and tagline span don't stretch to fill the banner
       column — without this, an SVG logo with width:auto + max-height
       (set by brand_overrides.jsp) gets stretched full-cell-width,
       collapsing the visual stack with the tagline. flex-start keeps
       both items left-aligned to match the bp-logo-area placement in
       the admin's live preview. */
    align-items: flex-start;
    gap: 2px;
    line-height: 1.15;
    /* Fill the banner cell vertically so the brand block aligns with
       the action buttons on the right (which use align-items: center
       inside .lb-banner-row). BS5 .row stretches its cols to row
       height, so height: 100% picks up the tallest sibling's height. */
    height: 100%;
    /* Inherit the banner's text color (--tln-header-fg) so brand text
       stays legible against whatever header bg is in effect. */
    color: inherit;
}
.lb-brand-name {
    /* Block so the optional tagline always wraps below, never trails
       on the same baseline as the name. */
    display: block;
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.1;
    color: inherit;
}
.lb-brand-tagline {
    /* Block so the tagline sits on its own line directly under the
       brand name or logo, matching the bp-tagline placement in the
       admin's live preview. */
    display: block;
    /* Smaller than the 1.25rem brand name so the tagline reads as a
       sub-label, not a competing wordmark, but large enough to stay
       legible whether it sits below or beside the logo. */
    font-size: 0.75rem;
    line-height: 1.2;
    /* Muted shade of the inherited header text colour — works equally
       well on a light or dark header without picking specific RGB. */
    opacity: 0.7;
    color: inherit;
}
/* Breathing room beneath the tagline when it stacks UNDER the logo / name
   (default "below" layout) so it isn't crammed against the menu strip. The
   banner grows to accommodate it (the menu sits a touch lower) — acceptable
   per design. Not applied in the side-by-side "right" layout. */
.lb-brand-block:not(.lb-brand-tagline-right) .lb-brand-tagline {
    padding-bottom: 6px;
}
/* When .lb-brand-block contains a logo image, the image already has its
   own height cap (set inline by brand_overrides.jsp from BrandLogoMaxHeight).
   The block just needs to align the optional tagline below it without
   adding extra vertical chrome. */
.lb-brand-block .lb-brand-logo {
    display: block;
}

/* Logo bottom gap so a logo sized to the row height isn't flush against the
   menu strip below. Covers both logo paths:
     - legacy org-banner logo (img.banner-img) - top-aligned, fills the row
     - Branding (Beta) brand logo (.lb-brand-logo) - can be sized large too
   Big logos are still allowed to grow the banner; they just keep this gap.
   Scoped to logo images, so text-only headers (no logo image) keep the square. */
.lb-banner .banner-img,
.lb-banner .lb-brand-logo {
    margin-bottom: 0.4rem;
}

/* Optional "tagline to the right of the logo" layout — opt in via
   BrandTaglinePosition=right (topFrame_include.jsp adds this modifier).
   Lay the logo / name + tagline out as a centered row instead of the
   default stacked column. */
.lb-brand-block.lb-brand-tagline-right {
    flex-direction: row;
    align-items: center;
    /* Base .lb-brand-block sets justify-content: center for VERTICAL
       centering in its default column layout. In row layout that same
       property centers horizontally, floating the logo/name toward the
       middle — pin it to the start so the brand stays left-aligned. */
    justify-content: flex-start;
    gap: 10px;
}
/* Narrow banner: drop the tagline back under the logo (the default "below"
   layout) so it never crowds the logo / name on small screens. Uses the
   same md breakpoint as the rest of the banner/menu responsive rules. The
   tagline keeps the same font size in both layouts. */
@media (max-width: 767.98px) {
    .lb-brand-block.lb-brand-tagline-right {
        flex-direction: column;
        align-items: flex-start;
        gap: 2px;
    }
}

/* ============================================================
   Dashboard Filters Panel
   ============================================================
   Owned 100% by main5.css. BS3 rules were removed from main.css
   (only consumer is dashboard_filters.jsp which is BS5-only).
   Class names match the existing JSP/JS hooks (.filter-panel,
   .panel-filter, .filter-toggle, .filter-edit, .filter-view, etc.)
   so no markup-renaming was needed. */
.filter-panel {
    /* Fixed so the panel doesn't participate in the page's scroll width.
       Previously position:absolute against a parent that didn't clip caused
       the off-screen (translateX(100%)) state to extend document width
       and push the page left/right on narrower viewports. */
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 280px;
    z-index: 100;
    background: rgba(0, 0, 0, 0.3);
    padding: 0.5rem;
    overflow-y: auto;
    transform: translateX(100%);
    opacity: 0;
    font-size: 0.875rem;
}

/* Outer "Filters" header - clickable strip to close the panel.
   The JSP currently lands a .btn-primary class on this header (BS3 era;
   the only thing it did was paint a primary bg). Scope BS5 primary chrome
   to the actual header so the visual lands regardless of the class soup. */
.filter-card-header {
    background-color: var(--bs-primary);
    color: #fff;
    padding: 0.5rem 0.75rem;
    border-radius: 0.25rem 0.25rem 0 0;
    font-weight: 600;
    user-select: none;
}
.filter-card-header .btn {
    /* Reset header buttons since they sit on a primary surface. */
    line-height: 1;
}

.apply-all-div {
    padding: 0.5rem 0;
    text-align: end;
    min-height: 2.25rem;
}

/* Top-level filter cards: direct children of #filterPanel. Each is a
   self-contained card with rounded corners and a small gap between siblings
   so an expanded body doesn't bleed into the next. Filter GROUPS (My Team
   etc) live at this level too - they wrap nested child filters but read as
   one card. Use > to NOT match nested children. */
.filter-panel > .panel-filter {
    margin-bottom: 0.4rem;
    border-radius: 0.375rem;
    background-color: #fff;
    overflow: hidden;
}
.filter-panel > .panel-filter:last-of-type {
    margin-bottom: 0;
}

/* Nested child filters inside a .filter-group (e.g. Organizations under
   My Team). These render inside the parent's body, so they shouldn't carry
   their own border/radius/margin chrome - they're items within the parent
   card, separated by a subtle divider only. */
.filter-panel .panel-filter .panel-filter {
    margin: 0;
    border: 0;
    border-radius: 0;
    border-top: 1px solid var(--bs-border-color);
    background-color: transparent;
}
/* The first nested child sits flush against the group's own card-body
   padding, so it doesn't need a top divider. */
.filter-panel .panel-filter .card-body > .panel-filter:first-child {
    border-top: 0;
}
/* Nested child headers: less weight than the group's outer header, but keep
   horizontal padding so the title and edit-pencil don't sit flush to the edge.
   Reset color/pencil-color too - parent painted them white for the primary
   band; nested headers sit on a white body so they need dark text again. */
.filter-panel .panel-filter .panel-filter > .card-header {
    background-color: transparent;
    color: var(--bs-body-color);
    padding: 0.3rem 0.6rem;
    font-size: 0.8125rem;
    border-bottom: 0;
}
.filter-panel .panel-filter .panel-filter .filter-toggle {
    color: var(--bs-secondary-color);
}
.filter-panel .panel-filter .panel-filter .filter-toggle:hover {
    color: var(--bs-primary);
}
/* Nested child bodies: match the header's horizontal padding so values
   indent under the title rather than touching the parent card's edge. */
.filter-panel .panel-filter .panel-filter > .card-body {
    padding: 0 0.6rem 0.3rem 0.6rem;
}

/* Per-filter card header - title + edit-toggle pencil. Primary-colored band
   matches the BS3 panel-primary > panel-heading look the filters had before
   modernization. BS5 doesn't cascade .border-primary to the header so we
   paint it explicitly. */
.filter-panel .panel-filter > .card-header {
    background-color: var(--bs-primary);
    color: #fff;
    padding: 0.4rem 0.6rem;
    font-size: 0.8125rem;
    font-weight: 600;
    border-bottom: 0;
}
/* Secondary-bordered cards (nested children that ARE top-level here, e.g.
   non-group filters using border-secondary) get a gray band instead. */
.filter-panel > .panel-filter.border-secondary > .card-header {
    background-color: var(--bs-secondary);
    color: #fff;
}
.filter-panel .filter-toggle {
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.875rem;
    line-height: 1;
}
.filter-panel .filter-toggle:hover {
    color: #fff;
}

/* Card body holds either the compact .filter-view (selected bubbles) or
   the expanded .filter-edit (checklist + search). */
.filter-panel .card-body {
    padding: 0.4rem 0.6rem;
    font-size: 0.8125rem;
}

/* Scrollable lists: cap height so a 200-option filter doesn't blow out the panel. */
.filter-choices,
.filter-view {
    max-height: 14rem;
    overflow-y: auto;
    overflow-x: hidden;
}

/* Tighten the inner table used to render checkbox rows. The legacy markup
   uses a <table> per filter; rather than rewrite that to native list markup,
   just kill the table's default cell padding so rows are dense. */
.filter-panel .table-filter,
.filter-panel .filter-view .table {
    margin: 0;
    font-size: 0.8125rem;
}
.filter-panel .table-filter td,
.filter-panel .filter-view td {
    padding: 0.1rem 0.25rem;
    border: 0;
}

/* Form-check rows. BS5 ships .form-check with padding-left:1.5em + the
   input at margin-left:-1.5em - DO NOT zero padding-left or the checkbox
   disappears off-screen. Just tighten vertical rhythm. */
.filter-panel .checkbox-filter {
    margin: 0;
    min-height: auto;
}
.filter-panel .checkbox-filter .form-check-input {
    margin-top: 0.2rem;
}
.filter-panel .checkbox-filter .form-check-label {
    font-size: 0.8125rem;
    line-height: 1.3;
}

/* Search input + clear button: kept tight so the clear icon doesn't visually
   crash into the input. */
.filter-panel .search-filter-group,
.filter-panel .input-group-sm .search-filter {
    font-size: 0.8125rem;
}
.filter-panel .input-group-sm .input-group-text {
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    padding: 0.25rem 0.5rem;
}

/* Selected-value badges shown in the compact .filter-view (default state). */
.filter-bubble {
    display: inline-block;
    max-width: 100%;
    margin: 0.1rem 0.2rem 0.1rem 0;
    font-weight: 500;
    font-size: 0.75rem;
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Apply footer per filter. Right-aligned button, light divider above. */
.filter-panel .panel-footer-filter {
    padding: 0.4rem 0.6rem;
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    border-top: 1px solid var(--bs-border-color);
}

/* Console widget cards — modernized 2026 styling.
   Softer border, slight shadow, larger radius. The colored "status" border
   (border-primary etc) becomes a top-only accent stripe instead of a full
   1px outline, which reads cleaner on a white page.
   Transparent card-cap-bg so the header zone reads as part of the card
   surface (BS5 defaults to a very faint gray which adds visual noise). */
/* Modern card chrome — applies to EVERY .card in the app.
   Stock BS5's .card-header is a light-grey strip with a bottom border. That
   reads "BS4-era control panel" rather than "2026 dashboard card." Soften
   the wrapper (rounded, shadowed, hairline border), make the header sit on
   the same surface as the body (transparent bg, no bottom border), and
   give cards a little more interior breathing room. */
.card {
    --bs-card-cap-bg: transparent;
    border-radius: 10px;
    border-color: rgba(0, 0, 0, 0.08);
    box-shadow: var(--app-card-shadow);
}
.card > .card-header {
    border-bottom: 0;
    --bs-card-cap-padding-y: 0.625rem;
    --bs-card-cap-padding-x: 1.25rem;
}
.card > .card-body {
    --bs-card-spacer-y: 1.25rem;
    --bs-card-spacer-x: 1.25rem;
}

/* Dashboard widget specifics: inter-card gap, header hover affordance.
   The chrome itself now lives on the bare .card rule above. */
.card-dshbd {
    /* Modern dashboard inter-card gap. main.css ships 10px for BS3 compat;
       this BS5-scope rule lifts to 1.25rem (20px) to match the new card
       padding and align with current SaaS dashboard standards. */
    margin-bottom: 1.25rem;
    /* Borderless by default. The base .card rule (above) adds a soft hairline
       suitable for admin/editor pages; dashboard widgets read cleaner with
       the shadow alone, and only show a border when a status color is
       requested (the .border-primary/.border-success/etc rules below).
       Result: style=none, style=default, and any unrecognized style class
       all render the same chrome-less look. Use width-only (NOT shorthand
       `border:0`) so border-style stays solid - the colored-status rules
       below set border-top-width to 3px and an element with border-style:none
       won't paint that width at any size. */
    border-width: 0;
    /* Theme-set widget-card background. A theme may set --lb-card-bg (e.g.
       performance-dark's soft grey radial gradient) to paint all dashboard
       cards; every other theme falls back to the solid --bs-card-bg, so this
       is a no-op for them. */
    background: var(--lb-card-bg, var(--bs-card-bg));
}

/* Subtle hover affordance on widget headers - says "this responds when you
   reach for it" without competing with the accent stripe. */
.card-dshbd > .card-header {
    transition: background-color 120ms ease;
}
.card-dshbd > .card-header:hover {
    background-color: rgba(0, 0, 0, 0.04);
}

/* When the widget body is collapsed, the header alone IS the widget. Make
   the entire header click-to-expand (open state keeps its existing controls
   intact). :has() lets us scope this purely to collapsed cards. */
.card-dshbd:has(> .card-body.collapse:not(.show)) > .card-header {
    cursor: pointer;
}

/* Top-edge status accent. Native border-top approach — color follows the
   rounded-corner curve naturally. The colored top is thicker (3px) for
   emphasis; sides/bottom inherit the soft hairline from .card-dshbd above. */
.card-dshbd.border-primary,
.card-dshbd.border-secondary,
.card-dshbd.border-success,
.card-dshbd.border-danger,
.card-dshbd.border-warning,
.card-dshbd.border-info {
    border-color: rgba(0, 0, 0, 0.08) !important;
    border-top-width: 3px !important;
}
/* Status accent uses the full theme color. */
.card-dshbd.border-primary   { border-top-color: var(--bs-primary)   !important; }
.card-dshbd.border-secondary { border-top-color: var(--bs-secondary) !important; }
.card-dshbd.border-success   { border-top-color: var(--bs-success)   !important; }
.card-dshbd.border-danger    { border-top-color: var(--bs-danger)    !important; }
.card-dshbd.border-warning   { border-top-color: var(--bs-warning)   !important; }
.card-dshbd.border-info      { border-top-color: var(--bs-info)      !important; }

/* Panel Style "No Style" (border-nostyle): the explicit opt-out that must stay
   chrome-less even when the branding editor's Card Top color is set. It already
   escapes the BrandCardTop accent in brand_overrides.jsp (it is not .border-0,
   and its class name contains "border-" so the :not([class*="border-"]) rule
   skips it), but assert borderless here so the contract survives any future
   change to that selector. Distinct from style=none / None Selected, which
   intentionally DO pick up the Card Top accent. */
.card-dshbd.border-nostyle { border-width: 0 !important; }

/* Console widgets must never push past their grid cell boundary. If a widget's
   content (DataTable with long unbreakable tokens, wide images, embedded
   iframes, etc.) is wider than the card body, scroll horizontally inside the
   widget rather than letting it bleed into adjacent widgets. Scoped to widget
   bodies (.card-body-dshbd) so admin/editor page card bodies are unaffected.

   overflow-y is clip (computes to hidden because overflow-x is auto): widget
   bodies must NEVER show a vertical scrollbar. Auto-height widgets grow with
   their content (the absolute grid reflows around them), so the only thing
   clip ever trims is a sub-pixel flex artifact (e.g. a wrapped button sitting
   ~3px taller than its row) that overflow-y:auto would otherwise turn into a
   bogus full scrollbar. Horizontal scroll is preserved via overflow-x:auto. */
.card-body-dshbd {
    overflow-x: auto;
    overflow-y: clip;
}
/* Exception: widgets that pin an explicit body height (w2_facebook_1 height:Npx;
   w2_chart_title_1 / w2_chart_subtitle_1 / w2_title_1 heightStyle) keep a real
   vertical scrollbar, so content taller than that fixed height stays reachable
   instead of being clipped/hidden. */
.card-body-dshbd[style*="height:"] {
    overflow-y: auto;
}

/* Catalog accordion sections (w2_certificationsbycatalog / w2_coursesbycatalog).
   Each catalog is a flat section inside the single widget card - NOT a nested
   .card. Nesting a bordered+shadowed card inside the widget card produced
   doubled edges (inner card border + trailing margin against the widget edge).
   A plain header bar + collapsible body, divided by a hairline, reads cleanly
   and matches other list widgets. */
.lb-catalog-section + .lb-catalog-section {
    border-top: 1px solid var(--bs-border-color);
}
/* Optional full-section colour. A theme sets --lb-catalog-bg / --lb-catalog-fg to
   paint the WHOLE catalog section (heading + body + content text), restoring the
   pre-BS5 solid-colour catalog look (e.g. the performance theme's navy that EVR
   had). Both default to transparent/inherit, so every other theme keeps the
   modern light section untouched. */
.lb-catalog-section {
    background-color: var(--lb-catalog-bg, transparent);
    color: var(--lb-catalog-fg, inherit);
}
/* Cert/course name + status links inside a coloured section follow
   --lb-catalog-fg; unset falls back to the normal link colour so uncoloured
   sections are unchanged. */
.lb-catalog-section a {
    color: var(--lb-catalog-fg, var(--bs-link-color));
}
.lb-catalog-heading {
    padding: 0.5rem 0.75rem;
    font-weight: 600;
    /* Catalog/category heading was inheriting 1rem - identical to the course
       rows below it. Bump so it reads as a section heading (BS3 parity, where
       the catalog title was larger than its rows). Scoped to .lb-catalog-heading
       so only these two catalog widgets are affected. */
    font-size: 1.125rem;   /* 18px */
}
.lb-catalog-heading .icon-catalog {
    color: var(--lb-catalog-fg, var(--bs-secondary-color));
    font-size: 0.875rem;
}
.lb-catalog-body {
    padding: 0.5rem 0.75rem;
}
/* Table inside a catalog body fills it without its default bottom margin
   (that margin + the widget edge was part of the doubled-border look). */
.lb-catalog-body .table {
    margin-bottom: 0;
}
/* A long unbroken course/cert name (e.g. underscore-joined, no spaces) has no
   wrap opportunity and would push the table past the widget edge, giving the
   whole widget a horizontal scrollbar. Let such tokens break so the table fits. */
.lb-catalog-body .table td {
    overflow-wrap: anywhere;
}
/* Open catalog header reads like a BS5 .accordion-button:not(.collapsed): a
   primary tint + inset divider so the expanded catalog is clearly distinct
   (PERF-14744). The open state differs per widget - the certs widget toggles
   .d-none on its body, the courses widget uses BS5 .collapse/.show - so match
   both via :has(). Tint is theme-aware (primary at 10%), not a fixed hex. */
.lb-catalog-section:has(> .catalog-group:not(.d-none)) > .lb-catalog-heading,
.lb-catalog-section:has(> .lb-catalog-body.show) > .lb-catalog-heading {
    /* Coloured sections keep the solid --lb-catalog-bg when open (no tint, exact
       parity); uncoloured themes get the modern primary tint. */
    background-color: var(--lb-catalog-bg, rgba(var(--bs-primary-rgb), 0.1));
    /* The divider between an open heading and its body matches --bs-border-color
       for uncoloured themes, but on a coloured section that grey hairline cuts the
       block in two; bind it to --lb-catalog-bg so it disappears into the colour and
       the open section reads as one solid block (PERF-14927). */
    box-shadow: inset 0 -1px 0 var(--lb-catalog-bg, var(--bs-border-color));
}
/* Hover affordance on the clickable catalog headers (open header keeps its tint -
   the :has rule above is more specific). */
.lb-catalog-heading:hover {
    background-color: var(--lb-catalog-bg, var(--bs-tertiary-bg));
}
/* Table inside a coloured catalog body: BS5 paints the CELLS (not the table
   element) with --bs-table-bg #fff, which would put the section's white text on
   white cells. Bind the cell fill + row borders to --lb-catalog-bg so they
   match the section colour (navy) and the white text is readable; uncoloured
   themes keep the normal white cells / hairline rows via the fallback. */
.lb-catalog-body .table {
    --bs-table-bg: var(--lb-catalog-bg, #ffffff);
    /* Non-link cell text (the "Not Started"/"Expired" status) is painted by BS5's
       --bs-table-color (dark), which overrides the section's inherited white. Bind
       it to --lb-catalog-fg so status text is white on the colour too; the course
       /cert name links already follow --lb-catalog-fg via the .lb-catalog-section a
       rule. Uncoloured themes keep the normal body text colour. */
    --bs-table-color: var(--lb-catalog-fg, var(--bs-body-color));
    --bs-table-border-color: var(--lb-catalog-bg, var(--bs-border-color));
}
/* Round the catalog section block to the card radius so a coloured section
   (e.g. the performance navy) reads as a rounded block instead of squaring off
   the card corners. overflow clips the inner heading/body fill to the radius. */
.lb-catalog-section {
    overflow: hidden;
}
.lb-catalog-section:first-child {
    border-top-left-radius: var(--bs-border-radius, 0.625rem);
    border-top-right-radius: var(--bs-border-radius, 0.625rem);
}
.lb-catalog-section:last-child {
    border-bottom-left-radius: var(--bs-border-radius, 0.625rem);
    border-bottom-right-radius: var(--bs-border-radius, 0.625rem);
}

/* Stacked cards: add vertical rhythm like BS3 panels had by default.
   Scoped to cards that are NOT inside a .row (grids use gutters for spacing). */
:not(.row) > .card:not(:last-child) {
    margin-bottom: 1rem;
}

/* btn-xs: BS3 button size, removed in BS5. Used by action buttons globally via snippets.properties.
   NOTE: bootstrap5-toggle.min.css redefines .btn-xs with direct padding/font-size/line-height
   (and a broken line-height: 0.5), so we have to override with concrete properties — the
   --bs-btn-* CSS vars alone get bypassed. */
.btn-xs {
    --bs-btn-padding-y: 0.125rem;
    --bs-btn-padding-x: 0.5rem;
    --bs-btn-font-size: 0.75rem;
    --bs-btn-border-radius: 0.2rem;
    padding: 0.125rem 0.5rem;
    font-size: 0.75rem;
    line-height: 1.5;
    border-radius: 0.2rem;
}
/* Action button main label: prevent text-wrap and let button grow with the
   wrapper's min-width so labels like "Configure" aren't cut off in BS5. */
.lb-action-btn {
    white-space: nowrap;
    flex: 1 1 auto;
}
/* Restore the BS3 visual divider between the main action button and its dropdown caret.
   In BS5 btn-group, adjacent same-color buttons blend together; this adds a subtle
   separator line that adapts to any theme color. */
.lb-action-btn-group .lb-action-btn-dropdown {
    border-left: 1px solid rgba(0, 0, 0, 0.2);
}

/* BS3 <span class="caret"> inside dropdown buttons produces nothing in BS5.
   Hide it so BS5's own ::after caret (rendered by .dropdown-toggle) can show. */
.dropdown-toggle .caret {
    display: none;
}

/* btn-outline-secondary readability: BS5 ships it with text color = --bs-secondary
   (#6c757d, medium gray), which reads as faded against a white card. BS3's btn-default
   used near-black text (~#333) — much more visible. Override the resting-state text
   color to body color while keeping the secondary border, and let hover invert to
   filled-secondary for the visual cue. */
.btn-outline-secondary {
    --bs-btn-color: var(--bs-body-color, #212529);
}

/* Horizontal-form labels: BS5's .col-form-label is left-aligned and not bold by default,
   but admin/editor JSPs originally used BS3's .control-label which was right-aligned and
   bold. Keep that look for visual continuity in the admin UI. Only applies inside .row
   so vertical-form labels (.form-label) get bold but not the right-align.
   Bold applies at every breakpoint. Right-align only at sm+ where the label sits in its
   narrow column next to the field; at xs the label stacks above the field and should be
   left-aligned (matches BS3 horizontal-form behavior at mobile). */
.row > .col-form-label {
    font-weight: 600;
}
@media (min-width: 576px) {
    .row > .col-form-label {
        text-align: right;
    }
}

/* Vertical/inline form labels: BS5's .form-label is not bold by default, but BS3's
   default <label> styling was. Restore the bold to preserve admin/editor look-and-feel
   and visual labels used as styling hooks (e.g. <span class="form-label">). */
.form-label {
    font-weight: 600;
}

/* Restore the BS3 .row negative-margin gutter cancel where a BS3 .row was dropped
   during migration but the parent .container-fluid's horizontal padding still needs
   to be cancelled out (e.g., custom grid layouts that can't use .row's flex-wrap).
   PERF-14668: console.jsp's #consoleBody used class="row" in BS3 to negate the
   container's 15px padding; in BS5 the class was removed (BS5 .row's flex-wrap
   conflicts with the Masonry grid), leaving an uncompensated 12px on each side. */
.lb-row-cancel {
    margin-left: calc(-0.5 * var(--bs-gutter-x, 1.5rem));
    margin-right: calc(-0.5 * var(--bs-gutter-x, 1.5rem));
}

/* Filter / picker <select>s in widget headers (and similar tight UI spots)
   shouldn't grow indefinitely with the selected option's text. Native
   <select> truncates the closed-state display with `...` when it can't fit;
   the open dropdown still renders the full option name. Apply where a
   <select> sits inline with other header controls and long option text
   would otherwise push the row out of shape. */
.lb-select-truncate {
    max-width: 400px;
}

/* Standard Flatpickr date-input sizing. Every Flatpickr date picker in the
   app uses the .input-group.date convention (with data-input on the text
   field and data-toggle on the calendar button — see appbase rule). Width
   fits long date formats ("Mon May 11 2026") + the calendar icon button,
   with enough headroom for an optional Start/End label addon. max-width:100%
   lets it shrink in narrow containers. No opt-in class needed; the
   convention IS the selector. */
.input-group.date {
    /* Fixed width so every Flatpickr date field is identical regardless of its
       container. max-width:100% was letting them shrink below the date format
       width in narrow columns (squashing to "mm," truncation) - on narrow
       viewports the row should wrap, not crush the input. */
    width: 230px;
    min-width: 230px;
    flex: 0 0 auto;
}

/* Date-range filter: a Start + End .input-group.date pair that adapts to the
   width of its widget/container (NOT the viewport - a widget can be narrow in a
   multi-column console on a wide screen). Pickers sit side by side when there's
   room, wrap to stacked when there isn't, grow to fill a narrow column (full-
   width feel + tap target), and are capped so a wide widget doesn't produce
   oversized date fields. Reusable across any Start/End date filter. */
.lb-date-range {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.lb-date-range > .input-group.date {
    /* Release the global fixed 230px so the picker can grow/fill; min-width:230px
       (inherited from the rule above) still floors it so the date never truncates. */
    width: auto;
    flex: 1 1 230px;
    max-width: 400px;
}

/* Video thumbnail tiles: BS3 sized .thumb-container at 257px, which only allowed 255px
   inside the card's 1px borders — making the 256px thumbnail cover the right border.
   Bump to 258px in BS5 so the thumb fits with both borders visible. */
.thumb-container {
    width: 258px;
}
/* Video thumbnail tiles: thumb-container is 257px wide (sized for BS3 panel-body's tight
   padding). BS5 card-body adds 16px horizontal padding which makes the 256px thumbnail
   overflow on the right. Zero-out horizontal card-body padding so the thumb sits flush
   with the card border, then re-pad the rows beneath the thumb so titles/ratings still
   have breathing room. */
.thumb-container > .card-body {
    padding-left: 0;
    padding-right: 0;
}
.thumb-container > .card-body > .row {
    padding-left: 10px;
    padding-right: 10px;
    margin-left: 0;
    margin-right: 0;
}
.thumb-container > .card-body > .row:first-child {
    padding-left: 0;
    padding-right: 0;
    --bs-gutter-x: 0;
}
.thumb-container > .card-body > .row:first-child > .col {
    padding-left: 0;
    padding-right: 0;
}
/* Round the thumbnail's top corners to match the card's border radius so the
   square image corners don't overlap the rounded card corners. */
.thumb-container .lb-thumb-img {
    border-top-left-radius: var(--bs-card-inner-border-radius, 0.375rem);
    border-top-right-radius: var(--bs-card-inner-border-radius, 0.375rem);
    display: block;
}
.thumb-container > .card-body > .row:first-child .video-title {
    padding-left: 10px;
    padding-right: 10px;
}

/* Modal header: BS3 placed the close button before the title; BS5 expects it after.
   Force the .btn-close to the right regardless of source order, for converted modals. */
.modal-header {
    justify-content: space-between;
}
.modal-header .btn-close {
    order: 1;
    margin-left: auto;
}

/* ================================
 * DataTables Bootstrap 5 adjustments
 * ================================
 */

.tbl-export-controls {
    margin-bottom: 1rem;
}

/* Expanded DataTables Responsive detail (custom formatDetails markup in ui.jsp,
   shared BS3/BS5). Indent the block under the row and give rows BS3-era
   breathing room. */
.details-table {
    margin: 0.25rem 0 0.25rem 1.5rem;
}

.details-row td {
    padding: 0.2rem 0.75rem 0.2rem 0;
    vertical-align: top;
}

/* Label column hugs its text (no forced 30% width) so the value sits right
   next to the label, like BS3. Bold the actual text - it lives in a nested
   <label class="small">, so font-weight on the td alone never reached it. */
.details-label {
    width: 1%;
    white-space: nowrap;
}
.details-label label {
    font-weight: 600;
    color: var(--bs-body-color);
}

.details-data {
    color: var(--bs-body-color);
}

/* BS-agnostic label/value list used inside DataTables Responsive detail rows
   (e.g. w2_course_transcript_1 Details + History cells). Native <dl><dt><dd>
   markup, styled as a 2-column grid: term left, value right. No Bootstrap
   .row/.col dependency, so renders identically on BS3 and BS5 pages. */
.lb-detail-dl {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.5rem;
    row-gap: 0.25rem;
    margin: 0;
    padding: 0;
}
.lb-detail-dl > dt {
    font-weight: 600;
    color: var(--bs-secondary-color, #6c757d);
    margin: 0;
}
.lb-detail-dl > dd {
    margin: 0;
}
/* Visual separator between successive History sessions. */
.lb-detail-dl-sep {
    border-top: 1px solid var(--bs-border-color, #dee2e6);
    padding-top: 0.5rem;
    margin-top: 0.5rem;
}

/* ================================
 * Form Styling
 * ================================
 */

.form-control {
    font-size: 1rem;
}

/* ================================
 * WCAG Accessibility Enhancements
 * ================================
 */

.skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: var(--bs-primary);
    color: white;
    padding: 8px;
    z-index: 100;
}

.skip-link:focus {
    top: 0;
}

a:not(.btn) {
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}

a:not(.btn):hover {
    text-decoration-thickness: 2px;
}

/* ================================
 * Page Header Styling
 * ================================
 */

.page-header {
    padding-bottom: 0.5rem;
    margin: 0.75rem 0 1.25rem;
    border-bottom: 1px solid var(--bs-border-color);
}

/* ================================
 * Section Start Spacing
 * ================================
 */

.section-start {
    margin-top: 1rem;
}

/* ================================
 * Pagination Top
 * ================================
 */

.pagination-top {
    display: flex;
    justify-content: flex-end;
}

/* ================================
 * Development Plan goal info bar
 * ================================ */


/* ================================
 * Login pages (#loginBody)
 * Ported from BS3 tln-light theme which had these styles; the BS5 theme
 * regeneration dropped them. Written theme-agnostically — pulls all colors
 * from BS5 CSS variables so each theme's login page picks up its own palette.
 * See .cursor/rules/themes.mdc for the no-hardcoded-colors rule.
 * ================================ */

#loginBody {
    background-color: var(--bs-tertiary-bg, #f8f9fa);
}

#loginBody .login-panel {
    max-width: 900px;
    margin: 0 auto;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 0;
    overflow: hidden;
    box-shadow: none;
}

/* BS5 dropped Glyphicons, so the BS3 bullet markers in main.css
   (.lb-triangle-bullet / .lb-chevron-bullet  li:before  use the 'Glyphicons
   Halflings' font at codepoints \e250 / \e080) render as tofu boxes on BS5 pages
   - e.g. campusSelection.jsp's campus list (PERF-14887). main5.css loads after
   main.css, so re-point them at plain Unicode glyphs in the inherited font (no
   icon font needed); the BS3 spacing (position/margin/top) still cascades. */
.lb-triangle-bullet li:before {
    content: "\25B8";          /* black right-pointing small triangle */
    font-family: inherit;
}
.lb-chevron-bullet li:before {
    content: "\203A";          /* single right-pointing angle quote (chevron) */
    font-family: inherit;
}

/* Alert icon alignment (PERF-14881). alert_include.jsp's alert is display:flex
   with align-items-start so the icon anchors to the FIRST line of a multi-line
   message. Bootstrap Icons rely on vertical-align to sit on the text baseline,
   which flex ignores, so the glyph floats off the line. A fixed translate nudge
   can't satisfy both cases - it centered the multi-line alert but pushed the
   single-line icon too low. Instead make the icon a one-line-tall (1lh) flex box
   that centers its own glyph: align-self keeps that box on the first text line,
   align-items centers the glyph on it. Correct for single-line AND the first
   line of a list, with no magic offset. Scoped to the flex alert's direct-child
   icon so inline icons and non-flex alerts are untouched. */
.alert.d-flex > .bi {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    height: 1lh;
}

/* Login heading (logo strip on the card). Defaults to transparent so the
   logo sits on the card's surface (white), matching pre-BS5 behaviour where
   .login-heading had no background. Opt in to a coloured/dark heading by
   setting --login-heading-bg — at a theme :root, the campus branding main.css
   (:root { --login-heading-bg: #212529 }), or brand_overrides.jsp. Driving it
   through a variable lets lower-specificity sources override this #id .class
   rule without a specificity fight. */
#loginBody .login-heading {
    background-color: var(--login-heading-bg, transparent);
    border-bottom: 0;
    padding: 1.5rem 2.5rem;
}

#loginBody .login-logo {
    /* Match the pre-BS5 (prod) behaviour: render the campus banner logo at its
       natural size, bounded only by the card width and the product's own logo
       ceiling. The old `height: 48px; max-width: 240px` shrank real logos
       (which run ~72-121px tall and up to ~746px wide) and overrode the inline
       BrandLoginLogoMaxHeight. max-height 120px = the [16,120] cap the branding
       editor already enforces and covers the tallest real logo (121px). */
    max-width: 100%;
    max-height: 120px;
    height: auto;
    width: auto;
}

/* App-name fallback shown in the login heading when no brand logo is set
   (mirrors the portal header's .lb-brand-name text). Sized up from the
   header default so it reads with similar weight to the 48px logo slot. */
#loginBody .login-brand-name {
    font-size: 1.75rem;
    color: var(--login-heading-fg, inherit);
}

/* Branded registration banner (reg_banner.jsp with brandLogo=true, e.g. the
   forgot-password page). Shows the brand logo or app-name text in place of the
   campus banner image, following the same rules as the login page. */
.lb-reg-banner-brand {
    padding: 1.25rem 0;
}
.lb-reg-banner-brand .lb-reg-brand-logo {
    height: 48px;
    max-width: 240px;
    width: auto;
}
.lb-reg-banner-brand .lb-brand-name {
    font-size: 1.75rem;
}

#loginBody .login-panel .card-body {
    padding: 2.5rem 3rem 1.5rem;
}

#loginBody .login-elements {
    max-width: 420px;
    margin: 0 auto;
}

/* Force labels to stack above inputs (vertical form), matching BS3 tln layout.
   Overrides the horizontal form rows the bulk sed produced. */
#loginBody .login-elements .form-label,
#loginBody .login-elements label.col-form-label {
    font-size: 1rem;
    font-weight: 700;
    color: var(--bs-body-color, #212529);
    text-align: left;
    padding-top: 0;
    margin-bottom: 0.35rem;
    width: 100%;
    max-width: 100%;
    flex: 0 0 100%;
    padding-left: 0;
    padding-right: 0;
}

#loginBody .login-elements .row > [class*="col-"] {
    flex: 0 0 100%;
    max-width: 100%;
    padding-left: 0;
    padding-right: 0;
}

#loginBody .login-elements .row.mb-3 {
    margin-bottom: 1rem;
}

#loginBody .login-elements .form-control,
#loginBody .login-elements .input-group-text {
    border-color: var(--bs-border-color, #dee2e6);
    border-radius: 0;
    box-shadow: none;
}

#loginBody .login-elements .input-group-text {
    background-color: var(--bs-body-bg, #fff);
}

#loginBody .login-elements #cmdLogin {
    background-color: var(--bs-primary, #0d6efd);
    border-color: var(--bs-primary, #0d6efd);
    color: #fff;
    padding: 0.5rem 1.5rem;
    font-size: 0.875rem;
}

#loginBody .login-elements #cmdLogin:hover,
#loginBody .login-elements #cmdLogin:focus {
    background-color: color-mix(in srgb, var(--bs-primary, #0d6efd) 85%, #000);
    border-color: color-mix(in srgb, var(--bs-primary, #0d6efd) 85%, #000);
}

#loginBody .login-panel a {
    color: var(--bs-link-color, #0d6efd);
    font-family: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 1rem;
    text-decoration: none;
}

#loginBody .login-panel a:hover,
#loginBody .login-panel a:focus {
    color: var(--bs-link-hover-color, #0a58ca);
    text-decoration: none;
}

/* Footer bar — subtle background, centered inline links. BS3 had this in
   .panel-footer; BS5 uses .card-footer. Keep .login-footer as the hook. */
#loginBody .login-footer,
#loginBody .card-footer.login-footer {
    background-color: var(--bs-secondary-bg, #e9ecef);
    border-top: 1px solid var(--bs-border-color, #dee2e6);
    padding: 0.75rem 2rem;
    text-align: center;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem 1.25rem;
}

#loginBody a.login-footer-link {
    color: var(--bs-link-color, #0d6efd);
    margin: 0;
    font-size: 0.95rem;
    text-decoration: none;
    display: inline-block;
}

#loginBody a.login-footer-link:hover,
#loginBody a.login-footer-link:focus {
    color: var(--bs-link-hover-color, #0a58ca);
}

@media (max-width: 767px) {
    #loginBody .login-panel .card-body {
        padding: 1.5rem 1.25rem 1rem;
    }
    #loginBody .login-heading {
        padding: 1rem 1.25rem;
    }
    #loginBody .login-logo {
        max-width: 200px;
        height: auto;
    }
    #loginBody .login-footer {
        padding: 0.75rem 1rem;
        gap: 0.35rem 0.8rem;
    }
}

/* ================================
 * Main banner / navbar (#banner.banner-navbar)
 *
 * Color/variant decisions live in the JSP (menu_include.jsp) via BS5
 * utility classes (navbar-light / navbar-dark / bg-* ). Each theme's
 * --bs-* variables drive the actual colors. This file only handles
 * app-structural tweaks that aren't about color.
 * ================================ */

/* Tightens BS5's default navbar padding (0.5rem top/bottom) — banner is a
   slim bar, not a hero. Explicit padding reset overrides BS3 main.css's
   `padding-bottom: 0` which leaves the banner top-heavy when BS5 default
   padding supplies only the top.
   (Former top+bottom 1px --bs-border-color separators were removed: that
   light gray hairline read as a white gap on colored menu strips, where
   --bs-border-color stays on the neutral palette rather than the strip
   color. The colored strip separates itself from header and content.) */
#banner.banner-navbar {
    --bs-navbar-padding-y: 0.25rem;
    padding-top: var(--bs-navbar-padding-y);
    padding-bottom: var(--bs-navbar-padding-y);
}
/* BS5's .navbar-toggler paints a thick focus ring (box-shadow 0.25rem) on
   click. Drop the chrome — banner is a slim bar and the ring looks like a
   bug at this size. */
#banner.banner-navbar .navbar-toggler {
    border: 0;
    padding: 0.25rem 0.5rem;
}
#banner.banner-navbar .navbar-toggler:focus,
#banner.banner-navbar .navbar-toggler:focus-visible {
    box-shadow: none;
    outline: 0;
}
/* Home uses .navbar-toggler for identical sizing/box treatment, but unlike
   the hamburger it must remain visible above the expand breakpoint. */
#banner.banner-navbar .navbar-toggler.navbar-home {
    display: inline-block;
}
/* smartmenus auto-injects .sub-arrow with position: absolute; right: 0
   per submenu row. Each <li> needs to be the positioning context so
   arrows anchor to their own row, not the enclosing <ul>. */
#banner.banner-navbar .navbar-nav > li,
#banner.banner-navbar .navbar-nav .dropdown-menu > li {
    position: relative;
}
/* Top-level nav items should not wrap (e.g., "Course Listing"). */
#banner.banner-navbar .navbar-nav > li > .nav-link {
    white-space: nowrap;
}

/* Allow the top-level nav to wrap to multiple rows above the collapse
   breakpoint, matching BS3's float-based navbar behavior. BS5's flex
   .navbar-nav defaults to nowrap so items would otherwise clip/overflow
   at narrow widths between md and full. */
#banner.banner-navbar .navbar-nav.sm-bootstrap {
    flex-wrap: wrap;
}

/* Banner button group: main.css sets .lb-banner-btn-group to display:inline-block
   which fights BS5's .btn-group (inline-flex). With inline-block, whitespace between
   <button> tags produces a visible ~3px gap, breaking the joined-pill look.
   Restore BS5's flex behavior here so buttons sit flush like in BS3. */
.lb-banner-btn-group {
    display: inline-flex;
}
/* main.css forces .lb-banner-links width:100%, which would push it to a new
   row inside a BS5 flex .row. Clear that so each sub-block fills its own
   column cleanly. */
.lb-banner-links {
    width: auto;
}
/* Visible divider between joined .banner-button pills. BS5's .btn-primary sets
   border-color to the same value as background, so adjacent buttons in a .btn-group
   have no visible line between them. Derive a darker shade of the active primary
   via color-mix so each theme keeps its own button tint. */
.lb-banner-btn-group .banner-button {
    --bs-btn-border-color: color-mix(in srgb, var(--bs-primary, #0d6efd) 80%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-primary, #0d6efd) 70%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-primary, #0d6efd) 65%, #000);
}

/* BS5 delta for .lb-progress-bar-empty (base rule in main.css sets the gray
   color). BS5's .progress-bar is flex with overflow:hidden AND has a default
   blue background — at 0% width the label is clipped, and giving it min-width
   to render the text paints a filled mini-bar. Suppress the fill, let the
   text escape, and let it sit just inside the empty track. */
.progress-bar.lb-progress-bar-empty {
    overflow: visible;
    background-color: transparent;
    padding-left: 5px;
}

/* Restore BS5's default `border-collapse: collapse` on .table. The legacy
   global `table { border-collapse: separate !important }` in main.css
   (left over from an IE-era workaround) gives cells a 2px border-spacing,
   so BS5's per-cell stripe paint (inset box-shadow on <td>) doesn't bleed
   into the gap between columns — leaving visible white vertical strips
   between every column in any striped table. Scope to .table to avoid
   disturbing legacy non-BS5 tables that may still rely on the separate
   collapse mode (e.g. dataTable detail rows referenced in the original
   comment). */
.table {
    border-collapse: collapse !important;
}

/* PERF-14947: While an ajax/responsive data table is still loading it holds a
   single "Loading…" row (<td class="dt-empty" colSpan=N>). With many columns
   (e.g. reports carrying the full UserFieldsInReports set), the not-yet-collapsed
   table lays out at its full natural width — wider than the viewport — so the
   Responsive extension hasn't run yet (it only collapses AFTER the first draw).
   The centered "Loading…" text then centers within that over-wide table and
   lands off to the right, with the trailing columns running off the page.
   Constrain the table to its container while it's in the empty/loading state so
   the message stays centered on screen; reverts automatically once real rows
   replace the dt-empty row.

   PERF-14965: gated on .dt-loading (added by ui.jsp on the processing.dt event)
   so this only applies while the table is ACTIVELY loading. Keying off the
   dt-empty row alone hid the headers permanently on a settled empty table
   (DataTables 2 reuses the dt-empty cell for both "Loading…" and the steady
   "no data" state), so e.g. a goal with no activities lost its column headers. */
table.dataTable.dt-loading:has(> tbody > tr > td.dt-empty) {
    table-layout: fixed;
    width: 100%;
}
/* ...and don't flash the (squished, not-yet-collapsed) column headers while
   loading — just show the centered "Loading…". visibility:hidden keeps the
   header's height so there's no vertical jump when the real rows arrive. */
table.dataTable.dt-loading:has(> tbody > tr > td.dt-empty) > thead {
    visibility: hidden;
}
