/* AdminPortal css 06: Dashboard content + the shared widget vocabulary -
   KPI tiles, cards, data tables, status badges, empty states, forms.

   These are the classes every page already uses, so this file is where a reskin
   lands on 17 pages at once without touching a single .razor. Rules here read
   role tokens only; there is no [data-theme="light"] block in this file and there
   should never need to be one.

   Removed in this pass as dead (verified against all 36 .razor files): the mobile
   bottom tab bar, .sync-status/.status-dot, the connection list, quick actions,
   quick search results, .filter-select/.filter-input, the whole searchable-select
   component and its mobile sheet, .form-hint/.form-value/.message-display/
   .form-actions, the settings rows, and the toggle switch. */

/* ============================================
   Content column
   ============================================ */

.dashboard-content {
    flex: 1;
    padding: 20px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* Legacy pages set their own bottom margins on the blocks below. The flex gap
   above is additive with those, so zero them out and let the gap do the spacing.
   .chart-controls (08) and .alert (07) carry their own bottom margin for use
   INSIDE a card, so they are zeroed here only as direct children of the column. */
.dashboard-content > .stats-grid,
.dashboard-content > .dashboard-grid,
.dashboard-content > .settings-grid,
.dashboard-content > .dashboard-card,
.dashboard-content > .chart-controls,
.dashboard-content > .alert {
    margin-bottom: 0;
}

.page-title {
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--foreground);
    margin: 0;
    white-space: nowrap;
}

/* ============================================
   Stats grid - hairline tiles

   The tiles butt together and are separated by a single border-coloured gap
   rather than floating as rounded cards. One seam instead of eight edges reads
   as a denser instrument panel, which is what a back-office needs.

   The `order` rules on .stat-label / .stat-value / .stat-footer are what put the
   muted label above the figure; StatCard emits them in that order anyway, but the
   declarations are kept so a hand-written tile cannot get the order wrong.
   ============================================ */
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
    gap: var(--hairline);
    background: var(--border);
    border: var(--hairline) solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    margin-bottom: 24px;
}

.stat-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    padding: 16px 18px;
    background: var(--card);
    transition: background var(--transition);
}

.stat-card:hover {
    background: var(--card-hover);
}

/* Drill-down variant: the docs require every aggregate to link to its filtered
   view. A tile rendered as <a class="stat-card"> gets this. */
a.stat-card {
    text-decoration: none;
    color: inherit;
}

a.stat-card:hover .stat-value {
    color: var(--primary);
}

.stat-label {
    order: 1;
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: var(--foreground-muted);
    overflow-wrap: anywhere;
}

.stat-value {
    order: 2;
    font-size: 1.6rem;
    font-weight: 600;
    line-height: 1.15;
    color: var(--foreground);
    font-variant-numeric: tabular-nums;   /* figures keep their columns while polling */
    transition: color var(--transition);
}

.stat-value.is-loading {
    color: var(--foreground-subtle);
}

/* Trend strip. Sunk to --surface so it reads as metadata about the figure
   rather than as more figures.

   StatCard emits this on EVERY tile, empty or not. The strip bleeds to the tile
   edges via negative margins, so across a row it reads as one continuous sunken
   band; a tile that omitted it punched a --card-coloured hole in that band and
   sat shorter than its neighbours, which pulled the figures off a common
   baseline. min-height holds the row when the delta inside is suppressed. */
.stat-footer {
    order: 3;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 5px;
    margin: 10px -18px -16px;
    padding: 8px 18px;
    border-top: var(--hairline) solid var(--border);
    background: var(--surface);
    font-size: 0.75rem;
    min-height: 33px;                     /* holds height when a delta is absent */
}

/* ============================================
   Delta - period-over-period change
   ============================================ */
.delta {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.delta i {
    font-size: 1rem;
    line-height: 1;
}

/* Sentiment, not direction: "disputes down 20%" is good news and renders green.
   The arrow still points the way the number moved, so the state is never carried
   by colour alone. */
/* -strong, not the fill value: this is small semibold text sitting on --surface /
   a 10% tint, where the light theme's fill inks measure ~3.6:1. See 02. */
.delta-up { color: var(--success-strong); }
.delta-down { color: var(--danger-strong); }
.delta-flat { color: var(--foreground-subtle); }

.delta-label {
    color: var(--foreground-muted);
    font-weight: 400;
}

/* Shown instead of a delta when the two sides are not like for like (a product
   started or stopped reporting between the baseline and now). Deliberately plain
   and muted: it is the absence of a figure, not a figure. */
.delta-note {
    color: var(--foreground-muted);
    font-weight: 400;
}

/* ============================================
   Cards

   Hairline panels, not floating rounded cards: 1px --border, --radius (4px),
   no shadow. The grid uses the same butt-together seam as .stats-grid so a row
   of cards reads as one instrument panel.
   ============================================ */
.dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(350px, 100%), 1fr));
    gap: 16px;
    margin-bottom: 24px;
}

.dashboard-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--card);
    border: var(--hairline) solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.dashboard-card.full-width {
    grid-column: 1 / -1;
}

.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 11px 16px;
    background: var(--surface);
    border-bottom: var(--hairline) solid var(--border);
}

.card-header h2,
.card-header h3,
.card-header h4 {
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--foreground);
    margin: 0;
}

.card-link {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--primary-strong);
    text-decoration: none;
}

.card-link:hover {
    text-decoration: underline;
}

/* Small muted caption / count shown on the right of a card header. */
.card-count {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--foreground-muted);
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.card-body {
    padding: 16px;
    flex: 1;
    min-width: 0;
}

.card-body.no-padding {
    padding: 0;
}

/* ============================================
   Empty state

   Every absent value in this portal renders an explicit empty state rather than
   a zero or a placeholder, so this class carries real weight. It must read as
   "there is nothing here", never as "something failed to load".
   ============================================ */
.empty-state {
    text-align: center;
    padding: 40px 24px;
}

.empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    margin-bottom: 12px;
    font-size: 1.1rem;
    color: var(--foreground-subtle);
    background: var(--surface);
    border: var(--hairline) solid var(--border);
    border-radius: var(--radius);
}

.empty-state p {
    color: var(--foreground-muted);
    font-size: 0.875rem;
    font-weight: 500;
    margin: 0 0 6px;
}

.empty-state p strong {
    color: var(--foreground);
}

.empty-hint {
    display: block;
    max-width: 420px;
    margin: 0 auto;
    font-size: 0.8125rem;
    color: var(--foreground-subtle);
    line-height: 1.5;
}

.empty-state.compact {
    padding: 24px;
}

.empty-state.compact .empty-icon {
    width: 32px;
    height: 32px;
    font-size: 0.95rem;
    margin-bottom: 10px;
}

.empty-state.compact p {
    font-size: 0.8125rem;
    margin-bottom: 4px;
}

/* ============================================
   Data table

   Dense rows, hairline separators, a sticky header, tabular figures.

   On the sticky header, honestly: .table-container needs overflow-x:auto for wide
   tables, and per spec that also computes overflow-y to auto, which makes the
   container the scrollport. So `position: sticky` on th pins to the container,
   not to the page - it does nothing until the container is given a height. No
   page bounds the container today, so the declaration is inert (not wrong): it
   starts working the moment one does.
   ============================================ */
.table-container {
    overflow-x: auto;
}

.data-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
}

.data-table th,
.data-table td {
    padding: 8px 14px;
    text-align: left;
    border-bottom: var(--hairline) solid var(--border);
}

.data-table th {
    position: sticky;
    top: 0;
    z-index: 2;
    font-size: 0.6875rem;
    font-weight: 600;
    color: var(--foreground-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    white-space: nowrap;
    /* Opaque, not a tint: a translucent header lets rows show through as they
       scroll under it. */
    background: var(--surface);
}

/* Tabular figures on EVERY cell, not opt-in.
   The design brief requires figures not to jitter while the page polls, and the
   densest figure grids in the portal are these tables - ~60 numeric columns, none
   of which had opted in. Proportional text columns are unaffected by the property
   (it only changes the advance width of digits), so a blanket default is safe and
   is the only version that cannot be forgotten on the next column someone adds. */
.data-table td,
.data-table th {
    font-variant-numeric: tabular-nums;
}

.data-table td {
    font-size: 0.8125rem;
    color: var(--foreground);
}

/* Aggregate row: a grand total must not read as one more product line. The border
   goes on the CELLS because .data-table is border-collapse:separate, where the spec
   requires user agents to IGNORE border properties on a <tr>. Two pages carried this
   rule on the row itself, so it had never rendered. */
.data-table .total-row td {
    border-top: 2px solid var(--border-strong);
    font-weight: 600;
}

.data-table tbody tr:last-child td {
    border-bottom: none;
}

.data-table tbody tr:not(.empty-row):hover {
    background: var(--card-hover);
}

.data-table .empty-row td {
    border-bottom: none;
    color: var(--foreground-subtle);
}

/* `.num` now only carries the alignment - the digit widths are handled for every
   cell above. Right-aligning a column is still a per-page decision. */
.data-table td.num,
.data-table th.num {
    text-align: right;
}

/* Inline row-action buttons (Edit / Archive / etc.) sit flush against each
   other by default - give adjacent buttons and links breathing room. */
.data-table td .btn + .btn,
.data-table td .btn + a.btn,
.data-table td a.btn + .btn {
    margin-left: 6px;
}

/* ============================================
   Filters bar
   ============================================ */
.filters-bar {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    align-items: flex-end;
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.filter-group label {
    font-size: 0.6875rem;
    font-weight: 600;
    color: var(--foreground-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.filter-group.search {
    flex: 1;
    min-width: 200px;
}

/* ============================================
   Page header

   Page titles live in the top app header (MainLayout), so the per-page title
   block is hidden and only the action buttons survive. A header that held ONLY
   a title collapses away entirely.
   ============================================ */
.page-header {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 12px;
}

.header-actions {
    display: flex;
    gap: 8px;
}

.page-header > div:has(> h1),
.page-header > h1,
.page-header > h2,
.page-subtitle {
    display: none;
}

.page-header:not(:has(button)):not(:has(a)):not(:has(select)):not(:has(input)) {
    display: none;
    margin: 0;
}

/* ============================================
   Forms
   ============================================ */
.settings-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr));
    gap: 16px;
    margin-bottom: 24px;
}

.form-group {
    margin-bottom: 16px;
}

.form-group:last-child {
    margin-bottom: 0;
}

.form-label {
    display: block;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--foreground-muted);
    margin-bottom: 6px;
}

.form-input,
select,
textarea.form-input {
    width: 100%;
    background: var(--surface);
    border: var(--hairline) solid var(--border-strong);
    border-radius: var(--radius);
    padding: 7px 10px;
    font-family: inherit;
    font-size: 0.875rem;
    color: var(--foreground);
    transition: border-color var(--transition);
}

/* Flat focus ring. The previous recipe painted a four-stop neon gradient into
   the border via background-clip, which cannot be themed and hid the border
   colour entirely. */
.form-input:focus,
select:focus,
textarea.form-input:focus {
    outline: 2px solid var(--ring);
    outline-offset: -1px;
    border-color: var(--ring);
}

.form-input::placeholder,
textarea::placeholder {
    color: var(--foreground-subtle);
}

/* Textareas: allow vertical resize only, and never let the resize handle drag
   the field wider than its container (default resize:both overflows layouts). */
textarea,
textarea.form-input {
    resize: vertical;
    max-width: 100%;
    min-width: 0;
}

select,
select.form-input {
    cursor: pointer;
}

/* Native <option>/<optgroup> painting is OS-controlled and only partly styleable.
   Set both themes' values from tokens and accept what the platform honours; the
   previous rules forced dark hexes with !important, which made the light theme's
   dropdown list unreadable. */
select option {
    background: var(--card);
    color: var(--foreground);
}

select optgroup {
    background: var(--surface);
    color: var(--foreground-muted);
    font-weight: 600;
    font-size: 0.75rem;
    font-style: normal;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

select optgroup option {
    color: var(--foreground);
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
}

select option:disabled {
    color: var(--foreground-subtle);
}

/* ============================================
   Status badge

   A subtle tinted pill per intent: --{intent}-subtle behind --{intent} text, with
   a matching hairline so the chip still has an edge on a tinted row. Uppercase
   plus the tint means the state is never carried by hue alone.

   A badge with NO modifier class gets the neutral chip rather than rendering as
   bare text - several pages use `<span class="status-badge">@product</span>`.
   ============================================ */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.6875rem;
    font-weight: 600;
    padding: 2px 7px;
    border-radius: var(--radius);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    background: var(--neutral-subtle);
    color: var(--foreground-muted);
    border: var(--hairline) solid transparent;
}

.status-badge.neutral,
.status-badge.disconnected {
    background: var(--neutral-subtle);
    color: var(--foreground-muted);
    border-color: var(--border);
}

.status-badge.connected,
.status-badge.success {
    background: var(--success-subtle);
    color: var(--success-strong);
    border-color: var(--success-subtle);
}

.status-badge.warning {
    background: var(--warning-subtle);
    color: var(--warning-strong);
    border-color: var(--warning-subtle);
}

.status-badge.danger {
    background: var(--danger-subtle);
    color: var(--danger-strong);
    border-color: var(--danger-subtle);
}

.status-badge.info {
    background: var(--info-subtle);
    color: var(--info-strong);
    border-color: var(--info-subtle);
}

.status-badge.primary {
    background: var(--primary-subtle);
    color: var(--primary-strong);
    border-color: var(--primary-subtle);
}

/* ============================================
   Utilities
   ============================================ */
.crm-muted {
    color: var(--foreground-muted);
    font-size: 0.8rem;
}

/* ============================================
   Mobile

   The theme row is a drawer-only affordance; declared hidden HERE, before the
   media query, because a later same-specificity rule would beat the @media one
   (media queries add no specificity).
   ============================================ */
.sidebar-theme-row {
    display: none;
}

@media (max-width: 768px) {
    .sidebar {
        transform: translateX(-104%);
        width: min(304px, 86vw);
        height: 100dvh;
        z-index: 110;
        border-right: var(--hairline) solid var(--border);
        box-shadow: var(--shadow-lg);
        padding-top: env(safe-area-inset-top);
        transition: transform 0.24s ease;
    }

    .sidebar.mobile-open {
        transform: translateX(0);
    }

    .dashboard-main {
        margin-left: 0 !important;
    }

    .sidebar-toggle {
        display: none;
    }

    .sidebar-overlay {
        display: block;
        position: fixed;
        inset: 0;
        /* --scrim, not an inverted tint: see 02. A white wash lifted the light
           theme's page to the drawer's own --surface and the two merged. */
        background: var(--scrim);
        z-index: 105;
    }

    .sidebar .nav-item {
        padding: 10px 12px;
        font-size: 0.9rem;
    }

    .sidebar-theme-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        padding: 2px 14px 10px;
    }

    .sidebar-theme-label {
        font-size: 0.8rem;
        font-weight: 600;
        color: var(--foreground-muted);
    }

    .dashboard-header .theme-toggle {
        display: none;
    }

    .stats-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .stat-card {
        padding: 12px 14px;
    }

    .stat-value {
        font-size: 1.25rem;
    }

    .stat-footer {
        margin: 8px -14px -12px;
        padding: 6px 14px;
    }

    .dashboard-grid,
    .settings-grid {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .filters-bar {
        flex-direction: column;
        align-items: stretch;
    }

    .filter-group.search {
        width: 100%;
    }

    .dashboard-header {
        padding: 0 14px;
        padding-top: env(safe-area-inset-top);
    }

    .dashboard-content {
        padding: 14px;
        gap: 14px;
    }
}

@media (max-width: 480px) {
    .dashboard-content {
        padding: 12px;
    }

    .stat-value {
        font-size: 1.2rem;
    }

    .empty-state {
        padding: 28px 16px;
    }
}
