/* ── Nxt.Presentation.Blazor — shared Common chrome ────────────────────
   Styles for the shared Blazor Common components (EmptyState, StatusBar, Spinner, MdiIcon), the Blazor
   siblings of the WPF Nxt.Presentation.Wpf controls. Every colour resolves from a --nxt-* custom
   property declared in the one palette, palette.css (#2574 — this sheet only CONSUMES them) — no colour
   literal here (C3, doc/Common/ui.md). Mirrors the WPF look: EmptyStateControl.xaml, StatusBar.xaml,
   LoadingSpinner.xaml.
   ─────────────────────────────────────────────────────────────────────────── */

/* ── Page base reset ─────────────────────────────────────────────────────────────
   Moved here verbatim from devices.css at #2687. It was never device chrome, and while it lived in a
   Drivers-area sheet the Nxt Atrium shell — a platform that draws no device chrome at all — had to link
   that whole file to get a `box-sizing` rule and the page's text/background colour. All four Blazor
   hosts link this sheet, and the three that also link devices.css link it directly after, so the
   cascade is unchanged for every one of them. See doc/Common/design-decisions.md (2026-08-31, #2687). */

*, *::before, *::after {
    box-sizing: border-box;
}

body {
    font-family: var(--nxt-font-ui);
    font-size: 13px;
    color: var(--nxt-text-primary);
    background: var(--nxt-bg-base);
    margin: 0;
    padding: 0;
}

/* ── Shared icon renderer (#774) ────────────────────────────────────────────────
   MdiIcon.razor emits <svg class="nxt-mdi" …> with <path fill="currentColor"/>. The glyph is a
   1em inline square that inherits the surrounding text `color`, so the same --nxt-* colour tokens
   apply to it (no per-icon colour). Each surface sizes the icon via `font-size` on the icon span;
   fill comes from `color`.

   .nxt-mdi is the ONLY thing giving the glyph a size — the <svg> carries a viewBox and no width or
   height — so it must ship in the same library as the component (#2687). Split them and the icon takes
   whatever its container offers, which is #1447's failure mode: 0x0 to full-width, silently. */
.nxt-mdi {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.125em;   /* optical baseline alignment, matches text-adjacent glyphs */
    fill: currentColor;
    flex-shrink: 0;
}

/* Spinner: the nxt-spin keyframes on any MDI Loading glyph. */
.nxt-mdi--spin { animation: nxt-spin 1s linear infinite; }

/* Shared with .spec-panel__spinner in devices.css, which is why the keyframes moved here with
   .nxt-mdi--spin rather than staying beside one of the two consumers. */
@keyframes nxt-spin {
    to { transform: rotate(360deg); }
}

/* ── EmptyState (mirror EmptyStateControl.xaml) ──────────────────────────────
   Centred icon (48) / bold title (16) / secondary body (13) / italic hint (12) / action button. */
.nxt-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    max-width: 420px;
    margin: 0 auto;
    padding: 24px 12px;
    gap: 6px;
}

.nxt-empty-state__icon {
    font-size: 48px;
    line-height: 1;
    color: var(--nxt-text-secondary);
    margin-bottom: 2px;
}

.nxt-empty-state__title {
    font-size: 16px;
    font-weight: 700;
    color: var(--nxt-text-primary);
}

.nxt-empty-state__body {
    font-size: 13px;
    color: var(--nxt-text-secondary);
}

.nxt-empty-state__suggestion {
    font-size: 12px;
    font-style: italic;
    color: var(--nxt-text-hint);
}

.nxt-empty-state__action {
    margin-top: 8px;
    padding: 6px 16px;
    font-size: 13px;
    cursor: pointer;
    color: var(--nxt-text-on-accent);
    background: var(--nxt-accent-primary);
    border: 1px solid var(--nxt-btn-primary-border);
    border-radius: 3px;
}

.nxt-empty-state__action:hover { opacity: 0.92; }
.nxt-empty-state__action:disabled { opacity: 0.3; cursor: default; }

/* ── StatusBar (mirror StatusBar.xaml) ───────────────────────────────────────
   A bottom strip on the Background.Secondary surface, Text.Secondary, 12px. */
.nxt-status-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 4px 8px;
    font-size: 12px;
    color: var(--nxt-text-secondary);
    background: var(--nxt-bg-secondary);
}

/* ── Spinner (mirror LoadingSpinner.xaml — 32px accent ring) ──────────────────
   The accent arc uses --nxt-accent-primary (the Nxt.Accent.Primary key the WPF ProgressRing
   resolves after #844's Status.Busy → Accent migration); the faint track is the primary border colour. */
.nxt-spinner {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.nxt-spinner__ring {
    width: 32px;
    height: 32px;
    border: 3px solid var(--nxt-border-primary);
    border-top-color: var(--nxt-accent-primary);
    border-radius: 50%;
    animation: nxt-spinner-spin 0.9s linear infinite;
}

.nxt-spinner__label {
    font-size: 12px;
    color: var(--nxt-text-secondary);
}

@keyframes nxt-spinner-spin {
    to { transform: rotate(360deg); }
}

/* ── DataGrid (the reusable filterable grid — twin of WPF DataGridView) ───────
   The shared DataGridViewModel computes the visible rows; this only renders them. Every colour resolves
   from a --nxt-* token supplied by the host (devices.css :root) — no colour literal (C3). */
.nxt-grid { display: flex; flex-direction: column; }

.nxt-grid__toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 4px;
}

.nxt-grid__count { font-size: 11px; color: var(--nxt-text-secondary); }

.nxt-grid__table { width: 100%; border-collapse: collapse; font-size: 12px; }

.nxt-grid__table th {
    text-align: left;
    font-weight: 600;
    color: var(--nxt-text-primary);
    border-bottom: 1px solid var(--nxt-border-primary);
    padding: 4px 8px;
    white-space: nowrap;
}

.nxt-grid__th-label { vertical-align: middle; }

/* #2217: a sortable header is a button that carries the column's own label — transparent and inheriting the
   header's type, so a sortable column looks like a header and not like a control, while still being a real
   click/keyboard target. The arrow is the state indicator; the tooltip says what the next click will do. */
/* #2239: the header is a flex row (the twin of WPF's DockPanel) and the sort button TAKES THE FREE SPACE.
   It used to be inline-flex — i.e. only as wide as its own text — inside a column stretched several times
   wider, so every pixel beside the word belonged to the <th> and did nothing. Clicking "the column head"
   missed unless you happened to hit the text, which is a G3/G5 failure (an invisible target, not where an
   operator aims) rather than merely a hit-test miss. */
.nxt-grid__th-inner { display: flex; align-items: center; gap: 4px; }

.nxt-grid__th-sort {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    display: flex;
    flex: 1 1 auto;          /* fill the header cell, so the whole width sorts */
    justify-content: flex-start;
    align-items: center;
    gap: 3px;
    text-align: left;
}

/* A non-sortable column keeps the same geometry, so the funnel sits in the same place either way. */
.nxt-grid__th-inner > .nxt-grid__th-label { flex: 1 1 auto; }

.nxt-grid__th-sort:hover .nxt-grid__th-label { text-decoration: underline; }

.nxt-grid__th-arrow {
    color: var(--nxt-text-secondary);
    font-size: 9px;
    line-height: 1;
}

.nxt-grid__table td {
    color: var(--nxt-text-primary);
    border-bottom: 1px solid var(--nxt-border-primary);
    padding: 3px 8px;
}

/* #1482 (FR-004): opt-in row selection — clickable rows highlight on hover and when selected (parity with the
   WPF DataGrid's SelectedItem highlight). Only rendered when VM.SelectionEnabled adds these classes. The soft
   accent tint is derived from the defined --nxt-accent-primary token (no new palette key needed). */
.nxt-grid__row--selectable { cursor: pointer; }
.nxt-grid__row--selectable:hover td { background: color-mix(in srgb, var(--nxt-accent-primary) 8%, transparent); }
.nxt-grid__row--selected td { background: color-mix(in srgb, var(--nxt-accent-primary) 16%, transparent); }

/* #2246 (FR-006): row emphasis — the left error bar and the heavier weight, the Blazor twin of WPF's
   EmphasisRowStyle. The class is added from the ROW's declared HasFailedEmphasis; this file no longer decides
   what "failing" means, which is the whole point (#1515). Specificity is raised past `.nxt-grid__table td` so
   the row's weight actually lands. */
.nxt-grid__table tr.nxt-grid__row--failed td { font-weight: 600; }
.nxt-grid__table tr.nxt-grid__row--failed td:first-child { border-left: 3px solid var(--nxt-semantic-error); }

/* #2246 (FR-007/FR-008): the cell tones. The ROLE is decided in the shared ViewModel
   (DataGridColumnViewModel.Content); these are only Blazor's tokens for it — the WPF twin is
   DataGridCellToneToBrushConverter. Existing semantic tokens only; no new one is introduced. Written as
   `td.<class>` so they outrank `.nxt-grid__table td`'s own colour. */
.nxt-grid__table td.nxt-grid__cell--positive { color: var(--nxt-semantic-active); }
.nxt-grid__table td.nxt-grid__cell--negative { color: var(--nxt-semantic-error); }
.nxt-grid__table td.nxt-grid__cell--muted    { color: var(--nxt-text-muted); }

/* #2246 (FR-003/FR-005): the Verdict cell — glyph, then the word when the column shows it. The glyph inherits
   the tone through `currentColor`, so one colour decision reaches both halves. Centred, like its WPF twin. */
.nxt-grid-verdict { display: inline-flex; align-items: center; gap: 4px; justify-content: center; }
.nxt-grid-verdict__icon { width: 11px; height: 11px; flex: none; }
.nxt-grid-verdict.nxt-grid__cell--positive { color: var(--nxt-semantic-active); }
.nxt-grid-verdict.nxt-grid__cell--negative { color: var(--nxt-semantic-error); }
.nxt-grid-verdict.nxt-grid__cell--muted    { color: var(--nxt-text-muted); }

.nxt-grid__empty {
    padding: 10px 8px;
    text-align: center;
    font-style: italic;
    color: var(--nxt-text-secondary);
}

/* ── Per-column funnel + value picker ──────────────────────────────────────── */
/* #2239 FR-017: every column has a funnel now, so ten of them would shout over the values (G11). The funnel is
   revealed on hover of its header cell and stays visible while its filter is ACTIVE or its popup is open.
   The "active" half is the ViewModel's (nxt-grid-filter--active), so both hosts keep the same rule; only the
   hover half is per-host. `visibility` rather than `display` so the header does not reflow on hover. */
.nxt-grid-filter { position: relative; display: inline-flex; align-items: center; visibility: hidden; }
.nxt-grid__table th:hover .nxt-grid-filter,
.nxt-grid__table th:focus-within .nxt-grid-filter,
.nxt-grid-filter--active { visibility: visible; }

.nxt-grid-funnel-btn {
    background: none;
    border: none;
    padding: 0 0 0 4px;
    cursor: pointer;
    line-height: 0;
}

.nxt-grid-funnel { width: 13px; height: 13px; color: var(--nxt-text-secondary); }
.nxt-grid-funnel--active { color: var(--nxt-accent-primary); }

.nxt-grid-filter__backdrop {
    position: fixed;
    inset: 0;
    z-index: 10;
}

.nxt-grid-filter__pop {
    position: absolute;
    z-index: 11;
    top: 100%;
    left: 0;
    min-width: 210px;
    max-width: 300px;
    margin-top: 2px;
    padding: 8px;
    background: var(--nxt-bg-panel);
    border: 1px solid var(--nxt-border-primary);
    border-radius: 3px;
    box-shadow: 0 2px 8px var(--nxt-overlay-scrim);
}

.nxt-grid-filter__search {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 6px;
    padding: 3px 6px;
    font-size: 12px;
    border: 1px solid var(--nxt-border-primary);
    border-radius: 2px;
}

.nxt-grid-filter__actions { display: flex; gap: 6px; margin-bottom: 4px; }
.nxt-grid-filter__actions button {
    background: none;
    border: none;
    padding: 1px 4px;
    font-size: 11px;
    color: var(--nxt-accent-primary);
    cursor: pointer;
}

.nxt-grid-filter__list { max-height: 220px; overflow-y: auto; }

/* #2272 / G1: above the distinct-value threshold the section still renders and says why there is no list.
   Muted italic — the same voice as .nxt-grid-num__none, because it is the same kind of statement. */
.nxt-grid-filter__toomany { margin: 0 0 4px; font-size: 12px; font-style: italic; color: var(--nxt-text-muted); }

.nxt-grid-filter__opt {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 1px 0;
    font-size: 12px;
    color: var(--nxt-text-primary);
    cursor: pointer;
}

/* ── #2239: the numeric comparison editor ──────────────────────────────────── */
.nxt-grid-num__prompt {
    display: block;
    margin-bottom: 4px;
    font-size: 12px;
    color: var(--nxt-text-secondary);
}

.nxt-grid-num__predicate {
    width: 100%;
    max-width: 284px;
    margin-bottom: 4px;
    padding: 3px 4px;
    border: 1px solid var(--nxt-border-primary);
    border-radius: 3px;
    background: var(--nxt-bg-panel);
    color: var(--nxt-text-primary);
}

.nxt-grid-num__bounds { display: flex; align-items: center; gap: 6px; }

.nxt-grid-num__input {
    width: 110px;
    padding: 3px 4px;
    border: 1px solid var(--nxt-border-primary);
    border-radius: 3px;
    background: var(--nxt-bg-panel);
    color: var(--nxt-text-primary);
}

.nxt-grid-num__and { font-size: 12px; color: var(--nxt-text-secondary); }

/* FR-015: the refusal sits ON the control, in the error colour, while the typed text stays put. */
.nxt-grid-num__refusal { margin: 4px 0 0; font-size: 12px; color: var(--nxt-semantic-error); }

/* FR-014: what OK would do — informational, so muted rather than coloured. */
.nxt-grid-num__pending { margin: 4px 0 0; font-size: 12px; color: var(--nxt-text-muted); }

/* G1: a column with nothing to compare says so instead of offering dead predicates. */
.nxt-grid-num__none { margin: 0 0 4px; font-size: 12px; font-style: italic; color: var(--nxt-text-muted); }

.nxt-grid-filter__footer { display: flex; justify-content: flex-end; gap: 6px; margin-top: 8px; }

.nxt-grid-btn {
    padding: 3px 12px;
    font-size: 12px;
    cursor: pointer;
    color: var(--nxt-text-primary);
    background: var(--nxt-bg-panel);
    border: 1px solid var(--nxt-border-primary);
    border-radius: 3px;
}

.nxt-grid-btn--primary {
    color: var(--nxt-text-on-accent);
    background: var(--nxt-accent-primary);
    border-color: var(--nxt-btn-primary-border);
}

.nxt-grid__clear {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.nxt-grid__clear .nxt-grid-funnel { color: inherit; }

/* ── Chart (reusable; #1045) — thin SVG renderer of the shared ChartViewModel geometry ── */
.nxt-chart { display: flex; flex-direction: column; gap: 6px; width: 100%; position: relative; }
.nxt-chart__svg { width: 100%; height: auto; display: block; touch-action: none; cursor: grab; }
.nxt-chart__svg:active { cursor: grabbing; }
/* Top-right chart affordances: Reset view (#1168) + Enlarge (#1781). */
.nxt-chart__tools { position: absolute; top: 2px; right: 4px; display: flex; gap: 6px; align-items: center; z-index: 1; }
/* #1168: just-in-time "Reset view", shown only while zoomed/panned */
.nxt-chart__reset { font-size: 11px; padding: 2px 8px; cursor: pointer;
    background: var(--nxt-bg-panel); color: var(--nxt-text-secondary); border: 1px solid var(--nxt-border-secondary); border-radius: 3px; }
/* #1781 (FR-002): the always-visible Enlarge button. */
.nxt-chart__expand { display: inline-flex; align-items: center; justify-content: center; font-size: 13px; padding: 2px 4px;
    cursor: pointer; background: var(--nxt-bg-panel); color: var(--nxt-text-secondary);
    border: 1px solid var(--nxt-border-secondary); border-radius: 3px; line-height: 1; }
.nxt-chart__expand:hover { color: var(--nxt-text-primary); }

/* #1781 (FR-003): the full-surface enlarge overlay (scrim + centred card). position:fixed escapes the chart box. */
.nxt-chart__overlay { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center;
    background: var(--nxt-overlay-scrim); }
.nxt-chart__overlay-card { position: relative; width: 90%; height: 88%; box-sizing: border-box; padding: 16px 18px;
    background: var(--nxt-bg-panel); border: 1px solid var(--nxt-border-primary); border-radius: 6px;
    display: flex; flex-direction: column; }
.nxt-chart__overlay-card > .nxt-chart { flex: 1 1 auto; min-height: 0; }
.nxt-chart__overlay-close { position: absolute; top: 8px; right: 8px; z-index: 1; display: inline-flex; align-items: center;
    justify-content: center; font-size: 15px; padding: 2px 5px; cursor: pointer; background: var(--nxt-bg-panel);
    color: var(--nxt-text-secondary); border: 1px solid var(--nxt-border-secondary); border-radius: 3px; line-height: 1; }
.nxt-chart__overlay-close:hover { color: var(--nxt-text-primary); }
.nxt-chart__frame { fill: none; stroke: var(--nxt-border-secondary); stroke-width: 1; }
.nxt-chart__grid { stroke: var(--nxt-border-secondary); stroke-width: 0.5; stroke-dasharray: 2 3; opacity: 0.55; }
.nxt-chart__line { fill: none; stroke-width: 1.5; stroke-linejoin: round; }
.nxt-chart__marker { stroke: none; }
/* #1470: an emphasised marker (e.g. the chosen/converged point) — larger with a contrasting halo so it
   stands out among overlapping markers. Parity with the WPF ChartView emphasis path. */
.nxt-chart__marker--emphasis { stroke: var(--nxt-bg-panel); stroke-width: 1.5; }
.nxt-chart__refline { stroke-width: 1; stroke-dasharray: 4 3; }
.nxt-chart__tick { fill: var(--nxt-text-muted); font-size: 11px; }
.nxt-chart__tick--x { text-anchor: middle; }
.nxt-chart__tick--y { text-anchor: end; }
/* #2231 — the axis TITLE (what the axis measures), a step more prominent than the tick values on it. */
.nxt-chart__axis-title { fill: var(--nxt-text-secondary); font-size: 11px; }
.nxt-chart__region-label, .nxt-chart__refline-label { font-size: 10px; }
.nxt-chart__legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 11px; color: var(--nxt-text-secondary); }
.nxt-chart__legend-item { display: inline-flex; align-items: center; gap: 5px; }
.nxt-chart__swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.nxt-chart__swatch--line { height: 3px; border-radius: 1px; }

/* ── Runtime log-level picker (#1315 / #1372) — shared by the DeviceTester + OperationTester Blazor hosts.
      Theme variables only, no hardcoded colours. */
.nxt-loglevel { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--nxt-text-label); }
.nxt-loglevel select {
    padding: 3px 6px;
    background: var(--nxt-bg-deep);
    color: var(--nxt-text-primary);
    border: 1px solid var(--nxt-border-primary);
    border-radius: 3px;
    font-size: 12px;
}

/* ── Iteration series (#1505) — the shared "every decision is inspectable" grid. Mirrors the WPF
      IterationSeriesView's section header + reused grid. Theme variables only, no hardcoded colours. */
.nxt-iterations__header { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.nxt-iterations__icon { width: 14px; height: 14px; color: var(--nxt-text-secondary); }
.nxt-iterations__title { font-weight: 600; color: var(--nxt-text-secondary); }

/* ── One-click copy affordance (#1800) — the shared CopyToClipboardButton (WPF twin: CopyToClipboardButton.xaml).
      A borderless icon button that copies an error's message + details; a transient "Copied" on success.
      Theme variables only, no hardcoded colours (C3). */
.nxt-copy-btn { display: inline-flex; align-items: center; gap: 3px; padding: 1px 3px; cursor: pointer;
    background: transparent; border: none; color: var(--nxt-text-secondary); line-height: 1; }
.nxt-copy-btn:hover { color: var(--nxt-text-primary); }
.nxt-copy-btn--copied { color: var(--nxt-semantic-active); }
.nxt-copy-btn__icon { width: 13px; height: 13px; }
.nxt-copy-btn__copied { display: inline-flex; align-items: center; gap: 3px; font-size: 11px;
    color: var(--nxt-semantic-active); }
