/* Shared kernel component styles -- tokens only, no hard-coded colors, per BLAZOR-REBUILD-SPEC.md's
   DESIGN SYSTEM -- BINDING section (0-DESIGN, 2026-08-04). Built to
   docs/for-claude-code/design/COMPONENT-SPECS.md; that document wins wherever this file and it
   disagree. --st- names throughout (not the deprecated aliases in tokens.css), since this file is
   the migration target, not a consumer that still needs the bridge. */

/* #blazor-error-ui ships hidden by default in every Blazor WASM template's own stylesheet; only
   the framework's boot JS ever removes the hidden state, on a genuine unhandled exception. This
   rule has to exist SOMEWHERE in the app's CSS or the div (a plain block-level <div> with no other
   styling) is visible on every single page load regardless of whether anything actually failed. */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: var(--st-danger-subtle);
    color: var(--st-danger-on-subtle);
    padding: var(--st-space-3) var(--st-space-4);
    box-shadow: var(--st-shadow-md);
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    float: right;
}

/* 0-LOADER (2026-08-05): the boot/reload splash's own styling moved to an inline <style> block in
   index.html's <head> -- a real Playwright run against beta proved it must never depend on an
   external stylesheet's network round-trip (see index.html's own comment on that block for the
   full story: the "render-blocking <link> means always-applied-before-paint" assumption didn't
   hold in practice). Nothing here anymore; kept as a marker so a future reader searching for
   ".loading-progress" finds this note instead of wondering where it went. */

/* ── Shell: top bar + left nav (COMPONENT-SPECS.md §8-9) ──────────────────────────────────────
   The chrome is dark in BOTH themes -- the product's signature, not a dark-mode artefact -- so it
   uses --st-surface-chrome / --st-text-on-chrome and the *-on-chrome state tokens throughout,
   never the theme-flipping --st-surface/--st-border. .st-on-chrome (on .shell-topbar and
   .shell-nav) swaps the global :focus-visible ring for the on-chrome variant (tokens.css). */

.shell {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

.shell-topbar {
    display: flex;
    align-items: center;
    height: var(--st-topbar-h);
    flex: 0 0 var(--st-topbar-h);
    padding: 0 var(--st-space-4);
    background: var(--st-surface-chrome);
    color: var(--st-text-on-chrome);
}

.shell-brand {
    flex: 0 0 calc(var(--st-nav-w) - var(--st-space-4));
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    font-family: var(--st-font-ui);
    font-weight: 600;
    font-size: var(--st-text-base);
    color: var(--st-text-on-chrome);
    text-decoration: none;
}

.shell-brand-mark {
    flex: 0 0 20px;
    width: 20px;
    height: 20px;
    background-color: currentColor;
    -webkit-mask: var(--menu-icon-url) no-repeat center / contain;
    mask: var(--menu-icon-url) no-repeat center / contain;
}

/* Task 28 (2026-08-04): hamburger that opens the phone nav drawer -- hidden on desktop, where the
   nav is always visible as the 210px sidebar and there's nothing to open. */
.shell-hamburger {
    display: none;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    margin-right: var(--st-space-2);
    border: none;
    border-radius: var(--st-radius-sm);
    background: transparent;
    color: var(--st-text-on-chrome);
    font-size: var(--st-text-lg);
    cursor: pointer;
}

.shell-hamburger:hover {
    background: var(--st-surface-hover-on-chrome);
}

/* COMPONENT-SPECS.md §8 "Page name" -- documented in the topbar anatomy since before ScreenHeader
   existed, never actually populated until 0-REVIEW-9 #4 (2026-08-06). min-width: 0 + overflow: hidden
   on the row (flex: 1 1 auto, the only flexible item between the fixed-width brand and the
   margin-left: auto actions) is what lets the description truncate with ellipsis instead of pushing
   the account name/icons off the edge of the topbar. */
.shell-page-name {
    display: flex;
    align-items: baseline;
    gap: var(--st-space-2);
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    margin-left: 30px;
}

.shell-page-name-title {
    font-family: var(--st-font-ui);
    font-size: var(--st-text-lg);
    font-weight: 400;
    color: var(--st-text-on-chrome);
    white-space: nowrap;
    flex: 0 0 auto;
}

.shell-page-name-sep {
    color: var(--st-text-on-chrome-muted);
    flex: 0 0 auto;
}

.shell-page-name-description {
    font-size: var(--st-text-sm);
    color: var(--st-text-on-chrome-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.shell-topbar-actions {
    display: flex;
    align-items: center;
    gap: var(--st-space-1);
    margin-left: auto;
}

.shell-signout-btn {
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    height: var(--st-control-h-sm);
    border: 1px solid var(--st-border-on-chrome);
    border-radius: var(--st-radius-sm);
    background: transparent;
    color: var(--st-text-on-chrome);
    padding: 0 var(--st-space-2);
    cursor: pointer;
}

.shell-signout-btn:hover {
    background: var(--st-surface-hover-on-chrome);
}

/* 0-REVIEW-9 #7 (2026-08-06), COMPONENT-SPECS.md §8 "Icon button": 35px circle, transparent, glyph
   in --st-text-on-chrome. Generic enough to reuse for any future topbar icon (search/notifications),
   not just the theme control. */
.shell-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 35px;
    height: 35px;
    border: none;
    border-radius: var(--st-radius-pill);
    background: transparent;
    color: var(--st-text-on-chrome);
    font-size: var(--st-text-base);
    line-height: 1;
    cursor: pointer;
}

.shell-icon-btn:hover {
    background: var(--st-surface-hover-on-chrome);
}

.shell-icon-btn[aria-expanded="true"] {
    background: var(--st-surface-active-on-chrome);
}

.shell-theme-menu-wrap {
    position: relative;
}

/* Same "real, focusable, labeled backdrop button" reasoning as .shell-nav-scrim (COMPONENT-SPECS.md
   §7's scrim precedent) -- click-outside-to-close without a JS interop listener. */
.shell-menu-scrim {
    position: fixed;
    inset: 0;
    z-index: 999;
    border: none;
    padding: 0;
    background: transparent;
    cursor: default;
}

/* R4 B1 (Mazhar, 2026-08-17): "Top-ribbon Settings menu: text area wider -- tiny labels currently wrap
   into two rows per option." The 140px floor was sized for the theme menu's three one-word options
   (Light/Dark/System) and then inherited by the Settings menu in ROUND 2, whose entries are real
   sec_menu labels ("Account Settings", "Change Password"). Those wrapped, and because the item's height
   is pinned to --st-tap-min the second line had nowhere to go -- so it read as clipped, not as wrapped.

   Two changes, and both are needed: `width: max-content` lets the panel take the width its LONGEST
   label actually needs instead of a guessed constant, and `white-space: nowrap` on the item is what
   makes that measurement honest -- without it the browser is free to wrap inside the fixed height
   again the moment a label grows. The max-width keeps a long label from running off a narrow screen;
   past that it ellipsises, with the full text still on the item's title attribute. */
.shell-theme-menu {
    position: absolute;
    top: calc(100% + var(--st-space-1));
    right: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    min-width: 200px;
    width: max-content;
    max-width: min(340px, calc(100vw - var(--st-space-4)));
    padding: var(--st-space-1);
    background: var(--st-surface-overlay);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    box-shadow: var(--st-shadow-md);
}

.shell-theme-menu-item {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    height: var(--st-tap-min);
    padding: 0 var(--st-space-2);
    border: none;
    border-radius: var(--st-radius-sm);
    background: transparent;
    color: var(--st-text-primary);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-sm);
    text-align: left;
    cursor: pointer;
    /* B1: see .shell-theme-menu -- the item must refuse to wrap for max-content to mean anything. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.shell-theme-menu-item:hover {
    background: var(--st-surface-hover);
}

.shell-theme-menu-item-active {
    color: var(--st-accent-fill);
    font-weight: 600;
}

/* ROUND 2 (2026-08-15): the Settings+Admin topbar menu reuses this whole block, so it needs the two
   shapes the theme menu never had -- a group heading (that menu has one flat list; this one shows
   "Setting" and "Admin" as separate sections) and an inert row for a child sec_menu names but this app
   has no screen for yet. The inert row is deliberately NOT a disabled <button>: it is a <span>, so it is
   skipped by tab order entirely rather than being a focus stop that does nothing. Same "(coming soon)"
   treatment the left rail already gives the identical entry -- one convention, two surfaces. */
.shell-theme-menu-heading {
    display: block;
    padding: var(--st-space-2) var(--st-space-2) var(--st-space-1);
    color: var(--st-text-secondary);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.shell-theme-menu-heading:not(:first-child) {
    border-top: 1px solid var(--st-border);
    margin-top: var(--st-space-1);
}

.shell-theme-menu-item-inert {
    color: var(--st-text-secondary);
    cursor: default;
}

.shell-theme-menu-item-inert:hover {
    background: transparent;
}

.shell-user-email {
    font-size: var(--st-text-xs);
    color: var(--st-text-on-chrome-muted);
    max-width: 16rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* NIGHT-QUEUE full-unpark (2026-08-06): persistent "you're in the read-only demo" indicator --
   a filled pill (not plain text like .shell-user-email) so it reads as a status, not just a label. */
.shell-demo-badge {
    font-size: var(--st-text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--st-text-on-fill);
    background: var(--st-accent-fill);
    border-radius: var(--st-radius-pill);
    padding: 2px var(--st-space-3);
    white-space: nowrap;
}

/* 0-UNPARK (2026-08-06): Terms/Support -- unconditional topbar utilities, same pattern as the old
   app's TopBar.razor (see AppShell.razor's own comment at the call site for why these aren't in the
   sec_menu-driven sidebar). Plain text links, not the bordered .shell-signout-btn treatment -- two
   more bordered boxes next to the theme select/sign-out would visually compete with the actual
   session-critical controls for a pair of low-frequency utility links. */
.shell-topbar-link {
    font-size: var(--st-text-xs);
    color: var(--st-text-on-chrome-muted);
    text-decoration: none;
    white-space: nowrap;
}

.shell-topbar-link:hover {
    color: var(--st-text-on-chrome);
    text-decoration: underline;
}

.shell-body {
    flex: 1;
    display: flex;
    min-height: 0;
}

.shell-nav {
    width: var(--st-nav-w);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    /* Bug ledger #23 (2026-08-11): Mazhar's own words, live on beta -- "I asked for gap between menu
       rows. Now it is too much... just increase a little than what was before. It was not too bad."
       "Before" was 0 (rows flush, matching old app's own .Nov-Body -- checked directly, no gap/margin
       between .Nav-Body-Item rows there at all). The full --st-space-1 (4px) step overshot what he
       pictured; there's no smaller token on the scale to fall back to, so this is a deliberate,
       narrow exception below it -- same class of exception this file's own literal-colour rules take
       elsewhere, not a token violation by accident. */
    gap: 2px;
    padding: var(--st-space-3);
    background: var(--st-surface-chrome);
    border-right: 1px solid var(--st-border-on-chrome);
    overflow-y: auto;
    /* Bug ledger #19 (2026-08-10, Mazhar, REOPENED "reported before"): the rail's own collapse/
       expand (the «/» toggle snapping between --st-nav-w and --st-nav-w-collapsed) had no
       transition at all -- width changed in a single frame. This is a DIFFERENT animation from the
       accordion group's own open/close (already 0.3s eased, PEN-2 item 8) -- that one was fine; this
       one was never built. Matches old app's own NavMenu.css exactly (`transition: width 0.3s
       ease` on both its expanded and collapsed rail rules). */
    transition: width 0.3s ease;
}

/* Close button + scrim only ever render/matter on the phone drawer (COMPONENT-SPECS.md §9 Phone) --
   hidden here, made visible inside the 768px breakpoint below. */
.shell-nav-close {
    display: none;
}

.shell-nav-scrim {
    display: none;
}

/* 0-MENU hardening (2026-08-05): MenuIcon.razor's own masking technique, ported verbatim from the
   old app's MenuIcon.razor/shared.css (same sec_menu.IconName key, same static SVG-per-key file) --
   background-color: currentColor means the icon always matches whatever color: the wrapping
   .shell-nav-item/.shell-nav-group/.shell-nav-subitem already sets, no per-theme icon variant
   needed. */
.menu-icon-masked {
    background-color: currentColor;
    -webkit-mask: var(--menu-icon-url) no-repeat center / contain;
    mask: var(--menu-icon-url) no-repeat center / contain;
}

/* ROUND 2 / bug 56 A/B (2026-08-15): the inline-SVG variant. It must occupy EXACTLY the box a masked div
   occupies, because the experiment is "two adjacent rows, one variable" -- a size difference would give
   Mazhar a second thing to react to and make the answer unreadable. Sizing comes from .shell-nav-icon
   below, which both variants carry; this rule only supplies what an <svg> needs and a <div> does not.
   Note there is deliberately NO background-color and NO mask here: removing those two is the entire point
   of the variant. `stroke: currentColor` is inherited from the element's own attribute, so the ONE
   variable the two rows still share is the colour -- which is what makes "neither paints" a readable
   result (currentColor is the fault) rather than an ambiguous one. */
.menu-icon-inline {
    display: block;
    fill: none;
    overflow: visible;
}

.shell-nav-icon {
    flex: 0 0 1.3rem;
    width: 1.3rem;
    height: 1.3rem;
}

/* 0-REVIEW-9 #6 (2026-08-06): a long name ("Interest Income") wrapped to two lines inside the fixed
   210px rail -- no nav item/sub-item label had nowrap+ellipsis, only the fixed row height clipped
   the wrapped second line. flex: 1 1 auto + min-width: 0 lets the label actually shrink inside its
   flex row (the browser default min-width: auto on a flex child blocks ellipsis otherwise); the
   native title= tooltip every item already carries (AppShell.razor, since 0-MENU hardening) covers
   "genuinely long names" once truncated. */
.shell-nav-trigger-label,
.shell-nav-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: left;
}

/* COMPONENT-SPECS.md §9 "Header"/"Divider" -- the collapse toggle's own row, above everything else
   in the rail. flex: 0 0 48px matches the top bar's own height so the toggle visually aligns with it. */
.shell-nav-header {
    flex: 0 0 48px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--st-space-2);
    padding: 0 0 0 var(--st-space-3);
}

/* 0-MENU-2 #1 (2026-08-06): signed-in account name, prod's own placement (beside the collapse
   arrow). Same shrink-and-ellipsis rule as every other nav label (see .shell-nav-trigger-label's
   own comment) -- a long store name must not push the toggle button out of the rail. Hidden on the
   collapsed rail alongside every other label, .shell-nav-collapsed's existing rule below. */
/* SPRINT-QA 2026-08-14 #12: the store/account name in the topbar, beside Support and Billing.
   max-width rather than flex:1 -- the topbar actions sit at the right edge and a long store name
   must not push Support/Billing/theme off it. .shell-nav-account-name below is no longer rendered
   by AppShell; it is left in place because the collapsed-rail rules still reference it, and
   deleting dead CSS belongs to a real sweep, not to a QA pass. */
/* R4 B3 (Mazhar, 2026-08-17): "Store name at upper right: shift left / ensure fully visible." The 18ch
   cap was set when this moved into the topbar and it ellipsises most real store names -- and because the
   actions cluster is right-aligned, the missing characters are the ones nearest the right edge, which is
   what "shift left" describes from the outside: the name looks pushed off the screen. Widening the cap
   grows the span leftwards into the empty middle of the ribbon rather than pushing Support/Billing/gear
   off the right, since those are laid out after it in the same flex row.

   The vw term is what keeps that true on a narrow window, where 32ch would be most of the ribbon; the
   right margin stops the name butting straight into the "Switch account" link. Ellipsis stays as the
   last resort for a genuinely enormous name, with the full value already on the title attribute. */
.shell-topbar-account-name {
    max-width: min(32ch, 30vw);
    margin-right: var(--st-space-2);
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 600;
    color: var(--st-text-on-chrome);
}

.shell-nav-account-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 600;
    color: var(--st-text-on-chrome);
}

.shell-nav-collapse-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--st-tap-min);
    height: var(--st-tap-min);
    border: none;
    border-radius: var(--st-radius-sm);
    background: transparent;
    color: var(--st-text-on-chrome-muted);
    font-size: var(--st-text-base);
    cursor: pointer;
}

.shell-nav-collapse-toggle:hover {
    background: var(--st-surface-hover-on-chrome);
    color: var(--st-text-on-chrome);
}

.shell-nav-divider {
    height: 1px;
    width: calc(100% - 16px);
    margin: 0 8px 8px;
    background: var(--st-border-on-chrome);
    opacity: 0.5;
}

/* Bug ledger #56 (2026-08-13, MEASURED on beta with a logged-in browser, not read from source --
   source reading had already failed twice here). Mazhar: "Dashboard still without icon and height
   still short", and only ever on Dashboards and Reports. Those two are the ONLY top-level rows that
   render as a bare <a class="shell-nav-item"> (AppShell.razor's `entry.Route is not null` branch);
   every other row is a <button> WRAPPED in .shell-nav-group-wrap. .shell-nav is a flex COLUMN with
   overflow-y:auto, so its direct children are flex items with the default flex-shrink:1 -- when the
   rail's rows overflow, `height: 44px` is only a hypothetical size and the item is shrunk down to
   its own min-content, which for these rows is the 18px icon box. The group rows escape it purely
   by accident of nesting: the WRAP shrinks, but the button inside it is not a flex item, so its
   height stands. Measured on beta: link branch 18.19px vs group branch 44px, a 25.81px gap, with
   the icon present and its mask asset 200 on every row -- so this was never a missing icon or a
   missing sec_menu value (both eliminated by query on stage AND prod), it is a squashed row that
   reads as one. flex-shrink:0 on both branches makes the declared height the real height. */
.shell-nav-item {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    height: var(--st-nav-item-h);
    flex-shrink: 0;
    padding: 0 var(--st-space-3);
    border-radius: var(--st-radius-sm);
    color: var(--st-text-on-chrome);
    text-decoration: none;
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    transition: background var(--st-duration-fast) var(--st-ease);
}

.shell-nav-item:hover {
    background: var(--st-surface-hover-on-chrome);
}

.shell-nav-item.active {
    background: var(--st-surface-active-on-chrome);
    /* LOGIN-SPEC §9.2: this is a GRAPHIC (the active-page indicator), not a focus ring that
       happens to be active -- --st-accent-on-chrome is the honest name for it now. */
    box-shadow: inset 3px 0 0 var(--st-accent-on-chrome);
}

.shell-nav-item-disabled {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    height: var(--st-nav-item-h);
    /* Bug #56, same reason as .shell-nav-item above: also a direct flex child of .shell-nav. */
    flex-shrink: 0;
    padding: 0 var(--st-space-3);
    color: var(--st-text-on-chrome-muted);
    opacity: var(--st-disabled-opacity);
    cursor: default;
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
}

.shell-nav-item-disabled small {
    font-size: var(--st-text-xs);
}

/* Item 1 (2026-08-07 pen, 0-429-REFRESH): the "never a dead end" banner -- AppSession retries a 429
   automatically before this ever shows; this is specifically the still-failed case, so it needs to
   read as recoverable (a real button), not as one more wall of red text on top of an already-empty
   nav. */
.shell-nav-session-error {
    display: flex;
    flex-direction: column;
    gap: var(--st-space-2);
    padding: var(--st-space-3);
    margin-bottom: var(--st-space-2);
    border-radius: var(--st-radius-sm);
    background: var(--st-surface-hover-on-chrome);
    color: var(--st-text-on-chrome);
    font-size: var(--st-text-xs);
}

.shell-nav-session-error p {
    margin: 0;
}

.shell-nav-session-error .kernel-btn {
    align-self: flex-start;
}

/* Group + sub-item (COMPONENT-SPECS.md §9) -- 0-MENU (2026-08-05): the spec already documented
   these tokens/metrics, this is the first screen that actually needed them (the nav was a flat list
   until this pass). */
.shell-nav-group {
    width: 100%;
    justify-content: space-between;
    background: transparent;
    border: none;
    cursor: pointer;
}

.shell-nav-chevron {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--st-text-on-chrome-muted);
    transition: transform var(--st-duration-fast) var(--st-ease);
}

.shell-nav-chevron-open {
    transform: rotate(90deg);
}

/* Positioning anchor for the collapsed rail's flyout (COMPONENT-SPECS.md §9 "Collapsed behaviour")
   -- .shell-nav-children becomes position: absolute against THIS element when collapsed, so the
   flyout opens flush against its own trigger, not pinned to the top of the whole rail. */
.shell-nav-group-wrap {
    position: relative;
    /* Bug #56: this wrap IS a flex item of .shell-nav and was being shrunk below its own button's
       44px, so the buttons were overlapping their wrappers whenever the rail overflowed. The symptom
       hid behind the fact that the BUTTON kept its height (it is not a flex item), which is exactly
       why only the two bare-link rows looked wrong. Pin it so the wrap is always as tall as what it
       contains and .shell-nav's own overflow-y:auto does the scrolling instead of the rows. */
    flex-shrink: 0;
}

/* PEN-2 item 8 (2026-08-08): old app's own submenu transition is `max-height 0.3s ease-in-out,
   opacity 0.3s ease-in-out` (SellerTally.WebUI.Blazor/wwwroot/Styles/Shared/Layout/NavMenu.css) --
   matched here exactly. 500px is a generous fixed ceiling (not a JS-measured scrollHeight, which
   old app's own CSS doesn't appear to use either) -- comfortably larger than any real group's child
   list, so the transition always runs its full visual duration against actual content height rather
   than snapping to it. Requires the element to always be in the DOM (AppShell.razor no longer wraps
   this in @if) -- a CSS transition can't animate something that doesn't exist yet. */
.shell-nav-children {
    display: flex;
    flex-direction: column;
    /* Bug ledger #23: same rhythm as .shell-nav's own row gap above -- kept in sync deliberately so
       top-level rows and their sub-items read as one consistent list, not two different densities. */
    gap: 2px;
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
}

.shell-nav-children.shell-nav-children-open {
    max-height: 500px;
    opacity: 1;
}

.shell-nav-subitem {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    height: var(--st-nav-subitem-h);
    padding: 0 var(--st-space-3) 0 40px;
    border-radius: var(--st-radius-sm);
    color: var(--st-text-on-chrome);
    text-decoration: none;
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    transition: background var(--st-duration-fast) var(--st-ease);
}

.shell-nav-subitem:hover {
    background: var(--st-surface-hover-on-chrome);
}

.shell-nav-subitem.active {
    background: var(--st-surface-active-on-chrome);
    box-shadow: inset 3px 0 0 var(--st-accent-on-chrome);
}

.shell-nav-subitem.shell-nav-item-disabled {
    height: var(--st-nav-subitem-h);
    padding: 0 var(--st-space-3) 0 40px;
}

/* COMPONENT-SPECS.md §9 "Collapsed behaviour" (0-MENU hardening, 2026-08-05): labels hidden (never
   shrunk), icons stay full size and centred, the active item keeps its 3px marker (untouched --
   .shell-nav-item.active above already applies regardless of width), every trigger gets a native
   title= tooltip (already on every item, not just collapsed, see AppShell.razor), and a group opens
   as a flyout instead of expanding inline. .shell-nav-trigger-label is deliberately a DIFFERENT class
   from the sub-items' own .shell-nav-label -- the flyout's contents must keep their labels even while
   the rail itself is collapsed, so the hide-on-collapse rule below must not reach inside
   .shell-nav-children at all. */
.shell-nav-collapsed {
    width: var(--st-nav-w-collapsed);
}

.shell-nav-collapsed .shell-nav-trigger-label {
    display: none;
}

/* W157 (Mazhar, 2026-08-12). The account name is the ONLY thing on screen that says whose books
   you are in, and one login attaches to many accounts -- an accountant misreading it is how a
   transaction lands in the wrong company's ledger. So it must NOT disappear when the rail
   collapses, which is what the old `display: none` here did. It now truncates instead, with the
   full value on hover via the element's own `title`. The header stacks on the collapsed rail
   because 70px cannot hold a name and the toggle side by side. */
.shell-nav-collapsed .shell-nav-header {
    flex: 0 0 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--st-space-1);
    padding: var(--st-space-2) var(--st-space-1);
}

.shell-nav-collapsed .shell-nav-account-name {
    flex: 0 0 auto;
    max-width: 100%;
    font-size: 10px;
    line-height: 1.2;
    text-align: center;
}

.shell-nav-collapsed .shell-nav-chevron {
    display: none;
}

.shell-nav-collapsed > .shell-nav-item,
.shell-nav-collapsed > .shell-nav-item-disabled,
.shell-nav-collapsed .shell-nav-group-wrap > .shell-nav-item {
    justify-content: center;
    padding: 0;
    gap: 0;
}

/* Bug #17 (2026-08-10): `position: fixed`, not `absolute` -- .shell-nav has `overflow-y: auto` for
   its own scrollbar, which clips ANY absolutely-positioned descendant that extends past the 70px
   collapsed rail's own right edge (confirmed live: the flyout opened, class and opacity both
   correct, but was never actually visible on screen). `fixed` escapes ALL ancestor overflow
   clipping by definition, so top/left are no longer expressible as a CSS offset from the trigger --
   AppShell.razor's OnGroupMouseEnter measures the real trigger position via nav-flyout.js and binds
   it as an inline style instead. Max-height-based clipping isn't meaningful here (there's no layout
   height to reclaim by collapsing it), so this overrides the base rule's height/overflow approach
   with a plain fade + slight rise instead, closed by default via opacity/visibility (not max-height)
   so this flyout can also be revealed on HOVER (PEN-2 item 1, AppShell.razor's OnGroupMouseEnter)
   without a click first. */
.shell-nav-collapsed .shell-nav-children {
    position: fixed;
    min-width: 200px;
    background: var(--st-surface-overlay);
    box-shadow: var(--st-shadow-md);
    border-radius: var(--st-radius-md);
    padding: var(--st-space-2);
    z-index: 20;
    max-height: none;
    overflow: visible;
    visibility: hidden;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s linear 0.2s;
}

.shell-nav-collapsed .shell-nav-children.shell-nav-children-open {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s linear 0s;
}

/* Bug #17 follow-up, found while verifying the position:fixed fix live: `.shell-nav-subitem`'s
   *-on-chrome tokens (color, hover/active background) are tuned for sitting on the DARK rail
   background (the inline-accordion case) -- but the collapsed flyout is `--st-surface-overlay`, a
   LIGHT popover card, not chrome. Left unpaired, this rendered literally white text on a white
   background: invisible, not just low-contrast. Re-paired to the same *-primary/*-hover tokens
   every other light-surface popover in this file uses. Left padding also drops the 40px inline-
   accordion indent -- that aligns a sub-item under its own parent icon in the rail; a standalone
   floating card has no parent icon to align under, so it read as a large empty gap instead. */
.shell-nav-collapsed .shell-nav-children .shell-nav-subitem {
    color: var(--st-text-primary);
    padding-left: var(--st-space-3);
}

.shell-nav-collapsed .shell-nav-children .shell-nav-subitem:hover {
    background: var(--st-surface-hover);
}

.shell-nav-collapsed .shell-nav-children .shell-nav-subitem.active {
    background: var(--st-surface-active);
    box-shadow: inset 3px 0 0 var(--st-accent);
}

.shell-main {
    flex: 1;
    min-width: 0;
    overflow: auto;
    padding: var(--st-space-4);
    background: var(--st-surface-app);
}

/* 0-LAYOUT (2026-08-04), rule 4: "Content has a max width -- nothing stretches just because the
   monitor is wide. Full-width is EARNED: data grids and report tables only." Two variants because
   data screens and form screens earn different widths -- a screen applies exactly one to its own
   root element. Neither is used by Dashboard (waits for DASHBOARD-SPEC.md, item 0x) or Login (its
   own spec already encodes correct desktop layout via .lg-shell). */
.st-page-wide {
    max-width: 1400px;
    margin: 0 auto;
}

.st-page-narrow {
    max-width: 800px;
    margin: 0 auto;
}

@media (max-width: 768px) {
    /* Task 28 (2026-08-04): CI's mobile-viewport check measured 89-98px of real horizontal overflow on
       every authenticated screen -- traced to .shell-topbar, not the nav or DataGrid (neither was
       independently overflowing at 375px in direct testing). .shell-brand's flex-basis pins it to the
       desktop NAV's width so the wordmark aligns above the sidebar -- on mobile the nav isn't a sidebar
       anymore, so that fixed 194px is pure waste and the single largest contributor. */
    .shell-brand {
        flex: 0 1 auto;
    }

    /* 0-REVIEW-9 #4 (2026-08-06): "keep phone behavior sane -- description may drop on narrow
       widths." Title stays (it's short and the whole reason the ribbon exists); the description is
       the long, droppable part -- topbar space is already tight against the account name/icons at
       this width. */
    .shell-page-name-sep,
    .shell-page-name-description {
        display: none;
    }

    .shell-page-name {
        margin-left: var(--st-space-2);
    }

    .shell-user-email {
        display: none;
    }

    .shell-hamburger {
        display: inline-flex;
    }

    /* COMPONENT-SPECS.md §9 Phone: "the nav becomes an overlay drawer... position: fixed, width:
       --st-nav-w, height: 100vh, z-index: 1000, transform: translateX(-100%) -> translateX(0)".
       Replaces the earlier always-visible wrapped-row stopgap (shipped same night to kill the
       overflow bug fast) with the real spec behavior -- hidden by default, opened via the
       hamburger, not permanently eating vertical space above every screen's content. */
    .shell-nav {
        position: fixed;
        inset: 0 auto 0 0;
        width: var(--st-nav-w);
        z-index: 1000;
        flex-direction: column;
        flex-wrap: nowrap;
        transform: translateX(-100%);
        transition: transform var(--st-duration-slow) var(--st-ease);
        border-right: 1px solid var(--st-border-on-chrome);
        border-bottom: none;
        box-shadow: var(--st-shadow-md);
    }

    .shell-nav.shell-nav-open {
        transform: translateX(0);
    }

    .shell-nav-close {
        display: flex;
        align-items: center;
        justify-content: center;
        align-self: flex-end;
        width: var(--st-tap-min);
        height: var(--st-tap-min);
        margin-bottom: var(--st-space-2);
        border: none;
        border-radius: var(--st-radius-sm);
        background: transparent;
        color: var(--st-text-on-chrome);
        font-size: var(--st-text-base);
        cursor: pointer;
    }

    .shell-nav-close:hover {
        background: var(--st-surface-hover-on-chrome);
    }

    /* Backdrop: a real <button>, not a bare div, so it's natively focusable/clickable/labeled --
       matches .st-modal-scrim's own reasoning (COMPONENT-SPECS.md §7) applied to the drawer. */
    .shell-nav-scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 999;
        border: none;
        padding: 0;
        background: var(--st-scrim);
        cursor: pointer;
    }

    .shell-nav-item,
    .shell-nav-item-disabled,
    .shell-nav-subitem {
        height: var(--st-tap-min);
    }

    /* COMPONENT-SPECS.md §9: "The drawer never coexists with the collapsed 70px state -- that
       variant is desktop-only." The toggle only makes sense on desktop; hidden here. Belt-and-
       suspenders against the state itself: isNavCollapsed lives in Blazor memory, not tied to
       viewport width, so a user who collapses on desktop and then resizes/rotates down to phone
       width (no page reload, same live SPA session) must not end up with a squashed 70px drawer --
       every collapsed-only visual effect is neutralised here regardless of the class still being
       present in the DOM. */
    .shell-nav-header {
        display: none;
    }

    .shell-nav.shell-nav-collapsed {
        width: var(--st-nav-w);
    }

    .shell-nav-collapsed .shell-nav-trigger-label {
        display: block;
    }

    .shell-nav-collapsed .shell-nav-chevron {
        display: inline-flex;
    }

    .shell-nav-collapsed > .shell-nav-item,
    .shell-nav-collapsed > .shell-nav-item-disabled,
    .shell-nav-collapsed .shell-nav-group-wrap > .shell-nav-item {
        justify-content: flex-start;
        padding: 0 var(--st-space-3);
        gap: var(--st-space-2);
    }

    /* PEN-2 item 8 (2026-08-08): on phone, "collapsed" renders inline (position:static above), not as
       the desktop flyout -- so it needs the desktop flyout rule's opacity/visibility/transform
       CLOSED-state properties overridden back to the base rule's max-height approach, otherwise
       visibility:hidden (unlike display:none) still reserves full layout height while "closed,"
       permanently pushing every nav item below it down regardless of expand state. */
    .shell-nav-collapsed .shell-nav-children {
        position: static;
        background: transparent;
        box-shadow: none;
        padding: 0;
        max-height: 0;
        overflow: hidden;
        visibility: visible;
        transform: none;
        transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
    }

    .shell-nav-collapsed .shell-nav-children.shell-nav-children-open {
        max-height: 500px;
        opacity: 1;
    }
}

/* Error boundary */

.error-boundary-fallback {
    max-width: 28rem;
    margin: var(--st-space-6) auto;
    padding: var(--st-space-5);
    text-align: center;
    background: var(--st-surface);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    box-shadow: var(--st-shadow-md);
}

.error-boundary-title {
    font-family: var(--st-font-ui);
    font-weight: 600;
    font-size: var(--st-text-lg);
    margin: 0 0 var(--st-space-2);
    color: var(--st-text-primary);
}

.error-boundary-detail {
    color: var(--st-text-secondary);
    margin: 0 0 var(--st-space-4);
}

.error-boundary-reload {
    height: var(--st-control-h);
    padding: 0 var(--st-space-4);
    background: var(--st-accent-fill);
    color: var(--st-text-on-fill);
    border: 1px solid var(--st-accent-fill);
    border-radius: var(--st-radius-sm);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    cursor: pointer;
}

/* ── Loading spinner (PEN-2 item 10, 2026-08-08) ───────────────────────────────────────────────
   Was N shimmering placeholder rows (COMPONENT-SPECS.md §5's original skeleton spec) -- Mazhar
   wants a real wait indicator instead, app-wide. One shared component (LoadingSkeleton.razor,
   ~20 screens already use it), so replacing what it renders here fixes every screen at once,
   nothing per-screen to touch. */

.loading-spinner {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--st-space-3);
    padding: var(--st-space-6) 0;
}

.loading-spinner-ring {
    width: 32px;
    height: 32px;
    border: 3px solid var(--st-border-subtle);
    border-top-color: var(--st-accent);
    border-radius: 50%;
    animation: loading-spinner-spin 0.8s linear infinite;
}

.loading-spinner-text {
    color: var(--st-text-muted);
    font-size: var(--st-text-sm);
}

@keyframes loading-spinner-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .loading-spinner-ring {
        animation: none;
    }
}

/* ── Empty state (COMPONENT-SPECS.md §4) ───────────────────────────────────────────────────── */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--st-space-3);
    padding: 40px var(--st-space-5);
    text-align: center;
    background: var(--st-surface);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
}

.empty-state-title {
    font-family: var(--st-font-ui);
    font-size: 15px;
    font-weight: 600;
    color: var(--st-text-primary);
    margin: 0;
}

.empty-state-detail {
    font-size: var(--st-text-sm);
    color: var(--st-text-muted);
    max-width: 34ch;
    margin: 0;
}

/* Auth screens (Login etc.) -- pre-boot chrome doesn't apply here (no shell yet), plain surface. */

.auth-card {
    max-width: 24rem;
    margin: var(--st-space-6) auto;
    padding: var(--st-space-5);
    background: var(--st-surface);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    box-shadow: var(--st-shadow-md);
}

.auth-title {
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xl);
    font-weight: 700;
    margin: 0 0 var(--st-space-4);
    color: var(--st-text-primary);
}

.auth-field {
    display: flex;
    flex-direction: column;
    gap: var(--st-space-1);
    margin-bottom: var(--st-space-3);
}

.auth-field label {
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    color: var(--st-text-secondary);
}

.auth-field input {
    height: var(--st-control-h);
    font-family: var(--st-font-text);
    font-size: var(--st-text-sm);
    padding: 0 10px;
    border: 1px solid var(--st-border-input);
    border-radius: var(--st-radius-sm);
    background: var(--st-surface);
    color: var(--st-text-primary);
}

.auth-field input:hover {
    border-color: var(--st-text-secondary);
}

.auth-field input:focus-visible {
    outline: 2px solid var(--st-focus-ring);
    outline-offset: 1px;
    border-color: var(--st-accent);
}

.auth-submit {
    width: 100%;
    height: var(--st-control-h);
    background: var(--st-accent-fill);
    color: var(--st-text-on-fill);
    border: 1px solid var(--st-accent-fill);
    border-radius: var(--st-radius-sm);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    cursor: pointer;
    transition: background var(--st-duration-fast) var(--st-ease);
}

.auth-submit:hover:not(:disabled) {
    background: var(--st-accent-fill-hover);
}

.auth-submit:disabled {
    opacity: var(--st-disabled-opacity);
    cursor: not-allowed;
}

.auth-error {
    color: var(--st-danger-on-subtle);
    background: var(--st-danger-subtle);
    box-shadow: inset 3px 0 0 var(--st-danger);
    padding: var(--st-space-3);
    border-radius: var(--st-radius-sm);
    font-size: var(--st-text-sm);
    margin-bottom: var(--st-space-3);
}

/* Charts (Kernel/Components/Charts/*.razor) -- shared here rather than per-component CSS isolation
   because BarChart/LineChart/DonutChart intentionally reuse the same class names for the same
   visual language (one axis style, one label style, everywhere a chart appears). Chart colours are
   categorical only (--st-chart-*) and never carry meaning -- COMPONENT-SPECS.md's own rule. */

.chart-svg {
    width: 100%;
    height: auto;
    max-height: 220px;
}

.chart-axis-line {
    stroke: var(--st-border-strong);
    stroke-width: 1;
}

.chart-axis-line-zero {
    stroke: var(--st-text-muted);
    stroke-dasharray: 3 3;
}

.chart-axis-label {
    fill: var(--st-text-secondary);
    font-size: 9px;
    font-family: var(--st-font-ui);
}

.chart-bar {
    fill: var(--st-accent);
}

.chart-bar-negative {
    fill: var(--st-danger);
}

.chart-line {
    fill: none;
    stroke: var(--st-accent);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.chart-area-fill {
    fill: var(--st-accent);
    opacity: 0.18;
}

.chart-point {
    fill: var(--st-surface);
    stroke: var(--st-accent);
    stroke-width: 1.5;
}

.donut-chart-wrap {
    display: flex;
    align-items: center;
    gap: var(--st-space-4);
    flex-wrap: wrap;
}

.chart-donut-svg {
    width: 120px;
    height: 120px;
    max-height: none;
    flex-shrink: 0;
}

.chart-donut-slice {
    stroke: var(--st-surface);
    stroke-width: 1;
}

.chart-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--st-space-1);
    flex: 1;
    min-width: 140px;
}

.chart-legend-item {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    font-size: var(--st-text-xs);
    color: var(--st-text-secondary);
}

.chart-legend-swatch {
    width: 10px;
    height: 10px;
    border-radius: var(--st-radius-sm);
    flex-shrink: 0;
}

.chart-legend-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chart-legend-value {
    color: var(--st-text-primary);
    font-family: var(--st-font-num);
    font-variant-numeric: tabular-nums;
}

/* ── Form field (COMPONENT-SPECS.md §2) ────────────────────────────────────────────────────── */

.st-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
    gap: var(--st-space-4) var(--st-space-5);
}

/* 0-VISUAL-PARITY finding 2 (2026-08-05), COMPONENT-SPECS.md §1a "Filter bar": one shared
   implementation instead of TransactionSearch/SubAccountSearch each carrying their own near-
   identical copy. A horizontal row of .kernel-field controls (each already capped to its own
   content-appropriate width -- date/number inputs narrower still, see .kernel-input[type] below)
   that wraps to additional rows before it ever stacks to a single narrow column. */
.kernel-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: var(--st-space-4);
}

@media (max-width: 767px) {
    .kernel-filter-bar {
        flex-direction: column;
        align-items: stretch;
    }
}

.kernel-field {
    display: flex;
    flex-direction: column;
    gap: var(--st-space-1);
    /* 0-LAYOUT (2026-08-04), rule 2: "Inputs size to content, never to viewport." .kernel-field is a
       flex item everywhere it's used, and flexbox's default align-items: stretch was silently
       stretching every field to its ANCESTOR's full width -- with no width cap anywhere in the
       chain, that ancestor was ultimately the viewport. max-width here (not just on .kernel-input)
       means the label/help text block stays the same width as its control, not just the control. */
    max-width: 400px;
}

.kernel-field > span:first-child {
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    color: var(--st-text-secondary);
}

.kernel-field-hint {
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    color: var(--st-text-muted);
}

.kernel-field-hint a {
    color: var(--st-text-muted);
    text-decoration: underline;
}

.kernel-field-hint a:hover {
    color: var(--st-text-secondary);
}

.kernel-input {
    width: 100%;
    height: var(--st-control-h);
    border: 1px solid var(--st-border-input);
    border-radius: var(--st-radius-sm);
    padding: 0 10px;
    background: var(--st-surface);
    color: var(--st-text-primary);
    font-family: var(--st-font-text);
    font-size: var(--st-text-sm);
    transition: border-color var(--st-duration-fast) var(--st-ease);
}

.kernel-input::placeholder {
    color: var(--st-text-muted);
}

.kernel-input:hover {
    border-color: var(--st-text-secondary);
}

.kernel-input:focus-visible {
    outline: 2px solid var(--st-focus-ring);
    outline-offset: 1px;
    border-color: var(--st-accent);
}

.kernel-input:disabled {
    background: var(--st-surface-alt);
    opacity: var(--st-disabled-opacity);
    cursor: not-allowed;
}

.kernel-input[aria-invalid="true"] {
    border-color: var(--st-danger);
}

textarea.kernel-input {
    height: auto;
    padding: var(--st-space-2) 10px;
}

/* 0-LAYOUT rule 2's own examples: "date fields date-wide, amount fields amount-wide" -- narrower
   than the 400px generic-text cap, sized to what the content actually needs. A .kernel-field
   wrapping one of these overrides its own max-width to match, so the label doesn't sit orphaned
   above a much narrower control. */
.kernel-input[type="date"],
.kernel-field:has(> .kernel-input[type="date"]) {
    max-width: 160px;
}

.kernel-input[type="number"],
.kernel-field:has(> .kernel-input[type="number"]) {
    max-width: 140px;
}

/* COMPONENT-SPECS.md §2 Form field: "--st-font-num right-aligned for amounts" -- every numeric
   control, not just read-only totals. Found unapplied everywhere during the 0-CONSISTENCY sweep
   (2026-08-05): TrnEdit's qty/unit-price/amount cells and Form1099K's monthly inputs were all
   left-aligned in the body text font. */
.kernel-input[type="number"] {
    text-align: right;
    font-family: var(--st-font-num);
    font-variant-numeric: tabular-nums;
}

/* Bug ledger #14 (2026-08-10, Mazhar): "a plain text box is easier to type into" -- the native
   up/down spinner steals horizontal space from a right-aligned amount and invites accidental
   scroll-wheel value changes; removed app-wide (every numeric input, not just .kernel-input --
   the checklist editor's own type="number" cells don't carry that class). Firefox needs the
   standard property, WebKit/Blink (Chrome/Edge/Safari) only respect the pseudo-elements. */
input[type="number"] {
    -moz-appearance: textfield;
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* R4 B6 (Mazhar, 2026-08-17): "Setup -> Sub Account: Priority field = numeric up/down control." The
   rule directly above removed the spinner from EVERY number input app-wide for bug 14, so simply
   marking the field type="number" — which it already was — could never produce one. This is the
   deliberate opt-back-in, and it is a class rather than a relaxation of the rule above because the
   reason bug 14 exists is unchanged for amounts: a spinner beside a right-aligned amount steals space
   and turns a stray scroll into a silent value change. Priority is neither right-aligned nor an amount,
   it is a small ordinal a user nudges by one, which is the one case a stepper is genuinely better at.

   Both properties are needed for the same reason the rule above needs both: Firefox honours the
   standard property, Chrome/Edge/Safari only the pseudo-elements. The class-plus-pseudo selector
   outranks the element-plus-pseudo rule above, so this wins without touching it. */
.kernel-input-stepper {
    -moz-appearance: number-input;
}

.kernel-input-stepper::-webkit-inner-spin-button,
.kernel-input-stepper::-webkit-outer-spin-button {
    -webkit-appearance: inner-spin-button;
    opacity: 1;
    margin: 0;
}

/* ── Shared amount formatting (COMPONENT-SPECS.md §0 type scale: "every amount" gets
   --st-font-num with tabular-nums so digits align down a column). 0-CONSISTENCY (2026-08-05):
   this rule existed in the spec since day one but no shared class carried it -- each screen that
   rendered a total either applied tabular-nums alone (CoaTree) or nothing at all (TrnEdit,
   TransactionSearch, Form1099K). One class now, applied everywhere a monetary amount renders. */
.st-amount {
    font-family: var(--st-font-num);
    font-variant-numeric: tabular-nums;
}

/* ── Buttons and the placement grammar (COMPONENT-SPECS.md §3) ────────────────────────────────
   Secondary is the unmarked default (.kernel-btn alone) -- matches the existing convention of every
   screen already reaching for it as the base class. */

.kernel-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: var(--st-control-h);
    padding: 0 var(--st-space-4);
    min-width: 132px;
    border: 1px solid var(--st-border-input);
    border-radius: var(--st-radius-sm);
    background: var(--st-surface);
    color: var(--st-text-primary);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--st-duration-fast) var(--st-ease), border-color var(--st-duration-fast) var(--st-ease);
}

/* SPRINT-QA 2026-08-14 #26 -- THIS RULE WAS THE WHOLE BUG, and it is worth spelling out because it
   looked like three unrelated complaints. Mazhar: "Add turns black in dark mode; Save goes black in
   dark mode (same as Cancel) while distinct in light."
   `[data-theme="dark"] .kernel-btn` has specificity (0,2,0) -- an attribute selector counts as a
   class -- while `.kernel-btn-primary` has (0,1,0). Every primary button also carries .kernel-btn,
   so in DARK MODE ONLY this rule outranked the primary fill and blanked it to transparent: Save and
   Add rendered identical to Cancel, while light mode (which has no such rule) kept them blue. The
   fix is to exclude the buttons that own their own background, not to raise their specificity --
   raising it would leave the next variant to rediscover this. */
[data-theme="dark"] .kernel-btn:not(.kernel-btn-primary):not(.kernel-btn-destructive) {
    background: transparent;
}

.kernel-btn:hover:not(:disabled) {
    background: var(--st-surface-hover);
    border-color: var(--st-text-secondary);
}

.kernel-btn:active:not(:disabled) {
    background: var(--st-surface-active);
}

.kernel-btn-primary {
    border-color: var(--st-accent-fill);
    background: var(--st-accent-fill);
    color: var(--st-text-on-fill);
}

.kernel-btn-primary:hover:not(:disabled) {
    background: var(--st-accent-fill-hover);
    border-color: var(--st-accent-fill-hover);
}

.kernel-btn-primary:active:not(:disabled) {
    background: var(--st-accent-fill-active);
    border-color: var(--st-accent-fill-active);
}

.kernel-btn-destructive {
    border-color: var(--st-danger-fill);
    background: var(--st-danger-fill);
    color: var(--st-text-on-fill);
}

.kernel-btn-destructive:hover:not(:disabled) {
    background: var(--st-danger-fill-hover);
    border-color: var(--st-danger-fill-hover);
}

.kernel-btn:disabled {
    opacity: var(--st-disabled-opacity);
    cursor: not-allowed;
    pointer-events: none;
}

.kernel-btn-sm {
    height: var(--st-control-h-sm);
    min-width: 0;
}

.kernel-btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--st-icon-btn);
    height: var(--st-icon-btn);
    min-width: 0;
    padding: 0;
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-sm);
    background: transparent;
    color: var(--st-text-secondary);
    cursor: pointer;
    transition: background var(--st-duration-fast) var(--st-ease);
}

.kernel-btn-icon:hover:not(:disabled) {
    background: var(--st-surface-hover);
}

/* ROUND 2 (2026-08-15), Mazhar on the transaction search grid: "delete button red (or per Claude
   Design palette) - dull gray now". The gray is .kernel-btn-icon's own --st-text-secondary, which is
   correct for a neutral icon button and wrong for the one action on the row that destroys something.

   Colour only -- geometry stays on the base class, because his OTHER item in the same list is that the
   edit screen's delete icon must be CONSISTENT with this one. A danger variant that also changed size
   would make those two harder to reconcile, not easier. --st-danger is themed (it lightens to #f2707c
   in dark mode), so this stays legible on the dark chrome instead of going muddy. */
.kernel-btn-icon-danger {
    color: var(--st-danger);
    border-color: var(--st-danger-subtle);
}

.kernel-btn-icon-danger:hover:not(:disabled) {
    background: var(--st-danger-subtle);
}

/* Placement grammar -- primary last in DOM order (tab order = visual order, never `order:`),
   cancel immediately to its left, constructive secondary actions in a separate left-hand group. */
.st-actionbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--st-space-4);
}

.st-actionbar__primary {
    display: flex;
    gap: var(--st-space-4);
    margin-left: auto;
}

@media (max-width: 640px) {
    .st-actionbar {
        position: sticky;
        bottom: 0;
        background: var(--st-surface);
        border-top: 1px solid var(--st-border);
        padding: var(--st-space-3);
    }

    .st-actionbar__primary {
        width: 100%;
    }

    .st-actionbar__primary .kernel-btn {
        flex: 1;
        min-width: 0;
        height: var(--st-tap-min);
    }

    .st-actionbar__primary .kernel-btn-primary {
        flex: 1.4;
    }
}

/* ── Banner / alert (COMPONENT-SPECS.md §6) ────────────────────────────────────────────────── */

.kernel-error {
    display: flex;
    align-items: center;
    gap: var(--st-space-3);
    padding: var(--st-space-3);
    border-radius: var(--st-radius-sm);
    background: var(--st-danger-subtle);
    box-shadow: inset 3px 0 0 var(--st-danger);
    color: var(--st-danger-on-subtle);
    font-size: var(--st-text-sm);
}

.kernel-banner {
    display: flex;
    align-items: center;
    gap: var(--st-space-3);
    padding: var(--st-space-3);
    border-radius: var(--st-radius-sm);
    font-size: var(--st-text-sm);
}

.kernel-banner-message {
    flex: 1;
}

.kernel-banner-info {
    background: var(--st-accent-subtle);
    box-shadow: inset 3px 0 0 var(--st-accent);
    color: var(--st-accent-on-subtle);
}

.kernel-banner-success {
    background: var(--st-success-subtle);
    box-shadow: inset 3px 0 0 var(--st-success);
    color: var(--st-success-on-subtle);
}

.kernel-banner-warning {
    background: var(--st-warning-subtle);
    box-shadow: inset 3px 0 0 var(--st-warning);
    color: var(--st-warning-on-subtle);
}

.kernel-banner-danger {
    background: var(--st-danger-subtle);
    box-shadow: inset 3px 0 0 var(--st-danger);
    color: var(--st-danger-on-subtle);
}

/* A labeled card grouping a few related fields -- introduced for AccountSettings, promoted here
   once Form1099K needed the identical pattern: Blazor's CSS isolation means a component-scoped
   class never applies outside its own .razor file, so a second screen needing the same look
   belongs in the shared kernel stylesheet, not copy-pasted into a second .razor.css. */
.kernel-card {
    display: flex;
    flex-direction: column;
    gap: var(--st-space-2);
    padding: var(--st-space-4);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    background: var(--st-surface);
}

.kernel-card-title {
    margin: 0 0 var(--st-space-1);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-base);
    font-weight: 600;
    color: var(--st-text-primary);
}

/* TreeView (Kernel/Components/TreeView.razor) -- generic expand/collapse tree. */

.tree-node-row {
    display: flex;
    align-items: center;
    width: 100%;
    padding: var(--st-space-2);
    border: none;
    background: none;
    color: var(--st-text-primary);
    font-family: var(--st-font-text);
    font-size: var(--st-text-sm);
    text-align: left;
    cursor: default;
    border-bottom: 1px solid var(--st-border);
}

.tree-node-row.tree-clickable {
    cursor: pointer;
}

.tree-node-row.tree-clickable:hover {
    background: var(--st-surface-hover);
}

.tree-node-label {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    flex: 1;
    min-width: 0;
}

.tree-toggle,
.tree-toggle-spacer {
    width: 0.75rem;
    flex-shrink: 0;
}

.tree-toggle::before {
    content: "▸";
    color: var(--st-text-secondary);
    display: inline-block;
    transition: transform var(--st-duration-fast) var(--st-ease);
}

.tree-toggle-expanded::before {
    transform: rotate(90deg);
}

.tree-children {
    margin-left: var(--st-space-4);
}

/* Pager (Kernel/Components/Pager.razor) -- secondary buttons at control-h-sm, per grid footer §1. */

.kernel-pager {
    display: flex;
    align-items: center;
    gap: var(--st-space-2);
    font-size: var(--st-text-xs);
    color: var(--st-text-muted);
    font-variant-numeric: tabular-nums;
}

.kernel-pager button {
    height: var(--st-control-h-sm);
    border: 1px solid var(--st-border-input);
    background: var(--st-surface);
    color: var(--st-text-primary);
    border-radius: var(--st-radius-sm);
    padding: 0 var(--st-space-3);
    font-family: var(--st-font-ui);
    font-size: var(--st-text-xs);
    cursor: pointer;
}

.kernel-pager button:hover:not(:disabled) {
    background: var(--st-surface-hover);
}

.kernel-pager button:disabled {
    opacity: var(--st-disabled-opacity);
    cursor: not-allowed;
}

/* CheckListEditor (Kernel/Components/CheckListEditor.razor) -- shared by the recipe and vendor
   pickers on SubAccountEdit. */

.checklist-editor {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--st-text-sm);
}

.checklist-editor th {
    text-align: left;
    color: var(--st-text-secondary);
    font-family: var(--st-font-ui);
    font-weight: 600;
    padding: var(--st-space-2);
    border-bottom: 1px solid var(--st-border-strong);
}

.checklist-editor td {
    padding: var(--st-space-2);
    border-bottom: 1px solid var(--st-border);
    color: var(--st-text-primary);
    vertical-align: middle;
}

.checklist-editor-check-col {
    width: 2.5rem;
}

.checklist-editor input[type="number"],
.checklist-editor input[type="text"] {
    width: 100%;
    max-width: 10rem;
    height: var(--st-control-h-sm);
    border: 1px solid var(--st-border-input);
    border-radius: var(--st-radius-sm);
    padding: 0 var(--st-space-2);
    background: var(--st-surface);
    color: var(--st-text-primary);
    font-family: var(--st-font-text);
}

.checklist-editor input:disabled {
    opacity: var(--st-disabled-opacity);
    background: var(--st-surface-alt);
}

.checklist-editor-empty {
    color: var(--st-text-muted);
    font-size: var(--st-text-sm);
}

/* ── Modal (COMPONENT-SPECS.md §7, Kernel/Components/Modal.razor) ─────────────────────────────── */

.st-modal-scrim {
    position: fixed;
    inset: 0;
    background: var(--st-scrim);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.st-modal {
    background: var(--st-surface-overlay);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    box-shadow: var(--st-shadow-md);
    display: flex;
    flex-direction: column;
    max-height: 90vh;
    width: 100%;
}

.st-modal-sm { max-width: 440px; }
.st-modal-md { max-width: 720px; }
.st-modal-lg { max-width: 960px; }

.st-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--st-space-4) var(--st-space-5);
    border-bottom: 1px solid var(--st-border);
    flex: 0 0 auto;
}

.st-modal-title {
    margin: 0;
    font-family: var(--st-font-ui);
    font-size: var(--st-text-base);
    font-weight: 600;
    color: var(--st-text-primary);
}

.st-modal-body {
    padding: var(--st-space-5);
    font-size: var(--st-text-sm);
    color: var(--st-text-secondary);
    max-height: 70vh;
    overflow-y: auto;
    flex: 1 1 auto;
}

.st-modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--st-space-4);
    padding: var(--st-space-4) var(--st-space-5);
    background: var(--st-surface-alt);
    border-top: 1px solid var(--st-border);
    flex: 0 0 auto;
}

@media (max-width: 640px) {
    .st-modal {
        max-width: 100%;
        width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
    }

    .st-modal-footer {
        position: sticky;
        bottom: 0;
    }

    .st-modal-footer .kernel-btn {
        flex: 1;
        min-width: 0;
    }
}

/* NIGHT-QUEUE full-unpark ruling (2026-08-06): Billing.razor's plan/usage summary. */
.billing-summary {
    display: flex;
    flex-direction: column;
    gap: var(--st-space-2);
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    padding: var(--st-space-4);
}

.billing-summary__row {
    display: flex;
    justify-content: space-between;
    gap: var(--st-space-4);
    font-size: var(--st-text-sm);
}

.billing-summary__label {
    color: var(--st-text-secondary);
}

.billing-summary__value {
    color: var(--st-text-primary);
    font-weight: 600;
}

.billing-recent__title {
    margin: 0;
    font-size: var(--st-text-sm);
    font-weight: 600;
    color: var(--st-text-primary);
}

.billing-recent__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--st-space-1);
}

.billing-recent__list li {
    display: flex;
    justify-content: space-between;
    gap: var(--st-space-4);
    font-size: var(--st-text-sm);
    color: var(--st-text-secondary);
    padding: var(--st-space-1) 0;
    border-bottom: 1px solid var(--st-border);
}

/* NIGHT-QUEUE full-unpark ruling (2026-08-06): SuperAdmin trio (Analytics/SupportAccess/Helpdesk). */
.analytics-section-title {
    margin: var(--st-space-5) 0 0;
    font-size: var(--st-text-sm);
    font-weight: 600;
    color: var(--st-text-primary);
}

.supportaccess-account-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--st-space-2);
}

.supportaccess-account-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--st-space-4);
    width: 100%;
    border: 1px solid var(--st-border);
    border-radius: var(--st-radius-md);
    padding: var(--st-space-3);
    background: var(--st-surface);
    color: var(--st-text-primary);
    font-size: var(--st-text-sm);
    text-align: left;
    cursor: pointer;
}

.supportaccess-account-row:hover {
    border-color: var(--st-accent);
}

/* Persistent banner AppShell shows while browsing a borrowed customer session -- same visual
   family as .kernel-banner-warning (impersonation is a genuinely higher-stakes state than the
   Demo badge, worth a stronger color), but full-width under the topbar rather than inline with it,
   since it needs room for the account name + the End button without cramping the topbar itself. */
.shell-support-access-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--st-space-4);
    background: var(--st-warning-subtle);
    color: var(--st-warning-on-subtle);
    padding: var(--st-space-2) var(--st-space-5);
    font-size: var(--st-text-sm);
    border-bottom: 1px solid var(--st-warning);
}

.shell-support-access-banner .kernel-btn {
    flex-shrink: 0;
}

/* ---- Report breadcrumb -------------------------------------------------------------------
   ROUND 2 (Mazhar, 2026-08-15): "dynamic-report navigation missing on Chart-of-Account tree (no
   path back to top)." Moved here VERBATIM from Features/Reports/DynamicReport.razor.css. It had to
   move rather than be copied: scoped CSS is per-component, so the same markup added to CoaTree
   would have rendered as unstyled buttons and slashes -- a "fix" that looks like a new bug. One
   definition means the two report screens cannot drift apart on what a breadcrumb looks like.
   Values are unchanged, so this is a move, not a restyle. ---------------------------------- */
.report-breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-sm);
}

.report-breadcrumb-link {
    border: none;
    background: transparent;
    padding: 0;
    color: var(--accent);
    font-size: var(--text-sm);
    cursor: pointer;
}

.report-breadcrumb-link:hover {
    text-decoration: underline;
}

.report-breadcrumb-current {
    color: var(--text-secondary);
}

.report-breadcrumb-sep {
    color: var(--text-secondary);
}



/* W527/W536 (2026-08-15): the plan-flow banner's timing line ("starts when your trial ends",
   "next billing cycle", proration). Block-level and quieter than the headline on purpose -- it is
   the sentence that stops a customer clicking through to a payment screen unsure of when they get
   charged, so it must be readable without competing with the headline it qualifies. */
.billing-state__note {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    opacity: 0.85;
}
