/* ============================================================
   NETORBUS — PREMIUM LAYER
   Loaded last, after page stylesheets, because its job is to make
   ~120 independently-styled Blade pages agree with each other.

   Two responsibilities and nothing else:
     1. NORMALISE  — one page masthead, one control size, one focus
                     ring, one card surface, one table, everywhere.
     2. PRESENT    — the effects driven by resources/ts/premium.ts.

   Rules for editing this file:
     • Colour/identity decisions live in tokens.css. Read from it.
     • Never restyle a page's semantic colours here; normalise
       geometry, motion, focus and rhythm only.
     • Every animation must be answered in the reduced-motion block
       at the bottom. An animation that starts at opacity:0 must have
       its opacity explicitly restored there.
   ============================================================ */

/* ------------------------------------------------------------
   1. FOUNDATIONS
   ------------------------------------------------------------ */

/* One focus ring for the whole game, and only one: experience.css also
   declared a ring for a narrower element list, so summaries, [role]
   controls and custom widgets had no visible focus at all. This rule
   is specific enough to be the single owner. An outline (not a box
   shadow) follows the element's own border-radius and survives an
   overflow:hidden ancestor. */
:is(a, button, input, select, textarea, summary, [tabindex], [role="button"], [role="tab"], [role="switch"]):focus-visible {
    outline: 2px solid var(--nb-gold-bright);
    outline-offset: 3px;
    box-shadow: none;
    position: relative;
    z-index: 1;
}

/* Inside a scroll container the 3px offset is the first thing clipped. */
.nb-table-scroll :is(a, button, input, select):focus-visible,
.nb-command-results a:focus-visible {
    outline-offset: -2px;
}

/* Tactile press. 0.96 is the whole range that reads as "pressed"
   rather than "collapsed", and it is applied by class list rather
   than to every element so text links do not shrink. */
:where(
    .btn, .game-btn, .action-btn, .cyberpunk-button, .filter-btn, .market-buy-btn,
    .attempt-btn, .apply-btn, .quit-btn, .sidebar-logout-btn, .nb-command-launch,
    .nb-pin-page, .inv-tab, .mobile-fab-toggle, button[type="submit"], input[type="submit"]
):not(:disabled):not(.disabled):active {
    scale: 0.96;
}

:where(
    .btn, .game-btn, .action-btn, .cyberpunk-button, .filter-btn, .market-buy-btn,
    .attempt-btn, .apply-btn, .quit-btn, .inv-tab, button[type="submit"], input[type="submit"]
) {
    transition-property: scale, background-color, border-color, color, box-shadow, filter, transform;
    transition-duration: var(--nb-dur-fast);
    transition-timing-function: var(--nb-ease-out);
}

/* Every interactive control clears the 40px target the rest of the
   system promises, without changing any page's chosen padding. */
:where(
    .btn, .game-btn, .action-btn, .cyberpunk-button, .filter-btn, .market-buy-btn,
    .attempt-btn, .apply-btn, .quit-btn, .inv-tab, button[type="submit"], input[type="submit"]
):not(.btn-sm):not(.badge) {
    min-height: 40px;
}

/* A disabled control must look unavailable before it is clicked. */
:where(button, .btn, .game-btn, .cyberpunk-button, a.btn):is(:disabled, .disabled, [aria-disabled="true"]) {
    cursor: not-allowed;
    filter: grayscale(0.35);
    opacity: 0.55;
    scale: 1;
}

/* ------------------------------------------------------------
   2. PAGE MASTHEAD
   Sixteen page-specific header blocks existed, in three unrelated
   styles: a boxed row (market/jobs), a bare Cinzel row (game-header)
   and a centred stack (fighting/casino/settings). They are unified
   here on the bare Cinzel row, which is the one the design system
   describes. Page markup is untouched.
   ------------------------------------------------------------ */
:is(.rpg-content, .admin-layout) :is(
    .game-header, .market-header, .jobs-header, .fighting-header, .casino-lobby-header,
    .faction-header, .education-header, .crypto-header, .map-header, .mf-header,
    .smelting-plant-header, .settings-header, .story-header, .achievements-header,
    .bounties-header, .auction-header, .leaderboard-header, .stats-header, .crafting-header
) {
    display: flex;
    align-items: center;
    gap: 14px;
    text-align: left;
    margin: 0 0 22px;
    padding: 0 0 16px;
    background: none;
    border: none;
    border-bottom: 1px solid var(--nb-border);
    border-radius: 0;
    position: relative;
    flex-wrap: wrap;
}

/* The gold hairline is a short accent, not a full rule: a rule the full
   width of the lane reads as a divider, an accent reads as a masthead. */
:is(.rpg-content, .admin-layout) :is(
    .game-header, .market-header, .jobs-header, .fighting-header, .casino-lobby-header,
    .faction-header, .education-header, .crypto-header, .map-header, .mf-header,
    .smelting-plant-header, .settings-header, .story-header, .achievements-header,
    .bounties-header, .auction-header, .leaderboard-header, .stats-header, .crafting-header
)::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -1px;
    width: 96px;
    height: 1px;
    background: linear-gradient(90deg, var(--nb-gold), transparent);
}

/* The icon becomes a struck-metal chip so the row has an anchor at any
   title length, and so pages whose icon was 2.5rem stop out-shouting
   pages whose icon was 1.5rem. */
:is(.rpg-content, .admin-layout) :is(
    .game-header, .market-header, .jobs-header, .fighting-header, .casino-lobby-header,
    .faction-header, .education-header, .crypto-header, .map-header, .mf-header,
    .smelting-plant-header, .settings-header, .story-header, .achievements-header,
    .bounties-header, .auction-header, .leaderboard-header, .stats-header, .crafting-header
) > i:first-child {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 42px;
    height: 42px;
    margin: 0;
    font-size: 1.05rem;
    color: var(--nb-gold-bright);
    background: var(--nb-grad-metal);
    border: 1px solid var(--nb-border-strong);
    border-radius: var(--nb-radius-md);
    box-shadow: inset 0 1px 0 rgba(249, 237, 201, 0.10), 0 2px 6px rgba(0, 0, 0, 0.5);
}

:is(.rpg-content, .admin-layout) :is(
    .game-header, .market-header, .jobs-header, .fighting-header, .casino-lobby-header,
    .faction-header, .education-header, .crypto-header, .map-header, .mf-header,
    .smelting-plant-header, .settings-header, .story-header, .achievements-header,
    .bounties-header, .auction-header, .leaderboard-header, .stats-header, .crafting-header
) :is(h1, h2) {
    margin: 0;
    flex: 1 1 auto;
    min-width: 0;
    font-family: var(--nb-font-display);
    font-size: clamp(1.35rem, 1.1rem + 0.9vw, 1.85rem);
    font-weight: 700;
    letter-spacing: 0.05em;
    line-height: 1.15;
    text-wrap: balance;
    text-transform: none;
    /* Undo the per-page gradient-clipped text: three pages clipped the
       title to a red→gold gradient, which rendered differently in every
       browser and failed outright where background-clip is unsupported. */
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    -webkit-text-fill-color: currentColor;
    color: var(--nb-ivory);
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
}

/* A subtitle under the masthead becomes a caption, not a paragraph. */
:is(.rpg-content, .admin-layout) :is(.casino-lobby-header, .fighting-header, .settings-header, .story-header) > p {
    flex: 1 0 100%;
    margin: 2px 0 0;
    color: var(--nb-text-dim);
    font-size: var(--nb-text-sm);
    text-wrap: pretty;
}

/* Bank and a handful of older pages print a bare heading instead of a
   header block. Give it the same rhythm so it lines up with the rest. */
:is(.rpg-content, .admin-layout) > :is(div, section) > h1.neon-text,
:is(.rpg-content, .admin-layout) > h1.neon-text {
    display: flex;
    align-items: center;
    gap: 14px;
    margin: 0 0 22px;
    padding: 0 0 16px;
    border-bottom: 1px solid var(--nb-border);
    font-family: var(--nb-font-display);
    font-size: clamp(1.35rem, 1.1rem + 0.9vw, 1.85rem);
    letter-spacing: 0.05em;
    color: var(--nb-ivory);
    text-wrap: balance;
}

:is(.rpg-content, .admin-layout) > :is(div, section) > h1.neon-text > i:first-child,
:is(.rpg-content, .admin-layout) > h1.neon-text > i:first-child {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 42px;
    height: 42px;
    font-size: 1.05rem;
    color: var(--nb-gold-bright);
    background: var(--nb-grad-metal);
    border: 1px solid var(--nb-border-strong);
    border-radius: var(--nb-radius-md);
    box-shadow: inset 0 1px 0 rgba(249, 237, 201, 0.10), 0 2px 6px rgba(0, 0, 0, 0.5);
}

/* A page has one masthead. A repeated header block on the same page is a
   section divider and steps down: smaller chip, smaller type, no accent. */
:is(.rpg-content, .admin-layout) .settings-section-head,
:is(.rpg-content, .admin-layout) .nb-section-head {
    padding-bottom: 12px;
    margin-top: 2.25rem;
}

:is(.rpg-content, .admin-layout) :is(.settings-section-head, .nb-section-head)::after { display: none; }

:is(.rpg-content, .admin-layout) :is(.settings-section-head, .nb-section-head) > i:first-child {
    width: 32px;
    height: 32px;
    font-size: 0.85rem;
}

:is(.rpg-content, .admin-layout) :is(.settings-section-head, .nb-section-head) :is(h1, h2) {
    font-size: var(--nb-text-md);
    letter-spacing: var(--nb-tracking-wide);
    text-transform: uppercase;
    color: var(--nb-champagne);
}

/* ------------------------------------------------------------
   3. SECTION HEADINGS
   Second-level headings inside a page get one rhythm: Cinzel, a
   restrained gold tick, and consistent space above.
   ------------------------------------------------------------ */
:is(.rpg-content, .admin-layout) :is(.card-header, .section-header, .panel-header) {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

:is(.rpg-content, .admin-layout) :is(.card-header, .section-header, .panel-header) > i:first-child {
    color: var(--nb-gold);
    font-size: 0.95rem;
    flex: none;
}

:is(.rpg-content, .admin-layout) :is(.card-header, .section-header, .panel-header) :is(h2, h3, h4) {
    font-family: var(--nb-font-display);
    letter-spacing: 0.06em;
    margin: 0;
    text-wrap: balance;
}

/* ------------------------------------------------------------
   4. FORM CONTROLS
   One height, one radius, one focus treatment across every page.
   ------------------------------------------------------------ */
:is(.rpg-content, .admin-layout) :is(
    input[type="text"], input[type="number"], input[type="search"], input[type="email"],
    input[type="password"], input[type="date"], input[type="url"], select, textarea
) {
    font-family: var(--nb-font-ui);
    font-size: var(--nb-text-base);
    border-radius: var(--nb-radius-sm);
    transition-property: border-color, box-shadow, background-color;
    transition-duration: var(--nb-dur-fast);
    transition-timing-function: var(--nb-ease-out);
}

:is(.rpg-content, .admin-layout) :is(input[type="number"], input[type="text"]).nb-num,
:is(.rpg-content, .admin-layout) input[type="number"] {
    font-family: var(--nb-font-data);
    font-variant-numeric: tabular-nums;
}

:is(.rpg-content, .admin-layout) :is(input, select, textarea):not(:disabled):hover {
    border-color: var(--nb-border-strong);
}

/* A field the browser rejected states it, once, without a dialog. */
.nb-field-invalid {
    border-color: var(--nb-danger) !important;
    box-shadow: 0 0 0 1px rgba(255, 70, 85, 0.35) !important;
}

.nb-shake {
    animation: nbShake 320ms var(--nb-ease-in-out);
}

@keyframes nbShake {
    0%, 100% { translate: 0; }
    20% { translate: -5px; }
    45% { translate: 4px; }
    70% { translate: -2px; }
}

/* ------------------------------------------------------------
   4b. NUMERALS
   Money is the game's main number. Several pages set it in Courier
   New with a neon glow, which is neither the design system's data
   face nor its identity. One face, one weight, one restrained lift.
   ------------------------------------------------------------ */
:is(.rpg-content, .admin-layout) :is(
    .balance-amount, .hero-amount, .stat-value, .money-value, .wallet-amount, .payout-amount
) {
    font-family: var(--nb-font-data);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

:is(.rpg-content, .admin-layout) .balance-amount {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 2px;
    color: var(--nb-jade-bright);
}

/* The currency mark is a sign, not a digit: smaller, dimmer, tight to
   the number instead of a 5px margin plus collapsed markup whitespace. */
:is(.rpg-content, .admin-layout) .balance-amount .currency {
    margin-right: 0;
    font-size: 0.55em;
    opacity: 0.7;
    align-self: flex-start;
    margin-top: 0.25em;
}

/* ------------------------------------------------------------
   5. SURFACES
   Card hover was inconsistent — some pages lifted 6px with a red
   glow, some did nothing. One restrained lift, one border warm-up.
   ------------------------------------------------------------ */
:is(.rpg-content, .admin-layout) :is(.card, .panel, .market-card, .job-card, .fight-card, .casino-game-card, .stat-card, .content-box) {
    transition-property: transform, border-color, box-shadow;
    transition-duration: var(--nb-dur-base);
    transition-timing-function: var(--nb-ease-out);
}

:is(.rpg-content, .admin-layout) :is(.card, .panel, .market-card, .job-card, .fight-card, .casino-game-card, .stat-card):hover {
    transform: translateY(-2px);
    border-color: var(--nb-border-strong);
    box-shadow: var(--nb-shadow-md);
}

/* Images sit on dark panels: a hairline of pure white at 10% keeps the
   edge crisp without tinting it. A missing asset keeps its frame. */
.nb-image {
    outline: 1px solid rgba(255, 255, 255, 0.1);
    outline-offset: -1px;
    border-radius: var(--nb-radius-sm);
    background: var(--nb-surface-inset);
}

.nb-image.is-broken {
    object-fit: contain;
    padding: 12%;
    opacity: 0.35;
}

/* ------------------------------------------------------------
   6. TABLES
   Long leaderboards and logs get a heading that stays put, a legible
   zebra and a row hover — everywhere, not on the three pages that
   happened to implement it.
   ------------------------------------------------------------ */
/* Heading treatment is global; stickiness is not. A `position: sticky` heading
   on a table in normal page flow sticks to the VIEWPORT top, which is behind
   the fixed header bar. Only tables inside a scroll container get it — that is
   the container experience.ts marks, and it is the only place the heading has
   a scrollport of its own to stick to. */
:is(.rpg-content, .admin-layout) table :is(thead th, th[scope="col"]) {
    background: var(--nb-carbon-3);
    font-family: var(--nb-font-ui);
    font-size: var(--nb-text-xs);
    letter-spacing: var(--nb-tracking-wide);
    text-transform: uppercase;
    color: var(--nb-text-dim);
    box-shadow: inset 0 -1px 0 var(--nb-border);
}

.nb-table-scroll table :is(thead th, th[scope="col"]) {
    position: sticky;
    top: 0;
    z-index: 2;
}

:is(.rpg-content, .admin-layout) table tbody tr:nth-child(even) {
    background: rgba(255, 255, 255, 0.015);
}

:is(.rpg-content, .admin-layout) table tbody tr {
    transition: background-color var(--nb-dur-fast) var(--nb-ease-out);
}

:is(.rpg-content, .admin-layout) table tbody tr:hover {
    background: rgba(201, 162, 39, 0.055);
}

/* Any number in a table is read by comparison, so it must not jitter. */
:is(.rpg-content, .admin-layout) table :is(td, th) {
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------
   7. HEADER CHROME
   ------------------------------------------------------------ */

/* Condense on scroll: the bar tightens, deepens and the gold edge
   strengthens, so the page reads as scrolled without a jump. */
.rpg-nav {
    transition-property: padding, background-color, box-shadow, border-color;
    transition-duration: var(--nb-dur-base);
    transition-timing-function: var(--nb-ease-out);
}

html.nb-scrolled .rpg-nav {
    padding-top: 4px;
    padding-bottom: 4px;
    background: linear-gradient(180deg, rgba(12, 8, 10, 0.985), rgba(8, 5, 6, 0.965));
    border-bottom-color: rgba(201, 162, 39, 0.34);
    box-shadow: 0 10px 34px rgba(0, 0, 0, 0.62);
}

html.nb-scrolled .rpg-menu > li > a {
    padding-top: 9px;
    padding-bottom: 9px;
}

/* You are here. The section the player is inside keeps a gold underline
   even when the active page is a sub-item of a dropdown. */
.rpg-menu > li.is-current-section > a {
    color: var(--nb-gold-bright);
}

.rpg-menu > li.is-current-section > a::before {
    content: '';
    position: absolute;
    left: 18px;
    right: 18px;
    bottom: 6px;
    height: 2px;
    border-radius: 1px;
    background: linear-gradient(90deg, transparent, var(--nb-gold), transparent);
}

.rpg-menu .sub-menu li.is-current-page > a {
    color: var(--nb-gold-bright);
    background: rgba(201, 162, 39, 0.08);
}

/* ------------------------------------------------------------
   8. NAVIGATION PROGRESS
   ------------------------------------------------------------ */
.nb-nav-progress {
    position: fixed;
    inset: 0 0 auto 0;
    height: 2px;
    z-index: calc(var(--nb-z-header) + 5);
    background: var(--nb-grad-gold);
    transform-origin: 0 50%;
    transform: scaleX(0);
    transition: transform 180ms linear, opacity 240ms ease;
    box-shadow: 0 0 10px rgba(232, 193, 90, 0.5);
    pointer-events: none;
}

.nb-nav-progress.is-done {
    opacity: 0;
    transition: transform 140ms ease-out, opacity 240ms ease 100ms;
}

/* ------------------------------------------------------------
   9. BACK TO TOP
   ------------------------------------------------------------ */
.nb-to-top {
    position: fixed;
    right: 20px;
    bottom: 22px;
    z-index: calc(var(--nb-z-header) - 1);
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: var(--nb-radius-md);
    border: 1px solid var(--nb-border-strong);
    background: var(--nb-grad-metal);
    color: var(--nb-gold-bright);
    cursor: pointer;
    box-shadow: var(--nb-shadow-md);
    opacity: 0;
    translate: 0 10px;
    transition: opacity var(--nb-dur-base) var(--nb-ease-out),
                translate var(--nb-dur-base) var(--nb-ease-out),
                border-color var(--nb-dur-fast) var(--nb-ease-out);
}

.nb-to-top.is-visible {
    opacity: 1;
    translate: 0 0;
}

.nb-to-top:hover {
    border-color: var(--nb-gold);
    box-shadow: var(--nb-shadow-md), var(--nb-glow-gold);
}

/* The friends sidebar docks 270px against the right edge above 1199px, so
   anything pinned to that edge lands on top of it. Both the back-to-top
   control and the toast stack move inboard while it is docked — and move
   back when the player collapses the sidebars. */
@media (min-width: 1200px) {
    html:not(.nb-sidebars-collapsed) body.nb-has-sidebars .nb-to-top { right: 290px; }
    html:not(.nb-sidebars-collapsed) body.nb-has-sidebars .nb-toast-region { right: 286px; }
}

/* Clear the bottom navigation bar. */
@media (max-width: 900px) {
    .nb-to-top { bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
}

/* ------------------------------------------------------------
   10. STAT DELTAS
   A value that changed announces the difference and gets out of the
   way. Absolutely positioned so the row never reflows.
   ------------------------------------------------------------ */
.sidebar-nav li { position: relative; }

.nb-stat-delta {
    position: absolute;
    right: 10px;
    top: 50%;
    font-family: var(--nb-font-data);
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    pointer-events: none;
    white-space: nowrap;
    animation: nbDeltaRise 1500ms var(--nb-ease-out) forwards;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.nb-stat-delta.is-gain { color: var(--nb-jade-bright); }
.nb-stat-delta.is-loss { color: var(--nb-danger); }

@keyframes nbDeltaRise {
    0%   { opacity: 0; translate: 0 -30%; }
    18%  { opacity: 1; translate: 0 -50%; }
    72%  { opacity: 1; translate: 0 -120%; }
    100% { opacity: 0; translate: 0 -170%; }
}

.nb-stat-pulse {
    animation: nbStatSweep 900ms var(--nb-ease-out);
}

@keyframes nbStatSweep {
    0%   { background-color: rgba(201, 162, 39, 0.16); }
    100% { background-color: transparent; }
}

/* ------------------------------------------------------------
   11. COUNTDOWNS
   One cooldown presentation for training, jail, hospital and crimes.
   ------------------------------------------------------------ */
.nb-countdown {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.nb-countdown-value {
    font-family: var(--nb-font-data);
    font-variant-numeric: tabular-nums;
    font-size: var(--nb-text-md);
    letter-spacing: 0.04em;
    color: var(--nb-gold-bright);
}

.nb-countdown.is-complete .nb-countdown-value {
    color: var(--nb-jade-bright);
}

/* A conic ring is a single painted gradient — no SVG, no per-frame DOM. */
.nb-countdown-ring {
    --nb-ring: 0;
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background:
        conic-gradient(var(--nb-gold) calc(var(--nb-ring) * 360deg), rgba(201, 162, 39, 0.16) 0);
    mask: radial-gradient(circle, transparent 58%, #000 60%);
    -webkit-mask: radial-gradient(circle, transparent 58%, #000 60%);
    transition: background 400ms linear;
}

.nb-countdown.is-complete .nb-countdown-ring {
    background: conic-gradient(var(--nb-jade) 360deg, var(--nb-jade) 0);
}

/* ------------------------------------------------------------
   12. CONFIRMATION DIALOG
   ------------------------------------------------------------ */
.nb-confirm {
    /* A stylesheet earlier in the cascade zeroes dialog margins, which pins a
       modal <dialog> to the top-left instead of centring it. inset:0 plus
       margin:auto restores centring without relying on that default. */
    position: fixed;
    inset: 0;
    margin: auto;
    width: min(440px, calc(100vw - 32px));
    height: fit-content;
    border: 1px solid var(--nb-border-strong);
    border-radius: var(--nb-radius-lg);
    background: var(--nb-surface-raised);
    color: var(--nb-text);
    padding: 0;
    box-shadow: var(--nb-shadow-lg);
}

/* A softer entrance than a pop: modal dialogs that scale from 1 look
   confident, dialogs that scale from 0.8 look like a notification. */
.nb-confirm[open] {
    animation: nbConfirmIn var(--nb-dur-base) var(--nb-ease-out);
}

@keyframes nbConfirmIn {
    from { opacity: 0; translate: 0 -8px; scale: 0.98; }
    to   { opacity: 1; translate: 0 0; scale: 1; }
}

.nb-confirm::backdrop {
    background: rgba(5, 4, 5, 0.72);
    backdrop-filter: blur(3px);
}

.nb-confirm.is-danger { border-color: var(--nb-border-danger); }

/* Concentric radii: the dialog is 10px with 18px of padding, so nothing
   inside it may be rounder than the corner it sits in. */
.nb-confirm-inner {
    padding: 18px;
    display: grid;
    gap: 10px;
}

.nb-confirm-title {
    margin: 0;
    font-family: var(--nb-font-display);
    font-size: var(--nb-text-md);
    letter-spacing: 0.05em;
    color: var(--nb-ivory);
}

.nb-confirm.is-danger .nb-confirm-title { color: var(--nb-danger); }

.nb-confirm-text {
    margin: 0;
    font-size: var(--nb-text-sm);
    color: var(--nb-text-dim);
    text-wrap: pretty;
}

.nb-confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 4px;
}

.nb-confirm-actions button {
    min-height: 40px;
    padding: 8px 18px;
    border-radius: var(--nb-radius-sm);
    font-family: var(--nb-font-ui);
    font-size: var(--nb-text-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    cursor: pointer;
}

.nb-confirm-cancel {
    border: 1px solid var(--nb-border-soft);
    background: transparent;
    color: var(--nb-text-dim);
}

.nb-confirm-cancel:hover { color: var(--nb-text); border-color: var(--nb-border-strong); }

.nb-confirm-ok {
    border: 1px solid transparent;
    background: var(--nb-grad-action);
    color: var(--nb-gilt);
}

.nb-confirm.is-danger .nb-confirm-ok { background: linear-gradient(135deg, #7e1522, #d92336); }
.nb-confirm-ok:hover { filter: brightness(1.08); }

/* ------------------------------------------------------------
   13. TOASTS v2
   ------------------------------------------------------------ */
.nb-toast-region {
    max-width: min(380px, calc(100vw - 32px));
    transition: right var(--nb-dur-base) var(--nb-ease-out);
}

.nb-toast {
    position: relative;
    overflow: hidden;
    align-items: flex-start;
    gap: 10px;
    padding-right: 38px;
    max-width: 100%;
}

/* Below the docked-sidebar breakpoint the stack spans the gutter instead of
   sitting half off-screen. */
@media (max-width: 600px) {
    .nb-toast-region { left: 12px; right: 12px; max-width: none; }
    .nb-toast { min-width: 0; max-width: none; }
}

.nb-toast-icon { margin-top: 2px; flex: none; }

.nb-toast-body {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex: 1;
    min-width: 0;
    text-wrap: pretty;
}

.nb-toast-count {
    flex: none;
    font-family: var(--nb-font-data);
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    color: var(--nb-gold-bright);
    background: rgba(201, 162, 39, 0.14);
    border: 1px solid rgba(201, 162, 39, 0.3);
    border-radius: var(--nb-radius-round);
    padding: 1px 7px;
}

.nb-toast-close {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border: none;
    border-radius: var(--nb-radius-sm);
    background: transparent;
    color: var(--nb-text-faint);
    cursor: pointer;
    transition: color var(--nb-dur-fast) var(--nb-ease-out), background-color var(--nb-dur-fast) var(--nb-ease-out);
}

/* The visible glyph is 26px; extend the target to 40px without moving it. */
.nb-toast-close::after {
    content: '';
    position: absolute;
    inset: -7px;
}

.nb-toast-close:hover { color: var(--nb-ivory); background: rgba(255, 255, 255, 0.07); }

/* A hairline that drains for exactly the toast's remaining life, so the
   dismissal is predicted rather than surprising. */
.nb-toast-life {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: 100%;
    transform-origin: 0 50%;
    background: currentColor;
    opacity: 0.45;
    animation: nbToastDrain linear forwards;
}

.nb-toast--success .nb-toast-life { color: var(--nb-jade); }
.nb-toast--error   .nb-toast-life { color: var(--nb-danger); }
.nb-toast--warning .nb-toast-life { color: var(--nb-warning); }
.nb-toast--gold    .nb-toast-life { color: var(--nb-gold); }
.nb-toast--info    .nb-toast-life { color: var(--nb-steel); }

@keyframes nbToastDrain {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

.nb-toast--bump { animation: nbToastBump 320ms var(--nb-ease-snap); }

@keyframes nbToastBump {
    0%   { scale: 1; }
    40%  { scale: 1.035; }
    100% { scale: 1; }
}

/* ------------------------------------------------------------
   14. TAB STRIPS
   ------------------------------------------------------------ */
.nb-tabstrip { position: relative; }

.nb-tabstrip-indicator {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: var(--nb-tab-w, 0);
    translate: var(--nb-tab-x, 0) 0;
    border-radius: 1px;
    background: var(--nb-grad-gold);
    box-shadow: 0 0 10px rgba(201, 162, 39, 0.4);
    opacity: 0;
    pointer-events: none;
    transition: translate var(--nb-dur-base) var(--nb-ease-out),
                width var(--nb-dur-base) var(--nb-ease-out),
                opacity var(--nb-dur-fast) linear;
}

.nb-tabstrip-indicator.is-ready { opacity: 1; }

/* ------------------------------------------------------------
   15. REWARD FLOURISH
   One gold sweep across a flash that reports a gain, so "You claimed
   $5,000" does not look like "Settings saved".
   ------------------------------------------------------------ */
.nb-reward {
    position: relative;
    overflow: hidden;
    border-color: rgba(201, 162, 39, 0.5) !important;
}

.nb-reward::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(105deg, transparent 35%, rgba(249, 237, 201, 0.22) 50%, transparent 65%);
    translate: -100% 0;
    animation: nbRewardSweep 1400ms var(--nb-ease-out) 120ms;
    pointer-events: none;
}

@keyframes nbRewardSweep {
    to { translate: 100% 0; }
}

/* ------------------------------------------------------------
   16. TOOLTIP PLACEMENT
   The shared bubble is centred above its anchor, which clipped inside
   the docked sidebars and under the fixed header. premium.ts adds a
   class; the geometry stays here.
   ------------------------------------------------------------ */
[data-tip].nb-tip-left::after,
[data-tooltip].nb-tip-left::after { left: 0; transform: none; }

[data-tip].nb-tip-right::after,
[data-tooltip].nb-tip-right::after { left: auto; right: 0; transform: none; }

[data-tip].nb-tip-below::after,
[data-tooltip].nb-tip-below::after { bottom: auto; top: calc(100% + 8px); }

/* ------------------------------------------------------------
   17. SKELETONS
   Panels that load over the network show their shape first, so the
   layout is final before the data lands.
   ------------------------------------------------------------ */
.nb-skeleton-stack {
    display: grid;
    gap: 8px;
    padding: 12px;
}

.nb-skeleton-line {
    height: 10px;
    border-radius: var(--nb-radius-xs);
    background: linear-gradient(90deg, var(--nb-carbon-3) 25%, var(--nb-carbon-4) 50%, var(--nb-carbon-3) 75%);
    background-size: 200% 100%;
    animation: nbSkeleton 1500ms linear infinite;
}

.nb-skeleton-line:nth-child(2) { width: 82%; }
.nb-skeleton-line:nth-child(3) { width: 64%; }

@keyframes nbSkeleton {
    to { background-position: -200% 0; }
}

/* ------------------------------------------------------------
   18. EMPTY STATES
   Six pages announced "nothing here" with an info alert, which reads
   as a warning. Normalise the common cases onto the quiet treatment.
   ------------------------------------------------------------ */
:is(.rpg-content, .admin-layout) .alert.alert-info.is-empty,
:is(.rpg-content, .admin-layout) .empty-message {
    display: grid;
    justify-items: center;
    gap: 6px;
    text-align: center;
    padding: 34px 20px;
    color: var(--nb-text-dim);
    background: var(--nb-surface-inset);
    border: 1px dashed var(--nb-border);
    border-radius: var(--nb-radius-md);
}

/* ------------------------------------------------------------
   19. MOBILE BOTTOM NAVIGATION
   Below 900px both sidebars are drawers and the top bar is icons
   only, which left the five most-used destinations three taps deep.
   ------------------------------------------------------------ */
.nb-bottom-nav { display: none; }

@media (max-width: 900px) {
    .nb-bottom-nav {
        position: fixed;
        inset: auto 0 0 0;
        z-index: var(--nb-z-header);
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        gap: 2px;
        padding: 6px 6px calc(6px + env(safe-area-inset-bottom, 0px));
        background: linear-gradient(180deg, rgba(14, 10, 12, 0.94), rgba(8, 5, 6, 0.99));
        border-top: 1px solid var(--nb-border);
        backdrop-filter: blur(12px);
        box-shadow: var(--nb-shadow-up);
    }

    .nb-bottom-nav a {
        display: grid;
        justify-items: center;
        align-content: center;
        gap: 3px;
        min-height: 48px;
        padding: 4px 2px;
        border-radius: var(--nb-radius-sm);
        color: var(--nb-text-faint);
        text-decoration: none;
        font-size: 0.62rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        transition: color var(--nb-dur-fast) var(--nb-ease-out), background-color var(--nb-dur-fast) var(--nb-ease-out);
    }

    .nb-bottom-nav a i { font-size: 1.05rem; }

    .nb-bottom-nav a:hover { color: var(--nb-text); }

    .nb-bottom-nav a[aria-current="page"] {
        color: var(--nb-gold-bright);
        background: rgba(201, 162, 39, 0.10);
    }

    /* Reserve the bar's height so the last row of any page stays reachable. */
    body { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)); }
}

/* ------------------------------------------------------------
   20. MOBILE HEADER IDENTITY
   The authenticated mobile bar was five identical-weight icon
   buttons and a truncated timer, with no wordmark at all.
   ------------------------------------------------------------ */
.nb-header-brand { display: none; }

@media (max-width: 900px) {
    /* The player-sidebar toggle is position:fixed at the left edge, so it sat
       on top of anything the bar placed there. Reserve its lane. */
    .rpg-header:has(#sidebar-toggle) .rpg-nav {
        box-sizing: border-box;
        padding-left: 50px;
        gap: 6px;
    }

    .nb-header-brand {
        display: inline-flex;
        align-items: center;
        margin-right: auto;
        /* flex:none rather than min-width:0 — a wordmark squeezed to "NETORI"
           is worse than no wordmark, so it either fits whole or the rule
           further down hides it outright. */
        flex: 0 0 auto;
        font-family: var(--nb-font-display);
        font-size: 0.85rem;
        font-weight: 800;
        letter-spacing: 0.1em;
        text-decoration: none;
        background: var(--nb-grad-gold-text);
        -webkit-background-clip: text;
        background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    /* The timer was the widest thing in the bar and still unreadable at
       "New moves in: …". Keep the value, drop the sentence. */
    .header-timer strong { display: none; }

    .header-timer::before {
        content: '\f017'; /* clock */
        font-family: 'Font Awesome 6 Free';
        font-weight: 900;
        margin-right: 5px;
        color: var(--nb-gold);
    }

    .header-timer {
        font-family: var(--nb-font-data);
        font-variant-numeric: tabular-nums;
        font-size: 0.74rem;
        white-space: nowrap;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* Under 460px the bar has room for the controls or the wordmark, not both.
   The controls win; the wordmark is decoration and the page title is right
   below it in the command bar. */
@media (max-width: 459px) {
    .nb-header-brand { display: none; }
}

/* The command bar's "YOUR EMPIRE" kicker wrapped to two lines on a phone and
   squeezed the page name it exists to introduce. Drop the kicker, keep the
   name, and let the two actions share the remaining width evenly. */
@media (max-width: 479px) {
    .nb-location-label { display: none; }

    .nb-location {
        flex: 1 1 100%;
        min-width: 0;
    }

    .nb-location > span:last-child {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .nb-command-bar { flex-wrap: wrap; row-gap: 8px; }
    .nb-command-bar > button { flex: 1 1 0; justify-content: center; }
}

/* ------------------------------------------------------------
   21. SKIP LINK
   ------------------------------------------------------------ */
.nb-skip-link:focus {
    box-shadow: var(--nb-focus-ring);
}

/* ------------------------------------------------------------
   22. REDUCED MOTION / CALM MODE
   Every animation above is answered here. Anything that begins at
   opacity:0 has its opacity explicitly restored, so disabling motion
   can never hide content.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
    .nb-to-top,
    .nb-stat-delta,
    .nb-tabstrip-indicator,
    .nb-toast-life,
    .nb-toast--bump,
    .nb-reward::after,
    .nb-skeleton-line,
    .nb-confirm[open],
    .nb-shake {
        animation: none !important;
        transition: none !important;
    }

    .nb-to-top.is-visible,
    .nb-tabstrip-indicator.is-ready { opacity: 1; translate: 0 0; }

    .nb-stat-delta { display: none; }
    .nb-reward::after { display: none; }
    .nb-skeleton-line { background: var(--nb-carbon-3); }

    :is(.rpg-content, .admin-layout) :is(.card, .panel, .market-card, .job-card, .fight-card, .casino-game-card, .stat-card):hover {
        transform: none;
    }

    :where(.btn, .game-btn, .action-btn, .cyberpunk-button):active { scale: 1; }
}

html.nb-calm .nb-stat-delta,
html.nb-calm .nb-reward::after {
    display: none;
}

html.nb-calm .nb-skeleton-line {
    animation: none;
    background: var(--nb-carbon-3);
}

html.nb-calm :where(.card, .panel, .market-card, .job-card, .fight-card, .casino-game-card):hover {
    transform: none;
}
