/* ============================================================
   KobaltTech design system — custom CSS layer
   Sits on top of MudBlazor. Blue = action, Gold = value, Teal = live.
   ============================================================ */

:root {
    /* ── FROZEN brand accents (do not change hex values) ── */
    --kt-cobalt: #4F7BFF;
    --kt-cobalt-deep: #3A5BE0;
    --kt-cobalt-bright: #5D86FF;
    --kt-cobalt-tint: #A9BEFF;
    --kt-cobalt-soft: rgba(79, 123, 255, 0.14);
    --kt-gold: #F5C451;
    --kt-teal: #22D3A7;
    --kt-red: #FF5C72;
    --kt-violet: #8A5CFF; /* chart-4 only */

    /* Structural tokens — follow MudBlazor so light/dark stays consistent. */
    --kt-canvas: var(--mud-palette-background, #0B0F17);
    --kt-surface: var(--mud-palette-surface, #141A24);
    --kt-text: var(--mud-palette-text-primary, #E9EDF5);
    --kt-muted: var(--mud-palette-text-secondary, #9AA6BC);
    --kt-hairline: var(--mud-palette-lines-default, rgba(255, 255, 255, 0.08));
    --kt-hairline-soft: var(--mud-palette-divider, rgba(255, 255, 255, 0.05));

    --kt-surface-2: #1A212E;
    --kt-surface-raised: var(--kt-surface-2);
    --kt-track: rgba(255, 255, 255, 0.06);
    --kt-fill-subtle: rgba(255, 255, 255, 0.03);
    --kt-hero-grad: linear-gradient(180deg, #131A26 0%, #0E141E 100%);
    --kt-hero-title-grad: linear-gradient(180deg, #FFFFFF 0%, #A9B4C7 100%);

    /* Spacing rhythm — 4px base */
    --kt-space-1: 4px;
    --kt-space-2: 8px;
    --kt-space-3: 12px;
    --kt-space-4: 16px;
    --kt-space-5: 20px;
    --kt-space-6: 24px;
    --kt-space-8: 32px;
    --kt-space-10: 40px;
    --kt-space-12: 48px;

    --kt-radius: 12px;
    --kt-radius-card: 16px;
    --kt-radius-pill: 999px;

    --kt-glow-cobalt: 0 0 24px rgba(79, 123, 255, 0.35);
    --kt-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.22), 0 4px 12px rgba(0, 0, 0, 0.14);
    --kt-shadow-lift: 0 1px 3px rgba(0, 0, 0, 0.2), 0 8px 20px rgba(0, 0, 0, 0.18), 0 0 0 1px var(--kt-hairline);
    --kt-shadow-feather: 0 1px 3px rgba(0, 0, 0, 0.2), 0 4px 12px rgba(0, 0, 0, 0.12);

    --kt-font-head: 'Outfit', system-ui, sans-serif;
    --kt-font-body: 'Source Sans 3', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --kt-font-mono: 'JetBrains Mono', ui-monospace, 'Segoe UI Mono', monospace;

    /* Motion — tasteful, honour prefers-reduced-motion below.
       Micro-transitions stay 150–180ms. Pulse loops 1.6–2.2s. The glide
       ease is for rare panel sweeps only, never for chrome. Corpus P0. */
    --kt-ease: cubic-bezier(0.4, 0, 0.2, 1);
    --kt-ease-glide: cubic-bezier(0.77, 0, 0.18, 1);
    --kt-dur-fast: 120ms;
    --kt-dur: 180ms;
    --kt-dur-fade: 80ms;
    --kt-dur-glide: 900ms;
    --kt-dur-pulse: 1.8s;
    --kt-optimistic-ms: 400;

    /* ── Ops-board layer (additive; opt-in; frozen hexes untouched) ──
       Inspired by big-screen performance boards. Nothing existing reads
       these, so no app shifts until it opts into the classes below. */
    --kt-canvas-deep: #07111D;                 /* optional deeper ground for full-screen boards */
    --kt-glass: rgba(120, 150, 190, 0.10);     /* tinted translucent surface */
    --kt-glass-line: rgba(150, 180, 220, 0.18);
    --kt-glow-teal: 0 0 22px rgba(34, 211, 167, 0.42);      /* board scale, via .kt-glow-live */
    --kt-glow-teal-soft: 0 0 14px rgba(34, 211, 167, 0.22); /* chrome scale, on live pills */
}

/* Light-mode overrides for the signature components that use hardcoded dark fills.
   Applied via the .kt-mode-light class MainLayout puts on MudLayout. */
.kt-mode-light {
    --kt-surface-2: #EEF1F7;
    --kt-track: rgba(11, 15, 23, 0.07);
    --kt-fill-subtle: rgba(11, 15, 23, 0.03);
    --kt-hero-grad: linear-gradient(180deg, #FFFFFF 0%, #EEF2F9 100%);
    --kt-hero-title-grad: linear-gradient(180deg, #0B0F17 0%, #3A4457 100%);
    /* Glass reads as a dark-on-light tint in light mode; the teal glow works on both grounds. */
    --kt-glass: rgba(11, 15, 23, 0.045);
    --kt-glass-line: rgba(11, 15, 23, 0.10);
}

html, body {
    font-family: var(--kt-font-body);
    background-color: var(--kt-canvas);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* ============================================================
   Shared animated background ("constellation field", kt-background.js)
   The canvas sits behind the page, so the body must not paint over
   it — html keeps the canvas colour as the true backdrop.
   ============================================================ */
body {
    background-color: transparent;

    /* THE THEME NOW OWNS ITS OWN BODY TYPE, and this closes a whole defect class rather than one
       instance of it. KobaltTheme.cs already DECLARES this intent — Typography.Default is 0.9rem /
       1.5 / 400 — but only in C#, which reaches the page as MudBlazor's body rule. That left the
       house scale to win or lose a cascade fight against vendor stylesheets in every product
       separately, and it lost three times: Marketing (Bootstrap linked after MudBlazor forced 1rem),
       AICode (declared its own 1rem last), PageGenerator (15px, deliberate and still winning because
       its own sheet loads last). Marketing additionally CEDED line-height to Bootstrap on the
       grounds that "Theme.Rcl declares none" — which was true, and is the reason this is here.

       The values are identical to the ones KobaltTheme already emits, so this changes nothing on
       screen anywhere. What changes is who owns them: a vendor sheet loading BEFORE Theme.Rcl can no
       longer take the body, and a product no longer has to re-assert the house scale to keep it.

       ON BODY, NEVER ON html — and this is the trap worth stating in place. `html { font-size }`
       redefines what a rem IS, so putting 0.9rem there would silently shrink every rem measurement
       in the estate: paddings, radii, breakpoints, the lot. The rule above deliberately pairs
       html and body for font-family and background only.

       WHY var() AND NOT THE LITERAL. These reference the very custom properties MudThemeProvider
       emits from KobaltTheme.Typography.Default, which is the same channel MudBlazor's own body rule
       reads. So the theme OBJECT remains the single source of the number: change Typography.Default
       in C# and this rule follows, with no second value to keep in step. A literal here would resolve
       to the right size today and then quietly stop tracking — which is precisely the objection
       FileManager's and AICode's conformance tests raise, and they are right to.

       The fallbacks are not decoration. A page that renders no MudThemeProvider leaves the property
       undefined, and a var() with no fallback would then resolve to nothing at all and drop the body
       back to the browser's 16px — the exact bug this rule exists to prevent. They are the only
       copied numbers left, so BodyTypographyOwnershipTests asserts each one still equals what
       KobaltTheme declares. */
    font-size: var(--mud-typography-default-size, 0.9rem);
    line-height: var(--mud-typography-default-lineheight, 1.5);
    font-weight: var(--mud-typography-default-weight, 400);
}

/* The canvas sits behind everything and NEVER takes the pointer — wells are raised
   from document-level listeners, so a click anywhere in the app drops one without
   any control being blocked. */
.kt-bg-canvas {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    z-index: -1;
    pointer-events: none;
}

/* Every non-login page gets the 50% scrim so the field never fights the UI. */
.kt-bg-dim {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-color: rgba(11, 15, 23, 0.5);
}


/* Light mode keeps the flat canvas — the field is a dark-surface treatment. */
:root[data-theme="light"] .kt-bg-canvas,
:root[data-theme="light"] .kt-bg-dim {
    display: none;
}

:root[data-theme="light"] body {
    background-color: var(--kt-canvas);
}

/* Light mode has no field behind it, so the auth surface takes its flat fill back. */
:root[data-theme="light"] .kt-auth {
    background:
        radial-gradient(circle at 25% 20%, rgba(79, 123, 255, 0.18), transparent 45%),
        radial-gradient(circle at 78% 75%, rgba(34, 211, 167, 0.10), transparent 45%),
        var(--kt-canvas, #F5F7FB);
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--kt-font-head);
    letter-spacing: -0.02em;
}

h1:focus,
 h1:focus-visible { outline: none; }

/* Skip link — visible on keyboard focus only */
.kt-skip {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 10000;
    padding: 0.65rem 1rem;
    background: var(--kt-cobalt-deep, #3A5BE0);
    color: #fff;
    font-weight: 600;
    border-radius: 0 0 8px 0;
    text-decoration: none;
}
.kt-skip:focus,
.kt-skip:focus-visible {
    left: 0;
    /* Was #fff, which is the first control a keyboard user meets and was invisible in light mode:
       the ring sits outside the box, on the app bar, and a white ring on the light app bar measures
       1.07:1. The house cobalt ring clears 3:1 on every ground in both modes — see .kt-scrolltop. */
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}
#kt-main:focus,
#kt-main:focus-visible { outline: none; }

/* Tabular figures for countdowns / prices / KPIs so digits don't jitter */
.kt-tnum,
.kt-countdown,
.kt-kpi-value,
.kt-price {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* ---------- Custom scrollbar ---------- */
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: rgba(154, 166, 188, 0.25);
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(154, 166, 188, 0.45); background-clip: content-box; }

/* ============================================================
   App bar — solid surface, 72px (no decorative glass blur)
   ============================================================ */
.kt-appbar {
    background: color-mix(in srgb, var(--kt-canvas) 92%, var(--kt-surface)) !important;
    border-bottom: 1px solid var(--kt-hairline);
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}

/* App-bar chrome: search, user chip */
.kt-appbar-search {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 300px;
    padding: 7px 12px;
    margin-right: 8px;
    border-radius: var(--kt-radius-pill);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-fill-subtle);
    transition: border-color .15s ease;
}
.kt-appbar-search:focus-within { border-color: rgba(79,123,255,0.45); }
.kt-appbar-search input {
    flex: 1;
    min-width: 0;
    background: none;
    border: none;
    outline: none;
    color: var(--kt-text);
    font-family: var(--kt-font-body);
    font-size: 0.83rem;
}
.kt-appbar-search input::placeholder { color: var(--kt-muted); }
.kt-kbd {
    font-size: 0.65rem;
    color: var(--kt-muted);
    border: 1px solid var(--kt-hairline);
    border-radius: 5px;
    padding: 2px 5px;
    line-height: 1;
    white-space: nowrap;
}

.kt-searchwrap { position: relative; margin-right: 8px; }
.kt-search-results {
    position: absolute;
    top: calc(100% + 6px);
    left: 0; right: 0;
    background: var(--kt-surface-2);
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius);
    box-shadow: 0 14px 34px -10px rgba(0,0,0,.6);
    overflow: hidden;
    z-index: 1400;
}
.kt-search-item {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 14px;
    font-size: 0.85rem;
    color: var(--kt-text);
    cursor: pointer;
}
.kt-search-item:hover { background: var(--kt-cobalt-soft); }
.kt-search-item--first { background: var(--kt-fill-subtle); }
.kt-search-item--first:hover { background: var(--kt-cobalt-soft); }
.kt-search-hint { font-size: 0.68rem; color: var(--kt-muted); }
.kt-search-foot {
    padding: 6px 14px;
    font-size: 0.66rem;
    color: var(--kt-muted);
    border-top: 1px solid var(--kt-hairline-soft);
}

.kt-userchip {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 8px;
    border-radius: var(--kt-radius);
    cursor: pointer;
    transition: background .15s ease;
}
.kt-userchip:hover { background: var(--kt-fill-subtle); }
/* KtUserMenu renders this chip as role="button" tabindex="0" with its own Enter/Space handler, so it
   really is in the tab order on every host in the estate — this ring fires rather than decorating a
   div nobody can reach. Borderless control, so a ring is the ONE indicator (a bordered field lights
   its own border instead; see .kt-login-input and the .mud-input rule below). */
.kt-userchip:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}
.kt-userchip-name { font-size: 0.8rem; font-weight: 600; color: var(--kt-text); line-height: 1.15; }
.kt-userchip-role { font-size: 0.66rem; color: var(--kt-muted); line-height: 1.15; }

/* KtUserMenu's Sign out row (§ default POST path).
   The row is a real <button type="submit"> so the browser posts the antiforgery form with
   no JavaScript — a CSP without 'unsafe-eval' must not be able to break sign-out. The item
   surrenders its padding to the button so the whole row, not just the text, is the target. */
.kt-usermenu-signout { padding: 0 !important; }
.kt-usermenu-signout-btn {
    display: flex;
    width: 100%;
    align-items: center;
    gap: 14px;
    padding: 6px 16px;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.kt-usermenu-signout-btn:hover { background: var(--kt-fill-subtle); }
/* Borderless control, so it takes the ring rather than lighting a border of its own.
   Inset (negative offset) because the row spans the popover's full width — a positive
   offset would be clipped by the menu edge. */
.kt-usermenu-signout-btn:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: -2px;
}

/* Drawer product line only (§0.1) — no second BrandMark tile */
.kt-drawer-product {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: -0.01em;
    color: var(--kt-text);
    border-bottom: 1px solid var(--kt-hairline);
}
/* Drawer footer hidden — products/Theme must not render footers (§0.4 / §0.6) */
.kt-drawer-foot {
    display: none !important;
}
.kt-navbadge {
    margin-left: auto;
    font-size: 0.66rem;
    font-weight: 700;
    padding: 1px 8px;
    border-radius: var(--kt-radius-pill);
    font-variant-numeric: tabular-nums;
}
.mud-nav-link .mud-nav-link-text { display: flex; align-items: center; }

/* Page footer styles retained only as dead CSS — products/Theme must not render footers (§0.4). */
.kt-footer {
    display: none !important;
}

/* Active nav item — soft fill + left rail (no decorative glow) */
.mud-nav-link.active {
    background: rgba(79,123,255,0.12) !important;
    border-radius: 10px;
    position: relative;
}
.mud-nav-link.active::before {
    content: "";
    position: absolute;
    left: 0; top: 20%; bottom: 20%;
    width: 2px; border-radius: 999px;
    background: var(--kt-cobalt);
}
.mud-nav-link.active .mud-nav-link-text { color: #C7D4FF !important; font-weight: 600; }
.mud-nav-link.active .mud-icon-root { color: var(--kt-cobalt) !important; }
/* Both prefixes. This rule carried only .kt-mode-light, so on a host that announces light mode by
   putting data-theme on <html> and nothing else, the active nav label stayed #C7D4FF — 1.47:1 on a
   white drawer. Found by LightModeInkContrastTests, which requires the pair. */
.kt-mode-light .mud-nav-link.active .mud-nav-link-text,
:root[data-theme="light"] .mud-nav-link.active .mud-nav-link-text { color: #2A46B8 !important; }
.mud-nav-menu .mud-nav-link { border-radius: 10px; margin: 1px 6px; transition: background .15s ease; }

/* Scroll-to-top floating button — flat, no glow */
.kt-scrolltop {
    position: fixed;
    right: 22px; bottom: 22px;
    width: 44px; height: 44px;   /* 44 is the tap-target floor; it is position:fixed so nothing shifts */
    border-radius: 12px;
    display: grid; place-items: center;
    background: var(--kt-cobalt-deep);
    color: #fff;
    border: 1px solid rgba(79,123,255,0.35);
    cursor: pointer;
    box-shadow: var(--kt-shadow-card);
    opacity: 0; transform: translateY(12px);
    transition: opacity .2s ease, transform .2s ease, filter .15s ease;
    pointer-events: none;
    z-index: 1500;
}
.kt-scrolltop.kt-show { opacity: 1; transform: translateY(0); pointer-events: auto; }
/* ONE HOVER RULE FOR THE WHOLE THEME, STATED ONCE HERE, and it is a measurement, not a direction.
   This sheet states two brightness factors — 1.08 here and on .mud-button-filled-primary, 0.92 on
   #blazor-error-ui.kt-error-ui .reload — and an authority that says two things is how the estate
   ends up with two dialects. They are ONE rule: the HOVERED pair must clear 4.5:1, not just the
   resting one. Both do, so neither of this sheet's two factors is a defect and neither is to be
   swept.
   WHICH DIRECTION IS SAFE IS NOT WHAT IT LOOKS LIKE. A filter paints the whole element, INK
   INCLUDED — not just the fill — and once you filter both, BOTH directions cost contrast. Every
   primary control here is #FFFFFF on --kt-cobalt-deep, 5.60:1 at rest. Brightening lifts the fill
   toward an ink already clamped at the channel ceiling that cannot move with it: 4.95:1 at 1.08.
   Darkening drops a bright ink further down the luminance curve than it drops an already-dark fill:
   5.32:1 at 0.92. So "darkening can only raise the ratio" is FALSE — it merely costs less, and that
   is the only reason it is the house default.
   NEW CONTROLS DARKEN, at 0.92. Neither factor is a free pass: push either far enough and the pair
   goes under (1.6 lands at 2.99:1), so a hover is measured before it ships.
   A FACTOR IS NOT A LICENCE. Every figure above is about ONE pair, #FFFFFF on --kt-cobalt-deep. The
   same 1.08 over #5D86FF — the light end of the ramp .kt-reconnect-btn used to carry, and a ground
   several products in this estate do use for a button — measures 2.98:1. A product copying a factor
   out of this sheet is copying a number, not a verdict, and has to measure it on the fill it lands
   on, in that product's own instrument. This sheet's tests read this sheet and nothing else.
   ControlFillContrastTests measures every state in this sheet that repaints or filters a pair, and
   computes every figure above; a third factor added here has to be decided there rather than
   becoming a third dialect. */
.kt-scrolltop:hover { filter: brightness(1.08); }
/* ONE RING COLOUR FOR THE WHOLE THEME, and the reason is where the ring actually lands. A positive
   outline-offset draws the ring OUTSIDE the border box, so it never sits on the control's own fill —
   it sits on whatever the page is showing there. That ground changes with the mode, and only
   --kt-cobalt clears 3:1 against all of them: 5.12:1 on the dark canvas, 3.31:1 on the lightest
   light surface. --kt-cobalt-tint looks like the obvious choice on a cobalt-filled button and is a
   trap — it measures 10.5:1 in dark and 1.70:1 in light, i.e. an indicator that vanishes for exactly
   the users who turned the lights on. InteractiveFocusTests measures every ring in this sheet
   against every ground in both modes, so the rule cannot regress quietly.
   This button's own 1px border does NOT change on focus, so the ring stays the single indicator. */
.kt-scrolltop:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}

/* Milestone banner — quiet surface + hairline */
.kt-milestone {
    position: relative;
    overflow: hidden;
    border-radius: var(--kt-radius-card);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-surface);
    padding: 1.1rem 1.35rem;
    display: flex; align-items: center; gap: 1rem;
}
.kt-milestone-icon {
    flex: 0 0 auto;
    width: 46px; height: 46px; border-radius: 13px;
    display: grid; place-items: center;
    background: rgba(245,196,81,0.12);
    color: var(--kt-gold);
}

/* Brand mark */
.kt-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: var(--kt-font-head);
    font-weight: 800;
    letter-spacing: -0.02em;
    font-size: 1.15rem;
    color: var(--kt-text);
    text-decoration: none;
}
.kt-brand-mark {
    width: 34px;
    height: 34px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    /* Logo PNG is transparent — white tile so the mark reads on dark chrome (not a black square). */
    background: #FFFFFF;
    border: 1px solid var(--kt-hairline);
    box-shadow: none;
    color: #0B0F17;
    flex: 0 0 auto;
    overflow: hidden;
}
.kt-brand-mark img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.kt-brand-mark svg { width: 20px; height: 20px; display: block; }
.kt-brand small {
    display: block;
    font-family: var(--kt-font-body);
    font-weight: 500;
    font-size: 0.62rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--kt-muted);
    margin-top: -2px;
}

/* ============================================================
   Eyebrow / overline — the small uppercase label above a value or
   section title (DESIGN-SPEC §3.2). Lives here because eight hosts
   use .kt-eyebrow in markup; hosts that ship their own definition
   load their stylesheet after this one and still win.
   ============================================================ */
.kt-eyebrow {
    font-family: var(--kt-font-body);
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--kt-muted);
    margin-bottom: 4px;
}

/* ============================================================
   Cards — border brighten on hover (no Y-lift)
   ============================================================ */
.kt-card {
    border-radius: var(--kt-radius-card) !important;
    border: 1px solid var(--kt-hairline);
    background: var(--kt-surface);
    transition: box-shadow var(--kt-dur) var(--kt-ease), border-color var(--kt-dur) var(--kt-ease);
}

/* Canonical inner padding for cards and nested panels (1.25rem / pa-5).
   Wrapped in :where() so it carries ZERO specificity: it is the default every card
   gets without having to remember a utility class, while any explicit MudBlazor
   pa-* utility or app rule still wins. Without this, a bare "kt-card" had no padding
   at all and content sat against the border — the padding was inconsistent across
   the portfolio because each app had to opt in. */
:where(.kt-card) { padding: 1.25rem; }
:where(.kt-inset) { padding: 1rem 1.1rem; }

/* ============================================================
   Upload dropzone (DESIGN-SPEC §9.9) — dashed hairline, fill-subtle,
   cloud icon. Theme-owned so every app's upload surface is identical
   and it can never fall back to the raw browser file control.
   ============================================================ */
.kt-dropzone-wrap {
    position: relative;
    display: block;
    width: 100%;
    cursor: pointer;
}
.kt-dropzone {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 18px;
    border: 1px dashed var(--kt-hairline);
    border-radius: var(--kt-radius);
    background: var(--kt-fill-subtle);
    color: var(--kt-muted);
    text-align: center;
    transition: border-color var(--kt-dur) var(--kt-ease), background var(--kt-dur) var(--kt-ease), color var(--kt-dur) var(--kt-ease);
}
.kt-dropzone-wrap:hover .kt-dropzone {
    border-color: rgba(79, 123, 255, 0.55);
    background: var(--kt-cobalt-soft);
}
/* DISABLED, and it is a real state rather than a hypothetical one: Deployer's Backups page renders
   <InputFile class="kt-dropzone-input" disabled="@_uploading" />, so the zone spends every upload
   disabled. Until now it looked exactly like a live zone AND still lit up under the cursor, which
   invites the second click that the control has already stopped accepting.
   Dimming with opacity rather than a muted colour is deliberate: it is the same technique
   .kt-login-submit:disabled already uses, it needs no new ink, and it cannot drag any text under
   the contrast floor by introducing a colour of its own. */
.kt-dropzone-wrap:has(.kt-dropzone-input:disabled) .kt-dropzone {
    opacity: 0.55;
    cursor: not-allowed;
}
/* The hover lift is suppressed second and separately: :has() above only dims, and a dimmed zone
   that still brightens on hover reads as available. */
.kt-dropzone-wrap:has(.kt-dropzone-input:disabled):hover .kt-dropzone {
    border-color: var(--kt-hairline);
    background: var(--kt-fill-subtle);
}
/* Dragging over: the zone commits to a solid cobalt edge. */
.kt-dropzone-over .kt-dropzone {
    border-style: solid;
    border-color: var(--kt-cobalt);
    background: rgba(79, 123, 255, 0.12);
    color: var(--kt-text);
}
/* The real <input type="file"> is always hidden behind the styled zone — if this rule
   is missing the browser's default "Choose file" control appears. */
.kt-dropzone-input {
    display: none;
}
.kt-card--hover:hover {
    box-shadow: var(--kt-shadow-card);
    border-color: rgba(79, 123, 255, 0.28);
}

/* DemoBlock — quieter catalogue rhythm (also uses Mud pa-5 mb-6 utilities) */
.kt-demo {
    padding: var(--kt-space-5, 20px) !important;
    margin-bottom: var(--kt-space-6, 24px) !important;
    box-sizing: border-box;
}
/* Inside MudGrid: fill the cell, let the grid gap own vertical rhythm */
.mud-grid-item > .kt-demo {
    height: 100%;
    margin-bottom: 0 !important;
}
.kt-demo__title {
    margin: 0;
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 1.05rem;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--kt-text);
}
.kt-demo__header {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--kt-space-3);
    margin-bottom: var(--kt-space-4);
}

/* Inset panel — hairline well INSIDE a card (never a second .kt-card) */
.kt-inset {
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius);
    background: var(--kt-fill-subtle);
    overflow: hidden;
}

/* Section wrapper — 4px rhythm */
.kt-section { margin-bottom: var(--kt-space-10); }
.kt-section-label {
    font-family: var(--kt-font-body);
    font-weight: 600;
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--kt-cobalt-tint);
    margin-bottom: var(--kt-space-2);
}

/* ============================================================
   MudBlazor polish — shared overrides (AA, motion, focus)
   ============================================================ */
.mud-button-filled-primary {
    background: var(--kt-cobalt-deep) !important;
    box-shadow: none;
    transition: filter var(--kt-dur-fast) var(--kt-ease), transform var(--kt-dur-fast) var(--kt-ease);
}
/* The hover rule is stated once, at .kt-scrolltop:hover — including why a factor copied out of here
   carries no verdict anywhere else. This 1.08 is kept because it MEASURES above the bar (4.95:1 on
   the house pair), not because brightening is house style — it is not. New controls darken. */
.mud-button-filled-primary:hover {
    filter: brightness(1.08);
}
.mud-button-filled-primary:active {
    transform: translateY(1px);
}
/* Buttons have no border of their own to light, so they get the offset focus ring. */
.mud-button:focus-visible,
.mud-icon-button:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}

/* Inputs light their OWN border on focus (MudBlazor already sets it to 2px primary), so
   an additional offset ring reads as two borders around one field. The field's border is
   the single focus indicator — recoloured to the cobalt accent.
   Selector deliberately mirrors MudBlazor's own (.mud-input.mud-input-outlined:focus-within):
   dropping the leading .mud-input drops a class and MudBlazor's rule wins the recolour. */
.mud-input.mud-input-outlined:focus-within > .mud-input-outlined-border,
.mud-input.mud-input-outlined:focus-within .mud-shrink > .mud-input-outlined-border {
    border-width: 2px;
    border-color: var(--kt-cobalt);
}
a.kt-link, .mud-link {
    color: var(--kt-cobalt-tint);
}
a.kt-link:hover, .mud-link:hover {
    color: var(--kt-cobalt-bright);
}
.kt-text-cobalt { color: var(--kt-cobalt-tint) !important; }

/* Page heading rhythm */
.kt-page-heading {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--kt-space-4);
    margin-bottom: var(--kt-space-6);
}
.kt-page-heading__actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--kt-space-2);
}
.kt-page-heading h1 {
    margin: 0;
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 1.25rem;
    line-height: 1.25;
    letter-spacing: -0.015em;
}
.kt-crumb {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--kt-muted);
    margin-bottom: var(--kt-space-1);
}

/* ============================================================
   Responsive grids
   ============================================================ */
.kt-grid { display: grid; gap: 1.25rem; }
.kt-grid--cards { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.kt-grid--kpi   { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.kt-grid--tiles { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }

/* ============================================================
   Pills / badges
   ============================================================ */
.kt-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: var(--kt-radius-pill);
    font-family: var(--kt-font-body);
    font-weight: 600;
    font-size: 0.72rem;
    line-height: 1;
    letter-spacing: 0.01em;
    border: 1px solid transparent;
    white-space: nowrap;
}
.kt-pill--muted    { background: rgba(154,166,188,0.10); color: var(--kt-muted); border-color: var(--kt-hairline); }
.kt-pill--cobalt   { background: var(--kt-cobalt-soft); color: #A9BEFF; border-color: rgba(79,123,255,0.22); }
.kt-pill--featured { background: rgba(245,196,81,0.12); color: var(--kt-gold); border-color: rgba(245,196,81,0.22); }
.kt-pill--ending   { background: rgba(255,92,114,0.12); color: #FF8A98; border-color: rgba(255,92,114,0.22); }
/* Live carries a soft teal halo so "this is live" reads at a glance — the one place in the
   chrome that glows. Kept at the soft scale; .kt-glow-live is the brighter board-scale version. */
.kt-pill--live     { background: rgba(34,211,167,0.12); color: #5AE7C4; border-color: rgba(34,211,167,0.22); box-shadow: var(--kt-glow-teal-soft); }

.kt-live-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--kt-teal);
    box-shadow: 0 0 0 0 rgba(34,211,167,0.7);
    animation: kt-pulse 1.8s infinite;
}
@keyframes kt-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(34,211,167,0.55); }
    70%  { box-shadow: 0 0 0 7px rgba(34,211,167,0); }
    100% { box-shadow: 0 0 0 0 rgba(34,211,167,0); }
}

/* ============================================================
   Hero
   ============================================================ */
.kt-hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--kt-radius-card);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-hero-grad);
    padding: clamp(1.75rem, 4vw, 3rem);
}
.kt-hero::before {
    content: "";
    position: absolute;
    top: -40%; right: -10%;
    width: 55%; height: 150%;
    background: radial-gradient(closest-side, rgba(79,123,255,0.12), rgba(79,123,255,0) 70%);
    pointer-events: none;
}
.kt-hero--quiet::before { display: none; }

/* Quiet media strip for product cards (no radial glow / icon theater) */
.kt-media-strip {
    height: 112px;
    display: flex;
    align-items: flex-end;
    padding: 0 1.15rem 1rem;
    background: var(--kt-fill-subtle);
    border-bottom: 1px solid var(--kt-hairline);
}
.kt-media-strip__mark {
    font-family: var(--kt-font-head);
    font-weight: 800;
    font-size: 1.75rem;
    letter-spacing: -0.03em;
    color: var(--kt-muted);
    opacity: 0.55;
    line-height: 1;
}
.kt-hero-grid {
    position: relative;
    display: grid;
    grid-template-columns: 1.3fr 1fr;
    gap: 2rem;
    align-items: center;
}
/* two-column hero collapses to one below 960px (design-language breakpoint) */
@media (max-width: 960px) {
    .kt-hero-grid { grid-template-columns: 1fr; }
}
.kt-hero-title {
    font-family: var(--kt-font-head);
    font-weight: 800;
    font-size: clamp(2rem, 4.5vw, 3.25rem);
    line-height: 1.05;
    letter-spacing: -0.03em;
    color: var(--kt-text);
    background: none;
    -webkit-text-fill-color: unset;
    margin: 0 0 0.75rem 0;
    text-align: left;
    max-width: 14ch;
}

/* ============================================================
   Countdown
   ============================================================ */
.kt-countdown { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.kt-countdown-cell {
    flex: 1 1 0;
    min-width: 56px;
    max-width: 84px;
    text-align: center;
    padding: 10px 8px;
    border-radius: var(--kt-radius);
    background: var(--kt-fill-subtle);
    border: 1px solid var(--kt-hairline);
}
.kt-countdown-num {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 1.6rem;
    line-height: 1;
    color: var(--kt-text);
    font-variant-numeric: tabular-nums;
}
.kt-countdown-lbl {
    display: block;
    margin-top: 5px;
    font-size: 0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--kt-muted);
}

/* ============================================================
   Ticket chips
   ============================================================ */
.kt-tickets { display: flex; flex-wrap: wrap; gap: 8px; }
.kt-ticket {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    padding: 7px 12px;
    border-radius: 10px;
    background: var(--kt-cobalt-soft);
    color: #B7C7FF;
    border: 1px solid rgba(79,123,255,0.3);
}

/* ============================================================
   Progress — thin fill, no outer glow (glow reserved for chart caps)
   ============================================================ */
.kt-progress {
    height: 6px;
    border-radius: 999px;
    background: var(--kt-track);
    overflow: hidden;
    border: 1px solid var(--kt-hairline);
}
.kt-progress-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--kt-cobalt-deep);
    box-shadow: none;
    transition: width .5s cubic-bezier(.4,0,.2,1);
}

/* ============================================================
   KPI tiles
   ============================================================ */
/* KPI / metric rows — operational, not “big number / small label / accent” theater */
.kt-kpi {
    padding: 0.95rem 1.05rem 1rem;
    border-radius: var(--kt-radius-card);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-surface);
    position: relative;
    overflow: hidden;
    transition: border-color .18s ease;
    box-shadow: var(--kt-shadow-feather);
}
.kt-kpi::after { display: none !important; }
.kt-kpi:hover { border-color: color-mix(in srgb, var(--accent, var(--kt-cobalt)) 28%, transparent); }
.kt-kpi-label {
    font-size: 0.8rem;
    letter-spacing: 0;
    text-transform: none;
    color: var(--kt-text);
    font-weight: 600;
    padding-right: 0;
}
.kt-kpi-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.35rem;
}
.kt-kpi-value {
    font-family: var(--kt-font-body);
    font-weight: 600;
    font-size: 1.05rem;
    line-height: 1.2;
    color: var(--kt-text);
    margin-top: 0;
    padding-right: 0;
    font-variant-numeric: tabular-nums;
}
.kt-kpi-icon { display: none !important; }
.kt-kpi-delta {
    font-size: 0.75rem;
    font-weight: 500;
    margin-top: 0.45rem;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.kt-kpi .kt-sparkline { margin-top: 0.55rem; opacity: 0.75; height: 22px; }
.kt-up   { color: var(--kt-teal); }
.kt-down { color: var(--kt-red); }

/* ============================================================
   Ops-board layer (additive, opt-in) — glass surface, live glow,
   hero KPI and the segmented leaderboard bar. None of these change
   an existing component, so no app in the estate shifts until it
   uses these classes. See the :root tokens above.
   ============================================================ */

/* Tinted glass card variant — depth without a new colour. Degrades to
   the flat --kt-glass fill where backdrop-filter is unsupported. */
.kt-card--glass {
    background: var(--kt-glass);
    border-color: var(--kt-glass-line);
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
}

/* Opt-in teal halo for "live" emphasis. Deliberately NOT applied to
   .kt-pill--live itself, so the estate is unchanged; add this class to
   opt in (e.g. on a wall-mounted board). */
.kt-glow-live { box-shadow: var(--kt-glow-teal); }

/* Hero KPI — a board-scale variant of .kt-kpi (.kt-kpi is position:
   relative; overflow:hidden already, so the accent rail is contained). */
.kt-kpi--hero { padding: 1.15rem 1.25rem 1.2rem; }
.kt-kpi--hero::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: linear-gradient(180deg, var(--kt-teal), var(--kt-cobalt));
    box-shadow: var(--kt-glow-teal);
}
.kt-kpi--hero .kt-kpi-value {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: clamp(1.7rem, 3.6vw, 2.4rem);
    letter-spacing: -0.02em;
    background: linear-gradient(180deg, #FFFFFF, #B7C4DA);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
/* Gradient ink only reads on the dark ground; light mode takes solid text. */
:root[data-theme="light"] .kt-kpi--hero .kt-kpi-value,
.kt-mode-light .kt-kpi--hero .kt-kpi-value {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: var(--kt-text);
}

/* Segmented leaderboard bar — position · who · amount · split-cap bar.
   Reads at a glance on a wall board; rank 1 takes the gold treatment. */
.kt-rankbar { display: flex; flex-direction: column; gap: 0.75rem; }
.kt-rank {
    display: grid;
    grid-template-columns: 22px 1fr auto;
    align-items: center;
    column-gap: 0.75rem;
}
.kt-rank__pos {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 0.9rem;
    color: var(--kt-muted);
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.kt-rank__who { min-width: 0; }
.kt-rank__name {
    font-weight: 600;
    font-size: 0.9rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kt-rank__meta { font-size: 0.74rem; color: var(--kt-muted); }
.kt-rank__amt {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 0.92rem;
    font-variant-numeric: tabular-nums;
}
.kt-rank__bar {
    grid-column: 2 / -1;
    height: 8px;
    margin-top: 0.4rem;
    border-radius: var(--kt-radius-pill);
    background: var(--kt-track);
    overflow: hidden;
}
.kt-rank__bar > span {
    display: block;
    height: 100%;
    border-radius: 8px 3px 3px 8px;
    background: linear-gradient(90deg, var(--kt-cobalt), var(--kt-teal));
}
.kt-rank--lead .kt-rank__pos { color: var(--kt-gold); }
.kt-rank--lead .kt-rank__bar > span {
    background: linear-gradient(90deg, var(--kt-gold), #FFDF8A);
    box-shadow: 0 0 14px rgba(245, 196, 81, 0.4);
}

/* ============================================================
   Swatches (design tokens page)
   ============================================================ */
.kt-swatch {
    border-radius: var(--kt-radius-card);
    border: 1px solid var(--kt-hairline);
    overflow: hidden;
    background: var(--kt-surface);
}
.kt-swatch-color { height: 84px; }
.kt-swatch-body { padding: 12px 14px; }
.kt-swatch-name { font-weight: 600; font-size: 0.85rem; color: var(--kt-text); }
.kt-swatch-hex  { font-size: 0.75rem; color: var(--kt-muted); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }

/* ============================================================
   Charts: sparklines, area chart, day-selector chips
   ============================================================ */
.kt-sparkline { width: 100%; height: 30px; display: block; margin-top: 10px; opacity: .9; }

.kt-legend {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 0.75rem; color: var(--kt-muted);
}
.kt-legend-swatch { width: 14px; height: 4px; border-radius: 999px; }
.kt-legend-swatch--dash { background: none; border-top: 2px dashed; height: 0; }

.kt-daychips {
    display: flex; gap: 8px;
    overflow-x: auto;
    padding: 2px 2px 10px;
    scrollbar-width: thin;
}
.kt-daychip {
    flex: 0 0 auto;
    min-width: 58px;
    padding: 9px 6px 7px;
    text-align: center;
    border-radius: var(--kt-radius);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-fill-subtle);
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease, transform .15s ease;
    user-select: none;
    /* The chip is a real <button> so it is keyboard-operable without a tabindex and a click handler
       of its own — which is why these three resets are here. A <button> otherwise takes the UA font
       and colour, and the chip would suddenly render in the browser's default face at the browser's
       default size, ignoring the house scale the theme just fought to own. */
    font: inherit;
    color: inherit;
    appearance: none;
}
.kt-daychip:hover { border-color: rgba(79,123,255,0.35); }
/* Bordered, but the border is already carrying hover AND selection — a third meaning on the same
   1px would be unreadable. The ring is the focus indicator here and the border is left alone, so
   there is still exactly one indicator for focus. */
.kt-daychip:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}
.kt-daychip--active {
    background: var(--kt-cobalt-soft);
    border-color: rgba(79,123,255,0.45);
}
.kt-daychip-num {
    font-family: var(--kt-font-head);
    font-weight: 700; font-size: 1.05rem; line-height: 1.1;
    color: var(--kt-text);
    font-variant-numeric: tabular-nums;
}
.kt-daychip--active .kt-daychip-num { color: #B7C7FF; }
.kt-daychip-lbl { font-size: 0.66rem; color: var(--kt-muted); letter-spacing: .04em; }

/* status chip (avatar cluster + "x of y live") */
.kt-statuschip {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 14px 6px 8px;
    border-radius: var(--kt-radius-pill);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-fill-subtle);
    font-size: 0.82rem; color: var(--kt-muted);
    white-space: nowrap;
}
.kt-statuschip b { color: var(--kt-text); font-weight: 600; }

/* ============================================================
   Live Ops — ongoing draw cards + rails
   ============================================================ */
.kt-timechip {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 3px 10px;
    border-radius: var(--kt-radius-pill);
    background: var(--kt-fill-subtle);
    border: 1px solid var(--kt-hairline);
    font-size: 0.74rem; font-weight: 600;
    color: var(--kt-text);
    font-variant-numeric: tabular-nums;
}

.kt-dotmatrix {
    display: grid;
    /* fluid columns: dots re-flow to the card's width instead of forcing a
       24-column minimum (24×9px + gaps ≈ 331px) that overflowed narrow cards */
    grid-template-columns: repeat(auto-fill, minmax(11px, 1fr));
    gap: 6px 4px;
    margin: 4px 0 12px;
    overflow: hidden;
}
.kt-dot {
    width: 9px; height: 9px; border-radius: 50%;
    background: rgba(154,166,188,0.16);
    justify-self: center;
}
.kt-dot--sold, .kt-dot--used { background: var(--kt-cobalt); box-shadow: 0 0 5px rgba(79,123,255,0.4); }
.kt-dot--win, .kt-dot--special  { background: var(--kt-gold); }
.kt-dot--hold, .kt-dot--reserved { background: transparent; border: 1.5px solid var(--kt-cobalt); box-sizing: border-box; }
.kt-dotmatrix-legend {
    display: flex; flex-wrap: wrap; gap: var(--kt-space-4);
    font-size: 0.75rem; color: var(--kt-muted); margin-top: var(--kt-space-2);
}
.kt-dotmatrix-legend .kt-dot { display: inline-block; vertical-align: middle; margin-right: 4px; }

.kt-railitem {
    position: relative;
    display: flex; align-items: center; gap: .7rem;
    padding: .7rem .85rem;
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius-card);
    background: var(--kt-fill-subtle);
    margin-bottom: .6rem;
}
.kt-railbadge {
    position: absolute; top: -7px; right: 10px;
    padding: 2px 8px;
    border-radius: var(--kt-radius-pill);
    background: var(--kt-surface-2);
    border: 1px solid var(--kt-hairline);
    font-size: 0.66rem; font-weight: 700;
    color: var(--kt-gold);
    font-variant-numeric: tabular-nums;
}
.kt-railbadge--red { color: #FF8A98; }

/* ============================================================
   Prize tracking — legs strip, map pulse, distance ticks
   ============================================================ */
.kt-legs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 1rem;
    margin: 1rem 0 1.25rem;
}
.kt-leg {
    padding: .8rem 1rem;
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius-card);
    background: var(--kt-fill-subtle);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.kt-leg--active {
    border-color: rgba(79,123,255,0.5);
    box-shadow: 0 0 18px rgba(79,123,255,0.18);
    background: var(--kt-cobalt-soft);
}
.kt-leg-line { display: flex; align-items: center; gap: 6px; margin: 8px 0 6px; }
.kt-leg-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.kt-leg-dash { flex: 1; border-top: 2px dashed; height: 0; }

.kt-mappulse {
    transform-box: fill-box;
    transform-origin: center;
    animation: kt-mappulse 2.2s ease-out infinite;
}
@keyframes kt-mappulse {
    0%   { transform: scale(1);    opacity: .8; }
    100% { transform: scale(2.4);  opacity: 0; }
}

.kt-ticks { display: flex; align-items: center; gap: 5px; }
.kt-tick {
    flex: 1;
    height: 10px;
    border-radius: 999px;
    background: rgba(154,166,188,0.18);
    min-width: 3px; max-width: 6px;
}
.kt-tick--done { background: var(--kt-teal); box-shadow: 0 0 6px rgba(34,211,167,0.4); }

/* ============================================================
   Auth layout (login / register) — matches Deployer kt-auth-bg
   ============================================================ */
.kt-auth {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    /* No solid fill: the constellation field lives behind the page and a canvas-coloured
       layer here would hide it. The html backdrop supplies the base colour, and light
       mode restores the flat fill (see the light-mode block near the top). */
    background:
        radial-gradient(circle at 25% 20%, rgba(79, 123, 255, 0.18), transparent 45%),
        radial-gradient(circle at 78% 75%, rgba(34, 211, 167, 0.10), transparent 45%);
}
.kt-auth-card {
    width: 100%;
    max-width: 420px;
    background: var(--kt-surface);
}

/* Login form controls (Deployer-parity native fields) */
.kt-login-input {
    width: 100%;
    box-sizing: border-box;
    margin-top: 6px;
    padding: 11px 13px;
    border-radius: 12px;
    border: 1px solid var(--kt-hairline);
    background: var(--kt-surface-raised);
    color: var(--kt-text);
    /* 1rem, not 0.95rem: at 15.2px iOS Safari auto-zooms the whole page on focus and the user has to
       pinch back out to reach the password field. 16px is the threshold. MudBlazor's own inputs already
       measure 16px, so this is the only field in the system that needed it. */
    font-size: 1rem;
    font-family: inherit;
}
/* Same rule for the native login fields: thicken and recolour the field's OWN border
   rather than drawing a separate ring outside it, so focus reads as one border.
   The inset shadow doubles the border weight without changing the box size. */
.kt-login-input:focus {
    outline: none;
    border-color: var(--kt-cobalt);
    box-shadow: inset 0 0 0 1px var(--kt-cobalt);
}
.kt-login-input--password {
    padding-right: 76px;
}
.kt-login-show {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(calc(-50% + 3px));
    border: none;
    background: none;
    color: var(--kt-muted);
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    padding: 6px 8px;
    font-family: inherit;
    /* Was 46x31. The field is 47px tall, so a 44px tap target fits inside it without shifting the
       layout - and this control sits right beside the password field, where a mis-tap is expensive. */
    min-height: 44px;
    min-width: 44px;
    display: grid;
    place-items: center;
}
/* Inset ring (negative offset), because this control sits INSIDE the password field's box. A
   positive offset would draw the ring on top of the field's own border and read as "the field is
   focused" when focus is actually on the reveal toggle — the one confusion this field cannot
   afford. Focus is only ever on one of the two, so the field's border rule and this never both
   fire: still one indicator per control. */
.kt-login-show:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: -2px;
}
.kt-login-submit {
    width: 100%;
    padding: 12px;
    border: none;
    border-radius: 12px;
    background: var(--kt-cobalt-deep);
    color: #FFFFFF;
    font-family: var(--kt-font-body);
    font-weight: 600;
    font-size: 0.95rem;
    cursor: pointer;
    box-shadow: 0 0 20px rgba(79, 123, 255, 0.3);
    transition: filter var(--kt-dur) var(--kt-ease), box-shadow var(--kt-dur) var(--kt-ease);
}
.kt-login-submit:hover:not(:disabled) {
    filter: brightness(1.08);
    box-shadow: var(--kt-glow-cobalt);
}
.kt-login-submit:disabled {
    opacity: 0.65;
    cursor: not-allowed;
}
/* The house ring — see .kt-scrolltop for why one colour serves every control and every mode. This
   is the primary control on the one page every user of every product in the estate has to get
   through, and until now it had no visible focus at all. */
.kt-login-submit:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}

/* Persistent demo disclosure (GPTFindings THEME-01) — hairline strip, not a card. */
.kt-demo-banner {
    margin: 0;
    padding: 0.45rem 1.25rem;
    font-size: 0.75rem;
    line-height: 1.35;
    letter-spacing: 0.01em;
    color: var(--kt-muted);
    background: rgba(79, 123, 255, 0.06);
    border-bottom: 1px solid var(--kt-hairline);
    text-align: center;
}
.kt-mode-light .kt-demo-banner {
    background: rgba(79, 123, 255, 0.08);
}
.kt-demo-banner--auth {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 20;
}
.kt-auth { padding-top: 2.5rem; }

/* ============================================================
   Kanban board
   ============================================================ */
.kt-caption-muted { font-size: 0.75rem; color: var(--kt-muted); }

.kt-kanban {
    display: grid;
    grid-template-columns: repeat(4, minmax(240px, 1fr));
    gap: 1rem;
    overflow-x: auto;
    padding-bottom: 0.5rem;
}
.kt-kanban-col { display: flex; flex-direction: column; min-width: 240px; }
.kt-kanban-head {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0 0.25rem 0.75rem;
}
.kt-kanban-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.kt-kanban-title { font-weight: 600; font-size: 0.9rem; color: var(--kt-text); flex: 1; }
.kt-kanban-zone {
    flex: 1;
    min-height: 140px;
    border-radius: var(--kt-radius-card);
    border: 1px dashed var(--kt-hairline);
    background: var(--kt-fill-subtle);
    padding: 0.75rem;
    transition: background .15s ease, border-color .15s ease;
}
.kt-kanban-zone--over {
    border-color: rgba(79,123,255,0.5);
    background: var(--kt-cobalt-soft);
}
.kt-kanban-card {
    background: var(--kt-surface);
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius);
    padding: 0.85rem;
    margin-bottom: 0.65rem;
    cursor: grab;
    transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.kt-kanban-card:hover { border-color: rgba(79,123,255,0.35); box-shadow: var(--kt-shadow-card); }
.kt-kanban-card:active { cursor: grabbing; }
.kt-kanban-card-title { font-weight: 600; font-size: 0.92rem; color: var(--kt-text); line-height: 1.3; }

@media (max-width: 960px) {
    .kt-kanban { grid-auto-flow: column; grid-template-columns: none; }
}

/* ============================================================
   Layout guards — keep content inside its blocks
   ============================================================ */
/* DataGrid/table toolbars: wrap instead of overflowing the card on narrow widths */
.mud-table-toolbar {
    flex-wrap: wrap;
    height: auto;
    min-height: 64px;
    row-gap: .5rem;
    padding-top: .6rem;
    padding-bottom: .6rem;
}

/* row-click affordance used by the entrants grid */
.cursor-pointer, .cursor-pointer td { cursor: pointer; }

/* the app-bar search dropdown must not be clipped by the toolbar */
.kt-appbar .mud-toolbar { overflow: visible; }

/* long unbroken content (emails, ids) must not push tables out of cards */
.kt-card { min-width: 0; }
.kt-card .mud-table-container { overflow-x: auto; }

/* stepper + tab headers scroll sideways instead of escaping the page on small screens */
.mud-stepper-nav { overflow-x: auto; }
.mud-tabs-tabbar-content { overflow-x: auto; scrollbar-width: thin; }
.mud-tabs-tabbar-wrapper {
    mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 12px), transparent);
}

/* Tabs as a system: 44px thumb targets, no wrap, slider eases instead of a hard cut.
   Corpus §2.3 / P2 — damping 22 / stiffness 200 approximated as --kt-ease-spring at 180ms. */
.mud-tab {
    min-height: 44px;
    white-space: nowrap;
    text-transform: none !important;
    letter-spacing: 0;
    transition: color var(--kt-dur) var(--kt-ease);
}
.mud-tabs-slider,
.mud-tab-slider {
    transition: left var(--kt-dur) var(--kt-ease-spring),
                width var(--kt-dur) var(--kt-ease-spring);
}

/* space between tab labels and panel content so they don't crowd */
.mud-tabs-panels {
    padding-top: 1.25rem;
    animation: kt-tab-fade var(--kt-dur) var(--kt-ease);
}
@keyframes kt-tab-fade {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* distance ticks (tracking page): shrink to slivers and clip, never escape */
.kt-ticks { overflow: hidden; gap: 3px; }
.kt-tick { min-width: 2px; }

/* ============================================================
   Accessibility & media
   ============================================================ */
/* AA contrast: brand-cobalt text on dark surfaces uses the light cobalt tint */
.mud-chip-outlined.mud-chip-color-primary { color: #A9BEFF; }

/* Honour OS-level reduced-motion: freeze pulses, floats, slides and lifts */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* Print (used by the Invoice page): drop the chrome, keep the document */
@media print {
    .mud-appbar, .mud-drawer, .kt-footer, .kt-drawer-foot,
    #blazor-error-ui, .mud-snackbar { display: none !important; }
    .mud-main-content { padding: 0 !important; }
    body, .mud-layout { background: #fff !important; }
    .kt-card {
        border-color: #d8dce4 !important;
        box-shadow: none !important;
        break-inside: avoid;
    }
}

/* ============================================================
   Blazor form validation + error UI (re-themed)
   ============================================================ */
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--kt-teal); }
.invalid { outline: 1px solid var(--kt-red); }
.validation-message { color: var(--kt-red); }

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem 1rem 1rem 1rem;
    color: white;
    border-radius: var(--kt-radius);
}
.blazor-error-boundary::after { content: "An error has occurred."; }

#blazor-error-ui {
    color-scheme: dark;
    background: var(--kt-surface-2);
    color: var(--kt-text);
    border-top: 1px solid var(--kt-hairline);
    bottom: 0;
    box-shadow: 0 -1px 20px rgba(0, 0, 0, 0.5);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.9rem 1.4rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}
#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 1rem;
    top: 0.75rem;
}

/* ------------------------------------------------------------
   Circuit-failure card — opt in with class="kt-error-ui".

   UseExceptionHandler only catches an exception that reaches the HTTP
   pipeline. An unhandled exception INSIDE a Blazor Server component kills the
   circuit instead: the framework runs document.querySelector('#blazor-error-ui')
   and sets style="display:block" on whatever it finds, and a host without that
   element leaves the user on a page that has silently stopped responding.

   Two rules the framework imposes on anything styled here:
     * the show is an inline `display: block`, so the root must lay out as a
       block and the flexing must happen on its children;
     * the reload and dismiss handlers are bound by the `.reload` and `.dismiss`
       class names, so those names are load-bearing, not decorative.

   Selectors are id+class so a host that still carries the plain bar keeps it —
   this is additive, and nothing renders differently until the markup opts in.
   ------------------------------------------------------------ */
#blazor-error-ui.kt-error-ui {
    position: fixed;
    right: var(--kt-space-5);
    bottom: var(--kt-space-5);
    left: auto;
    width: min(420px, calc(100vw - 2 * var(--kt-space-5)));
    padding: var(--kt-space-5);
    box-sizing: border-box;
    background: var(--kt-surface);
    color: var(--kt-text);
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius-card);
    box-shadow: var(--kt-shadow-lift);
    font-family: var(--kt-font-body);
    font-size: 0.95rem;
    line-height: 1.5;
    z-index: 2100;
}

#blazor-error-ui.kt-error-ui .kt-error-ui__head {
    display: flex;
    align-items: center;
    gap: var(--kt-space-2);
    margin: 0 0 var(--kt-space-2);
    font-family: var(--kt-font-head);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

#blazor-error-ui.kt-error-ui .kt-error-ui__dot {
    flex: 0 0 8px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--kt-cobalt);
    box-shadow: var(--kt-glow-cobalt);
}

#blazor-error-ui.kt-error-ui .kt-error-ui__body {
    margin: 0 0 var(--kt-space-5);
    color: var(--kt-muted);
}

#blazor-error-ui.kt-error-ui .kt-error-ui__actions {
    display: flex;
    align-items: center;
    gap: var(--kt-space-3);
}

#blazor-error-ui.kt-error-ui .reload {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.5rem 1.15rem;
    border: 0;
    border-radius: var(--kt-radius-pill);
    /* --kt-cobalt-deep, not --kt-cobalt. White on #4F7BFF is 3.74:1 — below the 4.5:1 AA bar — and
       this card is the one surface a user reads when everything else has already gone wrong. Every
       other white-on-cobalt button in this sheet already uses the deep tone (5.6:1); this rule was
       the one that did not. Caught by the product contrast instruments in Vault, Hub and KMS the
       moment those hosts started rendering this card. */
    background: var(--kt-cobalt-deep);
    color: #FFFFFF;
    font: inherit;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: filter var(--kt-dur) var(--kt-ease);
}
/* The house factor — see .kt-scrolltop:hover for the rule and the arithmetic. This rule used to say
   darkening "can only raise the contrast ratio, never lower it", which is what you get if you filter
   the fill and forget that the filter paints the white ink too. It does: 0.92 takes the house pair
   from 5.60:1 to 5.32:1. Darkening is the default because it costs LESS than brightening (4.95:1),
   not because it is free. */
#blazor-error-ui.kt-error-ui .reload:hover { filter: brightness(0.92); }
/* The house ring. Was the tint, which measured 10.5:1 against this card in dark and 1.83:1 in light
   — the card's surface is var(--kt-surface), i.e. white when the lights are on. See .kt-scrolltop. */
#blazor-error-ui.kt-error-ui .reload:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}

#blazor-error-ui.kt-error-ui .dismiss {
    position: static;
    right: auto;
    top: auto;
    margin-left: auto;
    padding: 0.45rem 0.6rem;
    border: 0;
    border-radius: var(--kt-radius);
    background: none;
    color: var(--kt-muted);
    font: inherit;
    cursor: pointer;
    transition: color var(--kt-dur) var(--kt-ease);
}
#blazor-error-ui.kt-error-ui .dismiss:hover { color: var(--kt-text); }
#blazor-error-ui.kt-error-ui .dismiss:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}

@media (max-width: 599px) {
    #blazor-error-ui.kt-error-ui {
        right: var(--kt-space-3);
        bottom: var(--kt-space-3);
        left: var(--kt-space-3);
        width: auto;
    }
}

/* ============================================================
   WASM loading splash (shown before Blazor boots)
   ============================================================ */
.kt-splash {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    background: radial-gradient(closest-side at 50% 38%, rgba(79,123,255,0.18), transparent 60%), #0B0F17;
    font-family: var(--kt-font-body);
    z-index: 2000;
}
.kt-splash-mark {
    width: 58px; height: 58px;
    border-radius: 16px;
    display: grid; place-items: center;
    background: linear-gradient(140deg, #5D86FF, #3A5BE0);
    box-shadow: var(--kt-glow-cobalt);
    margin-bottom: 0.75rem;
    animation: kt-splash-float 2.4s ease-in-out infinite;
}
/* Hosts put either an inline SVG or the logo PNG in the splash tile — size both, or an
   <img> renders at its intrinsic size and blows the tile out. */
.kt-splash-mark svg,
.kt-splash-mark img { width: 32px; height: 32px; object-fit: contain; }
@keyframes kt-splash-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-6px); }
}
.kt-splash-title {
    font-family: var(--kt-font-head);
    font-weight: 800; font-size: 1.5rem; letter-spacing: -0.02em;
    color: #E9EDF5;
}
.kt-splash-sub { color: #9AA6BC; font-size: 0.85rem; }
.kt-splash-bar {
    margin-top: 0.9rem;
    width: 180px; height: 4px; border-radius: 999px;
    background: rgba(255,255,255,0.08);
    overflow: hidden;
}
.kt-splash-bar span {
    display: block; height: 100%; width: 40%;
    border-radius: 999px;
    background: linear-gradient(90deg, #3A5BE0, #5D86FF);
    box-shadow: 0 0 12px rgba(79,123,255,0.6);
    animation: kt-splash-slide 1.2s ease-in-out infinite;
}
@keyframes kt-splash-slide {
    0%   { transform: translateX(-120%); }
    100% { transform: translateX(320%); }
}

/* ============================================================
   "Data is light" texture — fine grain over hero + auth surfaces
   ============================================================ */
.kt-hero::after, .kt-auth::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.05;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.kt-auth { position: relative; }
.kt-auth::after { position: fixed; }
.kt-auth-card { position: relative; z-index: 1; }

/* ============================================================
   Branded loader — the logo with a pulsing glow ring (data loads)
   ============================================================ */
.kt-loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
}
.kt-loader-mark {
    position: relative;
    display: grid; place-items: center;
}
.kt-loader-ring {
    position: absolute;
    inset: -7px;
    border-radius: 20px;
    border: 2px solid rgba(79,123,255,0.55);
    animation: kt-loader-pulse 1.6s ease-out infinite;
    pointer-events: none;
}
@keyframes kt-loader-pulse {
    0%   { transform: scale(1);    opacity: .9; }
    100% { transform: scale(1.45); opacity: 0; }
}
.kt-loader-msg { font-size: 0.82rem; color: var(--kt-muted); }

/* signature sample tiles on the Tokens page */
.kt-sig-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-width: 170px;
    padding: 14px;
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius-card);
    background: var(--kt-fill-subtle);
    text-align: center;
}


/* Branded Blazor reconnect overlay */
#components-reconnect-modal {
  max-width: none;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  color: #E9EDF5;
}

#components-reconnect-modal::backdrop {
  background: rgba(11, 15, 23, 0.92);
}

.kt-reconnect-shell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: #0B0F17;
  overflow: hidden;
}

.kt-reconnect-grain {
  position: absolute;
  inset: 0;
  opacity: 0.05;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.kt-reconnect-glow {
  position: absolute;
  width: 420px;
  height: 420px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(79,123,255,0.18) 0%, transparent 70%);
  pointer-events: none;
}

.kt-reconnect-card {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  text-align: center;
  padding: 2rem;
}

.kt-reconnect-tile {
  width: 58px;
  height: 58px;
  border-radius: 16px;
  display: grid;
  place-items: center;
  background: linear-gradient(140deg, #5D86FF, #3A5BE0);
  box-shadow: 0 0 24px rgba(79, 123, 255, 0.35);
  animation: kt-reconnect-float 2.4s ease-in-out infinite;
}

.kt-reconnect-tile img {
  width: 82%;
  height: 82%;
  object-fit: contain;
}

.kt-reconnect-brand {
  font-family: 'Outfit', sans-serif;
  font-weight: 800;
  font-size: 1.5rem;
  letter-spacing: -0.02em;
  margin: 0;
}

.kt-reconnect-msg {
  font-family: 'Source Sans 3', system-ui, sans-serif;
  font-size: 0.85rem;
  color: #9AA6BC;
  margin: 0;
}

.kt-reconnect-track {
  width: 180px;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
  margin-top: 0.35rem;
}

.kt-reconnect-bar {
  width: 40%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #3A5BE0, #5D86FF);
  box-shadow: 0 0 16px rgba(79, 123, 255, 0.55);
  animation: kt-reconnect-slide 1.2s ease-in-out infinite;
}

/* FLAT, NOT A RAMP, and this is the one button in the estate that could least afford to be either
   gradient-filled or hard to read. Retry and Resume are the only controls on screen once the circuit
   has dropped: the page behind them is dead, the overlay is a scrim, and the connection that is
   meant to carry the click is the thing that just failed.
   It was linear-gradient(180deg, #5D86FF 0%, #3A5BE0 100%) under #fff. The DECLARED END TONE is the
   house fill and measures 5.60:1, which is why nothing ever reported this: an instrument that
   resolves a background to one colour reads the end of the ramp and returns a clean number. The ink
   sits across the whole ramp, and white on the light stop — the top half of the label — is 3.32:1,
   under the 4.5:1 AA bar. The v1.3 deltas ban a gradient-filled button outright as well, so this was
   two house rules in one declaration, and the sheet every product in this estate is told to copy
   carried both.
   It is now the same flat --kt-cobalt-deep every other primary control here uses (.kt-login-submit,
   .kt-scrolltop, .mud-button-filled-primary, the circuit-failure .reload): 5.60:1 across the whole
   button rather than only at one end of it. ControlFillContrastTests measures every stop of every
   fill in this sheet and computes both figures above. */
.kt-reconnect-btn {
  margin-top: 0.5rem;
  padding: 0.65rem 1.25rem;
  border: none;
  border-radius: 12px;
  background: #3A5BE0;
  color: #fff;
  font-family: 'Source Sans 3', system-ui, sans-serif;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
}

/* The house ring — see .kt-scrolltop. Retry and Resume are the ONLY controls on screen once the
   circuit has dropped: the page behind is dead, so a keyboard user with no visible focus has
   nothing at all to aim at. */
.kt-reconnect-btn:focus-visible {
  outline: 2px solid var(--kt-cobalt);
  outline-offset: 2px;
}

.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible,
.components-rejoining-animation {
  display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
  display: block;
}

#components-reconnect-modal.components-reconnect-failed .kt-reconnect-btn.components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused .kt-reconnect-btn.components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .kt-reconnect-btn.components-resume-failed-visible {
  display: inline-flex;
}

@keyframes kt-reconnect-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}

@keyframes kt-reconnect-slide {
  0% { transform: translateX(-120%); }
  100% { transform: translateX(280%); }
}

@media (prefers-reduced-motion: reduce) {
  .kt-reconnect-tile,
  .kt-reconnect-bar,
  .kt-kpi-tile {
    animation: none !important;
    transition: none !important;
  }
}

/* ============================================================
   Real-product layer — texture, asymmetry, stagger, empty states
   ============================================================ */

:root {
    --kt-ease-spring: cubic-bezier(0.34, 1.2, 0.64, 1);
    --kt-ease-enter: cubic-bezier(0.22, 1, 0.36, 1);
    --kt-noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

.kt-grain { position: relative; }
.kt-grain::before {
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 40;
    opacity: 0.03;
    background-image: var(--kt-noise);
    background-repeat: repeat;
    background-size: 180px 180px;
    mix-blend-mode: overlay;
}
.mud-layout.kt-mode-light.kt-grain::before {
    opacity: 0.045;
    mix-blend-mode: multiply;
}

.kt-card { box-shadow: var(--kt-shadow-feather); }
.kt-card--pad-tight { padding: 14px 16px 18px !important; }
.kt-card--pad-body  { padding: 18px 22px 22px !important; }
.kt-card--pad-loose { padding: 26px 30px 28px !important; }
.kt-card--pad-split { padding: 16px 20px 24px !important; }

.mud-drawer .mud-nav-menu { padding: 4px 2px 12px; }
.kt-drawer-product { padding: 10px 14px !important; font-size: 0.88rem; }
.mud-main-content .mud-container {
    padding-top: 1.35rem !important;
    padding-bottom: 2.75rem !important;
}

.kt-grid--kpi {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1rem;
    align-items: stretch;
}
@media (max-width: 1100px) {
    .kt-grid--kpi { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 600px) {
    .kt-grid--kpi { grid-template-columns: 1fr; }
}

.kt-grid--cards {
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 1.25rem;
}
/* No permanent translateY offsets — they read as broken layout, not product polish */

.kt-grid--asymmetric {
    display: grid;
    grid-template-columns: 1.45fr 0.9fr;
    gap: 1.35rem 1.75rem;
    align-items: start;
}
@media (max-width: 960px) {
    .kt-grid--asymmetric { grid-template-columns: 1fr; }
}

.kt-hero {
    padding: clamp(1.5rem, 3.5vw, 2.6rem) clamp(1.25rem, 4vw, 3rem)
             clamp(1.75rem, 4vw, 3rem) clamp(1.25rem, 4vw, 3rem);
}
.kt-hero-grid {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: clamp(1.25rem, 3vw, 2.5rem);
    align-items: center;
}
.kt-hero-grid > :last-child { transform: none; }

.kt-prose {
    text-align: left;
    text-wrap: pretty;
    max-width: 58ch;
}
.kt-prose--narrow { max-width: 42ch; }

.kt-empty {
    text-align: left;
    padding: 1.75rem 1.5rem 1.6rem 1.65rem;
    border: 1px dashed var(--kt-hairline);
    border-radius: var(--kt-radius-card);
    background: var(--kt-fill-subtle);
}
.kt-empty__icon {
    width: 40px; height: 40px;
    border-radius: 11px;
    display: grid; place-items: center;
    margin-bottom: 0.85rem;
    background: var(--kt-fill-subtle);
    border: 1px solid var(--kt-hairline);
}
.kt-empty__title {
    font-family: var(--kt-font-head);
    font-weight: 700;
    font-size: 1.05rem;
    margin: 0 0 0.35rem;
    letter-spacing: -0.015em;
}
.kt-empty__body {
    color: var(--kt-muted);
    font-size: 0.9rem;
    line-height: 1.5;
    max-width: 36ch;
    margin: 0 0 1rem;
    text-align: left;
}
.kt-empty__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.kt-milestone {
    margin-right: clamp(0px, 6vw, 4.5rem);
    max-width: 920px;
    box-shadow: var(--kt-shadow-feather);
}

.kt-stagger > * {
    opacity: 0;
    transform: translateY(14px) scale(0.985);
    animation: kt-enter 520ms var(--kt-ease-enter) forwards;
    animation-delay: calc(var(--kt-stagger, 0) * 55ms + 40ms);
}
@keyframes kt-enter {
    to { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    .kt-stagger > * { opacity: 1; transform: none; animation: none; }
}

.kt-avatar {
    display: inline-grid;
    place-items: center;
    border-radius: 50%;
    overflow: hidden;
    flex: 0 0 auto;
    background: #1A2233;
    border: 1px solid var(--kt-hairline);
    box-shadow: inset 0 -6px 12px rgba(0,0,0,0.25);
}
.kt-avatar img, .kt-avatar svg { width: 100%; height: 100%; display: block; object-fit: cover; }
.kt-avatar--sm { width: 32px; height: 32px; }
.kt-avatar--md { width: 40px; height: 40px; }
.kt-avatar--lg { width: 56px; height: 56px; }
.kt-avatar--xl { width: 88px; height: 88px; border-radius: 22px; }
.kt-avatar--square { border-radius: 12px; }
.kt-avatar--xl.kt-avatar--square { border-radius: 14px; }

.kt-offset-actions { margin-top: 0.35rem; margin-left: 0.15rem; }
.kt-account-tile {
    text-align: left;
    padding: 0.85rem 1rem 1rem;
    border: 1px solid var(--kt-hairline);
    border-radius: var(--kt-radius-card);
    background: var(--kt-fill-subtle);
    display: flex;
    gap: 0.75rem;
    align-items: center;
}

.kt-powered-by, .kt-wordmark-foot, footer.kt-brand-foot { display: none !important; }

/* ============================================================
   Mobile floor — measured at 375x812, applies to every host
   ============================================================ */

/* MudBlazor's .mud-nav-link is padding 8px + line-height 1.75 on a 0.9rem base = 41px measured.
   The links are already display:flex/align-items:center, so the extra 3px distributes cleanly.

   THE ANCESTOR IS .mud-navmenu, ONE WORD. This rule shipped as `.mud-nav-menu .mud-nav-link` and
   matched NOTHING: MudNavMenu renders <nav class="mud-navmenu">, with no hyphen between "nav" and
   "menu". A tap-target floor that never applies is worse than none, because it reads as present in
   review — every host's drawer nav went on rendering the raw MudBlazor height while the one rule
   written to raise it was inert. Confirmed in the running Theme app at 375x812: with the hyphenated
   selector injected the links do not move and their min-height still computes `auto`; with this
   selector every link in the drawer takes the floor. NavTapTargetTests reads the ancestor class off
   the host's OWN SERVED MARKUP rather than off this comment, so a selector that names a class
   MudBlazor does not render fails there instead of shipping silently again.

   Two SIBLING rules in this sheet carry the same typo and are equally dead — the nav pill shape
   (border-radius/margin) and `.mud-drawer .mud-nav-menu` padding. They are NOT corrected here:
   waking them changes how every host's navigation looks on a desktop too, which is a visual change
   nobody asked for in a mobile pass. They are reported for Kobalt to rule on. */
.mud-navmenu .mud-nav-link {
    min-height: 44px;
}

/* ============================================================
   Additive primitives (opt-in) — skeleton, segmented control,
   activity timeline. Nothing in the estate renders these until it
   adds the classes, so no dependent app shifts. Frozen hexes/fonts
   untouched; roles read from the existing tokens.
   ============================================================ */

/* ---- Skeleton loading -------------------------------------
   The canonical shared placeholder: a quiet gradient that sweeps
   left→right while data resolves. Deliberately sets ONLY the four
   properties a host override needs (KMS ships its own .kt-skeleton
   loaded after this and still wins cleanly — same technique, own
   keyframes) and adds NO pseudo-element, so it can never leak a
   second animation onto a host that has redefined the class. Shape
   is supplied by the --* helpers or an inline height/width.
   Motion is frozen by the global prefers-reduced-motion block. */
.kt-skeleton {
    border-radius: var(--kt-radius);
    background: linear-gradient(90deg,
        var(--kt-fill-subtle) 0%,
        rgba(255, 255, 255, 0.06) 50%,
        var(--kt-fill-subtle) 100%);
    background-size: 200% 100%;
    animation: kt-skeleton-sweep 1.3s ease-in-out infinite;
}
.kt-mode-light .kt-skeleton {
    background: linear-gradient(90deg,
        var(--kt-fill-subtle) 0%,
        rgba(11, 15, 23, 0.05) 50%,
        var(--kt-fill-subtle) 100%);
    background-size: 200% 100%;
}
@keyframes kt-skeleton-sweep {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
/* Shape helpers — a block, a title bar, text lines (last line runs
   short so a stack reads as a paragraph), and a circle for avatars. */
.kt-skeleton--block  { height: 96px; border-radius: var(--kt-radius-card); }
.kt-skeleton--title  { height: 1.15rem; width: 45%; margin-bottom: 0.75rem; border-radius: 7px; }
.kt-skeleton--text   { height: 0.72rem; margin: 0.4rem 0; border-radius: 6px; }
.kt-skeleton--text-short { width: 60%; }
.kt-skeleton--circle { border-radius: 50%; }

/* ---- Segmented control ------------------------------------
   A compact inline switch (view / range / mode). Pure surface: the
   host wires the active class or aria-selected and the click. Each
   option is a real <button> so keyboard + focus work for free. */
.kt-segmented {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    border-radius: var(--kt-radius-pill);
    border: 1px solid var(--kt-hairline);
    background: var(--kt-fill-subtle);
}
.kt-segmented__option {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--kt-muted);
    font-family: var(--kt-font-body);
    font-weight: 600;
    font-size: 0.78rem;
    line-height: 1;
    padding: 7px 14px;
    border-radius: var(--kt-radius-pill);
    cursor: pointer;
    white-space: nowrap;
    transition: color .15s ease, background .15s ease;
}
.kt-segmented__option:hover { color: var(--kt-text); }
.kt-segmented__option--active,
.kt-segmented__option[aria-selected="true"] {
    background: var(--kt-cobalt-soft);
    color: #C7D4FF;
    box-shadow: inset 0 0 0 1px rgba(79, 123, 255, 0.28);
}
.kt-mode-light .kt-segmented__option--active,
:root[data-theme="light"] .kt-segmented__option--active,
.kt-mode-light .kt-segmented__option[aria-selected="true"],
:root[data-theme="light"] .kt-segmented__option[aria-selected="true"] { color: #2A46B8; }
/* Borderless control — takes the offset ring, not a border of its own. */
.kt-segmented__option:focus-visible {
    outline: 2px solid var(--kt-cobalt);
    outline-offset: 2px;
}

/* ---- Activity timeline ------------------------------------
   A vertical feed: a connector line threads node dots, each node
   coloured per role via --accent. --kt-timeline-bg is the ground the
   dot ring punches (defaults to the card surface a timeline usually
   sits in; set it to --kt-canvas when a timeline sits on bare page). */
.kt-timeline {
    --kt-timeline-bg: var(--kt-surface);
    margin: 0;
    padding: 0;
    list-style: none;
}
.kt-timeline__item {
    position: relative;
    padding: 0 0 1.15rem 1.65rem;
}
.kt-timeline__item:last-child { padding-bottom: 0; }
/* Connector — runs from this dot's centre down to the next. */
.kt-timeline__item::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 6px;
    bottom: -6px;
    width: 2px;
    background: var(--kt-hairline);
}
.kt-timeline__item:last-child::before { display: none; }
.kt-timeline__dot {
    position: absolute;
    left: 0;
    top: 4px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--accent, var(--kt-cobalt));
    box-shadow: 0 0 0 3px var(--kt-timeline-bg);
}
.kt-timeline__dot--hollow {
    background: var(--kt-timeline-bg);
    border: 2px solid var(--accent, var(--kt-muted));
}
.kt-timeline__time {
    font-size: 0.7rem;
    color: var(--kt-muted);
    font-variant-numeric: tabular-nums;
}
.kt-timeline__title {
    font-weight: 600;
    font-size: 0.88rem;
    color: var(--kt-text);
    line-height: 1.3;
    margin: 1px 0 2px;
}
.kt-timeline__body {
    font-size: 0.82rem;
    color: var(--kt-muted);
    line-height: 1.45;
}

/* ============================================================
   LIGHT-GROUND ACCENT INK
   The light-mode counterpart of every accent this sheet paints as TEXT.

   THE DEFECT, measured rather than argued. Each accent here has one ink, chosen for the dark
   ground, and no counterpart for the light one — so the very same rule renders 6-8:1 in dark mode
   and 1.25-1.72:1 in light. Vault's contrast instrument names the four literals this sheet ships
   (#A9BEFF, #F5C451, #FF8A98, #5AE7C4) and says outright that only VAULT'S OWN override keeps them
   off its findings list. That is the shape of the bug: one product measured it, one product fixed
   it locally, and every other host went on rendering the unreadable original. Fixing it in the
   theme is the difference between one sheet and the whole estate.

   THESE ARE NOT NEW ACCENTS. Each accent keeps its single job — cobalt action, gold value, teal
   live, red stop — and only its lightness moves, because a colour legible on #0B0F17 cannot also be
   legible on #FFFFFF. #2A46B8 is not new either: it is already this sheet's light-mode cobalt ink
   on the active nav link and the active segmented option, so this reuses the house ink rather than
   inventing a second one. The other three are their frozen accent mixed 45% with black, which is
   the shallowest darkening that clears 4.5:1 on ALL THREE light grounds (canvas, surface and
   surface-2); at 55% the gold measures 4.41:1 on surface-2 and fails. LightModeInkContrastTests
   derives all four from KobaltTheme and measures them against the palette KobaltTheme declares, so
   these numbers cannot rot into a comment that used to be true.

   BOTH PREFIXES, EVERY TIME. Hosts disagree about where light mode is announced: MainLayout puts
   .kt-mode-light on MudLayout, the boot script puts data-theme on <html>, and some hosts do only
   one of the two. A rule carrying one prefix is silently dead on half the estate.

   Dark mode is untouched here by construction — every selector below is mode-scoped, so nothing in
   this block can reach a dark page.
   ============================================================ */

/* Cobalt — action, links, selection. */
.kt-mode-light .kt-section-label,
:root[data-theme="light"] .kt-section-label,
.kt-mode-light a.kt-link,
:root[data-theme="light"] a.kt-link,
.kt-mode-light .mud-link,
:root[data-theme="light"] .mud-link,
.kt-mode-light .mud-chip-outlined.mud-chip-color-primary,
:root[data-theme="light"] .mud-chip-outlined.mud-chip-color-primary,
.kt-mode-light .kt-pill--cobalt,
:root[data-theme="light"] .kt-pill--cobalt,
.kt-mode-light .kt-ticket,
:root[data-theme="light"] .kt-ticket,
.kt-mode-light .kt-daychip--active .kt-daychip-num,
:root[data-theme="light"] .kt-daychip--active .kt-daychip-num {
    color: #2A46B8;
}
/* .kt-text-cobalt carries !important in its own declaration, so the light ink has to as well or the
   utility keeps the dark tint on a white page. */
.kt-mode-light .kt-text-cobalt,
:root[data-theme="light"] .kt-text-cobalt {
    color: #2A46B8 !important;
}
/* Hover moves toward the frozen cobalt-deep. Lighter than the resting ink, which mirrors what dark
   mode does (tint to bright) rather than inverting the gesture, and still clears the floor: 4.75:1
   on white, the worst of the three light grounds. */
.kt-mode-light a.kt-link:hover,
:root[data-theme="light"] a.kt-link:hover,
.kt-mode-light .mud-link:hover,
:root[data-theme="light"] .mud-link:hover {
    color: var(--kt-cobalt-deep);
}

/* Gold — value, milestones, the lead position. */
.kt-mode-light .kt-pill--featured,
:root[data-theme="light"] .kt-pill--featured,
.kt-mode-light .kt-railbadge,
:root[data-theme="light"] .kt-railbadge,
.kt-mode-light .kt-rank--lead .kt-rank__pos,
:root[data-theme="light"] .kt-rank--lead .kt-rank__pos,
.kt-mode-light .kt-milestone-icon,
:root[data-theme="light"] .kt-milestone-icon {
    color: #6E5824;
}

/* Teal — live, and the upward delta. */
.kt-mode-light .kt-pill--live,
:root[data-theme="light"] .kt-pill--live,
.kt-mode-light .kt-up,
:root[data-theme="light"] .kt-up {
    color: #0F5F4B;
}

/* Red — ending, overdue, the downward delta, and the message under a rejected field.
   .validation-message is Blazor's own class, not a house one, which is exactly why it was missed:
   it is the only ink in this sheet that no .kt- selector names. It measured 2.64:1 on the light
   surface — the sentence telling a user what they got wrong was the least readable text on the
   page. */
.kt-mode-light .kt-pill--ending,
:root[data-theme="light"] .kt-pill--ending,
.kt-mode-light .kt-railbadge--red,
:root[data-theme="light"] .kt-railbadge--red,
.kt-mode-light .kt-down,
:root[data-theme="light"] .kt-down,
.kt-mode-light .validation-message,
:root[data-theme="light"] .validation-message {
    color: #732933;
}
