/* =============================================================
 * Knowyn Shell — App chrome (header, sidebar, page wrappers)
 * Bootstrap 5 native + design tokens. Provides the class names
 * used by the layout and navbar components (.app, .app-header,
 * .app-aside, .app-main, .stacked-menu, .menu-*, .page-title-bar,
 * .page-section, .has-active, .btn-account, .tile, etc.).
 * ============================================================= */

/* -----------------------------------------------------------
 * Navigation feel
 * --------------------------------------------------------- */
/* Navigation is htmx-boosted: a link click swaps only #page-content in the same document (an instant
 * content swap, no crossfade). A page-to-page View Transition fade read as laggy/ghosty, so it was removed
 * (HX-Reswap no longer carries "transition:true"); the shell header/sidebar are never touched either way. */

/* -----------------------------------------------------------
 * Label voice
 * The app's ONE small-caps label recipe (table headers, KPI labels,
 * menu headers, env badge, theme-menu label, brand mark). Typography
 * lives here once; each site keeps only its own colour/spacing.
 * --------------------------------------------------------- */
.table thead th,
.stacked-menu .menu-header,
.aside-footer__env,
.theme-menu__label,
.brand-mark,
.stat-card__label {
    font-size: var(--font-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

/* -----------------------------------------------------------
 * Layout primitives
 * --------------------------------------------------------- */
html, body { height: 100%; }
/* Reserve the scrollbar gutter always so the position:fixed header's right-aligned items (help, account)
 * don't shift horizontally between short pages (no scrollbar) and tall ones (scrollbar present). */
html { scrollbar-gutter: stable; }
body {
    font-family: var(--font-family-sans);
    color: var(--text-primary);
    background-color: var(--surface-bg);
    line-height: var(--leading-normal);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.app {
    width: 100%;
    min-height: 100%;
    background-color: var(--surface-bg);
    overflow-x: hidden;
}

.app-main {
    position: relative;
    padding-top: 56px;
    overflow: hidden;
    /* Follows the sidebar width across the responsive tiers + the collapse toggle. */
    transition: padding-left var(--duration-base) var(--ease-in-out);
}

.wrapper {
    position: relative;
    margin: 0 auto;
}

/* -----------------------------------------------------------
 * Top bar (.app-header / .top-bar)
 * --------------------------------------------------------- */
.app-header {
    position: fixed;
    inset: 0 0 auto 0;
    height: 56px;
    z-index: var(--z-fixed);
    background: linear-gradient(135deg,
        var(--color-primary-700) 0%,
        var(--color-primary-500) 55%,
        var(--color-primary-400) 100%);
    color: rgba(255, 255, 255, 0.92);
    box-shadow: var(--shadow-2);
    /* Note: NO overflow:hidden — would clip the account dropdown
     * menu (it renders absolute, below the 56px header). The
     * ::before pseudo is already bounded by inset:0 + the header's
     * fixed positioning, so it can't escape vertically. */
}
/* Hero-matching dot pattern overlay — subtler (opacity 0.10) than
 * the page hero so the topbar stays readable. Clipped via
 * background-clip rather than overflow:hidden so it doesn't
 * trap descendants. */
.app-header::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: radial-gradient(
        circle at 1px 1px,
        rgba(255, 255, 255, 0.10) 1px,
        transparent 1.4px
    );
    background-size: 18px 18px;
    pointer-events: none;
}

.top-bar {
    position: relative;
    z-index: 1; /* sit above .app-header::before dot pattern */
    display: flex;
    width: 100%;
    height: 56px;
    align-items: stretch;
}

/* Plain wordmark on the gradient: no brand column box, no divider, no decoration pip. */
.top-bar-brand {
    display: none;
    position: relative;
    height: 56px;
    padding: 0 var(--space-4);
    font-size: var(--font-lg);
    align-items: center;
}
.top-bar-brand > a,
.top-bar-brand > a:hover,
.top-bar-brand > a:focus {
    color: var(--text-inverse);
    text-decoration: none;
}
@media (min-width: 768px) {
    .top-bar-brand { display: flex; }
}

.brand-text {
    font-family: var(--font-family-sans);
    font-weight: var(--weight-regular);
    letter-spacing: -0.01em;
}

.top-bar-list {
    display: flex;
    flex: 1;
    align-items: center;
}

.top-bar-item {
    display: flex;
    align-items: center;
    padding: 0;
}
@media (min-width: 768px) {
    .top-bar-item { padding-inline: var(--space-4); }
}
@media (min-width: 992px) {
    .top-bar-item { padding-left: var(--space-6); }
}

.top-bar-item-right { margin-left: auto; }

/* -----------------------------------------------------------
 * Account dropdown trigger (.btn-account)
 * --------------------------------------------------------- */
.btn-account {
    display: inline-flex;
    align-items: center;
    min-height: 56px;
    padding: 0 var(--space-4);
    background: transparent;
    border: 0;
    border-left: 1px solid rgba(255, 255, 255, 0.12);
    color: var(--text-inverse);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.btn-account:hover {
    background-color: rgba(255, 255, 255, 0.10);
    color: var(--text-inverse);
}
.btn-account:active,
.btn-account:focus,
.btn-account.show {
    background-color: rgba(20, 20, 31, 0.16);
    color: var(--text-inverse);
    outline: 0;
}
.btn-account:focus-visible {
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.6);
}

/* Plain top-bar icon button (help). A clean glyph, not a filled avatar tile. The tile's ring read as a
 * stray "border" around the help icon (only the account initial wants the avatar treatment). */
.btn-topbar-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: var(--radius-pill);
    color: rgba(255, 255, 255, 0.85);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.btn-topbar-icon .app-icon { font-size: 20px; }
.btn-topbar-icon:hover,
.btn-topbar-icon:focus {
    background-color: rgba(255, 255, 255, 0.12);
    color: #fff;
}
.btn-topbar-icon:focus-visible {
    outline: 0;
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.6);
}

.account-summary {
    display: none;
    flex-direction: column;
    margin-left: var(--space-2);
    text-align: left;
    /* Grows with the email; the cap is a safety net for extreme addresses, not a layout choice. */
    max-width: 24rem;
}
@media (min-width: 576px) {
    .account-summary { display: flex; }
}
/* No role line (members): centre the single email line against the avatar. */
.account-summary--solo { justify-content: center; }
.account-name {
    font-size: var(--font-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-inverse);
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.account-description {
    font-size: var(--font-xs);
    color: rgba(255, 255, 255, 0.75);
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Account avatar tile */
.tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: var(--color-neutral-200);
    color: var(--text-inverse);
    border-radius: var(--radius-md);
    width: 32px;
    height: 32px;
    font-size: var(--font-base);
    line-height: 1;
    flex-shrink: 0;
}
.tile-md { width: 36px; height: 36px; font-size: var(--font-lg); }
.tile-circle { border-radius: var(--radius-pill); }
.tile.bg-primary { background-color: var(--color-primary-500); }
.tile.bg-secondary { background-color: rgba(255, 255, 255, 0.16); color: var(--text-inverse); }
.tile.bg-success { background-color: var(--color-success-500); }
.tile.bg-info { background-color: var(--color-info-500); }
.tile.bg-warning { background-color: var(--color-warning-400); color: var(--color-warning-900); }
.tile.bg-danger { background-color: var(--color-danger-500); }
.tile.bg-light { background-color: var(--color-neutral-100); color: var(--text-primary); }
.tile.bg-dark { background-color: var(--color-neutral-800); }

/* Top-bar avatar: the classic muted grey circle with a white initial (no ring decoration). */
.btn-account .tile-circle {
    background: var(--color-neutral-400);
    border: 0;
    box-shadow: none;
    color: #fff;
    font-weight: var(--weight-semibold);
}
.btn-account:hover .tile-circle,
.btn-account:focus-visible .tile-circle,
.btn-account.show .tile-circle {
    background: var(--color-neutral-500);
}

/* -----------------------------------------------------------
 * Hamburger button (mobile sidebar trigger)
 * --------------------------------------------------------- */
.hamburger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-3);
    background: transparent;
    border: 0;
    color: var(--text-inverse);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.hamburger:hover,
.hamburger:focus {
    background-color: rgba(255, 255, 255, 0.10);
    outline: 0;
}
.hamburger:focus-visible {
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.6);
}
/* The sidebar is a persistent rail at md+, so the hamburger has nothing to toggle there. */
@media (min-width: 768px) {
    .hamburger { display: none; }
}
.hamburger-box {
    position: relative;
    display: inline-block;
    width: 22px;
    height: 18px;
}
.hamburger-inner,
.hamburger-inner::before,
.hamburger-inner::after {
    position: absolute;
    width: 22px;
    height: 2px;
    border-radius: 1px;
    background-color: currentColor;
    transition: transform var(--duration-base) var(--ease-in-out),
                opacity var(--duration-base) var(--ease-in-out),
                top var(--duration-base) var(--ease-in-out),
                bottom var(--duration-base) var(--ease-in-out);
}
.hamburger-inner {
    top: 50%;
    margin-top: -1px;
}
.hamburger-inner::before,
.hamburger-inner::after {
    content: "";
    display: block;
}
.hamburger-inner::before { top: -7px; }
.hamburger-inner::after  { bottom: -7px; }

.hamburger.is-active .hamburger-inner,
.hamburger[aria-expanded="true"] .hamburger-inner {
    transform: rotate(45deg);
}
.hamburger.is-active .hamburger-inner::before,
.hamburger[aria-expanded="true"] .hamburger-inner::before {
    top: 0;
    opacity: 0;
}
.hamburger.is-active .hamburger-inner::after,
.hamburger[aria-expanded="true"] .hamburger-inner::after {
    bottom: 0;
    transform: rotate(-90deg);
}

/* -----------------------------------------------------------
 * Side aside (.app-aside)
 * Mobile: offcanvas slide-in
 * Desktop (≥768px): persistent left rail
 * --------------------------------------------------------- */
.app-aside {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(calc(100vw - 3.5rem), 17rem);
    padding: 0;
    background-color: var(--surface-bg);
    border-right: 1px solid var(--border-subtle);
    z-index: var(--z-modal);
    transform: translate3d(-100%, 0, 0);
    transition: transform var(--duration-base) var(--ease-in-out);
}
.app-aside.show {
    transform: translate3d(0, 0, 0);
}

.app-aside-light {
    color: inherit;
    /* Subtle primary wash at the top, fading to surface-bg. Adds
     * brand presence to the sidebar without sacrificing legibility. */
    background: linear-gradient(180deg,
        var(--color-primary-50) 0%,
        var(--surface-bg) 280px);
}

/* Sidebar width tiers (desktop/laptop). --full hugs the longest nav label (snug, not the old fixed 240px);
   --rail is the icon + small-label-under column. Main content's padding-left tracks whichever is active. */
:root {
    --aside-w-full: 12rem;
    --aside-w-rail: 5.25rem;
}

/* >=768px: the sidebar is a persistent rail (no offcanvas / backdrop). Base = the narrow RAIL (auto at this
   width, or forced via menu-compact); the full layout is layered on at >=1200px (or forced via menu-expanded).
   The transform/z-index reset lifts it out of the mobile offcanvas behaviour. Item selectors go through
   .stacked-menu so they out-specify the base .stacked-menu .menu-link/.menu-text rules that appear LATER in
   this file (equal specificity would otherwise let those win by source order and the rail would render with
   full-width padding + non-wrapping labels). menu-compact rides the same rules so its rail == the auto rail. */
@media (min-width: 768px) {
    .app-aside-expand-md {
        top: 56px;
        z-index: var(--z-sticky);
        transform: none;
        transition: width var(--duration-base) var(--ease-in-out);
    }
    .app-aside-expand-md,
    .menu-compact .app-aside-expand-md { width: var(--aside-w-rail); }
    .app-aside-expand-md + .app-main,
    .menu-compact .app-aside-expand-md + .app-main { padding-left: var(--aside-w-rail); }
    /* Rail item: icon over a small centered label. */
    .app-aside-expand-md .stacked-menu .menu-link,
    .menu-compact .app-aside-expand-md .stacked-menu .menu-link {
        flex-direction: column;
        gap: var(--space-1);
        padding: var(--space-2) var(--space-1);
        text-align: center;
    }
    .app-aside-expand-md .stacked-menu .menu-text,
    .menu-compact .app-aside-expand-md .stacked-menu .menu-text {
        font-size: 9px;
        line-height: 1.15;
        white-space: normal;
        letter-spacing: 0;
    }
    /* The env badge / version footer has no room on the rail. */
    .app-aside-expand-md .aside-footer,
    .menu-compact .app-aside-expand-md .aside-footer { display: none; }
}

/* Full layout: automatic at >=1200px, or forced at any desktop width via menu-expanded (its extra class
   out-specifies the rail rules above, so it wins at 768-1199 too). Reverts the rail's column/small-label back
   to the roomy icon-beside-label row and widens to the snug content width. */
@media (min-width: 1200px) {
    .app-aside-expand-md { width: var(--aside-w-full); }
    .app-aside-expand-md + .app-main { padding-left: var(--aside-w-full); }
    .app-aside-expand-md .stacked-menu .menu-link {
        flex-direction: row;
        gap: var(--space-3);
        padding: var(--space-2) var(--space-4);
        text-align: left;
    }
    .app-aside-expand-md .stacked-menu .menu-text {
        font-size: var(--font-sm);
        line-height: 1.4;
        white-space: nowrap;
    }
    .app-aside-expand-md .aside-footer { display: flex; }
}
@media (min-width: 768px) {
    .menu-expanded .app-aside-expand-md { width: var(--aside-w-full); }
    .menu-expanded .app-aside-expand-md + .app-main { padding-left: var(--aside-w-full); }
    .menu-expanded .app-aside-expand-md .stacked-menu .menu-link {
        flex-direction: row;
        gap: var(--space-3);
        padding: var(--space-2) var(--space-4);
        text-align: left;
    }
    .menu-expanded .app-aside-expand-md .stacked-menu .menu-text {
        font-size: var(--font-sm);
        line-height: 1.4;
        white-space: nowrap;
    }
    .menu-expanded .app-aside-expand-md .aside-footer { display: flex; }
}

.aside-backdrop {
    position: fixed;
    inset: 0;
    background-color: rgba(20, 20, 31, 0.55);
    z-index: var(--z-modal-backdrop);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--duration-base) var(--ease-out);
}
.aside-backdrop.show {
    opacity: 1;
    pointer-events: auto;
}
@media (min-width: 768px) {
    .aside-backdrop,
    .aside-backdrop.show { display: none; opacity: 0; pointer-events: none; }
}

.aside-content {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.aside-menu {
    position: relative;
    flex: 1;
    overflow-y: auto;
    padding-block: var(--space-3);
    /* Column flex so .aside-footer's margin-top:auto pins it to the
     * bottom when the menu is short, but lets it scroll naturally
     * when the menu list grows past viewport. */
    display: flex;
    flex-direction: column;
}

/* -----------------------------------------------------------
 * Stacked menu (sidebar navigation)
 * --------------------------------------------------------- */
.stacked-menu {
    position: relative;
    width: 100%;
}
.stacked-menu ul.menu {
    display: block;
    margin: 0;
    padding: 0;
    list-style: none;
}
.stacked-menu ul.menu + .menu {
    margin-top: var(--space-3);
}

.stacked-menu .menu-header {
    margin-top: var(--space-4);
    padding: var(--space-2) var(--space-4);
    color: var(--color-primary-700);
}
.stacked-menu .menu-header:first-child { margin-top: 0; }
/* Section dividers — thin neutral line above each non-first header
 * to break the vertical rhythm of the menu list. */
.stacked-menu .menu-header:not(:first-child) {
    border-top: 1px solid var(--color-neutral-200);
    margin-top: var(--space-4);
    padding-top: calc(var(--space-2) + var(--space-2));
}

.stacked-menu .menu-item { position: relative; }

.stacked-menu .menu-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    font-size: var(--font-sm);
    color: var(--text-primary);
    text-decoration: none;
    line-height: 1.4;
    transition: color var(--duration-fast) var(--ease-out),
                background-color var(--duration-fast) var(--ease-out);
}
.stacked-menu .menu-link:hover,
.stacked-menu .menu-link:focus {
    color: var(--color-primary-700);
    background-color: var(--color-primary-50);
    text-decoration: none;
    outline: 0;
}
.stacked-menu .menu-link:focus-visible {
    box-shadow: inset var(--shadow-focus-ring);
}

.stacked-menu .menu-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: var(--radius-md);
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    color: var(--color-primary-600);
    box-shadow: var(--shadow-1);
    flex-shrink: 0;
    transition: background-color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
}
.stacked-menu .menu-icon .app-icon {
    font-size: 17px;
}
.stacked-menu .menu-link:hover .menu-icon,
.stacked-menu .menu-link:focus .menu-icon {
    background: var(--surface-card);
    border-color: var(--color-primary-300);
    color: var(--color-primary-700);
    box-shadow: var(--shadow-2);
}
.stacked-menu .menu-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stacked-menu .menu-link.disabled,
.stacked-menu .menu-item.disabled > .menu-link {
    color: var(--text-disabled);
    cursor: not-allowed;
    pointer-events: none;
}
.stacked-menu .menu-link.disabled .menu-icon,
.stacked-menu .menu-item.disabled > .menu-link .menu-icon {
    color: var(--text-disabled);
    background: var(--surface-sunken);
    border-color: var(--border-subtle);
    box-shadow: none;
}

/* Active menu item: filled primary bg + white text, a strong "you are here". */
.stacked-menu .menu-item.has-active > .menu-link {
    color: #fff;
    background-color: var(--color-primary-500);
    font-weight: var(--weight-semibold);
    box-shadow: var(--shadow-1);
}
.stacked-menu .menu-item.has-active > .menu-link .menu-icon {
    background: rgba(255, 255, 255, 0.18);
    border-color: rgba(255, 255, 255, 0.28);
    color: #fff;
    box-shadow: none;
}
.stacked-menu .menu-item.has-active > .menu-link:hover,
.stacked-menu .menu-item.has-active > .menu-link:focus {
    color: #fff;
    background-color: var(--color-primary-600);
}
.stacked-menu .menu-item.has-active > .menu-link:hover .menu-icon,
.stacked-menu .menu-item.has-active > .menu-link:focus .menu-icon {
    background: rgba(255, 255, 255, 0.24);
    border-color: rgba(255, 255, 255, 0.36);
    color: #fff;
}

/* -----------------------------------------------------------
 * Sidebar collapse toggle (the top arrow) — the persistent-rail
 * chrome that shrinks/expands the menu on demand. Only meaningful
 * where the sidebar is a rail (>=768px); hidden in the mobile
 * offcanvas.
 * --------------------------------------------------------- */
/* The collapse arrow anchors to the sidebar's right edge (a centered arrow floated oddly in the
   full-width layout); on the narrow rail the edge and the middle are close enough. */
.aside-head {
    display: none;
    align-items: center;
    justify-content: flex-end;
    padding: var(--space-2) var(--space-3);
}
@media (min-width: 768px) {
    .aside-head { display: flex; }
}
.aside-collapse {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--border-subtle);
    background: var(--surface-card);
    color: var(--text-secondary);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}
.aside-collapse:hover,
.aside-collapse:focus-visible {
    color: var(--color-primary-700);
    border-color: var(--color-primary-300);
    outline: 0;
}
.aside-collapse .app-icon {
    font-size: 13px;
    transition: transform var(--duration-fast) var(--ease-out);
}
/* The chevron points right by default (= "expand", shown on the rail); rotate it to point left when the
   sidebar is currently full (= "collapse"). site.js toggles .is-collapsed to match the effective state. */
.aside-collapse:not(.is-collapsed) .app-icon { transform: rotate(180deg); }

/* Rail icons run slightly larger so the glyph carries the item when the label shrinks to 9px; the full
   layout restores the default size. Mirrors the rail/full tier structure above (base+compact = rail,
   auto>=1200 + expanded = full). */
@media (min-width: 768px) {
    .app-aside-expand-md .stacked-menu .menu-icon,
    .menu-compact .app-aside-expand-md .stacked-menu .menu-icon { width: 34px; height: 34px; }
    .app-aside-expand-md .stacked-menu .menu-icon .app-icon,
    .menu-compact .app-aside-expand-md .stacked-menu .menu-icon .app-icon { font-size: 18px; }
}
@media (min-width: 1200px) {
    .app-aside-expand-md .stacked-menu .menu-icon { width: 30px; height: 30px; }
    .app-aside-expand-md .stacked-menu .menu-icon .app-icon { font-size: 17px; }
}
@media (min-width: 768px) {
    .menu-expanded .app-aside-expand-md .stacked-menu .menu-icon { width: 30px; height: 30px; }
    .menu-expanded .app-aside-expand-md .stacked-menu .menu-icon .app-icon { font-size: 17px; }
}

/* -----------------------------------------------------------
 * Sidebar footer — environment badge (dev/staging/prod).
 * Pinned visually at the bottom of the menu list.
 * --------------------------------------------------------- */
.aside-footer {
    padding: var(--space-3) var(--space-4);
    /* margin-top:auto pins to the bottom of .aside-menu's flex column;
     * negative bottom margin escapes the .aside-menu padding so the
     * border-top hugs the sidebar edge. */
    margin: auto 0 calc(var(--space-3) * -1);
    border-top: 1px solid var(--color-neutral-200);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    flex-shrink: 0;
}
/* App version, shown next to (or instead of, in prod) the env badge. Muted + tabular so it reads as
 * metadata, not a control. */
.aside-footer__version {
    font-size: var(--font-xs);
    color: var(--text-disabled);
    font-variant-numeric: tabular-nums;
}
.aside-footer__env {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 2px var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    color: var(--text-secondary);
}
.aside-footer__env-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-neutral-400);
    box-shadow: 0 0 6px currentColor;
}
.aside-footer__env--prod {
    color: var(--color-success-700, #15803d);
    border-color: var(--color-success-200, #bbf7d0);
    background: var(--color-success-50, #f0fdf4);
}
.aside-footer__env--prod .aside-footer__env-dot {
    background: var(--color-success-500, #22c55e);
    color: var(--color-success-500, #22c55e);
}
.aside-footer__env--stage {
    color: var(--color-warning-700, #b45309);
    border-color: var(--color-warning-200, #fde68a);
    background: var(--color-warning-50, #fffbeb);
}
.aside-footer__env--stage .aside-footer__env-dot {
    background: var(--color-warning-500, #f59e0b);
    color: var(--color-warning-500, #f59e0b);
}
.aside-footer__env--dev {
    color: var(--color-primary-700);
    border-color: var(--color-primary-200);
    background: var(--color-primary-50);
}
.aside-footer__env--dev .aside-footer__env-dot {
    background: var(--color-primary-500);
    color: var(--color-primary-500);
}

/* -----------------------------------------------------------
 * Page wrappers
 * --------------------------------------------------------- */
.page {
    min-height: calc(100vh - 56px);
}

/* In-page anchor targets (e.g. an /Articles filter tile jumping to #all-articles) must clear the fixed 56px
   top bar, otherwise scrollIntoView tucks the heading underneath it. */
#all-articles { scroll-margin-top: calc(56px + var(--space-4)); }

.page-inner {
    padding: var(--space-5) var(--space-4);
}
@media (min-width: 576px) {
    .page-inner { padding-inline: var(--space-4); }
}
@media (min-width: 992px) {
    .page-inner { padding-inline: var(--space-6); }
}

.page-title-bar,
.page-section {
    width: 100%;
    margin-inline: auto;
}

.page-title-bar {
    margin-bottom: var(--space-5);
}

.page-title {
    margin: 0;
    font-size: var(--font-xl);
    font-weight: var(--weight-semibold);
    line-height: var(--leading-tight);
    color: var(--text-primary);
}
.page-title > small {
    font-size: var(--font-sm);
    color: var(--text-secondary);
    font-weight: var(--weight-regular);
}

.page-section {
    padding-inline: 0;
    margin-bottom: var(--space-5);
}

/* -----------------------------------------------------------
 * Dropdown arrow (BS5 dropdown chevron)
 * --------------------------------------------------------- */
.dropdown-arrow {
    position: absolute;
    top: -6px;
    right: var(--space-4);
    width: 12px;
    height: 12px;
    background: var(--surface-card);
    transform: rotate(45deg);
    box-shadow: -1px -1px 0 var(--border-subtle);
    z-index: 1;
}

.dropdown-menu {
    margin-top: var(--space-1);
    background-color: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-3);
    padding: var(--space-2) 0;
    min-width: 220px;
}
.dropdown-menu .dropdown-item {
    padding: var(--space-2) var(--space-4);
    font-size: var(--font-sm);
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    text-decoration: none;
    background: transparent;
    border: 0;
    width: 100%;
    text-align: left;
}
.dropdown-menu .dropdown-item:hover,
.dropdown-menu .dropdown-item:focus {
    background-color: var(--color-primary-50);
    color: var(--color-primary-700);
    outline: 0;
}
.dropdown-menu .dropdown-divider {
    margin: var(--space-2) 0;
    border-color: var(--border-subtle);
}
.dropdown-menu .dropdown-icon {
    width: 18px;
    text-align: center;
    flex-shrink: 0;
}

.theme-menu {
    padding: var(--space-2) var(--space-3);
    min-width: 220px;
}
.theme-menu--inline {
    min-width: min(360px, calc(100vw - 32px));
    padding: var(--space-2);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, 0.12);
    color: var(--text-inverse);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.theme-menu__label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
    color: var(--text-secondary);
}
.theme-menu--inline .theme-menu__label {
    color: rgba(255, 255, 255, 0.78);
}
.theme-menu__current {
    margin-left: auto;
    color: var(--text-secondary);
    font-weight: var(--weight-medium);
    text-transform: none;
    letter-spacing: 0;
}
.theme-menu--inline .theme-menu__current {
    color: rgba(255, 255, 255, 0.88);
}
.theme-menu__choices {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-1);
}
.theme-menu__choice {
    min-height: var(--control-height-sm);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-sm);
    background: var(--surface-card);
    color: var(--text-primary);
    font-size: var(--font-xs);
    font-weight: var(--weight-medium);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.theme-menu__choice:hover,
.theme-menu__choice:focus-visible {
    border-color: var(--color-primary-400);
    color: var(--text-link-hover);
    outline: 0;
}
.theme-menu__choice.active {
    background: var(--color-primary-500);
    border-color: var(--color-primary-500);
    color: var(--text-inverse);
}
.theme-menu--inline .theme-menu__choice {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.22);
    color: var(--text-inverse);
}
.theme-menu--inline .theme-menu__choice:hover,
.theme-menu--inline .theme-menu__choice:focus-visible {
    background: rgba(255, 255, 255, 0.20);
    border-color: rgba(255, 255, 255, 0.36);
}
.theme-menu--inline .theme-menu__choice.active {
    background: #fff;
    border-color: #fff;
    color: #152c50;
}

/* -----------------------------------------------------------
 * Bootstrap 5 component refinements
 * --------------------------------------------------------- */

/* Modal — use design-tokens for radii and shadows */
.modal-content {
    border: 0;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-4);
    background-color: var(--surface-card);
    color: var(--text-primary);
}
.modal-header {
    border-bottom: 1px solid var(--border-subtle);
    padding: var(--space-4) var(--space-5);
    background-color: var(--surface-card);
}
.modal-body { padding: var(--space-5); }
.modal-footer {
    border-top: 1px solid var(--border-subtle);
    padding: var(--space-4) var(--space-5);
    background-color: var(--surface-card);
}

/* Cards */
.card {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
    background-color: var(--surface-card);
    color: var(--text-primary);
}
.card-header {
    background-color: transparent;
    /* Primary-100 border-bottom (instead of neutral border-subtle) so the
     * header has a subtle branded separation from card body content. */
    border-bottom: 1px solid var(--color-primary-100);
    padding: var(--space-3) var(--space-4);
    font-weight: var(--weight-semibold);
    color: var(--color-primary-800);
}
.card-header.border-0 { border-bottom: 0; }
.card-body { padding: var(--space-4); }

/* Help index topic cards: a nav-glyph accent beside the title, with a hover cue on the card. */
.help-topic-card__icon .app-icon {
    font-size: 1.5rem;
    color: var(--color-primary-600);
    line-height: 1;
}
.help-topic-card .card:hover { border-color: var(--color-primary-500); }

/* Help article body: readable spacing for the prose and its lists. */
.help-body p { margin-bottom: var(--space-3); }
.help-body ul,
.help-body ol { margin-bottom: var(--space-3); padding-left: 1.25rem; }
.help-body li { margin-bottom: var(--space-2); }

/* Buttons: inline-flex so the min-height (not padding) sets the box and the label stays centered. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: var(--weight-medium);
    border-radius: var(--radius-md);
    transition: background-color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
    min-height: var(--control-height-md);
    padding-inline: var(--space-4);
}
.btn-sm { min-height: var(--control-height-sm); padding-inline: var(--space-3); font-size: var(--font-sm); }
.btn-lg { min-height: var(--control-height-lg); padding-inline: var(--space-5); }

.btn-primary {
    background-color: var(--color-primary-500);
    border-color: var(--color-primary-500);
    color: var(--text-inverse);
}
.btn-primary:hover {
    background-color: var(--color-primary-600);
    border-color: var(--color-primary-600);
    color: var(--text-inverse);
}
.btn-primary:active,
.btn-primary:focus {
    background-color: var(--color-primary-700);
    border-color: var(--color-primary-700);
}

.btn-outline-primary {
    background-color: transparent;
    border-color: var(--color-primary-500);
    color: var(--color-primary-600);
}
.btn-outline-primary:hover {
    background-color: var(--color-primary-50);
    border-color: var(--color-primary-600);
    color: var(--color-primary-700);
}

.btn-light {
    background-color: var(--color-neutral-100);
    border-color: var(--color-neutral-100);
    color: var(--text-primary);
}
.btn-light:hover {
    background-color: var(--color-neutral-200);
    border-color: var(--color-neutral-200);
}

/* Tables */
.table {
    color: var(--text-primary);
    --bs-table-bg: transparent;
    --bs-table-color: var(--text-primary);
    --bs-table-border-color: var(--border-subtle);
    --bs-table-hover-bg: var(--surface-hover);
    --bs-table-hover-color: var(--text-primary);
}
/* Ledger table header: no fill, the shared label voice over a hairline, like a printed register. */
.table thead th {
    background-color: transparent;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border-default);
    padding: var(--space-3) var(--space-3);
}
.table tbody td {
    padding: var(--space-3);
    vertical-align: middle;
    border-top: 1px solid var(--border-subtle);
    font-size: var(--font-sm);
}
.table-hover tbody tr:hover {
    background-color: var(--color-neutral-50);
}
/* btn-light shares the row-hover grey, so action buttons (Users page: promote/deactivate) vanish into a
   hovered row. Lift them to the card surface with a border so they stay legible; their own hover still
   darkens (higher-specificity rule below). */
.table-hover tbody tr:hover .btn-light {
    background-color: var(--surface-card);
    border-color: var(--border-default);
}
.table-hover tbody tr:hover .btn-light:hover {
    background-color: var(--color-neutral-100);
    border-color: var(--color-neutral-200);
}
.table-sm tbody td { padding-block: var(--space-2); }

/* Base content links: quiet dark text BY DEFAULT (estate feel) so an unclassed link (e.g. dashboard
   "Visa alla") isn't loud Bootstrap blue. Element specificity (0,0,1) — every classed component (.btn,
   .link-quiet, .table-sort, ...) and the scoped header/nav/auth link rules override this; brand colour +
   underline appear only on hover/focus. This is the one place to make "dark links everywhere" stick. */
a {
    color: var(--text-primary);
    text-decoration: none;
}
/* Exclude buttons from the content-link hover: an <a class="btn"> (the segmented range toggle, order
   status filters, export/edit/download links) would otherwise pick up the link colour + underline on
   hover because a:hover (0,1,1) out-specifies Bootstrap's .btn (0,1,0). Excluding them lets each button
   keep its own variant colour with no underline. (.btn-link-quiet is not .btn, so it stays underlined.) */
a:not(.btn):not([role="button"]):hover,
a:not(.btn):not([role="button"]):focus-visible {
    color: var(--text-link);
    text-decoration: underline;
}

/* Quiet links read as clickable dark text (the estate's table-enhanced feel), not default blue:
   the dark body colour clears AA on the off-white surface; brand colour + underline only on
   hover/focus. Applied automatically to table-cell links, and via .link-quiet for content links
   (e.g. the assigned price-list link on a customer). */
.link-quiet,
.table tbody td a:not(.btn) {
    color: var(--text-primary);
    text-decoration: none;
    font-weight: var(--weight-medium);
}
/* Inline modal/help trigger that reads as quiet dark link text, not a loud blue btn-link. Strips
 * button chrome; a persistent underline keeps it discoverable as clickable within hint text. */
.btn-link-quiet {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--text-primary);
    font-weight: var(--weight-medium);
    text-decoration: underline;
    cursor: pointer;
}
.btn-link-quiet:hover,
.btn-link-quiet:focus-visible {
    color: var(--text-link);
}

.link-quiet:hover, .link-quiet:focus-visible,
.table tbody td a:not(.btn):hover,
.table tbody td a:not(.btn):focus-visible {
    color: var(--text-link);
    text-decoration: underline;
}
/* The header wordmark is a home link but reads as brand, not content: clickable, never underlined,
   white in both states. Out-specifies the base content-link hover above. */
.app-header .top-bar-brand > a:hover,
.app-header .top-bar-brand > a:focus-visible {
    color: var(--text-inverse);
    text-decoration: none;
}
/* Secondary link-buttons (back, remove, row toggle): Bootstrap's secondary grey (#6c757d) is
   ~4.4:1 on the off-white app surface; neutral-600 (the token flips per theme) clears AA both ways. */
.btn-link.text-secondary { color: var(--color-neutral-600) !important; }
.table-responsive { overflow-x: auto; }

/* Sortable column headers: the <th> content becomes a button/link that toggles server-side sort, with a
   direction caret on the active column. Inherits the header's font so it reads as a heading, not a control. */
.table-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    white-space: nowrap;
}
a.table-sort { text-decoration: none; }
.table-sort:hover { color: var(--color-primary-600); }
.table-sort:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: 2px; border-radius: var(--radius-sm); }
.table-sort.is-active { color: var(--text-primary); font-weight: var(--weight-semibold); }

/* Sort indicator. Inactive sortable header: a quiet up/down caret PAIR so sortability is discoverable.
   Active header: the pair collapses to a SINGLE larger brand-coloured caret pointing the current direction
   (the opposite one is hidden, not just dimmed, so the direction reads at a glance), and the whole <th> gets
   a primary tint + a 2px accent underline (below) so the sorted column is unmistakable. CSS border-triangles
   stay crisp at any zoom (no glyph font dependency). */
.table-sort__ind {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    line-height: 0;
    margin-left: 0.15rem;
}
.table-sort__up,
.table-sort__down {
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    opacity: 0.55;
}
.table-sort__up { border-bottom: 5px solid var(--color-neutral-500); }
.table-sort__down { border-top: 5px solid var(--color-neutral-500); }
/* Hovering a sortable header brightens the pair (a "you can sort this" affordance). */
.table-sort:hover .table-sort__up,
.table-sort:hover .table-sort__down { opacity: 0.9; }

/* Active column: hide the opposite caret, and make the direction caret larger + ink-coloured. */
.table-sort--asc .table-sort__down,
.table-sort--desc .table-sort__up { display: none; }
.table-sort--asc .table-sort__up {
    border-left-width: 5px;
    border-right-width: 5px;
    border-bottom: 7px solid var(--color-neutral-700);
    opacity: 1;
}
.table-sort--desc .table-sort__down {
    border-left-width: 5px;
    border-right-width: 5px;
    border-top: 7px solid var(--color-neutral-700);
    opacity: 1;
}

/* Sorted column header cell: an ink underline (aria-sort is set on the <th>) marks the sorted column;
   no colour wash, the line is the signal. Higher specificity than the base .table thead th. */
.table thead th[aria-sort="ascending"],
.table thead th[aria-sort="descending"] {
    box-shadow: inset 0 -2px 0 var(--color-neutral-700);
}

.list-group {
    --bs-list-group-bg: var(--surface-card);
    --bs-list-group-color: var(--text-primary);
    --bs-list-group-border-color: var(--border-subtle);
    --bs-list-group-action-hover-bg: var(--surface-hover);
    --bs-list-group-action-hover-color: var(--text-primary);
    --bs-list-group-action-active-bg: var(--surface-sunken);
    --bs-list-group-action-active-color: var(--text-primary);
}

/* Form controls */
.form-control,
.form-select {
    background-color: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    font-size: var(--font-sm);
    min-height: var(--control-height-md);
    padding: var(--space-2) var(--space-3);
    transition: border-color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
}
.form-control:focus,
.form-select:focus {
    border-color: var(--color-primary-500);
    box-shadow: var(--shadow-focus-ring);
    outline: 0;
}
/* The shared padding above collapses Bootstrap's right gutter that the caret sits in, so
   the value text runs under the chevron. Restore room for the arrow on selects only. */
.form-select {
    padding-right: 2.25rem;
}
.form-control::placeholder { color: var(--text-disabled); }
.form-label {
    font-size: var(--font-sm);
    font-weight: var(--weight-medium);
    color: var(--text-primary);
    margin-bottom: var(--space-1);
}

.form-check-input {
    background-color: var(--surface-card);
    border-color: var(--border-default);
}
/* Checked state uses the brand blue, not Bootstrap's default #0d6efd (which clashes in both themes). */
.form-check-input:checked {
    background-color: var(--color-primary-500);
    border-color: var(--color-primary-500);
}

.offcanvas {
    background-color: var(--surface-card);
    color: var(--text-primary);
}

.btn-close {
    filter: var(--theme-btn-close-filter, none);
}

/* Alerts */
.alert {
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    font-size: var(--font-sm);
}
.alert-primary {
    background-color: var(--color-primary-50);
    border-color: var(--color-primary-100);
    color: var(--color-primary-800);
}
.alert-success {
    background-color: var(--color-success-50);
    border-color: var(--color-success-100);
    color: var(--color-success-800);
}
.alert-warning {
    background-color: var(--color-warning-50);
    border-color: var(--color-warning-100);
    color: var(--color-warning-900);
}
.alert-danger {
    background-color: var(--color-danger-50);
    border-color: var(--color-danger-100);
    color: var(--color-danger-800);
}
.alert-info {
    background-color: var(--color-info-50);
    border-color: var(--color-info-100);
    color: var(--color-info-800);
}

/* Badges — the single reusable size token for all pills/badges across the app. */
.badge {
    font-weight: var(--weight-semibold);
    padding: 0.5em 0.8em;
    font-size: 0.75em;
    border-radius: var(--radius-sm);
}
.badge.rounded-pill { border-radius: var(--radius-pill); padding-inline: 0.95em; }

/* Breadcrumbs: a quiet chevron trail. Links read as muted text (not default blue), gaining brand colour
 * and an underline only on hover/focus; the chevron divider is a faint glyph, not Bootstrap's slash. */
.breadcrumb {
    --bs-breadcrumb-divider: '\203A';
    /* Zero Bootstrap's item padding-left so the only gap around the chevron is the symmetric
     * padding-inline on ::before below; otherwise item padding + ::before padding stack on the
     * left only and the chevron sits closer to the right label. */
    --bs-breadcrumb-item-padding-x: 0;
    margin-bottom: 0;
    font-size: var(--font-sm);
}
.breadcrumb-item + .breadcrumb-item::before {
    color: var(--text-disabled);
    padding-inline: var(--space-2);
}
.breadcrumb-item a {
    color: var(--text-secondary);
    text-decoration: none;
    font-weight: var(--weight-medium);
}
.breadcrumb-item a:hover,
.breadcrumb-item a:focus-visible {
    color: var(--text-link);
    text-decoration: underline;
}
.breadcrumb-item.active { color: var(--text-secondary); }

/* Destructive light button (btn-light + text-danger, e.g. delete). btn-light is theme-flipped
   (light bg in light mode, charcoal in dark), so the red has to flip too: a dark red on the
   light button, a light red on the dark one — both clear AA. */
.btn-light.text-danger { color: var(--color-danger-600) !important; }
:root[data-theme="dark"] .btn-light.text-danger { color: var(--color-danger-400) !important; }

/* -----------------------------------------------------------
 * Skin toggle hooks (legacy compatibility)
 * --------------------------------------------------------- */
body.default-skin { /* default skin. Currently no alternate. */ }

:root[data-theme="dark"] {
    --theme-btn-close-filter: invert(1) grayscale(100%) brightness(1.8);
}

:root[data-theme="dark"] .app-header {
    background: linear-gradient(135deg,
        #152c50 0%,
        #346cb0 55%,
        #5b8dcd 100%);
}

:root[data-theme="dark"] .app-aside-light {
    background: linear-gradient(180deg,
        var(--color-primary-50) 0%,
        var(--surface-bg) 280px);
}

:root[data-theme="dark"] .card-header {
    color: var(--color-primary-800);
    border-bottom-color: var(--border-subtle);
}

:root[data-theme="dark"] .btn-outline-primary {
    color: var(--color-primary-700);
    border-color: var(--color-primary-400);
}
:root[data-theme="dark"] .btn-primary:active,
:root[data-theme="dark"] .btn-primary:focus {
    background-color: var(--color-primary-600);
    border-color: var(--color-primary-600);
    color: var(--text-inverse);
}
:root[data-theme="dark"] .btn-outline-primary:hover {
    background-color: var(--color-primary-100);
    border-color: var(--color-primary-400);
    color: var(--color-primary-900);
}

:root[data-theme="dark"] .btn-light {
    background-color: var(--surface-hover);
    border-color: var(--border-default);
    color: var(--text-primary);
}
:root[data-theme="dark"] .btn-light:hover {
    background-color: var(--color-neutral-200);
    border-color: var(--border-strong);
}

:root[data-theme="dark"] .tile.bg-warning {
    color: #3e2900;
}

:root[data-theme="dark"] .dropdown-menu .dropdown-item:hover,
:root[data-theme="dark"] .dropdown-menu .dropdown-item:focus {
    background-color: var(--surface-hover);
    color: var(--text-link-hover);
}
