/* ============================================================================
   Segal.Ui — the single stylesheet a consuming application links.

     <link rel="stylesheet" href="_content/Segal.Ui/css/segal-ui.css" />

   Link it after the MudBlazor stylesheet and before your own. Order against MudBlazor does not
   actually affect colour resolution — MudThemeProvider emits --mud-palette-* as a runtime <style>
   element and var() resolves at computed-value time, not at link time — but keeping the framework
   ahead of application CSS is what lets an application override it without reaching for
   !important.

   This is one file rather than an @import of two, deliberately. Static web assets are
   fingerprinted individually, and an @import target is fetched by the browser rather than by the
   host, so it keeps its unfingerprinted URL and can be served stale from cache after a token
   changes. Imports are also fetched serially, after the importing sheet has parsed.

   Components carrying their own scoped .razor.css need no link at all: an RCL's isolated CSS is
   bundled into the consuming application's {AppName}.styles.css automatically.
   ========================================================================= */

/* ============================================================================
   Segal.Ui design tokens.

   Two halves, with different rules:

     COLOUR is an alias onto the active MudTheme. Change your brand by editing SgPalette in
     C#, never by editing this file. Light and dark both come from MudThemeProvider re-emitting
     --mud-palette-*, which is why there is no dark block below and no prefers-color-scheme
     query — adding one would create a second source of truth that drifts the first time
     someone sets Color.Secondary on a MudButton.

     GEOMETRY, TYPE and MOTION are authored here and are identical in every application. They
     have no MudBlazor equivalent, which is the whole reason this layer exists: a consumer who
     wants tighter radii, or who needs prefers-reduced-motion honoured everywhere, otherwise
     has no lever at all.
   ========================================================================= */

:root {
    /* ---- colour: one-directional alias onto the active MudTheme -------------------------

       The fallbacks are load-bearing, not defensive habit. MudBlazor does not emit every one
       of these under both palettes, and an unresolved var() inside color-mix() invalidates
       the WHOLE declaration rather than degrading — the element silently loses its
       background rather than showing a wrong colour, which is far harder to spot. */
    --sg-primary: var(--mud-palette-primary, #6741E0);
    --sg-primary-text: var(--mud-palette-primary-text, #FFFFFF);
    --sg-secondary: var(--mud-palette-secondary, #C6981C);
    --sg-tertiary: var(--mud-palette-tertiary, #12A9B8);

    --sg-info: var(--mud-palette-info, #3B82F6);
    --sg-success: var(--mud-palette-success, #2E9E6A);
    --sg-warning: var(--mud-palette-warning, #E08A1E);
    --sg-error: var(--mud-palette-error, #E14B4B);

    --sg-surface: var(--mud-palette-surface, #FFFFFF);
    --sg-background: var(--mud-palette-background, #EEF0F5);
    --sg-background-alt: var(--mud-palette-background-gray, #E4E7EE);
    --sg-lines: var(--mud-palette-lines-default, rgba(24, 23, 40, .10));
    --sg-lines-inputs: var(--mud-palette-lines-inputs, rgba(24, 23, 40, .26));
    --sg-text-primary: var(--mud-palette-text-primary, #1C1B22);
    --sg-text-secondary: var(--mud-palette-text-secondary, #5C5B66);
    --sg-action: var(--mud-palette-action-default, rgba(28, 27, 34, .56));
    --sg-hover: var(--mud-palette-action-default-hover, rgba(0, 0, 0, .05));

    /* ==== FIXED LAYER — identical in every project. Do not edit. ==================== */

    /* Geometry */
    --sg-radius-card: 12px;         /* toolbar, card, panel, stat tile, empty state */
    --sg-radius-chip: 9px;          /* icon chip, segmented container */
    --sg-radius-control: 8px;       /* icon button, list row */
    --sg-radius-control-sm: 7px;    /* segmented button */
    --sg-radius-well: 10px;         /* search box, small stat, pick list */
    --sg-radius-pill: 999px;
    --sg-radius-tag: 4px;
    --sg-border: 1px solid var(--sg-lines);

    /* The height of a form control, as the rules further down actually draw it: one 19px text
       row plus the 10px of padding above and below. Authored rather than derived because the
       text row is the MudTheme's own body line-height, which no var() here can reach.

       Named because anything standing beside a field — the button that ends an inline form,
       a bare div acting as a field — has to be this tall to line up with it, and measuring the
       field by hand at each of those call sites is how they end up a pixel or two apart. */
    --sg-field-h: 39px;
    /* A MudBlazor input at Margin.Dense, which is what a compact select has to line up with. */
    --sg-field-h-dense: 34px;

    /* Elevation — the only place a raw rgba is allowed, because these are deliberately
       neutral rather than tinted by the theme. */
    --sg-shadow-rest: 0 1px 2px rgba(0, 0, 0, .06);
    --sg-shadow-hover: 0 4px 14px rgba(0, 0, 0, .08);
    --sg-shadow-pop: 0 18px 48px rgba(0, 0, 0, .28);

    /* Spacing */
    --sg-gap-page: 10px;            /* between page blocks, and between grid cells */
    --sg-pad-bar: 10px 12px;        /* toolbar row, panel bar */
    --sg-pad-card: 14px;
    --sg-pad-empty: 48px 24px;

    /* Type — do not invent sizes. */
    --sg-fs-dialog-title: 17px;
    --sg-fs-brand: 15.5px;
    --sg-fs-title: 14.5px;
    --sg-fs-body: 14px;
    --sg-fs-secondary: 13px;
    --sg-fs-meta: 12.5px;
    --sg-fs-label: 11.5px;
    --sg-fs-tag: 10.5px;
    --sg-fs-stat: 24px;

    /* Motion */
    --sg-ease: .15s ease;
    --sg-ease-fast: .12s ease;      /* list-row hover, where .15s reads as lag */
    --sg-rise-duration: .45s;       /* a paper appearing: fade in while settling upward */
    --sg-rise-distance: 6px;
    --sg-rise-step: .04s;           /* stagger between siblings in a grid or stat row */
    --sg-pop-duration: .3s;         /* a small thing arriving: a check mark, an icon, an error line */
    --sg-press-scale: .94;          /* a control held under the pointer */
    --sg-press-scale-wide: .97;     /* the same for a button, whose long label would visibly shrink */
    --sg-lift: 2px;                 /* a clickable card rising to meet the pointer; a chevron's nudge */
    --sg-popover-duration: .16s;    /* a dropdown leaving its trigger: quicker than a paper, it answers a click */
    --sg-popover-distance: 4px;
    --sg-dialog-duration: .22s;     /* a dialog settling in over its scrim */
    --sg-dialog-distance: 10px;

    /* Tint strengths consumed by color-mix(). Backgrounds 12-15%, borders 40-45%.
       Tints are computed from an accent, never picked by hand, so every accent tints
       identically. */
    --sg-tint-bg: 14%;
    --sg-tint-bg-soft: 12%;
    --sg-tint-border: 40%;

    /* The primary tint, computed once. Named because the "on" state of icon buttons, chips
       and pick rows all need the same wash, and computing it at each call site is how two of
       them end up 1% apart. Derived from --sg-primary, so it follows a brand change for free. */
    --sg-primary-soft: color-mix(in srgb, var(--sg-primary) var(--sg-tint-bg), transparent);

    /* The fill of an input well. Translucent rather than a solid colour on purpose: a field sits
       sometimes on a card and sometimes on the page, and any solid token matches one of those
       two exactly and disappears. Mixed from the text colour, it darkens on a light theme and
       lightens on a dark one, and always separates from whatever is behind it. */
    --sg-field-fill: color-mix(in srgb, var(--sg-text-primary) 5%, transparent);
    --sg-field-fill-hover: color-mix(in srgb, var(--sg-text-primary) 7%, transparent);

    /* Device chrome. env() falls back to 0px on anything without a notch or home indicator;
       it only reports a real value when the host sets viewport-fit=cover. */
    --sg-safe-top: env(safe-area-inset-top, 0px);
    --sg-safe-bottom: env(safe-area-inset-bottom, 0px);

    /* Font family for right-to-left runs. The host owns the @font-face — the framework only
       names the slot, so an app that ships no RTL face simply inherits. */
    --sg-font-rtl: inherit;
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --sg-ease: 0s;
        --sg-ease-fast: 0s;

        /* The step as well as the duration: a stagger delay with no animation after it would
           just be a paper that stays invisible for a moment. */
        --sg-rise-duration: 0s;
        --sg-rise-step: 0s;
        --sg-pop-duration: 0s;
        --sg-popover-duration: 0s;
        --sg-dialog-duration: 0s;

        /* The distances too, not only the timing: a press or a lift with no transition does not
           go away, it just snaps, and a snapping card is more jarring than a gliding one. */
        --sg-press-scale: 1;
        --sg-press-scale-wide: 1;
        --sg-lift: 0px;
        --sg-popover-distance: 0px;
        --sg-dialog-distance: 0px;
    }
}


/* ============================================================================
   Segal.Ui component vocabulary. Built on the tokens above.

   Global and deliberately unscoped. Two things follow from that, both load-bearing:

     1. Any markup can opt in by writing class="sg-card" — you are never forced to adopt the
        Razor component to get the look. If these rules lived in SgCard.razor.css the SDK
        would rewrite them to .sg-card[b-abc123] and hand-written markup would get nothing.

     2. No ::deep is needed here. A rule like `.sg-panel .mud-table` simply matches, which is
        why the "one border per box" rule can be enforced once in this file instead of being
        restated as a ::deep override in every page that nests a table in a panel.

   The cost is that a consumer's scoped .razor.css cannot reach a framework component's root
   element — Blazor stamps scope attributes on HTML elements, not on component tags. The
   documented override is `.your-page ::deep .sg-card { }` from an element the page owns.
   Nothing here uses !important, so that override always wins.

   Every value comes from a token. Never change one while copying a recipe.
   ========================================================================= */

/* ------------------------------------------------------------------- page --- */

.sg-page {
    display: flex;
    flex-direction: column;
    gap: var(--sg-gap-page);
    padding: 12px 0 8px;
}

/* Full-bleed page (map, board, canvas): fill the viewport below the app bar. */
.sg-page-full {
    height: calc(100vh - 96px);
    min-height: 480px;
}

/* -------------------------------------------------------------- appearing --- */

/* Papers settle into place when they appear: a short fade while rising a few pixels. Cards,
   stat tiles, panels and empty states do it by default; any other surface opts in with .sg-rise.
   The toolbar is left still on purpose -- it is the page's chrome, and chrome that moves on every
   navigation reads as the whole page reloading.

   Fill mode is backwards, not both. During a stagger delay the paper holds the first frame, and
   once the animation ends it lets go entirely -- "both" would keep overriding opacity and
   transform forever, and a dimmed or lifted card would silently lose its own values.

   It plays when an element is created, not when it re-renders, so a refresh that keeps the DOM
   does not replay it; a loading state giving way to content does, which is when it helps. */
@keyframes sg-rise {
    from {
        opacity: 0;
        transform: translateY(var(--sg-rise-distance));
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.sg-card,
.sg-stat,
.sg-panel,
.sg-empty,
.sg-rise {
    animation: sg-rise var(--sg-rise-duration) ease backwards;
    animation-delay: var(--sg-rise-delay, 0s);
}

/* Siblings arrive one after another. Capped at the sixth, so a long grid does not keep somebody
   waiting for its last row. .sg-rise-group lends the same stagger to a container of your own. */
:is(.sg-grid, .sg-stats, .sg-stats-row, .sg-rise-group) > :nth-child(2) { --sg-rise-delay: var(--sg-rise-step); }
:is(.sg-grid, .sg-stats, .sg-stats-row, .sg-rise-group) > :nth-child(3) { --sg-rise-delay: calc(var(--sg-rise-step) * 2); }
:is(.sg-grid, .sg-stats, .sg-stats-row, .sg-rise-group) > :nth-child(4) { --sg-rise-delay: calc(var(--sg-rise-step) * 3); }
:is(.sg-grid, .sg-stats, .sg-stats-row, .sg-rise-group) > :nth-child(5) { --sg-rise-delay: calc(var(--sg-rise-step) * 4); }
:is(.sg-grid, .sg-stats, .sg-stats-row, .sg-rise-group) > :nth-child(n+6) { --sg-rise-delay: calc(var(--sg-rise-step) * 5); }

/* Loading states and overlays fade without rising. An overlay is pinned to its host with inset: 0,
   so moving it would open a gap along one edge; a spinner that drifts upward reads as a layout
   shift. Same duration token, so reduced motion stills these too. */
@keyframes sg-fade {
    from { opacity: 0; }
    to { opacity: 1; }
}

.sg-loading,
.sg-overlay {
    animation: sg-fade var(--sg-rise-duration) ease backwards;
}

/* Small things arrive with a little more character than papers do, because they are the answer
   to something the person just did: the check mark on the option they picked, the line that
   says what is wrong with what they typed. A pop for a mark, a short drop for a line of text.

   The empty state's icon waits for its paper to be mostly settled before popping, so the two read
   as one arrival rather than two things competing. Delay and duration both come from tokens that
   reduced motion zeroes. */
@keyframes sg-pop {
    from {
        opacity: 0;
        transform: scale(.6);
    }

    60% {
        opacity: 1;
        transform: scale(1.08);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes sg-drop {
    from {
        opacity: 0;
        transform: translateY(-3px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.sg-empty-icon {
    animation: sg-pop var(--sg-pop-duration) ease backwards;
    animation-delay: calc(var(--sg-rise-duration) * .5 + var(--sg-rise-delay, 0s));
}

.sg-pick-row-on .sg-pick-check > .mud-icon-root {
    animation: sg-pop var(--sg-pop-duration) ease backwards;
}

/* The class is added to a helper line that already exists when validation fails, and the animation
   starts when it is added, so an error drops in each time it appears — not when its text changes. */
.mud-input-helper-text.mud-input-error,
.sg-hint-error,
.sg-select-errortext {
    animation: sg-drop var(--sg-pop-duration) ease backwards;
}

/* ---------------------------------------------------------------- toolbar --- */

.sg-toolbar {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: var(--sg-pad-bar);
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-rest);
}

.sg-toolbar-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* Denser row for filter chips. */
.sg-toolbar-row-filters {
    gap: 6px;
}

/* An expanded query/filter area hangs below the rows, divided by a rule. */
.sg-toolbar-drawer {
    border-top: var(--sg-border);
    padding-top: 8px;
}

.sg-brand {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: var(--sg-fs-brand);
    font-weight: 600;
    letter-spacing: .2px;
    color: var(--sg-text-primary);
}

/* Contextual suffix (current project, folder, record) after a divider. */
.sg-brand-context {
    font-size: var(--sg-fs-secondary);
    font-weight: 500;
    color: var(--sg-text-secondary);
    padding-left: 8px;
    margin-left: 2px;
    border-left: var(--sg-border);
}

.sg-tools {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.sg-tools-sep {
    width: 1px;
    height: 20px;
    margin: 0 5px;
    background: var(--sg-lines);
}

/* ------------------------------------------------------- segmented control --- */

.sg-seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 3px;
    border-radius: var(--sg-radius-chip);
    border: var(--sg-border);
    background: var(--sg-background);
}

.sg-seg-label {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .6px;
    color: var(--sg-text-secondary);
    padding: 0 8px 0 6px;
}

.sg-seg-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: none;
    background: transparent;
    color: var(--sg-text-secondary);
    font: inherit;
    font-size: var(--sg-fs-body);
    padding: 4px 11px;
    border-radius: var(--sg-radius-control-sm);
    cursor: pointer;
    transition: background-color var(--sg-ease), color var(--sg-ease), transform var(--sg-ease-fast);
}

.sg-seg-btn:hover {
    color: var(--sg-text-primary);
    background: var(--sg-hover);
}

/* Pressed, the control gives a little under the pointer and springs back on release — the only
   confirmation a click gets on a toggle that is already on. Shared with icon buttons and chips. */
.sg-seg-btn:active,
.sg-icon-btn:active:not(:disabled),
.sg-chip:active {
    transform: scale(var(--sg-press-scale));
}

.sg-seg-btn-on,
.sg-seg-btn-on:hover {
    background: var(--sg-primary);
    color: var(--sg-primary-text);
    font-weight: 600;
}

.sg-seg-badge {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--sg-radius-pill);
    background: var(--sg-hover);
    font-size: var(--sg-fs-label);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* On the active button the neutral hover wash disappears into the solid fill. */
.sg-seg-btn-on .sg-seg-badge {
    background: color-mix(in srgb, var(--sg-primary-text) 22%, transparent);
}

/* ------------------------------------------------------------ icon button --- */

.sg-icon-btn {
    display: inline-grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--sg-radius-control);
    border: 1px solid transparent;
    background: transparent;
    color: var(--sg-action);
    cursor: pointer;
    transition: background-color var(--sg-ease), color var(--sg-ease), border-color var(--sg-ease), transform var(--sg-ease-fast);
}

.sg-icon-btn:hover:not(:disabled) {
    background: var(--sg-hover);
    color: var(--sg-text-primary);
}

.sg-icon-btn:disabled {
    opacity: .45;
    cursor: default;
}

.sg-icon-btn-on {
    background: var(--sg-primary-soft);
    border-color: var(--sg-primary);
    color: var(--sg-primary);
}

/* Text-only affordance inside chrome (Clear, Reset, Show all). */
.sg-link-btn {
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-size: var(--sg-fs-meta);
    color: var(--sg-text-secondary);
    text-decoration: underline;
    cursor: pointer;
}

.sg-link-btn:hover {
    color: var(--sg-primary);
}

/* -------------------------------------------------------- chips / toggles --- */

.sg-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 11px;
    border-radius: var(--sg-radius-pill);
    border: var(--sg-border);
    background: transparent;
    color: var(--sg-text-secondary);
    font: inherit;
    font-size: var(--sg-fs-body);
    cursor: pointer;
    transition: border-color var(--sg-ease), color var(--sg-ease), background-color var(--sg-ease), transform var(--sg-ease-fast);
}

.sg-chip:hover {
    border-color: var(--sg-primary);
    color: var(--sg-text-primary);
}

.sg-chip-on {
    border-color: var(--sg-primary);
    background: color-mix(in srgb, var(--sg-primary) var(--sg-tint-bg), transparent);
    color: var(--sg-primary);
    font-weight: 600;
}

/* Use the secondary accent when primary is already carrying the segmented control. */
.sg-chip-on-secondary {
    border-color: var(--sg-secondary);
    background: color-mix(in srgb, var(--sg-secondary) var(--sg-tint-bg), transparent);
    color: var(--sg-secondary);
    font-weight: 600;
}

/* Solid variant, for a chip row that is the page's primary filter rather than a refinement. */
.sg-chip-on-solid {
    border-color: var(--sg-primary);
    background: var(--sg-primary);
    color: var(--sg-primary-text);
}

.sg-chip-count {
    font-size: var(--sg-fs-label);
    padding: 0 6px;
    border-radius: var(--sg-radius-pill);
    background: var(--sg-hover);
    color: var(--sg-text-secondary);
    font-variant-numeric: tabular-nums;
}

.sg-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* ------------------------------------------------------------ search field --- */

.sg-search {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 11px;
    border-radius: var(--sg-radius-well);
    border: var(--sg-border);
    background: var(--sg-field-fill);
    color: var(--sg-text-secondary);
}

.sg-search input {
    flex: 1;
    min-width: 0;
    border: none;
    background: transparent;
    outline: none;
    font: inherit;
    font-size: var(--sg-fs-body);
    color: var(--sg-text-primary);
}

/* ------------------------------------------------------------------ panel --- */

.sg-panel {
    display: flex;
    flex-direction: column;
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-rest);
    overflow: hidden;
}

.sg-panel-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: var(--sg-pad-bar);
    border-bottom: var(--sg-border);
}

.sg-panel-title {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: var(--sg-fs-secondary);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--sg-text-secondary);
}

.sg-count {
    font-size: 12px;
    padding: 1px 8px;
    border-radius: var(--sg-radius-pill);
    background: var(--sg-hover);
    color: var(--sg-text-secondary);
    font-variant-numeric: tabular-nums;
}

.sg-panel-body {
    padding: 4px;
}

.sg-panel-pad {
    padding: 12px;
}

/* One border per box: the panel draws it, so anything nested draws none. Stated once here
   rather than as a ::deep override in every page that puts a table in a panel — which is
   what this sheet being unscoped buys. */
.sg-panel .mud-table,
.sg-panel .mud-table-root,
.sg-panel .mud-table-container,
.sg-panel .mud-paper,
.sg-panel table {
    background: transparent;
    box-shadow: none;
}

.sg-panel .mud-table-root {
    border: none;
}

/* ------------------------------------------------------------- settings --- */

/* A stack of settings inside a panel. The panel draws the outer border; the rows draw the rules
   between them, and the last one draws none so it does not double the panel's own edge. */
.sg-settings {
    display: flex;
    flex-direction: column;
}

.sg-setting {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 11px 16px;
    border: none;
    border-bottom: 1px solid var(--sg-lines);
    background: transparent;
    color: var(--sg-text-primary);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background-color var(--sg-ease);
}

.sg-setting:last-child {
    border-bottom: none;
}

.sg-setting:hover:not(:disabled) {
    background: var(--sg-hover);
}

.sg-setting:focus-visible {
    outline: 2px solid var(--sg-primary);
    outline-offset: -2px;
}

.sg-setting-off {
    cursor: default;
    opacity: 0.7;
}

.sg-setting-text {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

.sg-setting-title {
    font-size: 14px;
    font-weight: 500;
}

/* What it is set to now, which is the half somebody is actually scanning for. */
.sg-setting-value {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    color: var(--sg-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-setting-go {
    color: var(--sg-text-secondary);
    flex: 0 0 auto;
    transition: transform var(--sg-ease), color var(--sg-ease);
}

/* The chevron leans toward where the row goes. */
.sg-setting:hover:not(:disabled):not(.sg-setting-off) .sg-setting-go {
    color: var(--sg-text-primary);
    transform: translateX(var(--sg-lift));
}

/* ------------------------------------------------------------- card grid --- */

.sg-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--sg-gap-page);
}

.sg-card {
    display: flex;
    flex-direction: column;
    gap: var(--sg-gap-page);
    padding: var(--sg-pad-card);
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-rest);
}

/* Hover and pointer belong to a card that actually goes somewhere. A static card that
   lifts on hover promises a click that never happens. */
.sg-card-click {
    cursor: pointer;
    transition: border-color var(--sg-ease), box-shadow var(--sg-ease), transform var(--sg-ease);
}

/* It rises by --sg-lift to meet the pointer, which the hover shadow then reads as height, and
   settles back while pressed so the click lands on something that gives. The transform exists
   only during hover: it would otherwise make every card a containing block and stacking context. */
.sg-card-click:hover {
    border-color: color-mix(in srgb, var(--sg-primary) var(--sg-tint-border), var(--sg-lines));
    box-shadow: var(--sg-shadow-hover);
    transform: translateY(calc(var(--sg-lift) * -1));
}

.sg-card-click:active {
    transform: none;
}

.sg-card-click:focus-visible {
    outline: 2px solid var(--sg-primary);
    outline-offset: 2px;
}

/* Accent variants: a record in a state the grid should not hide. */
.sg-card-error { border-color: color-mix(in srgb, var(--sg-error) var(--sg-tint-border), var(--sg-lines)); }
.sg-card-warning { border-color: color-mix(in srgb, var(--sg-warning) var(--sg-tint-border), var(--sg-lines)); }
.sg-card-success { border-color: color-mix(in srgb, var(--sg-success) var(--sg-tint-border), var(--sg-lines)); }

.sg-card-head {
    display: flex;
    align-items: center;
    gap: var(--sg-gap-page);
}

.sg-card-titles {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1;
}

.sg-card-title {
    font-size: var(--sg-fs-title);
    font-weight: 600;
    line-height: 1.3;
    color: var(--sg-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-card-desc {
    font-size: var(--sg-fs-meta);
    line-height: 1.45;
    color: var(--sg-text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Pushed to the bottom so cards of different heights align their meta rows. */
.sg-card-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    margin-top: auto;
    font-size: 12px;
    color: var(--sg-text-secondary);
}

.sg-meta {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-variant-numeric: tabular-nums;
}

.sg-card-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    padding-top: var(--sg-gap-page);
    border-top: var(--sg-border);
    margin-top: 2px;
}

@media (max-width: 720px) {
    .sg-grid {
        grid-template-columns: 1fr;
    }
}

/* -------------------------------------------------------------- icon chip --- */

/* Tinted glyph chip that identifies the record type. */
.sg-icon-chip {
    display: inline-grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--sg-radius-chip);
    color: var(--sg-primary);
    background: color-mix(in srgb, var(--sg-primary) 13%, transparent);
}

.sg-icon-chip-dialog { width: 36px; height: 36px; }
.sg-icon-chip-empty { width: 52px; height: 52px; border-radius: 14px; }

.sg-icon-chip-secondary { color: var(--sg-secondary); background: color-mix(in srgb, var(--sg-secondary) 13%, transparent); }
.sg-icon-chip-tertiary { color: var(--sg-tertiary); background: color-mix(in srgb, var(--sg-tertiary) 13%, transparent); }
.sg-icon-chip-info { color: var(--sg-info); background: color-mix(in srgb, var(--sg-info) 13%, transparent); }
.sg-icon-chip-success { color: var(--sg-success); background: color-mix(in srgb, var(--sg-success) 13%, transparent); }
.sg-icon-chip-warning { color: var(--sg-warning); background: color-mix(in srgb, var(--sg-warning) 13%, transparent); }
.sg-icon-chip-error { color: var(--sg-error); background: color-mix(in srgb, var(--sg-error) 13%, transparent); }

/* ----------------------------------------------------------------- avatar --- */

/* Initials are sized in container units off the avatar's own box, so an avatar stretched by an
   inline width keeps its letters in proportion. Colours arrive as custom properties because they
   are the person's choice, not the palette's. */
.sg-avatar {
    --sg-avatar-bg: color-mix(in srgb, var(--sg-primary) var(--sg-tint-bg), var(--sg-surface));
    --sg-avatar-fg: var(--sg-primary);
    container-type: size;
    display: inline-grid;
    place-items: center;
    width: var(--sg-avatar-size, 40px);
    height: var(--sg-avatar-size, 40px);
    flex-shrink: 0;
    overflow: hidden;
    border-radius: 50%;
    background: var(--sg-avatar-bg);
    color: var(--sg-avatar-fg);
    user-select: none;
    vertical-align: middle;
}

.sg-avatar-rounded { border-radius: 28%; }
.sg-avatar-square { border-radius: var(--sg-radius-tag); }
/* Sized off the Size parameter, not container units: those resolve against the avatar's
   ancestors when used on the avatar itself, and a ring that wide fills the whole disc. */
.sg-avatar-ring { box-shadow: inset 0 0 0 max(1.5px, calc(var(--sg-avatar-size, 40px) * .05)) var(--sg-avatar-fg); }

.sg-avatar-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.sg-avatar-glyph { font-size: 62cqi !important; }

.sg-avatar-initials {
    font-size: 40cqi;
    font-weight: 700;
    line-height: 1;
    letter-spacing: .02em;
}

/* ------------------------------------------------------------ colour field --- */

.sg-color {
    display: inline-flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    cursor: pointer;
}

.sg-color-off { opacity: .5; cursor: default; }

.sg-color-label {
    font-size: var(--sg-fs-label);
    font-weight: 600;
    color: var(--sg-text-secondary);
}

.sg-color-well {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: var(--sg-field-h);
    padding: 0 10px 0 6px;
    border-radius: var(--sg-radius-control);
    background: var(--sg-field-fill);
    transition: background var(--sg-ease);
}

.sg-color:hover .sg-color-well { background: var(--sg-field-fill-hover); }
.sg-color-off:hover .sg-color-well { background: var(--sg-field-fill); }

.sg-color-swatch {
    position: relative;
    width: 27px;
    height: 27px;
    flex-shrink: 0;
    border-radius: var(--sg-radius-control-sm);
    box-shadow: inset 0 0 0 1px var(--sg-lines-inputs);
}

/* The native input covers the swatch invisibly: the platform picker opens where it is clicked,
   and the swatch is what shows. */
.sg-color-swatch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    border: 0;
    padding: 0;
    cursor: inherit;
}

.sg-color-swatch:has(input:focus-visible) { outline: 2px solid var(--sg-primary); outline-offset: 2px; }

.sg-color-hex {
    font-size: var(--sg-fs-secondary);
    font-variant-numeric: tabular-nums;
    color: var(--sg-text-primary);
}

/* -------------------------------------------------------- tags and badges --- */

/* Strong tag: kind / status. */
.sg-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--sg-fs-tag);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .5px;
    padding: 1px 6px;
    border-radius: var(--sg-radius-tag);
    color: var(--sg-primary);
    background: color-mix(in srgb, var(--sg-primary) 15%, transparent);
    flex-shrink: 0;
}

.sg-tag-secondary { color: var(--sg-secondary); background: color-mix(in srgb, var(--sg-secondary) 15%, transparent); }
.sg-tag-tertiary { color: var(--sg-tertiary); background: color-mix(in srgb, var(--sg-tertiary) 15%, transparent); }
.sg-tag-success { color: var(--sg-success); background: color-mix(in srgb, var(--sg-success) 15%, transparent); }
.sg-tag-warning { color: var(--sg-warning); background: color-mix(in srgb, var(--sg-warning) 15%, transparent); }
.sg-tag-error { color: var(--sg-error); background: color-mix(in srgb, var(--sg-error) 15%, transparent); }
.sg-tag-info { color: var(--sg-info); background: color-mix(in srgb, var(--sg-info) 15%, transparent); }

/* Neutral pill for metadata (row count, source, version). */
.sg-badges {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 5px;
}

.sg-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 7px;
    border-radius: var(--sg-radius-pill);
    border: var(--sg-border);
    background: var(--sg-background-alt);
    font-size: 11px;
    line-height: 1.6;
    color: var(--sg-text-secondary);
    white-space: nowrap;
}

.sg-badge-info { border-color: color-mix(in srgb, var(--sg-info) 45%, transparent); background: color-mix(in srgb, var(--sg-info) var(--sg-tint-bg-soft), transparent); color: var(--sg-info); }
.sg-badge-tertiary { border-color: color-mix(in srgb, var(--sg-tertiary) 45%, transparent); background: color-mix(in srgb, var(--sg-tertiary) var(--sg-tint-bg-soft), transparent); color: var(--sg-tertiary); }
.sg-badge-success { border-color: color-mix(in srgb, var(--sg-success) 45%, transparent); background: color-mix(in srgb, var(--sg-success) var(--sg-tint-bg-soft), transparent); color: var(--sg-success); }
.sg-badge-warning { border-color: color-mix(in srgb, var(--sg-warning) 45%, transparent); background: color-mix(in srgb, var(--sg-warning) var(--sg-tint-bg-soft), transparent); color: var(--sg-warning); }
.sg-badge-error { border-color: color-mix(in srgb, var(--sg-error) 45%, transparent); background: color-mix(in srgb, var(--sg-error) var(--sg-tint-bg-soft), transparent); color: var(--sg-error); }

/* -------------------------------------------------------------- stat tiles --- */

.sg-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sg-gap-page);
}

/* Dialogs use an even flex row instead of the grid. */
.sg-stats-row {
    display: flex;
    gap: var(--sg-gap-page);
}

.sg-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 11px 14px;
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-rest);
}

.sg-stats-row .sg-stat {
    flex: 1;
    align-items: center;
    padding: 10px 8px;
    border-radius: var(--sg-radius-well);
    background: var(--sg-background);
    box-shadow: none;
}

.sg-stat-top {
    display: flex;
    align-items: center;
    gap: 7px;
    color: var(--sg-text-secondary);
}

.sg-stat-label {
    font-size: var(--sg-fs-label);
    text-transform: uppercase;
    letter-spacing: .6px;
    color: var(--sg-text-secondary);
}

.sg-stat-value {
    font-size: var(--sg-fs-stat);
    font-weight: 700;
    line-height: 1.15;
    color: var(--sg-text-primary);
    font-variant-numeric: tabular-nums;
}

.sg-stats-row .sg-stat-value {
    font-size: 22px;
    line-height: 1.1;
}

.sg-stat-primary .sg-stat-top, .sg-stat-primary .sg-stat-value { color: var(--sg-primary); }
.sg-stat-secondary .sg-stat-top, .sg-stat-secondary .sg-stat-value { color: var(--sg-secondary); }
.sg-stat-tertiary .sg-stat-top, .sg-stat-tertiary .sg-stat-value { color: var(--sg-tertiary); }
.sg-stat-info .sg-stat-top, .sg-stat-info .sg-stat-value { color: var(--sg-info); }
.sg-stat-success .sg-stat-top, .sg-stat-success .sg-stat-value { color: var(--sg-success); }
.sg-stat-warning .sg-stat-top, .sg-stat-warning .sg-stat-value { color: var(--sg-warning); }
.sg-stat-error .sg-stat-top, .sg-stat-error .sg-stat-value { color: var(--sg-error); }

/* ------------------------------------------------------------------ motion --- */

/* Read by assistive technology, invisible on screen. The animated components render their text
   twice — whole here, split and aria-hidden for the eye. */
.sg-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Rolling number. A rolling digit is an inline-block holding the digit itself as its face, which
   gives the column its width and — the reason it is there — its baseline, so the number sits on
   the line like ordinary text. The 0-9 strip is absolutely positioned over it and clipped with
   clip-path rather than overflow: overflow:hidden would move the inline-block's baseline to its
   bottom edge and drop every digit below the surrounding text.

   The face is hidden while the strip rolls and the two swap the moment it lands, so a digit at
   rest is plain text. Left on the strip, it would sit at a fractional offset that differs per
   digit, and the rasteriser rounds some of them a pixel below their neighbours. */
.sg-roll {
    --sg-roll-duration: .9s;
    --sg-roll-curve: cubic-bezier(.22, 1, .36, 1);
    position: relative;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    /* A number reads left to right inside RTL text too. Without the isolate, the bidi algorithm
       treats each inline-block digit as a neutral and an RTL paragraph reverses them. */
    direction: ltr;
    unicode-bidi: isolate;
}

/* overflow: clip hides the 0-9 strip without counting it as scrollable overflow, so an
   overflow: auto container around the number shows no phantom scrollbar. Unlike overflow: hidden
   it doesn't make the digit a scroll container, so its baseline stays on the text's. */
.sg-roll-digit {
    position: relative;
    display: inline-block;
    overflow: clip;
}

.sg-roll-face {
    visibility: hidden;
    animation: sg-roll-show 0s var(--sg-roll-duration) forwards;
}

/* SgAnimatedNumber re-creates a column for every roll, so the animation always starts afresh
   from --sg-roll-from. A keyframe with no `to` runs towards the element's own transform. */
.sg-roll-strip {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* The strip is ten lines tall, so 10% of it is exactly one digit. */
    transform: translateY(calc(var(--sg-roll-d) * -10%));
    animation:
        sg-roll var(--sg-roll-duration) var(--sg-roll-curve) both,
        sg-roll-hide 0s var(--sg-roll-duration) forwards;
}

@keyframes sg-roll {
    from { transform: translateY(calc(var(--sg-roll-from) * -10%)); }
}

@keyframes sg-roll-show {
    to { visibility: visible; }
}

@keyframes sg-roll-hide {
    to { visibility: hidden; }
}

/* Staggered text entrance. --sg-i is set per segment by SgAnimatedText. */
.sg-type {
    --sg-type-duration: .55s;
    --sg-type-stagger: 45ms;
    --sg-type-delay: 0ms;
    --sg-type-curve: cubic-bezier(.22, 1, .36, 1);
}

/* Words never break internally, which in Character mode is what keeps a word's letters together. */
.sg-type-word {
    white-space: nowrap;
}

.sg-type-seg {
    display: inline-block;
    animation: sg-type-rise var(--sg-type-duration) var(--sg-type-curve) both;
    animation-delay: calc(var(--sg-type-delay) + var(--sg-i, 0) * var(--sg-type-stagger));
}

.sg-type-fade .sg-type-seg { animation-name: sg-type-fade; }
.sg-type-blur .sg-type-seg { animation-name: sg-type-blur; }

@keyframes sg-type-rise {
    from { opacity: 0; transform: translateY(.45em); }
}

@keyframes sg-type-fade {
    from { opacity: 0; }
}

@keyframes sg-type-blur {
    from { opacity: 0; filter: blur(8px); }
}

/* Reduced motion gets the final state immediately: no roll, no entrance. */
@media (prefers-reduced-motion: reduce) {
    .sg-type-seg {
        animation: none;
    }

    .sg-roll-face {
        visibility: visible;
        animation: none;
    }

    .sg-roll-strip {
        visibility: hidden;
        animation: none;
    }
}

/* ------------------------------------------------------ empty / loading --- */

.sg-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-align: center;
    padding: var(--sg-pad-empty);
    border-radius: var(--sg-radius-card);
    border: 1px dashed var(--sg-lines);
    background: var(--sg-surface);
    color: var(--sg-text-secondary);
}

.sg-empty-icon {
    display: inline-grid;
    place-items: center;
    width: 52px;
    height: 52px;
    border-radius: 14px;
    color: var(--sg-primary);
    background: color-mix(in srgb, var(--sg-primary) var(--sg-tint-bg-soft), transparent);
}

.sg-empty-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--sg-text-primary);
}

.sg-empty-hint {
    font-size: var(--sg-fs-secondary);
    max-width: 420px;
    line-height: 1.5;
}

.sg-empty-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 4px;
}

.sg-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 48px;
    color: var(--sg-text-secondary);
    font-size: var(--sg-fs-secondary);
}

/* Loading over existing content: veils without hiding, so context is kept. */
.sg-overlay {
    position: absolute;
    inset: 0;
    z-index: 500;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-align: center;
    padding: 24px;
    color: var(--sg-text-secondary);
    background: color-mix(in srgb, var(--sg-surface) 72%, transparent);
    backdrop-filter: blur(2px);
    pointer-events: none;
}

.sg-overlay-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--sg-text-primary);
}

/* ---------------------------------------------------------------- dialogs --- */

.sg-dlg-head {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

.sg-dlg-head-icon {
    display: inline-grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: var(--sg-radius-chip);
    background: color-mix(in srgb, var(--sg-primary) var(--sg-tint-bg), transparent);
    color: var(--sg-primary);
    flex-shrink: 0;
}

.sg-dlg-head-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.sg-dlg-head-title {
    font-size: var(--sg-fs-dialog-title);
    font-weight: 600;
    letter-spacing: .2px;
    line-height: 1.25;
    color: var(--sg-text-primary);
}

.sg-dlg-head-sub {
    font-size: var(--sg-fs-meta);
    color: var(--sg-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-dlg-body {
    min-width: min(460px, 90vw);
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.sg-dlg-body-wide {
    min-width: min(560px, 92vw);
}

/* Read-only summary of the record being acted on. */
.sg-summary {
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-background);
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.sg-summary-top {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.sg-summary-name {
    font-size: var(--sg-fs-title);
    font-weight: 600;
    color: var(--sg-text-primary);
}

.sg-summary-line {
    font-size: var(--sg-fs-secondary);
    color: var(--sg-text-secondary);
}

.sg-section {
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    padding: 8px 10px;
}

.sg-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--sg-fs-label);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--sg-text-secondary);
}

.sg-hint {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: var(--sg-fs-secondary);
    line-height: 1.5;
    color: var(--sg-text-secondary);
}

.sg-hint > svg,
.sg-hint > .mud-icon-root {
    flex-shrink: 0;
    margin-top: 1px;
}

/* A hint that is a refusal: the same shape, in the colour of something gone wrong.
 *
 * Here rather than in each page's scoped stylesheet, because the place it is needed most is
 * inside a dialog -- and a dialog is rendered by the dialog provider at the root of the layout,
 * not as a descendant of the page that opened it. A page's scoped `::deep` never reaches it, so
 * an error message styled that way is styled by nothing at all and turns up in body text. */
.sg-hint-error {
    color: var(--sg-error);
}

/* Identity block: avatar + name + subtitle. */
.sg-user {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.sg-user-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.sg-user-name {
    font-size: 15px;
    font-weight: 600;
    color: var(--sg-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-user-sub {
    font-size: var(--sg-fs-meta);
    color: var(--sg-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --------------------------------------------------------------- pick list --- */

.sg-pick-list {
    display: flex;
    flex-direction: column;
    gap: 3px;
    max-height: 42vh;
    overflow-y: auto;
    padding: 4px;
    border-radius: var(--sg-radius-well);
    border: var(--sg-border);
    background: var(--sg-surface);
}

.sg-pick-row {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    padding: 8px 10px;
    border: none;
    border-radius: var(--sg-radius-control);
    background: transparent;
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: background-color var(--sg-ease-fast);
}

.sg-pick-row:hover {
    background: var(--sg-hover);
}

.sg-pick-row-on {
    background: color-mix(in srgb, var(--sg-primary) var(--sg-tint-bg-soft), transparent);
}

.sg-pick-row-done,
.sg-pick-row-done:hover {
    cursor: default;
    opacity: .7;
    background: transparent;
}

.sg-pick-check {
    display: inline-grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: 6px;
    border: 1.5px solid var(--sg-lines);
    color: transparent;
    flex-shrink: 0;
    transition: background-color var(--sg-ease-fast), border-color var(--sg-ease-fast), color var(--sg-ease-fast);
}

.sg-pick-row-on .sg-pick-check {
    background: var(--sg-primary);
    border-color: var(--sg-primary);
    color: var(--sg-primary-text);
}

.sg-pick-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1;
}

.sg-pick-name {
    font-size: var(--sg-fs-body);
    color: var(--sg-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-pick-meta {
    font-size: 12px;
    color: var(--sg-text-secondary);
}

.sg-pick-empty {
    padding: 28px 12px;
    text-align: center;
    font-size: var(--sg-fs-secondary);
    color: var(--sg-text-secondary);
}

/* ---------------------------------------------------- long-running progress --- */

.sg-progress-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding: 22px 18px;
    border-radius: 14px;
    border: var(--sg-border);
    background: var(--sg-background);
}

.sg-progress-pct {
    font-size: 30px;
    font-weight: 700;
    line-height: 1;
    color: var(--sg-text-primary);
    font-variant-numeric: tabular-nums;
}

.sg-progress-stage {
    font-size: var(--sg-fs-secondary);
    color: var(--sg-text-secondary);
}

/* A -> B transfer illustration (reassign, handover, migrate). */
.sg-flow {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
}

.sg-flow-arrow {
    color: var(--sg-primary);
}

/* ------------------------------------------------------------- scrollbars --- */

.sg-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--sg-lines-inputs) transparent;
}

.sg-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.sg-scroll::-webkit-scrollbar-thumb { border-radius: var(--sg-radius-pill); background: var(--sg-lines-inputs); }
.sg-scroll::-webkit-scrollbar-track { background: transparent; }

/* ------------------------------------------------------------------- keys --- */

/* Keycap, for shortcut hints in palettes and help sheets. */
.sg-kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: 5px;
    border: var(--sg-border);
    border-bottom-width: 2px;
    background: var(--sg-background);
    color: var(--sg-text-secondary);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
}

/* -------------------------------------------------------------------- rtl --- */

/* Applied per-run, not per-app: a mixed-language feed needs direction decided by the text,
   which is what SgTextDirection.Of does. The host owns the @font-face; --sg-font-rtl is
   only the slot it fills, so an app without an RTL face simply inherits. */
.sg-rtl {
    direction: rtl;
    text-align: right;
    font-family: var(--sg-font-rtl);
}

/* ============================================================================
   FORM CONTROLS

   The one place this stylesheet reaches into MudBlazor's own DOM.

   The design language otherwise governs page chrome and leaves the framework's inputs alone,
   because dressing someone else's widget means depending on its internals. That trade stops
   being worth it for text fields and selects: Material draws them at 16px in a 56px-tall box
   with a 4px radius, which sits in a dialog next to a 12px-radius summary card and an 8px
   icon button and looks like it came from a different product. The buttons that stand beside
   those fields, and the popovers they open, are dressed here for the same reason.

   Only the OUTLINED variant is dressed. Filled and underlined are three different layouts for
   one look; picking one and dressing it makes an app consistent by construction, and a project
   that wants this should set Variant="Variant.Outlined" as its house default.

   The mechanism, since it is not obvious: the outline is a <fieldset>, and the floating label
   is cut out of it by a <legend> whose width is driven by its own font-size. Change the input's
   size without changing the legend's and the notch stops fitting the label. Every size below is
   derived from --sg-fs-body for that reason.
   ========================================================================= */

.mud-input.mud-input-outlined .mud-input-outlined-border {
    border-radius: var(--sg-radius-well);
    /* Soft, because the fill below is what says "you can type here". The stronger --sg-lines-inputs
       was needed while the field was a bare outline; with a well it would only draw a box. */
    border-color: var(--sg-lines);
    transition: border-color var(--sg-ease), background-color var(--sg-ease);
}

.mud-input.mud-input-outlined:hover:not(.mud-disabled) .mud-input-outlined-border {
    border-color: color-mix(in srgb, var(--sg-primary) var(--sg-tint-border), var(--sg-lines));
}

/* Focus is the one place a 2px border is right: it has to be unmistakable, and a colour change
   alone is not enough for anyone who cannot distinguish it. */
.mud-input.mud-input-outlined.mud-focused .mud-input-outlined-border {
    border-width: 2px;
    border-color: var(--sg-primary);
}

/* The class is mud-input-error, not mud-error. Listed after the focus rule on purpose: a field
   that is both focused and invalid should read as invalid, since the focus ring is not the thing
   the person needs to notice. */
.mud-input.mud-input-outlined.mud-input-error .mud-input-outlined-border,
.mud-input.mud-input-outlined.mud-input-error.mud-focused .mud-input-outlined-border {
    border-color: var(--sg-error);
}

/* ---- density and fill: 56px tall at 16px becomes 40px at 14px, on a soft well ------------- */

/* The field is a filled well rather than an outlined box: a tinted background, a hairline edge
   and a leading icon. It reads as a place to type without drawing a rectangle around itself,
   which matters now that the neutral lines elsewhere are near-invisible — an outlined field
   among hairline cards is the loudest thing on the page. */
.mud-input.mud-input-outlined .mud-input-outlined-border {
    background: var(--sg-field-fill);
}

.mud-input.mud-input-outlined:hover:not(.mud-disabled) .mud-input-outlined-border {
    background: var(--sg-field-fill-hover);
}

/* The whole horizontal gutter lives on the wrapper, and the input's own left/right padding is
   zeroed. MudBlazor splits it between the two and then changes the split again when an
   adornment is present, which is why the icon and the text below it did not line up: the icon
   was inset by the wrapper's 14px plus its own margin, the text by the input's padding alone. */
.mud-input.mud-input-outlined,
.mud-input.mud-input-outlined.mud-input-adorned-start,
.mud-input.mud-input-outlined.mud-input-adorned-end {
    padding-left: 12px;
    padding-right: 12px;
}

.mud-input-root-outlined,
.mud-input.mud-input-outlined .mud-input-slot {
    font-size: var(--sg-fs-body);
    padding-top: 10px;
    padding-bottom: 10px;
    padding-left: 0;
    padding-right: 0;
}

/* The same padding again, for Margin.Dense. MudBlazor's dense rule carries an element and three
   classes, so it outweighs the one above and puts 10.5px back — which made a dense field a pixel
   TALLER than the default field it is supposed to be denser than, and left the row's button
   matching one of the two. Density here comes from the type scale, not from a margin prop, so
   both land on the same --sg-field-h. Specificity matched and won on source order, as with the
   label rules below. */
.mud-input > input.mud-input-root-outlined.mud-input-root-margin-dense,
.mud-input > div.mud-input-slot.mud-input-root-outlined.mud-input-root-margin-dense {
    padding-top: 10px;
    padding-bottom: 10px;
}

/* The zeroing above, again for an ADORNED input. MudBlazor's adorned rules carry an element and
   three classes, so they outweighed it and put 14px back on the side away from the adornment —
   which pushed the text of every end-adorned control (select, autocomplete, date and time picker,
   a field with a unit) 26px in, past the 12px where its own floating label sits and where every
   unadorned field in the same column starts. Selectors are MudBlazor's own, as with the label
   rules below; padding-inline is the logical property MudBlazor sets, so RTL follows. */
.mud-input > input.mud-input-root-outlined.mud-input-root-adorned-start,
.mud-input > input.mud-input-root-outlined.mud-input-root-adorned-end,
.mud-input > textarea.mud-input-root-outlined.mud-input-root-adorned-start,
.mud-input > textarea.mud-input-root-outlined.mud-input-root-adorned-end,
div.mud-input-slot.mud-input-root-outlined.mud-input-root-adorned-start,
div.mud-input-slot.mud-input-root-outlined.mud-input-root-adorned-end {
    padding-inline: 0;
}

/* And for a numeric field with spin buttons, whose rule is heavier still (six classes and an
   element) and adds 14px at the start plus a 24px — 20px dense — reserve for the spinner at the
   end. The start padding goes; the reserve moves to the wrapper, below, where it applies whether
   or not the field has a unit, instead of living in two places that disagreed. */
.mud-input-control.mud-input-number-control.mud-input-showspin .mud-input:not(.mud-input-adorned-end).mud-input-outlined input,
.mud-input-control.mud-input-number-control.mud-input-showspin .mud-input:not(.mud-input-adorned-end).mud-input-outlined input.mud-input-root-margin-dense {
    padding-inline: 0;
}

/* MudBlazor gives a multiline textarea an 18.5px top margin to clear the label floating inside
   its 56px box. Here the label floats onto the border like a single-line field's, so that margin
   is dead space — it made a three-line field 95px tall when its own content needs 77. Zeroed, the
   arithmetic lines up with the single-line field exactly: one row is 19 + 20 padding = 39px. */
.mud-input.mud-input-outlined textarea.mud-input-slot {
    margin-top: 0;
}

/* The resting label sits on the text it labels, so it has to match the input exactly.

   The selector is MudBlazor's own, repeated verbatim rather than simplified: it sets the label's
   size through a three-level child selector, so a plain `.mud-input-label-outlined` loses on
   specificity and the label silently stays at 16px — which then makes the legend notch too narrow
   for it. Match the specificity and win on source order instead. */
.mud-input-control > .mud-input-control-input-container > .mud-input-label-inputcontrol {
    font-size: var(--sg-fs-body);
}

/* Resting position, for an empty unfocused field. MudBlazor centres the label in a 56px box;
   in a 39px one it has to come up. */
.mud-input-label-outlined {
    transform: translate(12px, 11px) scale(1);
}

/* Floated, the label is scaled to 0.75. The legend's font-size has to be the same 0.75 of the
   label or the notch it cuts will not be the width of the text sitting in it. */
.mud-input.mud-input-outlined .mud-input-outlined-border legend {
    font-size: calc(var(--sg-fs-body) * .75);
}

/* Floated position. Again MudBlazor's own selector — the shrink class sits on the input, which is
   the label's preceding sibling, and the rule carries four classes plus an element. Anything
   shorter loses and the label stays where Material put it. */
.mud-shrink ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined,
.mud-input:focus-within ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined {
    transform: translate(12px, -6px) scale(.75);
    max-width: calc(133.333% - 18.6667px);
}

/* ---- adornments, helper text, disabled ------------------------------------------------- */

/* One size for the leading/trailing icon of any form control. MudBlazor's adornment defaults to
   24px, which is large against 14px text and, more to the point, differed from the 17px the select
   trigger used — so a select and a text field in the same column had icons of two sizes. Set here
   rather than inline on each component so there is one number to change.

   Lifted a pixel: centred in the well, the icon's middle lands a pixel or more below the middle
   of the capitals beside it — 14px text centred in its line sits high — so a filter row read as
   icons hanging under their words. `translate`, not `transform`, so a caret's rotation is kept. */
.mud-input-adornment .mud-icon-root,
.sg-select-trigger > .mud-icon-root,
.sg-search > .mud-icon-root {
    font-size: 18px;
    translate: 0 -1px;
}

/* Only the gap to the text. The inset comes from the wrapper above, so the icon and the text
   share one gutter and cannot drift apart. */
.mud-input.mud-input-outlined .mud-input-adornment-start {
    margin: 0 8px 0 0;
    color: var(--sg-action);
}

.mud-input.mud-input-outlined .mud-input-adornment-end {
    margin: 0 0 0 8px;
    color: var(--sg-action);
}

/* The select's caret is an end adornment that MudBlazor gives its own margin. Same treatment,
   or it sits a couple of pixels off the gutter every other control lines up to.

   End only: MudBlazor puts `mud-select-input` on a select's START adornment too, so matching on
   that class alone turned a leading icon's margin around — pushed off the gutter and touching
   the text. */
.mud-select .mud-input.mud-input-outlined .mud-input-adornment-end,
.mud-input.mud-input-outlined .mud-input-adornment-end.mud-select-input {
    margin: 0 0 0 8px;
}

/* Adornment TEXT — a unit, a currency, a file extension. MudBlazor renders it as a body1 paragraph,
   16px on a 24px line, so "deg" came out larger than the 14px value it qualifies and sat on a
   different baseline. It takes the input's size and line instead, and the adornment's colour. */
.mud-input.mud-input-outlined .mud-input-adornment-text {
    font-size: var(--sg-fs-body);
    line-height: inherit;
    letter-spacing: inherit;
}

/* ---- numeric spin buttons ---------------------------------------------------------------- */

/* Material sizes the spinner for its own 56px field: two 24px chevrons stacked in a column
   pinned to the field's very edge. In a 39px well the chevrons spilled over the top and bottom
   border, touched the right-hand edge, and — because the reset above takes the adornment's end
   margin away with everything else — a unit such as "deg" was drawn underneath them.

   Here the spinner is a small column INSIDE the well: 20px wide, inset 4px top and bottom and 6px
   from the end, which puts the chevron glyphs on the same 12px gutter as every other end icon.
   The field reserves its width plus a matching gap on the wrapper, so the value and the unit stop
   short of it at any density. Logical insets, so RTL mirrors it along with MudBlazor's own rule. */
.mud-input-control.mud-input-number-control.mud-input-showspin .mud-input.mud-input-outlined {
    padding-inline-end: 32px;
}

.mud-input-control.mud-input-number-control .mud-input.mud-input-outlined .mud-input-numeric-spin {
    top: 4px;
    bottom: 4px;
    inset-inline-end: 6px;
    width: 20px;
}

.mud-input-control.mud-input-number-control .mud-input.mud-input-outlined .mud-input-numeric-spin button {
    flex: 1 1 0;
    min-height: 0;
    padding: 0;
    border-radius: var(--sg-radius-tag);
    color: var(--sg-action);
    transition: background-color var(--sg-ease-fast), color var(--sg-ease-fast), transform var(--sg-ease-fast);
}

.mud-input-control.mud-input-number-control .mud-input.mud-input-outlined .mud-input-numeric-spin button:hover:not(:disabled) {
    background: var(--sg-hover);
    color: var(--sg-text-primary);
}

.mud-input-control.mud-input-number-control .mud-input.mud-input-outlined .mud-input-numeric-spin button:active:not(:disabled) {
    transform: scale(var(--sg-press-scale));
}

.mud-input-control.mud-input-number-control .mud-input.mud-input-outlined .mud-input-numeric-spin .mud-icon-root {
    font-size: 16px;
}

.mud-input-helper-text {
    font-size: var(--sg-fs-label);
    color: var(--sg-text-secondary);
    margin-top: 4px;
}

.mud-input-helper-text.mud-input-error {
    color: var(--sg-error);
}

.mud-input.mud-input-outlined.mud-disabled .mud-input-outlined-border {
    border-color: var(--sg-lines);
}

.mud-input.mud-input-outlined.mud-disabled {
    opacity: .55;
}

/* ---- a field without a label, beside fields with one -------------------------------------- */

/* MudBlazor decides a control's top margin by whether it has a label, because a floating label
   needs room above the box to float into: 8px with one, 0 without (4px dense, and 16px against 8px
   at Margin.Normal). Stacked, that is harmless. Side by side in a grid it is not: a field with no
   label sits higher than its labelled neighbours, and the row of boxes visibly steps.

   So a field without a label takes the headroom of a labelled one — but only when a labelled one
   is actually its neighbour: a sibling in the same container, looking through MudBlazor's own
   select and picker wrappers and through a MudGrid item. Deciding it globally would add 12px to
   every toolbar search box and every table-cell editor, where no label is ever going to float and
   the caller owns the spacing.

   The container half is in :where(), so the whole selector weighs what MudBlazor's margin rules
   weigh and wins on source order. A form row is excluded outright: it zeroes every margin in the
   row on purpose, and the label floats into its padding instead. */
.mud-input-control {
    --sg-label-room-top: 8px;
    --sg-label-room-bottom: 4px;
}

.mud-input-control.mud-input-control-margin-normal {
    --sg-label-room-top: 16px;
    --sg-label-room-bottom: 8px;
}

:where(:not(.sg-form-row):has(> .mud-input-outlined-with-label, > .sg-select-labeled,
                              > :is(.mud-grid-item, .mud-select, .mud-picker) > .mud-input-outlined-with-label,
                              > .mud-grid-item > :is(.mud-select, .mud-picker) > .mud-input-outlined-with-label))
    > .mud-input-control:not(.mud-input-outlined-with-label, .mud-input-control-boolean-input),
:where(:not(.sg-form-row):has(> .mud-input-outlined-with-label, > .sg-select-labeled,
                              > :is(.mud-grid-item, .mud-select, .mud-picker) > .mud-input-outlined-with-label,
                              > .mud-grid-item > :is(.mud-select, .mud-picker) > .mud-input-outlined-with-label))
    > :where(.mud-grid-item, .mud-select, .mud-picker)
    > .mud-input-control:not(.mud-input-outlined-with-label, .mud-input-control-boolean-input),
:where(:not(.sg-form-row):has(> .mud-input-outlined-with-label, > .sg-select-labeled,
                              > :is(.mud-grid-item, .mud-select, .mud-picker) > .mud-input-outlined-with-label,
                              > .mud-grid-item > :is(.mud-select, .mud-picker) > .mud-input-outlined-with-label))
    > :where(.mud-grid-item) > :where(.mud-select, .mud-picker)
    > .mud-input-control:not(.mud-input-outlined-with-label, .mud-input-control-boolean-input) {
    margin-top: var(--sg-label-room-top);
    margin-bottom: var(--sg-label-room-bottom);
}

/* ---- an inline row of fields ------------------------------------------------------------- */

/* A short form laid out across the page rather than down it: the "new project" row above a list,
   the assign row at the top of a panel. Fields, then the button that submits them.

   It is here, in the framework, because every page that had one wrote the same four declarations
   into its own scoped stylesheet — and wrote them with `align-items: flex-end`, which is the
   trap this class exists to close. Helper text lives INSIDE the control, under the input, so a
   field that has one is ~20px taller than a field that does not. Bottom-aligning the row lines
   up the bottom of one field's helper text with the bottom of another field's input box, and
   pushes every field without helper text visibly down the page. The same applies to an error
   message, which is worse: the row jumps the moment something fails validation.

   Aligning to the top instead is what makes the inputs line up, because the input box is the
   first thing in every control and they are all one height. Helper text then hangs below its own
   field, where it belongs, and nothing moves when it appears. */
.sg-form-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 12px;
    padding: 12px;
}

/* The controls are `flex: 1 1 auto`, so their width is decided by their content — which includes
   the helper text. One long hint and that field takes the row, the select beside it gets 90px,
   and the button after them wraps to a line of its own. A flat basis makes the fields share the
   row evenly and wrap a whole field at a time. */
.sg-form-row > * {
    flex: 1 1 200px;
    min-width: 0;
}

/* MudBlazor's own margins on a control, matched at their specificity and zeroed: 8px above one
   with a floating label, 4px above a dense one without, 8px above a normal one — three different
   top offsets for controls that all have to start on the same line, and none of them applied to
   a button. The label still needs somewhere to float; it floats into the row's padding, which
   every child shares.

   The selectors are MudBlazor's own, repeated rather than shortened, for the reason given at the
   label rules above: anything with fewer classes loses and the margin silently stays. */
.sg-form-row .mud-input-control.mud-input-outlined-with-label,
.sg-form-row .mud-input-control.mud-input-control-margin-dense,
.sg-form-row .mud-input-control.mud-input-control-margin-normal {
    margin-top: 0;
    margin-bottom: 0;
}

/* The button ends the row, and is the one child that should stay exactly as wide as its label
   and exactly as tall as the field boxes it sits beside. */
.sg-form-row > .mud-button-root {
    flex: 0 0 auto;
    height: var(--sg-field-h);
}

/* ---- select and picker popovers ---------------------------------------------------------- */

/* Every dropdown gets the same surface as SgSelect's panel — a plain select, a multi-select, an
   autocomplete and a menu all drop the same thing. MudBlazor gives them a 4px radius and
   Material's three-layer elevation ramp, which is the single most obvious place the framework
   used to stop and Material started.

   Applied to the popover broadly rather than to selects alone, deliberately: a menu that did not
   match the select beside it would just move the inconsistency somewhere less obvious. */
.mud-popover.mud-popover-open {
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-pop);
    overflow: hidden;
}

.mud-popover.mud-popover-open .mud-list {
    padding: 6px;
}

/* Matches .sg-pick-row, so a row in a native dropdown and a row in SgSelect's panel are the same
   object. MudBlazor's 8px 16px is Material's list metric, not this one. */
.mud-popover .mud-list-item {
    padding: 8px 10px;
    min-height: 0;
}

.mud-select-input .mud-input-adornment {
    color: var(--sg-action);
}

/* A chosen option is marked the same way a pick-list row is, so the two read as one idea. */
.mud-popover .mud-list-item.mud-selected-item {
    background: var(--sg-primary-soft);
    color: var(--sg-primary);
}

.mud-popover .mud-list-item {
    border-radius: var(--sg-radius-control);
    transition: background-color var(--sg-ease-fast);
}

/* ---- tooltips and rich popovers ---------------------------------------------------------- */

/* A tooltip is the same element as its popover, so the surface above already reaches it. That
   gets it most of the way, and leaves two things wrong: `.mud-tooltip-default` paints Material's
   dark bubble over that surface — light-on-dark text on a light card — and the popover's
   `overflow: hidden` clips the arrow, which is an ::after positioned outside the box. Both are
   corrected here rather than per application, so a tooltip is a small surface card with ordinary
   text on it, like every other floating thing in the language. */
.mud-tooltip.mud-tooltip-default {
    color: var(--sg-text-primary);
    background-color: var(--sg-surface);
}

.mud-tooltip.mud-tooltip-default.mud-tooltip-arrow::after {
    border-top-color: var(--sg-surface);
}

.mud-tooltip.mud-popover,
.mud-tooltip.mud-popover-open {
    overflow: visible;
}

/* Rich popover: a tooltip carrying a card instead of a line of text — a person card, a preview,
   a summary of the row under the cursor. It goes on MudTooltip's `Class`, which lands on the
   bubble itself:

       <MudTooltip Class="sg-pop" Arrow>

   All it has to undo is what the Material bubble does to a card — 4px 8px of padding, centred
   text, 12px type. The surface, border, radius and shadow are already the popover's.

   Open one on click, not hover, wherever it holds anything actionable. A hover tooltip dismisses
   as soon as the pointer leaves its trigger, so a link or a button inside one can never be
   reached; hide those slots in hover mode rather than shipping controls nobody can press. */
.sg-pop.mud-tooltip {
    padding: 0;
    text-align: start;
    font-size: var(--sg-fs-body);
    font-weight: 400;
    line-height: 1.45;
}

/* The width cap goes on the content wrapper MudBlazor puts around TooltipContent, not on the
   bubble: the popover writes `max-width: none` into the bubble's style attribute, and inline wins
   over anything here short of !important, which nothing in this framework uses. Capping the only
   child is the same constraint by another route, because the bubble sizes to it. */
.sg-pop.mud-tooltip > * {
    max-width: min(320px, calc(100vw - 24px));
}

.sg-pop-head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 12px 10px;
}

/* Pulled back into the padding, so a close button reads as sitting in the corner rather than as
   a third column of content. */
.sg-pop-head > .sg-icon-btn {
    margin: -5px -5px 0 auto;
}

.sg-pop-body {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 0 12px 12px;
}

.sg-pop-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: var(--sg-fs-meta);
    color: var(--sg-text-secondary);
}

.sg-pop-row > .mud-icon-root {
    flex-shrink: 0;
    color: var(--sg-action);
}

/* min-width is what actually lets these ellipsis: a flex item refuses to shrink below its content
   without it, and the row would push the card wider instead of truncating. */
.sg-pop-row-text,
.sg-pop-link {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-pop-link {
    color: inherit;
    text-decoration: none;
}

.sg-pop-link:hover {
    color: var(--sg-primary);
    text-decoration: underline;
}

.sg-pop-foot {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    border-top: var(--sg-border);
}

/* ---- popover and dialog motion ------------------------------------------------------------ */

/* Dropdowns and dialogs arrive the way papers do — a fade with a short move — scaled to what they
   answer. A dropdown answers a click, so it is quicker and travels less than a card settling in;
   a dialog interrupts the page, so it rises a little further, from just under full size.

   A popover leaves its trigger in the direction it opens. MudBlazor names the popover's own
   attachment point in a class, which says which way that is: a popover pinned by its top edge
   hangs below the anchor and drops, one pinned by its bottom edge sits above and rises, the
   centre-left and centre-right placements travel sideways. MudBlazor swaps those classes when a
   dropdown flips to fit the viewport, and the offset follows the swap.

   Individual translate and scale properties rather than transform, on both: nothing that
   positions a popover or a dialog with a transform — MudBlazor, a consumer, a drag handler — is
   overridden while they play. Fill mode is backwards for the same reason as the paper rise. The
   dialog replaces Material's zoom from half size; the scrim keeps its fade on the same clock. */
@keyframes sg-popover-in {
    from {
        opacity: 0;
        translate: var(--sg-popover-from-x, 0px) var(--sg-popover-from-y, 0px);
        scale: .98;
    }

    to {
        opacity: 1;
        translate: none;
        scale: none;
    }
}

/* No end frame: it interpolates into the dialog's own values, which include the scale: 1 below. */
@keyframes sg-dialog-in {
    from {
        opacity: 0;
        translate: 0 var(--sg-dialog-distance);
        scale: .98;
    }
}

.mud-popover {
    --sg-popover-from-y: calc(var(--sg-popover-distance) * -1);
    transform-origin: top center;
}

.mud-popover:is(.mud-popover-bottom-left, .mud-popover-bottom-center, .mud-popover-bottom-right) {
    --sg-popover-from-y: var(--sg-popover-distance);
    transform-origin: bottom center;
}

.mud-popover:is(.mud-popover-center-left, .mud-popover-center-right, .mud-popover-center-center) {
    --sg-popover-from-y: 0px;
    transform-origin: center;
}

.mud-popover.mud-popover-center-left { --sg-popover-from-x: calc(var(--sg-popover-distance) * -1); }
.mud-popover.mud-popover-center-right { --sg-popover-from-x: var(--sg-popover-distance); }

.mud-popover.mud-popover-open {
    animation: sg-popover-in var(--sg-popover-duration) ease-out backwards;
}

/* A tooltip moves half as far: it follows the pointer rather than a click, and one that swept in
   every time the pointer crossed a row of icons would be the busiest thing on the page. */
.mud-popover.mud-tooltip {
    --sg-popover-distance: 2px;
}

/* scale: 1 is load-bearing, not a no-op. MudBlazor's own open animation ran with fill mode both and
   so left transform: scale(1) on every dialog for good — and a transform makes an element the
   containing block for its absolute and fixed descendants. The dialog's close button is absolutely
   positioned against exactly that: without it, the cross resolved against the full-viewport dialog
   container and jumped to the corner of the window the moment the rise finished. An identity scale
   keeps the containing block MudBlazor's dialogs have always had, for everything positioned inside
   one, without holding the animation's end frame over the dialog's own values. */
.mud-dialog {
    /* MudBlazor's own default is Material's 4px, which sat a dialog next to a 12px card, panel and
       popover and read as a different product's chrome wearing this one's colours. */
    border-radius: var(--sg-radius-card);
    scale: 1;
    animation: sg-dialog-in var(--sg-dialog-duration) cubic-bezier(.2, .8, .2, 1) backwards;
}

.mud-overlay-dialog .mud-overlay-scrim {
    animation: sg-fade var(--sg-dialog-duration) ease-out;
}

/* ---- dialog drag & resize ------------------------------------------------------------------ */

/* SgDialogHead.Draggable and SgDialogBody.Resizable wire these up through SegalInterop. Both are
   direct manipulation -- the paper has to stay glued to the pointer -- so neither transitions;
   see the JS module for why. They read `.mud-dialog` as their coordinate space rather than owning
   one of their own: it already has scale: 1 above, which per the comment there makes it the
   containing block its close button is positioned against, so this needs nothing extra to make it
   one for the resize handle too. */

.sg-dlg-head-drag {
    cursor: move;
    /* Without it, a touch drag on the header also tries to scroll or refresh the page underneath. */
    touch-action: none;
}

/* Selection creeping across the header's title while dragging fast is a papercut this removes at
   the source, on the whole paper rather than just the handle, since a fast drag outruns the
   pointer and the browser keeps selecting whatever it passes over. */
.mud-dialog.sg-dlg-dragging,
.mud-dialog.sg-dlg-resizing {
    user-select: none;
}

/* A corner grip, not a full-edge handle: the dialog already has a definite size from Width /
   MaxWidth, and a strip down every edge would invite a resize nobody asked for on an ordinary
   click near the border. Diagonal hairlines read as "grab here" without a label -- the same shape
   a native window's resize corner draws. */
.sg-dlg-resize-handle {
    position: absolute;
    right: 2px;
    bottom: 2px;
    width: 16px;
    height: 16px;
    cursor: nwse-resize;
    touch-action: none;
    background-image: repeating-linear-gradient(
        135deg,
        var(--sg-lines-inputs) 0 1.5px,
        transparent 1.5px 4px
    );
    clip-path: polygon(100% 0, 100% 100%, 0 100%);
    opacity: .7;
    transition: opacity var(--sg-ease-fast);
}

.sg-dlg-resize-handle:hover,
.mud-dialog.sg-dlg-resizing .sg-dlg-resize-handle {
    opacity: 1;
}

/* SgDialogHead.FullScreenToggle. Pushed to the header's far end, inside whatever room MudBlazor
   already reserves there for its own close button (.mud-dialog-title:has(.mud-button-close) above)
   -- so the two sit side by side instead of stacking, whether or not a close button exists.

   The negative margin corrects a mismatch that is MudBlazor's, not this button's: the close
   button is positioned 8px from the dialog's own top edge, absolutely, ignoring .mud-dialog-title's
   16px padding entirely -- while this one is a normal flex child and inherits that 16px. Without
   the -8px the two buttons read as the same size sitting on two different shelves. */
.sg-dlg-fullscreen-btn {
    margin-inline-start: auto;
    margin-block-start: -8px;
}

/* Unlike a drag or a resize, going full screen is one discrete click, not a gesture the pointer
   has to keep up with, so it is free to move over the same clock the dialog arrived on. Applied
   for the duration of the change only (segal-ui.js clears it on transitionend) so it never dampens
   the direct-manipulation feel of a drag or a resize that follows. */
.mud-dialog.sg-dlg-morphing {
    transition: width var(--sg-dialog-duration) cubic-bezier(.2, .8, .2, 1),
                height var(--sg-dialog-duration) cubic-bezier(.2, .8, .2, 1),
                transform var(--sg-dialog-duration) cubic-bezier(.2, .8, .2, 1);
}

/* The same shape MudBlazor's own DialogOptions.FullScreen draws, reproduced under this library's
   own class rather than toggling that one: a corner square with nothing left outside it to round. */
.mud-dialog.sg-dlg-fullscreen {
    width: 100%;
    height: 100%;
    margin: 0;
    border-radius: 0;
}

/* Nothing to drag or grow into once the paper already fills the viewport. */
.mud-dialog.sg-dlg-fullscreen .sg-dlg-head-drag {
    cursor: default;
}

.mud-dialog.sg-dlg-fullscreen .sg-dlg-resize-handle {
    display: none;
}

/* ---- buttons ------------------------------------------------------------------------------ */

/* MudButton, drawn the way the controls beside it are. Material's button is uppercase and
   letter-spaced, 4px round, lifts every filled button on a three-layer shadow and floods an ink
   ripple out of the click point — and each of those alone was enough to make a toolbar's
   "IMPORT DATA SOURCE" read as a different product from the sentence-case segmented control and
   the flat icon buttons in the same row. Here it is sentence case at the body size, the control
   radius, flat, and it gives under the pointer the way a chip does.

   Heights are taken from what a button stands beside rather than left to padding and a 1.75 line
   height: medium is the icon button's 34px, large is a form field's --sg-field-h, small is the
   compact toolbar action at 30px. min-height, so a label that wraps still fits.

   Icon buttons that merely carry .mud-button (a filled or outlined MudIconButton) are left out of
   the text metrics. MudBlazor's variant and size classes weigh what .mud-button weighs, so these
   win on source order; the size rules below repeat the exclusion to outweigh the base. */
.mud-button:not(.mud-icon-button) {
    min-height: 34px;
    padding: 0 14px;
    border-radius: var(--sg-radius-control);
    font-size: var(--sg-fs-body);
    font-weight: 600;
    line-height: 20px;
    letter-spacing: normal;
    text-transform: none;
    transition: background-color var(--sg-ease), border-color var(--sg-ease), color var(--sg-ease), transform var(--sg-ease-fast);
}

.mud-button-text:not(.mud-icon-button) {
    padding: 0 10px;
}

.mud-button-filled-size-small:not(.mud-icon-button),
.mud-button-outlined-size-small:not(.mud-icon-button),
.mud-button-text-size-small:not(.mud-icon-button) {
    min-height: 30px;
    padding: 0 10px;
    font-size: var(--sg-fs-secondary);
}

.mud-button-text-size-small:not(.mud-icon-button) {
    padding: 0 8px;
}

.mud-button-filled-size-large:not(.mud-icon-button),
.mud-button-outlined-size-large:not(.mud-icon-button) {
    min-height: var(--sg-field-h);
    padding: 0 18px;
}

.mud-button-text-size-large:not(.mud-icon-button) {
    min-height: var(--sg-field-h);
    padding: 0 12px;
}

/* Icons in a button are sized to the text rather than to Material's 20px, and sit closer to it. */
.mud-button-label .mud-button-icon-start,
.mud-button-label .mud-button-icon-start.mud-button-icon-size-small {
    margin-inline-start: -2px;
    margin-inline-end: 6px;
}

.mud-button-label .mud-button-icon-end,
.mud-button-label .mud-button-icon-end.mud-button-icon-size-small {
    margin-inline-start: 6px;
    margin-inline-end: -2px;
}

.mud-button-icon-size-small > *:first-child { font-size: 16px; }
.mud-button-icon-size-medium > *:first-child,
.mud-button-icon-size-large > *:first-child { font-size: 18px; }

/* Flat in every state. A filled button already has the strongest fill on the page; the shadow on
   top of it was the only elevated thing in a language whose surfaces sit on hairlines. */
.mud-button-root.mud-button-filled,
.mud-button-root.mud-button-filled:hover,
.mud-button-root.mud-button-filled:focus-visible,
.mud-button-root.mud-button-filled:active,
.mud-button-group-root.mud-button-group-filled,
.mud-button-group-filled .mud-button-root:hover {
    box-shadow: none;
}

/* An outline tinted from the button's own colour, so a default outlined button takes a neutral
   edge and a primary one a primary edge from the same rule, and it firms up under the pointer. */
.mud-button-root.mud-button-outlined:not(:disabled) {
    border-color: color-mix(in srgb, currentColor 30%, transparent);
}

.mud-button-root.mud-button-outlined:hover:not(:disabled) {
    border-color: color-mix(in srgb, currentColor 60%, transparent);
}

/* The same ring as a clickable card, rather than Material's darker wash, which on a filled button
   is barely distinguishable from hover. */
.mud-button:focus-visible:not(.mud-icon-button),
:is(button, a).mud-icon-button:focus-visible:not(.mud-picker-calendar-day, .mud-input-adornment-icon-button) {
    outline: 2px solid var(--sg-primary);
    outline-offset: 2px;
}

/* Pressed. A wide button gives less than a small control, or a long label visibly shrinks. */
.mud-button:not(.mud-icon-button):active:not(:disabled) {
    transform: scale(var(--sg-press-scale-wide));
}

:is(button, a).mud-icon-button:active:not(:disabled) {
    transform: scale(var(--sg-press-scale));
}

/* No ink. The ripple is a pseudo-element MudBlazor adds to every button-rooted control; removing
   the pseudo-element is the only way that does not depend on a DisableRipple on each call site. */
.mud-button-root.mud-ripple::after {
    content: none;
}

/* Icon buttons take the icon button's rounded square instead of Material's circle, so a
   MudIconButton and an SgIconButton in one toolbar hover alike. Their SIZE is left alone on
   purpose: a 48px medium is Material's, but hundreds of call sites are laid out around it.

   Real buttons only. MudBlazor builds checkboxes, radios and switches on the same classes as a
   <span>, and those halos stay round; calendar days stay round; and an icon button inside a
   field (a picker's calendar, a clear cross) keeps its circle, which reads as part of the field. */
:is(button, a).mud-icon-button:not(.mud-picker-calendar-day, .mud-input-adornment-icon-button) {
    border-radius: var(--sg-radius-control);
    transition: background-color var(--sg-ease), color var(--sg-ease), transform var(--sg-ease-fast);
}

.mud-button-group-root,
.mud-button-group-root .mud-button-root {
    border-radius: var(--sg-radius-control);
}

/* A group draws its own outlines, through rules a class heavier than the ones above. Mixed onto
   the surface rather than onto transparent: grouped buttons overlap their neighbour by a pixel to
   share one divider, and two translucent edges stacked there would draw every divider darker
   than the outline around the group. */
.mud-button-group-outlined.mud-button-group-override-styles .mud-button-root:not(:disabled) {
    border-color: color-mix(in srgb, currentColor 30%, var(--sg-surface));
}

/* ---- searchable select ------------------------------------------------------------------- */

/* SgSelect. The trigger deliberately reuses the outlined-field metrics above so it lines up
   with a MudTextField beside it; the panel reuses the search box and pick list from the dialog
   recipes, so a filtered dropdown and a pick-one dialog are visibly the same idea. */

.sg-select {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

/* The floated label of an outlined text field, at the same size and on the same 12px column, so a
   select and a field in one row read as one kind of control. It is centred on the border, and the
   legend below leaves the gap in the border it sits in. */
.sg-select-label {
    position: absolute;
    top: -6px;
    left: 12px;
    max-width: calc(100% - 24px);
    /* A text field's label is its 14px self scaled to .75, so its 5px padding lands as 3.75. */
    padding: 0 3.75px;
    font-size: calc(var(--sg-fs-body) * .75);
    line-height: 1.3;
    color: var(--sg-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    transition: color var(--sg-ease);
}

/* The outline of a labelled select, in a text field's measured geometry: flush with the box, and a
   zero-height legend 11px in with 5px either side, so the border line stays on the box's edge and
   the notch starts where the label does. The legend's font size is the label's, so the notch is the
   label's width. */
.sg-select-outline {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 0;
    min-width: 0;
    border: 1px solid var(--sg-lines);
    border-radius: var(--sg-radius-well);
    text-align: left;
    pointer-events: none;
    transition: border-color var(--sg-ease);
}

.sg-select-outline > legend {
    float: unset;
    display: block;
    width: auto;
    max-width: calc(100% - 22px);
    height: 0;
    margin-left: 11px;
    padding: 0 5px;
    font-size: calc(var(--sg-fs-body) * .75);
    white-space: nowrap;
    overflow: hidden;
    visibility: hidden;
}

/* A floating label needs room above the box to float into, and a text field takes it as margin:
   8px above and 4px below. A labelled select takes the same, or in a centred row the two boxes sit
   2px apart. A form row zeroes every margin in it on purpose, so it is left out, as it is for text
   fields. */
.sg-select-labeled:not(.sg-form-row > *) {
    margin-top: 8px;
    margin-bottom: 4px;
}

/* And an unlabelled select beside a labelled field takes the headroom too, as an unlabelled text
   field does (see the label-room rule above). */
:where(:not(.sg-form-row):has(> .mud-input-outlined-with-label, > .sg-select-labeled))
    > .sg-select:not(.sg-select-labeled) {
    margin-top: 8px;
    margin-bottom: 4px;
}

/* With an outline drawing the border, the trigger's own border and focus ring would draw a second
   box over it, without the notch. */
.sg-select-labeled .sg-select-trigger,
.sg-select-labeled .sg-select-trigger:hover:not(:disabled),
.sg-select-labeled .sg-select-trigger:focus-visible,
.sg-select-labeled .sg-select-trigger-open,
.sg-select-labeled .sg-select-trigger-error {
    border-color: transparent;
    box-shadow: none;
}

.sg-select-labeled .sg-select-trigger:hover:not(:disabled) ~ .sg-select-outline {
    border-color: color-mix(in srgb, var(--sg-primary) var(--sg-tint-border), var(--sg-lines));
}

/* 2px, as a focused text field's is. The outline is absolutely placed, so the extra width moves
   nothing beside it. */
.sg-select-labeled .sg-select-trigger:focus-visible ~ .sg-select-outline,
.sg-select-labeled .sg-select-trigger-open ~ .sg-select-outline {
    border-width: 2px;
    border-color: var(--sg-primary);
}

.sg-select-labeled .sg-select-trigger:focus-visible ~ .sg-select-label,
.sg-select-labeled .sg-select-trigger-open ~ .sg-select-label {
    color: var(--sg-primary);
}

/* Invalid wins over open and focused, as on the trigger itself. */
.sg-select-labeled .sg-select-trigger-error ~ .sg-select-outline,
.sg-select-labeled .sg-select-trigger-error:hover:not(:disabled) ~ .sg-select-outline {
    border-color: var(--sg-error);
}

.sg-select-labeled .sg-select-trigger-error ~ .sg-select-label {
    color: var(--sg-error);
}

.sg-select-labeled .sg-select-trigger:disabled ~ .sg-select-outline,
.sg-select-labeled .sg-select-trigger:disabled ~ .sg-select-label {
    opacity: .55;
}

.sg-select-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    /* Fixed rather than derived from padding and line-height. box-sizing is border-box here, so
       the trigger's border counts toward its height, while MudBlazor's outline is an absolutely
       positioned fieldset and does not — matching by arithmetic left it a pixel or two proud of a
       text field in the same row every time the font metrics moved. */
    height: 39px;
    /* 11px, not 12: this border sits inside the box, while MudBlazor's outline is an absolutely
       positioned fieldset that takes no content space. 11 + 1px border puts the icon and the text
       on the same 12px column as a text field stacked above it. */
    padding: 0 11px;
    border-radius: var(--sg-radius-well);
    border: var(--sg-border);
    background: var(--sg-field-fill);
    color: var(--sg-text-primary);
    font: inherit;
    font-size: var(--sg-fs-body);
    text-align: left;
    cursor: pointer;
    transition: border-color var(--sg-ease), background-color var(--sg-ease), box-shadow var(--sg-ease);
}

.sg-select-trigger:hover:not(:disabled) {
    background: var(--sg-field-fill-hover);
    border-color: color-mix(in srgb, var(--sg-primary) var(--sg-tint-border), var(--sg-lines));
}

/* Matches the focused field: 2px, primary, and a negative margin so the extra width does not
   shift the control's neighbours. */
/* An inset ring rather than a thicker border: growing the border would change the trigger's
   height and nudge everything beside it on every open and close. */
.sg-select-trigger:focus-visible,
.sg-select-trigger-open {
    outline: none;
    border-color: var(--sg-primary);
    box-shadow: inset 0 0 0 1px var(--sg-primary);
}

.sg-select-trigger:disabled {
    opacity: .55;
    cursor: default;
}

/* After the focus and open rules on purpose, matching the outlined field above: a select that is
   both open and invalid should read as invalid, since the open state is not the thing to notice. */
.sg-select-trigger-error,
.sg-select-trigger-error:hover:not(:disabled),
.sg-select-trigger-error.sg-select-trigger-open,
.sg-select-trigger-error:focus-visible {
    border-color: var(--sg-error);
}

.sg-select-trigger-error.sg-select-trigger-open,
.sg-select-trigger-error:focus-visible {
    box-shadow: inset 0 0 0 1px var(--sg-error);
}

.sg-select-errortext {
    font-size: var(--sg-fs-label);
    color: var(--sg-error);
}

/* Holds the trigger and the overlaid clear. Own element rather than .sg-select itself, which is a
   column that also carries the label. */
.sg-select-field {
    position: relative;
    display: flex;
    min-width: 0;
}

/* The trigger keeps its full width and gives the cross its gutter through padding, so the caret
   moves out of the way instead of being covered by it. */
.sg-select-trigger-clearable {
    padding-right: 35px;
}

.sg-select-clearvalue {
    position: absolute;
    top: 50%;
    right: 7px;
    transform: translateY(-50%);
    width: 22px;
    height: 22px;
}

.sg-select-value {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-select-placeholder {
    color: var(--sg-text-secondary);
}

.sg-select-caret {
    flex-shrink: 0;
    color: var(--sg-action);
    transition: transform var(--sg-ease), color var(--sg-ease);
}

/* Turns over while the panel is open: the usual sign that the next click folds it away. */
.sg-select-trigger-open .sg-select-caret {
    color: var(--sg-primary);
    transform: rotate(180deg);
}

/* Covers the viewport so the next click closes the panel wherever it lands. Transparent, and one
   below MudBlazor's popover layer so the panel itself stays clickable. */
.sg-select-backdrop {
    position: fixed;
    inset: 0;
    z-index: calc(var(--mud-zindex-popover, 1200) - 1);
}

/* MudPopover leaves an empty anchor div where it is declared. It is zero-height, but as a child of
   the .sg-select column it still took a gap, making the control 4px taller than its trigger — so in
   a centered toolbar row the trigger sat 2px above every text field beside it. Out of flow, it no
   longer counts; MudBlazor positions the panel from its parent, which is unchanged. */
.sg-select > .mud-popover-cascading-value {
    position: absolute;
}

/* The popover wrapper. MudBlazor owns its position; this only adds the gap to the trigger. */
.sg-select-pop {
    margin-top: 4px;
}

/* Static, because the popover around it is what is positioned. It was absolute before, anchored
   to the trigger — which meant any ancestor with an overflow clipped it, including a card, a
   dialog body, and this library's own .sg-panel. */
.sg-select-panel {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 6px;
    border-radius: var(--sg-radius-card);
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-pop);
}

.sg-select-search {
    padding: 6px 8px;
}

.sg-select-clear {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
}

/* The list already scrolls; the panel must not, or the search box scrolls away with it.

   Capped in absolute terms as well as relative. A pure vh cap looks reasonable on a laptop and
   becomes a 585px panel on a tall monitor — tall enough that it no longer fits below the
   trigger, so the popover flips above it and the dropdown opens in the opposite direction on
   the same page depending on the window height. 320px keeps it downward in almost every case,
   and the list scrolls for the rest. */
.sg-select-list {
    border: none;
    background: transparent;
    padding: 0;
    max-height: min(320px, 40vh);
}

.sg-select-trigger-dense {
    height: var(--sg-field-h-dense);
    padding: 0 9px;
    gap: 6px;
}

/* Says the list is a first page, and that typing reaches the rest. Sits under the list rather than
   in it, so it never scrolls away with the values. */
.sg-select-note {
    padding: 2px 4px 0;
}

/* ------------------------------------------------------------------ morph --- */

/* SgMorph: a card opening into its detail view. The motion is in segal-ui.js; this is the static
   frame it moves between. Above MudBlazor's app bar and drawer (1100-1300), below its dialogs
   (1400) and tooltips, so a confirmation raised from inside a detail view still lands on top. */
.sg-morph {
    position: fixed;
    inset: 0;
    z-index: 1350;
    display: grid;
    place-items: center;
    padding: 24px;
}

.sg-morph-backdrop {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, #0B0B14 42%, transparent);
    backdrop-filter: blur(2px);
}

/* Drawn under the panel and over the backdrop. Its surface recipe is the panel's exactly — the
   panel fades in on top of it at the end, and any difference would show as a flash. */
.sg-morph-ghost {
    position: fixed;
    z-index: 1;
    box-sizing: border-box;
    overflow: hidden;
    border: var(--sg-border);
    background: var(--sg-surface);
    pointer-events: none;
    will-change: left, top, width, height;
}

.sg-morph-panel {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: min(var(--sg-morph-width, 880px), 100%);
    max-height: calc(100vh - 48px);
    border-radius: 16px;
    border: var(--sg-border);
    background: var(--sg-surface);
    box-shadow: var(--sg-shadow-pop);
    overflow: hidden;
    outline: none;
}

/* Laid out but invisible until morphIn takes over, so the first painted frame is never the
   finished panel sitting in the middle of the screen before the motion starts. */
.sg-morph-pending {
    opacity: 0;
}

.sg-morph-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 14px 12px 18px;
    border-bottom: var(--sg-border);
}

.sg-morph-head-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}

.sg-morph-eyebrow {
    font-size: var(--sg-fs-label);
    font-weight: 600;
    letter-spacing: .6px;
    text-transform: uppercase;
    color: var(--sg-text-secondary);
}

.sg-morph-title {
    font-size: var(--sg-fs-dialog-title);
    font-weight: 600;
    color: var(--sg-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-morph-sub {
    font-size: var(--sg-fs-secondary);
    color: var(--sg-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-morph-head-actions {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-shrink: 0;
}

.sg-morph-divider {
    width: 1px;
    height: 20px;
    margin: 0 4px;
    background: var(--sg-lines);
}

.sg-morph-body {
    flex: 1;
    min-height: 0;
    overflow: auto;
    padding: 16px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* Each block of the body settles in behind the panel's own reveal, staggered, so the detail reads
   as being unpacked rather than switched on. The delay clears the morph's own 440ms. Stepping to
   another card re-keys the body, which replays this — the new content arrives the same way. */
.sg-morph-body > * {
    animation: sg-rise var(--sg-rise-duration) ease backwards;
    animation-delay: calc(.18s + var(--sg-rise-delay, 0s));
}

.sg-morph-body > :nth-child(2) { --sg-rise-delay: var(--sg-rise-step); }
.sg-morph-body > :nth-child(3) { --sg-rise-delay: calc(var(--sg-rise-step) * 2); }
.sg-morph-body > :nth-child(4) { --sg-rise-delay: calc(var(--sg-rise-step) * 3); }
.sg-morph-body > :nth-child(n+5) { --sg-rise-delay: calc(var(--sg-rise-step) * 4); }

.sg-morph-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    border-top: var(--sg-border);
    font-size: var(--sg-fs-meta);
    color: var(--sg-text-secondary);
}

/* A section inside a detail view: an uppercase label over its content, no border of its own —
   the panel already is the paper. */
.sg-morph-section {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

.sg-morph-section-title {
    font-size: var(--sg-fs-label);
    font-weight: 600;
    letter-spacing: .6px;
    text-transform: uppercase;
    color: var(--sg-text-secondary);
}

.sg-morph-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 14px 24px;
}

@media (max-width: 600px) {
    .sg-morph {
        padding: 10px;
    }

    .sg-morph-panel {
        max-height: calc(100vh - 20px);
        border-radius: var(--sg-radius-card);
    }

    .sg-morph-head {
        padding: 12px 10px 10px 14px;
    }

    .sg-morph-body {
        padding: 14px;
    }

    .sg-morph-columns {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ------------------------------------------------------------- bar list --- */

/* Bars grow from their start edge on arrival. Transform rather than width, so it never triggers
   layout, and scaleX from the inline-start so a right-to-left page grows the right way. */
@keyframes sg-grow {
    from { transform: scaleX(0); }
    to { transform: none; }
}

.sg-bars {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sg-bars-empty {
    padding: 12px 0;
    font-size: var(--sg-fs-secondary);
    color: var(--sg-text-secondary);
}

.sg-bar-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 4px 12px;
    padding: 6px 8px;
    margin: 0 -8px;
    border-radius: var(--sg-radius-control);
}

.sg-bar-row-click {
    cursor: pointer;
    transition: background-color var(--sg-ease-fast);
}

.sg-bar-row-click:hover,
.sg-bar-row-click:focus-visible {
    background: var(--sg-hover);
}

.sg-bar-label {
    font-size: var(--sg-fs-secondary);
    color: var(--sg-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-bar-value {
    font-size: var(--sg-fs-secondary);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--sg-text-primary);
    white-space: nowrap;
}

.sg-bar-note {
    margin-inline-start: 4px;
    font-weight: 400;
    color: var(--sg-text-secondary);
}

.sg-bar-track {
    grid-column: 1 / -1;
    display: block;
    height: 6px;
    border-radius: var(--sg-radius-pill);
    background: var(--sg-background-alt);
    overflow: hidden;
}

.sg-bar-fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--sg-primary);
    transform-origin: left center;
    animation: sg-grow .6s cubic-bezier(.2, .8, .2, 1) backwards;
    animation-delay: calc(.25s + min(var(--sg-bar-i, 0), 10) * .035s);
}

:dir(rtl) .sg-bar-fill { transform-origin: right center; }

.sg-bar-fill-secondary { background: var(--sg-secondary); }
.sg-bar-fill-tertiary { background: var(--sg-tertiary); }
.sg-bar-fill-info { background: var(--sg-info); }
.sg-bar-fill-success { background: var(--sg-success); }
.sg-bar-fill-warning { background: var(--sg-warning); }
.sg-bar-fill-error { background: var(--sg-error); }

/* ------------------------------------------------------------ split bar --- */

.sg-split {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.sg-split-bar {
    display: flex;
    gap: 2px;
    height: 12px;
    border-radius: var(--sg-radius-pill);
    background: var(--sg-background-alt);
    overflow: hidden;
}

.sg-split-seg {
    flex-basis: 0;
    min-width: 3px;
    transform-origin: left center;
    animation: sg-grow .6s cubic-bezier(.2, .8, .2, 1) backwards;
    animation-delay: calc(.25s + min(var(--sg-bar-i, 0), 10) * .05s);
}

:dir(rtl) .sg-split-seg { transform-origin: right center; }

.sg-split-legend {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 6px 16px;
}

.sg-split-key {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 8px;
    font-size: var(--sg-fs-secondary);
}

.sg-split-key-zero {
    opacity: .55;
}

.sg-split-dot {
    width: 9px;
    height: 9px;
    border-radius: 3px;
}

.sg-split-label {
    color: var(--sg-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sg-split-value {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--sg-text-primary);
}

.sg-split-share {
    min-width: 40px;
    text-align: end;
    font-size: var(--sg-fs-meta);
    font-variant-numeric: tabular-nums;
    color: var(--sg-text-secondary);
}

@media (prefers-reduced-motion: reduce) {
    .sg-bar-fill,
    .sg-split-seg {
        animation: none;
    }
}


/* ============================================================================
   SNACKBARS

   Every ISnackbar.Add in an application, dressed where it lands rather than through a component,
   because there are hundreds of call sites and none of them should have to change.

   Material draws a snackbar as a slab of solid status colour. Four of them stacked — a save, a
   warning, an error and an info line arriving together — read as four unrelated banners, and a
   saturated green slab is louder than "Changes saved" deserves. Here it is a surface card like
   every other paper in the language, and the severity lives where a dialog head keeps its accent:
   in a tinted icon chip, plus a thin edge on the leading side so a glance down a stack still
   sorts them by kind.

   The accent is resolved once per severity into --sg-snack-accent, from all three of MudBlazor's
   variant class families, so a host that switches SnackbarVariant gets the same card.

   Anchored on #mud-snackbar-container: the variant classes (.mud-alert-filled-success and
   friends) are single classes that set colour and background, and an id is the one prefix that
   outranks all of them without !important while staying inside the container MudBlazor renders.

   Motion: MudBlazor plays its fade as an INLINE animation on each snackbar, swapping the keyframes
   when it hides, so the fade is left exactly as it is (overriding it would also lose the exit).
   The arrival travel is a separate property — translate, eased from @starting-style — which
   composes with the inline opacity animation instead of fighting it.
   ========================================================================= */

/* Arrives travelling away from the screen edge it is pinned to, like a popover leaving its anchor. */
#mud-snackbar-container {
    --sg-snack-from: calc(var(--sg-dialog-distance) * -1);
}

/* Closer to the edge than Material's 24px, and clear of a notch or the home indicator. */
#mud-snackbar-container:is(.mud-snackbar-location-top-left, .mud-snackbar-location-top-center, .mud-snackbar-location-top-right) {
    top: calc(16px + var(--sg-safe-top));
}

#mud-snackbar-container:is(.mud-snackbar-location-bottom-left, .mud-snackbar-location-bottom-center, .mud-snackbar-location-bottom-right) {
    --sg-snack-from: var(--sg-dialog-distance);
    bottom: calc(16px + var(--sg-safe-bottom));
}

#mud-snackbar-container > .mud-snackbar {
    --sg-snack-accent: var(--sg-text-secondary);

    gap: 10px;
    margin-top: 8px;
    min-width: 320px;
    max-width: 440px;
    padding-block: 8px;
    padding-inline: 12px 8px;
    border: var(--sg-border);
    border-radius: var(--sg-radius-card);
    background: var(--sg-surface);
    color: var(--sg-text-primary);
    font-size: var(--sg-fs-body);
    font-weight: 400;
    line-height: 1.45;
    box-shadow: var(--sg-shadow-hover), 0 10px 28px rgba(0, 0, 0, .12);

    translate: none;
    transition: translate var(--sg-dialog-duration) cubic-bezier(.2, .8, .2, 1);
}

@starting-style {
    #mud-snackbar-container > .mud-snackbar {
        translate: 0 var(--sg-snack-from);
    }
}

/* The leading edge. Drawn inside the card (overflow is hidden), so the radius clips it into a
   rounded stripe rather than a square one standing on the border. */
#mud-snackbar-container > .mud-snackbar::before {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 3px;
    background: var(--sg-snack-accent);
}

#mud-snackbar-container > .mud-snackbar:is(.mud-alert-filled-info, .mud-alert-text-info, .mud-alert-outlined-info) {
    --sg-snack-accent: var(--sg-info);
}

#mud-snackbar-container > .mud-snackbar:is(.mud-alert-filled-success, .mud-alert-text-success, .mud-alert-outlined-success) {
    --sg-snack-accent: var(--sg-success);
}

#mud-snackbar-container > .mud-snackbar:is(.mud-alert-filled-warning, .mud-alert-text-warning, .mud-alert-outlined-warning) {
    --sg-snack-accent: var(--sg-warning);
}

#mud-snackbar-container > .mud-snackbar:is(.mud-alert-filled-error, .mud-alert-text-error, .mud-alert-outlined-error) {
    --sg-snack-accent: var(--sg-error);
}

/* Normal has no status, so its edge would only be a grey line; the chip alone carries it. */
#mud-snackbar-container > .mud-snackbar:is(.mud-alert-filled-normal, .mud-alert-text-normal, .mud-alert-outlined-normal)::before {
    content: none;
}

/* Same chip as a dialog head's icon, a size down for a single line of text. */
#mud-snackbar-container .mud-snackbar-icon {
    display: inline-grid;
    place-items: center;
    align-self: flex-start;
    width: 30px;
    height: 30px;
    margin: 0;
    padding: 0;
    border-radius: var(--sg-radius-chip);
    background: color-mix(in srgb, var(--sg-snack-accent) var(--sg-tint-bg), transparent);
    color: var(--sg-snack-accent);
    opacity: 1;
    flex-shrink: 0;
}

#mud-snackbar-container .mud-snackbar-icon .mud-icon-root {
    font-size: 18px;
}

/* Padded to the chip's height, so one line sits centred on it and a second wraps under the first
   instead of under the chip. */
#mud-snackbar-container .mud-snackbar-content-message {
    flex: 1 1 auto;
    min-width: 0;
    padding: 5px 0;
}

#mud-snackbar-container .mud-snackbar-content-message b,
#mud-snackbar-container .mud-snackbar-content-message strong {
    font-weight: 600;
}

#mud-snackbar-container .mud-snackbar-content-action {
    align-self: flex-start;
    gap: 2px;
    margin: 0;
    padding: 0;
}

/* The one thing in the card that does something, so it takes the accent. A text button, not
   the filled grey MudBlazor gives it: on a surface card the fill would read as a second card. */
#mud-snackbar-container .mud-snackbar-content-action > .mud-snackbar-action-button {
    height: 30px;
    min-width: 0;
    padding: 0 10px;
    border-radius: var(--sg-radius-control);
    background: transparent;
    box-shadow: none;
    color: var(--sg-snack-accent);
    font-size: var(--sg-fs-secondary);
    font-weight: 600;
    text-transform: none;
    letter-spacing: .1px;
    white-space: nowrap;
}

/* Normal's accent is the secondary text colour, which is too quiet for the only action. */
#mud-snackbar-container > .mud-snackbar:is(.mud-alert-filled-normal, .mud-alert-text-normal, .mud-alert-outlined-normal) .mud-snackbar-action-button {
    color: var(--sg-primary);
}

#mud-snackbar-container .mud-snackbar-content-action > .mud-snackbar-action-button:hover {
    background: color-mix(in srgb, currentColor var(--sg-tint-bg-soft), transparent);
}

#mud-snackbar-container .mud-snackbar-content-action > .mud-snackbar-close-button {
    width: 30px;
    height: 30px;
    margin: 0;
    padding: 0;
    border-radius: var(--sg-radius-control);
    color: var(--sg-action);
}

#mud-snackbar-container .mud-snackbar-content-action > .mud-snackbar-close-button:hover {
    background: var(--sg-hover);
    color: var(--sg-text-primary);
}

#mud-snackbar-container .mud-snackbar-close-button .mud-icon-root {
    font-size: 16px;
}

/* A phone: whichever corner was asked for, the stack spans the screen less a 12px gutter each side,
   so a long message wraps instead of running off one edge. */
@media (max-width: 600px) {
    #mud-snackbar-container[class*="mud-snackbar-location-"] {
        left: 12px;
        right: 12px;
        transform: none;
    }

    #mud-snackbar-container > .mud-snackbar {
        min-width: 0;
        max-width: none;
    }
}
