/* ============================================================================
   KOPPEL — DESIGN SYSTEM STYLESHEET
   ============================================================================

   This file is the single source of truth for Koppel's look. It has two halves:

     1. TOKENS   — CSS custom properties (colors, spacing, radii, type, motion).
                   Change a value here and it propagates everywhere.
     2. CLASSES  — the `koppel-*` classes the pages use. Every class is grouped
                   and commented so new pages can reuse them instead of
                   inventing one-off styles.

   HOW TO EXTEND THIS (read before adding CSS):

     - Never hard-code a color, radius, or spacing value in a page. Use a token.
     - Gradients are tokens too — see the Gradients block below. Reach for one of
       those before writing a `linear-gradient(...)` by hand, and layer it as
       `background-image` over a flat `background-color` so the element still
       reads if the image is ever dropped.
     - Never write inline `style="..."` in a .razor file for anything reusable.
       Add a `koppel-*` class here instead.
     - Prefer MudBlazor's own utility classes (`pa-4`, `d-flex`, `gap-2`, `mt-6`,
       …) for one-off layout nudges. Only add a class here when it's a *pattern*
       that repeats or a *design decision* worth naming.
     - Tokens are mirrored from Theme/KoppelTheme.cs. If you change the accent
       or a status color, change it in BOTH places — KoppelTheme.cs drives
       MudBlazor's components, this file drives Koppel's own chrome.

   DESIGN DIRECTION: modern SaaS dashboard. Flat surfaces over heavy elevation,
   one confident accent, generous rounding, hairline borders doing the work
   shadows normally do, and color reserved strictly for state — never decoration.

   Gradients are the exception to "flat", and they earn it by staying quiet:
   panels get a few percent of vertical ramp to suggest a light source, the
   accent ramp marks the things you can act on (primary buttons, the brand mark,
   the active nav pill), and the two hero surfaces get an atmospheric wash. What
   they never do is carry meaning — a status is still a flat, nameable color.

   CONTRAST IS A CONSTRAINT, NOT A DIAL. Because borders replace shadows here,
   they have to be dark enough to read as edges, and because the palette is
   quiet, the text has to carry. The rule for anything added below: text clears
   4.5:1 on the surface it sits on, borders and dividers clear ~3:1, and the
   surface levels stay far enough apart to distinguish side by side. If a screen
   feels busy, fix it with spacing, size, or weight — never by fading things out.
   ============================================================================ */


/* ============================================================================
   1. TOKENS
   ============================================================================ */

:root {
    /* --- Accent -------------------------------------------------------------
       One accent, used for primary actions, active nav, and focus rings.
       Nothing else in the UI is indigo — that's what makes it read as "the
       important thing" without needing to shout. */
    --k-accent:            #4338CA;
    --k-accent-hover:      #3730A3;
    --k-accent-active:     #312E81;
    --k-accent-subtle:     #E0E7FF;  /* tinted backgrounds: active nav, hover states */
    --k-accent-border:     #A5B4FC;
    --k-accent-contrast:   #FFFFFF;  /* text on top of --k-accent */

    /* The two extra hues exist only to give gradients somewhere to travel to —
       `bright` is the light end of the indigo ramp, `alt` the violet the brand
       gradient leans into. Never use either as a flat fill or as text: they are
       picked to look good mid-ramp, not to pass contrast on their own. */
    --k-accent-bright:     #4F46E5;
    --k-accent-alt:        #6D28D9;

    /* --- Surfaces -----------------------------------------------------------
       Three levels only. `page` is the app background, `surface` is anything
       raised onto it (cards, tables, drawer, appbar), `sunken` is for insets
       like table headers and code blocks. Resist adding a fourth.

       The steps between levels are deliberately wide enough to see: a card must
       read as a distinct object against the page without needing a shadow, and
       a table header must read as a separate band from the rows under it. */
    --k-page:              #F1F1F4;
    --k-surface:           #FFFFFF;
    --k-sunken:            #E4E4E7;
    --k-surface-hover:     #EDEDEF;

    /* The far end of each surface's gradient — a hair off the flat value, never
       far enough to read as a second surface level. */
    --k-surface-2:         #F6F6F8;
    --k-sunken-2:          #EDEDF0;

    /* --- Text ---------------------------------------------------------------
       Four weights of emphasis. `primary` for content, `secondary` for
       supporting copy, `muted` for metadata, `disabled` for inactive.

       Every one of these clears WCAG AA (4.5:1) on both --k-surface and
       --k-sunken, `muted` included — it carries the small uppercase labels on
       table headers and stat tiles, which are the hardest text in the app to
       read. Don't lighten these to "soften" a screen; use size or weight. */
    --k-text:              #09090B;
    --k-text-secondary:    #3F3F46;
    --k-text-muted:        #52525B;
    --k-text-disabled:     #71717A;

    /* --- Lines --------------------------------------------------------------
       Hairline borders are load-bearing in this design — they replace the
       shadows a Material-default app would use. Which means they have to be
       genuinely visible: at 1px, anything lighter than these disappears on a
       glossy screen and the layout loses its structure. `line` is the default,
       `strong` for emphasis, `subtle` for internal dividers inside a card.

       `strong` draws input borders and other interactive edges, so it clears
       WCAG's 3:1 non-text minimum against both the surface and the page — it is
       the one line in the set that a user has to be able to aim at. */
    --k-line:              #D4D4D8;
    --k-line-strong:       #8A8A93;
    --k-line-subtle:       #E4E4E7;

    /* --- Status -------------------------------------------------------------
       Mirrors ContractStatusColors / AssetStatusColors in Services/StatusColors.cs.
       Each status gets a foreground (text/icon) and a subtle (background) pair
       so chips and banners stay legible in both light and dark. The pairs are
       chosen together: each foreground clears 4.5:1 on its own subtle *and* on
       white, so the same token works as chip text, banner text, and a bare
       icon. */
    /* Success comes in two greens on purpose, and they are not interchangeable.
       `success` is the lighter green used as a *foreground* — money in the black, the OK flag on
       an import, the revenue figure. It is the lightest green that still clears 4.5:1 as text on
       white, so it cannot go further without becoming unreadable.
       `success-fill` is the lighter still green used as a *background*, on chips and dots, where
       a near-black label sits on top of it instead of white. That is the one people see as "the
       green", and it mirrors Success in KoppelTheme.cs. */
    --k-success:           #15803D;
    --k-success-fill:      #22C55E;
    --k-success-subtle:    #DCFCE7;
    --k-info:              #1D4ED8;
    --k-info-subtle:       #BFDBFE;
    --k-warning:           #92400E;
    --k-warning-subtle:    #FDE68A;
    --k-error:             #991B1B;
    --k-error-subtle:      #FECACA;
    --k-neutral:           #3F3F46;
    --k-neutral-subtle:    #E4E4E7;

    /* --- Gradients ----------------------------------------------------------
       Gradients are tokens too, for the same reason colors are: one ramp used
       in twelve places reads as a house style, twelve hand-rolled ramps read as
       a mess. Compose them from the color tokens above so a palette change
       carries through.

       Two families, and they behave differently:

         *-accent / *-brand   carry text or a mark on top. Both ends of these
                              ramps clear contrast on their own, so a label
                              stays legible wherever it lands on the sweep.
         *-surface / *-sunken / *-wash
                              are atmosphere. They shift by a few percent at
                              most — enough to give a panel a direction of
                              light, never enough to change what passes on top.

       If you add one: keep the sweep inside a single hue family, keep the angle
       at 135deg for objects and 180deg for bands, and check the *dark* block —
       the ramps there run the opposite direction (accents get lighter, not
       darker) so a shared definition would invert. */
    --k-gradient-accent:   linear-gradient(135deg, var(--k-accent-bright) 0%, var(--k-accent) 55%, var(--k-accent-hover) 100%);
    --k-gradient-accent-press: linear-gradient(135deg, var(--k-accent) 0%, var(--k-accent-active) 100%);
    --k-gradient-brand:    linear-gradient(135deg, var(--k-accent-bright) 0%, var(--k-accent-alt) 100%);
    --k-gradient-surface:  linear-gradient(180deg, var(--k-surface) 0%, var(--k-surface-2) 100%);
    --k-gradient-raised:   linear-gradient(150deg, var(--k-surface) 0%, var(--k-surface-2) 100%);
    --k-gradient-sunken:   linear-gradient(180deg, var(--k-sunken-2) 0%, var(--k-sunken) 100%);
    --k-gradient-wash:     linear-gradient(120deg, var(--k-accent-subtle) 0%, transparent 65%);
    --k-gradient-page:     radial-gradient(ellipse 130% 70% at 50% -25%, var(--k-accent-subtle) 0%, transparent 60%);
    --k-gradient-heading:  linear-gradient(115deg, var(--k-text) 10%, var(--k-accent) 90%);

    /* Sheen for anything filled with a status or accent colour that carries a label — chips,
       filled alerts. It is a *direction*, not a decoration: in light mode those labels are white
       on a dark fill, so the sheen darkens toward the bottom and contrast can only improve. The
       dark block flips it to lighten, because there the labels are near-black on a light fill.
       Applying a fixed white gloss to both would take a light-mode chip from 7:1 to about 4:1 at
       its brightest corner, which is why this token exists at all. */
    --k-sheen: linear-gradient(160deg, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, 0.24) 100%);

    /* --- Spacing ------------------------------------------------------------
       A 4px scale. Use these rather than arbitrary px values so vertical rhythm
       stays consistent across pages built by different people (or agents). */
    --k-space-1:  0.25rem;   /*  4px */
    --k-space-2:  0.5rem;    /*  8px */
    --k-space-3:  0.75rem;   /* 12px */
    --k-space-4:  1rem;      /* 16px */
    --k-space-5:  1.5rem;    /* 24px */
    --k-space-6:  2rem;      /* 32px */
    --k-space-7:  3rem;      /* 48px */
    --k-space-8:  4rem;      /* 64px */

    /* --- Radius -------------------------------------------------------------
       `md` (10px) is the default and matches LayoutProperties.DefaultBorderRadius
       in KoppelTheme.cs. `lg` for large cards, `full` for pills/avatars. */
    --k-radius-sm:   6px;
    --k-radius-md:   10px;
    --k-radius-lg:   14px;
    --k-radius-full: 999px;

    /* --- Elevation ----------------------------------------------------------
       Deliberately minimal. Most "raised" things get a border, not a shadow.
       Shadows are reserved for things that genuinely float: dialogs, menus,
       and hover-lifted cards. */
    --k-shadow-sm:  0 1px 2px rgba(9, 9, 11, 0.08);
    --k-shadow-md:  0 4px 12px rgba(9, 9, 11, 0.12);
    --k-shadow-lg:  0 12px 32px rgba(9, 9, 11, 0.20);

    /* --- Type ---------------------------------------------------------------
       Inter throughout, loaded in Components/App.razor. Tabular numerals matter
       for a money app — prices and IDs must align in columns. */
    --k-font:       'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --k-font-mono:  'SFMono-Regular', ui-monospace, 'Cascadia Mono', Menlo, monospace;
    --k-text-xs:    0.75rem;
    --k-text-sm:    0.8125rem;
    --k-text-base:  0.875rem;
    --k-text-md:    1rem;
    --k-text-lg:    1.125rem;
    --k-text-xl:    1.5rem;
    --k-text-2xl:   2rem;

    /* --- Motion -------------------------------------------------------------
       Fast and unobtrusive. Anything over 200ms starts to feel sluggish in a
       data-entry app people use all day. */
    --k-ease:       cubic-bezier(0.4, 0, 0.2, 1);
    --k-fast:       120ms;
    --k-normal:     180ms;

    /* --- Layout -------------------------------------------------------------
       Mirrors LayoutProperties in KoppelTheme.cs. */
    --k-appbar-height:  64px;
    --k-drawer-width:   248px;
    --k-content-max:    1400px;
}

/* --- Dark palette ----------------------------------------------------------
   Applied by MudBlazor when MudThemeProvider's IsDarkMode is true. Dark is a
   first-class palette here, not an inversion: surfaces get *lighter* as they
   rise (rather than darker), and the accent shifts up a step so it stays
   readable on near-black. Keep it that way when adding tokens.

   Dark is where low contrast bites hardest — a near-black stack collapses into
   one flat sheet if the steps are small. So the surface levels are spaced wider
   apart here than a "subtle" dark theme would space them, the lines are two
   steps lighter than the surface they sit on, and every accent/status hue is
   pulled up into the 400 range so it reads as color rather than as a smudge.
   The flip side: those hues are now too light to carry white text, so anything
   *filled* with one takes near-black text instead — see --k-accent-contrast
   and the *ContrastText entries in KoppelTheme.cs. */
.mud-theme-dark {
    --k-accent:            #818CF8;
    --k-accent-hover:      #A5B4FC;
    --k-accent-active:     #C7D2FE;
    --k-accent-subtle:     rgba(129, 140, 248, 0.22);
    --k-accent-border:     rgba(129, 140, 248, 0.50);
    --k-accent-contrast:   #0B0B12;
    --k-accent-bright:     #A5B4FC;
    --k-accent-alt:        #A78BFA;

    --k-page:              #08080B;
    --k-surface:           #17171E;
    --k-sunken:            #23232C;
    --k-surface-hover:     #26262F;
    --k-surface-2:         #1E1E28;
    --k-sunken-2:          #2A2A34;

    --k-text:              #FAFAFA;
    --k-text-secondary:    #D4D4D8;
    --k-text-muted:        #A1A1AA;
    --k-text-disabled:     #71717A;

    --k-line:              #3F3F46;
    --k-line-strong:       #71717A;
    --k-line-subtle:       #2A2A33;

    --k-success:           #4ADE80;
    --k-success-fill:      #4ADE80;
    --k-success-subtle:    rgba(74, 222, 128, 0.22);
    --k-info:              #60A5FA;
    --k-info-subtle:       rgba(96, 165, 250, 0.22);
    --k-warning:           #FBBF24;
    --k-warning-subtle:    rgba(251, 191, 36, 0.22);
    --k-error:             #F87171;
    --k-error-subtle:      rgba(248, 113, 113, 0.22);
    --k-neutral:           #D4D4D8;
    --k-neutral-subtle:    rgba(212, 212, 216, 0.18);

    /* Gradients, re-pointed for dark. The surface ramps stay as written above —
       they resolve through the redefined surface tokens — but every ramp that
       carries text is respelled, because the accent ramp runs light-to-dark in
       light mode and light-to-*less*-light here. Left shared, a filled button
       would sweep down to an indigo too dark for the near-black label on it. */
    --k-gradient-accent:   linear-gradient(135deg, var(--k-accent-bright) 0%, var(--k-accent) 100%);
    --k-gradient-brand:    linear-gradient(135deg, var(--k-accent-bright) 0%, var(--k-accent-alt) 100%);
    --k-gradient-wash:     linear-gradient(120deg, var(--k-accent-subtle) 0%, transparent 70%);
    --k-gradient-page:     radial-gradient(ellipse 130% 70% at 50% -25%, rgba(99, 102, 241, 0.18) 0%, transparent 65%);
    --k-gradient-heading:  linear-gradient(115deg, var(--k-text) 10%, var(--k-accent-bright) 90%);

    /* Flipped: dark-mode fills are light and their labels near-black, so the sheen lightens. */
    --k-sheen: linear-gradient(160deg, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.20) 100%);

    --k-shadow-sm:  0 1px 2px rgba(0, 0, 0, 0.6);
    --k-shadow-md:  0 6px 16px rgba(0, 0, 0, 0.7);
    --k-shadow-lg:  0 16px 40px rgba(0, 0, 0, 0.8);
}


/* ============================================================================
   2. BASE
   ============================================================================ */

/* The page carries one very soft accent glow behind the appbar, fixed so it
   stays put while content scrolls under it. It is the quietest gradient in the
   app on purpose — you should notice it only if it's removed. */
html, body {
    font-family: var(--k-font);
    background-color: var(--k-page);
    background-image: var(--k-gradient-page);
    background-repeat: no-repeat;
    background-attachment: fixed;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Money and IDs line up in columns. Non-negotiable in a contracts app. */
.koppel-numeric,
.koppel-table td[data-label="Selling"],
.koppel-table td[data-label="Cost"],
.koppel-table td[data-label="Profit"],
.koppel-table td[data-label="Commission"] {
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum';
}

.koppel-mono {
    font-family: var(--k-font-mono);
    font-size: var(--k-text-sm);
    letter-spacing: -0.01em;
}

/* One focus treatment for the whole app, so keyboard users get a consistent
   affordance no matter which component they're on. */
*:focus-visible {
    outline: 2px solid var(--k-accent);
    outline-offset: 2px;
    border-radius: var(--k-radius-sm);
}


/* ============================================================================
   3. APP CHROME — appbar, drawer, nav
   ============================================================================ */

.koppel-appbar {
    background-color: var(--k-surface) !important;
    background-image: var(--k-gradient-surface);
    border-bottom: 1px solid var(--k-line);
    backdrop-filter: saturate(180%) blur(8px);
}

.koppel-brand {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
    cursor: pointer;
    user-select: none;
    margin-right: var(--k-space-5);
}

/* The "K" tile. Stands in for a real logo — swap the mark, keep the box. The
   indigo→violet sweep is the app's signature: it appears here, on the login
   card's top edge, and nowhere it would compete with content. */
.koppel-brand-mark {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: var(--k-radius-sm);
    background-color: var(--k-accent);
    background-image: var(--k-gradient-brand);
    color: var(--k-accent-contrast);
    font-weight: 800;
    font-size: var(--k-text-md);
    letter-spacing: -0.02em;
    box-shadow: var(--k-shadow-sm);
}

.koppel-brand-mark-lg {
    width: 52px;
    height: 52px;
    border-radius: var(--k-radius-md);
    font-size: var(--k-text-xl);
}

.koppel-brand-name {
    font-weight: 700;
    font-size: var(--k-text-lg);
    letter-spacing: -0.02em;
    color: var(--k-text);
}

.koppel-appbar-search {
    flex: 1 1 auto;
    max-width: 420px;
}

.koppel-quicksearch .mud-input-outlined-border {
    border-radius: var(--k-radius-full);
}

.koppel-user-chip {
    font-weight: 600;
    color: var(--k-text-secondary) !important;
}

.koppel-user-role {
    color: var(--k-text-muted);
    font-weight: 500;
}

/* Two layers: an accent glow anchored to the top of the rail (so the drawer
   reads as continuous with the appbar's own wash) over the standard surface
   ramp. Both stay put when a long nav list scrolls — that's the default
   `background-attachment: scroll`, which pins the image to the element's box
   rather than to its content. */
.koppel-drawer {
    background-color: var(--k-surface) !important;
    background-image:
        radial-gradient(ellipse 140% 30% at 0% 0%, var(--k-accent-subtle) 0%, transparent 70%),
        var(--k-gradient-surface);
    border-right: 1px solid var(--k-line);

    /* MudBlazor sets `overflow-y: auto` on .mud-drawer. Per CSS, an `auto` on one
       axis promotes the other axis from `visible` to `auto` — so the rail sprouts
       a horizontal scrollbar the moment anything inside is a pixel too wide (a
       long username in the footer is the usual culprit). The rail scrolls
       vertically and only vertically; say so explicitly. The rules below then
       remove the overflow itself, so nothing is silently cut off. */
    overflow-x: hidden;
}

.koppel-nav {
    padding: var(--k-space-3) var(--k-space-2);
    min-width: 0;
}

/* A nav label longer than the rail ellipsizes instead of widening it. MudBlazor
   gives .mud-nav-link-text `width: 100%`, and a flex item's default
   `min-width: auto` refuses to shrink below its longest word — that floor is
   what pushes the rail wide, so it has to be lifted here too. */
.koppel-nav .mud-nav-link-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Nav links are pills, not full-bleed bars — softer, and the inset gutter makes
   the active state read as "selected" rather than "highlighted". */
.koppel-nav .mud-nav-link {
    border-radius: var(--k-radius-md);
    margin-bottom: 2px;
    font-weight: 500;
    font-size: var(--k-text-base);
    color: var(--k-text-secondary);
    transition: background-color var(--k-fast) var(--k-ease),
                color var(--k-fast) var(--k-ease);
}

.koppel-nav .mud-nav-link:hover {
    background-color: var(--k-surface-hover);
    color: var(--k-text);
}

/* The active pill fades out to the right rather than sitting as a flat block —
   it makes the selection feel anchored to the rail's left edge, which is where
   the eye tracks down the list. */
.koppel-nav .mud-nav-link.active {
    background-color: transparent;
    background-image: linear-gradient(90deg, var(--k-accent-subtle) 0%, var(--k-accent-subtle) 45%, transparent 100%);
    color: var(--k-accent);
    font-weight: 600;
    box-shadow: inset 3px 0 0 0 var(--k-accent);
}

.koppel-nav .mud-nav-link.active .mud-icon-root {
    color: var(--k-accent);
}

.koppel-nav-section {
    display: block;
    padding: var(--k-space-4) var(--k-space-4) var(--k-space-1);
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.koppel-nav-divider {
    margin: var(--k-space-3) var(--k-space-3) 0;
    border-color: var(--k-line);
}

.koppel-drawer-footer {
    margin-top: auto;
    padding: var(--k-space-4);
    border-top: 1px solid var(--k-line);
    min-width: 0;
}

/* A username is one unbreakable token, and a long one is the thing most likely
   to push the rail wider than itself. Let it break mid-word. */
.koppel-drawer-footer-text {
    color: var(--k-text-muted);
    overflow-wrap: anywhere;
}

/* The appbar is fixed, so content must be pushed down past it. MudMainContent sets its own
   padding-top for this — writing the `padding` shorthand here would silently clobber that
   and slide the first heading of every page under the appbar. So the offset is spelled out
   explicitly instead: appbar height + the usual page gutter. */
/* Deliberately NOT capped with `max-width: var(--k-content-max)`. MudMainContent is offset
   from the left by the drawer, so a fixed max-width here adds to that offset and pushes the
   page wider than the viewport (drawer + 1400px > screen), which pushes the top-right page
   action off-screen. Full-bleed is also the right call for a contracts app: the tables are
   wide and want the room. Pages that need a narrow measure cap themselves — see
   .koppel-search-hero and .koppel-login-shell-inner. */
.koppel-main {
    padding: calc(var(--k-appbar-height) + var(--k-space-6)) var(--k-space-6) var(--k-space-6);
    min-width: 0;
}

@media (max-width: 960px) {
    .koppel-main {
        padding: calc(var(--k-appbar-height) + var(--k-space-4)) var(--k-space-4) var(--k-space-4);
    }
    .koppel-appbar-search { display: none; }
}


/* ============================================================================
   4. PAGE PATTERNS — header, cards, tables
   ============================================================================ */

/* Standard page header: title + subtitle on the left, primary action on the
   right. Every list page should open with one of these. */
.koppel-page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--k-space-4);
    margin-bottom: var(--k-space-5);
    flex-wrap: wrap;
}

.koppel-page-subtitle {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-1);
}

/* The default container for content. Border, not shadow — that's the house
   style. Use this for tables, forms, and grouped detail. */
.koppel-card,
.koppel-table-card,
.koppel-results-card {
    background-color: var(--k-surface);
    background-image: var(--k-gradient-surface);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-lg);
    overflow: hidden;
}

.koppel-card {
    padding: var(--k-space-5);
}

/* Contract tables are wide (9+ columns) and the cells are set to `nowrap` so rows stay one
   line tall. Without an explicit scroll container the table just overflows its card and the
   rightmost columns — including Actions — become unreachable. Scroll horizontally instead. */
.koppel-table .mud-table-container {
    overflow-x: auto;
}

.koppel-table-filter {
    max-width: 320px;
}

.koppel-table-filter .mud-input-outlined-border {
    border-radius: var(--k-radius-full);
}

/* Table header: sunken, uppercase, small. Reads as a label row rather than
   competing with the data underneath. */
.koppel-table thead th {
    background-color: var(--k-sunken);
    background-image: var(--k-gradient-sunken);
    color: var(--k-text-muted);
    font-size: var(--k-text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    white-space: nowrap;
    border-bottom: 1px solid var(--k-line);
}

.koppel-table tbody td {
    font-size: var(--k-text-base);
    color: var(--k-text);
    border-bottom: 1px solid var(--k-line-subtle);
    white-space: nowrap;
}

/* The hovered row lights from the left, where the eye enters it, and fades out before the
   actions column so the buttons stay on a plain background. */
.koppel-table tbody tr:hover {
    background-color: var(--k-surface-hover);
    background-image: linear-gradient(90deg, var(--k-accent-subtle) 0%, transparent 45%);
}

/* Action buttons hug the right of their column, so the header has to follow them there. A
   left-aligned "Actions" over right-aligned buttons reads as a label for whichever column
   happens to sit to its left, and the wider the table the further the two drift apart.

   Scoped to the actions header rather than "the last header": on the clients and routers
   lists the actions column is admin-only, and for everyone else the last column is Status or
   Link, which belongs left-aligned like every other. */
.koppel-table thead th.koppel-actions-head {
    text-align: right;
}

/* Action buttons must never wrap onto a second line — it makes rows jump. */
.koppel-actions-cell {
    white-space: nowrap;
    text-align: right;
}

/* Status chips: solid enough to scan at a glance, quiet enough not to shout. Colors come from
   MudBlazor via Services/StatusColors.cs; this sets shape, and lays the house sheen over
   whatever hue arrived — one rule covering every status without naming any of them. */
.koppel-status-chip {
    font-weight: 600;
    font-size: var(--k-text-xs);
    letter-spacing: 0.01em;
    border-radius: var(--k-radius-full);
    background-image: var(--k-sheen);
}


/* ============================================================================
   5. SEARCH PAGE
   ============================================================================ */

.koppel-search-hero {
    max-width: 720px;
    margin: var(--k-space-7) auto var(--k-space-6);
    text-align: center;
}

/* Paired with .koppel-login-title — see the note there before adding a third. */
.koppel-search-title {
    font-weight: 700;
    letter-spacing: -0.03em;
    color: var(--k-text);
    background-image: var(--k-gradient-heading);
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.koppel-search-subtitle {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-2);
    margin-bottom: var(--k-space-5);
}

.koppel-search-box .mud-input-outlined-border {
    border-radius: var(--k-radius-full);
}

.koppel-search-box .mud-input-root {
    font-size: var(--k-text-md);
    padding-top: var(--k-space-2);
    padding-bottom: var(--k-space-2);
}

.koppel-quicklinks {
    max-width: 1000px;
    margin: 0 auto;
}

/* Quick-link cards lift on hover — one of the few places a shadow is used,
   because these are genuinely clickable objects rather than containers. */
.koppel-quicklink-card {
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-lg);
    padding: var(--k-space-5);
    height: 100%;
    cursor: pointer;
    transition: transform var(--k-normal) var(--k-ease),
                box-shadow var(--k-normal) var(--k-ease),
                border-color var(--k-normal) var(--k-ease);
}

/* Hover swaps the neutral ramp for the accent wash — the card doesn't just lift,
   it warms toward the color it navigates to. */
.koppel-quicklink-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--k-shadow-md);
    border-color: var(--k-accent-border);
    background-image: var(--k-gradient-wash), var(--k-gradient-raised);
}

.koppel-quicklink-icon {
    color: var(--k-accent);
    margin-bottom: var(--k-space-3);
}

.koppel-quicklink-title {
    font-weight: 600;
    color: var(--k-text);
}

.koppel-quicklink-desc {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-1);
}

.koppel-results-heading {
    display: block;
    margin: var(--k-space-5) 0 var(--k-space-2);
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.koppel-result-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--k-space-4);
    padding: var(--k-space-3) var(--k-space-4);
    border-bottom: 1px solid var(--k-line-subtle);
    cursor: pointer;
    transition: background-color var(--k-fast) var(--k-ease);
}

.koppel-result-row:last-child { border-bottom: none; }
.koppel-result-row:hover      { background-color: var(--k-surface-hover); }

.koppel-result-main { min-width: 0; }

.koppel-result-sub {
    color: var(--k-text-secondary);
    margin-top: 2px;
}

.koppel-search-empty {
    max-width: 720px;
    margin: 0 auto;
}


/* ============================================================================
   6. LOGIN
   ============================================================================ */

/* The login screen is the one place the app is allowed to be atmospheric — it's
   a single card on an empty field, with nothing to compete with. Three washes:
   indigo overhead, violet low-left, and a warm-neutral base. */
.koppel-login-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: var(--k-space-5);
    background:
        radial-gradient(ellipse 90% 55% at 50% -10%, var(--k-accent-subtle), transparent 70%),
        radial-gradient(ellipse 70% 50% at 5% 105%, var(--k-accent-subtle), transparent 65%),
        linear-gradient(180deg, var(--k-page) 0%, var(--k-sunken) 100%);
}

.koppel-login-shell-inner {
    width: 100%;
    max-width: 460px;
}

.koppel-login-card {
    position: relative;
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-lg);
    padding: var(--k-space-6);
    text-align: center;
    box-shadow: var(--k-shadow-lg);
    overflow: hidden;
}

/* The brand sweep as a 3px cap on the card. Same ramp as the "K" tile below it,
   so the two read as one object. */
.koppel-login-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 3px;
    background-image: var(--k-gradient-brand);
}

.koppel-login-brand {
    display: flex;
    justify-content: center;
    margin-bottom: var(--k-space-4);
}

/* Gradient text, used on exactly two headings in the app (here and the search
   hero). The ramp runs from body text to accent — both ends clear 7:1, so every
   point along the sweep does too. Don't reach for this on smaller type: the
   fallback `color` is what a screenshot tool or an old browser will show. */
.koppel-login-title {
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--k-text);
    background-image: var(--k-gradient-heading);
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.koppel-login-subtitle {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-2);
    margin-bottom: var(--k-space-5);
}

.koppel-login-roles {
    display: flex;
    flex-direction: column;
    gap: var(--k-space-2);
    text-align: left;
}

.koppel-role-card {
    display: flex;
    align-items: center;
    gap: var(--k-space-3);
    padding: var(--k-space-3);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-md);
    cursor: pointer;
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
    transition: border-color var(--k-fast) var(--k-ease),
                background-color var(--k-fast) var(--k-ease);
}

.koppel-role-card:hover {
    border-color: var(--k-accent-border);
    background-color: var(--k-accent-subtle);
    background-image: var(--k-gradient-wash), var(--k-gradient-raised);
}

.koppel-role-avatar { flex: 0 0 auto; }
.koppel-role-info   { flex: 1 1 auto; min-width: 0; }

.koppel-role-name {
    font-weight: 600;
    color: var(--k-text);
}

.koppel-role-desc {
    color: var(--k-text-secondary);
    font-size: var(--k-text-sm);
}

.koppel-login-footnote {
    display: block;
    margin-top: var(--k-space-5);
    color: var(--k-text-muted);
    line-height: 1.6;
}

.koppel-login-footnote code {
    font-family: var(--k-font-mono);
    background-color: var(--k-sunken);
    padding: 1px 5px;
    border-radius: var(--k-radius-sm);
}


/* ============================================================================
   7. DIALOGS & HISTORY
   ============================================================================ */

.mud-dialog {
    border-radius: var(--k-radius-lg) !important;
    border: 1px solid var(--k-line);
    background-image: var(--k-gradient-raised);
}

/* The title band fades from the sunken tone into the dialog body, so the header
   separates without a hard color break. */
.mud-dialog-title {
    border-bottom: 1px solid var(--k-line);
    padding-bottom: var(--k-space-3);
    background-image: linear-gradient(180deg, var(--k-sunken-2) 0%, var(--k-surface) 100%);
}

.mud-dialog-actions {
    border-top: 1px solid var(--k-line);
    padding-top: var(--k-space-3);
    gap: var(--k-space-2);
}

.koppel-history-meta {
    color: var(--k-text-muted);
    display: block;
    margin-top: 2px;
}

.koppel-history-empty {
    color: var(--k-text-secondary);
    padding: var(--k-space-5) 0;
    text-align: center;
}


/* ============================================================================
   8. EMPTY / PLACEHOLDER STATES
   ============================================================================ */

.koppel-comingsoon {
    max-width: 560px;
    margin: var(--k-space-8) auto;
    text-align: center;
    padding: var(--k-space-6);
    border: 1px dashed var(--k-line-strong);
    border-radius: var(--k-radius-lg);
    background-color: var(--k-surface);
    background-image:
        radial-gradient(ellipse 80% 100% at 50% 0%, var(--k-accent-subtle), transparent 70%),
        var(--k-gradient-surface);
}

.koppel-comingsoon-icon {
    color: var(--k-accent);
    margin-bottom: var(--k-space-3);
}

.koppel-comingsoon-title {
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--k-text);
}

.koppel-comingsoon-body {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-2);
    line-height: 1.65;
}

.koppel-comingsoon-ref {
    display: block;
    margin-top: var(--k-space-4);
    color: var(--k-text-muted);
}


/* ============================================================================
   9. BLAZOR PLUMBING
   Framework-generated markup for validation and the error banner. Restyled to
   match the design system rather than left at the default template look.
   ============================================================================ */

/* 2px, not 1px: a single hairline in a status color is the one signal in the app
   that must survive a quick glance at a filled-in form. */
.valid.modified:not([type=checkbox]) { outline: 2px solid var(--k-success); }
.invalid                             { outline: 2px solid var(--k-error); }

.validation-message {
    color: var(--k-error);
    font-size: var(--k-text-xs);
}

.blazor-error-boundary {
    background-color: var(--k-error-subtle);
    border: 1px solid var(--k-error);
    border-radius: var(--k-radius-md);
    padding: var(--k-space-4);
    color: var(--k-error);
    font-size: var(--k-text-base);
}

.blazor-error-boundary::after {
    content: "An error has occurred.";
}


/* ============================================================================
   10. KOPPEL MODULES
   Patterns the full app needs beyond the starter template: the dashboard, the
   list-page filter bar, detail dialogs, the import preview, and the accounting
   metric row. Same rules as everything above â€” tokens only, no hard-coded
   colors, and a named class rather than an inline style.
   ============================================================================ */

/* --- Shared list-page furniture ------------------------------------------- */

/* Filter bar sits between the page header and the table card. Wraps on narrow
   screens rather than squeezing the controls. */
.koppel-filter-bar {
    display: flex;
    align-items: center;
    gap: var(--k-space-3);
    margin-bottom: var(--k-space-4);
    flex-wrap: wrap;
}

.koppel-filter-search { width: 280px; }
.koppel-filter-select { width: 180px; }
.koppel-filter-spacer { flex: 1 1 auto; }

.koppel-filter-search .mud-input-outlined-border,
.koppel-filter-select .mud-input-outlined-border {
    border-radius: var(--k-radius-full);
}

/* A 3px indeterminate bar pinned to the top of the table card while loading â€”
   less jumpy than swapping the whole table out for a spinner. */
.koppel-table-card { position: relative; }

.koppel-table-loading {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 2;
}

.koppel-empty {
    display: grid;
    place-items: center;
    gap: var(--k-space-2);
    padding: var(--k-space-7) var(--k-space-5);
    text-align: center;
}

.koppel-empty-icon { color: var(--k-text-disabled); }

.koppel-empty-text { color: var(--k-text-secondary); }

/* A deleted (archived) record reads as receded, not absent. */
.koppel-row-archived td { color: var(--k-text-disabled); }

.koppel-row-clickable { cursor: pointer; }

/* --- Dashboard ------------------------------------------------------------ */

/* The greeting band. An accent wash rather than a saturated gradient, so it
   sits in the same visual family as the login shell: indigo entering top-left,
   a violet counterweight bottom-right, over the standard surface ramp. */
.koppel-hero {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--k-space-5);
    flex-wrap: wrap;
    padding: var(--k-space-5) var(--k-space-6);
    margin-bottom: var(--k-space-6);
    border: 1px solid var(--k-accent-border);
    border-radius: var(--k-radius-lg);
    overflow: hidden;
    background-color: var(--k-surface);
    background-image:
        radial-gradient(ellipse 70% 130% at 0% 0%, var(--k-accent-subtle), transparent 70%),
        radial-gradient(ellipse 55% 110% at 100% 100%, var(--k-accent-subtle), transparent 65%),
        var(--k-gradient-surface);
}

/* Brand sweep along the top edge, matching the login card. */
.koppel-hero::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 3px;
    background-image: var(--k-gradient-brand);
}

.koppel-hero-greeting {
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--k-text);
}

.koppel-hero-date {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-1);
}

.koppel-hero-search {
    width: 100%;
    max-width: 340px;
}

.koppel-hero-search .mud-input-outlined-border {
    border-radius: var(--k-radius-full);
}

/* Stat tiles. Clickable, so they get the same hover lift as the quick links. */
.koppel-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--k-space-4);
    margin-bottom: var(--k-space-6);
}

.koppel-stat {
    display: flex;
    flex-direction: column;
    gap: var(--k-space-2);
    padding: var(--k-space-5);
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-lg);
    cursor: pointer;
    transition: transform var(--k-normal) var(--k-ease),
                box-shadow var(--k-normal) var(--k-ease),
                border-color var(--k-normal) var(--k-ease);
}

.koppel-stat:hover {
    transform: translateY(-2px);
    box-shadow: var(--k-shadow-md);
    border-color: var(--k-accent-border);
    background-image: var(--k-gradient-wash), var(--k-gradient-raised);
}

.koppel-stat-head {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
}

/* The icon tile carries the only color on the tile â€” one hue per module, so the
   three cards stay tellable apart at a glance. */
.koppel-stat-icon {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: var(--k-radius-sm);
    flex: 0 0 auto;
}

/* Each tile fades its own hue out toward the card underneath. The flat
   background-color stays as the floor: if the gradient is ever dropped, the tile
   still reads. */
.koppel-stat-icon-clients {
    background-color: var(--k-accent-subtle);
    background-image: linear-gradient(140deg, var(--k-accent-subtle) 0%, transparent 130%);
    color: var(--k-accent);
}

.koppel-stat-icon-routers {
    background-color: var(--k-success-subtle);
    background-image: linear-gradient(140deg, var(--k-success-subtle) 0%, transparent 130%);
    color: var(--k-success);
}

.koppel-stat-icon-packages {
    background-color: var(--k-warning-subtle);
    background-image: linear-gradient(140deg, var(--k-warning-subtle) 0%, transparent 130%);
    color: var(--k-warning);
}

.koppel-stat-label {
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.koppel-stat-value {
    font-weight: 700;
    font-size: var(--k-text-2xl);
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: var(--k-text);
    font-variant-numeric: tabular-nums;
}

.koppel-stat-sub { color: var(--k-text-secondary); }

.koppel-section-heading {
    display: block;
    margin: 0 0 var(--k-space-3);
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.koppel-status-groups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--k-space-4);
    margin-bottom: var(--k-space-6);
}

.koppel-status-group {
    background-color: var(--k-surface);
    background-image: var(--k-gradient-surface);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-lg);
    overflow: hidden;
}

/* The band dissolves left-to-right into the card below it, so the group's name sits on the
   solid end and its total floats free on the near-white end — a header that introduces the
   rows rather than a grey bar stuck on top of them. */
.koppel-status-group-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: var(--k-space-3) var(--k-space-4);
    background-color: var(--k-sunken);
    background-image: linear-gradient(105deg,
        var(--k-sunken) 0%,
        var(--k-sunken-2) 45%,
        var(--k-surface) 100%);
    border-bottom: 1px solid var(--k-line);
}

.koppel-status-group-name {
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.06em;
}

.koppel-status-group-total {
    font-weight: 700;
    color: var(--k-text);
    font-variant-numeric: tabular-nums;
}

.koppel-status-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--k-space-3);
    padding: var(--k-space-3) var(--k-space-4);
    border-bottom: 1px solid var(--k-line-subtle);
}

.koppel-status-row:last-child { border-bottom: none; }

.koppel-status-row-name {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
    color: var(--k-text-secondary);
}

/* Filled from the status hex that DashboardService returns â€” the one place a
   color legitimately comes from data rather than a token. */
.koppel-status-dot {
    width: 9px;
    height: 9px;
    border-radius: var(--k-radius-full);
    flex: 0 0 auto;
}

.koppel-status-row-count {
    font-weight: 600;
    color: var(--k-text);
    font-variant-numeric: tabular-nums;
}

.koppel-router-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--k-space-4);
    padding: var(--k-space-4);
}

/* Each tile is washed with the status it reports. Dashboard.razor hands the status hex in as
   --k-tile-accent, and the whole box is built out of it: a 3px rule on top, then a diagonal
   fade from a tint of that colour into the plain sunken grey. The colour lights the tile
   rather than sitting on its edge, which is what makes a row of them readable at a glance —
   a green box, a red box and a grey box, rather than three identical greys with different trim.

   The tint stays low (18% at its strongest, over a white base) so the label and count keep the
   contrast they have on any other sunken surface. Raise it and check the text, not just the
   look. The flat background-color underneath is the floor: if color-mix is ever unsupported the
   whole background-image is dropped, and the tile is simply grey again. */
.koppel-router-tile {
    padding: var(--k-space-4);
    border-radius: var(--k-radius-md);
    border-top: 3px solid var(--k-tile-accent, var(--k-line-strong));
    background-color: var(--k-sunken);
    background-image: linear-gradient(155deg,
        color-mix(in srgb, var(--k-tile-accent, var(--k-line-strong)) 18%, var(--k-surface)) 0%,
        color-mix(in srgb, var(--k-tile-accent, var(--k-line-strong)) 7%, var(--k-sunken)) 55%,
        var(--k-sunken) 100%);
}

.koppel-router-tile-head {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
    margin-bottom: var(--k-space-2);
}

.koppel-router-tile-name {
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.06em;
}

.koppel-router-tile-count {
    font-weight: 700;
    font-size: var(--k-text-xl);
    color: var(--k-text);
    font-variant-numeric: tabular-nums;
}

/* --- Accounting ----------------------------------------------------------- */

.koppel-metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--k-space-4);
    margin-bottom: var(--k-space-6);
}

.koppel-metric {
    position: relative;
    padding: var(--k-space-5);
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-lg);
    overflow: hidden;
}

/* A 3px cap in the metric's own hue, fading out to the right. The card carries
   no hue class of its own — the dot inside does — so the cap is selected off
   that child rather than adding a modifier to every card in Accounting.razor.
   Default is the accent; each :has() below overrides it. */
.koppel-metric::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 3px;
    background-image: linear-gradient(90deg, var(--k-accent) 0%, transparent 100%);
}

.koppel-metric:has(.koppel-metric-dot-revenue)::before    { background-image: linear-gradient(90deg, var(--k-success) 0%, transparent 100%); }
.koppel-metric:has(.koppel-metric-dot-cost)::before       { background-image: linear-gradient(90deg, var(--k-error) 0%, transparent 100%); }
.koppel-metric:has(.koppel-metric-dot-profit)::before     { background-image: linear-gradient(90deg, var(--k-accent) 0%, transparent 100%); }
.koppel-metric:has(.koppel-metric-dot-margin)::before     { background-image: linear-gradient(90deg, var(--k-info) 0%, transparent 100%); }
.koppel-metric:has(.koppel-metric-dot-commission)::before { background-image: linear-gradient(90deg, var(--k-warning) 0%, transparent 100%); }

.koppel-metric-head {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
    margin-bottom: var(--k-space-3);
}

.koppel-metric-dot {
    width: 8px;
    height: 8px;
    border-radius: var(--k-radius-full);
    flex: 0 0 auto;
}

.koppel-metric-dot-revenue    { background-color: var(--k-success-fill); }
.koppel-metric-dot-cost       { background-color: var(--k-error); }
.koppel-metric-dot-profit     { background-color: var(--k-accent); }
.koppel-metric-dot-margin     { background-color: var(--k-info); }
.koppel-metric-dot-commission { background-color: var(--k-warning); }

.koppel-metric-label {
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.koppel-metric-value {
    font-weight: 700;
    font-size: var(--k-text-xl);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.koppel-metric-value-revenue    { color: var(--k-success); }
.koppel-metric-value-cost       { color: var(--k-error); }
.koppel-metric-value-profit     { color: var(--k-accent); }
.koppel-metric-value-margin     { color: var(--k-info); }
.koppel-metric-value-commission { color: var(--k-warning); }

.koppel-metric-sub {
    color: var(--k-text-secondary);
    margin-top: var(--k-space-1);
}

/* --- Detail dialogs ------------------------------------------------------- */

/* Label-above-value pairs in a responsive grid. Read-only by design: these
   dialogs answer what a record is, and never edit it. */
.koppel-detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--k-space-4);
}

.koppel-detail-label {
    display: block;
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.06em;
    margin-bottom: 2px;
}

.koppel-detail-value {
    color: var(--k-text);
    word-break: break-word;
}

.koppel-detail-section {
    margin-top: var(--k-space-5);
    padding-top: var(--k-space-4);
    border-top: 1px solid var(--k-line);
}

.koppel-detail-section:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

.koppel-detail-section-title {
    display: block;
    color: var(--k-text-muted);
    font-weight: 600;
    letter-spacing: 0.08em;
    margin-bottom: var(--k-space-3);
}

.koppel-detail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--k-space-3);
    flex-wrap: wrap;
    margin-bottom: var(--k-space-4);
}

.koppel-detail-title {
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--k-text);
}

.koppel-detail-subtitle { color: var(--k-text-secondary); }

/* A router-and-contract card inside the client detail dialog â€” one per live
   contract, since a client can hold several. */
.koppel-detail-card {
    padding: var(--k-space-4);
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-md);
    margin-bottom: var(--k-space-3);
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
}

.koppel-detail-card:last-child { margin-bottom: 0; }

/* Empty states fade from the sunken tone into nothing, so the dashed box reads as a space
   waiting to be filled rather than a panel that failed to load. */
.koppel-detail-empty {
    color: var(--k-text-secondary);
    padding: var(--k-space-5);
    text-align: center;
    border: 1px dashed var(--k-line-strong);
    border-radius: var(--k-radius-md);
    background-image: linear-gradient(180deg, var(--k-sunken-2) 0%, transparent 90%);
}

.koppel-money-positive { color: var(--k-success); font-weight: 600; }
.koppel-money-negative { color: var(--k-error);   font-weight: 600; }

/* --- Forms in dialogs ----------------------------------------------------- */

.koppel-form-row {
    display: flex;
    gap: var(--k-space-3);
    flex-wrap: wrap;
}

.koppel-form-row > * {
    flex: 1 1 200px;
    min-width: 0;
}

/* The client editor: the client's own fields on the left, the routers they hold on the right.
   Two equal columns rather than a wide-and-narrow split — the router cards carry a detail grid
   when open and need as much room as the form does. Collapses to one column on a narrow screen,
   where the panes simply stack in the same order. */
.koppel-client-editor {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--k-space-6);
    align-items: start;
}

@media (max-width: 900px) {
    .koppel-client-editor {
        grid-template-columns: 1fr;
        gap: var(--k-space-5);
    }
}

/* min-width: 0 so a long serial or address ellipsizes inside its column rather than widening
   the grid track and pushing the other pane off. */
.koppel-client-editor-pane {
    min-width: 0;
}

/* One router the client holds. Collapsed it is a single scannable line; open it carries the
   full hardware detail plus the price actually billed. */
.koppel-router-panel {
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-md);
    background-color: var(--k-surface);
    background-image: var(--k-gradient-raised);
    margin-bottom: var(--k-space-2);
    overflow: hidden;
    transition: border-color var(--k-fast) var(--k-ease);
}

.koppel-router-panel:hover {
    border-color: var(--k-line-strong);
}

/* The open card takes the accent border, so which one you are reading is obvious at a glance
   even after scrolling its header out of view. */
.koppel-router-panel-open,
.koppel-router-panel-open:hover {
    border-color: var(--k-accent-border);
}

.koppel-router-panel-head {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
    padding: var(--k-space-2) var(--k-space-3);
}

/* A real button, so it is focusable and operable from the keyboard without any ARIA of its own;
   stripped back to look like the row it sits in. */
.koppel-router-panel-toggle {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;

    /* Explicit, and not redundant: the UA stylesheet centres a button's content, and that
       survives switching it to display:flex — `text-align` has no say over a flex main axis, so
       without this the serial floats in the middle of the row instead of starting at the left. */
    justify-content: flex-start;

    gap: var(--k-space-2);
    padding: var(--k-space-1) 0;
    border: none;
    background: none;
    font: inherit;
    color: var(--k-text);
    text-align: left;
    cursor: pointer;
}

.koppel-router-panel-name {
    font-weight: 600;
    flex: 0 0 auto;
}

.koppel-router-panel-sub {
    color: var(--k-text-secondary);
    font-size: var(--k-text-xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.koppel-router-panel-body {
    padding: var(--k-space-3);
    border-top: 1px solid var(--k-line-subtle);
}

.koppel-router-panel-actions {
    display: flex;
    align-items: center;
    gap: var(--k-space-3);
    margin-top: var(--k-space-4);
    padding-top: var(--k-space-3);
    border-top: 1px solid var(--k-line-subtle);
}

.koppel-router-panel-price {
    flex: 0 0 160px;
}

.koppel-router-add {
    margin-top: var(--k-space-3);
}

.koppel-form-error {
    color: var(--k-error);
    background-color: var(--k-error-subtle);
    background-image: linear-gradient(100deg, var(--k-error-subtle) 0%, transparent 130%);
    border-radius: var(--k-radius-sm);
    padding: var(--k-space-2) var(--k-space-3);
}

.koppel-2fa-card {
    max-width: 480px;
}

.koppel-2fa-qr {
    display: block;
    width: 200px;
    height: 200px;
    margin: 0 auto;
    border-radius: var(--k-radius-sm);
    background-color: #fff;
    padding: var(--k-space-2);
}

/* Typeahead pickers (client, router) drop their matches into a bordered list
   under the field rather than a floating popover, which keeps the dialog's own
   scrolling predictable. */
.koppel-picker-results {
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-md);
    max-height: 220px;
    overflow-y: auto;
    background-color: var(--k-surface);
    background-image: var(--k-gradient-surface);
}

.koppel-picker-option {
    display: block;
    width: 100%;
    text-align: left;
    padding: var(--k-space-2) var(--k-space-3);
    border: none;
    border-bottom: 1px solid var(--k-line-subtle);
    background: none;
    cursor: pointer;
    font: inherit;
    color: var(--k-text);
}

.koppel-picker-option:last-child { border-bottom: none; }
.koppel-picker-option:hover      { background-color: var(--k-surface-hover); }

.koppel-picker-option-sub {
    display: block;
    color: var(--k-text-secondary);
    font-size: var(--k-text-xs);
}

.koppel-picker-selected {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--k-space-3);
    padding: var(--k-space-2) var(--k-space-3);
    border: 1px solid var(--k-accent-border);
    background-color: var(--k-accent-subtle);
    background-image: linear-gradient(100deg, var(--k-accent-subtle) 0%, transparent 120%);
    border-radius: var(--k-radius-md);
}

/* --- Import / export ------------------------------------------------------ */

.koppel-import-step {
    display: flex;
    align-items: center;
    gap: var(--k-space-3);
    flex-wrap: wrap;
    padding: var(--k-space-4) 0;
    border-bottom: 1px solid var(--k-line-subtle);
}

.koppel-import-step:last-of-type { border-bottom: none; }

.koppel-import-step-num {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    flex: 0 0 auto;
    border-radius: var(--k-radius-full);
    background-color: var(--k-accent);
    background-image: var(--k-gradient-accent);
    color: var(--k-accent-contrast);
    font-size: var(--k-text-xs);
    font-weight: 700;
}

.koppel-import-step-body { flex: 1 1 260px; min-width: 0; }

.koppel-import-hint { color: var(--k-text-secondary); }

.koppel-import-summary {
    color: var(--k-text-secondary);
    padding: var(--k-space-3) 0;
}

.koppel-import-preview {
    max-height: 320px;
    overflow: auto;
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-md);
    background-color: var(--k-surface);
    background-image: var(--k-gradient-surface);
}

/* The per-row verdict from validation: importable, duplicate, or broken. */
.koppel-import-flag       { font-weight: 700; }
.koppel-import-flag-ok    { color: var(--k-success); }
.koppel-import-flag-dupe  { color: var(--k-warning); }
.koppel-import-flag-error { color: var(--k-error); }

.koppel-import-errors {
    margin-top: var(--k-space-3);
    max-height: 140px;
    overflow-y: auto;
    font-size: var(--k-text-xs);
    color: var(--k-error);
}

/* Two-list column chooser: selected on the left in export order, available on
   the right. */
.koppel-column-picker {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: var(--k-space-3);
    align-items: stretch;
}

.koppel-column-list {
    border: 1px solid var(--k-line);
    border-radius: var(--k-radius-md);
    height: 300px;
    overflow-y: auto;
    background-color: var(--k-surface);
    background-image: var(--k-gradient-surface);
}

.koppel-column-list-head {
    position: sticky;
    top: 0;
    padding: var(--k-space-2) var(--k-space-3);
    background-color: var(--k-sunken);
    background-image: var(--k-gradient-sunken);
    border-bottom: 1px solid var(--k-line);
    color: var(--k-text-muted);
    font-size: var(--k-text-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.koppel-column-option {
    display: block;
    width: 100%;
    text-align: left;
    padding: var(--k-space-2) var(--k-space-3);
    border: none;
    background: none;
    cursor: pointer;
    font: inherit;
    font-size: var(--k-text-sm);
    color: var(--k-text);
}

.koppel-column-option:hover { background-color: var(--k-surface-hover); }

.koppel-column-option[aria-selected="true"] {
    background-color: var(--k-accent-subtle);
    color: var(--k-accent);
    font-weight: 600;
}

.koppel-column-actions {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--k-space-1);
}

@media (max-width: 700px) {
    .koppel-column-picker { grid-template-columns: 1fr; }
    .koppel-column-actions { flex-direction: row; justify-content: center; }
}

/* --- Login form ----------------------------------------------------------- */

.koppel-login-form {
    display: flex;
    flex-direction: column;
    gap: var(--k-space-3);
    text-align: left;
}

.koppel-login-label {
    display: block;
    font-size: var(--k-text-sm);
    font-weight: 600;
    color: var(--k-text-secondary);
    margin-bottom: var(--k-space-1);
}

/* Plain inputs, not MudBlazor fields: the login page renders statically so the
   browser posts the form and the server can set the auth cookie. */
.koppel-input {
    width: 100%;
    padding: 0.625rem var(--k-space-3);
    font: inherit;
    font-size: var(--k-text-base);
    color: var(--k-text);
    background-color: var(--k-surface);
    border: 1px solid var(--k-line-strong);
    border-radius: var(--k-radius-md);
    transition: border-color var(--k-fast) var(--k-ease);
}

.koppel-input:hover { border-color: var(--k-text-muted); }

.koppel-input:focus {
    border-color: var(--k-accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--k-accent-subtle);
}

.koppel-login-remember {
    display: flex;
    align-items: center;
    gap: var(--k-space-2);
    color: var(--k-text-secondary);
    font-size: var(--k-text-sm);
    cursor: pointer;
}

.koppel-login-button {
    width: 100%;
    padding: 0.6875rem var(--k-space-4);
    font: inherit;
    font-size: var(--k-text-base);
    font-weight: 600;
    color: var(--k-accent-contrast);
    background-color: var(--k-accent);
    background-image: var(--k-gradient-accent);
    border: none;
    border-radius: var(--k-radius-md);
    cursor: pointer;
    box-shadow: var(--k-shadow-sm);
    transition: background-color var(--k-fast) var(--k-ease),
                filter var(--k-fast) var(--k-ease);
}

/* Hover and press stay gradients rather than reverting to a flat color: hover
   lifts the whole ramp, press swaps to the deep end of it. */
.koppel-login-button:hover  { filter: brightness(1.08); }
.koppel-login-button:active { filter: none; background-image: var(--k-gradient-accent-press); }

.koppel-login-error {
    color: var(--k-error);
    background-color: var(--k-error-subtle);
    border: 1px solid var(--k-error);
    border-radius: var(--k-radius-md);
    padding: var(--k-space-2) var(--k-space-3);
    font-size: var(--k-text-sm);
    text-align: left;
}

.koppel-login-link {
    display: inline-block;
    margin-top: var(--k-space-3);
    color: var(--k-accent);
    font-size: var(--k-text-sm);
    text-decoration: none;
}

.koppel-login-link:hover { color: var(--k-accent-hover); text-decoration: underline; }

/* --- Appbar extras -------------------------------------------------------- */

.koppel-appbar-actions {
    display: flex;
    align-items: center;
    gap: var(--k-space-1);
}

/* Long change lists in the audit table stay on one line; the full text is in
   the cell's tooltip. */
.koppel-truncate {
    display: block;
    max-width: 420px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* The file input itself is visually hidden but still focusable and still the thing the label
   activates â€” dressing up a real <input type="file"> rather than faking one with a button. */
.koppel-file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.koppel-file-label {
    display: inline-flex;
    align-items: center;
    gap: var(--k-space-2);
    padding: 0.375rem var(--k-space-4);
    font-size: var(--k-text-base);
    font-weight: 600;
    color: var(--k-accent);
    background-color: transparent;
    border: 1px solid var(--k-accent-border);
    border-radius: var(--k-radius-md);
    cursor: pointer;
    transition: background-color var(--k-fast) var(--k-ease);
}

.koppel-file-label:hover {
    background-color: var(--k-accent-subtle);
    background-image: linear-gradient(100deg, var(--k-accent-subtle) 0%, transparent 130%);
}

.koppel-file-input:focus-visible + .koppel-file-label {
    outline: 2px solid var(--k-accent);
    outline-offset: 2px;
}


/* ============================================================================
   11. MUDBLAZOR SKINS
   A short list of MudBlazor's own components restyled to match the house
   gradients. Each one layers a `background-image` over the background-color
   MudBlazor already sets, which is why none of these need `!important`: the
   image paints on top of the color rather than fighting it, and if a theme
   color changes, the gradient follows because it's built from the same tokens.
   ============================================================================ */

/* Every primary CTA in the app — dialog confirms, page actions — picks up the
   accent ramp. Both ends of that ramp carry the button's label at >6:1, so this
   is safe to apply globally rather than per button. */
.mud-button-filled.mud-button-filled-primary {
    background-image: var(--k-gradient-accent);
    transition: filter var(--k-fast) var(--k-ease);
}

.mud-button-filled.mud-button-filled-primary:hover  { filter: brightness(1.08); }
.mud-button-filled.mud-button-filled-primary:active { filter: none; background-image: var(--k-gradient-accent-press); }

/* Indeterminate loading bars — the table-card bar and the dashboard's — sweep
   the accent ramp instead of sitting flat. */
.mud-progress-linear.mud-progress-linear-color-primary .mud-progress-linear-bar {
    background-image: var(--k-gradient-accent);
}

/* Floating surfaces (menus, select dropdowns) get the same raised ramp the cards
   use, so a menu reads as the same material as a card. Scoped to popovers that
   are also papers: those are the ones MudBlazor has already given an opaque
   surface color, and painting a gradient onto a deliberately transparent
   wrapper would draw a panel where there wasn't one. */
.mud-popover.mud-paper {
    background-image: var(--k-gradient-raised);
}

/* Alerts. The filled variants carry a label on a colour, so they take the sheen — the same
   contrast-safe direction the status chips use. The text variants are already a pale tint of
   their hue and only need the faintest ramp. */
.mud-alert[class*="mud-alert-filled-"] {
    background-image: var(--k-sheen);
}

.mud-alert[class*="mud-alert-text-"] {
    background-image: linear-gradient(150deg, rgba(255, 255, 255, 0.35) 0%, transparent 70%);
}

/* Where a page's own empty state lands inside a card, it gets the same fade as the dashed
   detail boxes so every "nothing here yet" in the app looks like the same thing. */
.koppel-empty {
    background-image: linear-gradient(180deg, var(--k-sunken-2) 0%, transparent 75%);
}


/* ============================================================================
   12. THE CHECKBOX
   Koppel's own mark, defined in Theme/KoppelIcons.cs and worn by
   <KoppelCheckBox>. Checking a box stamps the brand tile into it: the same
   squircle and the same indigo→violet sweep as the "K" in the appbar, with the
   tick drawing itself on.

   Read the geometry notes in KoppelIcons.cs before changing anything here — the
   two files share numbers, and --k-check-length below is the mark's actual path
   length. Get it wrong and the tick either finishes drawing early or never
   quite arrives.
   ============================================================================ */

:root {
    /* The tile's own sweep. Deliberately NOT --k-gradient-brand: that ramp travels
       almost entirely in hue (its two ends sit within 15% of each other in
       lightness), which is invisible across 18px — the first cut of this icon
       read as a flat violet chip. These two ends differ ~3× in lightness, so the
       tile looks lit rather than filled, and both still carry the white tick
       above 4.5:1. */
    --k-check-from: #6366F1;
    --k-check-to:   #5B21B6;

    /* The rim along the inside of the tile edge. One value, two readings: a
       highlight on the dark tile here, an inner shadow on the light tile in dark
       mode. Shared by the SVG icon and the login page's native input, which is
       why it carries its own alpha rather than leaning on stroke-opacity. */
    --k-check-rim:  rgba(255, 255, 255, 0.28);

    /* Length of the tick's path, in viewBox units. See KoppelIcons.cs. */
    --k-check-length: 14.4;

    /* The same tick, as an alpha mask — for the login page's native input, which
       gets no SVG of its own. Kept in sync with the path in KoppelIcons.cs by
       hand; it is the one duplicated coordinate set in the design system, and it
       exists because a mask can be tinted by the theme while a data-URI stroke
       color cannot. The stroke is 2.7 rather than the icon's 2.05 because here
       the 24-unit viewBox is scaled to fit an 18px box rather than a 24px one —
       2.05 × 24/18 ≈ 2.7 lands the two at the same visual weight. */
    --k-check-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.4 12.3 L10.8 15.7 L16.8 8.3' fill='none' stroke='%23000' stroke-width='2.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* Dark inverts the tile: a light lavender chip carrying a near-black tick, which
   is the only way round that works on a near-black page. Both ends again clear
   4.5:1 against --k-accent-contrast. */
.mud-theme-dark {
    --k-check-from: #A5B4FC;
    --k-check-to:   #7C6BF0;
    --k-check-rim:  rgba(11, 11, 18, 0.28);
}

/* The paint server itself lives in MainLayout; this is where its colors come
   from, so the sweep tracks the accent tokens like every other gradient. */
.koppel-svg-defs {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

.koppel-check-stop-from { stop-color: var(--k-check-from); }
.koppel-check-stop-to   { stop-color: var(--k-check-to); }

/* Unchecked: a hairline tile at the same weight as the app's strong borders, so
   an empty box sits in the same visual family as an empty input. */
.koppel-check-box {
    stroke: var(--k-line-strong);
    transition: stroke var(--k-fast) var(--k-ease);
}

.koppel-check:hover .koppel-check-box {
    stroke: var(--k-accent);
}

/* Checked: the brand tile. The flat color after the url() is a real fallback in
   SVG paint syntax — if the gradient element is ever missing, the tile fills
   solid accent rather than not painting at all. */
.koppel-check-tile {
    fill: url(#koppel-check-fill) var(--k-accent);
}

/* A disabled checkbox must not look like the liveliest thing on the page, so it
   drops the gradient and takes whatever muted color MudBlazor has set. */
.mud-disabled .koppel-check-tile {
    fill: currentColor;
}

.koppel-check-mark {
    stroke: var(--k-accent-contrast);
}

.koppel-check-rim {
    stroke: var(--k-check-rim);
    stroke-opacity: 1;
}

/* The tick draws itself in, and the tile lands under it. Both are short enough
   to feel like a response rather than an animation — the tile pops in a frame
   or two, the tick follows it in. MudBlazor swaps the icon element on toggle,
   so this replays on every check without any state to manage. */
.koppel-check .koppel-check-tile {
    transform-box: fill-box;
    transform-origin: center;
    animation: koppel-check-pop var(--k-fast) var(--k-ease);
}

.koppel-check .koppel-check-mark {
    stroke-dasharray: var(--k-check-length);
    animation: koppel-check-draw var(--k-normal) var(--k-ease);
}

@keyframes koppel-check-pop {
    from { transform: scale(0.82); opacity: 0; }
    to   { transform: scale(1);    opacity: 1; }
}

@keyframes koppel-check-draw {
    from { stroke-dashoffset: var(--k-check-length); }
    to   { stroke-dashoffset: 0; }
}

/* --- The login page's checkbox -------------------------------------------- */

/* Login renders statically and uses Blazor's <InputCheckbox>, so there is no
   MudBlazor icon to swap — the native control is rebuilt to match instead.
   Same 18px tile, same radius, same sweep, same tick (as a mask, so the theme
   can tint it). Two implementations of one mark is a cost worth paying to avoid
   the app's first screen wearing a different checkbox than every screen after. */
.koppel-login-remember input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin: 0;
    border: 2px solid var(--k-line-strong);
    border-radius: 5px;
    background-color: var(--k-surface);
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: border-color var(--k-fast) var(--k-ease),
                background-color var(--k-fast) var(--k-ease);
}

.koppel-login-remember:hover input[type="checkbox"] {
    border-color: var(--k-accent);
}

.koppel-login-remember input[type="checkbox"]:checked {
    border-color: transparent;
    background-color: var(--k-check-from);
    background-image: linear-gradient(135deg, var(--k-check-from), var(--k-check-to));
    box-shadow: inset 0 0 0 1.2px var(--k-check-rim);
}

.koppel-login-remember input[type="checkbox"]::before {
    content: "";
    width: 100%;
    height: 100%;
    background-color: var(--k-accent-contrast);
    -webkit-mask: var(--k-check-mask) center / contain no-repeat;
    mask: var(--k-check-mask) center / contain no-repeat;
    transform: scale(0);
    transition: transform var(--k-fast) var(--k-ease);
}

.koppel-login-remember input[type="checkbox"]:checked::before {
    transform: scale(1);
}

.koppel-login-remember input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--k-accent);
    outline-offset: 2px;
}

/* Motion here is decoration, not information — the tick's final state says
   everything the drawing does. */
@media (prefers-reduced-motion: reduce) {
    .koppel-check .koppel-check-tile,
    .koppel-check .koppel-check-mark {
        animation: none;
    }

    .koppel-login-remember input[type="checkbox"]::before {
        transition: none;
    }
}

